Skip to content

fw-emu: per-frame heap reporting + pin the output-overshoot degradation contract - #280

Draft
Yona-Appletree wants to merge 2 commits into
mainfrom
claude/great-keller-6cc73b
Draft

fw-emu: per-frame heap reporting + pin the output-overshoot degradation contract#280
Yona-Appletree wants to merge 2 commits into
mainfrom
claude/great-keller-6cc73b

Conversation

@Yona-Appletree

@Yona-Appletree Yona-Appletree commented Aug 2, 2026

Copy link
Copy Markdown
Member

Rebased onto current main (2026-08-02 evening). Most of what this PR originally carried is now redundant — this is the residue that isn't.

What happened to the original PR

It was filed as an investigation of docs/defects/2026-08-02-c6-project-outgrows-board-outputs-oom-storm.md, and concluded the defect was mis-attributed: a project declaring more outputs than the board offers degrades cleanly, with a flat heap and no retry, so the OOM storm came from somewhere else (soft-limit-bench, 227 KB resident on a 300 KB heap).

Since then, two things landed on main independently:

  • The unwinding teardown (ADR 2026-08-02-rv32-firmwares-are-abort-tier) killed the storm shape outright — there is no catcher any more, so there is no 53rd panic because there is no 2nd. It also explained the ledger-blamed stack-overflow crashes: unwinding one panic needed ~41 KB of stack on a chip with ~34 KB.
  • The defect doc on main was already amended with that analysis, and it explicitly adopts this PR's re-scoping ("What remains open is the real question, and it is the one PR fw-emu: per-frame heap reporting + pin the output-overshoot degradation contract #280 re-scoped this defect to").

So the conclusion has been absorbed. The doc commit has been dropped — main's version is better and says the same thing.

What's left, and why it's still worth landing

Two commits, +348 lines, no production behaviour change:

  • fw-emu/heap_report (off by default) and lp_riscv_emu_guest::allocator::heap_stats() — the emulator's equivalent of the ESP32s' esp32_memory_stats, logging [heap] used=N free=M once per frame. Nothing on main duplicates this. It is directly useful for the question that is still open — heap headroom when auto-loading an oversized project — which main's own amendment says should now be much easier to reproduce.
  • output_overshoot_emu.rs — pins the degradation contract, which outlived the storm it was written for. The abort tier makes a panic storm unreachable, but an endpoint that cannot open must still cost the board one sink rather than its boot, and must not ratchet the heap.

Re-verified against today's main

healthy (4 outputs / 4 channels) overshoot (5 outputs / 4 channels)
open failures over 40 frames 0 2, total
heap used, first → last frame 117,900 → 117,900 B 122,168 → 122,168 B
heap growth 0 B 0 B

The unopenable sink is asked twice — once on its first flush, then once more because the successful opens in that same flush bump the hardware generation, the designed one-retry-per-ownership-change — then parks. Same numbers as before the teardown, which is the point.

Reasonable to close instead if the appetite for extra test surface is low; the analysis is already safe in the defect doc on main. The instrumentation is the part that would have to be rebuilt later.

🤖 Generated with Claude Code

Yona-Appletree added a commit that referenced this pull request Aug 2, 2026
…ator firmware

fw-emu is the real firmware image: same engine, same server, and what answers
hardware questions without hardware (PR #280). Keeping panic-recovery here
while fw-esp32c6 dropped it would make it test a configuration that ships
nowhere -- the exact drift ADR 2026-07-23 existed to prevent.

The guest panic handler collapses to what it always did after unwinding gave
up: stage the breadcrumb, finalize, report to the host. memory.ld now discards
.eh_frame and .gcc_except_table explicitly rather than retaining them in ROM.

Three tests rewritten, not two. `FAULT_RECOVERED_PANIC` is renamed
`FAULT_FRAMED_PANIC` -- nothing recovers a panic in-process any more, but the
fault still routes through `catch_node_panic_framed`, which is what proves a
red-gated path is denied UP FRONT without running the body.

- repeat_panics_gate_then_power_on_clears (was recovered_panics_gate_...):
  reboot-shaped. Each offense now costs a boot. The escalation is observable
  because `Recovery::init` demotes reds BEFORE recording the prior crash, so a
  repeat offender lands straight back on red for the next run.
- escalation_gates_the_parent_and_clean_runs_return_to_green: same treatment;
  two distinct children still escalate to the parent.
- fuel_exhausted_shader_gates_without_reboot ->
  fuel_exhausted_shader_errors_without_reboot_or_blame. NOT identified in the
  plan. Its premise was that a repeat fuel offense red-gates the node, which
  worked only because the fuel trap PANICKED under panic-recovery and a caught
  panic is what records blame. With typed errors nothing latches: a hung shader
  now reports every frame instead of being disabled. The test asserts the
  ledger stays GREEN across three offenses, so restoring blame for fuel traps
  will fail it deliberately.

just test-recovery-emu: 7/7 green.

Plan: 2026-08-02-1314-rv32-unwinding-teardown

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Yona-Appletree and others added 2 commits August 2, 2026 19:51
The ESP32 firmwares all expose `esp32_memory_stats` from `esp_alloc::HEAP`;
the emulator had no equivalent, so a test could only infer heap behaviour.
`allocator::heap_stats()` returns the same (used, free) pair from the guest's
linked-list heap, and fw-emu's `heap_report` feature logs it once per frame.

Off by default — it is a log line per frame, for tests that need to tell a
bounded transient from a leak.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runs the real firmware image with two projects that differ only in output
count: four outputs on the virtual board's four timing channels, and five on
the same four. The fifth endpoint is valid but can never open — the shape the
C6 report hit with two outputs on a one-channel manifest, reproduced without
editing a manifest.

Measures what that costs per frame: open attempts, caught panics, and heap
used. The unopenable sink is asked twice (once, plus the designed retry after
the successful opens bump the hardware generation), then parks; the heap is
flat over 40 frames and nothing panics.

Guards the contract in docs/defects/2026-08-02-c6-project-outgrows-board-\
outputs-oom-storm.md: one endpoint that cannot open must cost the board a
sink, not its boot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Yona-Appletree
Yona-Appletree force-pushed the claude/great-keller-6cc73b branch from 6f26811 to a3cf3f8 Compare August 3, 2026 03:00
@Yona-Appletree Yona-Appletree changed the title C6 OOM storm: the output overshoot was not the cause fw-emu: per-frame heap reporting + pin the output-overshoot degradation contract Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant