Skip to content

Measure Dreamcast frame rate for the first time, and plan for full speed - #1

Open
awest813 wants to merge 75 commits into
GPF:dreamcastfrom
awest813:claude/dc-performance-baseline
Open

awest813 wants to merge 75 commits into
GPF:dreamcastfrom
awest813:claude/dc-performance-baseline

Conversation

@awest813

Copy link
Copy Markdown

What this is

The port had never had a frame rate recorded. The Dreamcast build reports timing through SDL_WM_SetCaption, which does nothing on target, so every performance discussion so far has been hypothesis. This PR records a baseline, audits where the time goes, and orders the work to fix it.

Baseline

Flycast 2.7, stock 16 MB, 200 MHz. The Dreamcast build presents exactly one flip per emulated GBA frame, so the host emulator's frame counter reads GBA speed directly.

Workload fps Share of the 59.73 target
Homebrew title, fully resident 53.9 90%
Super Puzzle Fighter II, mean over 65 s 23.2 39%
Super Puzzle Fighter II, worst sample 8.5 14%

The homebrew control carries the argument. The disc, video and flip path sustain near-full speed, so the deficit on commercial code is recompiler throughput rather than presentation.

Flycast's SH-4 timing is approximate rather than cycle-exact, so these are first-order estimates of Dreamcast speed, not hardware measurements. Real GD-ROM seek latency is not reproduced at all, so large-ROM paging cost does not appear here.

The audit

DC_PERFORMANCE_PLAN_2026-09-10.md records thirteen findings and a five-phase plan. Emission costs were counted, not estimated: dc/sh4_emit.h compiles on the host against the shim tests/sh4_emit_encoding_test.c already uses, and each emitter macro was run against a counting buffer.

Translated instruction SH-4 instructions emitted
ARM ADD r0,r1,r2 15
Hand-written equivalent 4

Leading findings:

  • F1 The entire used region of the translation cache is flushed on every newly translated block, so the cost grows as the cache fills. Cheapest large win, and not previously tracked.
  • F2 Every data-processing instruction compiles to a call to a one-line C helper, costing twelve instructions of address arithmetic before the branch.
  • F3 32-bit constants are built a byte at a time, 9.0 instructions on average, against one for a PC-relative literal pool load.
  • F4 Constant shifts emit one instruction per bit, up to 31.
  • F5 The flag slots sit just past the load/store displacement window, so every flag access costs three instructions instead of one.
  • F12 Automatic frameskip is the default but exists only in the PSP build, so the Dreamcast never drops a frame to catch up. Not previously tracked.
  • F13 Grand Theft Auto Advance boots to its title sequence and then stops with a recompiler bad-jump fatal error.

The plan supersedes roadmap rows B8 and B9.

Known defect, not fixed here

Grand Theft Auto Advance (16 MB) halts with bad jump 1a3019f8 (3000154) (0). An indirect branch produced a target outside any valid GBA region while executing in internal RAM. Two smaller titles do not reproduce it, and one of those is also demand-paged from disc, so ROM paging alone does not explain it. This is a correctness defect rather than a performance one, but it blocks benchmarking the most demanding title available. Isolating it needs the register dump that Phase 0 instrumentation would add.

Reviewer notes

This PR carries two separate bodies of work, in two commits.

  1. a0ab5b6 is the dynarec audit 4 work that was already sitting uncommitted in the worktree: the ADC/SBC/RSC flag fixes, register shift masking, masked CPSR writes, exception-return alignment, plus the new host test coverage and the save/build hardening. It is committed here so the measurement work has a reviewable base and so the plan document's links to DYNAREC_AUDIT_2026-09-10.md resolve. Review it on its own terms; it was not authored in the session that produced the second commit.
  2. bda07fd is this session's work: the plan document and the recorded measurements. Documentation only.

Testing. The Dreamcast cross-build was verified with the CI container. The host suite was not run locally, because no host compiler is available in the KOS container and Git Bash has none either; CI runs make -C tests test on Ubuntu and should be treated as the gate for commit 1. The second commit changes only Markdown.

Reproducing the run. scripts/smoke-test-results.md and section 4 of the plan carry the disc recipe. Three details each cost a failed boot to find: the boot binary must be scrambled, the ISO session offset must be 11702 to match where cdi4dc places the data track, and gba_bios.bin goes at the disc root rather than under gbaDC/. mkdcdisc, which dc/dc.sh expects, is not in the CI container; scramble, mkisofs and cdi4dc are.

