Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ReferenceStorage for manual allocation of references #14270

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
72 changes: 72 additions & 0 deletions spec/compiler/semantic/reference_storage_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
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)"
@[Primitive(:reference_storage_type)]
HertzDevil marked this conversation as resolved.
Show resolved Hide resolved
struct ReferenceStorage(T) < Value
end

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)"
@[Primitive(:reference_storage_type)]
struct ReferenceStorage(T) < Value
end

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)"
@[Primitive(:reference_storage_type)]
struct ReferenceStorage(T) < Value
end

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)"
@[Primitive(:reference_storage_type)]
struct ReferenceStorage(T) < Value
end

class Foo
end

ReferenceStorage(Foo?)
CRYSTAL
end

it "allows a different name" do
assert_type(<<-CRYSTAL) { types["Foo"].metaclass }
@[Primitive(:reference_storage_type)]
struct MyRef(U) < Value
def u
U
end
end

class Foo
end

MyRef(Foo).new.u
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
70 changes: 60 additions & 10 deletions src/compiler/crystal/semantic/top_level_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,39 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor

@last_doc : String?

# special types recognized for `@[Primitive]`
private enum PrimitiveType
ReferenceStorageType
end

def visit(node : ClassDef)
check_outside_exp node, "declare class"

scope, name, type = lookup_type_def(node)

annotations = read_annotations

special_type = nil
process_annotations(annotations) do |annotation_type, ann|
case annotation_type
when @program.primitive_annotation
if ann.args.size != 1
ann.raise "expected Primitive annotation to have one argument"
end

arg = ann.args.first
unless arg.is_a?(SymbolLiteral)
arg.raise "expected Primitive argument to be a symbol literal"
end

value = arg.value
special_type = PrimitiveType.parse?(value)
unless special_type
arg.raise "BUG: Unknown primitive type #{value.inspect}"
end
end
end

created_new_type = false

if type
Expand All @@ -70,14 +96,34 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
end
else
created_new_type = true
if type_vars = node.type_vars
type = GenericClassType.new @program, scope, name, nil, type_vars, false
type.splat_index = node.splat_index
else
type = NonGenericClassType.new @program, scope, name, nil, false
case special_type
in Nil
if type_vars = node.type_vars
type = GenericClassType.new @program, scope, name, nil, type_vars, false
type.splat_index = node.splat_index
else
type = NonGenericClassType.new @program, scope, name, nil, false
end
type.abstract = node.abstract?
type.struct = node.struct?
in .reference_storage_type?
type_vars = node.type_vars
case
when !node.struct?
node.raise "BUG: Expected ReferenceStorageType to be a struct type"
when node.abstract?
node.raise "BUG: Expected ReferenceStorageType to be a non-abstract type"
when !type_vars
node.raise "BUG: Expected ReferenceStorageType to be a generic type"
when type_vars.size != 1
node.raise "BUG: Expected ReferenceStorageType to have a single generic type parameter"
when node.splat_index
node.raise "BUG: Expected ReferenceStorageType to have no splat parameter"
end
type = GenericReferenceStorageType.new @program, scope, name, @program.value, type_vars
type.declare_instance_var("@type_id", @program.int32)
type.can_be_stored = false
end
type.abstract = node.abstract?
type.struct = node.struct?
end

type.private = true if node.visibility.private?
Expand Down Expand Up @@ -133,6 +179,10 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
if superclass.struct? && !superclass.abstract?
node.raise "can't extend non-abstract struct #{superclass}"
end

if type.is_a?(GenericReferenceStorageType) && superclass != @program.value
node.raise "BUG: Expected reference_storage_type to inherit from Value"
end
end

if created_new_type
Expand Down Expand Up @@ -375,7 +425,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor

process_def_annotations(node, annotations) do |annotation_type, ann|
if annotation_type == @program.primitive_annotation
process_primitive_annotation(node, ann)
process_def_primitive_annotation(node, ann)
end

node.add_annotation(annotation_type, ann)
Expand Down Expand Up @@ -460,7 +510,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
false
end

private def process_primitive_annotation(node, ann)
private def process_def_primitive_annotation(node, ann)
if ann.args.size != 1
ann.raise "expected Primitive annotation to have one argument"
end
Expand Down Expand Up @@ -924,7 +974,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
if annotation_type == @program.call_convention_annotation
call_convention = parse_call_convention(ann, call_convention)
elsif annotation_type == @program.primitive_annotation
process_primitive_annotation(external, ann)
process_def_primitive_annotation(external, ann)
else
ann.raise "funs can only be annotated with: NoInline, AlwaysInline, Naked, ReturnsTwice, Raises, CallConvention"
end
Expand Down
5 changes: 3 additions & 2 deletions src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4571,7 +4571,7 @@ module Crystal
raise "expecting block parameter name, not #{@token.type}", @token
end

var = Var.new(param_name).at(location)
var = Var.new(param_name).at(location).at_end(token_end_location)
HertzDevil marked this conversation as resolved.
Show resolved Hide resolved
{var, found_splat, unpack_expressions}
end

Expand Down Expand Up @@ -5852,9 +5852,10 @@ module Crystal
next_token_skip_space_or_newline

value = parse_bare_proc_type
end_location = value.end_location
skip_space

alias_node = Alias.new(name, value)
alias_node = Alias.new(name, value).at_end(end_location)
alias_node.doc = doc
alias_node
end
Expand Down
29 changes: 28 additions & 1 deletion src/compiler/crystal/types.cr
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ module Crystal
program.tuple, program.named_tuple,
program.pointer, program.static_array,
program.union, program.enum, program.proc,
PrimitiveType
PrimitiveType, GenericReferenceStorageType
false
else
true
Expand Down Expand Up @@ -2642,6 +2642,33 @@ module Crystal
end
end

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

def new_generic_instance(program, generic_type, type_vars)
type_param = self.type_vars.first
t = type_vars[type_param].type

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

ReferenceStorageType.new program, t, self, type_param
end
end

class ReferenceStorageType < GenericClassInstanceType
getter reference_type : Type

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

super(program, generic_type, program.value, {type_param => 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
55 changes: 55 additions & 0 deletions src/reference_storage.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# a `ReferenceStorage(T)` provides the minimum storage for the instance data of
HertzDevil marked this conversation as resolved.
Show resolved Hide resolved
# 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).")]
@[Primitive(:ReferenceStorageType)]
struct ReferenceStorage(T) < Value
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
Loading