Running the released analyzer over a real-world corpus, 97% of modules hit
unsoundness-fallback and only 18% of memory-touching modules have all their accesses
provable. Posting the operator ranking behind that, since it is a directly actionable priority
list — and one diagnostic-quality issue that makes the largest bucket unusable.
This is not a soundness complaint. scry behaved correctly on every probe I ran: it widens to top,
it refuses to prove what it cannot, and it announces every fallback through a dedicated severity.
The question here is purely how much of that value is reachable on real code today.
Measured on scry-3.2.4-wasm32-wasip2.wasm (release asset) via
wasmtime run --invoke 'analyze([...], {...})'.
Coverage on real-world modules
Corpus: 906 artifacts (toolchain output from this org's repos + components pulled from
wasm.directory). 530 are core modules; 468 fit within the ~150 KB that the --invoke argv path
allows.
analysed (ok): 281
analyzer error: 82 (uncharacterised — empty stderr in my capture)
crash: 2 (filed separately)
timed out at 60s: 103 (timing only, shared machine — not a defect claim)
modules with a memory access: 122
of those, ALL accesses provable: 22 (18%)
modules hitting unsoundness-fallback: 272 of 281 (97%)
The mechanism is what makes the fallback rate matter: each unmodelled operator degrades that
function's locals to top, so everything downstream in the function becomes unprovable. One
i32.and early in a function can cost every subsequent access in it.
The operator ranking
From scry's own fallback diagnostics, aggregated across the corpus:
| count |
operator |
| 579 |
<unsupported> — scry does not name it (see below) |
| 433 |
i32.and |
| 203 |
select |
| 151 |
unreachable |
| 61 |
nop |
| 46 |
i32.or |
| 41 |
i32.xor |
| 40 |
i32.shl |
| 25 |
i32.shr_s |
| 12 |
i32.shr_u |
| 3 |
drop |
Three clusters stand out, in what looks like decreasing value-per-effort:
- The trivial ones —
nop, drop, unreachable (215 combined). These have no effect on
locals at all (unreachable makes the rest of the block dead). Modelling them as identity
should be nearly free and immediately stops poisoning the functions that contain them.
- The bitwise/shift family —
and, or, xor, shl, shr_s, shr_u (597 combined). A
coherent group, and the one that matters most for bounds work specifically: i32.and with a
constant mask is the idiom producers emit to constrain an address, so it is exactly the
operator whose absence blocks the bounds proofs synth's --proven-safe wants. Interval
analysis of and against a constant mask gives a tight bound directly.
select (203). Needs a join of the two arms, more work than the above but standard.
For context on (2): I probed the boundary behaviour by hand and scry is byte-exact on constant
addresses — it proves i32.load at 65532 (last byte 65535, fits) and refuses 65533 (last byte
65536, overruns by one) against a 64 KiB region. The precision is there; i32.and is what stops
it being reachable on real code, where addresses are masked rather than constant.
The <unsupported> placeholder — 579 fallbacks that name nothing
The largest bucket is not an operator. scry emits the literal string:
severity: unsoundness-fallback, func-index: 0, pc: 3,
message: "unsupported operator at v0.2 AC#1: <unsupported> — locals degraded to top"
So for the majority of fallbacks a consumer is told that analysis degraded but not what
caused it. That makes the diagnostic non-actionable in exactly the cases where it would be most
useful, and it is presumably a cheap fix — the operator is known at the point the fallback is
raised. Naming it would also turn this whole ranking into something scry can report about itself
rather than something that has to be reconstructed from outside.
Why this ranking is worth having
synth's --proven-safe consumes proven-safe access sites to elide bounds guards (synth#901/#932);
scry's roadmap FEAT-069 is the producer. On today's numbers that channel would carry proofs for
roughly one in five memory-touching modules, and the single operator most responsible is the
constant-mask idiom that address-bounding code is written in. Modelling clusters (1) and (2) looks
like it would move that number substantially for a modest amount of work.
Happy to re-run the corpus against a branch and report the delta — the harness is scripted and the
comparison is cheap.
Running the released analyzer over a real-world corpus, 97% of modules hit
unsoundness-fallbackand only 18% of memory-touching modules have all their accessesprovable. Posting the operator ranking behind that, since it is a directly actionable priority
list — and one diagnostic-quality issue that makes the largest bucket unusable.
This is not a soundness complaint. scry behaved correctly on every probe I ran: it widens to top,
it refuses to prove what it cannot, and it announces every fallback through a dedicated severity.
The question here is purely how much of that value is reachable on real code today.
Measured on
scry-3.2.4-wasm32-wasip2.wasm(release asset) viawasmtime run --invoke 'analyze([...], {...})'.Coverage on real-world modules
Corpus: 906 artifacts (toolchain output from this org's repos + components pulled from
wasm.directory). 530 are core modules; 468 fit within the ~150 KB that the
--invokeargv pathallows.
The mechanism is what makes the fallback rate matter: each unmodelled operator degrades that
function's locals to top, so everything downstream in the function becomes unprovable. One
i32.andearly in a function can cost every subsequent access in it.The operator ranking
From scry's own fallback diagnostics, aggregated across the corpus:
<unsupported>— scry does not name it (see below)i32.andselectunreachablenopi32.ori32.xori32.shli32.shr_si32.shr_udropThree clusters stand out, in what looks like decreasing value-per-effort:
nop,drop,unreachable(215 combined). These have no effect onlocals at all (
unreachablemakes the rest of the block dead). Modelling them as identityshould be nearly free and immediately stops poisoning the functions that contain them.
and,or,xor,shl,shr_s,shr_u(597 combined). Acoherent group, and the one that matters most for bounds work specifically:
i32.andwith aconstant mask is the idiom producers emit to constrain an address, so it is exactly the
operator whose absence blocks the bounds proofs synth's
--proven-safewants. Intervalanalysis of
andagainst a constant mask gives a tight bound directly.select(203). Needs a join of the two arms, more work than the above but standard.For context on (2): I probed the boundary behaviour by hand and scry is byte-exact on constant
addresses — it proves
i32.loadat 65532 (last byte 65535, fits) and refuses 65533 (last byte65536, overruns by one) against a 64 KiB region. The precision is there;
i32.andis what stopsit being reachable on real code, where addresses are masked rather than constant.
The
<unsupported>placeholder — 579 fallbacks that name nothingThe largest bucket is not an operator. scry emits the literal string:
So for the majority of fallbacks a consumer is told that analysis degraded but not what
caused it. That makes the diagnostic non-actionable in exactly the cases where it would be most
useful, and it is presumably a cheap fix — the operator is known at the point the fallback is
raised. Naming it would also turn this whole ranking into something scry can report about itself
rather than something that has to be reconstructed from outside.
Why this ranking is worth having
synth's
--proven-safeconsumes proven-safe access sites to elide bounds guards (synth#901/#932);scry's roadmap FEAT-069 is the producer. On today's numbers that channel would carry proofs for
roughly one in five memory-touching modules, and the single operator most responsible is the
constant-mask idiom that address-bounding code is written in. Modelling clusters (1) and (2) looks
like it would move that number substantially for a modest amount of work.
Happy to re-run the corpus against a branch and report the delta — the harness is scripted and the
comparison is cheap.