| ... | ... | @@ -0,0 +1,102 @@ |
| 1 | //! Classifies Zig types to follow the C-ABI for Wasm. |
| 2 | //! The convention for Wasm's C-ABI can be found at the tool-conventions repo: |
| 3 | //! https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md |
| 4 | //! When not targeting the C-ABI, Zig is allowed to do derail from this convention. |
| 5 | //! Note: Above mentioned document is not an official specification, therefore called a convention. |
| 6 | |
| 7 | const std = @import("std"); |
| 8 | const Type = @import("../../type.zig").Type; |
| 9 | const Target = std.Target; |
| 10 | |
| 11 | /// Defines how to pass a type as part of a function signature, |
| 12 | /// both for parameters as well as return values. |
| 13 | pub const Class = enum { direct, indirect, none }; |
| 14 | |
| 15 | const none: [2]Class = .{ .none, .none }; |
| 16 | const memory: [2]Class = .{ .indirect, .none }; |
| 17 | const direct: [2]Class = .{ .direct, .none }; |
| 18 | |
| 19 | /// Classifies a given Zig type to determine how they must be passed |
| 20 | /// or returned as value within a wasm function. |
| 21 | /// When all elements result in `.none`, no value must be passed in or returned. |
| 22 | pub fn classifyType(ty: Type, target: Target) [2]Class { |
| 23 | if (!ty.hasRuntimeBitsIgnoreComptime()) return none; |
| 24 | switch (ty.zigTypeTag()) { |
| 25 | .Struct => { |
| 26 | // When the (maybe) scalar type exceeds max 'direct' integer size |
| 27 | if (ty.abiSize(target) > 8) return memory; |
| 28 | // When the struct type is non-scalar |
| 29 | if (ty.structFieldCount() > 1) return memory; |
| 30 | // When the struct's alignment is non-natural |
| 31 | const field = ty.structFields().values()[0]; |
| 32 | if (field.abi_align != 0) { |
| 33 | if (field.abi_align > field.ty.abiAlignment(target)) { |
| 34 | return memory; |
| 35 | } |
| 36 | } |
| 37 | if (field.ty.isInt() or field.ty.isAnyFloat()) { |
| 38 | return direct; |
| 39 | } |
| 40 | return classifyType(field.ty, target); |
| 41 | }, |
| 42 | .Int, .Enum, .ErrorSet, .Vector => { |
| 43 | const int_bits = ty.intInfo(target).bits; |
| 44 | if (int_bits <= 64) return direct; |
| 45 | if (int_bits > 64 and int_bits <= 128) return .{ .direct, .direct }; |
| 46 | return memory; |
| 47 | }, |
| 48 | .Float => { |
| 49 | const float_bits = ty.floatBits(target); |
| 50 | if (float_bits <= 64) return direct; |
| 51 | if (float_bits > 64 and float_bits <= 128) return .{ .direct, .direct }; |
| 52 | return memory; |
| 53 | }, |
| 54 | .Bool => return direct, |
| 55 | .ErrorUnion => { |
| 56 | const has_tag = ty.errorUnionSet().hasRuntimeBitsIgnoreComptime(); |
| 57 | const has_pl = ty.errorUnionPayload().hasRuntimeBitsIgnoreComptime(); |
| 58 | if (!has_pl) return direct; |
| 59 | if (!has_tag) { |
| 60 | return classifyType(ty.errorUnionPayload(), target); |
| 61 | } |
| 62 | return memory; |
| 63 | }, |
| 64 | .Optional => { |
| 65 | if (ty.isPtrLikeOptional()) return direct; |
| 66 | var buf: Type.Payload.ElemType = undefined; |
| 67 | const pl_has_bits = ty.optionalChild(&buf).hasRuntimeBitsIgnoreComptime(); |
| 68 | if (!pl_has_bits) return direct; |
| 69 | return memory; |
| 70 | }, |
| 71 | .Pointer => { |
| 72 | // Slices act like struct and will be passed by reference |
| 73 | if (ty.isSlice()) return memory; |
| 74 | return direct; |
| 75 | }, |
| 76 | .Array => { |
| 77 | if (ty.arrayLen() == 1) return direct; |
| 78 | return memory; |
| 79 | }, |
| 80 | .Union => { |
| 81 | const layout = ty.unionGetLayout(target); |
| 82 | if (layout.payload_size == 0 and layout.tag_size != 0) { |
| 83 | return classifyType(ty.unionTagType().?, target); |
| 84 | } |
| 85 | return classifyType(ty.errorUnionPayload(), target); |
| 86 | }, |
| 87 | .AnyFrame, .Frame => return direct, |
| 88 | |
| 89 | .NoReturn, |
| 90 | .Void, |
| 91 | .Type, |
| 92 | .ComptimeFloat, |
| 93 | .ComptimeInt, |
| 94 | .Undefined, |
| 95 | .Null, |
| 96 | .BoundFn, |
| 97 | .Fn, |
| 98 | .Opaque, |
| 99 | .EnumLiteral, |
| 100 | => unreachable, |
| 101 | } |
| 102 | } |