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