use rucc_ir::{Def, Extra, Flags, Func, Inst, Opcode, Value};
use crate::range::query::Ranges;
use crate::rules::{Piece, Subject, Table, safety};
use crate::{Analyses, Fuel, Pass, Preserved, Stats};
const REMOVED: &str = "bounds check removed, a dominating check covers the same bytes";
const REMOVED_LOCAL: &str = "bounds check removed, its bytes are inside a local this function \
declares";
const REMOVED_STATIC: &str = "bounds check removed, its bytes are inside an object of static \
storage duration";
const REMOVED_RANGE: &str = "bounds check removed, every address the walk can reach is inside the \
object it started from";
const REMOVED_LIVE: &str = "lifetime check removed, a dominating check covers the same storage";
const REMOVED_LIVE_STATIC: &str =
"lifetime check removed, its storage lives as long as the program does";
const REMOVED_LIVE_LOCAL: &str =
"lifetime check removed, its storage is a frame slot of this function";
const REMOVED_LIVE_RANGE: &str = "lifetime check removed, every address the walk can reach is in \
storage a check found alive";
const NO_FUEL: &str = "bounds check kept, the pass ran out of fuel";
const NO_FUEL_LIVE: &str = "lifetime check kept, the pass ran out of fuel";
const REMOVED_DERIV_RANGE: &str = "derivation check removed, every address either end can reach is \
inside one checked range";
const PAST_A_CALL: &str =
"bounds check kept, a call between it and the check that covers it might free";
const PAST_A_CALL_LIVE: &str =
"lifetime check kept, a call between it and the check that covers it might free";
const UNKNOWN_SHAPE: &str = "bounds check left alone, its pointer is not a base and a constant";
const COMPUTED_EXTENT: &str =
"bounds check left alone, how many bytes it covers is a number only the program has";
const UNKNOWN_SHAPE_LIVE: &str =
"lifetime check left alone, its pointer is not a base and a constant";
const REMOVED_DERIV: &str =
"derivation check removed, one checked range holds both the pointer and where it walked to";
const REMOVED_DERIV_LOCAL: &str =
"derivation check removed, it walks inside a local this function declares";
const REMOVED_DERIV_STATIC: &str =
"derivation check removed, it walks inside an object of static storage duration";
const NO_FUEL_DERIV: &str = "derivation check kept, the pass ran out of fuel";
const PAST_A_CALL_DERIV: &str =
"derivation check kept, a call between it and the range that holds both ends might free";
const UNKNOWN_SHAPE_DERIV: &str =
"derivation check left alone, its two pointers are not one base and two constants";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Discharge;
impl Pass for Discharge {
fn name(&self) -> &'static str {
"discharge"
}
fn describe(&self) -> &'static str {
"a bounds, lifetime or derivation check whose answer is already known is removed"
}
fn preserves(&self) -> Preserved {
Preserved::ALL
}
fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
let mut stats = Stats::new();
let Some(entry) = func.entry() else { return stats };
let dom = an.dominators(func).clone();
let cfg = walks_by_a_value(func).then(|| an.cfg(func).clone());
let mut ranges = cfg.as_ref().map(|cfg| Ranges::new(&*func, cfg, &dom));
let ends = ends_a_lifetime(func);
let mut going: Vec<(Inst, &'static str)> = Vec::new();
let mut work = vec![(entry, Scope::default())];
while let Some((block, mut scope)) = work.pop() {
for inst in func.insts(block).collect::<Vec<Inst>>() {
if opaque(func, inst) {
scope.forget();
continue;
}
match func[inst].opcode {
Opcode::CheckBounds => {
if func[func[inst].args].len() > 2 {
stats.missed(COMPUTED_EXTENT);
continue;
}
let Some(asked) = about(func, inst) else {
stats.missed(UNKNOWN_SHAPE);
continue;
};
let why = if func[inst].flags.contains(Flags::STATIC) {
Some(REMOVED_STATIC)
} else if declared(func, asked.base)
.is_some_and(|local| covers(&local, &asked))
{
Some(REMOVED_LOCAL)
} else if scope.bounds.covers(&asked) {
Some(REMOVED)
} else {
reach(func, ranges.as_mut(), &asked, inst)
.filter(|wide| {
declared(func, wide.base)
.is_some_and(|local| reaches(&local, wide))
|| scope.bounds.reaches(wide)
})
.map(|_| REMOVED_RANGE)
};
let Some(why) = why else {
if scope.bounds.covered_before(&asked) {
stats.missed(PAST_A_CALL);
}
scope.bounds.held.push(asked);
continue;
};
if !fuel.take() {
stats.missed(NO_FUEL);
scope.bounds.held.push(asked);
continue;
}
if why == REMOVED_RANGE {
scope.bounds.held.push(asked);
}
going.push((inst, why));
}
Opcode::CheckLive => {
let Some(asked) = alive(func, inst) else {
stats.missed(UNKNOWN_SHAPE_LIVE);
continue;
};
let why = if func[inst].flags.contains(Flags::STATIC) {
Some(REMOVED_LIVE_STATIC)
} else if !ends
&& declared(func, asked.base)
.is_some_and(|local| covers(&local, &asked))
{
Some(REMOVED_LIVE_LOCAL)
} else if scope.alive.covers(&asked) {
Some(REMOVED_LIVE)
} else {
reach(func, ranges.as_mut(), &asked, inst)
.filter(|wide| {
(!ends
&& declared(func, wide.base)
.is_some_and(|local| reaches(&local, wide)))
|| scope.alive.reaches(wide)
})
.map(|_| REMOVED_LIVE_RANGE)
};
let Some(why) = why else {
if scope.alive.covered_before(&asked) {
stats.missed(PAST_A_CALL_LIVE);
}
scope.alive.held.push(widened(func, &scope.bounds, asked));
continue;
};
if !fuel.take() {
stats.missed(NO_FUEL_LIVE);
scope.alive.held.push(widened(func, &scope.bounds, asked));
continue;
}
if why == REMOVED_LIVE_RANGE {
scope.alive.held.push(widened(func, &scope.bounds, asked));
}
going.push((inst, why));
}
Opcode::CheckDeriv => {
let narrow = derives(func, inst);
let why = narrow.and_then(|(from, to)| {
if func[inst].flags.contains(Flags::STATIC) {
Some(REMOVED_DERIV_STATIC)
} else if declared(func, from.base)
.is_some_and(|local| covers(&local, &from) && covers(&local, &to))
{
Some(REMOVED_DERIV_LOCAL)
} else if scope.bounds.holds_both(&from, &to) {
Some(REMOVED_DERIV)
} else {
None
}
});
let why = why.or_else(|| {
spread(func, ranges.as_mut(), inst, inst)
.filter(|(near, far)| {
declared(func, near.base).is_some_and(|local| {
reaches(&local, near) && reaches(&local, far)
}) || scope.bounds.reaches_both(near, far)
})
.map(|_| REMOVED_DERIV_RANGE)
});
let Some(why) = why else {
match narrow {
Some((from, to)) => {
if scope.bounds.held_both_before(&from, &to) {
stats.missed(PAST_A_CALL_DERIV);
}
}
None => stats.missed(UNKNOWN_SHAPE_DERIV),
}
continue;
};
if !fuel.take() {
stats.missed(NO_FUEL_DERIV);
continue;
}
going.push((inst, why));
}
_ => continue,
}
}
for child in dom.children(block) {
work.push((child, scope.clone()));
}
}
for (inst, why) in going {
func.remove_inst(inst);
stats.optimized(why);
}
stats
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Fact {
pub(crate) base: Value,
offset: i128,
size: i128,
}
impl Fact {
pub(crate) fn whole(base: Value, size: i128) -> Self {
Self { base, offset: 0, size }
}
}
#[derive(Debug, Clone, Copy)]
struct Reach {
base: Value,
low: i128,
width: i128,
size: i128,
}
#[derive(Debug, Clone, Default)]
struct Known {
held: Vec<Fact>,
lost: Vec<Fact>,
}
impl Known {
fn covers(&self, asked: &Fact) -> bool {
self.held.iter().any(|fact| covers(fact, asked))
}
fn reaches(&self, asked: &Reach) -> bool {
self.held.iter().any(|fact| reaches(fact, asked))
}
fn reaches_both(&self, from: &Reach, to: &Reach) -> bool {
self.held.iter().any(|fact| reaches(fact, from) && reaches(fact, to))
}
fn covered_before(&self, asked: &Fact) -> bool {
self.lost.iter().any(|fact| covers(fact, asked))
}
fn holds_both(&self, from: &Fact, to: &Fact) -> bool {
self.held.iter().any(|fact| covers(fact, from) && covers(fact, to))
}
fn held_both_before(&self, from: &Fact, to: &Fact) -> bool {
self.lost.iter().any(|fact| covers(fact, from) && covers(fact, to))
}
fn forget(&mut self) {
self.lost.append(&mut self.held);
}
}
#[derive(Debug, Clone, Default)]
struct Scope {
bounds: Known,
alive: Known,
}
impl Scope {
fn forget(&mut self) {
self.bounds.forget();
self.alive.forget();
}
}
fn opaque(func: &Func, inst: Inst) -> bool {
match func[inst].opcode {
Opcode::Call | Opcode::CallIndirect | Opcode::TailCall => {
!func[inst].flags.contains(Flags::NOFREE)
}
Opcode::InlineAsm | Opcode::MetaEnd | Opcode::MetaTransfer => true,
_ => false,
}
}
pub(crate) fn about(func: &Func, check: Inst) -> Option<Fact> {
let (base, offset) = addressed(func, check)?;
let Extra::Mem(info) = func[check].extra else { return None };
Some(Fact { base, offset, size: i128::from(func[info].size) })
}
pub(crate) fn alive(func: &Func, check: Inst) -> Option<Fact> {
let (base, offset) = addressed(func, check)?;
Some(Fact { base, offset, size: 1 })
}
fn addressed(func: &Func, check: Inst) -> Option<(Value, i128)> {
let args = &func[func[check].args];
let &capability = args.first()?;
let &pointer = args.get(1)?;
if operand_of(func, capability, Opcode::CapOf, 0) != Some(pointer) {
return None;
}
Some(normal(func, pointer))
}
pub(crate) fn derives(func: &Func, check: Inst) -> Option<(Fact, Fact)> {
let args = &func[func[check].args];
let &capability = args.first()?;
let &from = args.get(1)?;
let &to = args.get(2)?;
if operand_of(func, capability, Opcode::CapOf, 0) != Some(from) {
return None;
}
let (base, start) = normal(func, from);
let (walked, end) = normal(func, to);
if base != walked {
return None;
}
Some((Fact { base, offset: start, size: 1 }, Fact { base, offset: end, size: 1 }))
}
fn declared(func: &Func, base: Value) -> Option<Fact> {
let Def::Result { inst, .. } = func[base].def else { return None };
if func[inst].opcode != Opcode::Alloca || !func[func[inst].args].is_empty() {
return None;
}
let Extra::Mem(info) = func[inst].extra else { return None };
Some(Fact::whole(base, i128::from(func[info].size)))
}
fn widened(func: &Func, bounds: &Known, asked: Fact) -> Fact {
if let Some(local) = declared(func, asked.base).filter(|local| covers(local, &asked)) {
return local;
}
bounds.held.iter().find(|fact| covers(fact, &asked)).copied().unwrap_or(asked)
}
fn normal(func: &Func, value: Value) -> (Value, i128) {
let mut base = value;
let mut offset: i128 = 0;
while let Some((from, step)) = walked(func, base) {
let Some(sum) = offset.checked_add(step) else { break };
base = from;
offset = sum;
}
(base, offset)
}
fn reach(func: &Func, ranges: Option<&mut Ranges<'_>>, asked: &Fact, at: Inst) -> Option<Reach> {
let wide = spanned(func, ranges?, asked.base, asked.offset, asked.size, at)?;
(wide.base != asked.base).then_some(wide)
}
fn spread(
func: &Func,
ranges: Option<&mut Ranges<'_>>,
check: Inst,
at: Inst,
) -> Option<(Reach, Reach)> {
let ranges = ranges?;
let args = &func[func[check].args];
let &capability = args.first()?;
let &from = args.get(1)?;
let &to = args.get(2)?;
if operand_of(func, capability, Opcode::CapOf, 0) != Some(from) {
return None;
}
let (base, offset) = normal(func, from);
let near = spanned(func, ranges, base, offset, 1, at)?;
let (base, offset) = normal(func, to);
let far = spanned(func, ranges, base, offset, 1, at)?;
(near.base == far.base).then_some((near, far))
}
fn spanned(
func: &Func,
ranges: &mut Ranges<'_>,
base: Value,
offset: i128,
size: i128,
at: Inst,
) -> Option<Reach> {
let mut base = base;
let mut low = offset;
let mut width: i128 = 0;
loop {
if let Some((from, step)) = walked(func, base) {
low = low.checked_add(step)?;
base = from;
continue;
}
let Some(from) = operand_of(func, base, Opcode::PtrAdd, 0) else { break };
let by = operand_of(func, base, Opcode::PtrAdd, 1)?;
let (least, most) = ranges.at_inst(by, at).signed_bounds()?;
low = low.checked_add(least)?;
width = width.checked_add(most.checked_sub(least)?)?;
base = from;
}
Some(Reach { base, low, width, size })
}
fn ends_a_lifetime(func: &Func) -> bool {
func.blocks().any(|block| func.insts(block).any(|inst| func[inst].opcode == Opcode::MetaEnd))
}
fn walks_by_a_value(func: &Func) -> bool {
func.blocks().any(|block| {
func.insts(block).any(|inst| {
func[inst].opcode == Opcode::PtrAdd
&& func[func[inst].args].get(1).is_some_and(|&by| constant(func, by).is_none())
})
})
}
fn walked(func: &Func, value: Value) -> Option<(Value, i128)> {
let from = operand_of(func, value, Opcode::PtrAdd, 0)?;
let by = operand_of(func, value, Opcode::PtrAdd, 1)?;
Some((from, constant(func, by)?))
}
pub(crate) fn operand_of(func: &Func, value: Value, opcode: Opcode, index: usize) -> Option<Value> {
let Def::Result { inst, .. } = func[value].def else { return None };
if func[inst].opcode != opcode {
return None;
}
func[func[inst].args].get(index).copied()
}
fn constant(func: &Func, value: Value) -> Option<i128> {
let Def::Result { inst, .. } = func[value].def else { return None };
if func[inst].opcode != Opcode::IConst {
return None;
}
let Extra::Imm(imm) = func[inst].extra else { return None };
let ty = func[value].ty;
ty.is_int().then(|| func[imm].signed(ty))
}
pub(crate) fn covers(fact: &Fact, asked: &Fact) -> bool {
if fact.base != asked.base {
return false;
}
let Some(delta) = asked.offset.checked_sub(fact.offset) else { return false };
let mut question = Question::default();
let at = question.opaque();
let at = question.app("value.i64", &[at]);
let span = question.number(fact.size);
let span = question.app("iconst.i64", &[span]);
let far = question.number(delta);
let far = question.app("iconst.i64", &[far]);
let reach = question.number(asked.size);
let reach = question.app("iconst.i64", &[reach]);
let term = question.app("covered.i64", &[at, span, far, reach]);
match safety::TABLE.find(&question, term) {
Some(found) => yes(&safety::TABLE, found.rule),
None => false,
}
}
fn reaches(fact: &Fact, asked: &Reach) -> bool {
if fact.base != asked.base {
return false;
}
let Some(delta) = asked.low.checked_sub(fact.offset) else { return false };
let mut question = Question::default();
let at = question.opaque();
let at = question.app("value.i64", &[at]);
let span = question.number(fact.size);
let span = question.app("iconst.i64", &[span]);
let delta = question.number(delta);
let delta = question.app("iconst.i64", &[delta]);
let width = question.number(asked.width);
let width = question.app("iconst.i64", &[width]);
let size = question.number(asked.size);
let size = question.app("iconst.i64", &[size]);
let step = question.opaque();
let step = question.app("value.i64", &[step]);
let term = question.app("reached.i64", &[at, span, delta, width, size, step]);
match safety::TABLE.find(&question, term) {
Some(found) => yes(&safety::TABLE, found.rule),
None => false,
}
}
pub(crate) fn yes(table: &Table, rule: usize) -> bool {
matches!(table.rules[rule].replacement, [Piece::App { .. }, Piece::Int(1)])
}
#[derive(Debug, Default)]
pub(crate) struct Question {
held: Vec<Held>,
}
#[derive(Debug)]
enum Held {
Int(i128),
App(&'static str, Vec<usize>),
Opaque,
}
impl Question {
pub(crate) fn number(&mut self, value: i128) -> usize {
self.held.push(Held::Int(value));
self.held.len() - 1
}
pub(crate) fn app(&mut self, head: &'static str, args: &[usize]) -> usize {
self.held.push(Held::App(head, args.to_vec()));
self.held.len() - 1
}
pub(crate) fn opaque(&mut self) -> usize {
self.held.push(Held::Opaque);
self.held.len() - 1
}
}
impl Subject for Question {
type Node = usize;
fn head(&self, node: usize) -> Option<(&str, usize)> {
match &self.held[node] {
Held::App(head, args) => Some((head, args.len())),
Held::Int(_) | Held::Opaque => None,
}
}
fn arg(&self, node: usize, index: usize) -> usize {
match &self.held[node] {
Held::App(_, args) => args[index],
Held::Int(_) | Held::Opaque => unreachable!("only an application has arguments"),
}
}
fn int(&self, node: usize) -> Option<i128> {
match self.held[node] {
Held::Int(value) => Some(value),
Held::App(..) | Held::Opaque => None,
}
}
fn same(&self, a: usize, b: usize) -> bool {
a == b
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{
AsmInfo, Block, BlockCallList, Builder, Extra, Flags, Func, Inst, InstData, MemInfo,
MemOrder, Opcode, Restrict, Signature, Type, Value,
};
use super::{Discharge, Fact};
use crate::stats::Kind;
use crate::{Analyses, Fuel, Pass};
fn blank() -> (Interner, Func, Block, Value) {
let mut names = Interner::new();
let name = names.intern("f");
let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR]));
let block = func.create_block();
let pointer = func.append_param(block, Type::PTR);
(names, func, block, pointer)
}
fn check(build: &mut Builder<'_>, pointer: Value, size: u64) {
let args = build.func().push_values(&[pointer]);
let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
let info = MemInfo {
size,
align: 1,
order: MemOrder::NotAtomic,
tbaa: None,
restrict: Restrict::NONE,
};
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 live(build: &mut Builder<'_>, pointer: Value) {
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]);
build.inst(InstData { args, ..InstData::new(Opcode::CheckLive) }, &[]);
}
fn access(build: &mut Builder<'_>, pointer: Value, size: u64) {
check(build, pointer, size);
live(build, pointer);
}
fn past(build: &mut Builder<'_>, pointer: Value, bytes: i128) -> Value {
let offset = build.iconst(Type::int(64), bytes);
let args = build.func().push_values(&[pointer, offset]);
build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
}
fn marked(func: &mut Func) {
let insts: Vec<Inst> =
func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
for inst in insts {
let check = matches!(
func[inst].opcode,
Opcode::CheckBounds | Opcode::CheckLive | Opcode::CheckDeriv
);
if check {
func[inst].flags |= Flags::STATIC;
}
}
}
fn checks(func: &Func) -> usize {
func.blocks()
.flat_map(|block| func.insts(block).collect::<Vec<_>>())
.filter(|&inst| func[inst].opcode == Opcode::CheckBounds)
.count()
}
fn lives(func: &Func) -> usize {
func.blocks()
.flat_map(|block| func.insts(block).collect::<Vec<_>>())
.filter(|&inst| func[inst].opcode == Opcode::CheckLive)
.count()
}
fn run(func: &mut Func) -> crate::Stats {
Discharge.run(func, &mut Analyses::new(), &mut Fuel::unlimited())
}
#[test]
fn a_second_check_of_the_same_bytes_goes() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 4);
check(&mut build, pointer, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(checks(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
}
#[test]
fn a_check_over_a_length_the_program_worked_out_is_not_this_pass_to_read() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 4);
let args = build.func().push_values(&[pointer]);
let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
let bytes = build.iconst(Type::int(64), 4);
let info = MemInfo {
size: 4,
align: 1,
order: MemOrder::NotAtomic,
tbaa: None,
restrict: Restrict::NONE,
};
let extra = Extra::Mem(build.func().add_mem(info));
let args = build.func().push_values(&[capability, pointer, bytes]);
build.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(checks(&func), 2, "the second one stays");
assert_eq!(stats.count(Kind::Missed, super::COMPUTED_EXTENT), 1);
}
#[test]
fn a_check_of_bytes_inside_a_checked_range_goes() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 16);
let field = past(&mut build, pointer, 4);
check(&mut build, field, 4);
build.ret(&[]);
run(&mut func);
assert_eq!(checks(&func), 1);
}
#[test]
fn a_check_of_bytes_past_the_end_of_a_checked_range_stays() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 16);
let over = past(&mut build, pointer, 14);
check(&mut build, over, 4);
build.ret(&[]);
assert!(!run(&mut func).changed());
assert_eq!(checks(&func), 2);
}
#[test]
fn a_check_of_bytes_before_a_checked_range_stays() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 16);
let under = past(&mut build, pointer, -4);
check(&mut build, under, 4);
build.ret(&[]);
assert!(!run(&mut func).changed());
assert_eq!(checks(&func), 2);
}
#[test]
fn a_check_through_a_pointer_nothing_relates_to_the_first_stays() {
let mut names = Interner::new();
let name = names.intern("two");
let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR, Type::PTR]));
let block = func.create_block();
let one = func.append_param(block, Type::PTR);
let other = func.append_param(block, Type::PTR);
let mut build = Builder::new(&mut func, block);
check(&mut build, one, 16);
check(&mut build, other, 4);
build.ret(&[]);
assert!(!run(&mut func).changed());
assert_eq!(checks(&func), 2);
}
#[test]
fn a_check_a_call_stands_between_stays_and_is_counted() {
let (mut names, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 16);
let callee = names.intern("might_free");
let signature = build.func().add_signature(Signature::new());
build.call(callee, signature, &[]);
check(&mut build, pointer, 4);
build.ret(&[]);
let stats = run(&mut func);
assert!(!stats.changed());
assert_eq!(checks(&func), 2);
assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
}
#[test]
fn a_check_a_call_that_cannot_free_stands_between_goes() {
let (mut names, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 16);
let callee = names.intern("counts_them");
let signature = build.func().add_signature(Signature::new());
let call = build.call(callee, signature, &[]);
check(&mut build, pointer, 4);
build.ret(&[]);
func[call].flags |= Flags::NOFREE;
let stats = run(&mut func);
assert_eq!(checks(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
}
#[test]
fn inline_assembly_throws_the_facts_away_whatever_it_is_flagged() {
let (mut names, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 16);
build.inline_asm(
AsmInfo {
template: names.intern("nop"),
constraints: names.intern(""),
clobbers: names.intern(""),
targets: BlockCallList::EMPTY,
},
&[],
&[],
Flags::NONE,
);
check(&mut build, pointer, 4);
build.ret(&[]);
let stats = run(&mut func);
assert!(!stats.changed());
assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
}
#[test]
fn a_check_that_only_one_path_covers_stays() {
let (_, mut func, block, pointer) = blank();
let arm = func.create_block();
let join = func.create_block();
let mut build = Builder::new(&mut func, block);
let condition = build.iconst(Type::int(32), 1);
build.br_if(condition, arm, &[], join, &[]);
let mut build = Builder::new(&mut func, arm);
check(&mut build, pointer, 16);
build.jump(join, &[]);
let mut build = Builder::new(&mut func, join);
check(&mut build, pointer, 4);
build.ret(&[]);
assert!(!run(&mut func).changed());
assert_eq!(checks(&func), 2);
}
#[test]
fn a_check_a_dominating_block_covers_goes() {
let (_, mut func, block, pointer) = blank();
let after = func.create_block();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 16);
build.jump(after, &[]);
let mut build = Builder::new(&mut func, after);
let field = past(&mut build, pointer, 8);
check(&mut build, field, 8);
build.ret(&[]);
run(&mut func);
assert_eq!(checks(&func), 1);
}
#[test]
fn fuel_stops_the_removing_and_not_the_looking() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 4);
check(&mut build, pointer, 4);
check(&mut build, pointer, 4);
build.ret(&[]);
let mut fuel = Fuel::of(1);
let stats = Discharge.run(&mut func, &mut Analyses::new(), &mut fuel);
assert_eq!(checks(&func), 2);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
assert_eq!(stats.count(Kind::Missed, super::NO_FUEL), 1);
}
#[test]
fn a_second_lifetime_check_of_the_same_address_goes() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
live(&mut build, pointer);
live(&mut build, pointer);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(lives(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
}
#[test]
fn a_lifetime_check_inside_a_checked_range_goes() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
access(&mut build, pointer, 16);
let field = past(&mut build, pointer, 4);
access(&mut build, field, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(checks(&func), 1);
assert_eq!(lives(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
}
#[test]
fn a_lifetime_check_outside_every_checked_range_stays() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
access(&mut build, pointer, 16);
let over = past(&mut build, pointer, 20);
live(&mut build, over);
build.ret(&[]);
assert!(!run(&mut func).changed());
assert_eq!(lives(&func), 2);
}
#[test]
fn a_lifetime_check_with_no_range_around_it_does_not_widen() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
live(&mut build, pointer);
let field = past(&mut build, pointer, 4);
live(&mut build, field);
build.ret(&[]);
assert!(!run(&mut func).changed());
assert_eq!(lives(&func), 2);
}
#[test]
fn a_lifetime_check_a_call_stands_between_stays_and_is_counted() {
let (mut names, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
access(&mut build, pointer, 16);
let callee = names.intern("might_free");
let signature = build.func().add_signature(Signature::new());
build.call(callee, signature, &[]);
let field = past(&mut build, pointer, 4);
live(&mut build, field);
build.ret(&[]);
let stats = run(&mut func);
assert!(!stats.changed());
assert_eq!(lives(&func), 2);
assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 1);
}
#[test]
fn a_lifetime_check_a_call_that_cannot_free_stands_between_goes() {
let (mut names, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
access(&mut build, pointer, 16);
let callee = names.intern("counts_them");
let signature = build.func().add_signature(Signature::new());
let call = build.call(callee, signature, &[]);
let field = past(&mut build, pointer, 4);
live(&mut build, field);
build.ret(&[]);
func[call].flags |= Flags::NOFREE;
let stats = run(&mut func);
assert_eq!(lives(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
}
#[test]
fn ending_a_lifetime_throws_the_facts_away() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
access(&mut build, pointer, 16);
let size = build.iconst(Type::int(64), 16);
let args = build.func().push_values(&[pointer, size]);
build.inst(InstData { args, ..InstData::new(Opcode::MetaEnd) }, &[]);
access(&mut build, pointer, 16);
build.ret(&[]);
let stats = run(&mut func);
assert!(!stats.changed());
assert_eq!(checks(&func), 2);
assert_eq!(lives(&func), 2);
assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 1);
assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 1);
}
#[test]
fn fuel_runs_out_over_both_kinds_of_check() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
access(&mut build, pointer, 16);
access(&mut build, pointer, 4);
build.ret(&[]);
let mut fuel = Fuel::of(1);
let stats = Discharge.run(&mut func, &mut Analyses::new(), &mut fuel);
assert_eq!(checks(&func), 1);
assert_eq!(lives(&func), 2);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_LIVE), 1);
}
#[test]
fn a_distance_too_large_to_be_a_real_access_is_not_discharged() {
let huge = i128::from(u64::MAX) * 4;
let fact = Fact { base: Value::new(0), offset: 0, size: huge };
let asked = Fact { base: Value::new(0), offset: huge / 2, size: 4 };
assert!(!super::covers(&fact, &asked));
}
#[test]
fn a_range_of_addresses_wider_than_the_rule_allows_is_not_discharged() {
let base = Value::new(0);
let whole = Fact::whole(base, i128::from(u64::MAX) * 4);
let asked = super::Reach { base, low: 0, width: i128::from(u64::MAX), size: 4 };
assert!(!super::reaches(&whole, &asked));
}
#[test]
fn a_range_of_addresses_that_ends_where_the_object_does_is_discharged() {
let base = Value::new(0);
let whole = Fact::whole(base, 16);
let asked = super::Reach { base, low: 0, width: 12, size: 4 };
assert!(super::reaches(&whole, &asked));
let over = super::Reach { base, low: 0, width: 13, size: 4 };
assert!(!super::reaches(&whole, &over), "one byte further runs off the end");
}
#[test]
fn a_walk_by_a_bounded_step_off_a_local_takes_its_derivation_check_with_it() {
let (_, mut func, block, _, index) = indexed();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 16);
let step = low_bits(&mut build, index, 7);
let at = walk(&mut build, slot, step);
deriv(&mut build, slot, at, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(derivs(&func), 0);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_RANGE), 1);
}
#[test]
fn a_walk_that_can_leave_the_local_keeps_its_derivation_check() {
let (_, mut func, block, _, index) = indexed();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 8);
let step = low_bits(&mut build, index, 15);
let at = walk(&mut build, slot, step);
deriv(&mut build, slot, at, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(derivs(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_RANGE), 0);
}
#[test]
fn a_lifetime_check_a_bounded_walk_lands_inside_a_checked_range_goes() {
let (_, mut func, block, pointer, index) = indexed();
let mut build = Builder::new(&mut func, block);
access(&mut build, pointer, 32);
let step = low_bits(&mut build, index, 7);
let at = walk(&mut build, pointer, step);
live(&mut build, at);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(lives(&func), 1, "the one in front of the access stays");
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_RANGE), 1);
}
#[test]
fn a_lifetime_check_a_bounded_walk_can_leave_the_checked_range_keeps_it() {
let (_, mut func, block, pointer, index) = indexed();
let mut build = Builder::new(&mut func, block);
access(&mut build, pointer, 8);
let step = low_bits(&mut build, index, 15);
let at = walk(&mut build, pointer, step);
live(&mut build, at);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(lives(&func), 2);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_RANGE), 0);
}
fn local(build: &mut Builder<'_>, size: u64) -> Value {
let info = MemInfo {
size,
align: 8,
order: MemOrder::NotAtomic,
tbaa: None,
restrict: Restrict::NONE,
};
let extra = Extra::Mem(build.func().add_mem(info));
build.value(InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
}
fn indexed() -> (Interner, Func, Block, Value, Value) {
let mut names = Interner::new();
let name = names.intern("f");
let mut func = Func::new(name, Signature::new().with_params(&[Type::PTR, Type::int(64)]));
let block = func.create_block();
let pointer = func.append_param(block, Type::PTR);
let index = func.append_param(block, Type::int(64));
(names, func, block, pointer, index)
}
fn walk(build: &mut Builder<'_>, pointer: Value, by: Value) -> Value {
let args = build.func().push_values(&[pointer, by]);
build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
}
fn low_bits(build: &mut Builder<'_>, value: Value, mask: i128) -> Value {
let bits = build.iconst(Type::int(64), mask);
build.binary(Opcode::And, value, bits, Flags::NONE)
}
#[test]
fn a_walk_by_a_step_the_ranges_bound_inside_a_local_goes() {
let (_, mut func, block, _, index) = indexed();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 16);
let step = low_bits(&mut build, index, 7);
let at = walk(&mut build, slot, step);
check(&mut build, at, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(checks(&func), 0);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 1);
}
#[test]
fn a_walk_by_a_step_the_ranges_cannot_bound_is_left_alone() {
let (_, mut func, block, _, index) = indexed();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 16);
let at = walk(&mut build, slot, index);
check(&mut build, at, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(checks(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 0);
}
#[test]
fn a_walk_a_bounded_step_can_take_off_the_end_of_a_local_is_left_alone() {
let (_, mut func, block, _, index) = indexed();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 8);
let step = low_bits(&mut build, index, 7);
let at = walk(&mut build, slot, step);
check(&mut build, at, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(checks(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 0);
}
#[test]
fn a_constant_step_past_a_bounded_one_is_walked_too() {
let (_, mut func, block, _, index) = indexed();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 32);
let step = low_bits(&mut build, index, 15);
let element = walk(&mut build, slot, step);
let field = past(&mut build, element, 8);
check(&mut build, field, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(checks(&func), 0);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 1);
}
#[test]
fn what_a_range_discharge_records_is_the_bytes_and_not_the_range() {
let (_, mut func, block, _, index) = indexed();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 16);
let step = low_bits(&mut build, index, 7);
let at = walk(&mut build, slot, step);
check(&mut build, at, 4);
check(&mut build, at, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(checks(&func), 0);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_RANGE), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED), 1);
}
fn growable(build: &mut Builder<'_>, size: Value) -> Value {
let info = MemInfo {
size: 0,
align: 8,
order: MemOrder::NotAtomic,
tbaa: None,
restrict: Restrict::NONE,
};
let extra = Extra::Mem(build.func().add_mem(info));
let args = build.func().push_values(&[size]);
build.value(InstData { args, extra, ..InstData::new(Opcode::Alloca) }, Type::PTR)
}
#[test]
fn a_check_of_bytes_inside_a_local_goes_with_nothing_in_front_of_it() {
let (_, mut func, block, _) = blank();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 16);
let field = past(&mut build, slot, 8);
check(&mut build, field, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(checks(&func), 0);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 1);
}
#[test]
fn a_check_past_the_end_of_a_local_stays() {
let (_, mut func, block, _) = blank();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 16);
let field = past(&mut build, slot, 16);
check(&mut build, field, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(checks(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 0);
}
#[test]
fn a_check_of_bytes_inside_a_local_goes_across_a_call() {
let (mut names, mut func, block, _) = blank();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 16);
let callee = names.intern("might_free");
let signature = build.func().add_signature(Signature::new());
build.call(callee, signature, &[]);
check(&mut build, slot, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(checks(&func), 0);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 1);
assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
}
#[test]
fn a_check_inside_a_variable_length_array_stays() {
let (_, mut func, block, _) = blank();
let mut build = Builder::new(&mut func, block);
let bytes = build.iconst(Type::int(64), 64);
let slot = growable(&mut build, bytes);
check(&mut build, slot, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(checks(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LOCAL), 0);
}
#[test]
fn a_lifetime_check_in_a_local_goes_with_nothing_in_front_of_it() {
let (_, mut func, block, _) = blank();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 16);
live(&mut build, slot);
let field = past(&mut build, slot, 12);
live(&mut build, field);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(lives(&func), 0);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 2);
}
#[test]
fn a_lifetime_check_past_the_end_of_a_local_stays() {
let (_, mut func, block, _) = blank();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 16);
live(&mut build, slot);
let field = past(&mut build, slot, 24);
live(&mut build, field);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(lives(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 1);
}
#[test]
fn something_ending_a_lifetime_turns_the_frame_slot_rule_off() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 16);
live(&mut build, slot);
let field = past(&mut build, slot, 12);
live(&mut build, field);
let size = build.iconst(Type::int(64), 16);
let args = build.func().push_values(&[pointer, size]);
build.inst(InstData { args, ..InstData::new(Opcode::MetaEnd) }, &[]);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(lives(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_LOCAL), 0);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE), 1);
}
fn deriv(build: &mut Builder<'_>, from: Value, to: Value, stride: i128) {
let args = build.func().push_values(&[from]);
let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
let width = build.iconst(Type::int(64), stride);
let args = build.func().push_values(&[capability, from, to, width]);
build.inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[]);
}
fn derivs(func: &Func) -> usize {
func.blocks()
.flat_map(|block| func.insts(block).collect::<Vec<_>>())
.filter(|&inst| func[inst].opcode == Opcode::CheckDeriv)
.count()
}
#[test]
fn a_walk_inside_a_checked_range_goes() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 16);
let field = past(&mut build, pointer, 8);
deriv(&mut build, pointer, field, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(derivs(&func), 0);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 1);
}
#[test]
fn a_walk_that_leaves_the_checked_range_stays() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 4);
let field = past(&mut build, pointer, 8);
deriv(&mut build, pointer, field, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(derivs(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 0);
}
#[test]
fn two_ranges_holding_one_end_each_do_not_answer_a_walk() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 4);
let field = past(&mut build, pointer, 64);
check(&mut build, field, 4);
deriv(&mut build, pointer, field, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(derivs(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV), 0);
}
#[test]
fn a_walk_inside_a_local_goes_with_nothing_in_front_of_it() {
let (_, mut func, block, _) = blank();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 16);
let field = past(&mut build, slot, 8);
deriv(&mut build, slot, field, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(derivs(&func), 0);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_LOCAL), 1);
}
#[test]
fn a_walk_off_the_end_of_a_local_stays() {
let (_, mut func, block, _) = blank();
let mut build = Builder::new(&mut func, block);
let slot = local(&mut build, 16);
let field = past(&mut build, slot, 16);
deriv(&mut build, slot, field, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(derivs(&func), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_LOCAL), 0);
}
#[test]
fn a_walk_a_call_stands_between_stays_and_is_counted() {
let (mut names, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 16);
let callee = names.intern("might_free");
let signature = build.func().add_signature(Signature::new());
build.call(callee, signature, &[]);
let field = past(&mut build, pointer, 8);
deriv(&mut build, pointer, field, 4);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(derivs(&func), 1);
assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_DERIV), 1);
}
#[test]
fn a_check_the_module_says_is_inside_a_global_goes_with_nothing_in_front_of_it() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
let field = past(&mut build, pointer, 8);
deriv(&mut build, pointer, field, 1);
access(&mut build, field, 4);
build.ret(&[]);
marked(&mut func);
let stats = run(&mut func);
assert_eq!(checks(&func), 0);
assert_eq!(lives(&func), 0);
assert_eq!(derivs(&func), 0);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_STATIC), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_LIVE_STATIC), 1);
assert_eq!(stats.count(Kind::Optimized, super::REMOVED_DERIV_STATIC), 1);
}
#[test]
fn a_check_inside_a_global_goes_across_a_call() {
let (mut names, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
let callee = names.intern("might_free");
let signature = build.func().add_signature(Signature::new());
build.call(callee, signature, &[]);
access(&mut build, pointer, 4);
build.ret(&[]);
marked(&mut func);
let stats = run(&mut func);
assert_eq!(checks(&func), 0);
assert_eq!(lives(&func), 0);
assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL), 0);
assert_eq!(stats.count(Kind::Missed, super::PAST_A_CALL_LIVE), 0);
}
#[test]
fn a_check_the_module_marked_costs_fuel_like_any_other() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
access(&mut build, pointer, 4);
build.ret(&[]);
marked(&mut func);
let stats = Discharge.run(&mut func, &mut Analyses::new(), &mut Fuel::of(1));
assert_eq!(checks(&func) + lives(&func), 1);
assert_eq!(stats.count(Kind::Missed, super::NO_FUEL_LIVE), 1);
}
#[test]
fn a_walk_off_a_pointer_the_check_does_not_name_stays() {
let (_, mut func, block, pointer) = blank();
let mut build = Builder::new(&mut func, block);
check(&mut build, pointer, 16);
let field = past(&mut build, pointer, 8);
let args = build.func().push_values(&[field]);
let capability = build.value(InstData { args, ..InstData::new(Opcode::CapOf) }, Type::CAP);
let width = build.iconst(Type::int(64), 4);
let args = build.func().push_values(&[capability, pointer, field, width]);
build.inst(InstData { args, ..InstData::new(Opcode::CheckDeriv) }, &[]);
build.ret(&[]);
let stats = run(&mut func);
assert_eq!(derivs(&func), 1);
assert_eq!(stats.count(Kind::Missed, super::UNKNOWN_SHAPE_DERIV), 1);
}
}