From f29b0c087c6d167196b061dae78da66701c46753 Mon Sep 17 00:00:00 2001 From: Joe Eli McIlvain Date: Tue, 13 Aug 2024 17:37:36 -0700 Subject: [PATCH] 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. --- spec/language/semantics/struct_spec.savi | 26 +++++++++++++++++ src/savi/compiler/code_gen.cr | 37 +++++++++++++++++++----- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/spec/language/semantics/struct_spec.savi b/spec/language/semantics/struct_spec.savi index 0190d3bf..9759478b 100644 --- a/spec/language/semantics/struct_spec.savi +++ b/spec/language/semantics/struct_spec.savi @@ -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" diff --git a/src/savi/compiler/code_gen.cr b/src/savi/compiler/code_gen.cr index 91a7ea55..55f45f06 100644 --- a/src/savi/compiler/code_gen.cr +++ b/src/savi/compiler/code_gen.cr @@ -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, ) @@ -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) @@ -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.