Skip to content

cpu: revert to the upstream wall clock and restore ci_clock (#98) - #113

Merged
belikh merged 2 commits into
mainfrom
convergence/ci-clock
Oct 26, 2026
Merged

belikh merged 2 commits into
mainfrom
convergence/ci-clock

Conversation

@belikh

@belikh belikh commented Oct 26, 2026

Copy link
Copy Markdown

What changed

The fork's cycle-derived default clock is reverted to upstream's host wall clock, and --features ci_clock is a real feature again. This resolves the largest remaining divergence in clanker/plans/divergence-policy.md (#98; upstream decision on techomancer#322).

User-visible: a normal build once more runs guest time in step with host time. A guest that runs faster or slower than an R4400 still sees the same seconds pass. Deterministic runs (snapshots, replays, the validator) now need --features ci_clock; that build intentionally lets guest wall-clock drift from host time.

  • CP0 Count is derived from a host Instant anchor at the configured rate; Compare is an hptimer one-shot. src/cpu/mips_core.rs, mips_exec.rs, cop0 paths and the step preamble follow upstream.
  • src/cpu/guest_timer.rs is deleted with its Machine field, PIT registration and batch-drain calls. The 8254 PIT returns to upstream's Instant-based channels, keeping upstream pit: implement the 8254 read-back command and release single-byte latches techomancer/iris#323's read-back and latch-release.
  • ci_clock gets upstream's implementation: Count from hot.cycles at 10 ns each, Compare as count_fire_cycle, idle-park exit at the threshold, and snapshot feature gating.
  • Idle-pace (Headless idle spin loop pegs host CPU (no WAIT / IE=0) #83) stays, re-expressed against the wall clock. New: a park that wakes on a line the guest cannot take backs off for ~20 ms, because hot.cycles no longer advances Count and an instruction-counted delay loop would otherwise run at one batch per slice. This took ci_clock,idle-pause cold boot from 176 s to 45 s.
  • Snapshots keep the fork's registered StateDesc. The CPU payload is v3 with a clock field (wall/cycles); a v2 fork payload migrates to the running build's clock, drops the retired queue and gets a fresh Compare deadline from the CP0 load path. A v3 payload from the other clock model is refused.
  • The snapshot determinism validator releases the stopped-CPU Count latch for its inline passes, so it actually exercises the clock model (previously both passes compared identically frozen Counts and could not tell the designs apart).

Storage/BlockNode, the serial log and the graphics boards are untouched.

Tests

CARGO_TARGET_DIR on a separate target directory; host callisto (x86_64, 16 cores).

build command result
default cargo test --workspace 1465 passed, 0 failed, 26 ignored (lib) plus all integration/bin suites
jitv2 cargo test --workspace --features jitv2 1969 passed, 0 failed, 32 ignored
ci_clock cargo test --workspace --features ci_clock 1468 passed, 0 failed, 26 ignored
idle-pause cargo test -p iris --lib --features idle-pause 1403 passed, 0 failed
ci_clock,idle-pause cargo test -p iris --lib --features ci_clock,idle-pause 1406 passed, 0 failed

New/adapted tests: inline_pass_releases_the_stopped_count_latch, inline_pass_keeps_cycle_count_under_ci_clock, cpu_v2_cycle_clock_payload_migrates_to_a_fresh_compare_deadline, cpu_v3_payload_from_the_other_clock_is_refused, an_armed_compare_timer_ends_the_park, the_cycle_compare_threshold_ends_the_park, a_park_woken_by_a_masked_line_backs_off, plus the ip7 ticket tests now advance Count by the right clock.

Evidence

measurement build result runs
determinism, bare mfc0 Count loop, iris-ci validate -n 1000000 default wall deterministic: false, cp0_count A=0xe071974 B=0xe071a0e 1
same snapshot class, same command ci_clock deterministic: true 1
boot to IRIX 6.5 login (R4400, IP22, headless, clean disk) default wall 45 s 3
boot to login ci_clock 45 s 1
guest sleep 30 host elapsed (telnet) default wall 33 s 1
guest sleep 30 host elapsed ci_clock 108 s 1
bench suite, iris-bench run --iris ... --repeat 3 default wall 42.8 MIPS, 67.7 DMIPS, 100.0 % accuracy 3
bench suite ci_clock 41.5 MIPS, 65.2 DMIPS, 100.0 % accuracy 3
idle host CPU at the getty, 10 s samples default wall 202 %, 202 % 2
idle host CPU at the getty idle-pause (wall) 194 %, 194 %, 193 % — detector does not recognise IRIX 6.5's idle loop 3
cold boot with idle-pause wall not reached in 400 s (see risk 1) 1
cold boot with idle-pause ci_clock after the back-off 45 s 1

The determinism harness used a hand-built bare-metal loop (mfc0 t0, $9; addiu; bne) so the result does not depend on which kernel path a snapshot landed in; snapshotted from a bare machine with --load-elf. The default build's earlier "deterministic true" results were the validator comparing frozen Counts, which is why the live-Count change is in this PR.

Representative commands:

iris-bench run --iris ./bin/wall/iris --elf bench/prebuilt/irisbench.elf \
  --config bench/run/bare.toml --label wall-host --repeat 3
iris-ci --socket /tmp/iris-count.sock validate clock/wall-count-loop -n 1000000
iris --config irix-clock.toml    # IRIX 6.5 disk copy, headless, telnet 2323

Remaining risks

  1. Idle-pause cold boot with the wall clock. At the IRIX 6.5 getty the detector does not engage (194 % host), and a cold wall-clock boot with idle-pause did not reach login in 400 s, while the same image without idle-pause boots in 45 s. This is the pre-existing release-idle-pause-hangs-prom-boot issue, now written up with the Upstream: CPU clock model — discussion first #98 measurements. ci_clock,idle-pause boots in 45 s after the back-off. A follow-up should detect IRIX 6.5's idle loop and avoid parking wall-clock instruction-counted delay loops.
  2. PIT arrival is still host-timed under ci_clock. ci_clock makes Count/Compare deterministic, not the whole machine: 8254 and device interrupts still arrive from their own threads. Tests that need full determinism must not depend on PIT timing.
  3. The OS rig must choose. irix7/os timing and the benchmark history assumed the synthetic clock. Deterministic OS runs need --features ci_clock; interactive runs get wall time. The CHANGELOG and FEATURES.md say so.
  4. Old snapshots. A v2 fork snapshot loads into either build (migration gives a fresh Compare deadline); a build with the other clock is refused by the manifest feature check as well as the v3 clock field. Users with saved fork snapshots should expect one migration.
  5. Watchdog for jitv2? The jitv2 equivalence tests pass, but the wall clock means a JIT vs interpreter comparison needs ci_clock to be meaningful; the design note is updated rather than silently claiming otherwise.

Closes #98. References #83 and the upstream decision on techomancer#322.

@belikh
belikh force-pushed the convergence/ci-clock branch from d54777e to 295752b Compare October 26, 2026 06:28
CP0 Count is host-Instant-derived again in normal builds, and the Compare
interrupt is armed as an hptimer one-shot. This reverts the fork's
cycle-derived-by-default clock (e8b264e) and deletes the ordered guest-time
timer queue (800f785, src/cpu/guest_timer.rs) with its Machine field, PIT
registration and drain calls. The 8254 PIT returns to upstream's Instant
channels, keeping the read-back and latch-release work that landed as
upstream techomancer#323.

--features ci_clock is a real feature again: Count is derived from hot.cycles
at 10 ns per retired instruction, Compare is a count_fire_cycle threshold
checked by the per-instruction preamble and by idle parking, and the feature
is recorded in snapshot provenance so a restore into the other clock model is
refused. The CPU state description is version 3 with a `clock` field. A
version-2 fork payload migrates to the running build's clock, drops the
retired queue and gets a fresh Compare deadline from the CP0 load path.

Idle-pace (#83) is kept against the wall clock: a parked CPU advances
hot.cycles for cross-thread readers, wakes on any pending line, and paces a
masked pending interrupt. A park that wakes on a line the guest cannot take
backs off for about 20 ms: the wall clock no longer makes Count advance with
hot.cycles, so parking an instruction-counted delay loop capped boot at one
batch per slice.

The snapshot determinism validator now releases the stopped-CPU Count latch
for its inline passes, so it can tell the two clock models apart instead of
comparing two identically frozen Counts.

Measured on callisto (x86_64, 16 cores):
- determinism, bare Count-read loop, validate -n 1000000: default
  deterministic false (cp0_count A=0xe071974 B=0xe071a0e), ci_clock true.
- boot to IRIX 6.5 login, R4400: wall 45 s (three runs), ci_clock 45 s.
- guest sleep 30: wall 33 s host, ci_clock 108 s host (interface only).
- bench suite, R4400, three runs: wall 42.8 MIPS, ci_clock 41.5 MIPS,
  both 100% accuracy.
HACKING, FEATURES, STORAGE, the GUI README, the IOC 8254 guide and the
CHANGELOG now describe the host wall clock as the default and `ci_clock` as
the deterministic validator build. The CHANGELOG entry calls out the
user-visible clock change and the snapshot rules.

Update the agent notes that the revert invalidates: Count is wallclock-derived
again in default builds, the PIT is host-timed again, the idle-pause deadline
measurement is superseded, and the jitv2 design's determinism claims are
limited to Count/Compare. Add a note for the validator's live-Count rule and
an update to the idle-pause PROM-boot note with the cold-boot measurements.
Mark the reversion plan done and the divergence-policy item resolved.
@belikh
belikh force-pushed the convergence/ci-clock branch from 295752b to c7f8b5b Compare October 26, 2026 06:46
@belikh
belikh merged commit f72256f into main Oct 26, 2026
16 checks passed
@belikh
belikh deleted the convergence/ci-clock branch October 26, 2026 07:14
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.

Upstream: CPU clock model — discussion first

1 participant