1const std = @import("std");
2const bits = @import("bits.zig");
3const Register = bits.Register;
4const RegisterManagerFn = @import("../../register_manager.zig").RegisterManager;
5const Type = @import("../../Type.zig");
6const InternPool = @import("../../InternPool.zig");
7const Zcu = @import("../../Zcu.zig");
8const assert = std.debug.assert;
9
10pub const Class = enum { memory, byval, integer, double_integer, fields };
11
12pub fn classifyType(ty: Type, zcu: *Zcu) Class {
13 const target = zcu.getTarget();
14 std.debug.assert(ty.hasRuntimeBits(zcu));
15
16 const max_byval_size = target.ptrBitWidth() * 2;
17 switch (ty.zigTypeTag(zcu)) {
18 .@"struct" => {
19 if (ty.containerLayout(zcu) == .@"packed") {
20 if (ty.bitSize(zcu) > max_byval_size) return .memory;
21 return .byval;
22 }
23
24 if (target.cpu.has(.riscv, .d)) fields: {
25 var any_fp = false;
26 var field_count: usize = 0;
27 for (0..ty.structFieldCount(zcu)) |field_index| {
28 const field_ty = ty.fieldType(field_index, zcu);
29 if (!field_ty.hasRuntimeBits(zcu)) continue;
30 if (field_ty.isRuntimeFloat())
31 any_fp = true
32 else if (!field_ty.isAbiInt(zcu))
33 break :fields;
34 field_count += 1;
35 if (field_count > 2) break :fields;
36 }
37 std.debug.assert(field_count > 0 and field_count <= 2);
38 if (any_fp) return .fields;
39 }
40
41 // TODO this doesn't exactly match what clang produces but its better than nothing
42 const bit_size = ty.abiSize(zcu) * 8;
43 if (bit_size > max_byval_size) return .memory;
44 if (bit_size > max_byval_size / 2) return .double_integer;
45 return .integer;
46 },
47 .@"union" => {
48 if (ty.containerLayout(zcu) == .@"packed") {
49 if (ty.bitSize(zcu) > max_byval_size) return .memory;
50 return .byval;
51 }
52 // TODO this doesn't exactly match what clang produces but its better than nothing
53 const bit_size = ty.abiSize(zcu) * 8;
54 if (bit_size > max_byval_size) return .memory;
55 if (bit_size > max_byval_size / 2) return .double_integer;
56 return .integer;
57 },
58 .bool => return .integer,
59 .int, .@"enum", .error_set => {
60 const bit_size = ty.bitSize(zcu);
61 if (bit_size > max_byval_size) return .memory;
62 return .byval;
63 },
64 .float => return switch (ty.floatBits(target)) {
65 else => unreachable,
66 16, 32, 64, 128 => .byval,
67 80 => switch (max_byval_size) {
68 else => unreachable,
69 64 => .memory,
70 128 => .double_integer,
71 },
72 },
73 .vector => {
74 const bit_size = ty.bitSize(zcu);
75 if (bit_size > max_byval_size) return .memory;
76 return .integer;
77 },
78 .optional => {
79 std.debug.assert(ty.isPtrLikeOptional(zcu));
80 return .byval;
81 },
82 .pointer => {
83 std.debug.assert(!ty.isSlice(zcu));
84 return .byval;
85 },
86 .error_union,
87 .frame,
88 .@"anyframe",
89 .noreturn,
90 .void,
91 .type,
92 .comptime_float,
93 .comptime_int,
94 .undefined,
95 .null,
96 .@"fn",
97 .@"opaque",
98 .spirv,
99 .enum_literal,
100 .array,
101 => unreachable,
102 }
103}
104
105pub const SystemClass = enum { integer, float, memory, none };
106
107/// There are a maximum of 8 possible return slots. Returned values are in
108/// the beginning of the array; unused slots are filled with .none.
109pub fn classifySystem(ty: Type, zcu: *Zcu) [8]SystemClass {
110 var result: [8]SystemClass = @splat(.none);
111 const memory_class = [_]SystemClass{
112 .memory, .none, .none, .none,
113 .none, .none, .none, .none,
114 };
115 switch (ty.zigTypeTag(zcu)) {
116 .bool, .void, .noreturn => {
117 result[0] = .integer;
118 return result;
119 },
120 .pointer => switch (ty.ptrSize(zcu)) {
121 .slice => {
122 result[0] = .integer;
123 result[1] = .integer;
124 return result;
125 },
126 else => {
127 result[0] = .integer;
128 return result;
129 },
130 },
131 .optional => {
132 if (ty.isPtrLikeOptional(zcu)) {
133 result[0] = .integer;
134 return result;
135 }
136 return memory_class;
137 },
138 .int, .@"enum", .error_set => {
139 const int_bits = ty.intInfo(zcu).bits;
140 if (int_bits <= 64) {
141 result[0] = .integer;
142 return result;
143 }
144 if (int_bits <= 128) {
145 result[0] = .integer;
146 result[1] = .integer;
147 return result;
148 }
149 unreachable; // support > 128 bit int arguments
150 },
151 .float => {
152 const target = zcu.getTarget();
153
154 const float_bits = ty.floatBits(target);
155 const float_reg_size: u32 = if (target.cpu.has(.riscv, .d)) 64 else 32;
156 if (float_bits <= float_reg_size) {
157 result[0] = .float;
158 return result;
159 }
160 unreachable; // support split float args
161 },
162 .error_union => {
163 const payload_ty = ty.errorUnionPayload(zcu);
164
165 // the error union itself
166 result[0] = .integer;
167
168 // anyerror!void can fit into one register
169 if (!payload_ty.hasRuntimeBits(zcu)) return result;
170
171 return memory_class;
172 },
173 .@"struct", .@"union" => {
174 const layout = ty.containerLayout(zcu);
175 const ty_size = ty.abiSize(zcu);
176
177 if (layout == .@"packed") {
178 assert(ty_size <= 16);
179 result[0] = .integer;
180 if (ty_size > 8) result[1] = .integer;
181 return result;
182 }
183
184 return memory_class;
185 },
186 .array => {
187 const ty_size = ty.abiSize(zcu);
188 if (ty_size <= 8) {
189 result[0] = .integer;
190 return result;
191 }
192 if (ty_size <= 16) {
193 result[0] = .integer;
194 result[1] = .integer;
195 return result;
196 }
197 return memory_class;
198 },
199 .vector => {
200 // we pass vectors through integer registers if they are small enough to fit.
201 const vec_bits = ty.bitSize(zcu);
202 if (vec_bits <= 64) {
203 result[0] = .integer;
204 return result;
205 }
206 // we should pass vector registers of size <= 128 through 2 integer registers
207 // but we haven't implemented seperating vector registers into register_pairs
208 return memory_class;
209 },
210 else => |bad_ty| std.debug.panic("classifySystem {s}", .{@tagName(bad_ty)}),
211 }
212}
213
214fn classifyStruct(
215 result: *[8]Class,
216 byte_offset: *u64,
217 loaded_struct: InternPool.LoadedStructType,
218 zcu: *Zcu,
219) void {
220 const ip = &zcu.intern_pool;
221 var field_it = loaded_struct.iterateRuntimeOrder(ip);
222
223 while (field_it.next()) |field_index| {
224 const field_ty = Type.fromInterned(loaded_struct.field_types.get(ip)[field_index]);
225 const field_align = loaded_struct.fieldAlign(ip, field_index);
226 byte_offset.* = std.mem.alignForward(
227 u64,
228 byte_offset.*,
229 field_align.toByteUnits() orelse field_ty.abiAlignment(zcu).toByteUnits().?,
230 );
231 if (zcu.typeToStruct(field_ty)) |field_loaded_struct| {
232 if (field_loaded_struct.layout != .@"packed") {
233 classifyStruct(result, byte_offset, field_loaded_struct, zcu);
234 continue;
235 }
236 }
237 const field_class = std.mem.sliceTo(&classifySystem(field_ty, zcu), .none);
238 const field_size = field_ty.abiSize(zcu);
239
240 combine: {
241 const result_class = &result[@intCast(byte_offset.* / 8)];
242 if (result_class.* == field_class[0]) {
243 break :combine;
244 }
245
246 if (result_class.* == .none) {
247 result_class.* = field_class[0];
248 break :combine;
249 }
250 assert(field_class[0] != .none);
251
252 // "If one of the classes is MEMORY, the result is the MEMORY class."
253 if (result_class.* == .memory or field_class[0] == .memory) {
254 result_class.* = .memory;
255 break :combine;
256 }
257
258 // "If one of the classes is INTEGER, the result is the INTEGER."
259 if (result_class.* == .integer or field_class[0] == .integer) {
260 result_class.* = .integer;
261 break :combine;
262 }
263
264 result_class.* = .integer;
265 }
266 @memcpy(result[@intCast(byte_offset.* / 8 + 1)..][0 .. field_class.len - 1], field_class[1..]);
267 byte_offset.* += field_size;
268 }
269}
270
271const allocatable_registers = Registers.Integer.all_regs ++ Registers.Float.all_regs ++ Registers.Vector.all_regs;
272pub const RegisterManager = RegisterManagerFn(@import("CodeGen.zig"), Register, &allocatable_registers);
273
274const RegisterBitSet = RegisterManager.RegisterBitSet;
275
276pub const RegisterClass = enum {
277 int,
278 float,
279 vector,
280};
281
282pub const Registers = struct {
283 pub const all_preserved = Integer.callee_preserved_regs ++ Float.callee_preserved_regs;
284
285 pub const Integer = struct {
286 // zig fmt: off
287 pub const general_purpose = initRegBitSet(0, callee_preserved_regs.len);
288 pub const function_arg = initRegBitSet(callee_preserved_regs.len, function_arg_regs.len);
289 pub const function_ret = initRegBitSet(callee_preserved_regs.len, function_ret_regs.len);
290 pub const temporary = initRegBitSet(callee_preserved_regs.len + function_arg_regs.len, temporary_regs.len);
291 // zig fmt: on
292
293 pub const callee_preserved_regs = [_]Register{
294 // .s0 is omitted to be used as the frame pointer register
295 .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11,
296 };
297
298 pub const function_arg_regs = [_]Register{
299 .a0, .a1, .a2, .a3, .a4, .a5, .a6, .a7,
300 };
301
302 pub const function_ret_regs = [_]Register{
303 .a0, .a1,
304 };
305
306 pub const temporary_regs = [_]Register{
307 .t0, .t1, .t2, .t3, .t4, .t5, .t6,
308 };
309
310 pub const all_regs = callee_preserved_regs ++ function_arg_regs ++ temporary_regs;
311 };
312
313 pub const Float = struct {
314 // zig fmt: off
315 pub const general_purpose = initRegBitSet(Integer.all_regs.len, callee_preserved_regs.len);
316 pub const function_arg = initRegBitSet(Integer.all_regs.len + callee_preserved_regs.len, function_arg_regs.len);
317 pub const function_ret = initRegBitSet(Integer.all_regs.len + callee_preserved_regs.len, function_ret_regs.len);
318 pub const temporary = initRegBitSet(Integer.all_regs.len + callee_preserved_regs.len + function_arg_regs.len, temporary_regs.len);
319 // zig fmt: on
320
321 pub const callee_preserved_regs = [_]Register{
322 .fs0, .fs1, .fs2, .fs3, .fs4, .fs5, .fs6, .fs7, .fs8, .fs9, .fs10, .fs11,
323 };
324
325 pub const function_arg_regs = [_]Register{
326 .fa0, .fa1, .fa2, .fa3, .fa4, .fa5, .fa6, .fa7,
327 };
328
329 pub const function_ret_regs = [_]Register{
330 .fa0, .fa1,
331 };
332
333 pub const temporary_regs = [_]Register{
334 .ft0, .ft1, .ft2, .ft3, .ft4, .ft5, .ft6, .ft7, .ft8, .ft9, .ft10, .ft11,
335 };
336
337 pub const all_regs = callee_preserved_regs ++ function_arg_regs ++ temporary_regs;
338 };
339
340 pub const Vector = struct {
341 pub const general_purpose = initRegBitSet(Integer.all_regs.len + Float.all_regs.len, all_regs.len);
342
343 // zig fmt: off
344 pub const all_regs = [_]Register{
345 .v0, .v1, .v2, .v3, .v4, .v5, .v6, .v7,
346 .v8, .v9, .v10, .v11, .v12, .v13, .v14, .v15,
347 .v16, .v17, .v18, .v19, .v20, .v21, .v22, .v23,
348 .v24, .v25, .v26, .v27, .v28, .v29, .v30, .v31,
349 };
350 // zig fmt: on
351 };
352};
353
354fn initRegBitSet(start: usize, length: usize) RegisterBitSet {
355 var set = RegisterBitSet.empty;
356 set.setRangeValue(.{
357 .start = start,
358 .end = start + length,
359 }, true);
360 return set;
361}