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 UntypedPointer as a superclass of Pointer #10749

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions spec/compiler/codegen/pointer_spec.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
require "../../spec_helper"

describe "Code gen: untyped pointer" do
it "codegens malloc" do
run("UntypedPointer.malloc(10_u64)")
end

it "codegens realloc" do
run(%(
p = UntypedPointer.malloc(10_u64)
(p.as(UInt8*) + 9_i64).value = 1
x = p.realloc(20_u64)
(x.as(UInt8*) + 9_i64).value &+ 1_i64
)).to_i.should eq(2)
end
end

describe "Code gen: pointer" do
it "get pointer and value of it" do
run("a = 1; b = pointerof(a); b.value").to_i.should eq(1)
Expand Down
4 changes: 2 additions & 2 deletions spec/compiler/semantic/if_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ describe "Semantic: if" do
assert_type(%(
def foo
x = 1
y = false || pointerof(x) || nil
y = false || pointerof(x) || UntypedPointer.new(0_u64) || nil

if !y
return y
Expand All @@ -436,7 +436,7 @@ describe "Semantic: if" do
end

foo
)) { nilable union_of bool, pointer_of(int32), int32 }
)) { union_of([nil_type, bool, pointer_of(int32), untyped_pointer, int32]) }
end

it "doesn't fail on new variables inside typeof condition" do
Expand Down
36 changes: 35 additions & 1 deletion spec/compiler/semantic/pointer_spec.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
require "../../spec_helper"

describe "Semantic: untyped pointer" do
it "types UntypedPointer.new" do
assert_type("UntypedPointer.new(10_u64)") { untyped_pointer }
end

it "types UntypedPointer.malloc" do
assert_type("UntypedPointer.malloc(10_u64)") { untyped_pointer }
end

it "types UntypedPointer casting to pointer" do
assert_type("UntypedPointer.new(10_u64).as(Char*)") { pointer_of(char) }
end

it "types UntypedPointer casting to pointer of void" do
assert_type("UntypedPointer.new(10_u64).as(Void*)") { pointer_of(void) }
end

it "types UntypedPointer casting to object type" do
assert_type("UntypedPointer.new(10_u64).as(String)") { string }
end

it "types pointer casting to UntypedPointer" do
assert_type("Pointer(Int32).new(10_u64).as(UntypedPointer)") { untyped_pointer }
end
end

describe "Semantic: pointer" do
it "types int pointer" do
assert_type("a = 1; pointerof(a)") { pointer_of(int32) }
Expand Down Expand Up @@ -48,7 +74,7 @@ describe "Semantic: pointer" do
it "can't do Pointer.malloc without type var" do
assert_error "
Pointer.malloc(1_u64)
", "can't malloc pointer without type, use Pointer(Type).malloc(size)"
", "can't infer the type parameter T for Pointer(T), use UntypedPointer.malloc(size) or Pointer(Type).malloc(size) instead"
end

it "create pointer by address" do
Expand Down Expand Up @@ -98,6 +124,14 @@ describe "Semantic: pointer" do
"recursive pointerof expansion"
end

it "types union of pointers" do
assert_type(%(
x = 1
y = 'a'
true ? pointerof(x) : pointerof(y)
)) { union_of pointer_of(int32), pointer_of(char) }
end

