Skip to content

Commit

Permalink
Merge pull request #488 from savi-lang/fix/struct-field-cast
Browse files Browse the repository at this point in the history
Fix for FFI casting a struct to its pointer field.
  • Loading branch information
jemc committed Aug 14, 2024
2 parents cb6cb71 + f29b0c0 commit d138939
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 8 deletions.
26 changes: 26 additions & 0 deletions spec/language/semantics/struct_spec.savi
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
:struct _StructWithFieldInitializer
:let array Array(String): []

:struct _StructWithSingleStringField
:let string String
:new(@string)

:module _FFI.Cast(A, B)
:: An FFI-only utility function for bit-casting type A to B.
::
:: This is only meant to be used for pointer types, and will
:: fail badly if either A or B is not an ABI pointer type
::
:: Obviously this utility function makes it easy to break
:: memory safety, so it should be used with great care.
::
:: Being private, It is only accessible from within the core library,
:: though other libraries can set up similar mechanisms as well,
:: provided that they are explicitly allowed by the root manifest to use FFI.
:ffi pointer(input A) B
:foreign_name savi_cast_pointer

:module StructSpec
:fun run(test MicroTest)
s_w_f_i = _StructWithFieldInitializer.new
s_w_f_i.array << "example"
test["struct with field initializer"].pass = s_w_f_i.array == ["example"]

s_w_s_s_f = _StructWithSingleStringField.new("example")
test["struct FFI cast to its one field"].pass =
_FFI.Cast(_StructWithSingleStringField, String).pointer(s_w_s_s_f) == "example"

test["struct FFI cast from its one field"].pass =
_FFI.Cast(String, _StructWithSingleStringField).pointer("example").string == "example"
37 changes: 29 additions & 8 deletions src/savi/compiler/code_gen.cr
Original file line number Diff line number Diff line change
Expand Up @@ -918,33 +918,43 @@ class Savi::Compiler::CodeGen
param_count = llvm_func.params.size
args = param_count.times.map { |i| llvm_func.params[i] }.to_a

value = gen_ffi_call(gfunc, args)
value = gen_ffi_call(gfunc, args, llvm_func.function_type.return_type)

gen_return_value(value, nil)

gen_func_end(gfunc)
end

def gen_ffi_call(gfunc, args)
def gen_ffi_call(gfunc, args, cast_to_ret_type)
llvm_ffi_func = gen_ffi_decl(gfunc)
function_type = llvm_ffi_func.function_type

# Cast arguments to their corresponding parameter types (if necessary).
cast_args = args.map_with_index { |arg, index|
if index < function_type.params_types.size
arg = gen_force_cast(arg, function_type.params_types[index])
end
arg
}

# Now call the FFI function, according to its convention.
case gfunc.calling_convention
when GenFunc::Simple
value = @builder.call(
llvm_ffi_func.function_type,
function_type,
llvm_ffi_func,
args,
cast_args,
)
value = gen_none if llvm_ffi_func.ret_type == @void
value
value = gen_force_cast(value, cast_to_ret_type)

when GenFunc::Errorable
then_block = gen_block("invoke_then")
else_block = gen_block("invoke_else")
value = @builder.invoke(
llvm_ffi_func.function_type,
function_type,
llvm_ffi_func,
args,
cast_args,
then_block,
else_block,
)
Expand Down Expand Up @@ -2197,7 +2207,7 @@ class Savi::Compiler::CodeGen
while args.size > cast_args.size
cast_args << args[cast_args.size]
end
return gen_ffi_call(is_ffi_gfunc, cast_args)
return gen_ffi_call(is_ffi_gfunc, cast_args, llvm_type_of(signature.ret))
end

@builder.call(llvm_func_type, func, cast_args)
Expand Down Expand Up @@ -4053,6 +4063,17 @@ class Savi::Compiler::CodeGen
end
end

def gen_force_cast(value : LLVM::Value, to_type : LLVM::Type)
if value.type == to_type
value
elsif value.type.kind == LLVM::Type::Kind::Struct \
|| to_type.kind == LLVM::Type::Kind::Struct
gen_struct_bit_cast(value, to_type)
else
@builder.bit_cast(value, to_type)
end
end

def gen_struct_bit_cast(value : LLVM::Value, to_type : LLVM::Type)
# LLVM doesn't allow directly casting to/from structs, so we cheat a bit
# with an alloca in between the two as a pointer that we can cast.
Expand Down

0 comments on commit d138939

Please sign in to comment.