mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 18:25:35 -04:00
phase1.0e-d: per-block guest cycle accounting in codegen
Codegen-side foundation for cycle-driven peripheral timing per
docs/CYCLE_TIMING_ARCH.md. Each emitted basic block now calls
psx_advance_cycles(N) where N is the conservative 1-cycle-per-MIPS-
instruction count for the block (sum from leader to next leader,
including delay slot).
Generic per-instruction emit, no card-specific paths. Macro-gated by
PSX_ENABLE_BLOCK_CYCLES so generated output is comparable on/off.
This slice does NOT wire psx_advance_cycles to peripheral advance.
Reason: sio_tick has a legacy IRQ-countdown body that decrements
unconditionally per call; calling it from psx_advance_cycles (which
fires per block, ~100M+ times/sec) drains that countdown in
microseconds and breaks pad timing. A future slice with a peripheral-
only sio_advance (no legacy side effect) will wire SIO. Card byte-15
abort persists for now — expected; the fix needs that peripheral wiring.
Files:
- recompiler/src/full_function_emitter.cpp:
- precompute block_cycles map (instr count between leaders)
- emit psx_advance_cycles(<N>u) under #ifdef PSX_ENABLE_BLOCK_CYCLES
at every block leader, before psx_check_interrupts
- emit extern decl in generated header
- generated/SCPH1001_full.c: regenerated; 12794 PSX_ENABLE_BLOCK_CYCLES
guards, one per block leader
- runtime/CMakeLists.txt: PSX_ENABLE_BLOCK_CYCLES=1 added to both
psx-runtime and psx-beetleoracle target_compile_definitions
- runtime/src/psx_cycles.c: psx_advance_cycles accumulates counter only;
peripheral dispatch deferred to next slice (rationale in comment)
Validation against 5d4d4bd baseline:
- Build clean both targets
- t=20s tx_writes=928, t=30s tx_writes=5000 (matches baseline)
- pad arm audit 5056/5056 armed, no_ack=0
- 4x CROSS press all succeed
- card txn distribution unchanged: txns reach READ_DATA, "MC" bytes
flow at txn 0 bytes 10-11 (0x4D 0x43)
- psx_cycle_count grows: 1.72B (t=20s), 2.33B (t=30s), 10.04B
post-CROSS — proves codegen emit live and per-block accounting works
- Macro=0 build (verified before macro flip): output identical to
baseline
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -265,6 +265,27 @@ bool FullFunctionEmitter::emit_function(
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1.0e-d: precompute per-block cycle estimates. For each block
|
||||
// leader L, count instructions from L up to (but not including) the
|
||||
// next leader address (or end of function). Conservative 1 cycle per
|
||||
// instruction; precise R3000A timing (load delays, multiply/divide,
|
||||
// memory stalls) is a future refinement. Includes terminator and
|
||||
// delay slot in the block before the next leader. */
|
||||
std::map<uint32_t, uint32_t> block_cycles;
|
||||
for (auto it = addr_to_raw.begin(); it != addr_to_raw.end(); ++it) {
|
||||
uint32_t leader = it->first;
|
||||
if (!block_leaders.count(leader)) continue;
|
||||
uint32_t count = 0;
|
||||
auto walker = it;
|
||||
while (walker != addr_to_raw.end()) {
|
||||
count++;
|
||||
++walker;
|
||||
if (walker == addr_to_raw.end()) break;
|
||||
if (block_leaders.count(walker->first)) break;
|
||||
}
|
||||
block_cycles[leader] = count;
|
||||
}
|
||||
|
||||
for (auto it = addr_to_raw.begin(); it != addr_to_raw.end(); ++it) {
|
||||
uint32_t addr = it->first;
|
||||
uint32_t raw = it->second;
|
||||
@@ -274,6 +295,16 @@ bool FullFunctionEmitter::emit_function(
|
||||
// can service vblank and other hardware interrupts.
|
||||
if (block_leaders.count(addr)) {
|
||||
out += fmt::format("label_{:08X}:\n", addr);
|
||||
// Phase 1.0e-d: advance guest cycles for this block. Macro-
|
||||
// gated; when off, generated code matches pre-1.0e-d output.
|
||||
uint32_t bcyc = 0;
|
||||
auto bcit = block_cycles.find(addr);
|
||||
if (bcit != block_cycles.end()) bcyc = bcit->second;
|
||||
if (bcyc > 0) {
|
||||
out += "#ifdef PSX_ENABLE_BLOCK_CYCLES\n";
|
||||
out += fmt::format(" psx_advance_cycles({}u);\n", bcyc);
|
||||
out += "#endif\n";
|
||||
}
|
||||
out += " psx_check_interrupts(cpu);\n";
|
||||
}
|
||||
|
||||
@@ -975,7 +1006,13 @@ EmitStats FullFunctionEmitter::emit(
|
||||
full_c += "extern void gte_write_data(CPUState* cpu, uint8_t reg, uint32_t val);\n";
|
||||
full_c += "extern uint32_t gte_read_data(CPUState* cpu, uint8_t reg);\n";
|
||||
full_c += "extern void debug_server_log_call_entry(uint32_t func_addr);\n";
|
||||
full_c += "extern uint32_t g_debug_last_store_pc;\n\n";
|
||||
full_c += "extern uint32_t g_debug_last_store_pc;\n";
|
||||
full_c += "/* Phase 1.0e-d: per-block guest cycle accounting.\n";
|
||||
full_c += " * Compile generated code with -DPSX_ENABLE_BLOCK_CYCLES=1 to\n";
|
||||
full_c += " * activate cycle advancement at every block leader. */\n";
|
||||
full_c += "#ifdef PSX_ENABLE_BLOCK_CYCLES\n";
|
||||
full_c += "extern void psx_advance_cycles(uint32_t cycles);\n";
|
||||
full_c += "#endif\n\n";
|
||||
|
||||
// Forward declare ALL functions so intra-file calls resolve.
|
||||
for (const auto& fn : dr.functions) {
|
||||
|
||||
@@ -43,6 +43,7 @@ target_include_directories(psx-runtime PRIVATE include)
|
||||
target_compile_definitions(psx-runtime PRIVATE
|
||||
PSX_NATIVE_BUILD=1
|
||||
DEFAULT_DEBUG_PORT=4370
|
||||
PSX_ENABLE_BLOCK_CYCLES=1
|
||||
$<$<CXX_COMPILER_ID:MSVC>:SDL_MAIN_HANDLED>
|
||||
)
|
||||
|
||||
@@ -168,6 +169,7 @@ if(NOT MSVC AND EXISTS "${BEETLE_LIB}")
|
||||
PSX_NATIVE_BUILD=1
|
||||
ENABLE_BEETLE_PSX_ORACLE=1
|
||||
DEFAULT_DEBUG_PORT=4370
|
||||
PSX_ENABLE_BLOCK_CYCLES=1
|
||||
)
|
||||
|
||||
target_link_libraries(psx-beetleoracle PRIVATE
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/* psx_cycles.c — PSX guest CPU cycle clock.
|
||||
*
|
||||
* Phase 1.0e-a: scaffold only. psx_advance_cycles updates the counter
|
||||
* but invokes no peripheral hooks. 1.0e-b will dispatch to
|
||||
* sio_advance(cycles); 1.0e-c will add gpu_advance for VBlank; later
|
||||
* slices add timers/CDROM. */
|
||||
* Phase 1.0e-d minimal slice: receives per-block cycle counts from
|
||||
* generated code (when compiled with -DPSX_ENABLE_BLOCK_CYCLES=1) and
|
||||
* accumulates them in psx_cycle_count. NO peripheral hooks — wiring
|
||||
* sio_tick here would corrupt the legacy IRQ countdown (it decrements
|
||||
* unconditionally per call; millions of calls per second drain it in
|
||||
* microseconds). A future slice with a peripheral-only sio_advance
|
||||
* (no legacy countdown side effect) will wire SIO. */
|
||||
|
||||
#include "psx_cycles.h"
|
||||
|
||||
@@ -12,8 +15,7 @@ uint64_t psx_cycle_count = 0;
|
||||
void psx_advance_cycles(uint32_t cycles) {
|
||||
if (cycles == 0) return;
|
||||
psx_cycle_count += (uint64_t)cycles;
|
||||
/* Phase 1.0e-a: peripheral hooks empty.
|
||||
* 1.0e-b will call: sio_advance(cycles), gpu_advance(cycles), etc. */
|
||||
/* No peripheral dispatch in this slice — see comment above. */
|
||||
}
|
||||
|
||||
uint64_t psx_get_cycle_count(void) {
|
||||
|
||||
Reference in New Issue
Block a user