Repository navigation
vCPU Windows XMM Register Budget
Lifecycle: Performance - Unproven · Type: Backend optimization
State: Source-backed; runtime value is not established.
Status: Source-validated backend optimization candidate; not implemented or benchmarked.
Mechanism class: Register-allocation and JIT-boundary policy — not a shortcut.
Reviewed source:
322986f4c5502b3fa887a9605ac8cd88e9b2c579Primary file:
tcg/i386/tcg-target.c.inc.
The proposal does not bypass a maintained general emulation route under a narrow assumption. It expands the host register resources available to the normal TCG allocator by paying save/restore cost at the C-to-JIT boundary.
It belongs in the Performance Optimization Catalog, not the shortcut-only list.
The x86-64 allocation order exposes XMM0 through XMM5 on Windows. XMM5 is reserved as TCG_TMP_VEC. Initialization explicitly reserves XMM6 through XMM15 because they are ABI-preserved registers and the current JIT entry/exit path does not save them.
The practical normal allocation budget is therefore:
XMM0–XMM4: normal vector/FP allocation
XMM5: TCG temporary
XMM6–XMM15: reserved on Win64
The source contains an early comment calling XMM6–XMM15 caller-saved, but the later reservation comment describes the actual condition: they are call-saved/nonvolatile and xemu does not preserve them.
Save a deliberately small selected set at the C-to-JIT boundary, expose those registers to the allocator, and restore them on every exit:
flowchart TD
A[C code enters translated execution] --> B[Allocate enlarged JIT frame]
B --> C[Save selected nonvolatile XMM registers]
C --> D[Run chained translated blocks with larger register budget]
D --> E{Exit to C/main loop/exception?}
E --> F[Restore saved registers]
F --> G[Release frame and return]
Illustrative boundary:
sub rsp, enlarged_frame
movdqu [rsp + save_xmm6], xmm6
movdqu [rsp + save_xmm7], xmm7
; execute translated blocks
movdqu xmm6, [rsp + save_xmm6]
movdqu xmm7, [rsp + save_xmm7]
add rsp, enlarged_frame
retStart with XMM6/XMM7 or another small subset. Do not expose XMM6–XMM15 all at once before measuring marginal spill reduction.
Potential beneficiaries include:
- the hard-x87 path retaining multiple stack values;
- direct SSE arithmetic experiments;
- vectorized TCG operations;
- blocks with several simultaneously live scalar/vector temporaries;
- helper-adjacent code where nonvolatile low-128-bit values can survive C calls.
The trade is:
save/restore bytes and instructions per C-to-JIT entry/exit
versus
dynamic spill/reload traffic avoided inside translated execution
Long chained runs may benefit. Workloads that repeatedly leave translated execution may lose.
- XMM6–XMM15 low 128 bits are nonvolatile under Win64 and must be preserved for the C caller.
- Upper YMM/ZMM state has different volatility/ABI behavior; do not automatically allocate 256-bit values to newly exposed registers.
- Every normal and exceptional JIT exit must restore the saved state.
- Helper calls, signal/exception unwinding, long exits, and generated epilogues must agree on frame layout.
- Debug/unwind metadata must continue to describe the prologue/epilogue correctly where required.
-
TCG_TMP_VECremains reserved unless a separate design changes it. - Global call-clobber declarations must match what helpers and the platform ABI actually preserve.
| Area | Role |
|---|---|
tcg_target_reg_alloc_order[] |
Registers considered by normal allocation |
TCG_TMP_VEC |
Current XMM5 reservation |
tcg_target_init() reserved-register setup |
Reserves XMM6–XMM15 on Win64 |
tcg_target_qemu_prologue() and return paths |
C-to-JIT save/restore seam |
| TCG register allocator | Spill/reload decisions |
| JIT debug/unwind frame | Correct stack/register description |
Collect dynamic evidence by value type and block:
| Counter | Meaning |
|---|---|
| vector/FP temporaries simultaneously live | Actual pressure |
| spills and reloads by register class | Avoidable work |
| blocks with pressure above five XMM registers | Eligibility |
| JIT entries/exits | Boundary cost frequency |
| average/chained TBs per entry | Amortization opportunity |
| helper calls while values are live | Possible nonvolatile benefit |
| additional prologue/epilogue bytes and cycles | Candidate cost |
A source-level shortage of registers is not enough. If spills are rare, saving more registers on every entry loses.
- A Win64 C caller loads recognizable sentinel values into every candidate nonvolatile XMM register.
- Enter translated execution and exercise normal direct-chain exits.
- Exercise helper calls, interrupts, exceptions, single stepping, and early exits.
- Return to the caller and verify every sentinel exactly.
- Force high simultaneous FP/vector liveness and compare generated spills/reloads.
- Test low-pressure workloads to expose pure boundary overhead.
- Test 32-, 64-, and 128-bit values separately; reject unsupported wider values initially.
- Verify stack alignment, shadow space, unwind/debug data, and sanitizer behavior.
- Run Release ABBA/BAAB on CPU-heavy and retail workloads.
measured XMM spills/reloads are substantial
selected extra registers are frequently allocated
JIT entries are amortized across enough translated work
spill/reload reduction exceeds save/restore overhead
caller sentinel and all exit tests pass
- Treating the nonvolatile registers as free without saving them.
- Saving all ten registers before measuring marginal value.
- Ignoring upper YMM/ZMM ABI rules.
- Missing an exceptional or uncommon exit.
- Breaking unwind/debug frame information.
- Globally changing call-clobber behavior for values helpers do not preserve.
- Measuring a large generated block rather than dynamic spill frequency.
- Combining this immediately with native SSE retention, making attribution impossible.
- Add allocator spill/reload and liveness evidence.
- Select a two-register experiment if pressure justifies it.
- Implement and verify the complete JIT entry/exit save/restore contract.
- Run sentinel tests for all exit classes.
- Compare generated code and fixed-work CPU cost.
- Expand the register set only if each marginal addition remains beneficial.
- Keep this result separate from shortcut acceptance decisions.
- vCPU Shortcut Candidates
- Native SSE Arithmetic
- Helper-Aware x87 Cache Retention
- Performance Optimization Catalog
- CPU & vCPU Execution
Primary reference: This subject page is the design and research reference for Windows XMM Register Budget. Pull requests are implementation/test history only.
Lifecycle: Performance - Unproven · Mechanism: Win64 JIT register-allocation optimization
Reviewed source:322986f4c5502b3fa887a9605ac8cd88e9b2c579· Acceptance class: intended exact or behavior-preserving for its admitted scope
Save selected nonvolatile XMM registers at the C-to-JIT boundary so TCG can allocate more than XMM0-XMM4 during translated execution and potentially reduce spills.
This entry remains Unproven: the source path and opportunity are concrete, but no accepted xemu runtime result has established that the candidate improves representative workloads. The page defines what must be bypassed, what remains mandatory, and how the hypothesis can be rejected.
| File / function | Role |
|---|---|
tcg/i386/tcg-target.c.inc |
Win64 vector register allocation order/reserved set and JIT prologue/epilogue |
flowchart TD
N0["Enter JIT without saving XMM6-XMM15"]
N1["Reserve nonvolatile XMMs"]
N0 --> N1
N2["Allocate from XMM0-XMM4"]
N1 --> N2
N3["Spill/reload under pressure"]
N2 --> N3
N4["Exit JIT"]
N3 --> N4
flowchart TD
N0["Save selected XMM6+ once at JIT entry"]
N1["Expose them to allocator"]
N0 --> N1
N2["Run many TBs with larger register pool"]
N1 --> N2
N3["Restore saved registers at all exits"]
N2 --> N3
Windows x64 treats XMM6-XMM15 as nonvolatile. The current JIT avoids them because it does not preserve them, leaving five normal XMM registers after the temporary. Paying boundary saves may reduce dynamic spills when enough translated work occurs before exit. This is a backend optimization, not an assumption shortcut.
- Dynamic spills/reloads caused by a small vector/FP register pool
- ABI preservation
- Boundary save/restore traffic
- Call-clobber modeling
- Large-vector/YMM rules
- Reference register set
; Conceptual Win64 JIT entry
sub rsp, frame_size
movdqu [rsp + save_xmm6], xmm6
movdqu [rsp + save_xmm7], xmm7
; translated execution may allocate xmm6/xmm7
; every normal/exceptional exit restores them
movdqu xmm7, [rsp + save_xmm7]
movdqu xmm6, [rsp + save_xmm6]
add rsp, frame_size
retThe sketch defines the intended boundary; it is not a complete patch and does not replace source review, ownership design, or focused tests.
- All exits preserve Win64 nonvolatile registers.
- Allocator/call-clobber masks match the new availability.
- Save cost occurs at C/JIT boundary, not every TB edge.
- 256-bit upper-lane volatility and value widths are handled explicitly.
Saved register slots belong to each JIT entry frame/thread. Start with two added registers to bound cost. Exceptional exits, signals, helper returns and longjmp-like paths require the same restore contract.
The maintained reference path remains the fallback whenever admission, capability, lifetime, or validation is uncertain. A fast result with an unproven dependency is not acceptable as an exact optimization.
- May reduce spills in FP/vector-heavy TBs
- Can support later native SSE/x87 retention
- Clear ABI sentinel test
- Configurable small experiment
- Every JIT entry/exit pays save/restore
- Short runs can regress
- Backend and unwind complexity
- Spill pressure may not be significant
- Upper YMM semantics
Counters must be cheap, subsystem-owned, and snapshotted outside the hot loop. Record at least:
- JIT_entries/exits
- XMM_spills/reloads
- live_vector_values
- added_save_restore_bytes
- translated_cycles_per_entry
- host code size
- vCPU samples
A local reduction is not enough. Evidence must connect route reachability and removed work to fixed guest progress, CPU/GPU time where applicable, and p95/p99 behavior.
- ABI sentinel values in XMM6+ across all exits
- Different numbers of added registers
- Short vs long translated runs
- FP/vector pressure fixtures
- Helpers/exceptions/signals
- Spill/reload generated-code inspection
- Release games
Correctness runs may use validation layers, detailed comparisons, and fault injection. Performance acceptance must use optimized Release builds with matching symbols and minimal instrumentation.
Move to Researched when spill counts and boundary-cost experiments establish where the trade wins; implement only with complete ABI/exit coverage.
Reject a global expansion without preservation, or when entry/exit frequency makes save cost exceed dynamic spill savings.
CPU-and-vCPU-Execution · Performance-Unproven · Hot-Path-vCPU-Native-SSE-Arithmetic · Hot-Path-vCPU-Helper-Aware-x87-Cache-Retention
PR pages may be linked as supporting evidence, but they are not substitutes for this reference.
DSP tooling guide and navigation updated November 2, 2026 for experimental PR #384, source 8f51d237. Capture/replay usage and limits.
The earlier wiki-wide review was published November 1, 2026. Emulator source: b7140848. Reviewed wiki content: 1d2b671. Current status · Release availability · Publication process.
- Home
- Current status
- Stable versus main
- Implemented mechanisms
- Controls and defaults
- Measured results and limits
- Complete change register
- System
- CPU and vCPU
- Memory and DMA
- Graphics / NV2A
- APU and audio
- Timing and timers
- Platform and I/O
- Hardware observations
- Fidelity boundaries
- Methodology
- PGR2 routes
- Debugging lag / hot paths
- Tools and commands
- DSP capture, replay and fuzzing
- HLE readiness and public XISO proof
- Concurrent DSP shadow and OpenAL proof
- DSP HLE operation inventory
- DSP HLE fix examples and testing
- Development
- Upstream shortlist
- Wiki maintenance
- Research / retired work
- Optimization catalog
- Hot-path shortcuts
- Researched experiments
- Unproven proposal directory
- PR lessons
- September26 snapshot
Repository · PRs · Issues