Skip to content

C3 walk-free: bus cycle clock (read-side freshness) + RTC lazy migration + write-choke IRQ aggregation#516

Merged
w1ne merged 3 commits into
mainfrom
perf/c3-walk-free
Jul 11, 2026
Merged

C3 walk-free: bus cycle clock (read-side freshness) + RTC lazy migration + write-choke IRQ aggregation#516
w1ne merged 3 commits into
mainfrom
perf/c3-walk-free

Conversation

@w1ne

@w1ne w1ne commented Jul 11, 2026

Copy link
Copy Markdown
Owner

First production user of the walk-free plan's read-side freshness mechanism, plus the two C3-specific unlocks. Measured: ESP32-C3 OLED lab 3.27 → 4.56 MIPS native at interval 1 (+39%); 15.5 MIPS at interval 64 (unreachable until the remaining systimer/Class-A migrations land — max_safe_tick_interval honestly stays 1 for C3, asserted in a committed ledger test).

  • CycleClock (cycle_clock.rs): bus-published Arc<AtomicU64> "now", written only by SystemBus::set_current_cycle (the single choke covering batch start/end, step, idle-FF), handed to peripherals via new Peripheral::attach_cycle_clock at add-time. Contract: exact at batch boundaries, < one interval stale mid-batch — identical to the write-path sync_to bound. No clock attached ⇒ model must stay on the walk (conservative).
  • C3 RTC timer off the walk: lazy advance_to driven by both write-path sync and the TIME_UPDATE latch pulling clock.now() (its only firmware-visible time is the write-latch — hardware semantics). Fixes the audited double-count trap (tick_elapsed no longer bumps the anchor; modes mutually exclusive by construction). Resume re-anchors to the live clock so a snapshot restored on a fresh machine cannot freeze the counter (unit-tested).
  • C3 IRQ aggregation: shared recompute body + write-choke re-aggregation for INTC config and FROM_CPU IPI writes; per_cycle_tick_is_trivial no longer excludes C3. At interval 1 byte-identical; at interval > 1 write-driven inputs deliver at the write instruction, peripheral sources tick-quantised ≤ one interval. Also fixes a pre-existing coherence gap: the inherent SystemBus::write_u32/u16 never synced the C3 IRQ cache.
  • Hot-path syscall removal: LABWIRED_RXBUF_TRACE was an env::var on every load instruction — now a process-cached OnceLock gate.

Differentials (committed): real OLED-demo lab via the browser-equivalent flash-fast-start assembly, walk-on vs scheduler RTC — serial + total_cycles + SSD1306 framebuffer byte-identical over 30M instructions, framebuffer byte-identical across intervals {1,64}; the feared rtc_time_get busy-poll delay loop terminates byte-identically on both paths at intervals 1 and 64.

All lanes green (workspace lib, jit+event-scheduler 1845, clippy -D warnings both, fmt, wasm32 check, leo/oled e2e incl. the wasm fast-start paint test); known machine-local failures verified identical on untouched main.

Deliberately deferred (honest ledger in-tree): systimer event migration (needs the C3-matrix routing hook in apply_event_result), C3 I2C (wire-engine class), C3 Class-A sweep.

w1ne added 3 commits July 11, 2026 18:47
…e-choke IRQ aggregation

Walk-free plan Part 1 lands as shared infrastructure, with the ESP32-C3
RTC main timer as its first production user and the C3 interrupt
aggregation made batched-interval-compatible (perf/c3-walk-free).

Task A — bus-published CycleClock (read-side freshness):
- New `cycle_clock` module: an Arc<AtomicU64> the bus publishes
  `current_cycle` into at every refresh point (batch start/end, step,
  idle fast-forward) via the new `SystemBus::set_current_cycle` choke.
  A `&self` MMIO read can lazily sync Cell-held counter state to it —
  exact at batch boundaries, < one peripheral_tick_interval stale
  mid-batch, the identical bound the write-path sync_to documents.
