x64: clean up regalloc-related semantics on several instructions. (#4811)
* x64: clean up regalloc-related semantics on several instructions. This PR removes all uses of "modify" operands on instructions in the x64 backend, and also removes all uses of "pinned vregs", or vregs that are explicitly tied to particular physical registers. In place of both of these mechanisms, which are legacies of the old regalloc design and supported via compatibility code, the backend now uses operand constraints. This is more flexible as it allows the regalloc to see the liveranges and constraints without "reverse-engineering" move instructions. Eventually, after removing all such uses (including in other backends and by the ABI code), we can remove the compatibility code in regalloc2, significantly simplifying its liverange-construction frontend and thus allowing for higher confidence in correctness as well as possibly a bit more compilation speed. Curiously, there are a few extra move instructions now; they are likely poor splitting decisions and I can try to chase these down later. * Fix cranelift-codegen tests. * Review feedback.
This commit is contained in:
@@ -377,11 +377,11 @@ pub(crate) fn emit(
|
||||
} => {
|
||||
let dividend_lo = allocs.next(dividend_lo.to_reg());
|
||||
let dst_quotient = allocs.next(dst_quotient.to_reg().to_reg());
|
||||
let dst_remainder = allocs.next(dst_remainder.to_reg().to_reg());
|
||||
debug_assert_eq!(dividend_lo, regs::rax());
|
||||
debug_assert_eq!(dst_quotient, regs::rax());
|
||||
debug_assert_eq!(dst_remainder, regs::rdx());
|
||||
if size.to_bits() > 8 {
|
||||
let dst_remainder = allocs.next(dst_remainder.to_reg().to_reg());
|
||||
debug_assert_eq!(dst_remainder, regs::rdx());
|
||||
let dividend_hi = allocs.next(dividend_hi.to_reg());
|
||||
debug_assert_eq!(dividend_hi, regs::rdx());
|
||||
}
|
||||
@@ -468,7 +468,11 @@ pub(crate) fn emit(
|
||||
let src = allocs.next(src.to_reg());
|
||||
let dst = allocs.next(dst.to_reg().to_reg());
|
||||
debug_assert_eq!(src, regs::rax());
|
||||
debug_assert_eq!(dst, regs::rdx());
|
||||
if *size == OperandSize::Size8 {
|
||||
debug_assert_eq!(dst, regs::rax());
|
||||
} else {
|
||||
debug_assert_eq!(dst, regs::rdx());
|
||||
}
|
||||
match size {
|
||||
OperandSize::Size8 => {
|
||||
sink.put1(0x66);
|
||||
@@ -498,7 +502,7 @@ pub(crate) fn emit(
|
||||
} => {
|
||||
let dividend_lo = allocs.next(dividend_lo.to_reg());
|
||||
let dividend_hi = allocs.next(dividend_hi.to_reg());
|
||||
let divisor = allocs.next(divisor.to_reg().to_reg());
|
||||
let divisor = allocs.next(divisor.to_reg());
|
||||
let dst_quotient = allocs.next(dst_quotient.to_reg().to_reg());
|
||||
let dst_remainder = allocs.next(dst_remainder.to_reg().to_reg());
|
||||
let tmp = tmp.map(|tmp| allocs.next(tmp.to_reg().to_reg()));
|
||||
@@ -597,18 +601,45 @@ pub(crate) fn emit(
|
||||
sink.bind_label(do_op);
|
||||
}
|
||||
|
||||
let dividend_lo = Gpr::new(regs::rax()).unwrap();
|
||||
let dst_quotient = WritableGpr::from_reg(Gpr::new(regs::rax()).unwrap());
|
||||
let (dividend_hi, dst_remainder) = if *size == OperandSize::Size8 {
|
||||
(
|
||||
Gpr::new(regs::rax()).unwrap(),
|
||||
Writable::from_reg(Gpr::new(regs::rax()).unwrap()),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Gpr::new(regs::rdx()).unwrap(),
|
||||
Writable::from_reg(Gpr::new(regs::rdx()).unwrap()),
|
||||
)
|
||||
};
|
||||
|
||||
// Fill in the high parts:
|
||||
if kind.is_signed() {
|
||||
// sign-extend the sign-bit of rax into rdx, for signed opcodes.
|
||||
let inst = Inst::sign_extend_data(*size);
|
||||
let inst =
|
||||
Inst::sign_extend_data(*size, dividend_lo, WritableGpr::from_reg(dividend_hi));
|
||||
inst.emit(&[], sink, info, state);
|
||||
} else {
|
||||
} else if *size != OperandSize::Size8 {
|
||||
// zero for unsigned opcodes.
|
||||
let inst = Inst::imm(OperandSize::Size64, 0, Writable::from_reg(regs::rdx()));
|
||||
let inst = Inst::imm(
|
||||
OperandSize::Size64,
|
||||
0,
|
||||
Writable::from_reg(dividend_hi.to_reg()),
|
||||
);
|
||||
inst.emit(&[], sink, info, state);
|
||||
}
|
||||
|
||||
let inst = Inst::div(*size, kind.is_signed(), RegMem::reg(divisor));
|
||||
let inst = Inst::div(
|
||||
*size,
|
||||
kind.is_signed(),
|
||||
RegMem::reg(divisor),
|
||||
dividend_lo,
|
||||
dividend_hi,
|
||||
dst_quotient,
|
||||
dst_remainder,
|
||||
);
|
||||
inst.emit(&[], sink, info, state);
|
||||
|
||||
// Lowering takes care of moving the result back into the right register, see comment
|
||||
@@ -1393,7 +1424,8 @@ pub(crate) fn emit(
|
||||
// ;; generated by lowering: cmp #jmp_table_size, %idx
|
||||
// jnb $default_target
|
||||
// movl %idx, %tmp2
|
||||
// cmovnb %tmp1, %tmp2 ;; Spectre mitigation; we require tmp1 to be zero on entry.
|
||||
// mov $0, %tmp1
|
||||
// cmovnb %tmp1, %tmp2 ;; Spectre mitigation.
|
||||
// lea start_of_jump_table_offset(%rip), %tmp1
|
||||
// movslq [%tmp1, %tmp2, 4], %tmp2 ;; shift of 2, viz. multiply index by 4
|
||||
// addq %tmp2, %tmp1
|
||||
@@ -1406,6 +1438,13 @@ pub(crate) fn emit(
|
||||
let inst = Inst::movzx_rm_r(ExtMode::LQ, RegMem::reg(idx), tmp2);
|
||||
inst.emit(&[], sink, info, state);
|
||||
|
||||
// Zero `tmp1` to overwrite `tmp2` with zeroes on the
|
||||
// out-of-bounds case (Spectre mitigation using CMOV).
|
||||
// Note that we need to do this with a move-immediate
|
||||
// form, because we cannot clobber the flags.
|
||||
let inst = Inst::imm(OperandSize::Size32, 0, tmp1);
|
||||
inst.emit(&[], sink, info, state);
|
||||
|
||||
// Spectre mitigation: CMOV to zero the index if the out-of-bounds branch above misspeculated.
|
||||
let inst = Inst::cmove(
|
||||
OperandSize::Size64,
|
||||
@@ -1768,9 +1807,21 @@ pub(crate) fn emit(
|
||||
src1,
|
||||
src2,
|
||||
dst,
|
||||
}
|
||||
| Inst::XmmRmREvex3 {
|
||||
op,
|
||||
src1,
|
||||
src2,
|
||||
dst,
|
||||
// `dst` reuses `src3`.
|
||||
..
|
||||
} => {
|
||||
let dst = allocs.next(dst.to_reg().to_reg());
|
||||
let src2 = allocs.next(src2.to_reg());
|
||||
if let Inst::XmmRmREvex3 { src3, .. } = inst {
|
||||
let src3 = allocs.next(src3.to_reg());
|
||||
debug_assert_eq!(src3, dst);
|
||||
}
|
||||
let src1 = src1.clone().to_reg_mem().with_allocs(allocs);
|
||||
|
||||
let (w, opcode) = match op {
|
||||
@@ -2086,7 +2137,7 @@ pub(crate) fn emit(
|
||||
tmp_gpr1,
|
||||
tmp_gpr2,
|
||||
} => {
|
||||
let src = allocs.next(src.to_reg().to_reg());
|
||||
let src = allocs.next(src.to_reg());
|
||||
let dst = allocs.next(dst.to_reg().to_reg());
|
||||
let tmp_gpr1 = allocs.next(tmp_gpr1.to_reg().to_reg());
|
||||
let tmp_gpr2 = allocs.next(tmp_gpr2.to_reg().to_reg());
|
||||
@@ -2155,7 +2206,7 @@ pub(crate) fn emit(
|
||||
let inst = Inst::shift_r(
|
||||
OperandSize::Size64,
|
||||
ShiftKind::ShiftRightLogical,
|
||||
Some(1),
|
||||
Imm8Gpr::new(Imm8Reg::Imm8 { imm: 1 }).unwrap(),
|
||||
Writable::from_reg(tmp_gpr1),
|
||||
);
|
||||
inst.emit(&[], sink, info, state);
|
||||
@@ -2208,7 +2259,7 @@ pub(crate) fn emit(
|
||||
tmp_gpr,
|
||||
tmp_xmm,
|
||||
} => {
|
||||
let src = allocs.next(src.to_reg().to_reg());
|
||||
let src = allocs.next(src.to_reg());
|
||||
let dst = allocs.next(dst.to_reg().to_reg());
|
||||
let tmp_gpr = allocs.next(tmp_gpr.to_reg().to_reg());
|
||||
let tmp_xmm = allocs.next(tmp_xmm.to_reg().to_reg());
|
||||
@@ -2417,7 +2468,7 @@ pub(crate) fn emit(
|
||||
tmp_gpr,
|
||||
tmp_xmm,
|
||||
} => {
|
||||
let src = allocs.next(src.to_reg().to_reg());
|
||||
let src = allocs.next(src.to_reg());
|
||||
let dst = allocs.next(dst.to_reg().to_reg());
|
||||
let tmp_gpr = allocs.next(tmp_gpr.to_reg().to_reg());
|
||||
let tmp_xmm = allocs.next(tmp_xmm.to_reg().to_reg());
|
||||
|
||||
Reference in New Issue
Block a user