Commit a843be44a0

Pavel Verigo <paul.verigo@gmail.com>
2025-04-27 15:36:24
wasm-c-abi: llvm fix struct handling + reorganize
I changed to `wasm/abi.zig`, this design is certainly better than the previous one. Still there is some conflict of interest between llvm and self-hosted backend, better design will appear when abi tests will be tested with self-hosted. Resolves: #23304 Resolves: #23305
1 parent ad9cb40
Changed files (6)
src
arch
codegen
link
test
src/arch/wasm/abi.zig
@@ -13,70 +13,55 @@ const Zcu = @import("../../Zcu.zig");
 
 /// Defines how to pass a type as part of a function signature,
 /// both for parameters as well as return values.
-pub const Class = enum { direct, indirect, none };
-
-const none: [2]Class = .{ .none, .none };
-const memory: [2]Class = .{ .indirect, .none };
-const direct: [2]Class = .{ .direct, .none };
+pub const Class = union(enum) {
+    direct: Type,
+    indirect,
+};
 
 /// Classifies a given Zig type to determine how they must be passed
 /// or returned as value within a wasm function.
-/// When all elements result in `.none`, no value must be passed in or returned.
-pub fn classifyType(ty: Type, zcu: *const Zcu) [2]Class {
+pub fn classifyType(ty: Type, zcu: *const Zcu) Class {
     const ip = &zcu.intern_pool;
-    const target = zcu.getTarget();
-    if (!ty.hasRuntimeBitsIgnoreComptime(zcu)) return none;
+    assert(ty.hasRuntimeBitsIgnoreComptime(zcu));
     switch (ty.zigTypeTag(zcu)) {
+        .int, .@"enum", .error_set => return .{ .direct = ty },
+        .float => return .{ .direct = ty },
+        .bool => return .{ .direct = ty },
+        .vector => return .{ .direct = ty },
+        .array => return .indirect,
+        .optional => {
+            assert(ty.isPtrLikeOptional(zcu));
+            return .{ .direct = ty };
+        },
+        .pointer => {
+            assert(!ty.isSlice(zcu));
+            return .{ .direct = ty };
+        },
         .@"struct" => {
             const struct_type = zcu.typeToStruct(ty).?;
             if (struct_type.layout == .@"packed") {
-                if (ty.bitSize(zcu) <= 64) return direct;
-                return .{ .direct, .direct };
+                return .{ .direct = ty };
             }
             if (struct_type.field_types.len > 1) {
                 // The struct type is non-scalar.
-                return memory;
+                return .indirect;
             }
             const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[0]);
             const explicit_align = struct_type.fieldAlign(ip, 0);
             if (explicit_align != .none) {
                 if (explicit_align.compareStrict(.gt, field_ty.abiAlignment(zcu)))
-                    return memory;
+                    return .indirect;
             }
             return classifyType(field_ty, zcu);
         },
-        .int, .@"enum", .error_set => {
-            const int_bits = ty.intInfo(zcu).bits;
-            if (int_bits <= 64) return direct;
-            if (int_bits <= 128) return .{ .direct, .direct };
-            return memory;
-        },
-        .float => {
-            const float_bits = ty.floatBits(target);
-            if (float_bits <= 64) return direct;
-            if (float_bits <= 128) return .{ .direct, .direct };
-            return memory;
-        },
-        .bool => return direct,
-        .vector => return direct,
-        .array => return memory,
-        .optional => {
-            assert(ty.isPtrLikeOptional(zcu));
-            return direct;
-        },
-        .pointer => {
-            assert(!ty.isSlice(zcu));
-            return direct;
-        },
         .@"union" => {
             const union_obj = zcu.typeToUnion(ty).?;
             if (union_obj.flagsUnordered(ip).layout == .@"packed") {
-                if (ty.bitSize(zcu) <= 64) return direct;
-                return .{ .direct, .direct };
+                return .{ .direct = ty };
             }
             const layout = ty.unionGetLayout(zcu);
             assert(layout.tag_size == 0);
-            if (union_obj.field_types.len > 1) return memory;
+            if (union_obj.field_types.len > 1) return .indirect;
             const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]);
             return classifyType(first_field_ty, zcu);
         },
