Commit 6e89692d81

Timon Kruiper <timonkruiper@gmail.com>
2020-05-28 19:45:11
C ABI: Add C support for passing structs of floats to an extern function
Currently this does not handle returning these structs yet. Related: #1481
1 parent 3410112
Changed files (3)
src
test
stage1
src/codegen.cpp
@@ -2062,6 +2062,78 @@ static bool iter_function_params_c_abi(CodeGen *g, ZigType *fn_type, FnWalk *fn_
                 }
             }
             return true;
+        } else if (abi_class == X64CABIClass_SSE) {
+            // For now only handle structs with only floats/doubles in it.
+            if (ty->id != ZigTypeIdStruct) {
+                if (source_node != nullptr) {
+                    give_up_with_c_abi_error(g, source_node);
+                }
+                // otherwise allow codegen code to report a compile error
+                return false;
+            }
+
+            for (uint32_t i = 0; i < ty->data.structure.src_field_count; i += 1) {
+                if (ty->data.structure.fields[i]->type_entry->id != ZigTypeIdFloat) {
+                    if (source_node != nullptr) {
+                        give_up_with_c_abi_error(g, source_node);
+                    }
+                    // otherwise allow codegen code to report a compile error
+                    return false;
+                }
+            }
+
+            // The SystemV ABI says that we have to setup 1 FP register per f64.
+            // So two f32 can be passed in one f64, but 3 f32 have to be passed in 2 FP registers.
+            // To achieve this with LLVM API, we pass multiple f64 parameters to the LLVM function if
+            // the type is bigger than 8 bytes.
+
+            // Example:
+            // extern struct {
+            //      x: f32,
+            //      y: f32,
+            //      z: f32,
+            // };
+            // const ptr = (*f64)*Struct;
+            // Register 1: ptr.*
+            // Register 2: (ptr + 1).*
+
+            // One floating point register per f64 or 2 f32's
+            size_t number_of_fp_regs = (size_t)ceilf((float)ty_size / (float)8);
+
+            switch (fn_walk->id) {
+                case FnWalkIdAttrs: {
+                    fn_walk->data.attrs.gen_i += 1;
+                    break;
+                }
+                case FnWalkIdCall: {
+                    LLVMValueRef f64_ptr_to_struct = LLVMBuildBitCast(g->builder, val, LLVMPointerType(LLVMDoubleType(), 0), "");
+                    for (uint32_t i = 0; i < number_of_fp_regs; i += 1) {
+                        LLVMValueRef index = LLVMConstInt(g->builtin_types.entry_usize->llvm_type, i, false);
+                        LLVMValueRef indices[] = { index };
+                        LLVMValueRef adjusted_ptr_to_struct = LLVMBuildInBoundsGEP(g->builder, f64_ptr_to_struct, indices, 1, "");
+                        LLVMValueRef loaded = LLVMBuildLoad(g->builder, adjusted_ptr_to_struct, "");
+                        fn_walk->data.call.gen_param_values->append(loaded);
+                    }
+                    break;
+                }
+                case FnWalkIdTypes: {
+                    for (uint32_t i = 0; i < number_of_fp_regs; i += 1) {
+                        fn_walk->data.types.gen_param_types->append(get_llvm_type(g, g->builtin_types.entry_f64));
+                        fn_walk->data.types.param_di_types->append(get_llvm_di_type(g, g->builtin_types.entry_f64));
+                    }
+                    break;
+                }
+                case FnWalkIdVars:
+                case FnWalkIdInits: {
+                    // TODO: Handle exporting functions
+                    if (source_node != nullptr) {
+                        give_up_with_c_abi_error(g, source_node);
+                    }
+                    // otherwise allow codegen code to report a compile error
+                    return false;
+                }
+            }
+            return true;
         }
     }
     if (source_node != nullptr) {
test/stage1/c_abi/cfuncs.c
@@ -63,6 +63,20 @@ void zig_split_struct_ints(struct SplitStructInts);
 
 struct BigStruct zig_big_struct_both(struct BigStruct);
 
+typedef struct Vector3 {
+    float x;
+    float y;
+    float z;
+} Vector3;
+
+typedef struct Vector5 {
+    float x;
+    float y;
+    float z;
+    float w;
+    float q;
+} Vector5;
+
 void run_c_tests(void) {
     zig_u8(0xff);
     zig_u16(0xfffe);
@@ -226,3 +240,17 @@ struct BigStruct c_big_struct_both(struct BigStruct x) {
     struct BigStruct y = {10, 11, 12, 13, 14};
     return y;
 }
+
+void c_small_struct_floats(Vector3 vec) {
+    assert_or_panic(vec.x == 3.0);
+    assert_or_panic(vec.y == 6.0);
+    assert_or_panic(vec.z == 12.0);
+}
+
+void c_big_struct_floats(Vector5 vec) {
+    assert_or_panic(vec.x == 76.0);
+    assert_or_panic(vec.y == -1.0);
+    assert_or_panic(vec.z == -12.0);
+    assert_or_panic(vec.w == 69);
+    assert_or_panic(vec.q == 55);
+}
test/stage1/c_abi/main.zig
@@ -261,3 +261,37 @@ export fn zig_big_struct_both(x: BigStruct) BigStruct {
     };
     return s;
 }
+
+const Vector3 = extern struct {
+    x: f32,
+    y: f32,
+    z: f32,
+};
+extern fn c_small_struct_floats(Vector3) void;
+
+const Vector5 = extern struct {
+    x: f32,
+    y: f32,
+    z: f32,
+    w: f32,
+    q: f32,
+};
+extern fn c_big_struct_floats(Vector5) void;
+
+test "C ABI structs of floats as parameter" {
+    var v3 = Vector3{
+        .x = 3.0,
+        .y = 6.0,
+        .z = 12.0,
+    };
+    c_small_struct_floats(v3);
+
+    var v5 = Vector5{
+        .x = 76.0,
+        .y = -1.0,
+        .z = -12.0,
+        .w = 69.0,
+        .q = 55,
+    };
+    c_big_struct_floats(v5);
+}