| 1 | const std = @import("std"); |
| 2 | const bits = @import("bits.zig"); |
| 3 | const Register = bits.Register; |
| 4 | const RegisterManagerFn = @import("../../register_manager.zig").RegisterManager; |
| 5 | const Type = @import("../../Type.zig"); |
| 6 | const InternPool = @import("../../InternPool.zig"); |
| 7 | const Zcu = @import("../../Zcu.zig"); |
| 8 | const assert = std.debug.assert; |
| 9 | |
| 10 | pub const Class = enum { memory, byval, integer, double_integer, fields }; |
| 11 | |
| 12 | pub 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 | |
| 105 | pub 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. |
| 109 | pub 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 | |
| 214 | fn 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 | |
| 271 | const allocatable_registers = Registers.Integer.all_regs ++ Registers.Float.all_regs ++ Registers.Vector.all_regs; |
| 272 | pub const RegisterManager = RegisterManagerFn(@import("CodeGen.zig"), Register, &allocatable_registers); |
| 273 | |
| 274 | const RegisterBitSet = RegisterManager.RegisterBitSet; |
| 275 | |
| 276 | pub const RegisterClass = enum { |
| 277 | int, |
| 278 | float, |
| 279 | vector, |
| 280 | }; |
| 281 | |
| 282 | pub 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 | |
| 354 | fn 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 | } |