authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-07-17 15:10:56+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-07-25 06:34:00+02:00
log200b2e4ee108d388e0db2120aee38f83d1c7abdb
tree49139a306103997d18ed83624b3fec2c3cb13468
parentbf28a47cf23a60539f7cc6cb87309d1a7fa01c18
signature Commit is signed but in an unrecognized format.

llvm: correctly lower c-abi for Wasm target

When lowering the return type for Wasm if the calling convention is `C`, it now correctly lower it according to what clang does as specified in: https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md This makes use of the same logic as the Wasm backend, ensuring the generated code does not diverge in function signatures. When passing arguments accross the C-ABI for the Wasm target, we want slightly different behavior than x86_64. For instance: a struct with multiple fields must always be passed by reference, even if its ABI size fits in a single integer. However, we do pass larger integers such as 128bit by value, which LLVM will correctly lower to use double arguments instead.

2 files changed, 58 insertions(+), 28 deletions(-)

src/arch/wasm/abi.zig+27-28
......@@ -23,8 +23,6 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {
2323 if (!ty.hasRuntimeBitsIgnoreComptime()) return none;
2424 switch (ty.zigTypeTag()) {
2525 .Struct => {
26 // When the (maybe) scalar type exceeds max 'direct' integer size
27 if (ty.abiSize(target) > 8) return memory;
2826 // When the struct type is non-scalar
2927 if (ty.structFieldCount() > 1) return memory;
3028 // When the struct's alignment is non-natural
......@@ -34,56 +32,57 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {
3432 return memory;
3533 }
3634 }
37 if (field.ty.isInt() or field.ty.isAnyFloat()) {
38 return direct;
39 }
4035 return classifyType(field.ty, target);
4136 },
4237 .Int, .Enum, .ErrorSet, .Vector => {
4338 const int_bits = ty.intInfo(target).bits;
4439 if (int_bits <= 64) return direct;
45 if (int_bits > 64 and int_bits <= 128) return .{ .direct, .direct };
40 if (int_bits <= 128) return .{ .direct, .direct };
4641 return memory;
4742 },
4843 .Float => {
4944 const float_bits = ty.floatBits(target);
5045 if (float_bits <= 64) return direct;
51 if (float_bits > 64 and float_bits <= 128) return .{ .direct, .direct };
46 if (float_bits <= 128) return .{ .direct, .direct };
5247 return memory;
5348 },
5449 .Bool => return direct,
5550 .Array => return memory,
56 .ErrorUnion => {
57 const has_tag = ty.errorUnionSet().hasRuntimeBitsIgnoreComptime();
58 const has_pl = ty.errorUnionPayload().hasRuntimeBitsIgnoreComptime();
59 if (!has_pl) return direct;
60 if (!has_tag) {
61 return classifyType(ty.errorUnionPayload(), target);
62 }
63 return memory;
64 },
51 // .ErrorUnion => {
52 // const has_tag = ty.errorUnionSet().hasRuntimeBitsIgnoreComptime();
53 // const has_pl = ty.errorUnionPayload().hasRuntimeBitsIgnoreComptime();
54 // if (!has_pl) return direct;
55 // if (!has_tag) {
56 // return classifyType(ty.errorUnionPayload(), target);
57 // }
58 // return memory;
59 // },
6560 .Optional => {
66 if (ty.isPtrLikeOptional()) return direct;
67 var buf: Type.Payload.ElemType = undefined;
68 const pl_has_bits = ty.optionalChild(&buf).hasRuntimeBitsIgnoreComptime();
69 if (!pl_has_bits) return direct;
70 return memory;
61 std.debug.assert(ty.isPtrLikeOptional());
62 return direct;
63 // var buf: Type.Payload.ElemType = undefined;
64 // const pl_has_bits = ty.optionalChild(&buf).hasRuntimeBitsIgnoreComptime();
65 // if (!pl_has_bits) return direct;
66 // return memory;
7167 },
7268 .Pointer => {
73 // Slices act like struct and will be passed by reference
74 if (ty.isSlice()) return memory;
69 // // Slices act like struct and will be passed by reference
70 // if (ty.isSlice()) return memory;
7571 return direct;
7672 },
7773 .Union => {
7874 const layout = ty.unionGetLayout(target);
79 if (layout.payload_size == 0 and layout.tag_size != 0) {
80 return classifyType(ty.unionTagTypeSafety().?, target);
81 }
75 std.debug.assert(layout.tag_size == 0);
76 // if (layout.payload_size == 0 and layout.tag_size != 0) {
77 // return classifyType(ty.unionTagType().?, target);
78 // }
8279 if (ty.unionFields().count() > 1) return memory;
8380 return classifyType(ty.unionFields().values()[0].ty, target);
8481 },
85 .AnyFrame, .Frame => return direct,
86
82 // .AnyFrame, .Frame => return direct,
83 .ErrorUnion,
84 .Frame,
85 .AnyFrame,
8786 .NoReturn,
8887 .Void,
8988 .Type,
src/codegen/llvm.zig+31
......@@ -22,6 +22,7 @@ const Type = @import("../type.zig").Type;
2222const LazySrcLoc = Module.LazySrcLoc;
2323const CType = @import("../type.zig").CType;
2424const x86_64_abi = @import("../arch/x86_64/abi.zig");
25const wasm_c_abi = @import("../arch/wasm/abi.zig");
2526
2627const Error = error{ OutOfMemory, CodegenFail };
2728
......@@ -9093,6 +9094,10 @@ fn firstParamSRet(fn_info: Type.Payload.Function.Data, target: std.Target) bool
90939094 .windows => return x86_64_abi.classifyWindows(fn_info.return_type, target) == .memory,
90949095 else => return x86_64_abi.classifySystemV(fn_info.return_type, target)[0] == .memory,
90959096 },
9097 .wasm32 => {
9098 const classes = wasm_c_abi.classifyType(fn_info.return_type, target);
9099 return classes[0] == .indirect;
9100 },
90969101 else => return false, // TODO investigate C ABI for other architectures
90979102 },
90989103 else => return false,
......@@ -9197,6 +9202,20 @@ fn lowerFnRetTy(dg: *DeclGen, fn_info: Type.Payload.Function.Data) !*const llvm.
91979202 return dg.context.structType(&llvm_types_buffer, llvm_types_index, .False);
91989203 },
91999204 },
9205 .wasm32 => {
9206 if (is_scalar) {
9207 return dg.lowerType(fn_info.return_type);
9208 }
9209 const classes = wasm_c_abi.classifyType(fn_info.return_type, target);
9210 if (classes[0] == .indirect or classes[0] == .none) {
9211 return dg.context.voidType();
9212 }
9213
9214 assert(classes[0] == .direct and classes[1] == .none);
9215 const scalar_type = wasm_c_abi.scalarType(fn_info.return_type, target);
9216 const abi_size = scalar_type.abiSize(target);
9217 return dg.context.intType(@intCast(c_uint, abi_size * 8));
9218 },
92009219 // TODO investigate C ABI for other architectures
92019220 else => return dg.lowerType(fn_info.return_type),
92029221 }
......@@ -9372,6 +9391,18 @@ const ParamTypeIterator = struct {
93729391 return .multiple_llvm_ints;
93739392 },
93749393 },
9394 .wasm32 => {
9395 it.zig_index += 1;
9396 it.llvm_index += 1;
9397 if (is_scalar) {
9398 return .byval;
9399 }
9400 const classes = wasm_c_abi.classifyType(ty, it.target);
9401 if (classes[0] == .indirect) {
9402 return .byref;
9403 }
9404 return .abi_sized_int;
9405 },
93759406 // TODO investigate C ABI for other architectures
93769407 else => {
93779408 it.zig_index += 1;