use std::collections::{HashMap, HashSet};
use rucc_cost::heuristics;
use rucc_ir::{
Block, BlockCall, Builder, Def, Extra, Flags, Func, Inst, InstData, IntPred, Opcode, Type,
Value,
};
use crate::canon;
use crate::cfg::Cfg;
use crate::copy;
use crate::discharge::{Question, constant, operand_of, yes};
use crate::dom::Dominators;
use crate::frontier::Frontiers;
use crate::loops::{LoopId, Loops};
use crate::rules::safety;
use crate::scev::{Anchor, Evolution, Plain, Reading, Scev};
use crate::trip::{Around, counted, covered, inst_of};
use crate::{Analyses, Fuel, Pass, Preserved, Stats};
const SPLIT: &str = "loop split, the iterations in front of the first one that could fail a check \
run without them";
const CLOSED_HERE: &str = "loop put back into closed form, a value it defines is read after it and both halves define one";
const NO_FUEL: &str = "loop left alone, the pass ran out of fuel";
const NO_PREHEADER: &str = "loop left alone, it has no block in front of it to put a check in";
const NESTED_WITH_ONE: &str = "loop left alone, a loop inside it is being split here instead";
const INSIDE_A_LOOP: &str =
"check kept in both halves, it is in a loop inside the one being split and moves with that one";
const MANY_LATCHES: &str = "loop left alone, it goes back to its header from more than one place";
const A_CALL_INSIDE: &str = "loop left alone, a call in it might free what the loop is reading";
const ENDS_A_LIFETIME: &str = "loop left alone, something in it ends a lifetime";
const NOT_COPYABLE: &str = "loop left alone, something in it carries a side table this cannot copy";
const ESCAPES: &str = "loop left alone, a value it defines is read outside it";
const WANTED_ELSEWHERE: &str =
"loop left alone, the guard of another loop being split here names a value it defines";
const TOO_BIG: &str = "loop left alone, the two halves would be more code than the limit allows";
const NOT_A_SWEEP: &str = "check kept in both halves, its address does not walk the loop by a \
constant";
const NOT_FOLLOWED: &str = "check kept in both halves, what its address does round the loop is not \
something the analysis follows";
const MISALIGNED: &str =
"check kept in both halves, its step is not a whole number of its alignment";
const MEASURED_ALIGN: &str = "check kept in both halves, the guard would measure how far its \
address moved and that is no answer about its alignment";
const ALREADY_COMPUTED: &str =
"check kept in both halves, how many bytes it covers is a number only the program has";
const NOT_PROVED: &str = "check kept in both halves, no rule in the safety namespace says an offset inside the window is \
an access inside the object";
#[derive(Debug)]
pub struct Split;
impl Pass for Split {
fn name(&self) -> &'static str {
"split"
}
fn describe(&self) -> &'static str {
"a loop becomes a run of iterations with no checks in it and the rest of the loop with them"
}
fn preserves(&self) -> Preserved {
Preserved::NONE
}
fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
let mut stats = Stats::new();
if func.entry().is_none() {
return stats;
}
let cfg = an.cfg(func).clone();
let loops = an.loops(func).clone();
if loops.count() == 0 {
return stats;
}
let mut plans = planned(func, &cfg, &loops, &mut stats);
let dom = an.dominators(func).clone();
let fronts = an.frontiers(func).clone();
let repairs = repaired(func, &dom, &fronts, &loops, &plans, fuel);
if repairs.made > 0 {
stats = Stats::new();
plans = planned(func, &cfg, &loops, &mut stats);
for _ in 0..repairs.worked {
stats.optimized(CLOSED_HERE);
}
}
let named: Vec<(LoopId, Value)> = plans
.iter()
.flat_map(|plan| mentions(func, plan).into_iter().map(move |value| (plan.id, value)))
.collect();
plans.retain(|plan| {
if leaving(func, plan) {
stats.missed(ESCAPES);
return false;
}
if elsewhere(func, plan, &named) {
stats.missed(WANTED_ELSEWHERE);
return false;
}
true
});
let mut changed = false;
for plan in plans {
if !fuel.take() {
stats.missed(NO_FUEL);
continue;
}
apply(func, &plan);
stats.optimized(SPLIT);
changed = true;
}
if changed {
an.clear();
}
stats
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Walk {
By(i128),
Again {
at: Value,
guess: i128,
},
}
impl Walk {
fn still(self) -> bool {
self == Self::By(0)
}
fn down(self) -> bool {
matches!(self, Self::By(step) if step < 0)
}
fn stride(self) -> i128 {
match self {
Self::By(step) => step.abs(),
Self::Again { guess, .. } => guess,
}
}
fn key(self) -> Key {
match self {
Self::By(step) => Key::Every(step.abs()),
Self::Again { at, .. } => Key::From(at),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Key {
Every(i128),
From(Value),
}
#[derive(Debug)]
struct Sweep {
check: Inst,
base: Anchor,
apart: Plain,
walk: Walk,
rebuild: Vec<Value>,
reach: i128,
}
#[derive(Debug)]
struct Plan {
id: LoopId,
preheader: Block,
header: Block,
latch: Block,
body: Vec<Block>,
around: Around,
sweeps: Vec<Sweep>,
}
fn sweep(
func: &Func,
cfg: &Cfg,
loops: &Loops,
scev: &mut Scev<'_>,
id: LoopId,
plans: &mut Vec<Plan>,
stats: &mut Stats,
) {
let body = loops.blocks(id).to_vec();
let checks: Vec<Inst> = body
.iter()
.flat_map(|&block| func.insts(block).collect::<Vec<Inst>>())
.filter(|&inst| matches!(func[inst].opcode, Opcode::CheckBounds | Opcode::CheckLive))
.collect();
if checks.is_empty() {
return;
}
let (preheader, latch) = match shaped(func, cfg, loops, id, &body) {
Ok(shape) => shape,
Err(why) => {
stats.missed(why);
return;
}
};
let around =
counted(scev, id).unwrap_or(Around::Number(i128::from(crate::scev::ASSUMED_ITERATIONS)));
let mut sweeps = Vec::new();
for check in checks {
if func.block_of(check).is_none_or(|block| loops.innermost(block) != Some(id)) {
stats.missed(INSIDE_A_LOOP);
continue;
}
match walked(func, cfg, loops, scev, id, latch, check) {
Ok(sweep) => sweeps.push(sweep),
Err(why) => stats.missed(why),
}
}
if sweeps.is_empty() {
return;
}
plans.push(Plan { id, preheader, header: loops.header(id), latch, body, around, sweeps });
}
fn shaped(
func: &Func,
cfg: &Cfg,
loops: &Loops,
id: LoopId,
body: &[Block],
) -> Result<(Block, Block), &'static str> {
let Some(preheader) = loops.preheader(cfg, id) else {
return Err(NO_PREHEADER);
};
let [latch] = loops.latches(id) else {
return Err(MANY_LATCHES);
};
for &block in body {
for inst in func.insts(block) {
match func[inst].opcode {
Opcode::Call | Opcode::CallIndirect | Opcode::TailCall
if !func[inst].flags.contains(Flags::NOFREE) =>
{
return Err(A_CALL_INSIDE);
}
Opcode::InlineAsm | Opcode::MetaEnd | Opcode::MetaTransfer => {
return Err(ENDS_A_LIFETIME);
}
_ => {}
}
if !copy::copyable(func, inst) {
return Err(NOT_COPYABLE);
}
}
}
let size = body.iter().map(|&block| func.insts(block).count()).sum::<usize>();
if size > heuristics::SPLIT_MAX_INSNS as usize {
return Err(TOO_BIG);
}
Ok((preheader, *latch))
}
fn planned(func: &Func, cfg: &Cfg, loops: &Loops, stats: &mut Stats) -> Vec<Plan> {
let mut plans = Vec::new();
let mut scev = Scev::new(func, cfg, loops);
for id in loops.all() {
sweep(func, cfg, loops, &mut scev, id, &mut plans, stats);
}
plans.sort_by_key(|plan| std::cmp::Reverse(loops.depth(plan.id)));
let mut taken: HashSet<Block> = HashSet::new();
plans.retain(|plan| {
if plan.body.iter().any(|block| taken.contains(block)) {
stats.missed(NESTED_WITH_ONE);
return false;
}
taken.extend(plan.body.iter().copied());
true
});
plans
}
struct Repairs {
made: usize,
worked: usize,
}
fn repaired(
func: &mut Func,
dom: &Dominators,
fronts: &Frontiers,
loops: &Loops,
plans: &[Plan],
fuel: &mut Fuel,
) -> Repairs {
let mut repairs = Repairs { made: 0, worked: 0 };
for plan in plans {
if !leaving(func, plan) {
continue;
}
let mut wrote = false;
while let Some(job) = canon::leaked(func, dom, fronts, loops, plan.id) {
if !fuel.take() {
break;
}
canon::close(func, dom, loops, &job);
wrote = true;
}
if !wrote {
continue;
}
repairs.made += 1;
if !leaving(func, plan) {
repairs.worked += 1;
}
}
repairs
}
fn leaving(func: &Func, plan: &Plan) -> bool {
let inside: HashSet<Block> = plan.body.iter().copied().collect();
escapes(func, &plan.body, &inside)
}
fn mentions(func: &Func, plan: &Plan) -> Vec<Value> {
let mut found = Vec::new();
if let Around::Computed(plain, _) = plan.around {
found.extend(plain.value);
}
for sweep in &plan.sweeps {
found.extend(sweep.base.value());
found.extend(sweep.apart.value);
if let Walk::Again { at, .. } = sweep.walk {
found.push(at);
}
for &value in &sweep.rebuild {
found.push(value);
if let Def::Result { inst, .. } = func[value].def {
found.extend(func[func[inst].args].iter().copied());
}
}
}
found
}
fn elsewhere(func: &Func, plan: &Plan, named: &[(LoopId, Value)]) -> bool {
let defined = defines(func, &plan.body);
named.iter().any(|&(id, value)| id != plan.id && defined.contains(&value))
}
fn defines(func: &Func, body: &[Block]) -> HashSet<Value> {
let mut defined: HashSet<Value> = HashSet::new();
for &block in body {
defined.extend(func[block].params.iter().copied());
for inst in func.insts(block) {
defined.extend(func[inst].results());
}
}
defined
}
fn escapes(func: &Func, body: &[Block], inside: &HashSet<Block>) -> bool {
let defined = defines(func, body);
for block in func.blocks() {
if inside.contains(&block) {
continue;
}
for inst in func.insts(block) {
if func[func[inst].args].iter().any(|value| defined.contains(value)) {
return true;
}
for call in func.successors(inst) {
if func[call.args].iter().any(|value| defined.contains(value)) {
return true;
}
}
}
}
false
}
#[allow(clippy::too_many_arguments)]
fn walked(
func: &Func,
cfg: &Cfg,
loops: &Loops,
scev: &mut Scev<'_>,
id: LoopId,
latch: Block,
check: Inst,
) -> Result<Sweep, &'static str> {
let args = &func[func[check].args];
if args.len() > 2 {
return Err(ALREADY_COMPUTED);
}
let (Some(&capability), Some(&pointer)) = (args.first(), args.get(1)) else {
return Err(NOT_A_SWEEP);
};
if operand_of(func, capability, Opcode::CapOf, 0) != Some(pointer) {
return Err(NOT_A_SWEEP);
}
let (reach, align) = match func[check].extra {
Extra::Mem(held) => (i128::from(func[held].size), i128::from(func[held].align)),
_ => (1, 1),
};
let (base, apart, walk, rebuild) = match following(func, scev, id, pointer) {
Ok((base, apart, step)) => (base, apart, Walk::By(step), Vec::new()),
Err(why) => match measured(func, cfg, loops, id, latch, pointer, reach) {
Some(found) => found,
None => return Err(why),
},
};
match walk {
Walk::By(step) if step != 0 && step % align != 0 => return Err(MISALIGNED),
Walk::Again { .. } if align > 1 => return Err(MEASURED_ALIGN),
_ => {}
}
if !windowed(reach, walk.down()) {
return Err(NOT_PROVED);
}
Ok(Sweep { check, base, apart, walk, rebuild, reach })
}
fn following(
func: &Func,
scev: &mut Scev<'_>,
id: LoopId,
pointer: Value,
) -> Result<(Anchor, Plain, i128), &'static str> {
let (start, step) = match scev.evolution(id, pointer) {
Evolution::Affine(chrec) => {
let Some(step) = chrec.step.as_number() else {
return Err(NOT_A_SWEEP);
};
(chrec.base, step)
}
Evolution::Invariant(base) => (base, 0),
_ => return Err(NOT_FOLLOWED),
};
match (start.plain(), start.on()) {
(Some(at @ Plain { value: Some(base), read: None, scale: 1, .. }), _) => Ok((
Anchor::Value(base),
Plain { value: None, read: None, scale: 0, offset: at.offset },
step,
)),
(_, Some((base, apart))) if walks(func, base, apart) => Ok((base, apart, step)),
_ => Err(NOT_A_SWEEP),
}
}
#[allow(clippy::too_many_arguments)]
fn measured(
func: &Func,
cfg: &Cfg,
loops: &Loops,
id: LoopId,
latch: Block,
pointer: Value,
reach: i128,
) -> Option<(Anchor, Plain, Walk, Vec<Value>)> {
let (at, offset) = peeled(func, pointer);
if !func[at].ty.is_ptr() {
return None;
}
let mut rebuild = Vec::new();
let mut leaves = Vec::new();
let mut seen = HashSet::new();
if !writable(func, loops, id, at, &mut rebuild, &mut leaves, &mut seen) {
return None;
}
if rebuild.len() > heuristics::SPLIT_REMADE_INSNS {
return None;
}
let mut far = 0;
for leaf in leaves {
far = far.max(carried(func, cfg, loops, id, latch, leaf)?);
}
let guess = if far == 0 { reach } else { far };
let apart = Plain { value: None, read: None, scale: 0, offset };
Some((Anchor::Value(at), apart, Walk::Again { at, guess }, rebuild))
}
fn writable(
func: &Func,
loops: &Loops,
id: LoopId,
value: Value,
order: &mut Vec<Value>,
leaves: &mut Vec<Value>,
seen: &mut HashSet<Value>,
) -> bool {
if !seen.insert(value) {
return true;
}
let at = match func[value].def {
Def::Result { inst, .. } => func.block_of(inst),
Def::Param { block, .. } => Some(block),
};
if at.is_none_or(|at| !loops.contains(id, at)) {
if func[value].ty.is_ptr() {
leaves.push(value);
}
return true;
}
if at.is_some_and(|at| loops.innermost(at) != Some(id)) {
return false;
}
match func[value].def {
Def::Param { block, .. } => {
if block != loops.header(id) {
return false;
}
if func[value].ty.is_ptr() {
leaves.push(value);
}
true
}
Def::Result { inst, index } => {
if index != 0 || !plain(func[inst].opcode) {
return false;
}
let args = func[func[inst].args].to_vec();
if !args.iter().all(|&arg| writable(func, loops, id, arg, order, leaves, seen)) {
return false;
}
order.push(value);
true
}
}
}
fn plain(opcode: Opcode) -> bool {
matches!(
opcode,
Opcode::IConst
| Opcode::Add
| Opcode::Sub
| Opcode::Mul
| Opcode::Shl
| Opcode::LShr
| Opcode::AShr
| Opcode::And
| Opcode::Or
| Opcode::Xor
| Opcode::SExt
| Opcode::ZExt
| Opcode::Trunc
| Opcode::ICmp
| Opcode::Select
| Opcode::PtrAdd
| Opcode::GlobalAddr
)
}
fn remade(
build: &mut Builder<'_>,
made: &mut Vec<Value>,
order: &[Value],
at: Value,
swap: &HashMap<Value, Value>,
) -> Value {
let mut swap = swap.clone();
for &value in order {
let Def::Result { inst, .. } = build.func()[value].def else {
unreachable!("the order holds nothing but instruction results")
};
let data = build.func()[inst];
let args: Vec<Value> = build.func()[data.args]
.iter()
.map(|arg| swap.get(arg).copied().unwrap_or(*arg))
.collect();
let args = build.func().push_values(&args);
let ty = build.func()[value].ty;
let copy =
build.value(InstData { args, extra: data.extra, ..InstData::new(data.opcode) }, ty);
made.push(copy);
swap.insert(value, copy);
}
swap.get(&at).copied().unwrap_or(at)
}
fn carried(
func: &Func,
cfg: &Cfg,
loops: &Loops,
id: LoopId,
latch: Block,
leaf: Value,
) -> Option<i128> {
let header = loops.header(id);
let Def::Param { block, index } = func[leaf].def else { return Some(0) };
if block != header {
return Some(0);
}
let term = func.terminator(latch)?;
let round = copy::edge_args(func, term, header);
let &next = round.get(index as usize)?;
let mut seen = HashSet::new();
moving(func, cfg, loops, id, leaf, next, &mut seen)
}
fn peeled(func: &Func, pointer: Value) -> (Value, i128) {
let mut at = pointer;
let mut offset = 0;
while let Some(by) = operand_of(func, at, Opcode::PtrAdd, 1) {
let (Some(step), Some(of)) = (constant(func, by), operand_of(func, at, Opcode::PtrAdd, 0))
else {
break;
};
offset += step;
at = of;
}
(at, offset)
}
fn moving(
func: &Func,
cfg: &Cfg,
loops: &Loops,
id: LoopId,
param: Value,
value: Value,
seen: &mut HashSet<Value>,
) -> Option<i128> {
if value == param {
return Some(0);
}
if !seen.insert(value) {
return Some(0);
}
let at = match func[value].def {
Def::Result { inst, .. } => func.block_of(inst)?,
Def::Param { block, .. } => block,
};
if loops.innermost(at) != Some(id) {
return None;
}
match func[value].def {
Def::Result { inst, .. } => {
let args = &func[func[inst].args];
match func[inst].opcode {
Opcode::PtrAdd => {
let (&of, &by) = (args.first()?, args.get(1)?);
let far = moving(func, cfg, loops, id, param, of, seen)?;
Some(far.max(constant(func, by).map_or(0, i128::abs)))
}
Opcode::Select => {
let (&one, &two) = (args.get(1)?, args.get(2)?);
let one = moving(func, cfg, loops, id, param, one, seen)?;
let two = moving(func, cfg, loops, id, param, two, seen)?;
Some(one.max(two))
}
_ => None,
}
}
Def::Param { block, index } => {
if block == loops.header(id) {
return None;
}
let mut far = 0;
for &pred in cfg.predecessors(block) {
let term = func.terminator(pred)?;
let args = copy::edge_args(func, term, block);
let &came = args.get(index as usize)?;
far = far.max(moving(func, cfg, loops, id, param, came, seen)?);
}
Some(far)
}
}
}
fn walks(func: &Func, base: Anchor, apart: Plain) -> bool {
let word = Type::int(64);
if !base.value().is_none_or(|base| func[base].ty.is_ptr()) {
return false;
}
let Some(value) = apart.value.filter(|_| apart.scale != 0) else { return true };
match apart.read {
None => func[value].ty == word,
Some(read) => read.to == word && func[value].ty.is_int() && func[value].ty.bits() < 64,
}
}
fn apply(func: &mut Func, plan: &Plan) {
let mut renamed: HashMap<Value, Value> = HashMap::new();
let copies = copy::blocks(func, &plan.body, &mut renamed);
let slow = copies[&plan.header];
let Choice { ok, windows } = limited(func, plan);
if windows.is_empty() {
let term = func.terminator(plan.preheader).expect("a preheader ends in a jump");
let args = copy::edge_args(func, term, plan.header);
func.remove_inst(term);
Builder::new(func, plan.preheader).br_if(ok, plan.header, &args, slow, &args);
take(func, plan);
return;
}
let word = Type::int(64);
let counting: Vec<i128> =
windows.iter().filter(|window| window.from.is_none()).map(|w| stepped(w.key)).collect();
let types: Vec<Type> = func[plan.header].params.iter().map(|¶m| func[param].ty).collect();
let guard = func.create_block();
let offsets: Vec<Value> = counting.iter().map(|_| func.append_param(guard, word)).collect();
let carried: Vec<Value> = types.iter().map(|&ty| func.append_param(guard, ty)).collect();
let held: HashMap<Value, Value> =
func[plan.header].params.iter().copied().zip(carried.iter().copied()).collect();
let mut build = Builder::new(func, guard);
let mut spent = Vec::new();
let mut inside: Option<Value> = None;
let mut counted = 0;
for window in &windows {
let offset = match window.from {
None => {
let offset = offsets[counted];
counted += 1;
offset
}
Some(from) => {
let Key::From(at) = window.key else {
unreachable!("only a measured window holds where its pointer began")
};
let here = remade(&mut build, &mut spent, &window.rebuild, at, &held);
let now = build.unary(Opcode::PtrToInt, here, word);
build.binary(Opcode::Sub, now, from, Flags::NONE)
}
};
let under = build.icmp(IntPred::Ule, offset, window.bound);
inside = Some(match inside {
None => under,
Some(so_far) => build.binary(Opcode::And, so_far, under, Flags::NONE),
});
}
let inside = inside.expect("a plan with a window has at least one of them");
build.br_if(inside, plan.header, &carried, slow, &carried);
let term = func.terminator(plan.preheader).expect("a preheader ends in a jump to the header");
let args = copy::edge_args(func, term, plan.header);
func.remove_inst(term);
let mut build = Builder::new(func, plan.preheader);
let zero = build.iconst(word, 0);
let mut into: Vec<Value> = offsets.iter().map(|_| zero).collect();
into.extend_from_slice(&args);
build.br_if(ok, guard, &into, slow, &args);
let term = func.terminator(plan.latch).expect("a latch ends in a branch back to the header");
let mut build = Builder::new(func, plan.latch);
let mut made = Vec::new();
let mut next = Vec::new();
for (&offset, &step) in offsets.iter().zip(&counting) {
let by = build.iconst(word, step);
made.push(by);
let walked = build.binary(Opcode::Add, offset, by, Flags::NUW);
made.push(walked);
next.push(walked);
}
for value in made {
let inst = inst_of(func, value);
func.remove_inst(inst);
func.insert_before(inst, term);
}
route(func, term, plan.header, guard, &next);
take(func, plan);
}
fn take(func: &mut Func, plan: &Plan) {
for sweep in &plan.sweeps {
func.remove_inst(sweep.check);
}
}
fn route(func: &mut Func, term: Inst, from: Block, to: Block, first: &[Value]) {
for at in func.target_list(term).iter() {
let call = func[at];
if call.block != from {
continue;
}
let mut args = first.to_vec();
args.extend_from_slice(&func[call.args]);
let args = func.push_values(&args);
func.set_block_call(at, BlockCall { block: to, args });
}
}
struct Window {
key: Key,
bound: Value,
from: Option<Value>,
rebuild: Vec<Value>,
}
struct Choice {
ok: Value,
windows: Vec<Window>,
}
fn limited(func: &mut Func, plan: &Plan) -> Choice {
let word = Type::int(64);
let term = func.terminator(plan.preheader).expect("a preheader ends in a jump to the header");
let entering = copy::edge_args(func, term, plan.header);
let swap: HashMap<Value, Value> =
func[plan.header].params.iter().copied().zip(entering.iter().copied()).collect();
let mut made = Vec::new();
let mut build = Builder::new(func, plan.preheader);
let mut ok: Option<Value> = None;
let mut windows: Vec<Window> = Vec::new();
let mut begun: HashMap<Value, Value> = HashMap::new();
for sweep in &plan.sweeps {
let around = if sweep.walk.still() { Around::Number(0) } else { plan.around };
let base = match sweep.walk {
Walk::By(_) => anchored(&mut build, &mut made, sweep.base),
Walk::Again { at, .. } => match begun.get(&at) {
Some(&had) => had,
None => {
let first = remade(&mut build, &mut made, &sweep.rebuild, at, &swap);
begun.insert(at, first);
first
}
},
};
let (window, zero) = spare(&mut build, &mut made, sweep, around, base);
let fits = build.icmp(IntPred::Sge, window, zero);
made.push(fits);
ok = Some(match ok {
None => fits,
Some(so_far) => {
let both = build.binary(Opcode::And, so_far, fits, Flags::NONE);
made.push(both);
both
}
});
if sweep.walk.still() {
continue;
}
let key = sweep.walk.key();
match windows.iter().position(|held| held.key == key) {
Some(at) => {
let bound = windows[at].bound;
let smaller = build.icmp(IntPred::Ult, window, bound);
made.push(smaller);
let least = build.select(smaller, window, bound);
made.push(least);
windows[at].bound = least;
}
None => {
let from = match key {
Key::Every(_) => None,
Key::From(_) => {
let from = build.unary(Opcode::PtrToInt, base, word);
made.push(from);
Some(from)
}
};
let rebuild = if from.is_some() { sweep.rebuild.clone() } else { Vec::new() };
windows.push(Window { key, bound: window, from, rebuild });
}
}
}
let ok = ok.expect("a plan holds at least one check");
for window in &mut windows {
window.bound = bounded(&mut build, &mut made, stepped(window.key), window.bound);
}
for value in made {
let inst = inst_of(func, value);
func.remove_inst(inst);
func.insert_before(inst, term);
}
Choice { ok, windows }
}
fn stepped(key: Key) -> i128 {
match key {
Key::Every(step) => step,
Key::From(_) => 0,
}
}
fn bounded(build: &mut Builder<'_>, made: &mut Vec<Value>, step: i128, bound: Value) -> Value {
let word = Type::int(64);
let room = build.iconst(word, i128::from(i64::MAX) - step);
made.push(room);
let over = build.icmp(IntPred::Ugt, bound, room);
made.push(over);
let held = build.select(over, room, bound);
made.push(held);
held
}
fn windowed(reach: i128, down: bool) -> bool {
let mut question = Question::default();
let at = question.opaque();
let at = question.app("value.i64", &[at]);
let span = question.opaque();
let span = question.app("value.i64", &[span]);
let far = question.opaque();
let far = question.app("value.i64", &[far]);
let reach = question.number(reach);
let reach = question.app("iconst.i64", &[reach]);
let delta = question.opaque();
let delta = question.app("value.i64", &[delta]);
let head = if down { "swept.down.sym.i64" } else { "swept.sym.i64" };
let term = question.app(head, &[at, span, far, reach, delta]);
match safety::TABLE.find(&question, term) {
Some(found) => yes(&safety::TABLE, found.rule),
None => false,
}
}
fn anchored(build: &mut Builder<'_>, made: &mut Vec<Value>, base: Anchor) -> Value {
match base {
Anchor::Value(value) => value,
Anchor::Address(symbol) => {
let extra = Extra::Symbol(symbol);
let at =
build.value(InstData { extra, ..InstData::new(Opcode::GlobalAddr) }, Type::PTR);
made.push(at);
at
}
}
}
fn displacement(build: &mut Builder<'_>, made: &mut Vec<Value>, apart: Plain) -> Option<Value> {
let word = Type::int(64);
let mut sum = match apart.value.filter(|_| apart.scale != 0) {
None => {
return (apart.offset != 0).then(|| {
let by = build.iconst(word, apart.offset);
made.push(by);
by
});
}
Some(value) => value,
};
if let Some(read) = apart.read {
let widen = match read.reading {
Reading::Signed => Opcode::SExt,
Reading::Unsigned => Opcode::ZExt,
};
sum = build.unary(widen, sum, read.to);
made.push(sum);
}
if apart.scale != 1 {
let by = build.iconst(word, apart.scale);
made.push(by);
sum = build.binary(Opcode::Mul, sum, by, Flags::NONE);
made.push(sum);
}
if apart.offset != 0 {
let by = build.iconst(word, apart.offset);
made.push(by);
sum = build.binary(Opcode::Add, sum, by, Flags::NONE);
made.push(sum);
}
Some(sum)
}
fn spare(
build: &mut Builder<'_>,
made: &mut Vec<Value>,
sweep: &Sweep,
around: Around,
base: Value,
) -> (Value, Value) {
let word = Type::int(64);
let first = match displacement(build, made, sweep.apart) {
None => base,
Some(by) => {
let args = build.func().push_values(&[base, by]);
let sum = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
made.push(sum);
sum
}
};
let stride = sweep.walk.stride();
let want = match around {
Around::Number(times) => {
let far = times.saturating_mul(stride).saturating_add(sweep.reach);
let far = i64::try_from(far).unwrap_or(i64::MAX);
let bytes = build.iconst(word, i128::from(far));
made.push(bytes);
bytes
}
Around::Computed(count, reading) => {
covered(build, made, count, stride, sweep.reach, reading, Flags::NONE)
}
};
let (asked, at) = if sweep.walk.down() {
let by = build.iconst(word, sweep.reach);
made.push(by);
let args = build.func().push_values(&[first, by]);
let end = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
made.push(end);
(Opcode::CapExtentBack, end)
} else {
(Opcode::CapExtent, first)
};
let args = build.func().push_values(&[at]);
let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
made.push(capability);
let args = build.func().push_values(&[capability, at, want]);
let extent = build.value(InstData { args, ..InstData::new(asked) }, word);
made.push(extent);
let reach = build.iconst(word, sweep.reach);
made.push(reach);
let left = build.binary(Opcode::Sub, extent, reach, Flags::NSW);
made.push(left);
let zero = build.iconst(word, 0);
made.push(zero);
(left, zero)
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{
Block, Builder, Extra, Flags, Func, Inst, InstData, IntPred, MemInfo, MemOrder, Module,
Opcode, Restrict, Signature, Type, Value, verify_func,
};
use rucc_target::{TargetInfo, Triple};
use super::{SPLIT, Split};
use crate::canon::Canon;
use crate::stats::Kind;
use crate::{Fuel, Pass, Stats};
const TRIPS: i128 = 16;
const WIDTH: i128 = 4;
fn leaving() -> (Interner, Func, Vec<Block>) {
walking(Some(TRIPS), Flags::NSW)
}
fn counting() -> (Interner, Func, Vec<Block>) {
walking(None, Flags::NSW)
}
fn uncounted() -> (Interner, Func, Vec<Block>) {
walking(Some(TRIPS), Flags::NONE)
}
fn from_an_index() -> (Interner, Func, Vec<Block>) {
let mut names = Interner::new();
let params = [Type::PTR, Type::int(64)];
let mut func = Func::new(names.intern("f"), Signature::new().with_params(¶ms));
let entry = func.create_block();
let head = func.create_block();
let more = func.create_block();
let done = func.create_block();
let array = func.append_param(entry, Type::PTR);
let start = func.append_param(entry, Type::int(64));
let counter = func.append_param(head, Type::int(64));
let zero = Builder::new(&mut func, entry).iconst(Type::int(64), 0);
Builder::new(&mut func, entry).jump(head, &[zero]);
let mut build = Builder::new(&mut func, head);
let index = build.binary(Opcode::Add, counter, start, Flags::NSW);
let by = build.iconst(Type::int(64), WIDTH);
let scaled = build.binary(Opcode::Mul, index, by, Flags::NSW);
let args = build.func().push_values(&[array, scaled]);
let pointer = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
check(&mut build, pointer);
let read = build.load(Type::int(32), pointer, mem(), Flags::NONE);
let nothing = build.iconst(Type::int(32), 0);
let stop = build.icmp(IntPred::Eq, read, nothing);
build.br_if(stop, done, &[], more, &[]);
let mut build = Builder::new(&mut func, more);
let one = build.iconst(Type::int(64), 1);
let next = build.binary(Opcode::Add, counter, one, Flags::NSW);
let limit = build.iconst(Type::int(64), TRIPS);
let again = build.icmp(IntPred::Slt, next, limit);
build.br_if(again, head, &[next], done, &[]);
Builder::new(&mut func, done).ret(&[]);
(names, func, vec![entry, head, more, done])
}
fn over_a_global() -> (Interner, Func, Vec<Block>) {
let mut names = Interner::new();
let tab = names.intern("tab");
let mut func = Func::new(names.intern("f"), Signature::new());
let entry = func.create_block();
let head = func.create_block();
let more = func.create_block();
let done = func.create_block();
let counter = func.append_param(head, Type::int(64));
let zero = Builder::new(&mut func, entry).iconst(Type::int(64), 0);
Builder::new(&mut func, entry).jump(head, &[zero]);
let mut build = Builder::new(&mut func, head);
let by = build.iconst(Type::int(64), WIDTH);
let scaled = build.binary(Opcode::Mul, counter, by, Flags::NSW);
let extra = Extra::Symbol(tab);
let array = build.value(InstData { extra, ..InstData::new(Opcode::GlobalAddr) }, Type::PTR);
let args = build.func().push_values(&[array, scaled]);
let pointer = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
check(&mut build, pointer);
let read = build.load(Type::int(32), pointer, mem(), Flags::NONE);
let nothing = build.iconst(Type::int(32), 0);
let stop = build.icmp(IntPred::Eq, read, nothing);
build.br_if(stop, done, &[], more, &[]);
let mut build = Builder::new(&mut func, more);
let one = build.iconst(Type::int(64), 1);
let next = build.binary(Opcode::Add, counter, one, Flags::NSW);
let limit = build.iconst(Type::int(64), TRIPS);
let again = build.icmp(IntPred::Slt, next, limit);
build.br_if(again, head, &[next], done, &[]);
Builder::new(&mut func, done).ret(&[]);
(names, func, vec![entry, head, more, done])
}
fn from_a_narrow_index() -> (Interner, Func, Vec<Block>) {
let mut names = Interner::new();
let params = [Type::PTR, Type::int(32)];
let mut func = Func::new(names.intern("f"), Signature::new().with_params(¶ms));
let entry = func.create_block();
let head = func.create_block();
let more = func.create_block();
let done = func.create_block();
let array = func.append_param(entry, Type::PTR);
let start = func.append_param(entry, Type::int(32));
let counter = func.append_param(head, Type::int(32));
let zero = Builder::new(&mut func, entry).iconst(Type::int(32), 0);
Builder::new(&mut func, entry).jump(head, &[zero]);
let mut build = Builder::new(&mut func, head);
let index = build.binary(Opcode::Add, counter, start, Flags::NSW);
let wide = build.unary(Opcode::SExt, index, Type::int(64));
let by = build.iconst(Type::int(64), WIDTH);
let scaled = build.binary(Opcode::Mul, wide, by, Flags::NSW);
let args = build.func().push_values(&[array, scaled]);
let pointer = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
check(&mut build, pointer);
let read = build.load(Type::int(32), pointer, mem(), Flags::NONE);
let nothing = build.iconst(Type::int(32), 0);
let stop = build.icmp(IntPred::Eq, read, nothing);
build.br_if(stop, done, &[], more, &[]);
let mut build = Builder::new(&mut func, more);
let one = build.iconst(Type::int(32), 1);
let next = build.binary(Opcode::Add, counter, one, Flags::NSW);
let limit = build.iconst(Type::int(32), TRIPS);
let again = build.icmp(IntPred::Slt, next, limit);
build.br_if(again, head, &[next], done, &[]);
Builder::new(&mut func, done).ret(&[]);
(names, func, vec![entry, head, more, done])
}
fn downwards() -> (Interner, Func, Vec<Block>) {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"), Signature::new().with_params(&[Type::PTR]));
let entry = func.create_block();
let head = func.create_block();
let more = func.create_block();
let done = func.create_block();
let array = func.append_param(entry, Type::PTR);
let counter = func.append_param(head, Type::int(64));
let last = Builder::new(&mut func, entry).iconst(Type::int(64), TRIPS - 1);
Builder::new(&mut func, entry).jump(head, &[last]);
let mut build = Builder::new(&mut func, head);
let by = build.iconst(Type::int(64), WIDTH);
let scaled = build.binary(Opcode::Mul, counter, by, Flags::NSW);
let args = build.func().push_values(&[array, scaled]);
let pointer = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
check(&mut build, pointer);
let read = build.load(Type::int(32), pointer, mem(), Flags::NONE);
let nothing = build.iconst(Type::int(32), 0);
let stop = build.icmp(IntPred::Eq, read, nothing);
build.br_if(stop, done, &[], more, &[]);
let mut build = Builder::new(&mut func, more);
let one = build.iconst(Type::int(64), 1);
let next = build.binary(Opcode::Sub, counter, one, Flags::NSW);
let floor = build.iconst(Type::int(64), 0);
let again = build.icmp(IntPred::Sge, next, floor);
build.br_if(again, head, &[next], done, &[]);
Builder::new(&mut func, done).ret(&[]);
(names, func, vec![entry, head, more, done])
}
fn by_what_it_read() -> (Interner, Func, Vec<Block>) {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"), Signature::new().with_params(&[Type::PTR]));
let entry = func.create_block();
let head = func.create_block();
let more = func.create_block();
let one = func.create_block();
let two = func.create_block();
let back = func.create_block();
let done = func.create_block();
let text = func.append_param(entry, Type::PTR);
let at = func.append_param(head, Type::PTR);
let next = func.append_param(back, Type::PTR);
Builder::new(&mut func, entry).jump(head, &[text]);
let mut build = Builder::new(&mut func, head);
checking(&mut build, at, byte());
let read = build.load(Type::int(8), at, byte(), Flags::NONE);
let nothing = build.iconst(Type::int(8), 0);
let stop = build.icmp(IntPred::Eq, read, nothing);
build.br_if(stop, done, &[], more, &[]);
let mut build = Builder::new(&mut func, more);
let wide = build.icmp(IntPred::Slt, read, nothing);
build.br_if(wide, two, &[], one, &[]);
for (block, step) in [(one, 1), (two, 2)] {
let mut build = Builder::new(&mut func, block);
let by = build.iconst(Type::int(64), step);
let args = build.func().push_values(&[at, by]);
let far = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
build.jump(back, &[far]);
}
Builder::new(&mut func, back).jump(head, &[next]);
Builder::new(&mut func, done).ret(&[]);
(names, func, vec![entry, head, more, one, two, back, done])
}
fn from_what_it_carries(reading: bool) -> (Interner, Func, Vec<Block>) {
let word = Type::int(64);
let mut names = Interner::new();
let mut func =
Func::new(names.intern("f"), Signature::new().with_params(&[Type::PTR, word]));
let entry = func.create_block();
let head = func.create_block();
let done = func.create_block();
let text = func.append_param(entry, Type::PTR);
let count = func.append_param(entry, word);
let at = func.append_param(head, Type::PTR);
let index = func.append_param(head, word);
let mut build = Builder::new(&mut func, entry);
let zero = build.iconst(word, 0);
build.jump(head, &[text, zero]);
let mut build = Builder::new(&mut func, head);
let spread = if reading {
build.load(word, at, mem(), Flags::NONE)
} else {
let mask = build.iconst(word, 7);
build.binary(Opcode::And, index, mask, Flags::NONE)
};
let args = build.func().push_values(&[at, spread]);
let pointer = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
checking(&mut build, pointer, byte());
let one = build.iconst(word, 1);
let next = build.binary(Opcode::Add, index, one, Flags::NSW);
let by = build.iconst(word, 8);
let args = build.func().push_values(&[at, by]);
let far = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
let again = build.icmp(IntPred::Slt, next, count);
build.br_if(again, head, &[far, next], done, &[]);
Builder::new(&mut func, done).ret(&[]);
(names, func, vec![entry, head, done])
}
fn down_a_list() -> (Interner, Func, Vec<Block>) {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"), Signature::new().with_params(&[Type::PTR]));
let entry = func.create_block();
let head = func.create_block();
let more = func.create_block();
let done = func.create_block();
let list = func.append_param(entry, Type::PTR);
let at = func.append_param(head, Type::PTR);
Builder::new(&mut func, entry).jump(head, &[list]);
let mut build = Builder::new(&mut func, head);
checking(&mut build, at, byte());
let read = build.load(Type::int(8), at, byte(), Flags::NONE);
let nothing = build.iconst(Type::int(8), 0);
let stop = build.icmp(IntPred::Eq, read, nothing);
build.br_if(stop, done, &[], more, &[]);
let mut build = Builder::new(&mut func, more);
let by = build.iconst(Type::int(64), 8);
let args = build.func().push_values(&[at, by]);
let field = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
let next = build.load(Type::PTR, field, mem(), Flags::NONE);
build.jump(head, &[next]);
Builder::new(&mut func, done).ret(&[]);
(names, func, vec![entry, head, more, done])
}
fn walking(times: Option<i128>, flags: Flags) -> (Interner, Func, Vec<Block>) {
let mut names = Interner::new();
let mut params = vec![Type::PTR];
params.extend(times.is_none().then_some(Type::int(64)));
let mut func = Func::new(names.intern("f"), Signature::new().with_params(¶ms));
let entry = func.create_block();
let head = func.create_block();
let more = func.create_block();
let done = func.create_block();
let array = func.append_param(entry, Type::PTR);
let handed = times.is_none().then(|| func.append_param(entry, Type::int(64)));
let counter = func.append_param(head, Type::int(64));
let zero = Builder::new(&mut func, entry).iconst(Type::int(64), 0);
Builder::new(&mut func, entry).jump(head, &[zero]);
let mut build = Builder::new(&mut func, head);
let by = build.iconst(Type::int(64), WIDTH);
let scaled = build.binary(Opcode::Mul, counter, by, Flags::NSW);
let args = build.func().push_values(&[array, scaled]);
let pointer = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
check(&mut build, pointer);
let read = build.load(Type::int(32), pointer, mem(), Flags::NONE);
let nothing = build.iconst(Type::int(32), 0);
let stop = build.icmp(IntPred::Eq, read, nothing);
build.br_if(stop, done, &[], more, &[]);
let mut build = Builder::new(&mut func, more);
let one = build.iconst(Type::int(64), 1);
let next = build.binary(Opcode::Add, counter, one, flags);
let limit = match (times, handed) {
(Some(times), _) => build.iconst(Type::int(64), times),
(None, handed) => handed.expect("a loop with no number for a limit was handed one"),
};
let again = build.icmp(IntPred::Slt, next, limit);
build.br_if(again, head, &[next], done, &[]);
Builder::new(&mut func, done).ret(&[]);
(names, func, vec![entry, head, more, done])
}
fn joining() -> (Interner, Func, Vec<Block>) {
let mut names = Interner::new();
let params = [Type::PTR, Type::int(64)];
let mut func = Func::new(names.intern("f"), Signature::new().with_params(¶ms));
let entry = func.create_block();
let head = func.create_block();
let more = func.create_block();
let left = func.create_block();
let right = func.create_block();
let join = func.create_block();
let array = func.append_param(entry, Type::PTR);
let handed = func.append_param(entry, Type::int(64));
let counter = func.append_param(head, Type::int(64));
let zero = Builder::new(&mut func, entry).iconst(Type::int(64), 0);
Builder::new(&mut func, entry).jump(head, &[zero]);
let mut build = Builder::new(&mut func, head);
let by = build.iconst(Type::int(64), WIDTH);
let scaled = build.binary(Opcode::Mul, counter, by, Flags::NSW);
let args = build.func().push_values(&[array, scaled]);
let pointer = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
check(&mut build, pointer);
let read = build.load(Type::int(32), pointer, mem(), Flags::NONE);
let nothing = build.iconst(Type::int(32), 0);
let stop = build.icmp(IntPred::Eq, read, nothing);
build.br_if(stop, left, &[], more, &[]);
let mut build = Builder::new(&mut func, more);
let one = build.iconst(Type::int(64), 1);
let next = build.binary(Opcode::Add, counter, one, Flags::NSW);
let again = build.icmp(IntPred::Slt, next, handed);
build.br_if(again, head, &[next], right, &[]);
Builder::new(&mut func, left).jump(join, &[]);
Builder::new(&mut func, right).jump(join, &[]);
Builder::new(&mut func, join).ret(&[]);
(names, func, vec![entry, head, more, left, right, join])
}
fn one_after_another() -> (Interner, Func, Vec<Block>) {
let mut names = Interner::new();
let params = [Type::PTR, Type::int(64)];
let mut func = Func::new(names.intern("f"), Signature::new().with_params(¶ms));
let entry = func.create_block();
let head = func.create_block();
let more = func.create_block();
let over = func.create_block();
let next = func.create_block();
let again = func.create_block();
let done = func.create_block();
let array = func.append_param(entry, Type::PTR);
let limit = func.append_param(entry, Type::int(64));
let first = func.append_param(head, Type::int(64));
let second = func.append_param(next, Type::int(64));
let zero = Builder::new(&mut func, entry).iconst(Type::int(64), 0);
Builder::new(&mut func, entry).jump(head, &[zero]);
let mut build = Builder::new(&mut func, head);
let args = build.func().push_values(&[array, first]);
let at = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
checking(&mut build, at, byte());
let read = build.load(Type::int(8), at, byte(), Flags::NONE);
let nothing = build.iconst(Type::int(8), 0);
let stop = build.icmp(IntPred::Eq, read, nothing);
build.br_if(stop, over, &[], more, &[]);
let mut build = Builder::new(&mut func, more);
let one = build.iconst(Type::int(64), 1);
let step = build.binary(Opcode::Add, first, one, Flags::NSW);
build.jump(head, &[step]);
Builder::new(&mut func, over).jump(next, &[first]);
let mut build = Builder::new(&mut func, next);
let args = build.func().push_values(&[array, second]);
let here = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
checking(&mut build, here, byte());
let seen = build.load(Type::int(8), here, byte(), Flags::NONE);
let blank = build.iconst(Type::int(8), 32);
let over_too = build.icmp(IntPred::Eq, seen, blank);
build.br_if(over_too, done, &[], again, &[]);
let mut build = Builder::new(&mut func, again);
let one = build.iconst(Type::int(64), 1);
let onward = build.binary(Opcode::Add, second, one, Flags::NSW);
let go = build.icmp(IntPred::Slt, onward, limit);
build.br_if(go, next, &[onward], done, &[]);
Builder::new(&mut func, done).ret(&[]);
(names, func, vec![entry, head, more, over, next, again, done])
}
fn nested(inner_reads: bool) -> (Interner, Func, Vec<Block>) {
let mut names = Interner::new();
let params = [Type::PTR, Type::int(64), Type::int(64)];
let mut func = Func::new(names.intern("f"), Signature::new().with_params(¶ms));
let entry = func.create_block();
let outer = func.create_block();
let inner = func.create_block();
let round = func.create_block();
let after = func.create_block();
let done = func.create_block();
let array = func.append_param(entry, Type::PTR);
let rows = func.append_param(entry, Type::int(64));
let columns = func.append_param(entry, Type::int(64));
let row = func.append_param(outer, Type::int(64));
let column = func.append_param(inner, Type::int(64));
let zero = Builder::new(&mut func, entry).iconst(Type::int(64), 0);
Builder::new(&mut func, entry).jump(outer, &[zero]);
let mut build = Builder::new(&mut func, outer);
let by = build.iconst(Type::int(64), WIDTH);
let scaled = build.binary(Opcode::Mul, row, by, Flags::NSW);
let args = build.func().push_values(&[array, scaled]);
let at = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
check(&mut build, at);
build.load(Type::int(32), at, mem(), Flags::NONE);
let start = build.iconst(Type::int(64), 0);
build.jump(inner, &[start]);
let mut build = Builder::new(&mut func, inner);
let wide = build.iconst(Type::int(64), WIDTH);
let along = build.binary(Opcode::Mul, column, wide, Flags::NSW);
let args = build.func().push_values(&[array, along]);
let here = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
if inner_reads {
check(&mut build, here);
build.load(Type::int(32), here, mem(), Flags::NONE);
}
build.jump(round, &[]);
let mut build = Builder::new(&mut func, round);
let one = build.iconst(Type::int(64), 1);
let onward = build.binary(Opcode::Add, column, one, Flags::NSW);
let more = build.icmp(IntPred::Slt, onward, columns);
build.br_if(more, inner, &[onward], after, &[]);
let mut build = Builder::new(&mut func, after);
let one = build.iconst(Type::int(64), 1);
let next = build.binary(Opcode::Add, row, one, Flags::NSW);
let again = build.icmp(IntPred::Slt, next, rows);
build.br_if(again, outer, &[next], done, &[]);
Builder::new(&mut func, done).ret(&[]);
(names, func, vec![entry, outer, inner, round, after, done])
}
fn reading_what_the_inner_loop_found() -> (Interner, Func, Vec<Block>) {
let mut names = Interner::new();
let params = [Type::PTR, Type::int(64), Type::int(64)];
let mut func = Func::new(names.intern("f"), Signature::new().with_params(¶ms));
let entry = func.create_block();
let outer = func.create_block();
let inner = func.create_block();
let after = func.create_block();
let done = func.create_block();
let array = func.append_param(entry, Type::PTR);
let rows = func.append_param(entry, Type::int(64));
let columns = func.append_param(entry, Type::int(64));
let row = func.append_param(outer, Type::int(64));
let column = func.append_param(inner, Type::int(64));
let zero = Builder::new(&mut func, entry).iconst(Type::int(64), 0);
Builder::new(&mut func, entry).jump(outer, &[zero]);
let start = Builder::new(&mut func, outer).iconst(Type::int(64), 0);
Builder::new(&mut func, outer).jump(inner, &[start]);
let mut build = Builder::new(&mut func, inner);
let one = build.iconst(Type::int(64), 1);
let onward = build.binary(Opcode::Add, column, one, Flags::NSW);
let more = build.icmp(IntPred::Slt, onward, columns);
build.br_if(more, inner, &[onward], after, &[]);
let mut build = Builder::new(&mut func, after);
let args = build.func().push_values(&[array, onward]);
let at = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
checking(&mut build, at, byte());
build.load(Type::int(8), at, byte(), Flags::NONE);
let one = build.iconst(Type::int(64), 1);
let next = build.binary(Opcode::Add, row, one, Flags::NSW);
let again = build.icmp(IntPred::Slt, next, rows);
build.br_if(again, outer, &[next], done, &[]);
Builder::new(&mut func, done).ret(&[]);
(names, func, vec![entry, outer, inner, after, done])
}
fn mem() -> MemInfo {
MemInfo {
size: WIDTH as u64,
align: WIDTH as u32,
order: MemOrder::NotAtomic,
tbaa: None,
restrict: Restrict::NONE,
}
}
fn byte() -> MemInfo {
MemInfo {
size: 1,
align: 1,
order: MemOrder::NotAtomic,
tbaa: None,
restrict: Restrict::NONE,
}
}
fn check(build: &mut Builder<'_>, pointer: Value) {
checking(build, pointer, mem());
}
fn checking(build: &mut Builder<'_>, pointer: Value, info: MemInfo) {
let args = build.func().push_values(&[pointer]);
let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
let args = build.func().push_values(&[capability, pointer]);
let extra = Extra::Mem(build.func().add_mem(info));
build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
}
fn split_up(func: &mut Func) -> Stats {
let mut an = crate::machine::fixtures::analyses();
Canon.run(func, &mut an, &mut Fuel::unlimited());
Split.run(func, &mut an, &mut Fuel::unlimited())
}
#[test]
fn a_loop_whose_result_is_read_after_it_is_put_back_into_closed_form_first() {
let (mut names, mut func, blocks) = leaving();
let mut an = crate::machine::fixtures::analyses();
Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
let (head, done) = (blocks[1], blocks[3]);
let read = func
.insts(head)
.find(|&inst| func[inst].opcode == Opcode::Load)
.and_then(|inst| func[inst].results().next())
.expect("the loop loads what it walks over");
let term = func.terminator(done).expect("the block after the loop returns");
let sum = Builder::new(&mut func, done).binary(Opcode::Add, read, read, Flags::NONE);
let inst = super::inst_of(&func, sum);
func.remove_inst(inst);
func.insert_before(inst, term);
an.clear();
let stats = Split.run(&mut func, &mut an, &mut Fuel::unlimited());
assert_eq!(stats.count(Kind::Optimized, super::CLOSED_HERE), 1);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(func[done].params.len(), 1, "the block after the loop took the value in");
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
sound(&func, &mut names);
}
#[test]
fn a_value_read_past_a_join_that_neither_way_out_dominates_is_handed_over_there_as_well() {
let (mut names, mut func, blocks) = joining();
let mut an = crate::machine::fixtures::analyses();
Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
let (head, join) = (blocks[1], blocks[5]);
let read = func
.insts(head)
.find(|&inst| func[inst].opcode == Opcode::Load)
.and_then(|inst| func[inst].results().next())
.expect("the loop loads what it walks over");
let term = func.terminator(join).expect("the block the two ways out meet at returns");
let sum = Builder::new(&mut func, join).binary(Opcode::Add, read, read, Flags::NONE);
let inst = super::inst_of(&func, sum);
func.remove_inst(inst);
func.insert_before(inst, term);
an.clear();
let stats = Split.run(&mut func, &mut an, &mut Fuel::unlimited());
assert_eq!(stats.count(Kind::Optimized, super::CLOSED_HERE), 1);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(stats.count(Kind::Missed, super::ESCAPES), 0);
assert_eq!(func[join].params.len(), 1, "the meeting took the value in as well");
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
sound(&func, &mut names);
}
#[test]
fn a_loop_whose_value_the_next_loops_guard_names_is_left_alone() {
let (mut names, mut func, _) = one_after_another();
let mut an = crate::machine::fixtures::analyses();
Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
let stats = Split.run(&mut func, &mut an, &mut Fuel::unlimited());
sound(&func, &mut names);
assert_eq!(stats.count(Kind::Missed, super::WANTED_ELSEWHERE), 1);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
}
#[test]
fn a_loop_with_a_loop_inside_it_is_split() {
let (mut names, mut func, _) = nested(false);
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(stats.count(Kind::Missed, super::NESTED_WITH_ONE), 0);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
sound(&func, &mut names);
}
#[test]
fn the_inner_loop_is_the_one_split_when_both_of_them_could_be() {
let (mut names, mut func, _) = nested(true);
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(stats.count(Kind::Missed, super::NESTED_WITH_ONE), 1);
assert_eq!(stats.count(Kind::Missed, super::INSIDE_A_LOOP), 1);
sound(&func, &mut names);
}
#[test]
fn a_value_the_inner_loop_defined_is_not_one_the_guard_may_write_again() {
let (mut names, mut func, _) = reading_what_the_inner_loop_found();
let mut an = crate::machine::fixtures::analyses();
let stats = Split.run(&mut func, &mut an, &mut Fuel::unlimited());
sound(&func, &mut names);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 0);
}
fn all(func: &Func, opcode: Opcode) -> Vec<(Block, Inst)> {
func.blocks()
.flat_map(|block| func.insts(block).map(move |inst| (block, inst)).collect::<Vec<_>>())
.filter(|&(_, inst)| func[inst].opcode == opcode)
.collect()
}
fn sound(func: &Func, names: &mut Interner) {
let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
let module = Module::new(names.intern("t.c"), &target);
if let Err(errors) = verify_func(&module, func, names) {
panic!("{errors:#?}");
}
}
#[test]
fn a_loop_that_can_stop_early_is_split_even_though_hoisting_will_not_touch_it() {
let (mut names, mut func, _) = leaving();
let mut an = crate::machine::fixtures::analyses();
Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
let refused = crate::hoist::Hoist.run(&mut func, &mut an, &mut Fuel::unlimited());
assert!(!refused.changed(), "hoisting has nothing to say about this loop");
let stats = Split.run(&mut func, &mut an, &mut Fuel::unlimited());
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
sound(&func, &mut names);
}
#[test]
fn the_half_the_loop_runs_first_has_no_check_in_it_and_the_other_one_keeps_it() {
let (mut names, mut func, blocks) = leaving();
let head = blocks[1];
split_up(&mut func);
let left = all(&func, Opcode::CheckBounds);
assert_eq!(left.len(), 1, "one check, and it is the one the slow half kept");
assert_ne!(left[0].0, head, "and it is not in the block the loop started in");
sound(&func, &mut names);
}
#[test]
fn how_far_the_runtime_is_asked_to_look_is_settled_in_front_of_the_loop() {
let (mut names, mut func, _) = leaving();
split_up(&mut func);
let asked = all(&func, Opcode::CapExtent);
assert_eq!(asked.len(), 1, "one question for the one check that was sized");
let cfg = crate::Cfg::new(&func);
let doms = crate::Dominators::new(&cfg);
let loops = crate::Loops::new(&cfg, &doms);
assert!(
loops.all().all(|id| !loops.contains(id, asked[0].0)),
"and it is outside the loop"
);
sound(&func, &mut names);
}
#[test]
fn a_walk_that_starts_at_an_index_the_caller_handed_in_is_split() {
let (mut names, mut func, blocks) = from_an_index();
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
let asked = all(&func, Opcode::CapExtent);
assert_eq!(asked.len(), 1, "one question for the one check that was sized");
let at = func[func[asked[0].1].args][1];
let inst = super::inst_of(&func, at);
assert_eq!(func[inst].opcode, Opcode::PtrAdd, "the question is asked about a displacement");
assert_eq!(func[func[inst].args][0], func[blocks[0]].params[0], "off the array");
sound(&func, &mut names);
}
#[test]
fn a_walk_over_a_file_scope_array_is_split_and_the_address_is_written_out_again() {
let (mut names, mut func, _) = over_a_global();
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
let asked = all(&func, Opcode::CapExtent);
assert_eq!(asked.len(), 1, "one question for the one check that was sized");
let at = func[func[asked[0].1].args][1];
let inst = super::inst_of(&func, at);
assert_eq!(func[inst].opcode, Opcode::GlobalAddr, "asked about the array itself");
let cfg = crate::Cfg::new(&func);
let doms = crate::Dominators::new(&cfg);
let loops = crate::Loops::new(&cfg, &doms);
let addresses = all(&func, Opcode::GlobalAddr);
assert_eq!(addresses.len(), 3, "one in each half of the loop and one in front of them");
assert_eq!(
addresses
.iter()
.filter(|&&(block, _)| loops.all().all(|id| !loops.contains(id, block)))
.count(),
1,
"and the one in front is outside every loop, which is where the question is asked",
);
sound(&func, &mut names);
}
#[test]
fn a_walk_whose_step_is_not_a_number_is_split_and_the_guard_measures_how_far_it_got() {
let (mut names, mut func, blocks) = by_what_it_read();
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
let asked = all(&func, Opcode::CapExtent);
assert_eq!(asked.len(), 1, "one question for the one check that was sized");
assert_eq!(
func[func[asked[0].1].args][1], func[blocks[0]].params[0],
"asked about the pointer the loop was handed, which is where the walk begins",
);
let measured = all(&func, Opcode::PtrToInt);
assert_eq!(measured.len(), 2, "where the pointer began and where it is now");
sound(&func, &mut names);
}
#[test]
fn a_guard_that_measures_carries_nothing_round_the_loop() {
let (mut names, mut func, _) = by_what_it_read();
split_up(&mut func);
let cfg = crate::Cfg::new(&func);
let doms = crate::Dominators::new(&cfg);
let loops = crate::Loops::new(&cfg, &doms);
let guard = loops
.all()
.map(|id| loops.header(id))
.find(|&block| func.insts(block).any(|inst| func[inst].opcode == Opcode::PtrToInt))
.expect("the guard is the header of the loop it took over");
assert_eq!(func[guard].params.len(), 1, "the pointer the header carried, and nothing else");
sound(&func, &mut names);
}
#[test]
fn an_address_built_out_of_what_the_header_carries_is_written_again_in_the_guard() {
let (mut names, mut func, blocks) = from_what_it_carries(false);
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
let masks = all(&func, Opcode::And);
assert_eq!(masks.len(), 4, "one per half, one in the guard and one in the preheader");
let inside: Vec<Block> = masks.iter().map(|&(block, _)| block).collect();
assert!(inside.contains(&blocks[0]), "the preheader works the first address out");
let asked = all(&func, Opcode::CapExtent);
assert_eq!(asked.len(), 1, "one question, in front of the loop");
assert_eq!(asked[0].0, blocks[0], "asked in the preheader about the first address");
let measured = all(&func, Opcode::PtrToInt);
assert_eq!(measured.len(), 2, "where the address began and where it is now");
sound(&func, &mut names);
}
#[test]
fn an_address_built_on_something_read_out_of_memory_is_left_alone() {
let (mut names, mut func, _) = from_what_it_carries(true);
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 0, "the loop is left alone");
assert_eq!(stats.count(Kind::Missed, super::NOT_FOLLOWED), 1);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "and the check stays where it was");
assert!(all(&func, Opcode::CapExtent).is_empty(), "with nothing asked in front of it");
sound(&func, &mut names);
}
#[test]
fn a_walk_down_a_linked_list_is_left_alone() {
let (mut names, mut func, _) = down_a_list();
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 0, "the loop is left alone");
assert_eq!(stats.count(Kind::Missed, super::NOT_FOLLOWED), 1);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "and the check stays where it was");
assert!(all(&func, Opcode::CapExtent).is_empty(), "with nothing asked in front of it");
sound(&func, &mut names);
}
#[test]
fn a_walk_the_guard_would_measure_is_left_alone_when_its_access_wants_alignment() {
let (mut names, mut func, _) = by_what_it_read();
for (_, inst) in all(&func, Opcode::CheckBounds) {
let extra = Extra::Mem(func.add_mem(mem()));
func[inst].extra = extra;
}
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 0, "the loop is left alone");
assert_eq!(stats.count(Kind::Missed, super::MEASURED_ALIGN), 1);
sound(&func, &mut names);
}
#[test]
fn a_walk_from_an_index_in_int_is_split_and_the_extension_is_emitted_in_front() {
let (mut names, mut func, blocks) = from_a_narrow_index();
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
let asked = all(&func, Opcode::CapExtent);
assert_eq!(asked.len(), 1, "one question for the one check that was sized");
let at = func[func[asked[0].1].args][1];
let sum = super::inst_of(&func, at);
assert_eq!(func[sum].opcode, Opcode::PtrAdd, "the question is asked about a displacement");
assert_eq!(func[func[sum].args][0], func[blocks[0]].params[0], "off the array");
let widened = all(&func, Opcode::SExt);
assert_eq!(widened.len(), 3, "one extension in each half of the loop and one in front");
let start = func[blocks[0]].params[1];
assert_eq!(
widened.iter().filter(|&&(_, inst)| func[func[inst].args][0] == start).count(),
1,
"and the one in front is of the index the caller handed in, which the halves never take",
);
sound(&func, &mut names);
}
#[test]
fn a_walk_from_high_to_low_is_split_and_the_question_goes_the_other_way() {
let (mut names, mut func, blocks) = downwards();
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
assert!(all(&func, Opcode::CapExtent).is_empty(), "nothing asked about the bytes above");
let asked = all(&func, Opcode::CapExtentBack);
assert_eq!(asked.len(), 1, "one question for the one check that was sized");
let at = func[func[asked[0].1].args][1];
let end = super::inst_of(&func, at);
assert_eq!(func[end].opcode, Opcode::PtrAdd, "asked at the end of the first access");
let from = func[func[end].args][0];
let first = super::inst_of(&func, from);
assert_eq!(
func[first].opcode,
Opcode::PtrAdd,
"past a first access that is a displacement"
);
assert_eq!(func[func[first].args][0], func[blocks[0]].params[0], "off the array");
sound(&func, &mut names);
}
#[test]
fn a_loop_with_a_call_in_it_that_might_free_is_left_alone() {
let (_, mut func, _) = calling(Flags::NONE);
let stats = split_up(&mut func);
assert!(!stats.changed());
assert_eq!(stats.count(Kind::Missed, super::A_CALL_INSIDE), 1);
}
#[test]
fn a_loop_with_a_call_in_it_that_cannot_free_is_split() {
let (mut names, mut func, _) = calling(Flags::NOFREE);
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
assert_eq!(all(&func, Opcode::Call).len(), 2, "and both halves kept the call");
sound(&func, &mut names);
}
fn calling(flags: Flags) -> (Interner, Func, Vec<Block>) {
let (mut names, mut func, blocks) = leaving();
let more = blocks[2];
let term = func.terminator(more).expect("the latch branches");
let callee = names.intern("somewhere");
let signature = func.add_signature(Signature::new());
let call = Builder::new(&mut func, more).call(callee, signature, &[]);
func[call].flags |= flags;
func.remove_inst(call);
func.insert_before(call, term);
(names, func, blocks)
}
#[test]
fn a_check_whose_address_does_not_move_is_taken_too() {
let (mut names, mut func, _) = standing(false);
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(all(&func, Opcode::CapExtent).len(), 2, "both addresses were sized in front");
assert_eq!(
all(&func, Opcode::CheckBounds).len(),
2,
"the fast half lost both checks and the slow half kept both"
);
sound(&func, &mut names);
}
#[test]
fn the_window_is_worked_out_without_dividing_by_anything() {
let (mut names, mut func, _) = standing(false);
split_up(&mut func);
for opcode in [Opcode::SDiv, Opcode::UDiv] {
assert!(all(&func, opcode).is_empty(), "{opcode:?} is left in the window arithmetic");
}
sound(&func, &mut names);
}
#[test]
fn two_checks_that_walk_by_the_same_amount_share_one_offset() {
let (mut names, mut func, blocks) = twinned();
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(all(&func, Opcode::CapExtent).len(), 2, "both addresses were sized in front");
let head = blocks[1];
let cfg = crate::Cfg::new(&func);
let into = cfg.predecessors(head);
assert_eq!(into.len(), 1, "the guard is the only way into the header now");
let guard = into[0];
assert_eq!(
func[guard].params.len(),
func[head].params.len() + 1,
"one offset, not one per check"
);
sound(&func, &mut names);
}
fn twinned() -> (Interner, Func, Vec<Block>) {
let (names, mut func, blocks) = leaving();
let (entry, head) = (blocks[0], blocks[1]);
let array = func[entry].params[0];
let counter = func[head].params[0];
let term = func.terminator(head).expect("the header branches");
let mut build = Builder::new(&mut func, head);
let by = build.iconst(Type::int(64), WIDTH);
let scaled = build.binary(Opcode::Mul, counter, by, Flags::NSW);
let ahead = build.binary(Opcode::Add, scaled, by, Flags::NSW);
let args = build.func().push_values(&[array, ahead]);
let pointer = build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
check(&mut build, pointer);
let made: Vec<Inst> = func.insts(head).skip_while(|&inst| inst != term).skip(1).collect();
for inst in made {
func.remove_inst(inst);
func.insert_before(inst, term);
}
(names, func, blocks)
}
#[test]
fn a_loop_where_nothing_moves_picks_its_half_once_and_counts_nothing() {
let (mut names, mut func, blocks) = standing(true);
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(all(&func, Opcode::CapExtent).len(), 1);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
let (entry, head) = (blocks[0], blocks[1]);
let term = func.terminator(entry).expect("the preheader still ends in something");
assert_eq!(func[term].opcode, Opcode::BrIf, "the way in is the choice");
assert_eq!(func[head].params.len(), 1, "and the header took on no counter");
sound(&func, &mut names);
}
fn standing(alone: bool) -> (Interner, Func, Vec<Block>) {
let (names, mut func, blocks) = leaving();
let (entry, head) = (blocks[0], blocks[1]);
let array = func[entry].params[0];
let walking = all(&func, Opcode::CheckBounds);
let term = func.terminator(head).expect("the header branches");
let mut build = Builder::new(&mut func, head);
check(&mut build, array);
let made: Vec<Inst> = func.insts(head).skip_while(|&inst| inst != term).skip(1).collect();
for inst in made {
func.remove_inst(inst);
func.insert_before(inst, term);
}
if alone {
for (_, inst) in walking {
func.remove_inst(inst);
}
}
(names, func, blocks)
}
#[test]
fn a_loop_whose_count_is_an_expression_is_split_on_what_that_expression_says() {
let (mut names, mut func, _) = counting();
let stats = split_up(&mut func);
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(all(&func, Opcode::CapExtent).len(), 1);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
sound(&func, &mut names);
}
#[test]
fn a_loop_nobody_counted_is_split_on_a_guess() {
let (mut names, mut func, _) = uncounted();
let mut an = crate::machine::fixtures::analyses();
Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
let refused = crate::hoist::Hoist.run(&mut func, &mut an, &mut Fuel::unlimited());
assert!(!refused.changed(), "hoisting will not size a check from a count nobody settled");
let stats = Split.run(&mut func, &mut an, &mut Fuel::unlimited());
assert_eq!(stats.count(Kind::Optimized, SPLIT), 1);
assert_eq!(all(&func, Opcode::CheckBounds).len(), 1, "the fast half lost its check");
sound(&func, &mut names);
}
#[test]
fn the_pass_stops_when_the_fuel_runs_out() {
let (_, mut func, _) = leaving();
let mut an = crate::machine::fixtures::analyses();
Canon.run(&mut func, &mut an, &mut Fuel::unlimited());
let stats = Split.run(&mut func, &mut an, &mut Fuel::of(0));
assert!(!stats.changed());
assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
}
}