- `Peripheral::attach_cycle_clock` (default no-op) hands every
  peripheral a clone at the `add_peripheral` attach choke. Without an
  attached clock a model must stay on its legacy walk path, so
  hand-built buses keep exact semantics.

Task B — esp32c3/rtc_timer.rs off the walk:
- `uses_scheduler()` flips true when the clock is attached under
  event-scheduler; the per-cycle walk skips the RTC entirely. The
  TIME_UPDATE latch (the only point firmware observes time) syncs from
  the published clock; the write-path sync_to shares the same anchored
  advance, so double-counting is impossible by construction.
- The dead-anchor double-count trap flagged by the timing audit is
  gone: tick_elapsed no longer bumps anchor_tick (and is inert in
  scheduler mode); the anchor belongs exclusively to the lazy path.
- runtime_snapshot still persists anchor_tick, but restore re-anchors
  to the live clock: a snapshot resumed on a fresh machine (cycles
  ~0) previously risked freezing the counter until "now" caught up
  with a 150M-cycle stale anchor — the exact spin-forever failure the
  model exists to prevent. Counter value (boot-log determinism)
  carries across unchanged.
- Legacy path preserved byte-identically for feature-off builds and
  clockless buses.

Task C — C3 IRQ aggregation vs the fast paths:
- aggregate_esp32c3_irqs now records the tick's asserting source bitmap
  and rebuilds the routed line mask through a single shared body
  (recompute_esp32c3_irq_lines). INTC config + FROM_CPU IPI writes
  re-aggregate AT THE WRITE via sync_esp32c3_irq_cache_write, so at
  interval > 1 a yield/critical-section transition is reflected at the
  write instruction instead of up to one interval late; at interval 1
  the tick-end rebuild runs before the CPU's next interrupt check, so
  behaviour is byte-identical. Peripheral source assert/de-assert stays
  tick-quantised (≤ one interval — the write-path bound).
- per_cycle_tick_is_trivial no longer excludes C3 buses when the cached
  aggregation is present: on a (future) walk-deleted C3 bus nothing
  ticks, and the write chokes keep FROM_CPU/INTC routing correct, so
  the trivial per-cycle early-out is sound.
- The inherent SystemBus::write_u32/write_u16 paths now sync the C3 IRQ
  cache like the Bus-trait accessors already did (pre-existing
  coherence gap, exposed by the new write-choke test).
- Removed the per-read `std::env::var("LABWIRED_RXBUF_TRACE")` syscall
  from Bus::read_u32 (every load instruction!) and from the aggregation
  tick path — now a process-cached OnceLock gate in wifi_mac.

Gates (all green; #511/#512 style):
- tests/esp32c3_walk_differential.rs: the REAL esp32c3-oled-demo lab
  (browser flash fast-start assembly) walk-on RTC vs scheduler RTC at
  interval 1 — serial, total_cycles and SSD1306 framebuffer
  byte-identical after 30M instructions; framebuffer additionally
  byte-identical across intervals {1, 64}; FROM_CPU/INTC write-choke
  re-aggregation asserted with no tick in between; endgame ledger
  pins that rtc_cntl_timer no longer pins the walk and
  max_safe_tick_interval honestly stays 1 (systimer et al. remain).
- src/tests/esp32c3_rtc_delay_loop.rs: the firmware busy-poll delay
  loop the old comment feared, proven terminating and byte-identical
  to the walk at interval 1 AND 64.
- Native throughput probe (50M instructions, Machine::run, release,
  median of 3): OLED lab at interval 1 goes 3.27 -> 4.56 MIPS (+39%);
  interval 64 reaches 15.5 MIPS (the unlock target once the remaining
  walkers migrate).
@w1ne w1ne merged commit e487bb9 into main Jul 11, 2026
2 checks passed
@w1ne w1ne deleted the perf/c3-walk-free branch July 11, 2026 16:59
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