cargo rapx verify takes exponential time in the depth and branching of the local call chain below a target. This crate takes 24 seconds:
fn l4(a: u32) -> u32 { a ^ 1 }
fn l3(mut a: u32) -> u32 {
if a & 1 != 0 { a = l4(a) }
if a & 2 != 0 { a = l4(a) }
if a & 4 != 0 { a = l4(a) }
if a & 8 != 0 { a = l4(a) }
a
}
fn l2(mut a: u32) -> u32 {
if a & 1 != 0 { a = l3(a) }
if a & 2 != 0 { a = l3(a) }
if a & 4 != 0 { a = l3(a) }
if a & 8 != 0 { a = l3(a) }
a
}
fn l1(mut a: u32) -> u32 {
if a & 1 != 0 { a = l2(a) }
if a & 2 != 0 { a = l2(a) }
if a & 4 != 0 { a = l2(a) }
if a & 8 != 0 { a = l2(a) }
a
}
fn l0(mut a: u32) -> u32 {
if a & 1 != 0 { a = l1(a) }
if a & 2 != 0 { a = l1(a) }
if a & 4 != 0 { a = l1(a) }
if a & 8 != 0 { a = l1(a) }
a
}
pub fn target(xs: &[u32]) -> String {
format!("{}", l0(xs[0]))
}
Each function has K independent ifs with a call on the branch, chained D levels deep. target is a scan target because of the format!, and its slice parameter gives it the [T] invariants checked at the return. Seconds per run, same shape:
| K |
D = 3 |
D = 4 |
D = 5 |
| 3 |
0.5 |
1.0 |
6.0 |
| 4 |
1.2 |
24 |
596 |
| 5 |
10.8 |
823 |
over 1800 |
--postfix-repeat 0, 1 and auto make no difference. D = 6 costs the same as D = 5, which matches the depth > 4 cutoff.
A stuck run always sits in the same stack: must_write_args_rec nested five deep, under dependency_summary, under slicer::call_visit::visit, under the backward slicer in check_invariant_from_tree. must_write_args_rec (verify/call_summary/interprocedural.rs:1141) enumerates every whole-CFG path of the callee, up to 4000, and write_args_on_path recurses into it again for every call on each path (line 1358). Nothing is cached, so a callee is summarized once for every path that reaches it. On a real crate, where callees have hundreds of paths, one function ran for over 20 minutes without finishing. Caching the result per callee would likely remove most of it.
RAPx at 29cc910 (main), nightly-2026-09-22 (rustc 1.100.0-nightly 1303417c4 2026-09-21), on a lib crate holding only this file.
cargo rapx verifytakes exponential time in the depth and branching of the local call chain below a target. This crate takes 24 seconds:Each function has K independent
ifs with a call on the branch, chained D levels deep.targetis a scan target because of theformat!, and its slice parameter gives it the[T]invariants checked at the return. Seconds per run, same shape:--postfix-repeat0, 1 and auto make no difference. D = 6 costs the same as D = 5, which matches thedepth > 4cutoff.A stuck run always sits in the same stack:
must_write_args_recnested five deep, underdependency_summary, underslicer::call_visit::visit, under the backward slicer incheck_invariant_from_tree.must_write_args_rec(verify/call_summary/interprocedural.rs:1141) enumerates every whole-CFG path of the callee, up to 4000, andwrite_args_on_pathrecurses into it again for every call on each path (line 1358). Nothing is cached, so a callee is summarized once for every path that reaches it. On a real crate, where callees have hundreds of paths, one function ran for over 20 minutes without finishing. Caching the result per callee would likely remove most of it.RAPx at 29cc910 (main), nightly-2026-09-22 (rustc 1.100.0-nightly 1303417c4 2026-09-21), on a lib crate holding only this file.