From 331bd83f1115bf5e33b6b2d7651d67dcfe16ac2a Mon Sep 17 00:00:00 2001 From: Pavel Verigo Date: Sun, 27 Apr 2025 15:36:24 +0200 Subject: [PATCH] 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 --- src/arch/wasm/CodeGen.zig | 141 +++++++++++++++--------------- src/arch/wasm/abi.zig | 93 ++++++-------------- src/codegen/llvm.zig | 48 ++++++----- src/link/Wasm.zig | 33 ++++---- test/c_abi/cfuncs.c | 70 +++++++++++++++ test/c_abi/main.zig | 174 ++++++++++++++++++++++++++------------ 6 files changed, 329 insertions(+), 230 deletions(-) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 641347bee1..c48673d526 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1398,11 +1398,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; + }, } } }, @@ -1418,14 +1429,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, } @@ -1439,26 +1449,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) { + + 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); - 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) { - return cg.lowerToStack(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); @@ -1466,7 +1469,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), } } @@ -2125,23 +2128,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)) { @@ -2267,14 +2263,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); @@ -2547,26 +2553,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; diff --git a/src/arch/wasm/abi.zig b/src/arch/wasm/abi.zig index d7ca4cf715..a1fa812649 100644 --- a/src/arch/wasm/abi.zig +++ b/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; } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 52cd4ed0a3..91c955b4bd 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -12094,7 +12094,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, @@ -12179,18 +12179,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), @@ -12444,17 +12435,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 => { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 2c3d204c99..da722b0531 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -4617,10 +4617,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)); } @@ -4635,18 +4638,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)), diff --git a/test/c_abi/cfuncs.c b/test/c_abi/cfuncs.c index 92f95c339c..4a71ebd37c 100644 --- a/test/c_abi/cfuncs.c +++ b/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); diff --git a/test/c_abi/main.zig b/test/c_abi/main.zig index 4b42eb637b..c8bfb926fb 100644 --- a/test/c_abi/main.zig +++ b/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; +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; - _ = 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; + 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