Skip to content

Shadowcache - #216

Merged
techomancer merged 8 commits into
mainfrom
shadowcache
Oct 13, 2026
Merged

techomancer merged 8 commits into
mainfrom
shadowcache

Conversation

@techomancer

Copy link
Copy Markdown
Owner

shadow cache and decode cache based on physical memory.
should boost jit on r4k and r5k and help jit -> int fallback performance.

techomancer and others added 8 commits October 12, 2026 11:45
…PACT JIT

The GUI, the release binaries and profile.sh now build with gpujit
(rex-jit + gr4-jit) instead of rex-jit alone, so IMPACT gets its raster JIT
out of the box; the docs recommend `--features lightning,gpujit`.

IRIS_NO_JIT, which the Mac App Store build sets because its sandbox kills
Cranelift's executable pages, now turns the IMPACT raster JIT off as it
already did the REX3 one: rss_jit::forbidden() makes the default mode off,
and `mgras jit on|sync` refuses.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fixes found running SGI's own IP22 IDE against the full cache model. On
R4400, IDE icache2 now passes.

- Index_Store_Tag(SD) indexed the secondary by the TagLo value instead of
  the CACHE address (R4000 UM A-42, VR5000 UM 3.4). Init stores TagLo = 0,
  so it only ever hit set 0, and a round-trip test masked it.
- Index_Load_Tag returns generated check bits: primary P (even parity) and
  the R4400 secondary tag ECC (UM Figure 16-4). Computed on read, nothing
  stored; the hardware ignores them on Index_Store_Tag.
- R5000 primary TagLo uses the R4400 PState encoding (I valid = 2, D
  clean = 2, dirty = 3), as IDE icachetag/set_tag and IRIX mcparity.c
  expect on every IP22 CPU. The D(7)/V(6) layout came from a critique and
  failed icachetag.
- Hit_Writeback(I) wrote nothing. It now writes the words the line was
  filled with back to L2 or memory. R4400 keeps a fill-time snapshot
  (ic_raw), since its fetch path serves the L2 decode slots; R5000's
  ic_instrs already is the copy. Only the I-cache miss path gains work.
- Snapshot restore rebuilds I-cache words. A restored valid R5000 line was
  served from empty slots (NOPs) until evicted.

Parity/ECC injection (IDE dcache1, icache1, scache1) is still missing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
With --features cache-ecc, every IP22 IDE cache test (icache1, icache2,
dcache1, scache1) passes on R4400 and R5000. Off by default: nothing but a
diagnostic ever observes check bits, and gated off the hooks compile away.

- Cache Error: EXEC_CACHE_ERR sets ErrorEPC and ERL, jumps to 0xA0000100 or
  0xBFC00300 (BEV), and leaves Cause/EPC/EXL/BadVAddr alone. CacheErr comes
  from the cache.
- Status.CE: a store takes the ECC register as the check bits of the bytes
  it writes, Fill(I) as the line's parity, and a D-cache write-back by a
  CACHE op XORs it into the L2 ECC of the addressed doubleword.
- Detection with DE and ERL clear: D-cache loads, primary refills from L2
  (EC), and Hit_Writeback(I), which faults from the CACHE instruction.
- Index_Load_Tag loads CP0 ECC with the selected doubleword's check bits;
  the L2 data matrix is R4000 UM Figure 16-1.
- Check bits are only stored when forced. A line that needs checking carries
  L1DTag::SLOW, so it fails the exact tag compare (interpreter and jitv2
  inline guard) and is resolved on the miss path: no hit-path cost.

Always on, not gated: Fill(I) on the 2-way R5000 refills the way already
holding the line (IDE pins lines with Index_Store_Tag first), which fixes
icache2 there.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
ShadowCache (memory is the only store, tags answer CACHE ops) now covers the
R4400 and R5000 as well as the R10000, chosen by MODEL as CpuCache is:
CACHE index ops pick lines as the full model does (R4400 direct-mapped,
R5000 way = VA[14], R10000 way = VA[0]), and R4400/R5000 tags read back
exactly as the full model's, through shared r4k_store_taglo/r4k_load_taglo.

`[machine] cache = auto|full|shadow` and `--cache` (also `iris-bench run
--cache`) pick it at machine build. auto is shadow in a jitv2 build, full
otherwise: jitv2 full-length iris-bench, shadow vs full, R4400 x1.33, R5000
x1.50 geomean, while the interpreter re-decodes every instruction against
the shadow and is about x0.55-0.6. The R10000 only has the shadow.

Snapshots record cpu_cache; restoring into the other model is refused.
Legacy manifests mean the old default.

cpu-tests: the shadow loses only cache/hit_inv_discards, as jitv2's tcache
already did. Two more monomorphised CPUs: +4.3% .text.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The shadow cache keeps no lines, so the interpreter re-decoded every
instruction and reached RAM through a BusDevice call for every fetch and
data access.

- decode-cache (src/cpu/decode_cache.rs): decoded instructions by physical
  page, used by ShadowCache::fetch only, never touching its tag arrays. Each
  fetch re-reads the word (and a fused pair's next word) and reuses the
  decode only if unchanged, so it is exactly coherent with memory. jitv2's
  page write generations were tried as the validator and measured no faster.
- The shadow reads and writes RAM through ppmem's window in every build: the
  window hand-over is no longer gated on tcache. Decoded pages keep a
  pointer to their window page; PROM, MMIO and the low alias have none and
  use the bus. A region-bitmap change (remap), power-on or restore starts the
  cache over.
- The fetch hit path is inlined, the fill and bus paths cold.
- The shadow now clears its tags on power-on.

Shadow vs full: interpreter x1.36 (R4400), x1.03 (R5000); jitv2 x1.52 on
both; accuracy identical. jitv2 implies decode-cache. auto stays: full
cache for the interpreter, shadow under jitv2.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
1431f56 grew fill_l1i_line (the ic_raw snapshot), which was inlined into
fetch, and fetch stopped inlining into step_int: every interpreted
instruction paid a call. Bare-metal iris-bench (lightning, R4400, full
cache) dropped to x0.84 of main, sys/uncached to x0.51.

fetch is the per-instruction hit path, so it is #[inline(always)];
fill_l1i_line is the miss path, so #[inline(never)] #[cold], and its size
can no longer decide the hot path. Back to x1.02 of main.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
It is a win wherever the shadow runs, interpreter and jitv2 alike, and costs
nothing elsewhere: it is only reached from ShadowCache::fetch. The
decode-cache feature is now a no-op kept for existing command lines, as
tlbvmap is, and jitv2 no longer lists it. ShadowCache's old per-fetch bus
read into a scratch slot is gone with the gate.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@techomancer
techomancer merged commit 5f347c0 into main Oct 13, 2026
7 of 8 checks passed
@techomancer
techomancer deleted the shadowcache branch October 13, 2026 02:57
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