зеркало из https://github.com/github/ruby.git
* Split instructions if necessary * Add a reusable transform_insns function * Split out comments labels from transform_insns * Refactor alloc_regs to use transform_insns
This commit is contained in:
Родитель
2b7d4f277d
Коммит
a3d8e20cea
141
yjit/src/ir.rs
141
yjit/src/ir.rs
|
@ -48,6 +48,9 @@ pub enum Op
|
|||
// Low-level instructions
|
||||
//
|
||||
|
||||
// A low-level instruction that loads a value into a register.
|
||||
Load,
|
||||
|
||||
// A low-level mov instruction. It accepts two operands.
|
||||
Mov,
|
||||
|
||||
|
@ -389,10 +392,83 @@ impl Assembler
|
|||
Target::LabelIdx(insn_idx)
|
||||
}
|
||||
|
||||
/// Transform input instructions, consumes the input assembler
|
||||
fn transform_insns<F>(mut self, mut map_insn: F) -> Assembler
|
||||
where F: FnMut(&mut Assembler, usize, Op, Vec<Opnd>, Option<Target>)
|
||||
{
|
||||
let mut asm = Assembler::new();
|
||||
|
||||
// indices maps from the old instruction index to the new instruction
|
||||
// index.
|
||||
let mut indices: Vec<usize> = Vec::default();
|
||||
|
||||
// Map an operand to the next set of instructions by correcting previous
|
||||
// InsnOut indices.
|
||||
fn map_opnd(opnd: Opnd, indices: &mut Vec<usize>) -> Opnd {
|
||||
if let Opnd::InsnOut(index) = opnd {
|
||||
Opnd::InsnOut(indices[index])
|
||||
} else {
|
||||
opnd
|
||||
}
|
||||
}
|
||||
|
||||
for (index, insn) in self.insns.drain(..).enumerate() {
|
||||
let opnds: Vec<Opnd> = insn.opnds.into_iter().map(|opnd| map_opnd(opnd, &mut indices)).collect();
|
||||
|
||||
// For each instruction, either handle it here or allow the map_insn
|
||||
// callback to handle it.
|
||||
match insn.op {
|
||||
Op::Comment => {
|
||||
asm.comment(insn.text.unwrap().as_str());
|
||||
},
|
||||
Op::Label => {
|
||||
asm.label(insn.text.unwrap().as_str());
|
||||
},
|
||||
_ => {
|
||||
map_insn(&mut asm, index, insn.op, opnds, insn.target);
|
||||
}
|
||||
};
|
||||
|
||||
// Here we're assuming that if we've pushed multiple instructions,
|
||||
// the output that we're using is still the final instruction that
|
||||
// was pushed.
|
||||
indices.push(asm.insns.len() - 1);
|
||||
}
|
||||
|
||||
asm
|
||||
}
|
||||
|
||||
/// Transforms the instructions by splitting instructions that cannot be
|
||||
/// represented in the final architecture into multiple instructions that
|
||||
/// can.
|
||||
fn split_insns(self) -> Assembler
|
||||
{
|
||||
self.transform_insns(|asm, _, op, opnds, target| {
|
||||
match op {
|
||||
// Check for Add, Sub, or Mov instructions with two memory
|
||||
// operands.
|
||||
Op::Add | Op::Sub | Op::Mov => {
|
||||
match opnds.as_slice() {
|
||||
[Opnd::Mem(_), Opnd::Mem(_)] => {
|
||||
let output = asm.push_insn(Op::Load, vec![opnds[0]], None);
|
||||
asm.push_insn(op, vec![output, opnds[1]], None);
|
||||
},
|
||||
_ => {
|
||||
asm.push_insn(op, opnds, target);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
asm.push_insn(op, opnds, target);
|
||||
}
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the out field on the various instructions that require allocated
|
||||
/// registers because their output is used as the operand on a subsequent
|
||||
/// instruction. This is our implementation of the linear scan algorithm.
|
||||
fn alloc_regs(&mut self, regs: Vec<Reg>)
|
||||
fn alloc_regs(mut self, regs: Vec<Reg>) -> Assembler
|
||||
{
|
||||
// First, create the pool of registers.
|
||||
let mut pool: u32 = 0;
|
||||
|
@ -418,21 +494,12 @@ impl Assembler
|
|||
*pool &= !(1 << reg_index);
|
||||
}
|
||||
|
||||
// Next, create the next list of instructions.
|
||||
let mut next_insns: Vec<Insn> = Vec::default();
|
||||
|
||||
// Finally, walk the existing instructions and allocate.
|
||||
for (index, mut insn) in self.insns.drain(..).enumerate() {
|
||||
if self.live_ranges[index] != index {
|
||||
// This instruction is used by another instruction, so we need
|
||||
// to allocate a register for it.
|
||||
insn.out = Opnd::Reg(alloc_reg(&mut pool, ®s));
|
||||
}
|
||||
|
||||
let live_ranges: Vec<usize> = std::mem::take(&mut self.live_ranges);
|
||||
let result = self.transform_insns(|asm, index, op, opnds, target| {
|
||||
// Check if this is the last instruction that uses an operand that
|
||||
// spans more than one instruction. In that case, return the
|
||||
// allocated register to the pool.
|
||||
for opnd in &insn.opnds {
|
||||
for opnd in &opnds {
|
||||
if let Opnd::InsnOut(idx) = opnd {
|
||||
// Since we have an InsnOut, we know it spans more that one
|
||||
// instruction.
|
||||
|
@ -442,8 +509,8 @@ impl Assembler
|
|||
// We're going to check if this is the last instruction that
|
||||
// uses this operand. If it is, we can return the allocated
|
||||
// register to the pool.
|
||||
if self.live_ranges[start_index] == index {
|
||||
if let Opnd::Reg(reg) = next_insns[start_index].out {
|
||||
if live_ranges[start_index] == index {
|
||||
if let Opnd::Reg(reg) = asm.insns[start_index].out {
|
||||
dealloc_reg(&mut pool, ®s, ®);
|
||||
} else {
|
||||
unreachable!();
|
||||
|
@ -452,18 +519,25 @@ impl Assembler
|
|||
}
|
||||
}
|
||||
|
||||
// Push the instruction onto the next list of instructions now that
|
||||
// we have checked everything we needed to check.
|
||||
next_insns.push(insn);
|
||||
}
|
||||
asm.push_insn(op, opnds, target);
|
||||
|
||||
if live_ranges[index] != index {
|
||||
// This instruction is used by another instruction, so we need
|
||||
// to allocate a register for it.
|
||||
let length = asm.insns.len();
|
||||
asm.insns[length - 1].out = Opnd::Reg(alloc_reg(&mut pool, ®s));
|
||||
}
|
||||
});
|
||||
|
||||
assert_eq!(pool, 0, "Expected all registers to be returned to the pool");
|
||||
self.insns = next_insns;
|
||||
result
|
||||
}
|
||||
|
||||
// Optimize and compile the stored instructions
|
||||
fn compile()
|
||||
fn compile(self, regs: Vec<Reg>) -> Assembler
|
||||
{
|
||||
self.split_insns().alloc_regs(regs)
|
||||
|
||||
// TODO: splitting pass, split_insns()
|
||||
|
||||
// Peephole optimizations
|
||||
|
@ -582,6 +656,23 @@ mod tests {
|
|||
asm.add(out, Opnd::UImm(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_insns() {
|
||||
let mut asm = Assembler::new();
|
||||
|
||||
let reg1 = Reg { reg_no: 0, num_bits: 64, special: false };
|
||||
let reg2 = Reg { reg_no: 1, num_bits: 64, special: false };
|
||||
|
||||
asm.add(
|
||||
Opnd::mem(64, Opnd::Reg(reg1), 0),
|
||||
Opnd::mem(64, Opnd::Reg(reg2), 0)
|
||||
);
|
||||
|
||||
let result = asm.split_insns();
|
||||
assert_eq!(result.insns.len(), 2);
|
||||
assert_eq!(result.insns[0].op, Op::Load);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alloc_regs() {
|
||||
let mut asm = Assembler::new();
|
||||
|
@ -609,12 +700,12 @@ mod tests {
|
|||
// Here we're going to allocate the registers.
|
||||
let reg1 = Reg { reg_no: 0, num_bits: 64, special: false };
|
||||
let reg2 = Reg { reg_no: 1, num_bits: 64, special: false };
|
||||
asm.alloc_regs(vec![reg1, reg2]);
|
||||
let result = asm.alloc_regs(vec![reg1, reg2]);
|
||||
|
||||
// Now we're going to verify that the out field has been appropriately
|
||||
// updated for each of the instructions that needs it.
|
||||
assert_eq!(asm.insns[0].out, Opnd::Reg(reg1));
|
||||
assert_eq!(asm.insns[2].out, Opnd::Reg(reg2));
|
||||
assert_eq!(asm.insns[5].out, Opnd::Reg(reg1));
|
||||
assert_eq!(result.insns[0].out, Opnd::Reg(reg1));
|
||||
assert_eq!(result.insns[2].out, Opnd::Reg(reg2));
|
||||
assert_eq!(result.insns[5].out, Opnd::Reg(reg1));
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче