Skip to content

Commit a843be4

Browse files
pavelverigoandrewrk
authored andcommitted
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 commit a843be4

File tree

6 files changed

+329
-230
lines changed

6 files changed

+329
-230
lines changed

src/arch/wasm/CodeGen.zig

+69-72
Original file line numberDiff line numberDiff line change
@@ -1408,11 +1408,22 @@ fn resolveCallingConventionValues(
14081408
},
14091409
.wasm_mvp => {
14101410
for (fn_info.param_types.get(ip)) |ty| {
1411-
const ty_classes = abi.classifyType(Type.fromInterned(ty), zcu);
1412-
for (ty_classes) |class| {
1413-
if (class == .none) continue;
1414-
try args.append(.{ .local = .{ .value = result.local_index, .references = 1 } });
1415-
result.local_index += 1;
1411+
if (!Type.fromInterned(ty).hasRuntimeBitsIgnoreComptime(zcu)) {
1412+
continue;
1413+
}
1414+
switch (abi.classifyType(.fromInterned(ty), zcu)) {
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+
},
14161427
}
14171428
}
14181429
},
@@ -1428,14 +1439,13 @@ pub fn firstParamSRet(
14281439
zcu: *const Zcu,
14291440
target: *const std.Target,
14301441
) bool {
1442+
if (!return_type.hasRuntimeBitsIgnoreComptime(zcu)) return false;
14311443
switch (cc) {
14321444
.@"inline" => unreachable,
14331445
.auto => return isByRef(return_type, zcu, target),
1434-
.wasm_mvp => {
1435-
const ty_classes = abi.classifyType(return_type, zcu);
1436-
if (ty_classes[0] == .indirect) return true;
1437-
if (ty_classes[0] == .direct and ty_classes[1] == .direct) return true;
1438-
return false;
1446+
.wasm_mvp => switch (abi.classifyType(return_type, zcu)) {
1447+
.direct => |scalar_ty| return abi.lowerAsDoubleI64(scalar_ty, zcu),
1448+
.indirect => return true,
14391449
},
14401450
else => return false,
14411451
}
@@ -1449,34 +1459,27 @@ fn lowerArg(cg: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value: WV
14491459
}
14501460

14511461
const zcu = cg.pt.zcu;
1452-
const ty_classes = abi.classifyType(ty, zcu);
1453-
assert(ty_classes[0] != .none);
1454-
switch (ty.zigTypeTag(zcu)) {
1455-
.@"struct", .@"union" => {
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) {
1462+
1463+
switch (abi.classifyType(ty, zcu)) {
1464+
.direct => |scalar_type| if (!abi.lowerAsDoubleI64(scalar_type, zcu)) {
1465+
if (!isByRef(ty, zcu, cg.target)) {
14691466
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+
}
14701473
}
1471-
assert(ty_classes[0] == .direct and ty_classes[1] == .direct);
1474+
} else {
14721475
assert(ty.abiSize(zcu) == 16);
14731476
// in this case we have an integer or float that must be lowered as 2 i64's.
14741477
try cg.emitWValue(value);
14751478
try cg.addMemArg(.i64_load, .{ .offset = value.offset(), .alignment = 8 });
14761479
try cg.emitWValue(value);
14771480
try cg.addMemArg(.i64_load, .{ .offset = value.offset() + 8, .alignment = 8 });
14781481
},
1479-
else => return cg.lowerToStack(value),
1482+
.indirect => return cg.lowerToStack(value),
14801483
}
14811484
}
14821485

@@ -2142,23 +2145,16 @@ fn airRet(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
21422145
if (cg.return_value != .none) {
21432146
try cg.store(cg.return_value, operand, ret_ty, 0);
21442147
} else if (fn_info.cc == .wasm_mvp and ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
2145-
switch (ret_ty.zigTypeTag(zcu)) {
2146-
// Aggregate types can be lowered as a singular value
2147-
.@"struct", .@"union" => {
2148-
const scalar_type = abi.scalarType(ret_ty, zcu);
2149-
try cg.emitWValue(operand);
2150-
const opcode = buildOpcode(.{
2151-
.op = .load,
2152-
.width = @as(u8, @intCast(scalar_type.abiSize(zcu) * 8)),
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-
});
2148+
switch (abi.classifyType(ret_ty, zcu)) {
2149+
.direct => |scalar_type| {
2150+
assert(!abi.lowerAsDoubleI64(scalar_type, zcu));
2151+
if (!isByRef(ret_ty, zcu, cg.target)) {
2152+
try cg.emitWValue(operand);
2153+
} else {
2154+
_ = try cg.load(operand, scalar_type, 0);
2155+
}
21602156
},
2161-
else => try cg.emitWValue(operand),
2157+
.indirect => unreachable,
21622158
}
21632159
} else {
21642160
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
22842280
break :result_value .none;
22852281
} else if (first_param_sret) {
22862282
break :result_value sret;
2287-
// TODO: Make this less fragile and optimize
2288-
} else if (zcu.typeToFunc(fn_ty).?.cc == .wasm_mvp and ret_ty.zigTypeTag(zcu) == .@"struct" or ret_ty.zigTypeTag(zcu) == .@"union") {
2289-
const result_local = try cg.allocLocal(ret_ty);
2290-
try cg.addLocal(.local_set, result_local.local.value);
2291-
const scalar_type = abi.scalarType(ret_ty, zcu);
2292-
const result = try cg.allocStack(scalar_type);
2293-
try cg.store(result, result_local, scalar_type, 0);
2294-
break :result_value result;
2283+
} else if (zcu.typeToFunc(fn_ty).?.cc == .wasm_mvp) {
2284+
switch (abi.classifyType(ret_ty, zcu)) {
2285+
.direct => |scalar_type| {
2286+
assert(!abi.lowerAsDoubleI64(scalar_type, zcu));
2287+
if (!isByRef(ret_ty, zcu, cg.target)) {
2288+
const result_local = try cg.allocLocal(ret_ty);
2289+
try cg.addLocal(.local_set, result_local.local.value);
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+
}
22952301
} else {
22962302
const result_local = try cg.allocLocal(ret_ty);
22972303
try cg.addLocal(.local_set, result_local.local.value);
@@ -2597,26 +2603,17 @@ fn airArg(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
25972603
const cc = zcu.typeToFunc(zcu.navValue(cg.owner_nav).typeOf(zcu)).?.cc;
25982604
const arg_ty = cg.typeOfIndex(inst);
25992605
if (cc == .wasm_mvp) {
2600-
const arg_classes = abi.classifyType(arg_ty, zcu);
2601-
for (arg_classes) |class| {
2602-
if (class != .none) {
2606+
switch (abi.classifyType(arg_ty, zcu)) {
2607+
.direct => |scalar_ty| if (!abi.lowerAsDoubleI64(scalar_ty, zcu)) {
26032608
cg.arg_index += 1;
2604-
}
2605-
}
2606-
2607-
// When we have an argument that's passed using more than a single parameter,
2608-
// we combine them into a single stack value
2609-
if (arg_classes[0] == .direct and arg_classes[1] == .direct) {
2610-
if (arg_ty.zigTypeTag(zcu) != .int and arg_ty.zigTypeTag(zcu) != .float) {
2611-
return cg.fail(
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, &.{});
2609+
} else {
2610+
cg.arg_index += 2;
2611+
const result = try cg.allocStack(arg_ty);
2612+
try cg.store(result, arg, Type.u64, 0);
2613+
try cg.store(result, cg.args[arg_index + 1], Type.u64, 8);
2614+
return cg.finishAir(inst, result, &.{});
2615+
},
2616+
.indirect => cg.arg_index += 1,
26202617
}
26212618
} else {
26222619
cg.arg_index += 1;

src/arch/wasm/abi.zig

+26-67
Original file line numberDiff line numberDiff line change
@@ -13,70 +13,55 @@ const Zcu = @import("../../Zcu.zig");
1313

1414
/// Defines how to pass a type as part of a function signature,
1515
/// both for parameters as well as return values.
16-
pub const Class = enum { direct, indirect, none };
17-
18-
const none: [2]Class = .{ .none, .none };
19-
const memory: [2]Class = .{ .indirect, .none };
20-
const direct: [2]Class = .{ .direct, .none };
16+
pub const Class = union(enum) {
17+
direct: Type,
18+
indirect,
19+
};
2120

2221
/// Classifies a given Zig type to determine how they must be passed
2322
/// or returned as value within a wasm function.
24-
/// When all elements result in `.none`, no value must be passed in or returned.
25-
pub fn classifyType(ty: Type, zcu: *const Zcu) [2]Class {
23+
pub fn classifyType(ty: Type, zcu: *const Zcu) Class {
2624
const ip = &zcu.intern_pool;
27-
const target = zcu.getTarget();
28-
if (!ty.hasRuntimeBitsIgnoreComptime(zcu)) return none;
25+
assert(ty.hasRuntimeBitsIgnoreComptime(zcu));
2926
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+
},
3040
.@"struct" => {
3141
const struct_type = zcu.typeToStruct(ty).?;
3242
if (struct_type.layout == .@"packed") {
33-
if (ty.bitSize(zcu) <= 64) return direct;
34-
return .{ .direct, .direct };
43+
return .{ .direct = ty };
3544
}
3645
if (struct_type.field_types.len > 1) {
3746
// The struct type is non-scalar.
38-
return memory;
47+
return .indirect;
3948
}
4049
const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[0]);
4150
const explicit_align = struct_type.fieldAlign(ip, 0);
4251
if (explicit_align != .none) {
4352
if (explicit_align.compareStrict(.gt, field_ty.abiAlignment(zcu)))
44-
return memory;
53+
return .indirect;
4554
}
4655
return classifyType(field_ty, zcu);
4756
},
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-
},
7157
.@"union" => {
7258
const union_obj = zcu.typeToUnion(ty).?;
7359
if (union_obj.flagsUnordered(ip).layout == .@"packed") {
74-
if (ty.bitSize(zcu) <= 64) return direct;
75-
return .{ .direct, .direct };
60+
return .{ .direct = ty };
7661
}
7762
const layout = ty.unionGetLayout(zcu);
7863
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;
8065
const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]);
8166
return classifyType(first_field_ty, zcu);
8267
},
@@ -97,32 +82,6 @@ pub fn classifyType(ty: Type, zcu: *const Zcu) [2]Class {
9782
}
9883
}
9984

100-
/// Returns the scalar type a given type can represent.
101-
/// Asserts given type can be represented as scalar, such as
102-
/// a struct with a single scalar field.
103-
pub 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-
}
85+
pub fn lowerAsDoubleI64(scalar_ty: Type, zcu: *const Zcu) bool {
86+
return scalar_ty.bitSize(zcu) > 64;
12887
}

src/codegen/llvm.zig

+25-23
Original file line numberDiff line numberDiff line change
@@ -11723,7 +11723,7 @@ fn firstParamSRet(fn_info: InternPool.Key.FuncType, zcu: *Zcu, target: std.Targe
1172311723
.x86_64_win => x86_64_abi.classifyWindows(return_type, zcu) == .memory,
1172411724
.x86_sysv, .x86_win => isByRef(return_type, zcu),
1172511725
.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,
1172711727
.aarch64_aapcs,
1172811728
.aarch64_aapcs_darwin,
1172911729
.aarch64_aapcs_win,
@@ -11808,18 +11808,9 @@ fn lowerFnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) Allocator.Error!Bu
1180811808
return o.builder.structType(.normal, types[0..types_len]);
1180911809
},
1181011810
},
11811-
.wasm_mvp => {
11812-
if (isScalar(zcu, return_type)) {
11813-
return o.lowerType(return_type);
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));
11811+
.wasm_mvp => switch (wasm_c_abi.classifyType(return_type, zcu)) {
11812+
.direct => |scalar_ty| return o.lowerType(scalar_ty),
11813+
.indirect => return .void,
1182311814
},
1182411815
// TODO investigate other callconvs
1182511816
else => return o.lowerType(return_type),
@@ -12073,17 +12064,28 @@ const ParamTypeIterator = struct {
1207312064
},
1207412065
}
1207512066
},
12076-
.wasm_mvp => {
12077-
it.zig_index += 1;
12078-
it.llvm_index += 1;
12079-
if (isScalar(zcu, ty)) {
12080-
return .byval;
12081-
}
12082-
const classes = wasm_c_abi.classifyType(ty, zcu);
12083-
if (classes[0] == .indirect) {
12067+
.wasm_mvp => switch (wasm_c_abi.classifyType(ty, zcu)) {
12068+
.direct => |scalar_ty| {
12069+
if (isScalar(zcu, ty)) {
12070+
it.zig_index += 1;
12071+
it.llvm_index += 1;
12072+
return .byval;
12073+
} else {
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;
1208412087
return .byref;
12085-
}
12086-
return .abi_sized_int;
12088+
},
1208712089
},
1208812090
// TODO investigate other callconvs
1208912091
else => {

0 commit comments

Comments
 (0)