authorgravatar for paul.verigo@gmail.comPavel Verigo <paul.verigo@gmail.com> 2025-04-27 15:36:24+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-05-01 18:10:36-04:00
loga843be44a0cd88d152116a11ba75d059bbb01073
tree8f424e9ac8029a632d9ed43e5df7e944465040ce
parentad9cb401124e4b2d59e49bed2736aad7813e4f1d

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

6 files changed, 329 insertions(+), 230 deletions(-)

src/arch/wasm/CodeGen.zig+69-72
...@@ -1408,11 +1408,22 @@ fn resolveCallingConventionValues(...@@ -1408,11 +1408,22 @@ fn resolveCallingConventionValues(
1408 },1408 },
1409 .wasm_mvp => {1409 .wasm_mvp => {
1410 for (fn_info.param_types.get(ip)) |ty| {1410 for (fn_info.param_types.get(ip)) |ty| {
1411 const ty_classes = abi.classifyType(Type.fromInterned(ty), zcu);1411 if (!Type.fromInterned(ty).hasRuntimeBitsIgnoreComptime(zcu)) {
1412 for (ty_classes) |class| {1412 continue;
1413 if (class == .none) continue;1413 }
1414 try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } });1414 switch (abi.classifyType(.fromInterned(ty), zcu)) {
1415 result.local_index += 1;1415 .direct => |scalar_ty| if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) {
1416 try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } });
1417 result.local_index += 1;
1418 } else {
1419 try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } });
1420 try args.append(.{ .local = .{ .value = result.local_index + 1, .references = 1 } });
1421 result.local_index += 2;
1422 },
1423 .indirect => {
1424 try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } });
1425 result.local_index += 1;
1426 },
1416 }1427 }
1417 }1428 }
1418 },1429 },
...@@ -1428,14 +1439,13 @@ pub fn firstParamSRet(...@@ -1428,14 +1439,13 @@ pub fn firstParamSRet(
1428 zcu: *const Zcu,1439 zcu: *const Zcu,
1429 target: *const std.Target,1440 target: *const std.Target,
1430) bool {1441) bool {
1442 if (!return_type.hasRuntimeBitsIgnoreComptime(zcu)) return false;
1431 switch (cc) {1443 switch (cc) {
1432 .@"inline" => unreachable,1444 .@"inline" => unreachable,
1433 .auto => return isByRef(return_type, zcu, target),1445 .auto => return isByRef(return_type, zcu, target),
1434 .wasm_mvp => {1446 .wasm_mvp => switch (abi.classifyType(return_type, zcu)) {
1435 const ty_classes = abi.classifyType(return_type, zcu);1447 .direct => |scalar_ty| return abi.lowerAsDoubleI64(scalar_ty, zcu),
1436 if (ty_classes[0] == .indirect) return true;1448 .indirect => return true,
1437 if (ty_classes[0] == .direct and ty_classes[1] == .direct) return true;
1438 return false;
1439 },1449 },
1440 else => return false,1450 else => return false,
1441 }1451 }
...@@ -1449,26 +1459,19 @@ fn lowerArg(cg: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WV...@@ -1449,26 +1459,19 @@ fn lowerArg(cg: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WV
1449 }1459 }
14501460
1451 const zcu = cg.pt.zcu;1461 const zcu = cg.pt.zcu;
1452 const ty_classes = abi.classifyType(ty, zcu);1462
1453 assert(ty_classes[0] != .none);1463 switch (abi.classifyType(ty, zcu)) {
1454 switch (ty.zigTypeTag(zcu)) {1464 .direct => |scalar_type| if (!abi.lowerAsDoubleI64(scalar_type, zcu)) {
1455 .@"struct", .@"union" => {1465 if (!isByRef(ty, zcu, cg.target)) {
1456 if (ty_classes[0] == .indirect) {
1457 return cg.lowerToStack(value);
1458 }
1459 assert(ty_classes[0] == .direct);
1460 const scalar_type = abi.scalarType(ty, zcu);
1461 switch (value) {
1462 .nav_ref, .stack_offset => _ = try cg.load(value, scalar_type, 0),
1463 .dead => unreachable,
1464 else => try cg.emitWValue(value),
1465 }
1466 },
1467 .int, .float => {
1468 if (ty_classes[1] == .none) {
1469 return cg.lowerToStack(value);1466 return cg.lowerToStack(value);
1467 } else {
1468 switch (value) {
1469 .nav_ref, .stack_offset => _ = try cg.load(value, scalar_type, 0),
1470 .dead => unreachable,
1471 else => try cg.emitWValue(value),
1472 }
1470 }1473 }
1471 assert(ty_classes[0] == .direct and ty_classes[1] == .direct);1474 } else {
1472 assert(ty.abiSize(zcu) == 16);1475 assert(ty.abiSize(zcu) == 16);
1473 // in this case we have an integer or float that must be lowered as 2 i64's.1476 // in this case we have an integer or float that must be lowered as 2 i64's.
1474 try cg.emitWValue(value);1477 try cg.emitWValue(value);
...@@ -1476,7 +1479,7 @@ fn lowerArg(cg: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WV...@@ -1476,7 +1479,7 @@ fn lowerArg(cg: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WV
1476 try cg.emitWValue(value);1479 try cg.emitWValue(value);
1477 try cg.addMemArg(.i64_load, .{ .offset = value.offset() + 8, .alignment = 8 });1480 try cg.addMemArg(.i64_load, .{ .offset = value.offset() + 8, .alignment = 8 });
1478 },1481 },
1479 else => return cg.lowerToStack(value),1482 .indirect => return cg.lowerToStack(value),
1480 }1483 }
1481}1484}
14821485
...@@ -2142,23 +2145,16 @@ fn airRet(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -2142,23 +2145,16 @@ fn airRet(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
2142 if (cg.return_value != .none) {2145 if (cg.return_value != .none) {
2143 try cg.store(cg.return_value, operand, ret_ty, 0);2146 try cg.store(cg.return_value, operand, ret_ty, 0);
2144 } else if (fn_info.cc == .wasm_mvp and ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {2147 } else if (fn_info.cc == .wasm_mvp and ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
2145 switch (ret_ty.zigTypeTag(zcu)) {2148 switch (abi.classifyType(ret_ty, zcu)) {
2146 // Aggregate types can be lowered as a singular value2149 .direct => |scalar_type| {
2147 .@"struct", .@"union" => {2150 assert(!abi.lowerAsDoubleI64(scalar_type, zcu));
2148 const scalar_type = abi.scalarType(ret_ty, zcu);2151 if (!isByRef(ret_ty, zcu, cg.target)) {
2149 try cg.emitWValue(operand);2152 try cg.emitWValue(operand);
2150 const opcode = buildOpcode(.{2153 } else {
2151 .op = .load,2154 _ = try cg.load(operand, scalar_type, 0);
2152 .width = @as(u8, @intCast(scalar_type.abiSize(zcu) * 8)),2155 }
2153 .signedness = if (scalar_type.isSignedInt(zcu)) .signed else .unsigned,
2154 .valtype1 = typeToValtype(scalar_type, zcu, cg.target),
2155 });
2156 try cg.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{
2157 .offset = operand.offset(),
2158 .alignment = @intCast(scalar_type.abiAlignment(zcu).toByteUnits().?),
2159 });
2160 },2156 },
2161 else => try cg.emitWValue(operand),2157 .indirect => unreachable,
2162 }2158 }
2163 } else {2159 } else {
2164 if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu) and ret_ty.isError(zcu)) {2160 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...@@ -2284,14 +2280,24 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifie
2284 break :result_value .none;2280 break :result_value .none;
2285 } else if (first_param_sret) {2281 } else if (first_param_sret) {
2286 break :result_value sret;2282 break :result_value sret;
2287 // TODO: Make this less fragile and optimize2283 } else if (zcu.typeToFunc(fn_ty).?.cc == .wasm_mvp) {
2288 } else if (zcu.typeToFunc(fn_ty).?.cc == .wasm_mvp and ret_ty.zigTypeTag(zcu) == .@"struct" or ret_ty.zigTypeTag(zcu) == .@"union") {2284 switch (abi.classifyType(ret_ty, zcu)) {
2289 const result_local = try cg.allocLocal(ret_ty);2285 .direct => |scalar_type| {
2290 try cg.addLocal(.local_set, result_local.local.value);2286 assert(!abi.lowerAsDoubleI64(scalar_type, zcu));
2291 const scalar_type = abi.scalarType(ret_ty, zcu);2287 if (!isByRef(ret_ty, zcu, cg.target)) {
2292 const result = try cg.allocStack(scalar_type);2288 const result_local = try cg.allocLocal(ret_ty);
2293 try cg.store(result, result_local, scalar_type, 0);2289 try cg.addLocal(.local_set, result_local.local.value);
2294 break :result_value result;2290 break :result_value result_local;
2291 } else {
2292 const result_local = try cg.allocLocal(ret_ty);
2293 try cg.addLocal(.local_set, result_local.local.value);
2294 const result = try cg.allocStack(ret_ty);
2295 try cg.store(result, result_local, scalar_type, 0);
2296 break :result_value result;
2297 }
2298 },
2299 .indirect => unreachable,
2300 }
2295 } else {2301 } else {
2296 const result_local = try cg.allocLocal(ret_ty);2302 const result_local = try cg.allocLocal(ret_ty);
2297 try cg.addLocal(.local_set, result_local.local.value);2303 try cg.addLocal(.local_set, result_local.local.value);
...@@ -2597,26 +2603,17 @@ fn airArg(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -2597,26 +2603,17 @@ fn airArg(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
2597 const cc = zcu.typeToFunc(zcu.navValue(cg.owner_nav).typeOf(zcu)).?.cc;2603 const cc = zcu.typeToFunc(zcu.navValue(cg.owner_nav).typeOf(zcu)).?.cc;
2598 const arg_ty = cg.typeOfIndex(inst);2604 const arg_ty = cg.typeOfIndex(inst);
2599 if (cc == .wasm_mvp) {2605 if (cc == .wasm_mvp) {
2600 const arg_classes = abi.classifyType(arg_ty, zcu);2606 switch (abi.classifyType(arg_ty, zcu)) {
2601 for (arg_classes) |class| {2607 .direct => |scalar_ty| if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) {
2602 if (class != .none) {
2603 cg.arg_index += 1;2608 cg.arg_index += 1;
2604 }2609 } else {
2605 }2610 cg.arg_index += 2;
26062611 const result = try cg.allocStack(arg_ty);
2607 // When we have an argument that's passed using more than a single parameter,2612 try cg.store(result, arg, Type.u64, 0);
2608 // we combine them into a single stack value2613 try cg.store(result, cg.args[arg_index + 1], Type.u64, 8);
2609 if (arg_classes[0] == .direct and arg_classes[1] == .direct) {2614 return cg.finishAir(inst, result, &.{});
2610 if (arg_ty.zigTypeTag(zcu) != .int and arg_ty.zigTypeTag(zcu) != .float) {2615 },
2611 return cg.fail(2616 .indirect => cg.arg_index += 1,
2612 "TODO: Implement C-ABI argument for type '{}'",
2613 .{arg_ty.fmt(pt)},
2614 );
2615 }
2616 const result = try cg.allocStack(arg_ty);
2617 try cg.store(result, arg, Type.u64, 0);
2618 try cg.store(result, cg.args[arg_index + 1], Type.u64, 8);
2619 return cg.finishAir(inst, result, &.{});
2620 }2617 }
2621 } else {2618 } else {
2622 cg.arg_index += 1;2619 cg.arg_index += 1;
src/arch/wasm/abi.zig+26-67
...@@ -13,70 +13,55 @@ const Zcu = @import("../../Zcu.zig");...@@ -13,70 +13,55 @@ const Zcu = @import("../../Zcu.zig");
1313
14/// Defines how to pass a type as part of a function signature,14/// Defines how to pass a type as part of a function signature,
15/// both for parameters as well as return values.15/// both for parameters as well as return values.
16pub const Class = enum { direct, indirect, none };16pub const Class = union(enum) {
1717 direct: Type,
18const none: [2]Class = .{ .none, .none };18 indirect,
19const memory: [2]Class = .{ .indirect, .none };19};
20const direct: [2]Class = .{ .direct, .none };
2120
22/// Classifies a given Zig type to determine how they must be passed21/// Classifies a given Zig type to determine how they must be passed
23/// or returned as value within a wasm function.22/// or returned as value within a wasm function.
24/// When all elements result in `.none`, no value must be passed in or returned.23pub fn classifyType(ty: Type, zcu: *const Zcu) Class {
25pub fn classifyType(ty: Type, zcu: *const Zcu) [2]Class {
26 const ip = &zcu.intern_pool;24 const ip = &zcu.intern_pool;
27 const target = zcu.getTarget();25 assert(ty.hasRuntimeBitsIgnoreComptime(zcu));
28 if (!ty.hasRuntimeBitsIgnoreComptime(zcu)) return none;
29 switch (ty.zigTypeTag(zcu)) {26 switch (ty.zigTypeTag(zcu)) {
27 .int, .@"enum", .error_set => return .{ .direct = ty },
28 .float => return .{ .direct = ty },
29 .bool => return .{ .direct = ty },
30 .vector => return .{ .direct = ty },
31 .array => return .indirect,
32 .optional => {
33 assert(ty.isPtrLikeOptional(zcu));
34 return .{ .direct = ty };
35 },
36 .pointer => {
37 assert(!ty.isSlice(zcu));
38 return .{ .direct = ty };
39 },
30 .@"struct" => {40 .@"struct" => {
31 const struct_type = zcu.typeToStruct(ty).?;41 const struct_type = zcu.typeToStruct(ty).?;
32 if (struct_type.layout == .@"packed") {42 if (struct_type.layout == .@"packed") {
33 if (ty.bitSize(zcu) <= 64) return direct;43 return .{ .direct = ty };
34 return .{ .direct, .direct };
35 }44 }
36 if (struct_type.field_types.len > 1) {45 if (struct_type.field_types.len > 1) {
37 // The struct type is non-scalar.46 // The struct type is non-scalar.
38 return memory;47 return .indirect;
39 }48 }
40 const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[0]);49 const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[0]);
41 const explicit_align = struct_type.fieldAlign(ip, 0);50 const explicit_align = struct_type.fieldAlign(ip, 0);
42 if (explicit_align != .none) {51 if (explicit_align != .none) {
43 if (explicit_align.compareStrict(.gt, field_ty.abiAlignment(zcu)))52 if (explicit_align.compareStrict(.gt, field_ty.abiAlignment(zcu)))
44 return memory;53 return .indirect;
45 }54 }
46 return classifyType(field_ty, zcu);55 return classifyType(field_ty, zcu);
47 },56 },
48 .int, .@"enum", .error_set => {
49 const int_bits = ty.intInfo(zcu).bits;
50 if (int_bits <= 64) return direct;
51 if (int_bits <= 128) return .{ .direct, .direct };
52 return memory;
53 },
54 .float => {
55 const float_bits = ty.floatBits(target);
56 if (float_bits <= 64) return direct;
57 if (float_bits <= 128) return .{ .direct, .direct };
58 return memory;
59 },
60 .bool => return direct,
61 .vector => return direct,
62 .array => return memory,
63 .optional => {
64 assert(ty.isPtrLikeOptional(zcu));
65 return direct;
66 },
67 .pointer => {
68 assert(!ty.isSlice(zcu));
69 return direct;
70 },
71 .@"union" => {57 .@"union" => {
72 const union_obj = zcu.typeToUnion(ty).?;58 const union_obj = zcu.typeToUnion(ty).?;
73 if (union_obj.flagsUnordered(ip).layout == .@"packed") {59 if (union_obj.flagsUnordered(ip).layout == .@"packed") {
74 if (ty.bitSize(zcu) <= 64) return direct;60 return .{ .direct = ty };
75 return .{ .direct, .direct };
76 }61 }
77 const layout = ty.unionGetLayout(zcu);62 const layout = ty.unionGetLayout(zcu);
78 assert(layout.tag_size == 0);63 assert(layout.tag_size == 0);
79 if (union_obj.field_types.len > 1) return memory;64 if (union_obj.field_types.len > 1) return .indirect;
80 const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]);65 const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]);
81 return classifyType(first_field_ty, zcu);66 return classifyType(first_field_ty, zcu);
82 },67 },
...@@ -97,32 +82,6 @@ pub fn classifyType(ty: Type, zcu: *const Zcu) [2]Class {...@@ -97,32 +82,6 @@ pub fn classifyType(ty: Type, zcu: *const Zcu) [2]Class {
97 }82 }
98}83}
9984
100/// Returns the scalar type a given type can represent.85pub fn lowerAsDoubleI64(scalar_ty: Type, zcu: *const Zcu) bool {
101/// Asserts given type can be represented as scalar, such as86 return scalar_ty.bitSize(zcu) > 64;
102/// a struct with a single scalar field.
103pub fn scalarType(ty: Type, zcu: *Zcu) Type {
104 const ip = &zcu.intern_pool;
105 switch (ty.zigTypeTag(zcu)) {
106 .@"struct" => {
107 if (zcu.typeToPackedStruct(ty)) |packed_struct| {
108 return scalarType(Type.fromInterned(packed_struct.backingIntTypeUnordered(ip)), zcu);
109 } else {
110 assert(ty.structFieldCount(zcu) == 1);
111 return scalarType(ty.fieldType(0, zcu), zcu);
112 }
113 },
114 .@"union" => {
115 const union_obj = zcu.typeToUnion(ty).?;
116 if (union_obj.flagsUnordered(ip).layout != .@"packed") {
117 const layout = Type.getUnionLayout(union_obj, zcu);
118 if (layout.payload_size == 0 and layout.tag_size != 0) {
119 return scalarType(ty.unionTagTypeSafety(zcu).?, zcu);
120 }
121 assert(union_obj.field_types.len == 1);
122 }
123 const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]);
124 return scalarType(first_field_ty, zcu);
125 },
126 else => return ty,
127 }
128}87}
src/codegen/llvm.zig+25-23
...@@ -11723,7 +11723,7 @@ fn firstParamSRet(fn_info: InternPool.Key.FuncType, zcu: *Zcu, target: std.Targe...@@ -11723,7 +11723,7 @@ fn firstParamSRet(fn_info: InternPool.Key.FuncType, zcu: *Zcu, target: std.Targe
11723 .x86_64_win => x86_64_abi.classifyWindows(return_type, zcu) == .memory,11723 .x86_64_win => x86_64_abi.classifyWindows(return_type, zcu) == .memory,
11724 .x86_sysv, .x86_win => isByRef(return_type, zcu),11724 .x86_sysv, .x86_win => isByRef(return_type, zcu),
11725 .x86_stdcall => !isScalar(zcu, return_type),11725 .x86_stdcall => !isScalar(zcu, return_type),
11726 .wasm_mvp => wasm_c_abi.classifyType(return_type, zcu)[0] == .indirect,11726 .wasm_mvp => wasm_c_abi.classifyType(return_type, zcu) == .indirect,
11727 .aarch64_aapcs,11727 .aarch64_aapcs,
11728 .aarch64_aapcs_darwin,11728 .aarch64_aapcs_darwin,
11729 .aarch64_aapcs_win,11729 .aarch64_aapcs_win,
...@@ -11808,18 +11808,9 @@ fn lowerFnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) Allocator.Error!Bu...@@ -11808,18 +11808,9 @@ fn lowerFnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) Allocator.Error!Bu
11808 return o.builder.structType(.normal, types[0..types_len]);11808 return o.builder.structType(.normal, types[0..types_len]);
11809 },11809 },
11810 },11810 },
11811 .wasm_mvp => {11811 .wasm_mvp => switch (wasm_c_abi.classifyType(return_type, zcu)) {
11812 if (isScalar(zcu, return_type)) {11812 .direct => |scalar_ty| return o.lowerType(scalar_ty),
11813 return o.lowerType(return_type);11813 .indirect => return .void,
11814 }
11815 const classes = wasm_c_abi.classifyType(return_type, zcu);
11816 if (classes[0] == .indirect or classes[0] == .none) {
11817 return .void;
11818 }
11819
11820 assert(classes[0] == .direct and classes[1] == .none);
11821 const scalar_type = wasm_c_abi.scalarType(return_type, zcu);
11822 return o.builder.intType(@intCast(scalar_type.abiSize(zcu) * 8));
11823 },11814 },
11824 // TODO investigate other callconvs11815 // TODO investigate other callconvs
11825 else => return o.lowerType(return_type),11816 else => return o.lowerType(return_type),
...@@ -12073,17 +12064,28 @@ const ParamTypeIterator = struct {...@@ -12073,17 +12064,28 @@ const ParamTypeIterator = struct {
12073 },12064 },
12074 }12065 }
12075 },12066 },
12076 .wasm_mvp => {12067 .wasm_mvp => switch (wasm_c_abi.classifyType(ty, zcu)) {
12077 it.zig_index += 1;12068 .direct => |scalar_ty| {
12078 it.llvm_index += 1;12069 if (isScalar(zcu, ty)) {
12079 if (isScalar(zcu, ty)) {12070 it.zig_index += 1;
12080 return .byval;12071 it.llvm_index += 1;
12081 }12072 return .byval;
12082 const classes = wasm_c_abi.classifyType(ty, zcu);12073 } else {
12083 if (classes[0] == .indirect) {12074 var types_buffer: [8]Builder.Type = undefined;
12075 types_buffer[0] = try it.object.lowerType(scalar_ty);
12076 it.types_buffer = types_buffer;
12077 it.types_len = 1;
12078 it.llvm_index += 1;
12079 it.zig_index += 1;
12080 return .multiple_llvm_types;
12081 }
12082 },
12083 .indirect => {
12084 it.zig_index += 1;
12085 it.llvm_index += 1;
12086 it.byval_attr = true;
12084 return .byref;12087 return .byref;
12085 }12088 },
12086 return .abi_sized_int;
12087 },12089 },
12088 // TODO investigate other callconvs12090 // TODO investigate other callconvs
12089 else => {12091 else => {
src/link/Wasm.zig+17-16
...@@ -4627,10 +4627,13 @@ fn convertZcuFnType(...@@ -4627,10 +4627,13 @@ fn convertZcuFnType(
4627 try params_buffer.append(gpa, .i32); // memory address is always a 32-bit handle4627 try params_buffer.append(gpa, .i32); // memory address is always a 32-bit handle
4628 } else if (return_type.hasRuntimeBitsIgnoreComptime(zcu)) {4628 } else if (return_type.hasRuntimeBitsIgnoreComptime(zcu)) {
4629 if (cc == .wasm_mvp) {4629 if (cc == .wasm_mvp) {
4630 const res_classes = abi.classifyType(return_type, zcu);4630 switch (abi.classifyType(return_type, zcu)) {
4631 assert(res_classes[0] == .direct and res_classes[1] == .none);4631 .direct => |scalar_ty| {
4632 const scalar_type = abi.scalarType(return_type, zcu);4632 assert(!abi.lowerAsDoubleI64(scalar_ty, zcu));
4633 try returns_buffer.append(gpa, CodeGen.typeToValtype(scalar_type, zcu, target));4633 try returns_buffer.append(gpa, CodeGen.typeToValtype(scalar_ty, zcu, target));
4634 },
4635 .indirect => unreachable,
4636 }
4634 } else {4637 } else {
4635 try returns_buffer.append(gpa, CodeGen.typeToValtype(return_type, zcu, target));4638 try returns_buffer.append(gpa, CodeGen.typeToValtype(return_type, zcu, target));
4636 }4639 }
...@@ -4645,18 +4648,16 @@ fn convertZcuFnType(...@@ -4645,18 +4648,16 @@ fn convertZcuFnType(
46454648
4646 switch (cc) {4649 switch (cc) {
4647 .wasm_mvp => {4650 .wasm_mvp => {
4648 const param_classes = abi.classifyType(param_type, zcu);4651 switch (abi.classifyType(param_type, zcu)) {
4649 if (param_classes[1] == .none) {4652 .direct => |scalar_ty| {
4650 if (param_classes[0] == .direct) {4653 if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) {
4651 const scalar_type = abi.scalarType(param_type, zcu);4654 try params_buffer.append(gpa, CodeGen.typeToValtype(scalar_ty, zcu, target));
4652 try params_buffer.append(gpa, CodeGen.typeToValtype(scalar_type, zcu, target));4655 } else {
4653 } else {4656 try params_buffer.append(gpa, .i64);
4654 try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target));4657 try params_buffer.append(gpa, .i64);
4655 }4658 }
4656 } else {4659 },
4657 // i128/f1284660 .indirect => try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)),
4658 try params_buffer.append(gpa, .i64);
4659 try params_buffer.append(gpa, .i64);
4660 }4661 }
4661 },4662 },
4662 else => try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)),4663 else => try params_buffer.append(gpa, CodeGen.typeToValtype(param_type, zcu, target)),
test/c_abi/cfuncs.c+70
...@@ -227,6 +227,38 @@ void c_struct_u64_u64_8(size_t, size_t, size_t, size_t, size_t, size_t, size_t,...@@ -227,6 +227,38 @@ void c_struct_u64_u64_8(size_t, size_t, size_t, size_t, size_t, size_t, size_t,
227 assert_or_panic(s.b == 40);227 assert_or_panic(s.b == 40);
228}228}
229229
230struct Struct_f32 {
231 float a;
232};
233
234struct Struct_f32 zig_ret_struct_f32(void);
235
236void zig_struct_f32(struct Struct_f32);
237
238struct Struct_f32 c_ret_struct_f32(void) {
239 return (struct Struct_f32){ 2.5f };
240}
241
242void c_struct_f32(struct Struct_f32 s) {
243 assert_or_panic(s.a == 2.5f);
244}
245
246struct Struct_f64 {
247 double a;
248};
249
250struct Struct_f64 zig_ret_struct_f64(void);
251
252void zig_struct_f64(struct Struct_f64);
253
254struct Struct_f64 c_ret_struct_f64(void) {
255 return (struct Struct_f64){ 2.5 };
256}
257
258void c_struct_f64(struct Struct_f64 s) {
259 assert_or_panic(s.a == 2.5);
260}
261
230struct Struct_f32f32_f32 {262struct Struct_f32f32_f32 {
231 struct {263 struct {
232 float b, c;264 float b, c;
...@@ -296,6 +328,13 @@ void c_struct_u32_union_u32_u32u32(struct Struct_u32_Union_u32_u32u32 s) {...@@ -296,6 +328,13 @@ void c_struct_u32_union_u32_u32u32(struct Struct_u32_Union_u32_u32u32 s) {
296 assert_or_panic(s.b.c.e == 3);328 assert_or_panic(s.b.c.e == 3);
297}329}
298330
331struct Struct_i32_i32 {
332 int32_t a;
333 int32_t b;
334};
335
336void zig_struct_i32_i32(struct Struct_i32_i32);
337
299struct BigStruct {338struct BigStruct {
300 uint64_t a;339 uint64_t a;
301 uint64_t b;340 uint64_t b;
...@@ -2674,6 +2713,18 @@ void run_c_tests(void) {...@@ -2674,6 +2713,18 @@ void run_c_tests(void) {
2674 }2713 }
26752714
2676#if !defined(ZIG_RISCV64)2715#if !defined(ZIG_RISCV64)
2716 {
2717 struct Struct_f32 s = zig_ret_struct_f32();
2718 assert_or_panic(s.a == 2.5f);
2719 zig_struct_f32((struct Struct_f32){ 2.5f });
2720 }
2721
2722 {
2723 struct Struct_f64 s = zig_ret_struct_f64();
2724 assert_or_panic(s.a == 2.5);
2725 zig_struct_f64((struct Struct_f64){ 2.5 });
2726 }
2727
2677 {2728 {
2678 struct Struct_f32f32_f32 s = zig_ret_struct_f32f32_f32();2729 struct Struct_f32f32_f32 s = zig_ret_struct_f32f32_f32();
2679 assert_or_panic(s.a.b == 1.0f);2730 assert_or_panic(s.a.b == 1.0f);
...@@ -2699,6 +2750,10 @@ void run_c_tests(void) {...@@ -2699,6 +2750,10 @@ void run_c_tests(void) {
2699 assert_or_panic(s.b.c.e == 3);2750 assert_or_panic(s.b.c.e == 3);
2700 zig_struct_u32_union_u32_u32u32(s);2751 zig_struct_u32_union_u32_u32u32(s);
2701 }2752 }
2753 {
2754 struct Struct_i32_i32 s = {1, 2};
2755 zig_struct_i32_i32(s);
2756 }
2702#endif2757#endif
27032758
2704 {2759 {
...@@ -5024,6 +5079,21 @@ double complex c_cmultd(double complex a, double complex b) {...@@ -5024,6 +5079,21 @@ double complex c_cmultd(double complex a, double complex b) {
5024 return 1.5 + I * 13.5;5079 return 1.5 + I * 13.5;
5025}5080}
50265081
5082struct Struct_i32_i32 c_mut_struct_i32_i32(struct Struct_i32_i32 s) {
5083 assert_or_panic(s.a == 1);
5084 assert_or_panic(s.b == 2);
5085 s.a += 100;
5086 s.b += 250;
5087 assert_or_panic(s.a == 101);
5088 assert_or_panic(s.b == 252);
5089 return s;
5090}
5091
5092void c_struct_i32_i32(struct Struct_i32_i32 s) {
5093 assert_or_panic(s.a == 1);
5094 assert_or_panic(s.b == 2);
5095}
5096
5027void c_big_struct(struct BigStruct x) {5097void c_big_struct(struct BigStruct x) {
5028 assert_or_panic(x.a == 1);5098 assert_or_panic(x.a == 1);
5029 assert_or_panic(x.b == 2);5099 assert_or_panic(x.b == 2);
test/c_abi/main.zig+122-52
...@@ -13,7 +13,7 @@ const expectEqual = std.testing.expectEqual;...@@ -13,7 +13,7 @@ const expectEqual = std.testing.expectEqual;
13const have_i128 = builtin.cpu.arch != .x86 and !builtin.cpu.arch.isArm() and13const have_i128 = builtin.cpu.arch != .x86 and !builtin.cpu.arch.isArm() and
14 !builtin.cpu.arch.isMIPS() and !builtin.cpu.arch.isPowerPC32();14 !builtin.cpu.arch.isMIPS() and !builtin.cpu.arch.isPowerPC32();
1515
16const have_f128 = builtin.cpu.arch.isX86() and !builtin.os.tag.isDarwin();16const have_f128 = builtin.cpu.arch.isWasm() or (builtin.cpu.arch.isX86() and !builtin.os.tag.isDarwin());
17const have_f80 = builtin.cpu.arch.isX86();17const have_f80 = builtin.cpu.arch.isX86();
1818
19extern fn run_c_tests() void;19extern fn run_c_tests() void;
...@@ -339,6 +339,56 @@ test "C ABI struct u64 u64" {...@@ -339,6 +339,56 @@ test "C ABI struct u64 u64" {
339 c_struct_u64_u64_8(0, 1, 2, 3, 4, 5, 6, 7, .{ .a = 39, .b = 40 });339 c_struct_u64_u64_8(0, 1, 2, 3, 4, 5, 6, 7, .{ .a = 39, .b = 40 });
340}340}
341341
342const Struct_f32 = extern struct {
343 a: f32,
344};
345
346export fn zig_ret_struct_f32() Struct_f32 {
347 return .{ .a = 2.5 };
348}
349
350export fn zig_struct_f32(s: Struct_f32) void {
351 expect(s.a == 2.5) catch @panic("test failure");
352}
353
354extern fn c_ret_struct_f32() Struct_f32;
355
356extern fn c_struct_f32(Struct_f32) void;
357
358test "C ABI struct f32" {
359 if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
360 if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest;
361
362 const s = c_ret_struct_f32();
363 try expect(s.a == 2.5);
364 c_struct_f32(.{ .a = 2.5 });
365}
366
367const Struct_f64 = extern struct {
368 a: f64,
369};
370
371export fn zig_ret_struct_f64() Struct_f64 {
372 return .{ .a = 2.5 };
373}
374
375export fn zig_struct_f64(s: Struct_f64) void {
376 expect(s.a == 2.5) catch @panic("test failure");
377}
378
379extern fn c_ret_struct_f64() Struct_f64;
380
381extern fn c_struct_f64(Struct_f64) void;
382
383test "C ABI struct f64" {
384 if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
385 if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest;
386
387 const s = c_ret_struct_f64();
388 try expect(s.a == 2.5);
389 c_struct_f64(.{ .a = 2.5 });
390}
391
342const Struct_f32f32_f32 = extern struct {392const Struct_f32f32_f32 = extern struct {
343 a: extern struct { b: f32, c: f32 },393 a: extern struct { b: f32, c: f32 },
344 d: f32,394 d: f32,
...@@ -434,6 +484,34 @@ test "C ABI struct{u32,union{u32,struct{u32,u32}}}" {...@@ -434,6 +484,34 @@ test "C ABI struct{u32,union{u32,struct{u32,u32}}}" {
434 c_struct_u32_union_u32_u32u32(.{ .a = 1, .b = .{ .c = .{ .d = 2, .e = 3 } } });484 c_struct_u32_union_u32_u32u32(.{ .a = 1, .b = .{ .c = .{ .d = 2, .e = 3 } } });
435}485}
436486
487const Struct_i32_i32 = extern struct {
488 a: i32,
489 b: i32,
490};
491extern fn c_mut_struct_i32_i32(Struct_i32_i32) Struct_i32_i32;
492extern fn c_struct_i32_i32(Struct_i32_i32) void;
493
494test "C ABI struct i32 i32" {
495 if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest;
496 if (builtin.cpu.arch.isPowerPC()) return error.SkipZigTest;
497
498 const s: Struct_i32_i32 = .{
499 .a = 1,
500 .b = 2,
501 };
502 const mut_res = c_mut_struct_i32_i32(s);
503 try expect(s.a == 1);
504 try expect(s.b == 2);
505 try expect(mut_res.a == 101);
506 try expect(mut_res.b == 252);
507 c_struct_i32_i32(s);
508}
509
510export fn zig_struct_i32_i32(s: Struct_i32_i32) void {
511 expect(s.a == 1) catch @panic("test failure: zig_struct_i32_i32 1");
512 expect(s.b == 2) catch @panic("test failure: zig_struct_i32_i32 2");
513}
514
437const BigStruct = extern struct {515const BigStruct = extern struct {
438 a: u64,516 a: u64,
439 b: u64,517 b: u64,
...@@ -5591,64 +5669,56 @@ test "f80 extra struct" {...@@ -5591,64 +5669,56 @@ test "f80 extra struct" {
5591 try expect(a.b == 24);5669 try expect(a.b == 24);
5592}5670}
55935671
5594comptime {5672export fn zig_f128(x: f128) f128 {
5595 skip: {5673 expect(x == 12) catch @panic("test failure");
5596 if (builtin.target.cpu.arch.isWasm()) break :skip;5674 return 34;
55975675}
5598 _ = struct {5676extern fn c_f128(f128) f128;
5599 export fn zig_f128(x: f128) f128 {5677test "f128 bare" {
5600 expect(x == 12) catch @panic("test failure");5678 if (!have_f128) return error.SkipZigTest;
5601 return 34;
5602 }
5603 extern fn c_f128(f128) f128;
5604 test "f128 bare" {
5605 if (!have_f128) return error.SkipZigTest;
56065679
5607 const a = c_f128(12.34);5680 const a = c_f128(12.34);
5608 try expect(@as(f64, @floatCast(a)) == 56.78);5681 try expect(@as(f64, @floatCast(a)) == 56.78);
5609 }5682}
56105683
5611 const f128_struct = extern struct {5684const f128_struct = extern struct {
5612 a: f128,5685 a: f128,
5613 };5686};
5614 export fn zig_f128_struct(a: f128_struct) f128_struct {5687export fn zig_f128_struct(a: f128_struct) f128_struct {
5615 expect(a.a == 12345) catch @panic("test failure");5688 expect(a.a == 12345) catch @panic("test failure");
5616 return .{ .a = 98765 };5689 return .{ .a = 98765 };
5617 }5690}
5618 extern fn c_f128_struct(f128_struct) f128_struct;5691extern fn c_f128_struct(f128_struct) f128_struct;
5619 test "f128 struct" {5692test "f128 struct" {
5620 if (!have_f128) return error.SkipZigTest;5693 if (!have_f128) return error.SkipZigTest;
56215694
5622 const a = c_f128_struct(.{ .a = 12.34 });5695 const a = c_f128_struct(.{ .a = 12.34 });
5623 try expect(@as(f64, @floatCast(a.a)) == 56.78);5696 try expect(@as(f64, @floatCast(a.a)) == 56.78);
56245697
5625 const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 });5698 const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 });
5626 try expect(@as(f64, @floatCast(b.a)) == 56.78);5699 try expect(@as(f64, @floatCast(b.a)) == 56.78);
5627 try expect(@as(f64, @floatCast(b.b)) == 43.21);5700 try expect(@as(f64, @floatCast(b.b)) == 43.21);
5628 }5701}
56295702
5630 const f128_f128_struct = extern struct {5703const f128_f128_struct = extern struct {
5631 a: f128,5704 a: f128,
5632 b: f128,5705 b: f128,
5633 };5706};
5634 export fn zig_f128_f128_struct(a: f128_f128_struct) f128_f128_struct {5707export fn zig_f128_f128_struct(a: f128_f128_struct) f128_f128_struct {
5635 expect(a.a == 13) catch @panic("test failure");5708 expect(a.a == 13) catch @panic("test failure");
5636 expect(a.b == 57) catch @panic("test failure");5709 expect(a.b == 57) catch @panic("test failure");
5637 return .{ .a = 24, .b = 68 };5710 return .{ .a = 24, .b = 68 };
5638 }5711}
5639 extern fn c_f128_f128_struct(f128_f128_struct) f128_f128_struct;5712extern fn c_f128_f128_struct(f128_f128_struct) f128_f128_struct;
5640 test "f128 f128 struct" {5713test "f128 f128 struct" {
5641 if (!have_f128) return error.SkipZigTest;5714 if (!have_f128) return error.SkipZigTest;
56425715
5643 const a = c_f128_struct(.{ .a = 12.34 });5716 const a = c_f128_struct(.{ .a = 12.34 });
5644 try expect(@as(f64, @floatCast(a.a)) == 56.78);5717 try expect(@as(f64, @floatCast(a.a)) == 56.78);
56455718
5646 const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 });5719 const b = c_f128_f128_struct(.{ .a = 12.34, .b = 87.65 });
5647 try expect(@as(f64, @floatCast(b.a)) == 56.78);5720 try expect(@as(f64, @floatCast(b.a)) == 56.78);
5648 try expect(@as(f64, @floatCast(b.b)) == 43.21);5721 try expect(@as(f64, @floatCast(b.b)) == 43.21);
5649 }
5650 };
5651 }
5652}5722}
56535723
5654// The stdcall attribute on C functions is ignored when compiled on non-x865724// The stdcall attribute on C functions is ignored when compiled on non-x86