Skip to content

Commit

Permalink
Support Slice.literal in the interpreter
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil committed Feb 27, 2025
1 parent 07552f6 commit 1f620e5
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 6 deletions.
4 changes: 2 additions & 2 deletions spec/primitives/slice_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ describe "Primitives: Slice" do
describe ".literal" do
# TODO: implement in the interpreter
{% for num in BUILTIN_NUMBER_TYPES %}
pending_interpreted {{ "creates a read-only Slice(#{num})" }} do
it {{ "creates a read-only Slice(#{num})" }} do
slice = Slice({{ num }}).literal(0, 1, 4, 9, 16, 25)
slice.should be_a(Slice({{ num }}))
slice.to_a.should eq([0, 1, 4, 9, 16, 25] of {{ num }})
slice.read_only?.should be_true
end

# TODO: these should probably return the same pointers
pending_interpreted "creates multiple literals" do
it "creates multiple literals" do
slice1 = Slice({{ num }}).literal(1, 2, 3)
slice2 = Slice({{ num }}).literal(1, 2, 3)
slice1.should eq(slice2)
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/codegen/codegen.cr
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ module Crystal
symbol_table.initializer = llvm_type(@program.string).const_array(@symbol_table_values)
end

program.const_slices.each do |info|
program.const_slices.each_value do |info|
define_slice_constant(info)
end

Expand Down
21 changes: 21 additions & 0 deletions src/compiler/crystal/interpreter/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,19 @@ class Crystal::Repl::Compiler < Crystal::Visitor
end

private def compile_pointerof_read_instance_var(obj, obj_type, name)
# Special handling for slice literals which have been expanded into
# `::Slice.new(pointerof($Slice:n.@buffer), ...)`,
# where `$Slice:n` is a `StaticArray`; we will build the literal contents
# in the context directly instead of using interpreter bytecode
if obj.is_a?(Path) && obj_type.is_a?(StaticArrayInstanceType) && name == "@buffer"
if buffer_name = obj.single_name?
if info = @context.program.const_slices[buffer_name]?
compile_pointerof_slice_literal_buffer(obj, info)
return false
end
end
end

ivar = obj_type.lookup_instance_var(name)
ivar_offset = ivar_offset(obj_type, name)
ivar_size = inner_sizeof_type(ivar)
Expand Down Expand Up @@ -2537,6 +2550,10 @@ class Crystal::Repl::Compiler < Crystal::Visitor
assign_to_temporary_and_return_pointer(obj)
end

private def compile_pointerof_slice_literal_buffer(obj : Path, info : Program::ConstSliceInfo) : Nil
put_ptr @context.const_slice_buffer(info), node: obj
end

# Assigns the object's value to a temporary
# local variable, and then produces a pointer to that local variable.
# In this way we make sure that the memory the pointer is pointing
Expand Down Expand Up @@ -3248,6 +3265,10 @@ class Crystal::Repl::Compiler < Crystal::Visitor
put_i128 value.to_i128!, node: node
end

private def put_ptr(value : Pointer, *, node : ASTNode?)
put_i64 value.address.to_i64!, node: node
end

private def put_string(value : String, *, node : ASTNode?)
cached_string = @context.program.string_pool.get(value)

Expand Down
35 changes: 35 additions & 0 deletions src/compiler/crystal/interpreter/context.cr
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ class Crystal::Repl::Context
# the proc in this Hash.
getter ffi_closure_to_compiled_def : Hash(Void*, CompiledDef)

# Cached underlying buffers for constant slices constructed via the
# `Slice.literal` compiler built-in, indexed by the internal buffer name
# (e.g. `$Slice:0`).
@const_slice_buffers = {} of String => UInt8*

def initialize(@program : Program)
@program.flags << "interpreted"

Expand Down Expand Up @@ -287,6 +292,36 @@ class Crystal::Repl::Context
end
end

def const_slice_buffer(info : Program::ConstSliceInfo) : UInt8*
@const_slice_buffers.put_if_absent(info.name) do
kind = info.element_type
element_size = kind.bytesize // 8
buffer = Pointer(UInt8).malloc(info.args.size * element_size)
ptr = buffer

info.args.each do |arg|
num = arg.as(NumberLiteral)
case kind
in .i8? then ptr.as(Int8*).value = num.value.to_i8
in .i16? then ptr.as(Int16*).value = num.value.to_i16
in .i32? then ptr.as(Int32*).value = num.value.to_i32
in .i64? then ptr.as(Int64*).value = num.value.to_i64
in .i128? then ptr.as(Int128*).value = num.value.to_i128
in .u8? then ptr.as(UInt8*).value = num.value.to_u8
in .u16? then ptr.as(UInt16*).value = num.value.to_u16
in .u32? then ptr.as(UInt32*).value = num.value.to_u32
in .u64? then ptr.as(UInt64*).value = num.value.to_u64
in .u128? then ptr.as(UInt128*).value = num.value.to_u128
in .f32? then ptr.as(Float32*).value = num.value.to_f32
in .f64? then ptr.as(Float64*).value = num.value.to_f64
end
ptr += element_size
end

buffer
end
end

def aligned_sizeof_type(node : ASTNode) : Int32
aligned_sizeof_type(node.type?)
end
Expand Down
5 changes: 3 additions & 2 deletions src/compiler/crystal/program.cr
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ module Crystal
element_type : NumberKind,
args : Array(ASTNode)

# All constant slices constructed via the `Slice.literal` primitive.
getter const_slices = [] of ConstSliceInfo
# All constant slices constructed via the `Slice.literal` compiler built-in,
# indexed by their buffers' internal names (e.g. `$Slice:0`).
getter const_slices = {} of String => ConstSliceInfo

# Here we store constants, in the
# order that they are used. They will be initialized as soon
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1607,7 +1607,7 @@ module Crystal
const_value.type = @program.static_array_of(element_type, node.args.size)
const = Const.new(@program, @program, const_name, const_value)
@program.types[const_name] = const
@program.const_slices << Program::ConstSliceInfo.new(const_name, kind, node.args)
@program.const_slices[const_name] = Program::ConstSliceInfo.new(const_name, kind, node.args)

# ::Slice.new(pointerof($Slice:n.@buffer), {{ args.size }}, read_only: true)
pointer_node = PointerOf.new(ReadInstanceVar.new(Path.new(const_name).at(node), "@buffer").at(node)).at(node)
Expand Down
1 change: 1 addition & 0 deletions src/compiler/crystal/syntax/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ module Crystal
super.downcase
end

# TODO: rename to `bit_width`
def bytesize
case self
in .i8? then 8
Expand Down

0 comments on commit 1f620e5

Please sign in to comment.