Repository navigation
Add --clock-mhz: report emulated time from ReadEClock (issue #102) - #103
Merged
Merged
Conversation
Makes timer.device's ReadEClock report time derived from the CPU's own emulated m68k cycle count at a configurable rate, instead of host wall-clock time, so compiler A/B benchmarks under volamos are reproducible and independent of host load. - Cpu trait gains emulated_cycles()/clock_hz() with non-breaking defaults (0 / None). - M68kCpu::set_clock_mhz(Some(mhz)) switches Cpu::run from run_batch to m68k::CpuCore::run_for_cycles, accumulating real cycle counts; the run_batch path is untouched when the flag is absent. - read_eclock_handler derives ev_hi/ev_lo from cycles/clock_hz when active, but always reports ECLOCK_PAL_HZ in D0 -- real hardware derives the E-Clock from chipset timing, not the CPU clock. GetSysTime/TR_GETSYSTIME/DateStamp/CurrentTime are untouched. - --clock-mhz errors cleanly when combined with an explicit --jit (the trace JIT never tracks a cycle count); a default-off --jit does not spuriously trip this, since jit only becomes true via an explicit override. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EHvk3UiKvgnG2xb71UJ67R
…102) Code review fixes on the --clock-mhz branch: 1. Hard-error --clock-mhz + --sanitize (check_clock_mhz_sanitize): the cycle-counted run_via_cycles path never calls the sanitizer's per-instruction hooks, so combining the two would leave the shadow call stack/below-SP tracking silently stale while --sanitize still reports a normal-looking summary. Refused the same way as --jit, checked second so --jit's conflict still reports first when both apply. 2. Re-measured the perf claim: today's real numbers (CoreMark 1.0, --cpu 68020, host wall-clock throughput, three-run average) are ~217 iter/sec for --no-jit, ~680 for --jit, ~97 for --clock-mhz 25 -- --clock-mhz is ~2.2x slower than --no-jit and ~7x slower than --jit, not the stale/wrong "~5x slower than --no-jit" previously quoted in three places (that figure was actually --jit-vs-interpreter, and pre-dated a FlatMemory read speedup that also sped up the precise-bus path). 3. Fixed cpu.rs's emulated_cycles doc: only Cpu::run accumulates cycles, never Cpu::step -- guest code reached via RawDoFmt's PutChProc callback or Supervisor's single-stepped routine (execfmt.rs) costs zero emulated cycles, same class of invisibility as native library handlers. Added the same caveat to CLI-Reference.md. 4. Documented the nested System()/Execute() cycle-counter-resets-to-zero caveat in CLI-Reference.md (was previously only a source comment). 5. Added MIN_CLOCK_MHZ (1 Hz) to parse_clock_mhz: --clock-mhz 1e-30 drove ReadEClock's tick count to f64::INFINITY, saturating to u64::MAX on the cast -- harmless, but silently wrong. 6. check_clock_mhz_jit's error message now names --no-jit/the config file instead of "drop --jit" when jit came from a config file's JIT=true rather than an explicit CLI flag (JitSource, threaded through resolve()). The "incidental pre-existing bug" reported in the previous commit is retracted per review (reproduced by an accidental jsr-displacement typo in the test fixture, not a real runtime bug) and not filed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EHvk3UiKvgnG2xb71UJ67R
sidick
added a commit
that referenced
this pull request
Sep 29, 2026
As merged in #103, volamos's cycle counter was only reachable indirectly: the guest itself had to call ReadEClock and report its own elapsed time. That works for an instrumented benchmark like BenchWork, but leaves an uninstrumented binary unmeasurable -- while `vamos -v` has printed an equivalent "total cycles:" line for its own runs all along (amitools/vamos/main.py, summing Musashi's per-call cycle returns). Adds Runtime::emulated_cycles(), reporting the top-level run's count and the rate it was counted at, and prints one line from it at exit when --clock-mhz is active. Nothing is printed otherwise, because on the default run_batch path no cycle count exists to print. The line goes to stderr, so a harness parsing the guest's own stdout never sees it, and is printed even when the run ends in an error -- a benchmark that died part-way still burned the cycles it burned. Cross-checked against vamos on the same binary: 340,020 cycles vs vamos's 339,044 at 68000 (+0.3%), and 157,436 vs 155,855 at 68020 (+1.0%) -- two independent cycle models (the m68k crate's timing tables vs Musashi's) agreeing closely. Claude-Session: https://claude.ai/code/session_01EHvk3UiKvgnG2xb71UJ67R Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
sidick
added a commit
that referenced
this pull request
Oct 10, 2026
* Bump version to 0.9.0 Closes out the 0.9 changelog section: the ten commits merged since v0.8 (#100) were landing under a stale "## 0.8" header without a version bump, so this also backfills changelog entries that were missing entirely -- the --clock-mhz base feature and its two follow-on reports (#102/#103, #104, #107), both math-library condition-code fixes (#112, #113), HUNK_RELRELOC32 loader support (#116), and the dispatch task-struct-corruption diagnostic -- alongside the two that already had entries (clap migration #108, native-handler counts #110). Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * docs: fix two stale cross-page anchor links mkdocs slugifies a heading like "## \`--sanitize\`" to #-sanitize, not #--sanitize -- the backtick-wrapped flag name collapses the doubled dash to one. Found via mkdocs build --strict's anchor-mismatch INFO output while checking 0.9's docs were current. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #102.
Adds
--clock-mhz N, which makesReadEClockreport emulated time derivedfrom the CPU's emulated cycle count at the given clock rate, instead of host
wall-clock time. The motivation (from @codewiz in #102) is compiler A/B
benchmarking: host time measures how fast the host ran the emulation, not what
the guest would have cost, and the distortion is per-workload — 7x to 27x
across BenchWork — so it doesn't cancel out. With this flag the numbers become
reproducible and independent of host load.
How it works
Cpu::runforM68kCpunormally goes throughm68k::CpuCore::run_batch,whose
BatchResultcarries onlyinstructions— there is no cycle count onthat path at all. When
--clock-mhzis given, the run loop instead usesrun_for_cycles, which returns a realcyclestotal. That is thetransaction-exact path (
set_precise_bus(true), one instruction at a time, notrace JIT, no
fast_mem), so it is deliberately the slowest mode volamos has.ReadEClock's reported rate inD0staysECLOCK_PAL_HZ(709379)regardless of
--clock-mhz. Real Amigas derive the E-clock from chipsettiming, not the CPU clock, so the flag governs only the cycles-to-time
conversion feeding
ev_hi/ev_lo.GetSysTime/TR_GETSYSTIME/DateStamp/CurrentTimeall stay on the host clock — a benchmark run shouldn't thinkit's 1978.
The change is strictly additive: with the flag absent,
run()takes theexisting
run_batchpath unchanged andread_eclock_handlerruns theidentical host-clock arithmetic as before.
Refused combinations
Both are hard errors rather than warnings, because in each case the result
would be silently meaningless rather than merely degraded:
--jit—run_batchnever tracks a cycle count, so there is nothing toderive emulated time from. Not approximate: absent.
--sanitize—run_via_cyclesdoesn't run the per-instructionshadow-map hooks, so the shadow call stack, return-address checks, below-SP
tracking and violation-PC attribution would all be dead or stale. A
sanitizer that reports clean because its checks didn't run is worse than one
that refuses to start.
Measured cost
CoreMark 1.0 (
coremark.amiga,--cpu 68020), host throughput, 3 runsaveraged per mode:
--jit~680 iter/s,--no-jit~217,--clock-mhz 25~97.So about 2.2x slower than
--no-jitand 7x slower than--jit. Forreplacing a 20-minute Copperline A/B with one command that's a rounding error,
but it does mean this wants to stay opt-in for benchmarking rather than
something the testsuite picks up.
Documented caveats
userdocs/CLI-Reference.mdspells out four things that stay unrepresentative,because each could otherwise produce a confidently wrong number:
FlatMemoryimplements nosyncand no bustiming, while Copperline bills real Chip/Fast RAM contention. Memory-bound
code will improve enormously over host time but will not converge on real
hardware.
handlers costs zero emulated cycles, so a workload dominated by e.g.
CopyMemreports an emulated time implying it spent none there.Cpu::stepdoesn't accumulate. Guest code reached viastep—RawDoFmt'sPutChProccallouts,Supervisorroutines — is invisible inthe total.
System()/Execute()gets a fresh counter, so bracketing achild process reads as ~zero elapsed time.
Testing
cargo test --workspace: 1035 passed, 0 failed. Build, clippy(
--all-targets) andfmt --checkall clean.New tests cover the cycle accounting,
D0staying at the PAL rate under thenew mode, a two-reading delta that genuinely discriminates cycle-derived from
wall-clock-derived timing, the default path being unaffected, both refused
combinations in both orders, and flag parsing including fractional, zero,
negative, NaN, inf, and out-of-range values.
The cycles-to-ticks arithmetic was verified independently against a
hand-built fixture at three clock rates (25 / 12.5 / 50 MHz), producing exact
predicted tick values with correct inverse-linear scaling.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EHvk3UiKvgnG2xb71UJ67R