@@ -97,32 +82,6 @@ pub fn classifyType(ty: Type, zcu: *const Zcu) [2]Class {
     }
 }
 
-/// Returns the scalar type a given type can represent.
-/// Asserts given type can be represented as scalar, such as
-/// a struct with a single scalar field.
-pub fn scalarType(ty: Type, zcu: *Zcu) Type {
-    const ip = &zcu.intern_pool;
-    switch (ty.zigTypeTag(zcu)) {
-        .@"struct" => {
-            if (zcu.typeToPackedStruct(ty)) |packed_struct| {
-                return scalarType(Type.fromInterned(packed_struct.backingIntTypeUnordered(ip)), zcu);
-            } else {
-                assert(ty.structFieldCount(zcu) == 1);
-                return scalarType(ty.fieldType(0, zcu), zcu);
-            }
-        },
-        .@"union" => {
-            const union_obj = zcu.typeToUnion(ty).?;
-            if (union_obj.flagsUnordered(ip).layout != .@"packed") {
-                const layout = Type.getUnionLayout(union_obj, zcu);
-                if (layout.payload_size == 0 and layout.tag_size != 0) {
-                    return scalarType(ty.unionTagTypeSafety(zcu).?, zcu);
-                }
-                assert(union_obj.field_types.len == 1);
-            }
-            const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]);
-            return scalarType(first_field_ty, zcu);
-        },
-        else => return ty,
-    }
+pub fn lowerAsDoubleI64(scalar_ty: Type, zcu: *const Zcu) bool {
+    return scalar_ty.bitSize(zcu) > 64;
 }
src/arch/wasm/CodeGen.zig
@@ -1408,11 +1408,22 @@ fn resolveCallingConventionValues(
         },
         .wasm_mvp => {
             for (fn_info.param_types.get(ip)) |ty| {
-                const ty_classes = abi.classifyType(Type.fromInterned(ty), zcu);
-                for (ty_classes) |class| {
-                    if (class == .none) continue;
-                    try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } });
-                    result.local_index += 1;
+                if (!Type.fromInterned(ty).hasRuntimeBitsIgnoreComptime(zcu)) {
+                    continue;
+                }
+                switch (abi.classifyType(.fromInterned(ty), zcu)) {
+                    .direct => |scalar_ty| if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) {
+                        try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } });
+                        result.local_index += 1;
+                    } else {
+                        try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } });
+                        try args.append(.{ .local = .{ .value = result.local_index + 1, .references = 1 } });
+                        result.local_index += 2;
+                    },
+                    .indirect => {
+                        try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } });
+                        result.local_index += 1;
+                    },
                 }
             }
         },
