From 0c1f005c35574359bcf5697ac3ddc8295a0bf984 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Mon, 15 Jun 2026 19:36:43 -0700 Subject: [PATCH] feat(widescreen,sljit): shared cull helper across backends, cache versioning, sljit default-on Three coupled landings on top of the sljit merge: 1. Shared widescreen render-cull helper. auto_screen_x now emits a call to one runtime helper psx_ws_cull_sltiu(sx,imm) (gpu.c) instead of an inline expr, and the sljit JIT (overlay_sljit.c) + dirty-RAM interpreter (dirty_ram_interp.c) route flagged render-funnel sltiu 0x140/0x141 through the SAME helper. So an overlay's GTE render funnel widens identically whether it runs as a gcc DLL, a sljit shard, or interpreted. Helper is byte-identical to vanilla at 4:3 (margin 0), so widescreen-off stays bit-exact. psx_ws_func_has_screen_cull gates the widen to real render funnels (sltiu 0x140 + sltiu 0xE0 signature). 2. Versioned overlay cache path. gcc cache moves to /gcc//cg/ where N = PSX_OVERLAY_CODEGEN_VER (overlay_api.h), parsed identically by the runtime (overlay_loader.c) and compile_overlays.py so they can't drift. A build whose emitter output changed (e.g. this auto_screen_x change) reads+writes a FRESH dir and never reuses a stale DLL; old versions coexist (no auto-delete). Pre-1.0: legacy flat/unversioned fallbacks dropped. 3. sljit default-on (validated). gcc and sljit are complementary, not exclusive: gcc fills the cache via prebuilt DLLs + async batch (compile_fragment NULL), sljit JITs gcc-absent regions synchronously on-miss. New code_provider_sljit() exposes sljit for the JIT-on-miss gap-fill independent of the "active" backend; the dispatch gate fires whenever sljit is available (head<0 == gcc-absent), and apply_live_policy defaults live ON when sljit is available. Priority gcc > sljit > interp; shards still route through the same-state diff gate (device-touch + diverging shards pinned to interp) so default-on does NOT reintroduce the save-load wedge; a declined/failing shard falls to interp (never a crash). Verified live: live=1, 62 shards compiled, 2 declines->interp, native:interp ratio ~31%->~87%, gcc still primary. Co-Authored-By: Claude Opus 4.8 (1M context) --- recompiler/src/code_generator.cpp | 17 +++++------- runtime/include/code_provider.h | 13 ++++++++-- runtime/include/gpu.h | 10 +++++++ runtime/include/overlay_api.h | 13 ++++++++++ runtime/src/code_provider.c | 6 +++++ runtime/src/dirty_ram_interp.c | 34 +++++++++++++++++++++++- runtime/src/gpu.c | 31 ++++++++++++++++++++++ runtime/src/overlay_loader.c | 43 ++++++++++++++++++++----------- runtime/src/overlay_sljit.c | 41 ++++++++++++++++++++++++++--- tools/compile_overlays.py | 33 +++++++++++++++--------- 10 files changed, 198 insertions(+), 43 deletions(-) diff --git a/recompiler/src/code_generator.cpp b/recompiler/src/code_generator.cpp index a87e3255..085466d0 100644 --- a/recompiler/src/code_generator.cpp +++ b/recompiler/src/code_generator.cpp @@ -730,16 +730,12 @@ std::string CodeGenerator::translate_instruction(uint32_t addr, uint32_t instr) uint16_t uimm = get_imm16_u(instr); if (uimm == 0x140 || uimm == 0x141) { uint32_t rs = get_rs(instr), rt = get_rt(instr); - // SX is a 16-bit GTE screen coord loaded via lhu, so an off-LEFT - // vertex is a large unsigned value (e.g. -30 -> 0xFFE2). A plain - // `SX sljit > interp). NULL => sljit unavailable, gaps fall to interp. */ +const CodeProvider *code_provider_sljit(void); + #ifdef __cplusplus } #endif diff --git a/runtime/include/gpu.h b/runtime/include/gpu.h index dee285d5..1a806d84 100644 --- a/runtime/include/gpu.h +++ b/runtime/include/gpu.h @@ -111,6 +111,16 @@ int gpu_ws_present_native_43(void); * cull immediates by the recompiler ([widescreen.cull]); 0 unless stretching. */ int psx_ws_x_margin(void); +/* Shared render-funnel screen-X cull widening ([widescreen.cull] auto_screen_x): + * the gcc emit, the sljit JIT, and the interpreter all route a flagged + * `sltiu rt, sx, 0x140/0x141` through this one helper so every overlay execution + * path widens identically. Returns the sltiu verdict (1 = keep). 0 at 4:3. */ +int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm); +/* True if a run of instruction words carries the screen-extent reject signature + * (a sltiu 0x140/0x141 AND a sltiu 0xE0/0xF1). Used by sljit/interp to gate the + * widening to real render funnels. */ +int psx_ws_func_has_screen_cull(const uint32_t *words, int n); + /* Backdrop screen-X correction ([widescreen.backdrop] x_sites). The parallax * 2D backdrop layer computes screen-X without the GTE, so it misses the * widescreen squash; the recompiler emits this on each backdrop handler's diff --git a/runtime/include/overlay_api.h b/runtime/include/overlay_api.h index 849f7a95..dc7f0a1c 100644 --- a/runtime/include/overlay_api.h +++ b/runtime/include/overlay_api.h @@ -48,6 +48,19 @@ #define PSX_OVERLAY_ABI_TAG \ ((int)((PSX_OVERLAY_ABI_VERSION & 0xFFFF) | ((PSX_OVERLAY_FLAVOR & 0xFFFF) << 16))) +/* Codegen version — bumped whenever the recompiler emits DIFFERENT bytes for the + * same guest overlay WITHOUT an ABI change (the load-time overlay_abi() gate only + * catches ABI/flavor changes; a pure-emitter change produces a different DLL the + * gate would happily accept). The cache is namespaced by this version + * (gcc//cg/), so a build with new codegen writes + reads a FRESH + * directory and never reuses a stale DLL; old versions coexist on disk (no + * auto-delete — a user who downgrades still finds their matching cache). Python + * (compile_overlays.py) parses this same constant from this header, so the two + * sides can never drift. BUMP THIS when you change code_generator.cpp emit in a + * way that alters overlay output (e.g. the auto_screen_x widescreen widening). + * 1: baseline + auto_screen_x render-funnel cull via psx_ws_cull_sltiu. */ +#define PSX_OVERLAY_CODEGEN_VER 1 + typedef struct { /* Core dispatch: routes call_by_address() and out-of-overlay jal */ void (*dispatch_call)(CPUState *cpu, uint32_t addr, uint32_t ra); diff --git a/runtime/src/code_provider.c b/runtime/src/code_provider.c index 7705b3fb..f851fd50 100644 --- a/runtime/src/code_provider.c +++ b/runtime/src/code_provider.c @@ -70,3 +70,9 @@ void code_provider_init(const char *cfg_backend, int gcc_configured) { } const CodeProvider *code_provider_active(void) { return s_active; } + +/* sljit provider for the synchronous JIT-on-miss gap-fill, regardless of which + * provider is "active". NULL when sljit isn't available so callers fall to interp. */ +const CodeProvider *code_provider_sljit(void) { + return (s_sljit.available && s_sljit.available()) ? &s_sljit : NULL; +} diff --git a/runtime/src/dirty_ram_interp.c b/runtime/src/dirty_ram_interp.c index 9b30beab..b899c917 100644 --- a/runtime/src/dirty_ram_interp.c +++ b/runtime/src/dirty_ram_interp.c @@ -147,6 +147,31 @@ static inline uint32_t fetch_word(uint32_t phys) { | ((uint32_t)ram[phys + 3] << 24); } +/* Widescreen render-funnel cull detection for the interpreter ([widescreen.cull] + * auto_screen_x). A `sltiu …,0x140/0x141` is widened ONLY when its enclosing + * render function also carries a `sltiu …,0xE0/0xF1` (the GTE per-vertex trivial- + * reject signature) — a lone 0x140 elsewhere stays vanilla. The interp has no + * function boundaries, so we scan a +/-512-byte window around the site (clamped + * to main RAM) via the shared psx_ws_func_has_screen_cull, cached per-PC so a + * hot render loop pays the scan once. Single-threaded interp => static buffers ok. */ +static int ws_cull_site(uint32_t pc) { + enum { WIN = 128 }; /* +/- 128 words = +/- 512 bytes */ + static struct { uint32_t pc; int8_t flag; } cache[256]; + uint32_t slot = (pc >> 2) & 255u; + if (cache[slot].pc == pc) return cache[slot].flag; + uint32_t phys = pc & 0x1FFFFFFFu; + uint32_t lo = (phys > (uint32_t)(WIN * 4)) ? phys - (uint32_t)(WIN * 4) : 0u; + uint32_t hi = phys + (uint32_t)(WIN * 4); + if (hi > 0x200000u) hi = 0x200000u; /* 2 MB main RAM */ + static uint32_t words[2 * WIN + 1]; + int n = 0; + for (uint32_t a = lo; a + 4u <= hi && n < (int)(2 * WIN + 1); a += 4u) + words[n++] = fetch_word(a); + int flag = psx_ws_func_has_screen_cull(words, n); + cache[slot].pc = pc; cache[slot].flag = (int8_t)flag; + return flag; +} + static void dirty_ram_log_instruction(CPUState *cpu, uint32_t pc, uint32_t insn, uint32_t before_s0, uint32_t next_pc, uint32_t target, int transferred) { @@ -684,7 +709,14 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) { cpu->gpr[0] = 0; return 0; case 0x0B: /* SLTIU */ - cpu->gpr[rt] = (cpu->gpr[rs] < (uint32_t)simm) ? 1u : 0u; + /* Widescreen render-funnel cull widening (auto_screen_x): always apply + * the shared helper for a flagged render-cull site — it is byte-identical + * to the vanilla compare at 4:3 (margin 0) and widens at 16:9, so the one + * code path serves both aspects (no widescreen-specific caching). */ + if ((imm == 0x140 || imm == 0x141) && ws_cull_site(pc)) + cpu->gpr[rt] = (uint32_t)psx_ws_cull_sltiu(cpu->gpr[rs], imm); + else + cpu->gpr[rt] = (cpu->gpr[rs] < (uint32_t)simm) ? 1u : 0u; cpu->gpr[0] = 0; return 0; case 0x0C: /* ANDI */ diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index df3a64cc..dd9af896 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -182,6 +182,37 @@ int psx_ws_x_margin(void) { return (160 * (ws_xden - ws_xnum) + ws_xnum / 2) / ws_xnum; } +/* Shared render-funnel screen-X cull widening ([widescreen.cull] auto_screen_x), + * called identically by the gcc emit, the sljit JIT, and the interpreter so every + * overlay execution path widens the same way. sx = the GTE screen-X as loaded by + * the guest's lhu (low 16 bits significant); imm = the original bound (0x140 / + * 0x141). Returns the sltiu verdict (1 = on-screen/keep). Sign-extends sx and + * shifts by +margin so BOTH 16:9 margins pass; at 4:3 margin==0 so it reduces + * bit-for-bit to the vanilla `(uint16)sx < imm`. */ +int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm) { + int m = psx_ws_x_margin(); + return ((uint32_t)((int32_t)(int16_t)(uint16_t)sx + m) + < (uint32_t)((int32_t)imm + 2 * m)) ? 1 : 0; +} + +/* Detect the GTE per-vertex trivial-reject signature in a run of instruction + * words: at least one `sltiu …,0x140/0x141` (width) AND one `sltiu …,0xE0/0xF1` + * (height). Lets the sljit JIT + interpreter gate the cull-widening to real + * render funnels (a lone sltiu 0x140 elsewhere must stay vanilla). Mirrors the + * recompiler's func_has_screen_extent_cull. */ +int psx_ws_func_has_screen_cull(const uint32_t *words, int n) { + int has_x = 0, has_y = 0; + for (int i = 0; i < n; i++) { + uint32_t w = words[i]; + if ((w & 0xFC000000u) != 0x2C000000u) continue; /* sltiu */ + uint32_t imm = w & 0xFFFFu; + if (imm == 0x140 || imm == 0x141) has_x = 1; + else if (imm == 0xE0 || imm == 0xF1) has_y = 1; + if (has_x && has_y) return 1; + } + return 0; +} + /* Widescreen backdrop screen-X correction ([widescreen.backdrop] x_sites). * The parallax 2D backdrop layer (ocean/cloud/mountain/grass — overlay actor * handlers e.g. 0x801216BC) computes its screen-X in pure integer math diff --git a/runtime/src/overlay_loader.c b/runtime/src/overlay_loader.c index 6f003360..34eaea17 100644 --- a/runtime/src/overlay_loader.c +++ b/runtime/src/overlay_loader.c @@ -379,8 +379,11 @@ static void sljit_mark_tried(uint32_t phys) { * hook. Decodes from live RAM exactly as the interpreter does. */ static void try_sljit_region(uint32_t addr) { uint32_t phys = addr & 0x1FFFFFFFu; - const CodeProvider *cp = code_provider_active(); - if (!cp->compile_fragment) return; + /* Use the sljit provider specifically (not the active one): gcc is the + * primary/batch provider with compile_fragment == NULL, so the synchronous + * JIT-on-miss gap-fill must go through sljit even when gcc is "active". */ + const CodeProvider *cp = code_provider_sljit(); + if (!cp || !cp->compile_fragment) return; if (sljit_already_tried(phys)) return; extern int dirty_ram_is_dirty(uint32_t phys); if (!dirty_ram_is_dirty(phys)) return; /* only JIT real runtime code */ @@ -490,17 +493,16 @@ static void scan_one_cache_dir(const char *dir) { #endif } -/* Scan the namespaced gcc cache (gcc//) AND the legacy flat layout - * (/*.dll) for back-compat — existing caches keep loading with no - * migration. The sljit// namespace is reserved (no on-disk blobs in - * v1: sljit re-JITs from the coverage manifest; see SLJIT.md §5.3). */ +/* Scan the namespaced gcc cache: gcc//cg/. The codegen + * version segment means a build with new emitter output reads a FRESH directory + * and never picks up a stale DLL (old versions coexist on disk, no migration). + * The sljit// namespace is reserved (no on-disk blobs: sljit re-JITs + * from the coverage manifest; see SLJIT.md §5.3). (Pre-1.0: no legacy fallback — + * older flat / unversioned caches are simply ignored and regenerated.) */ static void scan_cache_dir(void) { char dir[768]; - snprintf(dir, sizeof(dir), "%s/%s/gcc/%s", - s_cache_dir, s_game_id, PSX_OVERLAY_ARCH_ABI); - scan_one_cache_dir(dir); - /* Legacy flat layout (pre-namespacing). */ - snprintf(dir, sizeof(dir), "%s/%s", s_cache_dir, s_game_id); + snprintf(dir, sizeof(dir), "%s/%s/gcc/%s/cg%d", + s_cache_dir, s_game_id, PSX_OVERLAY_ARCH_ABI, PSX_OVERLAY_CODEGEN_VER); scan_one_cache_dir(dir); } @@ -717,13 +719,19 @@ void overlay_loader_init(const char *cache_dir, const char *game_id) { * forces on) — a dev escape hatch. NOTE: "live" here is VALIDATED-live, not the * old blind path; see overlay_loader_dispatch's diff gate. */ void overlay_loader_apply_live_policy(void) { - int live = (overlay_backend_active() == OVERLAY_BACKEND_SLJIT) ? 1 : 0; + /* DEFAULT-ON (validated): sljit is live whenever it is AVAILABLE, so it fills + * gcc-absent overlay regions even on a gcc dev box (gcc > sljit > interp). + * "Live" is VALIDATED-live — shards still route through the same-state diff + * gate (see overlay_loader_dispatch) and device-touching / diverging shards + * stay on the interpreter, so default-on cannot reintroduce the save-load + * wedge. The PSX_OVERLAY_SLJIT_LIVE env var still overrides (0 forces off). */ + int live = (code_provider_sljit() != NULL) ? 1 : 0; const char *e = getenv("PSX_OVERLAY_SLJIT_LIVE"); if (e && *e) live = (*e != '0') ? 1 : 0; s_sljit_live = live; - loader_log("sljit live policy: backend=%s env=%s -> live=%d", + loader_log("sljit live policy: active_backend=%s sljit_avail=%d env=%s -> live=%d", overlay_backend_name(overlay_backend_active()), - e && *e ? e : "(unset)", live); + code_provider_sljit() != NULL, e && *e ? e : "(unset)", live); } void overlay_loader_check_cache(uint32_t load_addr, uint32_t size, @@ -844,7 +852,12 @@ int overlay_loader_dispatch(CPUState *cpu, uint32_t addr) { * production model — shards created and run live, no per-shard diff). * Never inside a shadow run. */ if (head < 0 && (s_diff_mode || s_sljit_live) && !s_in_shadow && - overlay_backend_active() == OVERLAY_BACKEND_SLJIT) { + code_provider_sljit() != NULL) { + /* head < 0 == no gcc DLL (or any candidate) registered for this region + * after the cache-load attempt above => gcc-absent => sljit fills the + * gap (priority gcc > sljit > interp). Fires whenever sljit is + * AVAILABLE, not only when it is the exclusive backend, so sljit and + * gcc run complementarily. */ try_sljit_region(addr); /* virtual entry (carries KSEG bits) */ head = idx_head(phys); } diff --git a/runtime/src/overlay_sljit.c b/runtime/src/overlay_sljit.c index f72e427a..b2817af6 100644 --- a/runtime/src/overlay_sljit.c +++ b/runtime/src/overlay_sljit.c @@ -171,6 +171,12 @@ typedef struct { int reg; int dirty; int pinned; uint32_t lru; } GprSlot; static GprSlot s_gpr[GPR_SLOTS_MAX]; static int s_gpr_slots; /* active slots this compile (<= GPR_SLOTS_MAX)*/ static uint32_t s_gpr_lru; +/* Set per-fragment (PASS 1) when this fragment carries the GTE render-funnel + * screen-extent reject signature (sltiu 0x140/0x141 + sltiu 0xE0/0xF1). When + * set, SLTIU 0x140/0x141 is routed through psx_ws_cull_sltiu so a sljit-JIT'd + * overlay widens identically to the gcc cache + interp (widescreen FOV). */ +static int s_frag_ws_cull; +extern int psx_ws_cull_sltiu(uint32_t sx, uint32_t imm); /* gpu.c — shared widen */ static inline sljit_s32 slot_reg(int k) { return SLJIT_S(k + 2); } /* S2.. */ @@ -345,6 +351,23 @@ static void emit_helper2(struct sljit_compiler *C, void *fn, uint32_t insn) { gpr_reset(); } +/* Emit `rt = psx_ws_cull_sltiu(gpr[rs], imm)` for a flagged auto_screen_x cull + * site, so a sljit-JIT'd overlay widens the render-funnel screen-X reject the + * same way the gcc backend does. The helper reads only its two args (+ the + * runtime widescreen margin); it does NOT touch cpu->gpr[], so the GPR cache + * survives — same icall discipline as emit_load (materialise the source into a + * scratch before the call, write the result into rt after). */ +static void emit_ws_cull(struct sljit_compiler *C, uint32_t rt, uint32_t rs, uint32_t imm) { + sljit_s32 a; sljit_sw aw; + gpr_src(C, rs, &a, &aw); + sljit_emit_op1(C, SLJIT_MOV32, SLJIT_R0, 0, a, aw); /* sx -> R0 (arg0) */ + sljit_emit_op1(C, SLJIT_MOV32, SLJIT_R1, 0, SLJIT_IMM, (sljit_sw)imm); /* imm -> R1 (arg1) */ + sljit_emit_op1(C, SLJIT_MOV, SLJIT_R2, 0, SLJIT_IMM, (sljit_sw)(uintptr_t)psx_ws_cull_sltiu); + sljit_emit_icall(C, SLJIT_CALL, SLJIT_ARGS2(32, 32, 32), SLJIT_R2, 0); /* R0 = verdict */ + sljit_emit_op1(C, SLJIT_MOV32, gpr_dst(C, rt), 0, SLJIT_R0, 0); + gpr_unpin(); +} + enum { EMIT_OK = 0, EMIT_TERM = 1, EMIT_ABORT = 2 }; /* Emit ONE non-control instruction. Returns EMIT_OK, or EMIT_TERM iff `insn` @@ -507,7 +530,10 @@ static int emit_one(struct sljit_compiler *C, uint32_t insn) { emit_slt(C, SLJIT_SUB32 | SLJIT_SET_SIG_LESS, SLJIT_SIG_LESS, rt, rs, 0, 0, simm); return EMIT_OK; case 0x0B: /* SLTIU (unsigned, simm sign-extended then compared unsigned) */ - emit_slt(C, SLJIT_SUB32 | SLJIT_SET_LESS, SLJIT_LESS, rt, rs, 0, 0, simm); + if (s_frag_ws_cull && (imm == 0x140 || imm == 0x141)) + emit_ws_cull(C, rt, rs, imm); /* widescreen render-funnel cull widen (auto_screen_x) */ + else + emit_slt(C, SLJIT_SUB32 | SLJIT_SET_LESS, SLJIT_LESS, rt, rs, 0, 0, simm); return EMIT_OK; case 0x0C: case 0x0D: case 0x0E: { /* ANDI / ORI / XORI (zero-extended imm) */ sljit_s32 a, d; sljit_sw aw; @@ -742,8 +768,17 @@ void overlay_sljit_try_compile(uint32_t entry, * see gpr_ref_mask). distinct >= the max regs in any single instruction, so * gpr_alloc always finds a slot. */ uint32_t used_mask = 0; - for (uint32_t i = 0; i < frag_words; i++) - used_mask |= gpr_ref_mask(img_word(bytes, off0 + i * 4u)); + int ws_hx = 0, ws_hy = 0; /* render-funnel screen-cull signature (auto_screen_x) */ + for (uint32_t i = 0; i < frag_words; i++) { + uint32_t w = img_word(bytes, off0 + i * 4u); + used_mask |= gpr_ref_mask(w); + if ((w & 0xFC000000u) == 0x2C000000u) { /* sltiu */ + uint32_t im = w & 0xFFFFu; + if (im == 0x140 || im == 0x141) ws_hx = 1; + else if (im == 0xE0 || im == 0xF1) ws_hy = 1; + } + } + s_frag_ws_cull = ws_hx && ws_hy; int distinct = 0; for (uint32_t m = used_mask; m; m &= m - 1u) distinct++; s_gpr_slots = (distinct < GPR_SLOTS_MAX) ? distinct : GPR_SLOTS_MAX; diff --git a/tools/compile_overlays.py b/tools/compile_overlays.py index 1d640ecc..ba91d5a2 100644 --- a/tools/compile_overlays.py +++ b/tools/compile_overlays.py @@ -42,6 +42,20 @@ except ImportError: import json import platform +import re + + +def codegen_ver(runtime_include: str) -> int: + """Parse PSX_OVERLAY_CODEGEN_VER from overlay_api.h so the cache path version + is the SAME value the runtime (overlay_loader.c) uses — the two can't drift. + The cache is namespaced gcc//cg/, so a build with new emitter + output reads+writes a FRESH dir and never reuses a stale DLL.""" + hdr = os.path.join(runtime_include, 'overlay_api.h') + with open(hdr) as f: + m = re.search(r'#define\s+PSX_OVERLAY_CODEGEN_VER\s+(\d+)', f.read()) + if not m: + raise SystemExit(f'PSX_OVERLAY_CODEGEN_VER not found in {hdr}') + return int(m.group(1)) def cache_arch_abi() -> str: @@ -1126,13 +1140,14 @@ def main(): return os.makedirs(args.out_dir, exist_ok=True) else: - # Namespaced gcc cache (SLJIT.md §4 — no comingling). Legacy flat layout - # (//) is still READ by the loader and for prior-ranges - # coverage merge below, so existing caches keep working with no migration. - legacy_cache_dir = os.path.join(args.out_dir, game_id) - cache_dir = os.path.join(legacy_cache_dir, 'gcc', cache_arch_abi()) + # Namespaced + versioned gcc cache: /gcc//cg/ + # (SLJIT.md §4 — no comingling; cg = codegen version so a new emitter + # build never reuses a stale DLL, old versions coexist). MUST match + # overlay_loader.c scan_cache_dir(). Pre-1.0: no legacy fallback. + cg = codegen_ver(args.runtime_include) + cache_dir = os.path.join(args.out_dir, game_id, 'gcc', cache_arch_abi(), f'cg{cg}') os.makedirs(cache_dir, exist_ok=True) - print(f'Cache dir: {cache_dir}') + print(f'Cache dir: {cache_dir} (codegen ver {cg})') with open(args.captures) as f: captures = json.load(f) @@ -1160,12 +1175,6 @@ def main(): if not args.static: ranges_name = f'{phys_addr:08X}_{crc32:08X}.ranges' prior_ranges = os.path.join(cache_dir, ranges_name) - # Fall back to the legacy flat layout so coverage from pre-namespacing - # builds still merges forward (don't regress a fresh build). - if not os.path.exists(prior_ranges): - legacy_prior = os.path.join(legacy_cache_dir, ranges_name) - if os.path.exists(legacy_prior): - prior_ranges = legacy_prior if os.path.exists(prior_ranges): prior_entries = [] with open(prior_ranges) as pf: