-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rewrite the optimiser to be a one pass optimiser.
[Well, OK the first lie is in the first line. Because we have to do backwards code generation, we actually have to do a second pass. But that's a boring detail, and something that predates this commit.] This commit moves the optimiser into a model that's closer to RPython: rather than multiple forward stages each doing one optimisation, we have a single forward stage that does all optimisations in one go. This is quicker (fairly obviously!) but also simpler, because it means that we can make strong assumptions at the point of the wavefront: no-one can change our knowledge of earlier parts of the trace. The crucial difference between this commit and what came before is that as well as mutating the IR as we go along (e.g. constant folding) we maintain an analysis of what values an instruction can produce, which we gradually refine. Crucially, mutation can only happen at the current instruction, but analysis refinement can happen on previous instructions. Consider a trace along these lines: ``` %0: i8 = load_ti ... ; at this point we know nothing about %0 %1: i8 = add %0, 1i8 ; we still know nothing about %0 %2: i1 = eq %0, 0i8 ; we know that henceforth %0 must be 0i8 guard true %2 %4: I8 = mul %0, %0 ; we know this must equal the constant 0i8 ``` Notice that it's only at the point of the guard that our analysis allows us to start using the knowledge "%0 = 0i8" for optimisations. The code is currently woefully incomplete (but if I fill in anything else, I trip up on the "removing guards tends to kill things" bug that I believe is fixed but not yet merged), I haven't yet bothered porting across one old optimisation (`mul_chain`), and I haven't really got a good API for sharing analysis and mutations (which is currently done inconsistently). All that said, this nudges the optimiser forward just about enough that I think it's worth considering for merging now, and then it can be improved in-tree.
- Loading branch information
Showing
6 changed files
with
611 additions
and
457 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
//! Analyse a trace and gradually refine what values we know a previous instruction can produce. | ||
use super::{ | ||
super::jit_ir::{GuardInst, Inst, InstIdx, Module, Operand, Predicate}, | ||
Value, | ||
}; | ||
|
||
/// Ongoing analysis of a trace: what value can a given instruction in the past produce? | ||
/// | ||
/// Note that the analysis is forward-looking: just because an instruction's `Value` is (say) a | ||
/// `Const` now does not mean it would be valid to assume that at earlier points it is safe to | ||
/// assume it was also a `Const`. | ||
pub(super) struct Analyse { | ||
/// For each instruction, what have we learnt about its [Value] so far? | ||
values: Vec<Value>, | ||
} | ||
|
||
impl Analyse { | ||
pub(super) fn new(m: &Module) -> Analyse { | ||
Analyse { | ||
values: vec![Value::Unknown; m.insts_len()], | ||
} | ||
} | ||
|
||
/// Map `op` based on our analysis so far. In some cases this will return `op` unchanged, but | ||
/// in others it may be able to turn what looks like a variable reference into a constant. | ||
pub(super) fn op_map(&mut self, m: &Module, op: Operand) -> Operand { | ||
match op { | ||
Operand::Var(iidx) => match self.values[usize::from(iidx)] { | ||
Value::Unknown => { | ||
// Since we last saw an `ICmp` instruction, we may have gathered new knowledge | ||
// that allows us to turn it into a constant. | ||
if let (iidx, Inst::ICmp(inst)) = m.inst_decopy(iidx) { | ||
let lhs = self.op_map(m, inst.lhs(m)); | ||
let pred = inst.predicate(); | ||
let rhs = self.op_map(m, inst.rhs(m)); | ||
if let (&Operand::Const(lhs_cidx), &Operand::Const(rhs_cidx)) = (&lhs, &rhs) | ||
{ | ||
if pred == Predicate::Equal && m.const_(lhs_cidx) == m.const_(rhs_cidx) | ||
{ | ||
self.values[usize::from(iidx)] = Value::Const(m.true_constidx()); | ||
return Operand::Const(m.true_constidx()); | ||
} | ||
} | ||
} | ||
op | ||
} | ||
Value::Const(cidx) => Operand::Const(cidx), | ||
}, | ||
Operand::Const(_) => op, | ||
} | ||
} | ||
|
||
/// Update our idea of what value the instruction at `iidx` can produce. | ||
pub(super) fn set_value(&mut self, iidx: InstIdx, v: Value) { | ||
self.values[usize::from(iidx)] = v; | ||
} | ||
|
||
/// Use the guard `inst` to update our knowledge about the variable used as its condition. | ||
pub(super) fn guard(&mut self, m: &Module, inst: GuardInst) { | ||
if let Operand::Var(iidx) = inst.cond(m) { | ||
if let (_, Inst::ICmp(inst)) = m.inst_decopy(iidx) { | ||
let lhs = self.op_map(m, inst.lhs(m)); | ||
let pred = inst.predicate(); | ||
let rhs = self.op_map(m, inst.rhs(m)); | ||
match (&lhs, &rhs) { | ||
(&Operand::Const(_), &Operand::Const(_)) => { | ||
// This will have been handled by icmp/guard optimisations. | ||
unreachable!(); | ||
} | ||
(&Operand::Var(iidx), &Operand::Const(cidx)) | ||
| (&Operand::Const(cidx), &Operand::Var(iidx)) => { | ||
if pred == Predicate::Equal { | ||
self.set_value(iidx, Value::Const(cidx)); | ||
} | ||
} | ||
(&Operand::Var(_), &Operand::Var(_)) => (), | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.