| author | |
| committer | |
| log | 2aee2302515ba444999b82c2e40cbc35dee08baf |
| tree | 356e7ffa416b3f319243412e68e6a8b26b10997e |
| parent | 357561840d705bafbeb22b4919d6a79b208cd8fe |
4 files changed, 595 insertions(+), 266 deletions(-)
src/arch/x86_64/CodeGen.zig+128-5| ... | ... | @@ -202,6 +202,7 @@ pub const MCValue = union(enum) { |
| 202 | 202 | fn isRegister(mcv: MCValue) bool { |
| 203 | 203 | return switch (mcv) { |
| 204 | 204 | .register => true, |
| 205 | .avx_register => true, | |
| 205 | 206 | else => false, |
| 206 | 207 | }; |
| 207 | 208 | } |
| ... | ... | @@ -971,6 +972,7 @@ pub fn spillCompareFlagsIfOccupied(self: *Self) !void { |
| 971 | 972 | .compare_flags_signed, |
| 972 | 973 | .compare_flags_unsigned, |
| 973 | 974 | => try self.allocRegOrMem(inst_to_save, true), |
| 975 | .avx_register => try self.allocRegOrMem(inst_to_save, false), | |
| 974 | 976 | else => unreachable, |
| 975 | 977 | }; |
| 976 | 978 | |
| ... | ... | @@ -988,6 +990,7 @@ pub fn spillCompareFlagsIfOccupied(self: *Self) !void { |
| 988 | 990 | .register_overflow_signed, |
| 989 | 991 | .register_overflow_unsigned, |
| 990 | 992 | => |reg| self.register_manager.freeReg(reg), |
| 993 | .avx_register => |reg| self.avx_register_manager.freeReg(reg), | |
| 991 | 994 | else => {}, |
| 992 | 995 | } |
| 993 | 996 | } |
| ... | ... | @@ -2497,7 +2500,6 @@ fn reuseOperand( |
| 2497 | 2500 | fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!void { |
| 2498 | 2501 | const elem_ty = ptr_ty.elemType(); |
| 2499 | 2502 | const abi_size = elem_ty.abiSize(self.target.*); |
| 2500 | std.log.warn("{} => {}, {}", .{ ptr_ty.fmtDebug(), ptr, dst_mcv }); | |
| 2501 | 2503 | switch (ptr) { |
| 2502 | 2504 | .none => unreachable, |
| 2503 | 2505 | .undef => unreachable, |
| ... | ... | @@ -2627,7 +2629,6 @@ fn loadMemPtrIntoRegister(self: *Self, reg: Register, ptr_ty: Type, ptr: MCValue |
| 2627 | 2629 | |
| 2628 | 2630 | fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type) InnerError!void { |
| 2629 | 2631 | const abi_size = value_ty.abiSize(self.target.*); |
| 2630 | std.log.warn("{} => {}, {} => {}", .{ ptr_ty.fmtDebug(), ptr, value_ty.fmtDebug(), value }); | |
| 2631 | 2632 | switch (ptr) { |
| 2632 | 2633 | .none => unreachable, |
| 2633 | 2634 | .undef => unreachable, |
| ... | ... | @@ -3375,13 +3376,39 @@ fn genBinOp( |
| 3375 | 3376 | const rhs = try self.resolveInst(rhs_air); |
| 3376 | 3377 | const lhs_ty = self.air.typeOf(lhs_air); |
| 3377 | 3378 | 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) { | |
| 3379 | 3380 | return self.fail("TODO implement genBinOp for {}", .{lhs_ty.fmtDebug()}); |
| 3380 | 3381 | } |
| 3381 | 3382 | if (lhs_ty.abiSize(self.target.*) > 8) { |
| 3382 | 3383 | return self.fail("TODO implement genBinOp for {}", .{lhs_ty.fmtDebug()}); |
| 3383 | 3384 | } |
| 3384 | 3385 | |
| 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 | ||
| 3385 | 3412 | const is_commutative: bool = switch (tag) { |
| 3386 | 3413 | .add, |
| 3387 | 3414 | .addwrap, |
| ... | ... | @@ -3550,8 +3577,28 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu |
| 3550 | 3577 | }, |
| 3551 | 3578 | } |
| 3552 | 3579 | }, |
| 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 | } | |
| 3555 | 3602 | }, |
| 3556 | 3603 | .ptr_stack_offset, .stack_offset => |off| { |
| 3557 | 3604 | if (off > math.maxInt(i32)) { |
| ... | ... | @@ -4209,6 +4256,37 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void { |
| 4209 | 4256 | self.compare_flags_inst = inst; |
| 4210 | 4257 | |
| 4211 | 4258 | 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 | } | |
| 4212 | 4290 | // There are 2 operands, destination and source. |
| 4213 | 4291 | // Either one, but not both, can be a memory operand. |
| 4214 | 4292 | // 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 |
| 5962 | 6040 | else => return self.fail("TODO genSetAvxReg from stack offset for type {}", .{ty.fmtDebug()}), |
| 5963 | 6041 | } |
| 5964 | 6042 | }, |
| 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 | }, | |
| 5965 | 6088 | else => |other| { |
| 5966 | 6089 | return self.fail("TODO genSetAvxReg from {}", .{other}); |
| 5967 | 6090 | }, |
src/arch/x86_64/Emit.zig+207-10| ... | ... | @@ -186,6 +186,10 @@ pub fn lowerMir(emit: *Emit) InnerError!void { |
| 186 | 186 | // AVX instructions |
| 187 | 187 | .mov_f64 => try emit.mirMovF64(inst), |
| 188 | 188 | |
| 189 | .add_f64 => try emit.mirAddF64(inst), | |
| 190 | ||
| 191 | .cmp_f64 => try emit.mirCmpF64(inst), | |
| 192 | ||
| 189 | 193 | // Pseudo-instructions |
| 190 | 194 | .call_extern => try emit.mirCallExtern(inst), |
| 191 | 195 | |
| ... | ... | @@ -960,11 +964,11 @@ fn mirMovF64(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 960 | 964 | assert(tag == .mov_f64); |
| 961 | 965 | const ops = emit.mir.instructions.items(.ops)[inst]; |
| 962 | 966 | const flags = @truncate(u2, ops); |
| 963 | const imm = emit.mir.instructions.items(.data)[inst].imm; | |
| 964 | 967 | |
| 965 | 968 | switch (flags) { |
| 966 | 969 | 0b00 => { |
| 967 | 970 | const decoded = Mir.Ops(AvxRegister, GpRegister).decode(ops); |
| 971 | const imm = emit.mir.instructions.items(.data)[inst].imm; | |
| 968 | 972 | return lowerToRmEnc(.vmovsd, Register.avxReg(decoded.reg1), RegisterOrMemory.mem(.qword_ptr, .{ |
| 969 | 973 | .disp = imm, |
| 970 | 974 | .base = decoded.reg2, |
| ... | ... | @@ -972,11 +976,63 @@ fn mirMovF64(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 972 | 976 | }, |
| 973 | 977 | 0b01 => { |
| 974 | 978 | const decoded = Mir.Ops(GpRegister, AvxRegister).decode(ops); |
| 979 | const imm = emit.mir.instructions.items(.data)[inst].imm; | |
| 975 | 980 | return lowerToMrEnc(.vmovsd, RegisterOrMemory.mem(.qword_ptr, .{ |
| 976 | 981 | .disp = imm, |
| 977 | 982 | .base = decoded.reg1, |
| 978 | 983 | }), Register.avxReg(decoded.reg2), emit.code); |
| 979 | 984 | }, |
| 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 | ||
| 999 | fn 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 | ||
| 1020 | fn 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 | }, | |
| 980 | 1036 | else => return emit.fail("TODO unused variant 0b{b} for mov_f64", .{flags}), |
| 981 | 1037 | } |
| 982 | 1038 | } |
| ... | ... | @@ -1217,6 +1273,9 @@ const Tag = enum { |
| 1217 | 1273 | cmovb, |
| 1218 | 1274 | cmovnae, |
| 1219 | 1275 | vmovsd, |
| 1276 | vaddsd, | |
| 1277 | vcmpsd, | |
| 1278 | vucomisd, | |
| 1220 | 1279 | |
| 1221 | 1280 | fn isSetCC(tag: Tag) bool { |
| 1222 | 1281 | return switch (tag) { |
| ... | ... | @@ -1301,6 +1360,12 @@ const Encoding = enum { |
| 1301 | 1360 | |
| 1302 | 1361 | /// OP r64, r/m64, imm32 |
| 1303 | 1362 | rmi, |
| 1363 | ||
| 1364 | /// OP xmm1, xmm2, xmm3/m64 | |
| 1365 | rvm, | |
| 1366 | ||
| 1367 | /// OP xmm1, xmm2, xmm3/m64, imm8 | |
| 1368 | rvmi, | |
| 1304 | 1369 | }; |
| 1305 | 1370 | |
| 1306 | 1371 | const OpCode = union(enum) { |
| ... | ... | @@ -1452,6 +1517,7 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { |
| 1452 | 1517 | .cmovb, .cmovnae => OpCode.twoByte(0x0f, 0x42), |
| 1453 | 1518 | .cmovl, .cmovng => OpCode.twoByte(0x0f, 0x4c), |
| 1454 | 1519 | .vmovsd => OpCode.oneByte(0x10), |
| 1520 | .vucomisd => OpCode.oneByte(0x2e), | |
| 1455 | 1521 | else => null, |
| 1456 | 1522 | }, |
| 1457 | 1523 | .oi => return switch (tag) { |
| ... | ... | @@ -1470,6 +1536,15 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { |
| 1470 | 1536 | .imul => OpCode.oneByte(if (is_one_byte) 0x6b else 0x69), |
| 1471 | 1537 | else => null, |
| 1472 | 1538 | }, |
| 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 | }, | |
| 1473 | 1548 | } |
| 1474 | 1549 | } |
| 1475 | 1550 | |
| ... | ... | @@ -1578,6 +1653,16 @@ inline fn getVexPrefix(tag: Tag, enc: Encoding) ?VexPrefix { |
| 1578 | 1653 | }, |
| 1579 | 1654 | .rm => switch (tag) { |
| 1580 | 1655 | .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 }, | |
| 1581 | 1666 | else => return null, |
| 1582 | 1667 | }, |
| 1583 | 1668 | else => unreachable, |
| ... | ... | @@ -2013,15 +2098,33 @@ fn lowerToRmEnc( |
| 2013 | 2098 | const opc = getOpCode(tag, .rm, reg.size() == 8 or reg_or_mem.size() == 8).?; |
| 2014 | 2099 | switch (reg_or_mem) { |
| 2015 | 2100 | .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 | }; | |
| 2025 | 2128 | opc.encode(encoder); |
| 2026 | 2129 | encoder.modRm_direct(reg.lowId(), src_reg.lowId()); |
| 2027 | 2130 | }, |
| ... | ... | @@ -2188,6 +2291,79 @@ fn lowerToRmiEnc( |
| 2188 | 2291 | encodeImm(encoder, imm, reg.size()); |
| 2189 | 2292 | } |
| 2190 | 2293 | |
| 2294 | fn 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 | ||
| 2328 | fn 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 | ||
| 2191 | 2367 | fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void { |
| 2192 | 2368 | assert(expected.len > 0); |
| 2193 | 2369 | if (mem.eql(u8, expected, given)) return; |
| ... | ... | @@ -2598,3 +2774,24 @@ test "lower RMI encoding" { |
| 2598 | 2774 | try lowerToRmiEnc(.imul, Register.reg(.r12w), RegisterOrMemory.reg(.r12w), 0x10, emit.code()); |
| 2599 | 2775 | try expectEqualHexStrings("\x66\x45\x69\xE4\x10\x00", emit.lowered(), "imul r12w, r12w, 0x10"); |
| 2600 | 2776 | } |
| 2777 | ||
| 2778 | test "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 { |
| 353 | 353 | /// AVX instructions |
| 354 | 354 | /// ops flags: form: |
| 355 | 355 | /// 0b00 reg1, qword ptr [reg2 + imm32] |
| 356 | /// 0b10 qword ptr [reg1 + imm32], reg2 | |
| 356 | /// 0b01 qword ptr [reg1 + imm32], reg2 | |
| 357 | /// 0b10 reg1, reg2 | |
| 357 | 358 | mov_f64, |
| 358 | 359 | |
| 360 | /// ops flags: form: | |
| 361 | /// 0b00 reg1, reg1, reg2 | |
| 362 | add_f64, | |
| 363 | ||
| 364 | /// ops flags: form: | |
| 365 | /// | |
| 366 | cmp_f64, | |
| 367 | ||
| 359 | 368 | /// Pseudo-instructions |
| 360 | 369 | /// call extern function |
| 361 | 370 | /// Notes: |
src/register_manager.zig+250-250| ... | ... | @@ -338,253 +338,253 @@ pub fn RegisterManager( |
| 338 | 338 | }; |
| 339 | 339 | } |
| 340 | 340 | |
| 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 | } | |
| 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 | //} |