🤖 Generated with Claude Code

cursoragent and others added 30 commits June 8, 2026 13:27
Add HIGH_IMPACT_FIXES.md with a phased plan for Dreamcast port improvements.
Restore blit_to_screen using the SH-4 Store Queue path on Dreamcast and a
simple pixel-copy fallback on other non-PSP builds, fixing savestate preview
thumbnails in the menu.

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Port x86-style dynarec helpers and instruction macros to the SH-4 backend.
Add sh4_helpers.c for ALU, shift, load, CPSR/SPSR, and SWI helpers.
Expand sh4_emit.h with SH-4 code generation, conditional branches, idle-loop
elimination, and cycle accounting. Complete sh4_stub.c with SMC-aware stores
and execute_arm_translate entry. Enable dynarec on Dreamcast in main.c.

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Phase 1 polish:
- Pass PC into sh4_update_gba via generate_update_pc_reg on SH-4
- Add generate_update_pc_reg to MIPS emit for cpu_threaded compatibility
- Fix execute_swi return type; remove noisy Dreamcast config debug prints

Phase 2 interpreter fixes (cpu.c):
- Apply LDM/STM writeback after the transfer loop so STM preserves the
  original base value when the base register is in the list
- Skip load writeback when the base is in the list (ARM7TDMI behavior)
- Honor the s_bit (^) flag by accessing user bank R8-R14 in privileged modes

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Phase 1 audit fixes:
- Fix reg[16+] load/store, branch patching, and cycle reload after sh4_update_gba
- Tail-jump from sh4_update_gba on PC change; pass instruction PC to execute_store_*
- Add shift-reg macros, cmp/str compares, Thumb PC branch mov, swi_hle_div fix

Phase 3:
- Unify 32KB gamepak swap pages on Dreamcast (was 8KB count vs 32KB I/O)
- DC ROM buffer tries 16/12/8/4MB with malloc fallback
- Add host tests for div remainder and page math

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
- Add README.md for the Dreamcast port (build, disc layout, controls)
- Rename COPYING.DOC to LICENSE (GPL v2)
- Remove outdated PSP-focused readme.txt
- Point build.txt at README.md

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Squashes phased PR work into a single changeset on dreamcast.

Phase 0: Restore blit_to_screen for savestate/menu thumbnails (SQ + fallback).
Phase 1: SH-4 dynarec — helpers, emit, instr macros, SMC-aware stores, idle loops.
Phase 2: LDM/STM interpreter fixes (writeback timing, user bank ^ suffix).
Phase 3: Unified 32KB gamepak paging; DC ROM buffer 16/12/8/4MB fallback.
Docs: README.md, LICENSE (GPLv2), HIGH_IMPACT_FIXES.md roadmap.
Tests: host-side phase1_helpers_test.

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Co-authored-by: nerdretro <awest813@users.noreply.github.com>
- Route startup/DMA/SDL debug prints through gpsp_debug_printf (silent
  unless -DGPSP_DEBUG); keep user-facing BIOS and ROM load errors
- Fix Gameshark v3 I/O opcode extraction; add ROM patch, button-gated,
  and master-hook cheat handling
- Call check_for_interrupts() after CPSR mode changes in sh4_helpers
- Add phase4_cheats_test; update HIGH_IMPACT_FIXES.md and README

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
execute_store_cpsr now returns the IRQ vector when unmasking interrupts,
and dynarec emission branches to it via arm_psr_store_cpsr_post().
execute_spsr_restore uses the same sh4_take_pending_irq helper so MOVS PC
correctly redirects through 0x18 instead of leaving IRQ state half-applied.

Adds host tests for the IRQ decision logic and source contracts.

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
- Gameshark v1 IF codes (0xD/0xE) with EQ/NE/LE/GE and range skip counts
- PAR v3 conditionals on 00000000 control lines and 0x08-0x38 prefixes
- Dynarec emits process_cheats at cheat_master_hook; flushes caches on change
- x86 execute_store_cpsr returns IRQ vector with dynarec branch (matches SH-4)
- Host tests and docs updated

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Cheat conditionals, dynarec hooks, SH-4/x86 CPSR IRQ fixes
Replace the partial PAR3 conditional parser with SkyEmu's stack-based
gba_run_ar_cheat model: nested IF/ELSE/ENDIF, ROM patches, fill codes,
button-gated writes, and full-width IF comparisons.

