Commit 200b2e4ee1

Luuk de Gram <luuk@degram.dev>
2022-07-17 15:10:56
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.
1 parent bf28a47
Changed files (2)
src
arch
wasm
codegen
src/arch/wasm/abi.zig
@@ -23,8 +23,6 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {
     if (!ty.hasRuntimeBitsIgnoreComptime()) return none;
     switch (ty.zigTypeTag()) {
         .Struct => {
-            // When the (maybe) scalar type exceeds max 'direct' integer size
-            if (ty.abiSize(target) > 8) return memory;
             // When the struct type is non-scalar
             if (ty.structFieldCount() > 1) return memory;
             // When the struct's alignment is non-natural
@@ -34,56 +32,57 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {
                     return memory;
                 }
             }
-            if (field.ty.isInt() or field.ty.isAnyFloat()) {
-                return direct;
-            }
             return classifyType(field.ty, target);
         },
         .Int, .Enum, .ErrorSet, .Vector => {
             const int_bits = ty.intInfo(target).bits;
             if (int_bits <= 64) return direct;
-            if (int_bits > 64 and int_bits <= 128) return .{ .direct, .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 > 64 and float_bits <= 128) return .{ .direct, .direct };
+            if (float_bits <= 128) return .{ .direct, .direct };
             return memory;
         },
         .Bool => return direct,
         .Array => return memory,
-        .ErrorUnion => {
-            const has_tag = ty.errorUnionSet().hasRuntimeBitsIgnoreComptime();
-            const has_pl = ty.errorUnionPayload().hasRuntimeBitsIgnoreComptime();
-            if (!has_pl) return direct;
-            if (!has_tag) {
-                return classifyType(ty.errorUnionPayload(), target);
-            }
-            return memory;
-        },
+        // .ErrorUnion => {
+        //     const has_tag = ty.errorUnionSet().hasRuntimeBitsIgnoreComptime();
+        //     const has_pl = ty.errorUnionPayload().hasRuntimeBitsIgnoreComptime();
+        //     if (!has_pl) return direct;
+        //     if (!has_tag) {
+        //         return classifyType(ty.errorUnionPayload(), target);
+        //     }
+        //     return memory;
+        // },
         .Optional => {
-            if (ty.isPtrLikeOptional()) return direct;
-            var buf: Type.Payload.ElemType = undefined;
-            const pl_has_bits = ty.optionalChild(&buf).hasRuntimeBitsIgnoreComptime();
-            if (!pl_has_bits) return direct;
-            return memory;
+            std.debug.assert(ty.isPtrLikeOptional());
+            return direct;
+            // var buf: Type.Payload.ElemType = undefined;
+            // const pl_has_bits = ty.optionalChild(&buf).hasRuntimeBitsIgnoreComptime();
+            // if (!pl_has_bits) return direct;
+            // return memory;
         },
         .Pointer => {
-            // Slices act like struct and will be passed by reference
-            if (ty.isSlice()) return memory;
+            // // Slices act like struct and will be passed by reference
+            // if (ty.isSlice()) return memory;
             return direct;
         },
         .Union => {
             const layout = ty.unionGetLayout(target);
-            if (layout.payload_size == 0 and layout.tag_size != 0) {
-                return classifyType(ty.unionTagTypeSafety().?, target);
-            }
+            std.debug.assert(layout.tag_size == 0);
+            // if (layout.payload_size == 0 and layout.tag_size != 0) {
+            //     return classifyType(ty.unionTagType().?, target);
+            // }
             if (ty.unionFields().count() > 1) return memory;
             return classifyType(ty.unionFields().values()[0].ty, target);
         },
-        .AnyFrame, .Frame => return direct,
-
+        // .AnyFrame, .Frame => return direct,
+        .ErrorUnion,
+        .Frame,
+        .AnyFrame,
         .NoReturn,
         .Void,
         .Type,
src/codegen/llvm.zig
@@ -22,6 +22,7 @@ const Type = @import("../type.zig").Type;
 const LazySrcLoc = Module.LazySrcLoc;
 const CType = @import("../type.zig").CType;
 const x86_64_abi = @import("../arch/x86_64/abi.zig");
+const wasm_c_abi = @import("../arch/wasm/abi.zig");
 
 const Error = error{ OutOfMemory, CodegenFail };
 
@@ -9093,6 +9094,10 @@ fn firstParamSRet(fn_info: Type.Payload.Function.Data, target: std.Target) bool
                 .windows => return x86_64_abi.classifyWindows(fn_info.return_type, target) == .memory,
                 else => return x86_64_abi.classifySystemV(fn_info.return_type, target)[0] == .memory,
             },
+            .wasm32 => {
+                const classes = wasm_c_abi.classifyType(fn_info.return_type, target);
+                return classes[0] == .indirect;
+            },
             else => return false, // TODO investigate C ABI for other architectures
         },
         else => return false,
@@ -9197,6 +9202,20 @@ fn lowerFnRetTy(dg: *DeclGen, fn_info: Type.Payload.Function.Data) !*const llvm.
                         return dg.context.structType(&llvm_types_buffer, llvm_types_index, .False);
                     },
                 },
+                .wasm32 => {
+                    if (is_scalar) {
+                        return dg.lowerType(fn_info.return_type);
+                    }
+                    const classes = wasm_c_abi.classifyType(fn_info.return_type, target);
+                    if (classes[0] == .indirect or classes[0] == .none) {
+                        return dg.context.voidType();
+                    }
+
+                    assert(classes[0] == .direct and classes[1] == .none);
+                    const scalar_type = wasm_c_abi.scalarType(fn_info.return_type, target);
+                    const abi_size = scalar_type.abiSize(target);
+                    return dg.context.intType(@intCast(c_uint, abi_size * 8));
+                },
                 // TODO investigate C ABI for other architectures
                 else => return dg.lowerType(fn_info.return_type),
             }
@@ -9372,6 +9391,18 @@ const ParamTypeIterator = struct {
                             return .multiple_llvm_ints;
                         },
                     },
+                    .wasm32 => {
+                        it.zig_index += 1;
+                        it.llvm_index += 1;
+                        if (is_scalar) {
+                            return .byval;
+                        }
+                        const classes = wasm_c_abi.classifyType(ty, it.target);
+                        if (classes[0] == .indirect) {
+                            return .byref;
+                        }
+                        return .abi_sized_int;
+                    },
                     // TODO investigate C ABI for other architectures
                     else => {
                         it.zig_index += 1;