Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions spec/compiler/semantic/reference_storage_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require "../../spec_helper"

describe "Semantic: ReferenceStorage" do
it "errors if T is a struct type" do
assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = Foo (T must be a reference type)"
struct Foo
@x = 1
end

ReferenceStorage(Foo)
CRYSTAL
end

it "errors if T is a value type" do
assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = Int32 (T must be a reference type)"
ReferenceStorage(Int32)
CRYSTAL
end

it "errors if T is a union type" do
assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = (Bar | Foo) (T must be a reference type)"
class Foo
end

class Bar
end

ReferenceStorage(Foo | Bar)
CRYSTAL
end

it "errors if T is a nilable type" do
assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = (Foo | Nil) (T must be a reference type)"
class Foo
end

ReferenceStorage(Foo?)
CRYSTAL
end
end
4 changes: 1 addition & 3 deletions spec/primitives/reference_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ describe "Primitives: reference" do
end

it "works when address is on the stack" do
# suitably aligned, sufficient storage for type ID + i64 + ptr
# TODO: use `ReferenceStorage` instead
foo_buffer = uninitialized UInt64[3]
foo_buffer = uninitialized ReferenceStorage(Foo)
foo = Foo.pre_initialize(pointerof(foo_buffer))
pointerof(foo_buffer).as(typeof(Foo.crystal_instance_type_id)*).value.should eq(Foo.crystal_instance_type_id)
foo.str.should eq("abc")
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/crystal/codegen/llvm_typer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,10 @@ module Crystal
llvm_type(type.remove_alias, wants_size)
end

private def create_llvm_type(type : ReferenceStorageType, wants_size)
llvm_struct_type(type.reference_type, wants_size)
end

private def create_llvm_type(type : NonGenericModuleType | GenericClassType, wants_size)
# This can only be reached if the module or generic class don't have implementors
@llvm_context.int1
Expand Down
6 changes: 5 additions & 1 deletion src/compiler/crystal/program.cr
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ module Crystal
types["Struct"] = struct_t = @struct_t = NonGenericClassType.new self, self, "Struct", value
abstract_value_type(struct_t)

types["ReferenceStorage"] = @reference_storage = reference_storage = GenericReferenceStorageType.new self, self, "ReferenceStorage", value, ["T"]
reference_storage.declare_instance_var("@type_id", int32)
reference_storage.can_be_stored = false

types["Enumerable"] = @enumerable = GenericModuleType.new self, self, "Enumerable", ["T"]
types["Indexable"] = @indexable = GenericModuleType.new self, self, "Indexable", ["T"]

Expand Down Expand Up @@ -493,7 +497,7 @@ module Crystal

{% for name in %w(object no_return value number reference void nil bool char int int8 int16 int32 int64 int128
uint8 uint16 uint32 uint64 uint128 float float32 float64 string symbol pointer enumerable indexable
array static_array exception tuple named_tuple proc union enum range regex crystal
array static_array reference_storage exception tuple named_tuple proc union enum range regex crystal
packed_annotation thread_local_annotation no_inline_annotation
always_inline_annotation naked_annotation returns_twice_annotation
raises_annotation primitive_annotation call_convention_annotation
Expand Down
28 changes: 27 additions & 1 deletion src/compiler/crystal/types.cr
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ module Crystal
def allows_instance_vars?
case self
when program.object, program.value, program.struct,
program.reference, program.class_type,
program.reference, program.class_type, program.reference_storage,
program.number, program.int, program.float,
program.tuple, program.named_tuple,
program.pointer, program.static_array,
Expand Down Expand Up @@ -2642,6 +2642,32 @@ module Crystal
end
end

# The non-instantiated ReferenceStorage(T) type.
class GenericReferenceStorageType < GenericClassType
@struct = true

def new_generic_instance(program, generic_type, type_vars)
t = type_vars["T"].type

unless t.is_a?(TypeParameter) || (t.class? && !t.struct?)
raise TypeException.new "Can't instantiate ReferenceStorage(T) with T = #{t} (T must be a reference type)"
end

ReferenceStorageType.new program, t
end
end

class ReferenceStorageType < GenericClassInstanceType
getter reference_type : Type

def initialize(program, @reference_type)
t_var = Var.new("T", @reference_type)
t_var.bind_to t_var

super(program, program.reference_storage, program.struct, {"T" => t_var} of String => ASTNode)
end
end

# A lib type, like `lib LibC`.
class LibType < ModuleType
getter link_annotations : Array(LinkAnnotation)?
Expand Down
1 change: 1 addition & 0 deletions src/prelude.cr
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ require "raise"
require "random"
require "range"
require "reference"
require "reference_storage"
require "regex"
require "set"
{% unless flag?(:wasm32) %}
Expand Down
54 changes: 54 additions & 0 deletions src/reference_storage.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# a `ReferenceStorage(T)` provides the minimum storage for the instance data of
# an object of type `T`. The compiler guarantees that
# `sizeof(ReferenceStorage(T)) == instance_sizeof(T)` and
# `alignof(ReferenceStorage(T)) == instance_alignof(T)` always hold, which means
# `Pointer(ReferenceStorage(T))` and `T` are binary-compatible.
#
# `T` must be a non-union reference type.
#
# WARNING: `ReferenceStorage` is only necessary for manual memory management,
# such as creating instances of `T` with a non-default allocator. Therefore,
# this type is unsafe and no public constructors are defined.
#
# WARNING: `ReferenceStorage` is unsuitable when instances of `T` require more
# than `instance_sizeof(T)` bytes, such as `String` and `Log::Metadata`.
@[Experimental("This type's API is still under development. Join the discussion about custom reference allocation at [#13481](https://github.com/crystal-lang/crystal/issues/13481).")]
struct ReferenceStorage(T)
private def initialize
end

# Returns whether `self` and *other* are bytewise equal.
#
# NOTE: This does not call `T#==`, so it works even if `self` or *other* does
# not represent a valid instance of `T`. If validity is guaranteed, call
# `to_reference == other.to_reference` instead to use `T#==`.
def ==(other : ReferenceStorage(T)) : Bool
to_bytes == other.to_bytes
end

def ==(other) : Bool
false
end

def hash(hasher)
to_bytes.hash(hasher)
end

def to_s(io : IO) : Nil
io << "ReferenceStorage(#<" << T << ":0x"
pointerof(@type_id).address.to_s(io, 16)
io << ">)"
end

# Returns a `T` whose instance data refers to `self`.
#
# WARNING: The caller is responsible for ensuring that the instance data is
# correctly initialized and outlives the returned `T`.
def to_reference : T
pointerof(@type_id).as(T)
end

protected def to_bytes
Slice.new(pointerof(@type_id).as(UInt8*), instance_sizeof(T))
end
end