Adds THIRD_PARTY_NOTICES.md with MIT attribution.

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
- PAR v3 slowdown (00000000 0800xx00) re-runs cheat list xx times per cycle
- DEADFACE lines reseed Gameshark/PAR decryption during cheat file load
- Track up to eight master hooks; dynarec uses cheat_pc_is_hook()
- Add mGBA MPL attribution for reseed tables; extend host tests and docs

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Cheat conditionals, dynarec hooks, CPSR IRQ dispatch, PAR gaps
…3-2878c28d-7ac1-40c2-9a37-508949c51376

Audit and fix core compilation issues for non-Dreamcast platforms
* Phase 5 user readiness audit for Dreamcast release

- Show on-screen fatal errors for missing BIOS, ROM buffer failure, and
  gamepak load errors on Dreamcast (path, MD5, wait for Start)
- Guard against NULL gamepak_rom after init_gamepak_buffer on DC
- Fix command-line ROM load error to report argv[1]
- Correct README savestate filenames to .0.svs through .9.svs
- Gate host SDL F2 palette dump behind GPSP_DEBUG
- Add phase5_user_readiness_test contract checks

Co-authored-by: nerdretro <awest813@users.noreply.github.com>

* Ignore phase5_user_readiness_test binary in tests/.gitignore

Co-authored-by: nerdretro <awest813@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: nerdretro <awest813@users.noreply.github.com>
- Add GPSPDC_VERSION (0.9.1-dc) and show it on the empty-ROM menu splash
- Route in-menu ROM load failures through gpsp_gamepak_load_error on DC
- Document hardware/Flycast smoke tests in HARDWARE_SMOKE_TEST.md
- Add GitHub Actions workflow to run make -C tests test
- Add phase6_release_verification_test contract checks
- Update HIGH_IMPACT_FIXES.md and README with Phase 6 roadmap entry

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: nerdretro <awest813@users.noreply.github.com>
* Add Dreamcast stable build CI and build contracts

Cross-compile gdC.elf in GitHub Actions using the KOS Docker toolchain,
add a romdisk placeholder for fresh checkouts, and document a local
docker build helper. Host contract tests verify source paths and CI.

Co-authored-by: nerdretro <awest813@users.noreply.github.com>

* Fix Dreamcast CI to use docker run with KOS entrypoint

Co-authored-by: nerdretro <awest813@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: nerdretro <awest813@users.noreply.github.com>
cursoragent and others added 30 commits June 9, 2026 17:13
P0 correctness:
- Remove duplicate bios_rom/bios_read_protect definitions in memory.c
- Add dc/romdisk/.gitkeep for KOS genromfs on fresh checkouts

