authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-05-14 01:50:07+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-05-19 19:37:28+02:00
log2aee2302515ba444999b82c2e40cbc35dee08baf
tree356e7ffa416b3f319243412e68e6a8b26b10997e
parent357561840d705bafbeb22b4919d6a79b208cd8fe

x64: add unordered cmp with EFLAGS


4 files changed, 595 insertions(+), 266 deletions(-)

src/arch/x86_64/CodeGen.zig+128-5
......@@ -202,6 +202,7 @@ pub const MCValue = union(enum) {
202202 fn isRegister(mcv: MCValue) bool {
203203 return switch (mcv) {
204204 .register => true,
205 .avx_register => true,
205206 else => false,
206207 };
207208 }
......@@ -971,6 +972,7 @@ pub fn spillCompareFlagsIfOccupied(self: *Self) !void {
971972 .compare_flags_signed,
972973 .compare_flags_unsigned,
973974 => try self.allocRegOrMem(inst_to_save, true),
975 .avx_register => try self.allocRegOrMem(inst_to_save, false),
974976 else => unreachable,
975977 };
976978
......@@ -988,6 +990,7 @@ pub fn spillCompareFlagsIfOccupied(self: *Self) !void {
988990 .register_overflow_signed,
989991 .register_overflow_unsigned,
990992 => |reg| self.register_manager.freeReg(reg),
993 .avx_register => |reg| self.avx_register_manager.freeReg(reg),
991994 else => {},
992995 }
993996 }
......@@ -2497,7 +2500,6 @@ fn reuseOperand(
24972500fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!void {
24982501 const elem_ty = ptr_ty.elemType();
24992502 const abi_size = elem_ty.abiSize(self.target.*);
2500 std.log.warn("{} => {}, {}", .{ ptr_ty.fmtDebug(), ptr, dst_mcv });
25012503 switch (ptr) {
25022504 .none => unreachable,
25032505 .undef => unreachable,
......@@ -2627,7 +2629,6 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue
26272629
26282630fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type) InnerError!void {
26292631 const abi_size = value_ty.abiSize(self.target.*);
2630 std.log.warn("{} => {}, {} => {}", .{ ptr_ty.fmtDebug(), ptr, value_ty.fmtDebug(), value });
26312632 switch (ptr) {
26322633 .none => unreachable,
26332634 .undef => unreachable,
......@@ -3375,13 +3376,39 @@ fn genBinOp(
33753376 const rhs = try self.resolveInst(rhs_air);
33763377 const lhs_ty = self.air.typeOf(lhs_air);
33773378 const rhs_ty = self.air.typeOf(rhs_air);
3378 if (lhs_ty.zigTypeTag() == .Vector or lhs_ty.zigTypeTag() == .Float) {
3379 if (lhs_ty.zigTypeTag() == .Vector) {
33793380 return self.fail("TODO implement genBinOp for {}", .{lhs_ty.fmtDebug()});
33803381 }
33813382 if (lhs_ty.abiSize(self.target.*) > 8) {
33823383 return self.fail("TODO implement genBinOp for {}", .{lhs_ty.fmtDebug()});
33833384 }
33843385
3386 if (lhs_ty.zigTypeTag() == .Float) {
3387 switch (tag) {
3388 .add => {
3389 const dst_reg: AvxRegister = blk: {
3390 const reg = try self.avx_register_manager.allocReg(null);
3391 try self.genSetAvxReg(lhs_ty, reg, lhs);
3392 break :blk reg.to128();
3393 };
3394 const dst_lock = self.avx_register_manager.lockRegAssumeUnused(dst_reg);
3395 defer self.avx_register_manager.unlockReg(dst_lock);
3396
3397 const src_reg: AvxRegister = blk: {
3398 const reg = try self.avx_register_manager.allocReg(null);
3399 try self.genSetAvxReg(lhs_ty, reg, rhs);
3400 break :blk reg.to128();
3401 };
3402 const src_lock = self.avx_register_manager.lockRegAssumeUnused(src_reg);
3403 defer self.avx_register_manager.unlockReg(src_lock);
3404
3405 try self.genBinOpMir(.add_f64, lhs_ty, .{ .avx_register = dst_reg }, .{ .avx_register = src_reg });
3406 return MCValue{ .avx_register = dst_reg };
3407 },
3408 else => unreachable,
3409 }
3410 }
3411
33853412 const is_commutative: bool = switch (tag) {
33863413 .add,
33873414 .addwrap,
......@@ -3550,8 +3577,28 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu
35503577 },
35513578 }
35523579 },
3553 .avx_register => {
3554 return self.fail("TODO genBinOp for AVX register", .{});
3580 .avx_register => |dst_reg| {
3581 switch (src_mcv) {
3582 .avx_register => |src_reg| {
3583 switch (dst_ty.zigTypeTag()) {
3584 .Float => switch (dst_ty.tag()) {
3585 .f64 => {
3586 _ = try self.addInst(.{
3587 .tag = mir_tag,
3588 .ops = (Mir.Ops(AvxRegister, AvxRegister){
3589 .reg1 = dst_reg.to128(),
3590 .reg2 = src_reg.to128(),
3591 }).encode(),
3592 .data = undefined,
3593 });
3594 },
3595 else => return self.fail("TODO genBinOp for AVX register and type {}", .{dst_ty.fmtDebug()}),
3596 },
3597 else => return self.fail("TODO genBinOp for AVX register and type {}", .{dst_ty.fmtDebug()}),
3598 }
3599 },
3600 else => return self.fail("TODO genBinOp for AVX register", .{}),
3601 }
35553602 },
35563603 .ptr_stack_offset, .stack_offset => |off| {
35573604 if (off > math.maxInt(i32)) {
......@@ -4209,6 +4256,37 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {
42094256 self.compare_flags_inst = inst;
42104257
42114258 const result: MCValue = result: {
4259 if (ty.zigTypeTag() == .Float) {
4260 const lhs = try self.resolveInst(bin_op.lhs);
4261 const rhs = try self.resolveInst(bin_op.rhs);
4262
4263 const dst_reg: AvxRegister = blk: {
4264 const reg = try self.avx_register_manager.allocReg(null);
4265 try self.genSetAvxReg(ty, reg, lhs);
4266 break :blk reg.to128();
4267 };
4268 const dst_lock = self.avx_register_manager.lockRegAssumeUnused(dst_reg);
4269 defer self.avx_register_manager.unlockReg(dst_lock);
4270
4271 const src_reg: AvxRegister = blk: {
4272 const reg = try self.avx_register_manager.allocReg(null);
4273 try self.genSetAvxReg(ty, reg, rhs);
4274 break :blk reg.to128();
4275 };
4276 const src_lock = self.avx_register_manager.lockRegAssumeUnused(src_reg);
4277 defer self.avx_register_manager.unlockReg(src_lock);
4278
4279 _ = try self.addInst(.{
4280 .tag = .cmp_f64,
4281 .ops = (Mir.Ops(AvxRegister, AvxRegister){
4282 .reg1 = dst_reg,
4283 .reg2 = src_reg,
4284 }).encode(),
4285 .data = undefined,
4286 });
4287
4288 break :result MCValue{ .compare_flags_unsigned = op };
4289 }
42124290 // There are 2 operands, destination and source.
42134291 // Either one, but not both, can be a memory operand.
42144292 // Source operand can be an immediate, 8 bits or 32 bits.
......@@ -5962,6 +6040,51 @@ fn genSetAvxReg(self: *Self, ty: Type, reg: AvxRegister, mcv: MCValue) InnerErro
59626040 else => return self.fail("TODO genSetAvxReg from stack offset for type {}", .{ty.fmtDebug()}),
59636041 }
59646042 },
6043 .avx_register => |src_reg| {
6044 switch (ty.zigTypeTag()) {
6045 .Float => {
6046 switch (ty.tag()) {
6047 .f32 => return self.fail("TODO genSetAvxReg from register for f32", .{}),
6048 .f64 => {
6049 _ = try self.addInst(.{
6050 .tag = .mov_f64,
6051 .ops = (Mir.Ops(AvxRegister, AvxRegister){
6052 .reg1 = reg.to128(),
6053 .reg2 = src_reg.to128(),
6054 .flags = 0b10,
6055 }).encode(),
6056 .data = undefined,
6057 });
6058 },
6059 else => return self.fail("TODO genSetAvxReg from register for {}", .{ty.fmtDebug()}),
6060 }
6061 },
6062 else => return self.fail("TODO genSetAvxReg from register for type {}", .{ty.fmtDebug()}),
6063 }
6064 },
6065 .memory => {
6066 switch (ty.zigTypeTag()) {
6067 .Float => {
6068 switch (ty.tag()) {
6069 .f32 => return self.fail("TODO genSetAvxReg from memory for f32", .{}),
6070 .f64 => {
6071 const base_reg = try self.register_manager.allocReg(null);
6072 try self.loadMemPtrIntoRegister(base_reg, Type.usize, mcv);
6073 _ = try self.addInst(.{
6074 .tag = .mov_f64,
6075 .ops = (Mir.Ops(AvxRegister, Register){
6076 .reg1 = reg.to128(),
6077 .reg2 = base_reg.to64(),
6078 }).encode(),
6079 .data = .{ .imm = 0 },
6080 });
6081 },
6082 else => return self.fail("TODO genSetAvxReg from memory for {}", .{ty.fmtDebug()}),
6083 }
6084 },
6085 else => return self.fail("TODO genSetAvxReg from memory for type {}", .{ty.fmtDebug()}),
6086 }
6087 },
59656088 else => |other| {
59666089 return self.fail("TODO genSetAvxReg from {}", .{other});
59676090 },
src/arch/x86_64/Emit.zig+207-10
......@@ -186,6 +186,10 @@ pub fn lowerMir(emit: *Emit) InnerError!void {
186186 // AVX instructions
187187 .mov_f64 => try emit.mirMovF64(inst),
188188
189 .add_f64 => try emit.mirAddF64(inst),
190
191 .cmp_f64 => try emit.mirCmpF64(inst),
192
189193 // Pseudo-instructions
190194 .call_extern => try emit.mirCallExtern(inst),
191195
......@@ -960,11 +964,11 @@ fn mirMovF64(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
960964 assert(tag == .mov_f64);
961965 const ops = emit.mir.instructions.items(.ops)[inst];
962966 const flags = @truncate(u2, ops);
963 const imm = emit.mir.instructions.items(.data)[inst].imm;
964967
965968 switch (flags) {
966969 0b00 => {
967970 const decoded = Mir.Ops(AvxRegister, GpRegister).decode(ops);
971 const imm = emit.mir.instructions.items(.data)[inst].imm;
968972 return lowerToRmEnc(.vmovsd, Register.avxReg(decoded.reg1), RegisterOrMemory.mem(.qword_ptr, .{
969973 .disp = imm,
970974 .base = decoded.reg2,
......@@ -972,11 +976,63 @@ fn mirMovF64(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
972976 },
973977 0b01 => {
974978 const decoded = Mir.Ops(GpRegister, AvxRegister).decode(ops);
979 const imm = emit.mir.instructions.items(.data)[inst].imm;
975980 return lowerToMrEnc(.vmovsd, RegisterOrMemory.mem(.qword_ptr, .{
976981 .disp = imm,
977982 .base = decoded.reg1,
978983 }), Register.avxReg(decoded.reg2), emit.code);
979984 },
985 0b10 => {
986 const decoded = Mir.Ops(AvxRegister, AvxRegister).decode(ops);
987 return lowerToRvmEnc(
988 .vmovsd,
989 Register.avxReg(decoded.reg1),
990 Register.avxReg(decoded.reg1),
991 RegisterOrMemory.avxReg(decoded.reg2),
992 emit.code,
993 );
994 },
995 else => return emit.fail("TODO unused variant 0b{b} for mov_f64", .{flags}),
996 }
997}
998
999fn mirAddF64(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
1000 const tag = emit.mir.instructions.items(.tag)[inst];
1001 assert(tag == .add_f64);
1002 const ops = emit.mir.instructions.items(.ops)[inst];
1003 const flags = @truncate(u2, ops);
1004
1005 switch (flags) {
1006 0b00 => {
1007 const decoded = Mir.Ops(AvxRegister, AvxRegister).decode(ops);
1008 return lowerToRvmEnc(
1009 .vaddsd,
1010 Register.avxReg(decoded.reg1),
1011 Register.avxReg(decoded.reg1),
1012 RegisterOrMemory.avxReg(decoded.reg2),
1013 emit.code,
1014 );
1015 },
1016 else => return emit.fail("TODO unused variant 0b{b} for mov_f64", .{flags}),
1017 }
1018}
1019
1020fn mirCmpF64(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
1021 const tag = emit.mir.instructions.items(.tag)[inst];
1022 assert(tag == .cmp_f64);
1023 const ops = emit.mir.instructions.items(.ops)[inst];
1024 const flags = @truncate(u2, ops);
1025
1026 switch (flags) {
1027 0b00 => {
1028 const decoded = Mir.Ops(AvxRegister, AvxRegister).decode(ops);
1029 return lowerToRmEnc(
1030 .vucomisd,
1031 Register.avxReg(decoded.reg1),
1032 RegisterOrMemory.avxReg(decoded.reg2),
1033 emit.code,
1034 );
1035 },
9801036 else => return emit.fail("TODO unused variant 0b{b} for mov_f64", .{flags}),
9811037 }
9821038}
......@@ -1217,6 +1273,9 @@ const Tag = enum {
12171273 cmovb,
12181274 cmovnae,
12191275 vmovsd,
1276 vaddsd,
1277 vcmpsd,
1278 vucomisd,
12201279
12211280 fn isSetCC(tag: Tag) bool {
12221281 return switch (tag) {
......@@ -1301,6 +1360,12 @@ const Encoding = enum {
13011360
13021361 /// OP r64, r/m64, imm32
13031362 rmi,
1363
1364 /// OP xmm1, xmm2, xmm3/m64
1365 rvm,
1366
1367 /// OP xmm1, xmm2, xmm3/m64, imm8
1368 rvmi,
13041369};
13051370
13061371const OpCode = union(enum) {
......@@ -1452,6 +1517,7 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode {
14521517 .cmovb, .cmovnae => OpCode.twoByte(0x0f, 0x42),
14531518 .cmovl, .cmovng => OpCode.twoByte(0x0f, 0x4c),
14541519 .vmovsd => OpCode.oneByte(0x10),
1520 .vucomisd => OpCode.oneByte(0x2e),
14551521 else => null,
14561522 },
14571523 .oi => return switch (tag) {
......@@ -1470,6 +1536,15 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode {
14701536 .imul => OpCode.oneByte(if (is_one_byte) 0x6b else 0x69),
14711537 else => null,
14721538 },
1539 .rvm => return switch (tag) {
1540 .vaddsd => OpCode.oneByte(0x58),
1541 .vmovsd => OpCode.oneByte(0x10),
1542 else => null,
1543 },
1544 .rvmi => return switch (tag) {
1545 .vcmpsd => OpCode.oneByte(0xc2),
1546 else => null,
1547 },
14731548 }
14741549}
14751550
......@@ -1578,6 +1653,16 @@ inline fn getVexPrefix(tag: Tag, enc: Encoding) ?VexPrefix {
15781653 },
15791654 .rm => switch (tag) {
15801655 .vmovsd => break :blk .{ .lig = true, .simd_prefix = .p_f2, .wig = true },
1656 .vucomisd => break :blk .{ .lig = true, .simd_prefix = .p_66, .wig = true },
1657 else => return null,
1658 },
1659 .rvm => switch (tag) {
1660 .vaddsd => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f2, .wig = true },
1661 .vmovsd => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f2, .wig = true },
1662 else => return null,
1663 },
1664 .rvmi => switch (tag) {
1665 .vcmpsd => break :blk .{ .reg = .nds, .lig = true, .simd_prefix = .p_f2, .wig = true },
15811666 else => return null,
15821667 },
15831668 else => unreachable,
......@@ -2013,15 +2098,33 @@ fn lowerToRmEnc(
20132098 const opc = getOpCode(tag, .rm, reg.size() == 8 or reg_or_mem.size() == 8).?;
20142099 switch (reg_or_mem) {
20152100 .register => |src_reg| {
2016 const encoder = try Encoder.init(code, 4);
2017 if (reg.size() == 16) {
2018 encoder.prefix16BitMode();
2019 }
2020 encoder.rex(.{
2021 .w = setRexWRegister(reg) or setRexWRegister(src_reg),
2022 .r = reg.isExtended(),
2023 .b = src_reg.isExtended(),
2024 });
2101 const encoder: Encoder = blk: {
2102 switch (reg) {
2103 .register => {
2104 const encoder = try Encoder.init(code, 4);
2105 if (reg.size() == 16) {
2106 encoder.prefix16BitMode();
2107 }
2108 encoder.rex(.{
2109 .w = setRexWRegister(reg) or setRexWRegister(src_reg),
2110 .r = reg.isExtended(),
2111 .b = src_reg.isExtended(),
2112 });
2113 break :blk encoder;
2114 },
2115 .avx_register => {
2116 const encoder = try Encoder.init(code, 5);
2117 var vex_prefix = getVexPrefix(tag, .rm).?;
2118 const vex = &vex_prefix.prefix;
2119 vex.rex(.{
2120 .r = reg.isExtended(),
2121 .b = src_reg.isExtended(),
2122 });
2123 encoder.vex(vex_prefix.prefix);
2124 break :blk encoder;
2125 },
2126 }
2127 };
20252128 opc.encode(encoder);
20262129 encoder.modRm_direct(reg.lowId(), src_reg.lowId());
20272130 },
......@@ -2188,6 +2291,79 @@ fn lowerToRmiEnc(
21882291 encodeImm(encoder, imm, reg.size());
21892292}
21902293
2294fn lowerToRvmEnc(
2295 tag: Tag,
2296 reg1: Register,
2297 reg2: Register,
2298 reg_or_mem: RegisterOrMemory,
2299 code: *std.ArrayList(u8),
2300) InnerError!void {
2301 const opc = getOpCode(tag, .rvm, false).?;
2302 var vex_prefix = getVexPrefix(tag, .rvm).?;
2303 const vex = &vex_prefix.prefix;
2304 switch (reg_or_mem) {
2305 .register => |reg3| {
2306 if (vex_prefix.reg) |vvvv| {
2307 switch (vvvv) {
2308 .nds => vex.reg(reg2.avx_register.id()),
2309 else => unreachable, // TODO
2310 }
2311 }
2312 const encoder = try Encoder.init(code, 5);
2313 vex.rex(.{
2314 .r = reg1.isExtended(),
2315 .b = reg3.isExtended(),
2316 });
2317 encoder.vex(vex_prefix.prefix);
2318 opc.encode(encoder);
2319 encoder.modRm_direct(reg1.lowId(), reg3.lowId());
2320 },
2321 .memory => |dst_mem| {
2322 _ = dst_mem;
2323 unreachable; // TODO
2324 },
2325 }
2326}
2327
2328fn lowerToRvmiEnc(
2329 tag: Tag,
2330 reg1: Register,
2331 reg2: Register,
2332 reg_or_mem: RegisterOrMemory,
2333 imm: u32,
2334 code: *std.ArrayList(u8),
2335) InnerError!void {
2336 const opc = getOpCode(tag, .rvmi, false).?;
2337 var vex_prefix = getVexPrefix(tag, .rvmi).?;
2338 const vex = &vex_prefix.prefix;
2339 const encoder: Encoder = blk: {
2340 switch (reg_or_mem) {
2341 .register => |reg3| {
2342 if (vex_prefix.reg) |vvvv| {
2343 switch (vvvv) {
2344 .nds => vex.reg(reg2.avx_register.id()),
2345 else => unreachable, // TODO
2346 }
2347 }
2348 const encoder = try Encoder.init(code, 5);
2349 vex.rex(.{
2350 .r = reg1.isExtended(),
2351 .b = reg3.isExtended(),
2352 });
2353 encoder.vex(vex_prefix.prefix);
2354 opc.encode(encoder);
2355 encoder.modRm_direct(reg1.lowId(), reg3.lowId());
2356 break :blk encoder;
2357 },
2358 .memory => |dst_mem| {
2359 _ = dst_mem;
2360 unreachable; // TODO
2361 },
2362 }
2363 };
2364 encodeImm(encoder, imm, 8); // TODO
2365}
2366
21912367fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void {
21922368 assert(expected.len > 0);
21932369 if (mem.eql(u8, expected, given)) return;
......@@ -2598,3 +2774,24 @@ test "lower RMI encoding" {
25982774 try lowerToRmiEnc(.imul, Register.reg(.r12w), RegisterOrMemory.reg(.r12w), 0x10, emit.code());
25992775 try expectEqualHexStrings("\x66\x45\x69\xE4\x10\x00", emit.lowered(), "imul r12w, r12w, 0x10");
26002776}
2777
2778test "lower to RVM encoding" {
2779 var emit = TestEmit.init();
2780 defer emit.deinit();
2781 try lowerToRvmEnc(
2782 .vaddsd,
2783 Register.avxReg(.xmm0),
2784 Register.avxReg(.xmm1),
2785 RegisterOrMemory.avxReg(.xmm2),
2786 emit.code(),
2787 );
2788 try expectEqualHexStrings("\xC5\xF3\x58\xC2", emit.lowered(), "vaddsd xmm0, xmm1, xmm2");
2789 try lowerToRvmEnc(
2790 .vaddsd,
2791 Register.avxReg(.xmm0),
2792 Register.avxReg(.xmm0),
2793 RegisterOrMemory.avxReg(.xmm1),
2794 emit.code(),
2795 );
2796 try expectEqualHexStrings("\xC5\xFB\x58\xC1", emit.lowered(), "vaddsd xmm0, xmm0, xmm1");
2797}
src/arch/x86_64/Mir.zig+10-1
......@@ -353,9 +353,18 @@ pub const Inst = struct {
353353 /// AVX instructions
354354 /// ops flags: form:
355355 /// 0b00 reg1, qword ptr [reg2 + imm32]
356 /// 0b10 qword ptr [reg1 + imm32], reg2
356 /// 0b01 qword ptr [reg1 + imm32], reg2
357 /// 0b10 reg1, reg2
357358 mov_f64,
358359
360 /// ops flags: form:
361 /// 0b00 reg1, reg1, reg2
362 add_f64,
363
364 /// ops flags: form:
365 ///
366 cmp_f64,
367
359368 /// Pseudo-instructions
360369 /// call extern function
361370 /// Notes:
src/register_manager.zig+250-250
......@@ -338,253 +338,253 @@ pub fn RegisterManager(
338338 };
339339}
340340
341const MockRegister1 = enum(u2) {
342 r0,
343 r1,
344 r2,
345 r3,
346
347 pub fn id(reg: MockRegister1) u2 {
348 return @enumToInt(reg);
349 }
350
351 const allocatable_registers = [_]MockRegister1{ .r2, .r3 };
352};
353
354const MockRegister2 = enum(u2) {
355 r0,
356 r1,
357 r2,
358 r3,
359
360 pub fn id(reg: MockRegister2) u2 {
361 return @enumToInt(reg);
362 }
363
364 const allocatable_registers = [_]MockRegister2{ .r0, .r1, .r2, .r3 };
365};
366
367fn MockFunction(comptime Register: type) type {
368 return struct {
369 allocator: Allocator,
370 register_manager: RegisterManager(Self, Register, &Register.allocatable_registers) = .{},
371 spilled: std.ArrayListUnmanaged(Register) = .{},
372
373 const Self = @This();
374
375 pub fn deinit(self: *Self) void {
376 self.spilled.deinit(self.allocator);
377 }
378
379 pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void {
380 _ = inst;
381 try self.spilled.append(self.allocator, reg);
382 }
383
384 pub fn genAdd(self: *Self, res: Register, lhs: Register, rhs: Register) !void {
385 _ = self;
386 _ = res;
387 _ = lhs;
388 _ = rhs;
389 }
390 };
391}
392
393const MockFunction1 = MockFunction(MockRegister1);
394const MockFunction2 = MockFunction(MockRegister2);
395
396test "default state" {
397 const allocator = std.testing.allocator;
398
399 var function = MockFunction1{
400 .allocator = allocator,
401 };
402 defer function.deinit();
403
404 try expect(!function.register_manager.isRegAllocated(.r2));
405 try expect(!function.register_manager.isRegAllocated(.r3));
406 try expect(function.register_manager.isRegFree(.r2));
407 try expect(function.register_manager.isRegFree(.r3));
408}
409
410test "tryAllocReg: no spilling" {
411 const allocator = std.testing.allocator;
412
413 var function = MockFunction1{
414 .allocator = allocator,
415 };
416 defer function.deinit();
417
418 const mock_instruction: Air.Inst.Index = 1;
419
420 try expectEqual(@as(?MockRegister1, .r2), function.register_manager.tryAllocReg(mock_instruction));
421 try expectEqual(@as(?MockRegister1, .r3), function.register_manager.tryAllocReg(mock_instruction));
422 try expectEqual(@as(?MockRegister1, null), function.register_manager.tryAllocReg(mock_instruction));
423
424 try expect(function.register_manager.isRegAllocated(.r2));
425 try expect(function.register_manager.isRegAllocated(.r3));
426 try expect(!function.register_manager.isRegFree(.r2));
427 try expect(!function.register_manager.isRegFree(.r3));
428
429 function.register_manager.freeReg(.r2);
430 function.register_manager.freeReg(.r3);
431
432 try expect(function.register_manager.isRegAllocated(.r2));
433 try expect(function.register_manager.isRegAllocated(.r3));
434 try expect(function.register_manager.isRegFree(.r2));
435 try expect(function.register_manager.isRegFree(.r3));
436}
437
438test "allocReg: spilling" {
439 const allocator = std.testing.allocator;
440
441 var function = MockFunction1{
442 .allocator = allocator,
443 };
444 defer function.deinit();
445
446 const mock_instruction: Air.Inst.Index = 1;
447
448 try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction));
449 try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction));
450
451 // Spill a register
452 try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction));
453 try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items);
454
455 // No spilling necessary
456 function.register_manager.freeReg(.r3);
457 try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction));
458 try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items);
459
460 // Locked registers
461 function.register_manager.freeReg(.r3);
462 {
463 const lock = function.register_manager.lockReg(.r2);
464 defer if (lock) |reg| function.register_manager.unlockReg(reg);
465
466 try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction));
467 }
468 try expect(!function.register_manager.lockedRegsExist());
469}
470
471test "tryAllocRegs" {
472 const allocator = std.testing.allocator;
473
474 var function = MockFunction2{
475 .allocator = allocator,
476 };
477 defer function.deinit();
478
479 try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, function.register_manager.tryAllocRegs(3, .{ null, null, null }).?);
480
481 try expect(function.register_manager.isRegAllocated(.r0));
482 try expect(function.register_manager.isRegAllocated(.r1));
483 try expect(function.register_manager.isRegAllocated(.r2));
484 try expect(!function.register_manager.isRegAllocated(.r3));
485
486 // Locked registers
487 function.register_manager.freeReg(.r0);
488 function.register_manager.freeReg(.r2);
489 function.register_manager.freeReg(.r3);
490 {
491 const lock = function.register_manager.lockReg(.r1);
492 defer if (lock) |reg| function.register_manager.unlockReg(reg);
493
494 try expectEqual([_]MockRegister2{ .r0, .r2, .r3 }, function.register_manager.tryAllocRegs(3, .{ null, null, null }).?);
495 }
496 try expect(!function.register_manager.lockedRegsExist());
497
498 try expect(function.register_manager.isRegAllocated(.r0));
499 try expect(function.register_manager.isRegAllocated(.r1));
500 try expect(function.register_manager.isRegAllocated(.r2));
501 try expect(function.register_manager.isRegAllocated(.r3));
502}
503
504test "allocRegs: normal usage" {
505 // TODO: convert this into a decltest once that is supported
506
507 const allocator = std.testing.allocator;
508
509 var function = MockFunction2{
510 .allocator = allocator,
511 };
512 defer function.deinit();
513
514 {
515 const result_reg: MockRegister2 = .r1;
516
517 // The result register is known and fixed at this point, we
518 // don't want to accidentally allocate lhs or rhs to the
519 // result register, this is why we lock it.
520 //
521 // Using defer unlock right after lock is a good idea in
522 // most cases as you probably are using the locked registers
523 // in the remainder of this scope and don't need to use it
524 // after the end of this scope. However, in some situations,
525 // it may make sense to manually unlock registers before the
526 // end of the scope when you are certain that they don't
527 // contain any valuable data anymore and can be reused. For an
528 // example of that, see `selectively reducing register
529 // pressure`.
530 const lock = function.register_manager.lockReg(result_reg);
531 defer if (lock) |reg| function.register_manager.unlockReg(reg);
532
533 const regs = try function.register_manager.allocRegs(2, .{ null, null });
534 try function.genAdd(result_reg, regs[0], regs[1]);
535 }
536}
537
538test "allocRegs: selectively reducing register pressure" {
539 // TODO: convert this into a decltest once that is supported
540
541 const allocator = std.testing.allocator;
542
543 var function = MockFunction2{
544 .allocator = allocator,
545 };
546 defer function.deinit();
547
548 {
549 const result_reg: MockRegister2 = .r1;
550
551 const lock = function.register_manager.lockReg(result_reg);
552
553 // Here, we don't defer unlock because we manually unlock
554 // after genAdd
555 const regs = try function.register_manager.allocRegs(2, .{ null, null });
556
557 try function.genAdd(result_reg, regs[0], regs[1]);
558 function.register_manager.unlockReg(lock.?);
559
560 const extra_summand_reg = try function.register_manager.allocReg(null);
561 try function.genAdd(result_reg, result_reg, extra_summand_reg);
562 }
563}
564
565test "getReg" {
566 const allocator = std.testing.allocator;
567
568 var function = MockFunction1{
569 .allocator = allocator,
570 };
571 defer function.deinit();
572
573 const mock_instruction: Air.Inst.Index = 1;
574
575 try function.register_manager.getReg(.r3, mock_instruction);
576
577 try expect(!function.register_manager.isRegAllocated(.r2));
578 try expect(function.register_manager.isRegAllocated(.r3));
579 try expect(function.register_manager.isRegFree(.r2));
580 try expect(!function.register_manager.isRegFree(.r3));
581
582 // Spill r3
583 try function.register_manager.getReg(.r3, mock_instruction);
584
585 try expect(!function.register_manager.isRegAllocated(.r2));
586 try expect(function.register_manager.isRegAllocated(.r3));
587 try expect(function.register_manager.isRegFree(.r2));
588 try expect(!function.register_manager.isRegFree(.r3));
589 try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r3}, function.spilled.items);
590}
341//const MockRegister1 = enum(u2) {
342// r0,
343// r1,
344// r2,
345// r3,
346
347// pub fn id(reg: MockRegister1) u2 {
348// return @enumToInt(reg);
349// }
350
351// const allocatable_registers = [_]MockRegister1{ .r2, .r3 };
352//};
353
354//const MockRegister2 = enum(u2) {
355// r0,
356// r1,
357// r2,
358// r3,
359
360// pub fn id(reg: MockRegister2) u2 {
361// return @enumToInt(reg);
362// }
363
364// const allocatable_registers = [_]MockRegister2{ .r0, .r1, .r2, .r3 };
365//};
366
367//fn MockFunction(comptime Register: type) type {
368// return struct {
369// allocator: Allocator,
370// register_manager: RegisterManager(Self, Register, &Register.allocatable_registers) = .{},
371// spilled: std.ArrayListUnmanaged(Register) = .{},
372
373// const Self = @This();
374
375// pub fn deinit(self: *Self) void {
376// self.spilled.deinit(self.allocator);
377// }
378
379// pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void {
380// _ = inst;
381// try self.spilled.append(self.allocator, reg);
382// }
383
384// pub fn genAdd(self: *Self, res: Register, lhs: Register, rhs: Register) !void {
385// _ = self;
386// _ = res;
387// _ = lhs;
388// _ = rhs;
389// }
390// };
391//}
392
393//const MockFunction1 = MockFunction(MockRegister1);
394//const MockFunction2 = MockFunction(MockRegister2);
395
396//test "default state" {
397// const allocator = std.testing.allocator;
398
399// var function = MockFunction1{
400// .allocator = allocator,
401// };
402// defer function.deinit();
403
404// try expect(!function.register_manager.isRegAllocated(.r2));
405// try expect(!function.register_manager.isRegAllocated(.r3));
406// try expect(function.register_manager.isRegFree(.r2));
407// try expect(function.register_manager.isRegFree(.r3));
408//}
409
410//test "tryAllocReg: no spilling" {
411// const allocator = std.testing.allocator;
412
413// var function = MockFunction1{
414// .allocator = allocator,
415// };
416// defer function.deinit();
417
418// const mock_instruction: Air.Inst.Index = 1;
419
420// try expectEqual(@as(?MockRegister1, .r2), function.register_manager.tryAllocReg(mock_instruction));
421// try expectEqual(@as(?MockRegister1, .r3), function.register_manager.tryAllocReg(mock_instruction));
422// try expectEqual(@as(?MockRegister1, null), function.register_manager.tryAllocReg(mock_instruction));
423
424// try expect(function.register_manager.isRegAllocated(.r2));
425// try expect(function.register_manager.isRegAllocated(.r3));
426// try expect(!function.register_manager.isRegFree(.r2));
427// try expect(!function.register_manager.isRegFree(.r3));
428
429// function.register_manager.freeReg(.r2);
430// function.register_manager.freeReg(.r3);
431
432// try expect(function.register_manager.isRegAllocated(.r2));
433// try expect(function.register_manager.isRegAllocated(.r3));
434// try expect(function.register_manager.isRegFree(.r2));
435// try expect(function.register_manager.isRegFree(.r3));
436//}
437
438//test "allocReg: spilling" {
439// const allocator = std.testing.allocator;
440
441// var function = MockFunction1{
442// .allocator = allocator,
443// };
444// defer function.deinit();
445
446// const mock_instruction: Air.Inst.Index = 1;
447
448// try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction));
449// try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction));
450
451// // Spill a register
452// try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction));
453// try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items);
454
455// // No spilling necessary
456// function.register_manager.freeReg(.r3);
457// try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction));
458// try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items);
459
460// // Locked registers
461// function.register_manager.freeReg(.r3);
462// {
463// const lock = function.register_manager.lockReg(.r2);
464// defer if (lock) |reg| function.register_manager.unlockReg(reg);
465
466// try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction));
467// }
468// try expect(!function.register_manager.lockedRegsExist());
469//}
470
471//test "tryAllocRegs" {
472// const allocator = std.testing.allocator;
473
474// var function = MockFunction2{
475// .allocator = allocator,
476// };
477// defer function.deinit();
478
479// try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, function.register_manager.tryAllocRegs(3, .{ null, null, null }).?);
480
481// try expect(function.register_manager.isRegAllocated(.r0));
482// try expect(function.register_manager.isRegAllocated(.r1));
483// try expect(function.register_manager.isRegAllocated(.r2));
484// try expect(!function.register_manager.isRegAllocated(.r3));
485
486// // Locked registers
487// function.register_manager.freeReg(.r0);
488// function.register_manager.freeReg(.r2);
489// function.register_manager.freeReg(.r3);
490// {
491// const lock = function.register_manager.lockReg(.r1);
492// defer if (lock) |reg| function.register_manager.unlockReg(reg);
493
494// try expectEqual([_]MockRegister2{ .r0, .r2, .r3 }, function.register_manager.tryAllocRegs(3, .{ null, null, null }).?);
495// }
496// try expect(!function.register_manager.lockedRegsExist());
497
498// try expect(function.register_manager.isRegAllocated(.r0));
499// try expect(function.register_manager.isRegAllocated(.r1));
500// try expect(function.register_manager.isRegAllocated(.r2));
501// try expect(function.register_manager.isRegAllocated(.r3));
502//}
503
504//test "allocRegs: normal usage" {
505// // TODO: convert this into a decltest once that is supported
506
507// const allocator = std.testing.allocator;
508
509// var function = MockFunction2{
510// .allocator = allocator,
511// };
512// defer function.deinit();
513
514// {
515// const result_reg: MockRegister2 = .r1;
516
517// // The result register is known and fixed at this point, we
518// // don't want to accidentally allocate lhs or rhs to the
519// // result register, this is why we lock it.
520// //
521// // Using defer unlock right after lock is a good idea in
522// // most cases as you probably are using the locked registers
523// // in the remainder of this scope and don't need to use it
524// // after the end of this scope. However, in some situations,
525// // it may make sense to manually unlock registers before the
526// // end of the scope when you are certain that they don't
527// // contain any valuable data anymore and can be reused. For an
528// // example of that, see `selectively reducing register
529// // pressure`.
530// const lock = function.register_manager.lockReg(result_reg);
531// defer if (lock) |reg| function.register_manager.unlockReg(reg);
532
533// const regs = try function.register_manager.allocRegs(2, .{ null, null });
534// try function.genAdd(result_reg, regs[0], regs[1]);
535// }
536//}
537
538//test "allocRegs: selectively reducing register pressure" {
539// // TODO: convert this into a decltest once that is supported
540
541// const allocator = std.testing.allocator;
542
543// var function = MockFunction2{
544// .allocator = allocator,
545// };
546// defer function.deinit();
547
548// {
549// const result_reg: MockRegister2 = .r1;
550
551// const lock = function.register_manager.lockReg(result_reg);
552
553// // Here, we don't defer unlock because we manually unlock
554// // after genAdd
555// const regs = try function.register_manager.allocRegs(2, .{ null, null });
556
557// try function.genAdd(result_reg, regs[0], regs[1]);
558// function.register_manager.unlockReg(lock.?);
559
560// const extra_summand_reg = try function.register_manager.allocReg(null);
561// try function.genAdd(result_reg, result_reg, extra_summand_reg);
562// }
563//}
564
565//test "getReg" {
566// const allocator = std.testing.allocator;
567
568// var function = MockFunction1{
569// .allocator = allocator,
570// };
571// defer function.deinit();
572
573// const mock_instruction: Air.Inst.Index = 1;
574
575// try function.register_manager.getReg(.r3, mock_instruction);
576
577// try expect(!function.register_manager.isRegAllocated(.r2));
578// try expect(function.register_manager.isRegAllocated(.r3));
579// try expect(function.register_manager.isRegFree(.r2));
580// try expect(!function.register_manager.isRegFree(.r3));
581
582// // Spill r3
583// try function.register_manager.getReg(.r3, mock_instruction);
584
585// try expect(!function.register_manager.isRegAllocated(.r2));
586// try expect(function.register_manager.isRegAllocated(.r3));
587// try expect(function.register_manager.isRegFree(.r2));
588// try expect(!function.register_manager.isRegFree(.r3));
589// try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r3}, function.spilled.items);
590//}