Skip to content

Commit f29b0c0

Browse files
committed
Fix for FFI casting a struct to/from its pointer field.
FFI casting for pointer-containing structs is a pattern I'm using in my LLVM library that's currently in progress. This pattern is useful for treating different opaque pointers as specific types in Savi, and converting between them when appropriate.
1 parent f8f1944 commit f29b0c0

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)