Large ROM / RAM (from PR #19):
- Smaller DC translation caches in cpu.h (~1.25 MB saved for ROM buffer)
- Adjacent-page prefetch in gamepak paging

Input / menus (includes PR #20):
- DC gamepad_config_map honored in gameplay; Y opens pause menu
- Fix joystick A/B/X/Y bit mapping; default X -> GBA B
- Fix RAPIDFIRE_R handling on PSP and DC paths
- Menu save on close, ROM browser hints, grey out unavailable actions

Other polish:
- gpsp_video_init_error for SDL/video init failures on DC
- Cheats load via relative path after fs_chdir
- DC-specific gamepad help and GD-ROM backup wording
- README large-ROM section; gitignore CDI build artifacts
- Extended phase3/phase8 contract tests

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Co-authored-by: nerdretro <awest813@users.noreply.github.com>
- Cap backup/save reads and bound gamepak open paths; validate GD-ROM
  fread via file_read_ok; zero-fill failed ROM pages
- Guard ZIP entry names; ROM browser malloc/realloc failures; copy_screen
  NULL checks; refuse emulation without a loaded ROM
- Serialize savestate load/save and direct sound mixing with sound_mutex;
  lock reset_sound buffer clears
- Repair flash_bank_ptr after savestate load; iterative reload (no recurse)
- Cap dynarec external exits and translation redo loops; fatal on NULL
  translation target in sh4_stub
- Add phase9_stability_test contract suite

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Resolve conflicts in README, smoke-test-results, and phase3 tests.
Includes romdisk placeholder, bios_rom dedup, joystick mapping,
video init fatal, rapidfire R, and cheats path fixes.

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
Resolve conflicts in memory.c paging (keep file_read_ok guards),
README, and smoke-test-results. All phases 1-9 tests pass.

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
ARM LDM/STM with PC in the register list delegated to
execute_arm_block_memory() but the emitted epilogue still branched via
stale rv instead of the loaded PC. Load REG_PC and use dual indirect
branch so Thumb targets work via block_lookup_address_dual.

Match raise_interrupt() in sh4_take_pending_irq: set bios_read_protect,
wake from halt, and mark CHANGED_PC_STATUS when unmasking a pending IRQ
from CPSR/SPSR restore paths.

Store the full loaded PC value (including Thumb bit) in the block memory
helper to match the interpreter.

Co-authored-by: nerdretro <awest813@users.noreply.github.com>
The Dreamcast dynarec re-entered translated blocks with ordinary C calls
(`((void (*)(void))target)()`) from sh4_update_gba, the indirect-branch
helpers, and the SMC/alert store paths. Translated blocks never return, so
every IRQ dispatch, halt wake-up, SMC flush, and indirect branch leaked a
stack frame. The SH-4 stack grew without bound until it overflowed and
corrupted memory mid-game.

Re-enter translated code the way the x86 backend does: capture the stack
base in execute_arm_translate and reset r15 to it on every (re-)entry via a
single noreturn sh4_dispatch_block() trampoline that also reloads r12 (reg
base) and r13 (cycle counter). sh4_update_gba, the indirect-branch helpers,
and the store helpers now tail-dispatch through it instead of nesting C
frames, and the live cycle counter is threaded explicitly:

- indirect-branch helpers take (address, cycles); emitter passes r13 in r5
- store helpers take (address, value, pc, cycles); emitter passes r13 in r7
- sh4_lookup_pc(cycles) replaces the leaking parameterless helper

Also fix block linking: external/long branches were patched to a 12-bit
`bra` (+/-4 KB) and forced a full retranslation (return -1) whenever the
target was farther, which could livelock on large caches. Block exits now
reserve a full absolute-jump slot (SH4_EMIT_LONG_BRANCH_FILLER); near
targets are rewritten to bra/nop and far targets fill the slot's literal,
restoring the conventional record-at-emit / patch-at-link structure.

Flush the operand cache before invalidating the instruction cache so freshly
emitted code is written back to RAM before refetch (dcache_flush_range +
icache_flush_range), and gate the SWI trace emission behind
GPSP_DC_RUNTIME_TRACE.

Tests: extend sh4_emit_encoding_test with near/far long-branch patch checks
and update sh4_integration_contract_test for the new dispatch discipline.
All host test suites pass.

https://claude.ai/code/session_01W5CticHkv7cGVuDxV9t1CT
Two correctness bugs found while auditing the SH-4 backend.

1. Inverted IRQ branch on CPSR store (severe). arm_psr_store_cpsr_post
   emitted `tst r0; bf _skip_irq`, which branches past the IRQ dispatch when
   r0 != 0 -- the exact case where execute_store_cpsr returns a vector and the
   IRQ must be taken. Worse, on the common path (r0 == 0, no pending IRQ) it
   fell through and dispatched with r4 = 0, branching into a null/BIOS block on
   essentially every MSR that writes the control byte. The x86 backend skips
   when the result is zero (`jz`); the SH-4 skip must therefore trigger on
   T == 1 (r0 == 0). Use COND_SKIP_T.

2. Conditional skip limited to +/-254 bytes. The ARM per-condition block
   header skips a whole run of same-condition instructions with a single 8-bit
   bt/bf; generate_branch_patch_conditional truncated the displacement to 8
   bits, so any run whose body exceeded ~254 bytes was patched to a bogus
   target. Replace the lone bt/bf with SH4_EMIT_COND_SKIP_T/F: a short bt/bf
   that hops over a 12-bit bra (reach +/-4 KB), and patch the bra. The same
   primitive now backs the cycle-budget skip and the IRQ skip, so the patch
   path is unified (generate_branch_patch_conditional == bra patch).

Polarity of the existing condition fillers (verified correct against the EQ
path and the working cycle-budget skip) is preserved exactly.

Tests: update the filler-polarity encoding test for the new tst/bf/bra/nop
shape, add near and far conditional-skip patch checks, and add contract
assertions locking in the COND_SKIP_T IRQ skip. All host suites pass.

https://claude.ai/code/session_01W5CticHkv7cGVuDxV9t1CT
Three interpreter-parity bugs in the long-multiply and block-store paths.

1. UMLAL/SMLAL accumulator high word passed in the wrong register.
   execute_mul_long_regs(rm, rs, acc_lo, acc_hi) takes its arguments in
   r4/r5/r6/r7 per the SH-4 ABI, but arm_multiply_long_add_yes loaded rdhi
   into s0 (r14), leaving r7 garbage: every long multiply-accumulate
   corrupted the high word. Add the missing sh4_reg_a3 (r7) alias and load
   rdhi into a3.

2. SMLAL used an unsigned product. Both generate_multiply_s64_add and
   generate_multiply_u64_add called the same helper, which computed
   (u64)rm * (u64)rs. The interpreter (and ARM) use the signed 64-bit
   product for SMLAL. Split the helper into execute_mul_long_regs_s64
   (signed product) and _u64 (unsigned); the accumulate is bit-identical
   in u64 math.

3. STM with PC in the register list stored insn_pc + 4. The interpreter
   advances pc by 4 (arm_pc_offset) and stores pc + 4, i.e. insn_pc + 8,
   and the x86 backend stores pc + 8 via generate_load_reg_pc(a1, 15, 8).
   Store insn_pc + 8 for parity.

Also audited this pass, no change needed: shift-by-register uses the full
32-bit Rs in both interpreter and helpers (shared gpSP divergence from
hardware's bottom-byte rule); SWP alignment/rotation matches the
interpreter's read_memory32 path; halt processing is correctly confined to
the store-alert loop (x86 write_epilogue parity); the stale
CHANGED_PC_STATUS left by direct CPSR/SPSR IRQ dispatch costs only one
redundant block lookup at the next update_gba and matches the documented
raise_interrupt behavior.

Contract tests lock in the a3 load, the signed product, and the pc+8
store. All host suites pass.

https://claude.ai/code/session_01W5CticHkv7cGVuDxV9t1CT
The dreamcast-build job was a false green. dc/gdC.elf was committed (and only
later gitignored), so `test -f dc/gdC.elf` always passed against a stale
binary, while ./scripts/dc-build.sh ran `make` with no target -- which
resolves to the KOS `subdirs` goal from Makefile.rules ("Nothing to be done
for 'subdirs'") and builds nothing. The SH-4 cross-compile had therefore
never actually run in CI.

- dc-build.sh now defaults to the real `all` target when invoked with no
  arguments (overridable, e.g. `dc-build.sh clean`).
- Remove the committed dc/gdC.elf (already in .gitignore) so the verify step
  is meaningful, and add a CI step to delete any stale ELF before building.
- Contract tests assert the build script targets `all` and CI removes the
  stale ELF, so the false-green cannot silently return.

https://claude.ai/code/session_01W5CticHkv7cGVuDxV9t1CT
Fix SH-4 dynarec: stack leak, branch range, IRQ polarity, and multiply/STM parity
…ed roadmap

Audit pass over dc/sh4_stub.c, dc/sh4_helpers.c, dc/sh4_emit.h, and
dc/sh4_instr.inc against the in-tree MIPS stub and x86 backend:

- Pin sh4_dispatch_block operands to r0-r3 so the register allocator can
  never place an input in r12/r13/r15, which the asm overwrites before
  the jump.
- Store helpers dispatch CPU_ALERT_IRQ straight to sh4_lookup_pc with the
  live cycle counter (MIPS irq_alert parity) instead of running the
  update_gba halt loop, which billed the rest of the current video/timer
  period early on every DMA-completion IRQ.
- collapse_flags() before slow-path write_memory* so raise_interrupt()
  snapshots current NZCV into SPSR_irq; handlers no longer return with
  stale flags after DMA IRQs.
- ARM conditional headers estimate the same-condition run size from scan
  data and emit a bt/bf hop over an absolute-jump slot when the 12-bit
  bra might not reach; the conditional patch dispatches on slot shape and
  fails loudly via gpsp_dynarec_fatal_error instead of silently emitting
  a truncated bra.
- MUL/MULL results flow through r0 and the reg[REG_SAVE]/[REG_SAVE2]
  mailbox instead of an unsound 'leave it in r4/r5' inline-asm contract.
- Drop dead duplicate macros in sh4_emit.h that sh4_instr.inc redefines;
  skip cycle-update emission when the balance is zero.
- x86 host backend: fix arm_psr_store_finish shadowing (MSR CPSR called
  execute_store_spsr) and set bios_read_protect in x86_take_pending_irq.
- CI workflows also trigger on claude/** branches.
- Tests: far-skip shape/polarity/patch coverage, conditional-header
  run-length selection (expands the real macro), range backstop,
  cycle-update guard, and updated stub/helper/emit contracts (64 checks).
- Docs: new ROADMAP.md unifies all remaining work from the phase docs,
  smoke-test checklists, validation track, and audit findings;
  HIGH_IMPACT_FIXES.md records this pass as Phase 11.

https://claude.ai/code/session_01GHrZB3u4SVBVsadogo6kgG
- autoload.txt now points to the bundled license-clean DangerousXmas.bin
  instead of the missing Tekken Advance ROM, so a clean-checkout CDI boots
  straight into a working game (smoke-test #5) rather than failing on
  first boot.
- Add scripts/flycast-test.md: build steps (KOS ELF -> dc.sh CDI),
  known-good Flycast config, and expected result per smoke-test row.
- smoke-test-results.md: pre-fill the known-good Flycast environment block
  and link the runbook (ROADMAP A1 / E3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCEpbuNCctwevnBDC3c1vn
A clean checkout could build a CDI whose autoload.txt named a ROM not in
the tree, so the disc failed on first boot. Guard the whole class:

- scripts/check-disc.sh: validate BIOS presence/size/MD5, game_config, and
  that any autoload ROM actually exists in dc/cd/gbaDC/.
- dc/dc.sh: run the preflight (after config sync) so a broken disc cannot
  be packaged into a CDI.
- tests/dc_build_contract_test.c: new disc-layout contract fails CI if the
  committed autoload ROM is missing or dc.sh drops the preflight.
- flycast-test.md: document running the preflight before the cross-compile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCEpbuNCctwevnBDC3c1vn
The three 06-12 debug CDIs (gbapspDC-fresh / -trace-dynarec /
-trace-interpreter) plus gbapspDC.cdi were tracked at ~243 MB total and
predate the autoload fix and recent dynarec work, so they were stale and
misleading for testing. CDIs are regenerable build outputs, so:

- git rm --cached the four CDIs (kept on local disk, recoverable from history)
- .gitignore: add dc/*.cdi so future build outputs stay untracked

Disc-build inputs (gba_bios.bin, DangerousXmas.bin, autoload.txt,
game_config.txt) remain tracked. Rebuild a fresh CDI with dc/dc.sh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCEpbuNCctwevnBDC3c1vn
Add Flycast smoke test runbook and disc preflight validation
When translated EWRAM code spanned more than one 32KB page,
flush_translation_cache_ram() cleared neither the first page's tag tail
nor the last page's tag head, and cleared page 0's tag range instead.
Tag numbers are reused from 0x0101 after a flush, so the surviving
stale tags dispatched into the reset translation cache (another block's
code, or garbage). Present since the original import.

Clear the tag range of every page the code span touches, mirroring the
single-page branch's semantics, and add phase 9 contract checks for the
min-page-tail and max-page-head memsets. Also drop a stray backslash
line that spliced into the following #define after arm_set_condition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012H1eeCBT72M4zLj8LVADwf
dc/cd/gba_bios.bin (the copyrighted Nintendo BIOS) was tracked in git
even though the README lists it as a user-supplied file that is not
included in the repository. Untrack it (.gitignore's *.bin already
covers it; the local copy stays for dc/dc.sh disc builds), and flip the
disc-layout contract from requiring the blob to exist to asserting it
is NOT tracked, so it cannot silently come back. The build-time guards
are unchanged: dc.sh requires the file and the preflight verifies its
MD5.

Note: the blob remains in git history.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012H1eeCBT72M4zLj8LVADwf
cpu_threaded.c had drifted to the SH-4 backend's contracts and the x86
host debugging build no longer compiled:

- translation_ptr_t was only defined by dc/sh4_emit.h; define it (u8 *)
  in the x86 backend too.
- The shared ARM LDM/STM call sites use the 4-argument
  arm_block_memory(access, pre, post, s_bit) shape, while x86_emit.h
  still had the old 5-argument macro whose preadjust variants (up/down/
  full) did not even match the call sites (down_a/down_b). Route LDM/STM
  through an execute_arm_block_memory C helper with the same semantics
  as dc/sh4_helpers.c and the audited interpreter: transfers before
  writeback, writeback suppressed for LDM with the base in the list,
  S-bit user-bank transfers, and STM of the PC storing pc + 8.
- generate_update_pc_reg (force_pc_update_target sync) and set_cpu_mode
  were undeclared; both compiled as implicit declarations.

All C objects now build on a 64-bit host. x86_stub.S still needs a
32-bit toolchain to assemble and link (tracked as ROADMAP D2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012H1eeCBT72M4zLj8LVADwf
README claimed a 16/12/8/4 MB resident-buffer ladder; the code (and the
phase 3 contract test) use a single conservative 4 MB allocation with
demand paging beyond it. Record the audit 4 findings in
HIGH_IMPACT_FIXES.md and update ROADMAP D2 with the remaining 32-bit
toolchain gap for the x86 host build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012H1eeCBT72M4zLj8LVADwf
Dynarec audit 4: EWRAM flush fix, BIOS untracking, x86 host build repair
Carries the work that was already sitting uncommitted in the worktree, so
the measurement work that follows has a reviewable base.

SH-4 helper correctness (see DYNAREC_AUDIT_2026-09-10.md):
- ADC/SBC/RSC capture the incoming carry before touching flags, account for
  equality when computing carry/borrow, and derive overflow from operand and
  result sign bits.
- Register-specified shifts mask to the low byte; rotate counts are
  normalised so multiples of 32 preserve the value and set carry from bit 31.
- Masked CPSR writes collapse the live flag slots into CPSR first, so a
  control-bits-only write no longer drops live N/Z/C/V, and unmasking an IRQ
  snapshots the right flags into SPSR_irq.
- Exception returns align the return PC by the restored SPSR.T before IRQ
  handling, so an ARM target with bit 0 set cannot reach the dual dispatcher
  as a Thumb target.

Emission: immediate materialisation skips leading zero bytes at translation
time, shortening small constants without adding literal pools or scratch
registers.

Tests: adds sh4_helpers_behavior_test, which compiles the production helpers
against a minimal KOS shim and checks results and all NZCV flags across
boundary operands and 10,000 deterministic pairs. Adds an emitted-code
simulator covering immediate loads and conditional branch polarity. Adds
save/config fault-injection coverage in save_io_test.py.

Also restores x86 host backend C compilation against the shared translator,
stops distributing the GBA BIOS, and documents the resident-buffer behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The port had never had a frame rate recorded, because the Dreamcast build
reports timing through SDL_WM_SetCaption, which does nothing on target.

Baseline (Flycast 2.7, stock 16 MB, 200 MHz). The Dreamcast build presents
exactly one flip per emulated GBA frame, so the host emulator's frame counter
reads GBA speed directly:

  homebrew title, fully resident   53.9 fps   90% of the 59.73 target
  Super Puzzle Fighter II, 65s     23.2 fps   39%
  Super Puzzle Fighter II, worst    8.5 fps   14%

The homebrew control carries the argument: the disc, video and flip path
sustain near-full speed, so the deficit on commercial code is recompiler
throughput rather than presentation.

DC_PERFORMANCE_PLAN_2026-09-10.md records thirteen findings with measured
costs and a five-phase plan. Emission costs were counted by compiling
dc/sh4_emit.h on the host against the existing test shim and counting the
instructions each emitter macro writes. Highlights:

- The entire used region of the translation cache is flushed on every newly
  translated block, so the cost grows as the cache fills. Not previously
  tracked.
- Every data-processing instruction compiles to a call to a one-line C
  helper; an ARM add emits 15 SH-4 instructions where 4 would do.
- 32-bit constants are built a byte at a time, 9.0 instructions on average,
  against 1 for a PC-relative literal pool load.
- Constant shifts emit one instruction per bit, up to 31.
- The flag slots sit just past the load/store displacement window, so every
  flag access costs 3 instructions instead of 1.
- Automatic frameskip is the default but is implemented only in the PSP
  build, so the Dreamcast never drops a frame to catch up. Not previously
  tracked.
- Grand Theft Auto Advance boots to its title sequence and then stops with a
  recompiler bad-jump fatal error. Correctness, not performance, but it
  blocks benchmarking the largest available title.

The plan supersedes roadmap rows B8 and B9 and orders the work so the
cheapest large wins land first. Frame-rate samples, the games tested, and the
disc-build details needed to reproduce any of this are recorded in
scripts/smoke-test-results.md.

Flycast's SH-4 timing is approximate rather than cycle-exact, so these are
first-order estimates of Dreamcast speed, not hardware measurements.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

4 participants