Skip to content

Commit

Permalink
Add Crystal::Macros::TypeNode#has_inner_pointers? (#14847)
Browse files Browse the repository at this point in the history
This allows `Pointer.malloc` and `Reference#allocate` to be implemented without compiler primitives eventually, see #13589 and #13481. This might be helpful to diagnostic tools related to the GC too.
  • Loading branch information
HertzDevil authored Aug 8, 2024
1 parent e4390a3 commit 74f0093
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 6 deletions.
59 changes: 59 additions & 0 deletions spec/compiler/macro/macro_methods_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2407,6 +2407,65 @@ module Crystal
end
end
end

describe "#has_inner_pointers?" do
it "works on structs" do
assert_macro("{{x.has_inner_pointers?}}", %(false)) do |program|
klass = NonGenericClassType.new(program, program, "SomeType", program.struct)
klass.struct = true
klass.declare_instance_var("@var", program.int32)
{x: TypeNode.new(klass)}
end

assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
klass = NonGenericClassType.new(program, program, "SomeType", program.struct)
klass.struct = true
klass.declare_instance_var("@var", program.string)
{x: TypeNode.new(klass)}
end
end

it "works on references" do
assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
{x: TypeNode.new(klass)}
end
end

it "works on ReferenceStorage" do
assert_macro("{{x.has_inner_pointers?}}", %(false)) do |program|
reference_storage = GenericReferenceStorageType.new program, program, "ReferenceStorage", program.struct, ["T"]
klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
klass.declare_instance_var("@var", program.int32)
{x: TypeNode.new(reference_storage.instantiate([klass] of TypeVar))}
end

assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
reference_storage = GenericReferenceStorageType.new program, program, "ReferenceStorage", program.struct, ["T"]
klass = NonGenericClassType.new(program, program, "SomeType", program.reference)
klass.declare_instance_var("@var", program.string)
{x: TypeNode.new(reference_storage.instantiate([klass] of TypeVar))}
end
end

it "works on primitive values" do
assert_macro("{{x.has_inner_pointers?}}", %(false)) do |program|
{x: TypeNode.new(program.int32)}
end

assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
{x: TypeNode.new(program.void)}
end

assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
{x: TypeNode.new(program.pointer_of(program.int32))}
end

assert_macro("{{x.has_inner_pointers?}}", %(true)) do |program|
{x: TypeNode.new(program.proc_of(program.void))}
end
end
end
end

describe "type declaration methods" do
Expand Down
19 changes: 19 additions & 0 deletions src/compiler/crystal/macros.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2853,5 +2853,24 @@ module Crystal::Macros
# `self` is an ancestor of *other*.
def >=(other : TypeNode) : BoolLiteral
end

# Returns whether `self` contains any inner pointers.
#
# Primitive types, except `Void`, are expected to not contain inner pointers.
# `Proc` and `Pointer` contain inner pointers.
# Unions, structs and collection types (tuples, static arrays)
# have inner pointers if any of their contained types has inner pointers.
# All other types, including classes, are expected to contain inner pointers.
#
# Types that do not have inner pointers may opt to use atomic allocations,
# i.e. `GC.malloc_atomic` rather than `GC.malloc`. The compiler ensures
# that, for any type `T`:
#
# * `Pointer(T).malloc` is atomic if and only if `T` has no inner pointers;
# * `T.allocate` is atomic if and only if `T` is a reference type and
# `ReferenceStorage(T)` has no inner pointers.
# NOTE: Like `#instance_vars` this method must be called from within a method. The result may be incorrect when used in top-level code.
def has_inner_pointers? : BoolLiteral
end
end
end
2 changes: 2 additions & 0 deletions src/compiler/crystal/macros/methods.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2013,6 +2013,8 @@ module Crystal
SymbolLiteral.new("public")
end
end
when "has_inner_pointers?"
interpret_check_args { BoolLiteral.new(type.has_inner_pointers?) }
else
super
end
Expand Down
8 changes: 2 additions & 6 deletions src/primitives.cr
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,8 @@ struct Pointer(T)
# ```
#
# The implementation uses `GC.malloc` if the compiler is aware that the
# allocated type contains inner address pointers. Otherwise it uses
# `GC.malloc_atomic`. Primitive types are expected to not contain pointers,
# except `Void`. `Proc` and `Pointer` are expected to contain pointers.
# For unions, structs and collection types (tuples, static array)
# it depends on the contained types. All other types, including classes are
# expected to contain inner address pointers.
# allocated type contains inner address pointers. See
# `Crystal::Macros::TypeNode#has_inner_pointers?` for details.
#
# To override this implicit behaviour, `GC.malloc` and `GC.malloc_atomic`
# can be used directly instead.
Expand Down

0 comments on commit 74f0093

Please sign in to comment.