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
7const std = @import("std");
8const Target = std.Target;
9const assert = std.debug.assert;
10
11const Type = @import("../../Type.zig");
12const Zcu = @import("../../Zcu.zig");
13
14/// Describes how the Wasm backend represents a C ABI value.
15pub 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
25pub const LlvmClass = union(enum) {
26 direct: Type,
27 indirect,
28};
29
30pub 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
51pub 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}