fix(core): inline loop-free callees first so the loop guard can fire
The guard ran before inlining and only ever saw calls.
This commit is contained in:
@@ -61,6 +61,14 @@ pub fn optimize(
|
|||||||
// inlined loop down with it, so leave those callees where they are:
|
// inlined loop down with it, so leave those callees where they are:
|
||||||
// as their own word the loop keeps its registers, and one call is far
|
// as their own word the loop keeps its registers, and one call is far
|
||||||
// cheaper than a loop's worth of memory traffic.
|
// cheaper than a loop's worth of memory traffic.
|
||||||
|
//
|
||||||
|
// This takes two passes, because before the primitives are substituted
|
||||||
|
// the caller is nothing but `Call`s -- `DROP` and `CR` included -- and
|
||||||
|
// the promotability check deliberately looks through calls. Asked too
|
||||||
|
// early it says "promotable" about almost anything, which is how this
|
||||||
|
// guard managed to be a no-op. Inline the loop-free callees first, then
|
||||||
|
// ask, then let the loop-bearing ones in if the answer was yes.
|
||||||
|
ir = inline(ir, bodies, 8, true);
|
||||||
let keep_loops_out = !crate::codegen::promotable_modulo_calls(&ir);
|
let keep_loops_out = !crate::codegen::promotable_modulo_calls(&ir);
|
||||||
ir = inline(ir, bodies, 8, keep_loops_out);
|
ir = inline(ir, bodies, 8, keep_loops_out);
|
||||||
}
|
}
|
||||||
@@ -1017,6 +1025,49 @@ mod tests {
|
|||||||
assert_eq!(expand_self_guard(body.clone(), SELF), body);
|
assert_eq!(expand_self_guard(body.clone(), SELF), body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_loop_stays_out_of_a_caller_that_is_only_unpromotable_through_a_call() {
|
||||||
|
// The shape the benchmark harness uses, and the one that made this
|
||||||
|
// guard a no-op for its whole life: at the moment the guard runs, the
|
||||||
|
// caller's `CR` is still `Call(cr_word)`, not `IrOp::Cr`. A test built
|
||||||
|
// from `IrOp::Cr` directly passes even with the bug.
|
||||||
|
let cross = WordId(7);
|
||||||
|
let cr = WordId(9);
|
||||||
|
let mut bodies = HashMap::new();
|
||||||
|
bodies.insert(cr, vec![IrOp::Cr]);
|
||||||
|
bodies.insert(
|
||||||
|
cross,
|
||||||
|
vec![
|
||||||
|
IrOp::PushI32(0),
|
||||||
|
IrOp::Swap,
|
||||||
|
IrOp::PushI32(0),
|
||||||
|
IrOp::DoLoop {
|
||||||
|
body: vec![IrOp::RFetch, IrOp::Call(WordId(8)), IrOp::Xor],
|
||||||
|
is_plus_loop: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let out = opt_with_inline(
|
||||||
|
vec![
|
||||||
|
IrOp::PushI32(300000),
|
||||||
|
IrOp::Call(cross),
|
||||||
|
IrOp::Drop,
|
||||||
|
IrOp::Call(cr),
|
||||||
|
],
|
||||||
|
&bodies,
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
out.iter()
|
||||||
|
.any(|op| matches!(op, IrOp::Call(id) if *id == cross)),
|
||||||
|
"a loop-bearing callee must not be inlined into a caller that cannot \
|
||||||
|
be promoted -- it would lose its registers: {out:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
out.iter().any(|op| matches!(op, IrOp::Cr)),
|
||||||
|
"the loop-free callee should still have been inlined: {out:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn opt_with_inline(ops: Vec<IrOp>, bodies: &HashMap<WordId, Vec<IrOp>>) -> Vec<IrOp> {
|
fn opt_with_inline(ops: Vec<IrOp>, bodies: &HashMap<WordId, Vec<IrOp>>) -> Vec<IrOp> {
|
||||||
let config = OptConfig {
|
let config = OptConfig {
|
||||||
peephole: true,
|
peephole: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user