Skip to content

Inlining reference type constant / class variable allocation with ReferenceStorage #15929

Open
@HertzDevil

Description

@HertzDevil

Suppose we have a lazily initialized constant of a reference type:

FOO.matches?("FOO BAR") # => true

FOO = /foo \w+/i

Codegen actually produces something equivalent to the following Crystal pseudocode:

foo__const = uninitialized Regex
foo__initialized = false

def FOO
  initializer = -> : Nil do
    # regular assignment to a variable
    # allocation happens inside `Regex.new`
    foo__const = /foo \w+/i
  end
  __crystal_once(pointerof(foo__initialized), initializer.pointer)
  foo__const
end

Since FOO is a constant, reassignments should not be possible, which means instance_sizeof(typeof(FOO)) would never change. This suggests we could use ReferenceStorage to elide the allocation in foo__const:

foo__const = uninitialized ReferenceStorage(Regex)
foo__initialized = false

def FOO
  initializer = -> : Nil do
    Regex.unsafe_construct(pointerof(foo__const), _source: "foo \\w+", _options: Regex::CompileOptions::IGNORE_CASE)
  end
  __crystal_once(pointerof(foo__initialized), initializer.pointer)
  pointerof(foo__const).as(Regex)
end

Already there is a problem if we try to implement this as a compiler transformation: it has to go from the literal constructor all the way down to Regex's canonical, internal constructor. Another example using [1, 2]:

initializer = -> : Nil do
  __temp_1 = begin
    # this is derived from the inlining of `Array.unsafe_build`
    ary = ::Array(typeof(1, 2)).unsafe_construct(pointerof(foo__const), 2)
    ary.size = 2
    ary
  end
  __temp_2 = __temp_1.to_unsafe
  __temp_2[0] = 1
  __temp_2[1] = 2
  __temp_1
end

With the current compiler architecture, this is extremely difficult to implement for arbitrary initializer expressions. However, if we are willing to write our initializers explicitly, and use class variables instead of constants, then we could somewhat replicate this without compiler support:

Test.foo.matches?("FOO BAR") # => true

macro inlined_const(decl, &block)
  @@{{ decl.var }}__const = uninitialized ::ReferenceStorage({{ decl.type }})
  @@{{ decl.var }}__initialized = false

  def self.{{ decl.var }} : {{ decl.type }}
    initializer = ::Proc(::Nil).new do
      {{ block.args[0] }} = pointerof(@@{{ decl.var }}__const)
      {{ block.body }}
    end
    ::__crystal_once(pointerof(@@{{ decl.var }}__initialized), initializer.pointer)
    pointerof(@@{{ decl.var }}__const).as({{ decl.type }})
  end
end

module Test
  inlined_const foo : Regex do |ptr|
    Regex.unsafe_construct(ptr, _source: "foo \\w+", _options: Regex::CompileOptions::IGNORE_CASE)
  end
end

Would we be interested in exposing this publicly in the standard library?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions