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}