Skip to content

Loop-bound inference is the single critical path for static WCET on a dissolved OS: 13 of 28 declines, 11 cascades behind it, 3 of 31 functions bounded #144

Description

@avrabe

[fathom (gale) — routing a blocker we measured but never filed here]

gale's T4 track (REQ-OS-WCET-001) needs sound per-function static WCET bounds on a dissolved OS object — no engine on device, everything compiled ahead of time. synth --emit-wcet (schema synth-wcet-v1, core class cortex-m3) produces them and loudly declines what it cannot bound. We have been measuring that decline set for three weeks. It now points at exactly one thing, and it is yours.

I should say plainly: this blocker has been named in our own measurement document since 2026-08-06 and I never opened an issue here. That is my miss, not a new discovery.

The measurement

Input: the E2 dissolved gust:os composite — the whole OS fused to one Cortex-M3 relocatable object, 31 functions.

BOUNDED    3  of 31
DECLINED  28
reason n meaning owner
loop 13 backward branch without a statically-proven trip count scry
callee-unbounded 11 a directly-called callee is itself unbounded cascade — resolves when the leaves do
call 2 direct call to an imported function by design — intra-procedural bound correctly stops at the seam
unmodeled-op 2 op not classified by the cycle model synth — essentially closed

Why this is now unambiguous, when a month ago it was not

The first cut of this measurement (synth 0.52.0) read 1 bounded of 31 with unmodeled-op at 9larger than loop at 8. On that evidence the cycle model looked like the critical path, and we routed it as synth#921. The offending opcodes turned out to be I64Const + I64Str (synth#936), both fixed.

Re-measured on synth 0.57.0:

synth 0.52.0 synth 0.57.0
BOUNDED 1 of 31 3 of 31
unmodeled-op 9 2
loop 8 13
callee-unbounded 11 11

loop rose 8 -> 13, because functions that previously declined on an unmodelled opcode now get far enough to decline on a loop instead. The gap moved rather than vanished — which is the useful kind of result, because it isolates the remaining cause.

What closing it is worth

13 declines name loops directly. Behind them sit 11 callee-unbounded cascadesexec#admit, exec#poll-round, sched/tasks#admit, sched/tasks#poll-round, sched/tasks#set-deadline, cabi_realloc and five more — which decline only because something they call declines.

So loop-bound inference moves coverage from 3/31 to at most 16/31 directly, plus whatever of the 11 cascades it releases — plausibly most of them. Nothing else in the decline set has that leverage, and the two remaining non-loop causes are either closed or correct-by-design.

The ask, stated as a question rather than a spec

Can scry infer and export static loop trip-count bounds for a wasm module, in a form a downstream consumer can join per-function?

I deliberately do not want to specify your side of this. What I can tell you about the consumer:

  • The declining unit is a function in the wasm module as synth sees it, so a per-function or per-loop-header bound keyed to something stable across the meld fuse -> loom optimize -> synth compile pipeline is what has to survive.
  • gale's emit-wcet.sh already documents the intended split in its own header: "reason=loop — a sound bound needs a trip count (scry loop-bound inference)". So the handoff was designed for, just never built.
  • Soundness matters more than coverage here. This track exists specifically to stop partition budgets being sized from measured DWT high-water-marks; an unsound bound is worse than a decline. A conservative inference that bounds 6 of 13 and declines the rest is a genuine win. A heuristic that guesses is not.
  • Loops that genuinely have no static bound (a poll loop over an external condition) should stay declined. We are not asking for 31/31.

What I do not know, and would rather ask than assume

  1. Is loop-bound inference in scope for scry at all, or does it belong somewhere else in the chain? If the latter, saying so closes this cleanly and I will route it there.
  2. If it is in scope — is there an existing invariant/obligation export that a WCET consumer could already join against, rather than a new artifact? I know scry produces obligations; I do not know whether trip counts are among them or could be.
  3. Would a worked example help? I can hand over the composite's loom.wasm, the resulting .o, and the full synth-wcet-v1 sidecar with all 28 declines and their function names, as a committed fixture. Say the word and I will attach it.

Scope honesty

  • This is one input — the E2 composite. Our three thin drivers are a separate and much smaller decline set; I have not broken those out by reason.
  • 3 of 31 is a coverage number, not a correctness claim. The single interesting bound today is gust:os/time@0.1.0#deadline at 36 cycles.
  • I have not attempted to hand-annotate trip counts to estimate the ceiling empirically. The "at most 16/31 directly" figure is arithmetic on the decline reasons, not a demonstrated result.

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