it "can assign nil to void pointer" do
assert_type(%(
ptr = Pointer(Void).malloc(1_u64)
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/crystal/codegen/cast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,11 @@ class Crystal::CodeGenVisitor
bit_cast value, llvm_context.void_pointer
end

def downcast_distinct(value, to_type : PointerInstanceType, from_type : UntypedPointerType)
# cast of a pointer being cast to Void*
bit_cast value, llvm_context.void_pointer
end

def downcast_distinct(value, to_type : ReferenceUnionType, from_type : ReferenceUnionType)
value
end
Expand Down
23 changes: 12 additions & 11 deletions src/compiler/crystal/codegen/codegen.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2032,15 +2032,22 @@ module Crystal
generic_malloc(type) { crystal_malloc_atomic_fun }
end

def generic_malloc(type)
size = type.size
# In order for `UntypedPointer.malloc(...).as(ReferenceType)` to consider
# potential inner pointers as such, this cannot use `malloc_atomic`.
def malloc_untyped(size)
malloc_untyped(size) { crystal_malloc_fun }
end

def malloc_untyped(size)
if malloc_fun = yield
pointer = call malloc_fun, size
call malloc_fun, size
else
pointer = call_c_malloc size
call_c_malloc size
end
end

def generic_malloc(type)
pointer = malloc_untyped(type.size) { yield }
bit_cast pointer, type.pointer
end

Expand All @@ -2054,13 +2061,7 @@ module Crystal

def generic_array_malloc(type, count)
size = builder.mul type.size, count

if malloc_fun = yield
pointer = call malloc_fun, size
else
pointer = call_c_malloc size
end

pointer = malloc_untyped(size) { yield }
memset pointer, int8(0), size
bit_cast pointer, type.pointer
end
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/crystal/codegen/cond.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Crystal::CodeGenVisitor
codegen_cond type.typedef
end

private def codegen_cond_impl(type : NilableType | NilableReferenceUnionType | PointerInstanceType)
private def codegen_cond_impl(type : NilableType | NilableReferenceUnionType | PointerInstanceType | UntypedPointerType)
not_null_pointer? @last
end

Expand All @@ -31,7 +31,7 @@ class Crystal::CodeGenVisitor

has_nil = union_types.any? &.nil_type?
has_bool = union_types.any? &.bool_type?
has_pointer = union_types.any? &.is_a?(PointerInstanceType)
has_pointer = union_types.any? &.pointer?

cond = llvm_true

Expand All @@ -51,7 +51,7 @@ class Crystal::CodeGenVisitor

if has_pointer
union_types.each do |union_type|
next unless union_type.is_a?(PointerInstanceType)
next unless union_type.pointer?

is_pointer = equal? type_id, type_id(union_type)
pointer_value = load(bit_cast value_ptr, llvm_type(union_type).pointer)
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 @@ -164,6 +164,10 @@ module Crystal
@llvm_context.int32
end

private def create_llvm_type(type : UntypedPointerType, wants_size)
@llvm_context.void_pointer
end

private def create_llvm_type(type : PointerInstanceType, wants_size)
if wants_size
return @llvm_context.void_pointer
Expand Down
36 changes: 24 additions & 12 deletions src/compiler/crystal/codegen/primitives.cr
Original file line number Diff line number Diff line change
Expand Up @@ -722,18 +722,22 @@ class Crystal::CodeGenVisitor
end

def codegen_primitive_pointer_malloc(node, target_def, call_args)
type = node.type.as(PointerInstanceType)
llvm_type = llvm_embedded_type(type.element_type)

old_debug_location = @current_debug_location
if @debug.line_numbers? && (location = node.location)
set_current_debug_location(node.location)
end

if type.element_type.has_inner_pointers?
last = array_malloc(llvm_type, call_args[1])
if node.type.is_a?(UntypedPointerType)
last = malloc_untyped(call_args[0])
else
last = array_malloc_atomic(llvm_type, call_args[1])
type = node.type.as(PointerInstanceType)
llvm_type = llvm_embedded_type(type.element_type)

if type.element_type.has_inner_pointers?
last = array_malloc(llvm_type, call_args[1])
else
last = array_malloc_atomic(llvm_type, call_args[1])
end
end

if @debug.line_numbers?
Expand Down Expand Up @@ -764,16 +768,24 @@ class Crystal::CodeGenVisitor
end

def codegen_primitive_pointer_new(node, target_def, call_args)
int2ptr(call_args[1], llvm_type(node.type))
if node.type.is_a?(UntypedPointerType)
int2ptr call_args[0], llvm_context.void_pointer
else
int2ptr call_args[1], llvm_type(node.type)
end
end

def codegen_primitive_pointer_realloc(node, target_def, call_args)
type = context.type.as(PointerInstanceType)
if context.type.is_a?(UntypedPointerType)
realloc call_args[0], call_args[1]
else
type = context.type.as(PointerInstanceType)

casted_ptr = cast_to_void_pointer(call_args[0])
size = builder.mul call_args[1], llvm_size(type.element_type)
reallocated_ptr = realloc casted_ptr, size
cast_to_pointer reallocated_ptr, type.element_type
casted_ptr = cast_to_void_pointer(call_args[0])
size = builder.mul call_args[1], llvm_size(type.element_type)
reallocated_ptr = realloc casted_ptr, size
cast_to_pointer reallocated_ptr, type.element_type
end
end

def codegen_primitive_pointer_add(node, target_def, call_args)
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/crystal/codegen/types.cr
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module Crystal
# In the codegen phase these types are passed as byval pointers.
def passed_by_value?
case self
when PrimitiveType, PointerInstanceType, ProcInstanceType
when PrimitiveType, PointerInstanceType, UntypedPointerType, ProcInstanceType
false
when TupleInstanceType, NamedTupleInstanceType, MixedUnionType
true
Expand Down Expand Up @@ -52,6 +52,8 @@ module Crystal
# Pointer(Void).malloc(...).as(ReferenceType)
# will consider potential inner pointers as such.
true
when UntypedPointerType
true
when PointerInstanceType
true
when ProcInstanceType
Expand Down
8 changes: 6 additions & 2 deletions src/compiler/crystal/program.cr
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,11 @@ module Crystal
types["Float64"] = @float64 = FloatType.new self, self, "Float64", float, 8, 10

types["Symbol"] = @symbol = SymbolType.new self, self, "Symbol", value, 4
types["Pointer"] = pointer = @pointer = PointerType.new self, self, "Pointer", value, ["T"]

types["UntypedPointer"] = untyped_pointer = @untyped_pointer = UntypedPointerType.new self, self, "UntypedPointer", value, 8 # TODO: don't hard-code this
untyped_pointer.struct = true

types["Pointer"] = pointer = @pointer = PointerType.new self, self, "Pointer", untyped_pointer, ["T"]
pointer.struct = true
pointer.can_be_stored = false

Expand Down Expand Up @@ -453,7 +457,7 @@ module Crystal
end

{% 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 array static_array
uint8 uint16 uint32 uint64 uint128 float float32 float64 string symbol untyped_pointer pointer array static_array
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
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1852,7 +1852,7 @@ module Crystal
end

obj_type = node.obj.type?
if obj_type.is_a?(PointerInstanceType)
if obj_type.is_a?(PointerInstanceType) || obj_type.is_a?(UntypedPointerType)
to_type = node.to.type.instance_type
if to_type.is_a?(GenericType)
node.raise "can't cast #{obj_type} to #{to_type}"
Expand Down Expand Up @@ -2393,7 +2393,7 @@ module Crystal

def visit_pointer_malloc(node)
if scope.instance_type.is_a?(GenericClassType)
node.raise "can't malloc pointer without type, use Pointer(Type).malloc(size)"
node.raise "can't infer the type parameter T for Pointer(T), use UntypedPointer.malloc(size) or Pointer(Type).malloc(size) instead"
end

node.type = scope.instance_type
Expand All @@ -2416,7 +2416,7 @@ module Crystal

def visit_pointer_new(node)
if scope.instance_type.is_a?(GenericClassType)
node.raise "can't create pointer without type, use Pointer(Type).new(address)"
node.raise "can't infer the type parameter T for Pointer(T), use UntypedPointer.new(address) or Pointer(Type).new(address) instead"
end

node.type = scope.instance_type
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/semantic/recursive_struct_checker.cr
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ class Crystal::RecursiveStructChecker
end

def struct?(type)
type.struct? && type.is_a?(InstanceVarContainer) && !type.is_a?(PrimitiveType) && !type.is_a?(ProcInstanceType) && !type.abstract?
type.struct? && type.is_a?(InstanceVarContainer) && !type.is_a?(PrimitiveType) && !type.pointer? && !type.is_a?(ProcInstanceType) && !type.abstract?
end

def push(path, type)
Expand Down
13 changes: 11 additions & 2 deletions src/compiler/crystal/types.cr
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ module Crystal
end

def pointer?
self.is_a?(PointerInstanceType)
self.is_a?(PointerInstanceType) || self.is_a?(UntypedPointerType)
end

def nil_type?
Expand Down Expand Up @@ -549,7 +549,8 @@ module Crystal
case self
when program.object, program.value, program.struct,
program.number, program.int, program.float,
PrimitiveType, program.reference
PrimitiveType, program.reference,
program.pointer, program.untyped_pointer
false
else
true
Expand Down Expand Up @@ -1358,6 +1359,9 @@ module Crystal
class NilType < PrimitiveType
end

class UntypedPointerType < PrimitiveType
end

class NoReturnType < NamedType
# NoReturn can be assigned to any other type (because it never will)
def implements?(other_type)
Expand Down Expand Up @@ -2160,6 +2164,11 @@ module Crystal
def type_desc
"generic struct"
end

def all_instance_vars
# don't use superclass (UntypedPointer)
instance_vars
end
end

# An instantiated pointer type, like Pointer(Int32).
Expand Down