YJIT: Merge csel and mov on arm64 (#7747)
* YJIT: Refactor arm64_split with &mut insn * YJIT: Merge csel and mov on arm64
This commit is contained in:
parent
995b960c70
commit
64a25977ed
Notes:
git
2023-04-20 20:09:12 +00:00
Merged-By: k0kubun <takashikkbn@gmail.com>
@ -376,6 +376,7 @@ impl Assembler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let live_ranges: Vec<usize> = take(&mut self.live_ranges);
|
||||||
let mut asm_local = Assembler::new_with_label_names(take(&mut self.label_names), take(&mut self.side_exits));
|
let mut asm_local = Assembler::new_with_label_names(take(&mut self.label_names), take(&mut self.side_exits));
|
||||||
let asm = &mut asm_local;
|
let asm = &mut asm_local;
|
||||||
let mut iterator = self.into_draining_iter();
|
let mut iterator = self.into_draining_iter();
|
||||||
@ -405,11 +406,11 @@ impl Assembler
|
|||||||
// We are replacing instructions here so we know they are already
|
// We are replacing instructions here so we know they are already
|
||||||
// being used. It is okay not to use their output here.
|
// being used. It is okay not to use their output here.
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
match insn {
|
match &mut insn {
|
||||||
Insn::Add { left, right, .. } => {
|
Insn::Add { left, right, .. } => {
|
||||||
match (left, right) {
|
match (*left, *right) {
|
||||||
(Opnd::Reg(_) | Opnd::InsnOut { .. }, Opnd::Reg(_) | Opnd::InsnOut { .. }) => {
|
(Opnd::Reg(_) | Opnd::InsnOut { .. }, Opnd::Reg(_) | Opnd::InsnOut { .. }) => {
|
||||||
asm.add(left, right);
|
asm.add(*left, *right);
|
||||||
},
|
},
|
||||||
(reg_opnd @ (Opnd::Reg(_) | Opnd::InsnOut { .. }), other_opnd) |
|
(reg_opnd @ (Opnd::Reg(_) | Opnd::InsnOut { .. }), other_opnd) |
|
||||||
(other_opnd, reg_opnd @ (Opnd::Reg(_) | Opnd::InsnOut { .. })) => {
|
(other_opnd, reg_opnd @ (Opnd::Reg(_) | Opnd::InsnOut { .. })) => {
|
||||||
@ -417,23 +418,19 @@ impl Assembler
|
|||||||
asm.add(reg_opnd, opnd1);
|
asm.add(reg_opnd, opnd1);
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
let opnd0 = split_load_operand(asm, left);
|
let opnd0 = split_load_operand(asm, *left);
|
||||||
let opnd1 = split_shifted_immediate(asm, right);
|
let opnd1 = split_shifted_immediate(asm, *right);
|
||||||
asm.add(opnd0, opnd1);
|
asm.add(opnd0, opnd1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Insn::And { left, right, .. } => {
|
Insn::And { left, right, .. } |
|
||||||
let (opnd0, opnd1) = split_boolean_operands(asm, left, right);
|
Insn::Or { left, right, .. } |
|
||||||
asm.and(opnd0, opnd1);
|
|
||||||
},
|
|
||||||
Insn::Or { left, right, .. } => {
|
|
||||||
let (opnd0, opnd1) = split_boolean_operands(asm, left, right);
|
|
||||||
asm.or(opnd0, opnd1);
|
|
||||||
},
|
|
||||||
Insn::Xor { left, right, .. } => {
|
Insn::Xor { left, right, .. } => {
|
||||||
let (opnd0, opnd1) = split_boolean_operands(asm, left, right);
|
let (opnd0, opnd1) = split_boolean_operands(asm, *left, *right);
|
||||||
asm.xor(opnd0, opnd1);
|
*left = opnd0;
|
||||||
|
*right = opnd1;
|
||||||
|
asm.push_insn(insn);
|
||||||
},
|
},
|
||||||
Insn::CCall { opnds, fptr, .. } => {
|
Insn::CCall { opnds, fptr, .. } => {
|
||||||
assert!(opnds.len() <= C_ARG_OPNDS.len());
|
assert!(opnds.len() <= C_ARG_OPNDS.len());
|
||||||
@ -448,8 +445,8 @@ impl Assembler
|
|||||||
// a UImm of 0 along as the argument to the move.
|
// a UImm of 0 along as the argument to the move.
|
||||||
let value = match opnd {
|
let value = match opnd {
|
||||||
Opnd::UImm(0) | Opnd::Imm(0) => Opnd::UImm(0),
|
Opnd::UImm(0) | Opnd::Imm(0) => Opnd::UImm(0),
|
||||||
Opnd::Mem(_) => split_memory_address(asm, opnd),
|
Opnd::Mem(_) => split_memory_address(asm, *opnd),
|
||||||
_ => opnd
|
_ => *opnd
|
||||||
};
|
};
|
||||||
|
|
||||||
asm.load_into(C_ARG_OPNDS[idx], value);
|
asm.load_into(C_ARG_OPNDS[idx], value);
|
||||||
@ -457,12 +454,12 @@ impl Assembler
|
|||||||
|
|
||||||
// Now we push the CCall without any arguments so that it
|
// Now we push the CCall without any arguments so that it
|
||||||
// just performs the call.
|
// just performs the call.
|
||||||
asm.ccall(fptr, vec![]);
|
asm.ccall(*fptr, vec![]);
|
||||||
},
|
},
|
||||||
Insn::Cmp { left, right } => {
|
Insn::Cmp { left, right } => {
|
||||||
let opnd0 = split_load_operand(asm, left);
|
let opnd0 = split_load_operand(asm, *left);
|
||||||
let opnd0 = split_less_than_32_cmp(asm, opnd0);
|
let opnd0 = split_less_than_32_cmp(asm, opnd0);
|
||||||
let split_right = split_shifted_immediate(asm, right);
|
let split_right = split_shifted_immediate(asm, *right);
|
||||||
let opnd1 = match split_right {
|
let opnd1 = match split_right {
|
||||||
Opnd::InsnOut { .. } if opnd0.num_bits() != split_right.num_bits() => {
|
Opnd::InsnOut { .. } if opnd0.num_bits() != split_right.num_bits() => {
|
||||||
split_right.with_num_bits(opnd0.num_bits().unwrap()).unwrap()
|
split_right.with_num_bits(opnd0.num_bits().unwrap()).unwrap()
|
||||||
@ -482,81 +479,66 @@ impl Assembler
|
|||||||
// make sure the displacement isn't too large and then
|
// make sure the displacement isn't too large and then
|
||||||
// load it into the return register.
|
// load it into the return register.
|
||||||
Opnd::Mem(_) => {
|
Opnd::Mem(_) => {
|
||||||
let split = split_memory_address(asm, opnd);
|
let split = split_memory_address(asm, *opnd);
|
||||||
asm.load_into(C_RET_OPND, split);
|
asm.load_into(C_RET_OPND, split);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Otherwise we just need to load the value into the
|
// Otherwise we just need to load the value into the
|
||||||
// return register.
|
// return register.
|
||||||
_ => {
|
_ => {
|
||||||
asm.load_into(C_RET_OPND, opnd);
|
asm.load_into(C_RET_OPND, *opnd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
asm.cret(C_RET_OPND);
|
asm.cret(C_RET_OPND);
|
||||||
},
|
},
|
||||||
Insn::CSelZ { truthy, falsy, .. } => {
|
Insn::CSelZ { truthy, falsy, out } |
|
||||||
let (opnd0, opnd1) = split_csel_operands(asm, truthy, falsy);
|
Insn::CSelNZ { truthy, falsy, out } |
|
||||||
asm.csel_z(opnd0, opnd1);
|
Insn::CSelE { truthy, falsy, out } |
|
||||||
},
|
Insn::CSelNE { truthy, falsy, out } |
|
||||||
Insn::CSelNZ { truthy, falsy, .. } => {
|
Insn::CSelL { truthy, falsy, out } |
|
||||||
let (opnd0, opnd1) = split_csel_operands(asm, truthy, falsy);
|
Insn::CSelLE { truthy, falsy, out } |
|
||||||
asm.csel_nz(opnd0, opnd1);
|
Insn::CSelG { truthy, falsy, out } |
|
||||||
},
|
Insn::CSelGE { truthy, falsy, out } => {
|
||||||
Insn::CSelE { truthy, falsy, .. } => {
|
let (opnd0, opnd1) = split_csel_operands(asm, *truthy, *falsy);
|
||||||
let (opnd0, opnd1) = split_csel_operands(asm, truthy, falsy);
|
*truthy = opnd0;
|
||||||
asm.csel_e(opnd0, opnd1);
|
*falsy = opnd1;
|
||||||
},
|
// Merge `csel` and `mov` into a single `csel` when possible
|
||||||
Insn::CSelNE { truthy, falsy, .. } => {
|
match iterator.peek() {
|
||||||
let (opnd0, opnd1) = split_csel_operands(asm, truthy, falsy);
|
Some(Insn::Mov { dest: Opnd::Reg(reg), src })
|
||||||
asm.csel_ne(opnd0, opnd1);
|
if matches!(out, Opnd::InsnOut { .. }) && *out == *src && live_ranges[index] == index + 1 => {
|
||||||
},
|
*out = Opnd::Reg(*reg);
|
||||||
Insn::CSelL { truthy, falsy, .. } => {
|
asm.push_insn(insn);
|
||||||
let (opnd0, opnd1) = split_csel_operands(asm, truthy, falsy);
|
iterator.map_insn_index(asm);
|
||||||
asm.csel_l(opnd0, opnd1);
|
iterator.next_unmapped(); // Pop merged Insn::Mov
|
||||||
},
|
}
|
||||||
Insn::CSelLE { truthy, falsy, .. } => {
|
_ => {
|
||||||
let (opnd0, opnd1) = split_csel_operands(asm, truthy, falsy);
|
asm.push_insn(insn);
|
||||||
asm.csel_le(opnd0, opnd1);
|
}
|
||||||
},
|
}
|
||||||
Insn::CSelG { truthy, falsy, .. } => {
|
|
||||||
let (opnd0, opnd1) = split_csel_operands(asm, truthy, falsy);
|
|
||||||
asm.csel_g(opnd0, opnd1);
|
|
||||||
},
|
|
||||||
Insn::CSelGE { truthy, falsy, .. } => {
|
|
||||||
let (opnd0, opnd1) = split_csel_operands(asm, truthy, falsy);
|
|
||||||
asm.csel_ge(opnd0, opnd1);
|
|
||||||
},
|
},
|
||||||
Insn::IncrCounter { mem, value } => {
|
Insn::IncrCounter { mem, value } => {
|
||||||
let counter_addr = match mem {
|
let counter_addr = match mem {
|
||||||
Opnd::Mem(_) => split_lea_operand(asm, mem),
|
Opnd::Mem(_) => split_lea_operand(asm, *mem),
|
||||||
_ => mem
|
_ => *mem
|
||||||
};
|
};
|
||||||
|
|
||||||
asm.incr_counter(counter_addr, value);
|
asm.incr_counter(counter_addr, *value);
|
||||||
},
|
},
|
||||||
Insn::JmpOpnd(opnd) => {
|
Insn::JmpOpnd(opnd) => {
|
||||||
if let Opnd::Mem(_) = opnd {
|
if let Opnd::Mem(_) = opnd {
|
||||||
let opnd0 = split_load_operand(asm, opnd);
|
let opnd0 = split_load_operand(asm, *opnd);
|
||||||
asm.jmp_opnd(opnd0);
|
asm.jmp_opnd(opnd0);
|
||||||
} else {
|
} else {
|
||||||
asm.jmp_opnd(opnd);
|
asm.jmp_opnd(*opnd);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Insn::Load { opnd, .. } => {
|
Insn::Load { opnd, .. } |
|
||||||
let value = match opnd {
|
Insn::LoadInto { opnd, .. } => {
|
||||||
Opnd::Mem(_) => split_memory_address(asm, opnd),
|
*opnd = match opnd {
|
||||||
_ => opnd
|
Opnd::Mem(_) => split_memory_address(asm, *opnd),
|
||||||
|
_ => *opnd
|
||||||
};
|
};
|
||||||
|
asm.push_insn(insn);
|
||||||
asm.load(value);
|
|
||||||
},
|
|
||||||
Insn::LoadInto { dest, opnd } => {
|
|
||||||
let value = match opnd {
|
|
||||||
Opnd::Mem(_) => split_memory_address(asm, opnd),
|
|
||||||
_ => opnd
|
|
||||||
};
|
|
||||||
|
|
||||||
asm.load_into(dest, value);
|
|
||||||
},
|
},
|
||||||
Insn::LoadSExt { opnd, .. } => {
|
Insn::LoadSExt { opnd, .. } => {
|
||||||
match opnd {
|
match opnd {
|
||||||
@ -567,50 +549,50 @@ impl Assembler
|
|||||||
Opnd::Reg(Reg { num_bits: 32, .. }) |
|
Opnd::Reg(Reg { num_bits: 32, .. }) |
|
||||||
Opnd::InsnOut { num_bits: 32, .. } |
|
Opnd::InsnOut { num_bits: 32, .. } |
|
||||||
Opnd::Mem(Mem { num_bits: 32, .. }) => {
|
Opnd::Mem(Mem { num_bits: 32, .. }) => {
|
||||||
asm.load_sext(opnd);
|
asm.load_sext(*opnd);
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
asm.load(opnd);
|
asm.load(*opnd);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
Insn::Mov { dest, src } => {
|
Insn::Mov { dest, src } => {
|
||||||
match (dest, src) {
|
match (&dest, &src) {
|
||||||
// If we're attempting to load into a memory operand, then
|
// If we're attempting to load into a memory operand, then
|
||||||
// we'll switch over to the store instruction.
|
// we'll switch over to the store instruction.
|
||||||
(Opnd::Mem(_), _) => {
|
(Opnd::Mem(_), _) => {
|
||||||
let value = match src {
|
let value = match *src {
|
||||||
// If the first operand is zero, then we can just use
|
// If the first operand is zero, then we can just use
|
||||||
// the zero register.
|
// the zero register.
|
||||||
Opnd::UImm(0) | Opnd::Imm(0) => Opnd::Reg(XZR_REG),
|
Opnd::UImm(0) | Opnd::Imm(0) => Opnd::Reg(XZR_REG),
|
||||||
// If the first operand is a memory operand, we're going
|
// If the first operand is a memory operand, we're going
|
||||||
// to transform this into a store instruction, so we'll
|
// to transform this into a store instruction, so we'll
|
||||||
// need to load this anyway.
|
// need to load this anyway.
|
||||||
Opnd::UImm(_) => asm.load(src),
|
Opnd::UImm(_) => asm.load(*src),
|
||||||
// The value that is being moved must be either a
|
// The value that is being moved must be either a
|
||||||
// register or an immediate that can be encoded as a
|
// register or an immediate that can be encoded as a
|
||||||
// bitmask immediate. Otherwise, we'll need to split the
|
// bitmask immediate. Otherwise, we'll need to split the
|
||||||
// move into multiple instructions.
|
// move into multiple instructions.
|
||||||
_ => split_bitmask_immediate(asm, src, dest.rm_num_bits())
|
_ => split_bitmask_immediate(asm, *src, dest.rm_num_bits())
|
||||||
};
|
};
|
||||||
|
|
||||||
let opnd0 = split_memory_address(asm, dest);
|
let opnd0 = split_memory_address(asm, *dest);
|
||||||
asm.store(opnd0, value);
|
asm.store(opnd0, value);
|
||||||
},
|
},
|
||||||
// If we're loading a memory operand into a register, then
|
// If we're loading a memory operand into a register, then
|
||||||
// we'll switch over to the load instruction.
|
// we'll switch over to the load instruction.
|
||||||
(Opnd::Reg(_), Opnd::Mem(_)) => {
|
(Opnd::Reg(_), Opnd::Mem(_)) => {
|
||||||
let value = split_memory_address(asm, src);
|
let value = split_memory_address(asm, *src);
|
||||||
asm.load_into(dest, value);
|
asm.load_into(*dest, value);
|
||||||
},
|
},
|
||||||
// Otherwise we'll use the normal mov instruction.
|
// Otherwise we'll use the normal mov instruction.
|
||||||
(Opnd::Reg(_), _) => {
|
(Opnd::Reg(_), _) => {
|
||||||
let value = match src {
|
let value = match *src {
|
||||||
// Unlike other instructions, we can avoid splitting this case, using movz.
|
// Unlike other instructions, we can avoid splitting this case, using movz.
|
||||||
Opnd::UImm(uimm) if uimm <= 0xffff => src,
|
Opnd::UImm(uimm) if uimm <= 0xffff => *src,
|
||||||
_ => split_bitmask_immediate(asm, src, dest.rm_num_bits()),
|
_ => split_bitmask_immediate(asm, *src, dest.rm_num_bits()),
|
||||||
};
|
};
|
||||||
asm.mov(dest, value);
|
asm.mov(*dest, value);
|
||||||
},
|
},
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
};
|
};
|
||||||
@ -619,8 +601,8 @@ impl Assembler
|
|||||||
// The value that is being negated must be in a register, so
|
// The value that is being negated must be in a register, so
|
||||||
// if we get anything else we need to load it first.
|
// if we get anything else we need to load it first.
|
||||||
let opnd0 = match opnd {
|
let opnd0 = match opnd {
|
||||||
Opnd::Mem(_) => split_load_operand(asm, opnd),
|
Opnd::Mem(_) => split_load_operand(asm, *opnd),
|
||||||
_ => opnd
|
_ => *opnd
|
||||||
};
|
};
|
||||||
|
|
||||||
asm.not(opnd0);
|
asm.not(opnd0);
|
||||||
@ -633,38 +615,38 @@ impl Assembler
|
|||||||
// the zero register.
|
// the zero register.
|
||||||
Opnd::UImm(0) | Opnd::Imm(0) => Opnd::Reg(XZR_REG),
|
Opnd::UImm(0) | Opnd::Imm(0) => Opnd::Reg(XZR_REG),
|
||||||
// Otherwise we'll check if we need to load it first.
|
// Otherwise we'll check if we need to load it first.
|
||||||
_ => split_load_operand(asm, src)
|
_ => split_load_operand(asm, *src)
|
||||||
};
|
};
|
||||||
|
|
||||||
match dest {
|
match dest {
|
||||||
Opnd::Reg(_) => {
|
Opnd::Reg(_) => {
|
||||||
// Store does not support a register as a dest operand.
|
// Store does not support a register as a dest operand.
|
||||||
asm.mov(dest, opnd1);
|
asm.mov(*dest, opnd1);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// The displacement for the STUR instruction can't be more
|
// The displacement for the STUR instruction can't be more
|
||||||
// than 9 bits long. If it's longer, we need to load the
|
// than 9 bits long. If it's longer, we need to load the
|
||||||
// memory address into a register first.
|
// memory address into a register first.
|
||||||
let opnd0 = split_memory_address(asm, dest);
|
let opnd0 = split_memory_address(asm, *dest);
|
||||||
asm.store(opnd0, opnd1);
|
asm.store(opnd0, opnd1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Insn::Sub { left, right, .. } => {
|
Insn::Sub { left, right, .. } => {
|
||||||
let opnd0 = split_load_operand(asm, left);
|
let opnd0 = split_load_operand(asm, *left);
|
||||||
let opnd1 = split_shifted_immediate(asm, right);
|
let opnd1 = split_shifted_immediate(asm, *right);
|
||||||
asm.sub(opnd0, opnd1);
|
asm.sub(opnd0, opnd1);
|
||||||
},
|
},
|
||||||
Insn::Test { left, right } => {
|
Insn::Test { left, right } => {
|
||||||
// The value being tested must be in a register, so if it's
|
// The value being tested must be in a register, so if it's
|
||||||
// not already one we'll load it first.
|
// not already one we'll load it first.
|
||||||
let opnd0 = split_load_operand(asm, left);
|
let opnd0 = split_load_operand(asm, *left);
|
||||||
|
|
||||||
// The second value must be either a register or an
|
// The second value must be either a register or an
|
||||||
// unsigned immediate that can be encoded as a bitmask
|
// unsigned immediate that can be encoded as a bitmask
|
||||||
// immediate. If it's not one of those, we'll need to load
|
// immediate. If it's not one of those, we'll need to load
|
||||||
// it first.
|
// it first.
|
||||||
let opnd1 = split_bitmask_immediate(asm, right, opnd0.rm_num_bits());
|
let opnd1 = split_bitmask_immediate(asm, *right, opnd0.rm_num_bits());
|
||||||
asm.test(opnd0, opnd1);
|
asm.test(opnd0, opnd1);
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
@ -1617,4 +1599,19 @@ mod tests {
|
|||||||
0x4: orr x1, xzr, #0x10000
|
0x4: orr x1, xzr, #0x10000
|
||||||
"});
|
"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merge_csel_mov() {
|
||||||
|
let (mut asm, mut cb) = setup_asm();
|
||||||
|
|
||||||
|
let out = asm.csel_l(Qtrue.into(), Qfalse.into());
|
||||||
|
asm.mov(Opnd::Reg(Assembler::TEMP_REGS[0]), out);
|
||||||
|
asm.compile_with_num_regs(&mut cb, 2);
|
||||||
|
|
||||||
|
assert_disasm!(cb, "8b0280d20c0080d261b18c9a", {"
|
||||||
|
0x0: mov x11, #0x14
|
||||||
|
0x4: mov x12, #0
|
||||||
|
0x8: csel x1, x11, x12, lt
|
||||||
|
"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user