@@ -1428,14 +1439,13 @@ pub fn firstParamSRet(
     zcu: *const Zcu,
     target: *const std.Target,
 ) bool {
+    if (!return_type.hasRuntimeBitsIgnoreComptime(zcu)) return false;
     switch (cc) {
         .@"inline" => unreachable,
         .auto => return isByRef(return_type, zcu, target),
-        .wasm_mvp => {
-            const ty_classes = abi.classifyType(return_type, zcu);
-            if (ty_classes[0] == .indirect) return true;
-            if (ty_classes[0] == .direct and ty_classes[1] == .direct) return true;
-            return false;
+        .wasm_mvp => switch (abi.classifyType(return_type, zcu)) {
+            .direct => |scalar_ty| return abi.lowerAsDoubleI64(scalar_ty, zcu),
+            .indirect => return true,
         },
         else => return false,
     }
@@ -1449,26 +1459,19 @@ fn lowerArg(cg: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WV
     }
 
     const zcu = cg.pt.zcu;
-    const ty_classes = abi.classifyType(ty, zcu);
-    assert(ty_classes[0] != .none);
-    switch (ty.zigTypeTag(zcu)) {
-        .@"struct", .@"union" => {
-            if (ty_classes[0] == .indirect) {
-                return cg.lowerToStack(value);
-            }
-            assert(ty_classes[0] == .direct);
-            const scalar_type = abi.scalarType(ty, zcu);
-            switch (value) {
-                .nav_ref, .stack_offset => _ = try cg.load(value, scalar_type, 0),
-                .dead => unreachable,
-                else => try cg.emitWValue(value),
-            }
-        },
-        .int, .float => {
-            if (ty_classes[1] == .none) {
+
+    switch (abi.classifyType(ty, zcu)) {
+        .direct => |scalar_type| if (!abi.lowerAsDoubleI64(scalar_type, zcu)) {
+            if (!isByRef(ty, zcu, cg.target)) {
                 return cg.lowerToStack(value);
+            } else {
+                switch (value) {
+                    .nav_ref, .stack_offset => _ = try cg.load(value, scalar_type, 0),
+                    .dead => unreachable,
+                    else => try cg.emitWValue(value),
+                }
             }
-            assert(ty_classes[0] == .direct and ty_classes[1] == .direct);
+        } else {
             assert(ty.abiSize(zcu) == 16);
             // in this case we have an integer or float that must be lowered as 2 i64's.
             try cg.emitWValue(value);
@@ -1476,7 +1479,7 @@ fn lowerArg(cg: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WV
             try cg.emitWValue(value);
             try cg.addMemArg(.i64_load, .{ .offset = value.offset() + 8, .alignment = 8 });
         },
-        else => return cg.lowerToStack(value),
+        .indirect => return cg.lowerToStack(value),
     }
 }
 
@@ -2142,23 +2145,16 @@ fn airRet(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     if (cg.return_value != .none) {
         try cg.store(cg.return_value, operand, ret_ty, 0);
     } else if (fn_info.cc == .wasm_mvp and ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
-        switch (ret_ty.zigTypeTag(zcu)) {
-            // Aggregate types can be lowered as a singular value
-            .@"struct", .@"union" => {
-                const scalar_type = abi.scalarType(ret_ty, zcu);
-                try cg.emitWValue(operand);
-                const opcode = buildOpcode(.{
-                    .op = .load,
-                    .width = @as(u8, @intCast(scalar_type.abiSize(zcu) * 8)),
-                    .signedness = if (scalar_type.isSignedInt(zcu)) .signed else .unsigned,
-                    .valtype1 = typeToValtype(scalar_type, zcu, cg.target),
-                });
-                try cg.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{
-                    .offset = operand.offset(),
-                    .alignment = @intCast(scalar_type.abiAlignment(zcu).toByteUnits().?),
-                });
+        switch (abi.classifyType(ret_ty, zcu)) {
+            .direct => |scalar_type| {
+                assert(!abi.lowerAsDoubleI64(scalar_type, zcu));
+                if (!isByRef(ret_ty, zcu, cg.target)) {
+                    try cg.emitWValue(operand);
+                } else {
+                    _ = try cg.load(operand, scalar_type, 0);
+                }
             },
-            else => try cg.emitWValue(operand),
+            .indirect => unreachable,
         }
     } else {
         if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu) and ret_ty.isError(zcu)) {
@@ -2284,14 +2280,24 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifie
             break :result_value .none;
         } else if (first_param_sret) {
             break :result_value sret;
-            // TODO: Make this less fragile and optimize
-        } else if (zcu.typeToFunc(fn_ty).?.cc == .wasm_mvp and ret_ty.zigTypeTag(zcu) == .@"struct" or ret_ty.zigTypeTag(zcu) == .@"union") {
-            const result_local = try cg.allocLocal(ret_ty);
-            try cg.addLocal(.local_set, result_local.local.value);
-            const scalar_type = abi.scalarType(ret_ty, zcu);
-            const result = try cg.allocStack(scalar_type);
-            try cg.store(result, result_local, scalar_type, 0);
-            break :result_value result;
+        } else if (zcu.typeToFunc(fn_ty).?.cc == .wasm_mvp) {
+            switch (abi.classifyType(ret_ty, zcu)) {
+                .direct => |scalar_type| {
+                    assert(!abi.lowerAsDoubleI64(scalar_type, zcu));
+                    if (!isByRef(ret_ty, zcu, cg.target)) {
+                        const result_local = try cg.allocLocal(ret_ty);
+                        try cg.addLocal(.local_set, result_local.local.value);
+                        break :result_value result_local;
+                    } else {
+                        const result_local = try cg.allocLocal(ret_ty);
+                        try cg.addLocal(.local_set, result_local.local.value);
+                        const result = try cg.allocStack(ret_ty);
+                        try cg.store(result, result_local, scalar_type, 0);
+                        break :result_value result;
+                    }
+                },
+                .indirect => unreachable,
+            }
         } else {
             const result_local = try cg.allocLocal(ret_ty);
             try cg.addLocal(.local_set, result_local.local.value);
@@ -2597,26 +2603,17 @@ fn airArg(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
     const cc = zcu.typeToFunc(zcu.navValue(cg.owner_nav).typeOf(zcu)).?.cc;
     const arg_ty = cg.typeOfIndex(inst);
     if (cc == .wasm_mvp) {
-        const arg_classes = abi.classifyType(arg_ty, zcu);
-        for (arg_classes) |class| {
-            if (class != .none) {
+        switch (abi.classifyType(arg_ty, zcu)) {
+            .direct => |scalar_ty| if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) {
                 cg.arg_index += 1;
-            }
-        }
-
-        // When we have an argument that's passed using more than a single parameter,
-        // we combine them into a single stack value
-        if (arg_classes[0] == .direct and arg_classes[1] == .direct) {
-            if (arg_ty.zigTypeTag(zcu) != .int and arg_ty.zigTypeTag(zcu) != .float) {
-                return cg.fail(
-                    "TODO: Implement C-ABI argument for type '{}'",
-                    .{arg_ty.fmt(pt)},
-                );
-            }
-            const result = try cg.allocStack(arg_ty);
-            try cg.store(result, arg, Type.u64, 0);
-            try cg.store(result, cg.args[arg_index + 1], Type.u64, 8);
-            return cg.finishAir(inst, result, &.{});
+            } else {
+                cg.arg_index += 2;
+                const result = try cg.allocStack(arg_ty);
+                try cg.store(result, arg, Type.u64, 0);
+                try cg.store(result, cg.args[arg_index + 1], Type.u64, 8);
+                return cg.finishAir(inst, result, &.{});
+            },
+            .indirect => cg.arg_index += 1,
         }
     } else {
         cg.arg_index += 1;
src/codegen/llvm.zig
@@ -11723,7 +11723,7 @@ fn firstParamSRet(fn_info: InternPool.Key.FuncType, zcu: *Zcu, target: std.Targe
         .x86_64_win => x86_64_abi.classifyWindows(return_type, zcu) == .memory,
         .x86_sysv, .x86_win => isByRef(return_type, zcu),
         .x86_stdcall => !isScalar(zcu, return_type),
-        .wasm_mvp => wasm_c_abi.classifyType(return_type, zcu)[0] == .indirect,
+        .wasm_mvp => wasm_c_abi.classifyType(return_type, zcu) == .indirect,
         .aarch64_aapcs,
         .aarch64_aapcs_darwin,
         .aarch64_aapcs_win,
@@ -11808,18 +11808,9 @@ fn lowerFnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) Allocator.Error!Bu
                 return o.builder.structType(.normal, types[0..types_len]);
             },
         },
-        .wasm_mvp => {
-            if (isScalar(zcu, return_type)) {
-                return o.lowerType(return_type);
-            }
-            const classes = wasm_c_abi.classifyType(return_type, zcu);
-            if (classes[0] == .indirect or classes[0] == .none) {
-                return .void;
-            }
-
-            assert(classes[0] == .direct and classes[1] == .none);
-            const scalar_type = wasm_c_abi.scalarType(return_type, zcu);
-            return o.builder.intType(@intCast(scalar_type.abiSize(zcu) * 8));
+        .wasm_mvp => switch (wasm_c_abi.classifyType(return_type, zcu)) {
+            .direct => |scalar_ty| return o.lowerType(scalar_ty),
+            .indirect => return .void,
         },
         // TODO investigate other callconvs
         else => return o.lowerType(return_type),
@@ -12073,17 +12064,28 @@ const ParamTypeIterator = struct {
                     },
                 }
             },
-            .wasm_mvp => {
-                it.zig_index += 1;
-                it.llvm_index += 1;
-                if (isScalar(zcu, ty)) {
-                    return .byval;
-                }
-                const classes = wasm_c_abi.classifyType(ty, zcu);
-                if (classes[0] == .indirect) {
+            .wasm_mvp => switch (wasm_c_abi.classifyType(ty, zcu)) {
+                .direct => |scalar_ty| {
+                    if (isScalar(zcu, ty)) {
+                        it.zig_index += 1;
+                        it.llvm_index += 1;
+                        return .byval;
+                    } else {
+                        var types_buffer: [8]Builder.Type = undefined;
+                        types_buffer[0] = try it.object.lowerType(scalar_ty);
+                        it.types_buffer = types_buffer;
+                        it.types_len = 1;
+                        it.llvm_index += 1;
+                        it.zig_index += 1;
+                        return .multiple_llvm_types;
+                    }
+                },
+                .indirect => {
+                    it.zig_index += 1;
+                    it.llvm_index += 1;
+                    it.byval_attr = true;
                     return .byref;
-                }
-                return .abi_sized_int;
+                },
             },
             // TODO investigate other callconvs
             else => {
src/link/Wasm.zig
@@ -4627,10 +4627,13 @@ fn convertZcuFnType(
         try params_buffer.append(gpa, .i32); // memory address is always a 32-bit handle
     } else if (return_type.hasRuntimeBitsIgnoreComptime(zcu)) {
         if (cc == .wasm_mvp) {
-            const res_classes = abi.classifyType(return_type, zcu);
-            assert(res_classes[0] == .direct and res_classes[1] == .none);
-            const scalar_type = abi.scalarType(return_type, zcu);
-            try returns_buffer.append(gpa, CodeGen.typeToValtype(scalar_type, zcu, target));
+            switch (abi.classifyType(return_type, zcu)) {
+                .direct => |scalar_ty| {
+                    assert(!abi.lowerAsDoubleI64(scalar_ty, zcu));
+                    try returns_buffer.append(gpa, CodeGen.typeToValtype(scalar_ty, zcu, target));
+                },
+                .indirect => unreachable,
+            }
         } else {
             try returns_buffer.append(gpa, CodeGen.typeToValtype(return_type, zcu, target));
         }
@@ -4645,18 +4648,16 @@ fn convertZcuFnType(
 
         switch (cc) {
             .wasm_mvp => {
-                const param_classes = abi.classifyType(param_type, zcu);
-                if (param_classes[1] == .none) {
-                    if (param_classes[0] == .direct) {
-                        const scalar_type = abi.scalarType(param_type, zcu);
-                        try params_buffer.append(gpa, CodeGen.typeToValtype(scalar_type, zcu, target));
-                    } else {
-                        try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target));
-                    }
-                } else {
-                    // i128/f128
-                    try params_buffer.append(gpa, .i64);
-                    try params_buffer.append(gpa, .i64);
+                switch (abi.classifyType(param_type, zcu)) {
+                    .direct => |scalar_ty| {
+                        if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) {
+                            try params_buffer.append(gpa, CodeGen.typeToValtype(scalar_ty, zcu, target));
+                        } else {
+                            try params_buffer.append(gpa, .i64);
+                            try params_buffer.append(gpa, .i64);
+                        }
+                    },
+                    .indirect => try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)),
                 }
             },
             else => try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)),
test/c_abi/cfuncs.c
@@ -227,6 +227,38 @@ void c_struct_u64_u64_8(size_t, size_t, size_t, size_t, size_t, size_t, size_t,
     assert_or_panic(s.b == 40);
 }
 
+struct Struct_f32 {
+    float a;
+};
+
+struct Struct_f32 zig_ret_struct_f32(void);
+
+void zig_struct_f32(struct Struct_f32);
+
+struct Struct_f32 c_ret_struct_f32(void) {
+    return (struct Struct_f32){ 2.5f };
+}
+
+void c_struct_f32(struct Struct_f32 s) {
+    assert_or_panic(s.a == 2.5f);
+}
+
+struct Struct_f64 {
+    double a;
+};
+
+struct Struct_f64 zig_ret_struct_f64(void);
+
+void zig_struct_f64(struct Struct_f64);
+
+struct Struct_f64 c_ret_struct_f64(void) {
+    return (struct Struct_f64){ 2.5 };
+}
+
+void c_struct_f64(struct Struct_f64 s) {
+    assert_or_panic(s.a == 2.5);
+}
+
 struct Struct_f32f32_f32 {
     struct {
         float b, c;
@@ -296,6 +328,13 @@ void c_struct_u32_union_u32_u32u32(struct Struct_u32_Union_u32_u32u32 s) {
     assert_or_panic(s.b.c.e == 3);
 }
 
+struct Struct_i32_i32 {
+    int32_t a;
+    int32_t b;
+};
+
+void zig_struct_i32_i32(struct Struct_i32_i32);
+
 struct BigStruct {
     uint64_t a;
     uint64_t b;
@@ -2674,6 +2713,18 @@ void run_c_tests(void) {
     }
 
 #if !defined(ZIG_RISCV64)
+    {
+        struct Struct_f32 s = zig_ret_struct_f32();
+        assert_or_panic(s.a == 2.5f);
+        zig_struct_f32((struct Struct_f32){ 2.5f });
+    }
+
+    {
+        struct Struct_f64 s = zig_ret_struct_f64();
+        assert_or_panic(s.a == 2.5);
+        zig_struct_f64((struct Struct_f64){ 2.5 });
+    }
+
     {
         struct Struct_f32f32_f32 s = zig_ret_struct_f32f32_f32();
         assert_or_panic(s.a.b == 1.0f);
@@ -2699,6 +2750,10 @@ void run_c_tests(void) {
         assert_or_panic(s.b.c.e == 3);
         zig_struct_u32_union_u32_u32u32(s);
     }
+    {
+        struct Struct_i32_i32 s = {1, 2};
+        zig_struct_i32_i32(s);
+    }
 #endif
 
     {
@@ -5024,6 +5079,21 @@ double complex c_cmultd(double complex a, double complex b) {
     return 1.5 + I * 13.5;
 }
 
+struct Struct_i32_i32 c_mut_struct_i32_i32(struct Struct_i32_i32 s) {
+    assert_or_panic(s.a == 1);
+    assert_or_panic(s.b == 2);
+    s.a += 100;
+    s.b += 250;
+    assert_or_panic(s.a == 101);
+    assert_or_panic(s.b == 252);
+    return s;
+}
+
+void c_struct_i32_i32(struct Struct_i32_i32 s) {
+    assert_or_panic(s.a == 1);
+    assert_or_panic(s.b == 2);
+}
+
 void c_big_struct(struct BigStruct x) {
     assert_or_panic(x.a == 1);
     assert_or_panic(x.b == 2);
test/c_abi/main.zig
@@ -13,7 +13,7 @@ const expectEqual = std.testing.expectEqual;
 const have_i128 = builtin.cpu.arch != .x86 and !builtin.cpu.arch.isArm() and
     !builtin.cpu.arch.isMIPS() and !builtin.cpu.arch.isPowerPC32();
 
-const have_f128 = builtin.cpu.arch.isX86() and !builtin.os.tag.isDarwin();
+const have_f128 = builtin.cpu.arch.isWasm() or (builtin.cpu.arch.isX86() and !builtin.os.tag.isDarwin());
 const have_f80 = builtin.cpu.arch.isX86();
 
 extern fn run_c_tests() void;
@@ -339,6 +339,56 @@ test "C ABI struct u64 u64" {
     c_struct_u64_u64_8(0, 1, 2, 3, 4, 5, 6, 7, .{ .a = 39, .b = 40 });
 }
 
+const Struct_f32 = extern struct {
+    a: f32,
+};
+
+export fn zig_ret_struct_f32() Struct_f32 {
+    return .{ .a = 2.5 };
+}
+
+export fn zig_struct_f32(s: Struct_f32) void {
+    expect(s.a == 2.5) catch @panic("test failure");
+}
+
+extern fn c_ret_struct_f32() Struct_f32;
+
+extern fn c_struct_f32(Struct_f32) void;
+
+test "C ABI struct f32" {
+    if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
+    if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest;
+
+    const s = c_ret_struct_f32();
+    try expect(s.a == 2.5);
+    c_struct_f32(.{ .a = 2.5 });
+}
+
+const Struct_f64 = extern struct {
+    a: f64,
+};
+
+export fn zig_ret_struct_f64() Struct_f64 {
+    return .{ .a = 2.5 };
+}
+
+export fn zig_struct_f64(s: Struct_f64) void {
+    expect(s.a == 2.5) catch @panic("test failure");
+}
+
+extern fn c_ret_struct_f64() Struct_f64;
+
+extern fn c_struct_f64(Struct_f64) void;
+
+test "C ABI struct f64" {
+    if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
+    if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest;
+
+    const s = c_ret_struct_f64();
+    try expect(s.a == 2.5);
+    c_struct_f64(.{ .a = 2.5 });
+}
+
 const Struct_f32f32_f32 = extern struct {
     a: extern struct { b: f32, c: f32 },
     d: f32,
@@ -434,6 +484,34 @@ test "C ABI struct{u32,union{u32,struct{u32,u32}}}" {
     c_struct_u32_union_u32_u32u32(.{ .a = 1, .b = .{ .c = .{ .d = 2, .e = 3 } } });
 }
 
+const Struct_i32_i32 = extern struct {
+    a: i32,
+    b: i32,
+};
+extern fn c_mut_struct_i32_i32(Struct_i32_i32) Struct_i32_i32;
+extern fn c_struct_i32_i32(Struct_i32_i32) void;
+
+test "C ABI struct i32 i32" {
+    if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
+    if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest;
+
+    const s: Struct_i32_i32 = .{
+        .a = 1,
+        .b = 2,
+    };
+    const mut_res = c_mut_struct_i32_i32(s);
+    try expect(s.a == 1);
+    try expect(s.b == 2);
+    try expect(mut_res.a == 101);
+    try expect(mut_res.b == 252);
+    c_struct_i32_i32(s);
+}
+
+export fn zig_struct_i32_i32(s: Struct_i32_i32) void {
+    expect(s.a == 1) catch @panic("test failure: zig_struct_i32_i32 1");
+    expect(s.b == 2) catch @panic("test failure: zig_struct_i32_i32 2");
+}
+
 const BigStruct = extern struct {
     a: u64,
     b: u64,
@@ -5591,64 +5669,56 @@ test "f80 extra struct" {
     try expect(a.b == 24);
 }
 
-comptime {
-    skip: {
-        if (builtin.target.cpu.arch.isWasm()) break :skip;
-
-        _ = struct {
-            export fn zig_f128(x: f128) f128 {
-                expect(x == 12) catch @panic("test failure");
-                return 34;
-            }
-            extern fn c_f128(f128) f128;
-            test "f128 bare" {
-                if (!have_f128) return error.SkipZigTest;
+export fn zig_f128(x: f128) f128 {
+    expect(x == 12) catch @panic("test failure");
+    return 34;
+}
+extern fn c_f128(f128) f128;
+test "f128 bare" {
+    if (!have_f128) return error.SkipZigTest;
 
-                const a = c_f128(12.34);
-                try expect(@as(f64, @floatCast(a)) == 56.78);
-            }
+    const a = c_f128(12.34);
+    try expect(@as(f64, @floatCast(a)) == 56.78);
+}
 
-            const f128_struct = extern struct {
-                a: f128,
-            };
-            export fn zig_f128_struct(a: f128_struct) f128_struct {
-                expect(a.a == 12345) catch @panic("test failure");
-                return .{ .a = 98765 };
-            }
-            extern fn c_f128_struct(f128_struct) f128_struct;
-            test "f128 struct" {
-                if (!have_f128) return error.SkipZigTest;
+const f128_struct = extern struct {
+    a: f128,
+};
+export fn zig_f128_struct(a: f128_struct) f128_struct {
+    expect(a.a == 12345) catch @panic("test failure");
+    return .{ .a = 98765 };
+}
+extern fn c_f128_struct(f128_struct) f128_struct;
+test "f128 struct" {
+    if (!have_f128) return error.SkipZigTest;
 
-                const a = c_f128_struct(.{ .a = 12.34 });
-                try expect(@as(f64, @floatCast(a.a)) == 56.78);
+    const a = c_f128_struct(.{ .a = 12.34 });
+    try expect(@as(f64, @floatCast(a.a)) == 56.78);
 
-                const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 });
-                try expect(@as(f64, @floatCast(b.a)) == 56.78);
-                try expect(@as(f64, @floatCast(b.b)) == 43.21);
-            }
+    const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 });
+    try expect(@as(f64, @floatCast(b.a)) == 56.78);
+    try expect(@as(f64, @floatCast(b.b)) == 43.21);
+}
 
-            const f128_f128_struct = extern struct {
-                a: f128,
-                b: f128,
-            };
-            export fn zig_f128_f128_struct(a: f128_f128_struct) f128_f128_struct {
-                expect(a.a == 13) catch @panic("test failure");
-                expect(a.b == 57) catch @panic("test failure");
-                return .{ .a = 24, .b = 68 };
-            }
-            extern fn c_f128_f128_struct(f128_f128_struct) f128_f128_struct;
-            test "f128 f128 struct" {
-                if (!have_f128) return error.SkipZigTest;
+const f128_f128_struct = extern struct {
+    a: f128,
+    b: f128,
+};
+export fn zig_f128_f128_struct(a: f128_f128_struct) f128_f128_struct {
+    expect(a.a == 13) catch @panic("test failure");
+    expect(a.b == 57) catch @panic("test failure");
+    return .{ .a = 24, .b = 68 };
+}
+extern fn c_f128_f128_struct(f128_f128_struct) f128_f128_struct;
+test "f128 f128 struct" {
+    if (!have_f128) return error.SkipZigTest;
 
-                const a = c_f128_struct(.{ .a = 12.34 });
-                try expect(@as(f64, @floatCast(a.a)) == 56.78);
+    const a = c_f128_struct(.{ .a = 12.34 });
+    try expect(@as(f64, @floatCast(a.a)) == 56.78);
 
-                const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 });
-                try expect(@as(f64, @floatCast(b.a)) == 56.78);
-                try expect(@as(f64, @floatCast(b.b)) == 43.21);
-            }
-        };
-    }
+    const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 });
+    try expect(@as(f64, @floatCast(b.a)) == 56.78);
+    try expect(@as(f64, @floatCast(b.b)) == 43.21);
 }
 
 // The stdcall attribute on C functions is ignored when compiled on non-x86