| 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 Target = std.Target; |
| 9 | const assert = std.debug.assert; |
| 10 | |
| 11 | const Type = @import("../../Type.zig"); |
| 12 | const Zcu = @import("../../Zcu.zig"); |
| 13 | |
| 14 | /// Describes how the Wasm backend represents a C ABI value. |
| 15 | pub const Class = union(enum) { |
| 16 | direct: Type, |
| 17 | double_i64, |
| 18 | indirect, |
| 19 | unrolled: struct { |
| 20 | elem_type: Type, |
| 21 | len: u32, |
| 22 | }, |
| 23 | }; |
| 24 | |
| 25 | pub const LlvmClass = union(enum) { |
| 26 | direct: Type, |
| 27 | indirect, |
| 28 | }; |
| 29 | |
| 30 | pub fn classifyType(ty: Type, zcu: *const Zcu, target: *const Target) Class { |
| 31 | if (ty.zigTypeTag(zcu) == .vector) { |
| 32 | if (!(ty.bitSize(zcu) == 128 and target.cpu.has(.wasm, .simd128))) { |
| 33 | const elem_type = ty.childType(zcu); |
| 34 | return .{ .unrolled = .{ |
| 35 | .elem_type = elem_type, |
| 36 | .len = ty.vectorLen(zcu), |
| 37 | } }; |
| 38 | } |
| 39 | return .{ .direct = ty }; |
| 40 | } |
| 41 | |
| 42 | return switch (classifyTypeForLlvm(ty, zcu)) { |
| 43 | .direct => |scalar_ty| if (scalar_ty.bitSize(zcu) > 64) |
| 44 | .double_i64 |
| 45 | else |
| 46 | .{ .direct = scalar_ty }, |
| 47 | .indirect => .indirect, |
| 48 | }; |
| 49 | } |
| 50 | |
| 51 | pub fn classifyTypeForLlvm(ty: Type, zcu: *const Zcu) LlvmClass { |
| 52 | const ip = &zcu.intern_pool; |
| 53 | assert(ty.hasRuntimeBits(zcu)); |
| 54 | switch (ty.zigTypeTag(zcu)) { |
| 55 | .int, .@"enum", .error_set => return .{ .direct = ty }, |
| 56 | .float => return switch (ty.floatBits(zcu.getTarget())) { |
| 57 | else => unreachable, |
| 58 | 16, 32, 64, 128 => .{ .direct = ty }, |
| 59 | 80 => .indirect, |
| 60 | }, |
| 61 | .bool => return .{ .direct = ty }, |
| 62 | .vector => return .{ .direct = ty }, |
| 63 | .array => return .indirect, |
| 64 | .optional => { |
| 65 | assert(ty.isPtrLikeOptional(zcu)); |
| 66 | return .{ .direct = ty }; |
| 67 | }, |
| 68 | .pointer => { |
| 69 | assert(!ty.isSlice(zcu)); |
| 70 | return .{ .direct = ty }; |
| 71 | }, |
| 72 | .@"struct" => { |
| 73 | const struct_type = zcu.typeToStruct(ty).?; |
| 74 | switch (struct_type.layout) { |
| 75 | .auto => unreachable, |
| 76 | .@"packed" => return .{ .direct = ty }, |
| 77 | .@"extern" => {}, |
| 78 | } |
| 79 | var opt_single_field_ty: ?Type = null; |
| 80 | for (struct_type.field_types.get(ip), 0..) |field_ty_index, field_index| { |
| 81 | const field_ty: Type = .fromInterned(field_ty_index); |
| 82 | if (!field_ty.hasRuntimeBits(zcu)) continue; |
| 83 | |
| 84 | if (opt_single_field_ty != null) { |
| 85 | return .indirect; |
| 86 | } |
| 87 | |
| 88 | const field_align = struct_type.field_aligns.getOrNone(ip, field_index); |
| 89 | if (field_align != .none and field_align.compareStrict(.gt, field_ty.abiAlignment(zcu))) { |
| 90 | return .indirect; |
| 91 | } |
| 92 | opt_single_field_ty = field_ty; |
| 93 | } |
| 94 | const single_field_ty = opt_single_field_ty.?; |
| 95 | if (single_field_ty.zigTypeTag(zcu) == .array) { |
| 96 | switch (single_field_ty.arrayLenIncludingSentinel(zcu)) { |
| 97 | 0 => unreachable, |
| 98 | 1 => return classifyTypeForLlvm(single_field_ty.childType(zcu), zcu), |
| 99 | else => {}, |
| 100 | } |
| 101 | } |
| 102 | return classifyTypeForLlvm(single_field_ty, zcu); |
| 103 | }, |
| 104 | .@"union" => { |
| 105 | const union_obj = zcu.typeToUnion(ty).?; |
| 106 | if (union_obj.layout == .@"packed") { |
| 107 | return .{ .direct = ty }; |
| 108 | } |
| 109 | const layout = ty.unionGetLayout(zcu); |
| 110 | assert(layout.tag_size == 0); |
| 111 | if (union_obj.field_types.len > 1) return .indirect; |
| 112 | const first_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[0]); |
| 113 | if (first_field_ty.zigTypeTag(zcu) == .array) { |
| 114 | switch (first_field_ty.arrayLenIncludingSentinel(zcu)) { |
| 115 | 0 => unreachable, |
| 116 | 1 => return classifyTypeForLlvm(first_field_ty.childType(zcu), zcu), |
| 117 | else => {}, |
| 118 | } |
| 119 | } |
| 120 | return classifyTypeForLlvm(first_field_ty, zcu); |
| 121 | }, |
| 122 | .error_union, |
| 123 | .frame, |
| 124 | .@"anyframe", |
| 125 | .noreturn, |
| 126 | .void, |
| 127 | .type, |
| 128 | .comptime_float, |
| 129 | .comptime_int, |
| 130 | .undefined, |
| 131 | .null, |
| 132 | .@"fn", |
| 133 | .@"opaque", |
| 134 | .spirv, |
| 135 | .enum_literal, |
| 136 | => unreachable, |
| 137 | } |
| 138 | } |