Skip to content

Commit d138939

Browse files
authored
Merge pull request #488 from savi-lang/fix/struct-field-cast
Fix for FFI casting a struct to its pointer field.
2 parents cb6cb71 + f29b0c0 commit d138939

File tree

2 files changed

+55
-8
lines changed

2 files changed

+55
-8
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,34 @@
11
:struct _StructWithFieldInitializer
22
:let array Array(String): []
33

4+
:struct _StructWithSingleStringField
5+
:let string String
6+
:new(@string)
7+
8+
:module _FFI.Cast(A, B)
9+
:: An FFI-only utility function for bit-casting type A to B.
10+
::
11+
:: This is only meant to be used for pointer types, and will
12+
:: fail badly if either A or B is not an ABI pointer type
13+
::
14+
:: Obviously this utility function makes it easy to break
15+
:: memory safety, so it should be used with great care.
16+
::
17+
:: Being private, It is only accessible from within the core library,
18+
:: though other libraries can set up similar mechanisms as well,
19+
:: provided that they are explicitly allowed by the root manifest to use FFI.
20+
:ffi pointer(input A) B
21+
:foreign_name savi_cast_pointer
22+
423
:module StructSpec
524
:fun run(test MicroTest)
625
s_w_f_i = _StructWithFieldInitializer.new
726
s_w_f_i.array << "example"
827
test["struct with field initializer"].pass = s_w_f_i.array == ["example"]
28+
29+
s_w_s_s_f = _StructWithSingleStringField.new("example")
30+
test["struct FFI cast to its one field"].pass =
31+
_FFI.Cast(_StructWithSingleStringField, String).pointer(s_w_s_s_f) == "example"
32+
33+
test["struct FFI cast from its one field"].pass =
34+
_FFI.Cast(String, _StructWithSingleStringField).pointer("example").string == "example"

src/savi/compiler/code_gen.cr

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -918,33 +918,43 @@ class Savi::Compiler::CodeGen
918918
param_count = llvm_func.params.size
919919
args = param_count.times.map { |i| llvm_func.params[i] }.to_a
920920

921-
value = gen_ffi_call(gfunc, args)
921+
value = gen_ffi_call(gfunc, args, llvm_func.function_type.return_type)
922922

923923
gen_return_value(value, nil)
924924

925925
gen_func_end(gfunc)
926926
end
927927

928-
def gen_ffi_call(gfunc, args)
928+
def gen_ffi_call(gfunc, args, cast_to_ret_type)
929929
llvm_ffi_func = gen_ffi_decl(gfunc)
930+
function_type = llvm_ffi_func.function_type
931+
932+
# Cast arguments to their corresponding parameter types (if necessary).
933+
cast_args = args.map_with_index { |arg, index|
934+
if index < function_type.params_types.size
935+
arg = gen_force_cast(arg, function_type.params_types[index])
936+
end
937+
arg
938+
}
930939

931940
# Now call the FFI function, according to its convention.
932941
case gfunc.calling_convention
933942
when GenFunc::Simple
934943
value = @builder.call(
935-
llvm_ffi_func.function_type,
944+
function_type,
936945
llvm_ffi_func,
937-
args,
946+
cast_args,
938947
)
939948
value = gen_none if llvm_ffi_func.ret_type == @void
940-
value
949+
value = gen_force_cast(value, cast_to_ret_type)
950+
941951
when GenFunc::Errorable
942952
then_block = gen_block("invoke_then")
943953
else_block = gen_block("invoke_else")
944954
value = @builder.invoke(
945-
llvm_ffi_func.function_type,
955+
function_type,
946956
llvm_ffi_func,
947-
args,
957+
cast_args,
948958
then_block,
949959
else_block,
950960
)
@@ -2197,7 +2207,7 @@ class Savi::Compiler::CodeGen
21972207
while args.size > cast_args.size
21982208
cast_args << args[cast_args.size]
21992209
end
2200-
return gen_ffi_call(is_ffi_gfunc, cast_args)
2210+
return gen_ffi_call(is_ffi_gfunc, cast_args, llvm_type_of(signature.ret))
22012211
end
22022212

22032213
@builder.call(llvm_func_type, func, cast_args)
@@ -4053,6 +4063,17 @@ class Savi::Compiler::CodeGen
40534063
end
40544064
end
40554065

4066+
def gen_force_cast(value : LLVM::Value, to_type : LLVM::Type)
4067+
if value.type == to_type
4068+
value
4069+
elsif value.type.kind == LLVM::Type::Kind::Struct \
4070+
|| to_type.kind == LLVM::Type::Kind::Struct
4071+
gen_struct_bit_cast(value, to_type)
4072+
else
4073+
@builder.bit_cast(value, to_type)
4074+
end
4075+
end
4076+
40564077
def gen_struct_bit_cast(value : LLVM::Value, to_type : LLVM::Type)
40574078
# LLVM doesn't allow directly casting to/from structs, so we cheat a bit
40584079
# with an alloca in between the two as a pointer that we can cast.

0 commit comments

Comments
 (0)