Skip to content

97% of real-world modules hit unsoundness-fallback, only 18% of memory-touching modules fully provable — operator ranking, and 579 fallbacks that name no operator #126

Description

@avrabe

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:

  1. 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.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions