Commit cb49af6c9a

Luuk de Gram <luuk@degram.dev>
2022-04-23 20:45:10
wasm: Initial C-ABI implementation
This implements the C-ABI convention as specified by: https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md While not an official specification, it's the ABI that is output by clang/LLVM. As we use LLVM to compile compiler-rt, and want to integrate with C-libraries, we follow the same convention when the calling convention results in 'C'.
1 parent ab658e3
Changed files (1)
src
arch
wasm
src/arch/wasm/abi.zig
@@ -0,0 +1,102 @@
+//! Classifies Zig types to follow the C-ABI for Wasm.
+//! The convention for Wasm's C-ABI can be found at the tool-conventions repo:
+//! https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md
+//! When not targeting the C-ABI, Zig is allowed to do derail from this convention.
+//! Note: Above mentioned document is not an official specification, therefore called a convention.
+
+const std = @import("std");
+const Type = @import("../../type.zig").Type;
+const Target = std.Target;
+
+/// 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 };
+
+/// 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, 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
+            const field = ty.structFields().values()[0];
+            if (field.abi_align != 0) {
+                if (field.abi_align > field.ty.abiAlignment(target)) {
+                    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 };
+            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 };
+            return memory;
+        },
+        .Bool => return direct,
+        .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;
+        },
+        .Pointer => {
+            // Slices act like struct and will be passed by reference
+            if (ty.isSlice()) return memory;
+            return direct;
+        },
+        .Array => {
+            if (ty.arrayLen() == 1) return direct;
+            return memory;
+        },
+        .Union => {
+            const layout = ty.unionGetLayout(target);
+            if (layout.payload_size == 0 and layout.tag_size != 0) {
+                return classifyType(ty.unionTagType().?, target);
+            }
+            return classifyType(ty.errorUnionPayload(), target);
+        },
+        .AnyFrame, .Frame => return direct,
+
+        .NoReturn,
+        .Void,
+        .Type,
+        .ComptimeFloat,
+        .ComptimeInt,
+        .Undefined,
+        .Null,
+        .BoundFn,
+        .Fn,
+        .Opaque,
+        .EnumLiteral,
+        => unreachable,
+    }
+}