Repository navigation
CPU and vCPU Execution
Reviewed: November 1, 2026 · Source:
b7140848
Release availability: Existing hard-FPU control is in v0.8.136-0-g458730bf5373 (September 30, 2026); #296 is main only at this review.
xemu models the Xbox CPU as a single Pentium III-compatible x86 guest CPU. The machine class permits one CPU, and the TCG accelerator executes that guest on a dedicated vCPU thread.
This page follows the current path from machine creation through translation-block execution and back out to interrupts, exceptions, I/O, and waits.
On supported x86_64 hosts, General → Performance → Hard FPU emulation controls perf.hard_fpu, which defaults to true. A full application restart is required: the translator reads the setting at initialization. This uses the existing host-assisted x87 path; it does not add guest CPUs. Audio voice-worker settings also leave the single Xbox vCPU unchanged.
#296, merged November 1, automatically generates FNSTSW AX inside this existing hard-FPU choice. It has no separate user switch. The soft-FPU setting retains the helper path. ARM ordering #320 and ARM hard FPU #321 remain unmerged/HOLD; #321 has concrete semantic defects as well as missing qualification. Do not infer ARM support from the x86_64 control.
Configuration defaults · General settings UI · Current x86 translator.
flowchart TD
A[Xbox machine creates x86 CPU] --> B[One guest vCPU]
B --> C[MTTCG vCPU thread]
C --> D[tcg_cpus_exec]
D --> E[cpu_exec]
E --> F{Translation block available?}
F -->|Yes| G[tb_exec]
F -->|No| H[tb_gen_code]
H --> G
G --> I{Continue translated execution?}
I -->|Chain / next TB| F
I -->|Interrupt / exception / exit / I/O boundary| J[Return to dispatcher]
J --> K{CPU can run?}
K -->|Yes| D
K -->|No| L[Wait for event / kick]
L --> K
The important distinction is that guest instructions do not run one at a time through a giant interpreter loop. TCG translates groups of guest instructions into host code called translation blocks, then executes those blocks directly until an event requires returning to emulator control.
hw/xbox/xbox.c defines the Xbox machine's CPU policy.
| Property | Current model |
|---|---|
| Default guest CPU |
pentium3 x86 CPU type |
| Maximum guest CPUs | 1 |
| Execution backend | QEMU TCG for translated CPU execution |
| Guest architecture | 32-bit x86 Xbox CPU environment |
The machine creates the CPU object from the machine's configured cpu_type. The single-vCPU limit is an important architectural fact: adding more host threads does not automatically create more guest CPU parallelism because the original Xbox exposes one CPU.
The MTTCG accelerator path is implemented in accel/tcg/tcg-accel-ops-mttcg.c.
mttcg_start_vcpu_thread() creates a QemuThread for the CPUState. The thread entry is mttcg_cpu_thread_fn().
Conceptually:
mttcg_start_vcpu_thread(cpu)
↓
create host QemuThread
↓
mttcg_cpu_thread_fn(cpu)
↓
while CPU may run
↓
tcg_cpus_exec(cpu)
↓
handle debug/exit state
↓
wait when CPU cannot currently run
This is the primary host thread on which Xbox guest x86 code executes.
accel/tcg/cpu-exec.c contains the central TCG execution loop. cpu_exec(CPUState *cpu) coordinates translated execution with asynchronous emulator state.
Responsibilities include:
- Establishing the current CPU execution context.
- Checking pending exceptions and interrupt requests.
- Locating or generating a translation block for the current guest program counter and CPU state.
- Executing generated host code.
- Processing translation-block exit reasons.
- Returning when another subsystem needs control.
A useful mental model is:
CPU architectural state
↓
Can an interrupt/exception be delivered now?
↓
Find TB for current execution state
↓
Execute generated host instructions
↓
Decode why generated code returned
↓
Repeat or return to the outer vCPU loop
A TranslationBlock is TCG's reusable compiled representation of a region of guest code under a particular set of execution assumptions.
The dispatcher attempts to find a compatible existing TB. The lookup identity includes more than a raw guest address because translation can depend on CPU execution state.
On a miss, tb_gen_code() invokes the target translator through the CPU class TCG operations. The x86 translator decodes guest instructions and emits TCG intermediate operations, which are then turned into host machine code.
tb_exec() transfers control into generated host code. The generated code may return because it reached the end of the block, because chaining cannot continue, or because an asynchronous/event condition requires the dispatcher.
Where legal, translated blocks can link directly to later blocks and avoid a full high-level dispatch lookup for every short guest sequence. This is an important performance mechanism: a profile with heavy dispatch/lookup cost often means execution is returning to the dispatcher more frequently than ideal.
sequenceDiagram
participant V as vCPU thread
participant E as cpu_exec
participant C as TB cache
participant T as x86/TCG translator
participant H as Generated host code
V->>E: tcg_cpus_exec(cpu)
E->>C: Find compatible TB
alt Cache hit
C-->>E: Existing TranslationBlock
else Cache miss
E->>T: tb_gen_code(...)
T-->>E: New TranslationBlock + host code
end
E->>H: tb_exec(...)
H-->>E: TB exit reason / next state
E-->>V: Return only when outer handling is required
A translated block cannot hide asynchronous machine state indefinitely. Common reasons to return to the execution engine include:
| Exit category | Why emulator control is required |
|---|---|
| Guest exception | The CPU must update architectural exception state |
| Interrupt | Device/PIC/APIC state must become visible at a legal x86 boundary |
| MMIO / helper path | A guest access needs device-model code rather than plain host memory |
| Debug event | Breakpoint/single-step/guest-debug processing is required |
| Halt/wait | The CPU should stop consuming host cycles until an event makes it runnable |
| TB invalidation | Previously translated code or assumptions are no longer safe to execute |
| External kick | Another thread requests that the vCPU leave translated execution |
The MTTCG kick path uses cpu_exit(cpu) to make translated execution return. This is one of the synchronization bridges from other emulator threads into the vCPU execution domain.
Interrupt delivery is a cooperation between device models and TCG. A device asserts guest-visible interrupt state, but the guest CPU can only take that interrupt when x86 semantics permit it.
flowchart LR
A[Device changes IRQ state] --> B[CPU interrupt request]
B --> C[TCG observes exit/request]
C --> D{Interrupt currently deliverable?}
D -->|No| E[Continue guest execution until legal boundary]
D -->|Yes| F[Enter guest interrupt/exception path]
This is why apparently small changes around interrupt shadows, STI, or block termination can affect dispatch frequency and performance: correctness requires precise architectural boundaries.
Guest memory accesses fall into several broad classes.
When a guest access maps to ordinary RAM and the TCG memory model permits it, generated host code can often perform the access efficiently through translated memory helpers or fast paths.
Accesses to emulated hardware must reach the relevant device model. These accesses can leave the cheapest translated path and involve address-space dispatch, locking, device state changes, or cross-thread consequences.
For Xbox workloads, guest writes that eventually become GPU work can follow a path such as:
x86 translated guest code
↓
write guest memory / MMIO
↓
NV2A-visible state or pushbuffer data
↓
PFIFO / PGRAPH processing
↓
renderer backend
See NV2A Graphics Architecture.
TCG may reuse a translation block only while the generated host code still represents the correct guest instructions and execution context. If guest code pages or relevant translation assumptions change, stale translated code must not continue to execute.
The invalidation system therefore trades two goals:
- Keep translated blocks alive long enough to get reuse and chaining benefits.
- Invalidate them quickly enough to preserve guest correctness.
Broad or frequent invalidation can become a performance issue because it increases translation and dispatch work. Any optimization in this area must prove that it does not let modified guest code execute through a stale TB.
| Work | Primary execution context |
|---|---|
| Xbox guest x86 instructions | Single vCPU host thread |
| TCG TB lookup/generation/dispatch | vCPU execution path |
| Device timer callback | Event/timer context associated with the QEMU clock/timer system |
| PFIFO/PGRAPH graphics work | NV2A command-processing domain, coordinated by its locks/threading |
| OpenGL/Vulkan renderer calls | Renderer/PGRAPH ownership rules, not arbitrary vCPU code |
The vCPU can cause device work, but that does not mean all device work executes synchronously on the vCPU thread.
TCG's goal is architectural x86 correctness, not a clock-by-clock Pentium III microarchitecture simulation. xemu does not normally model the physical P6 front end, reorder buffer, branch predictor, cache-fill latency and bus-cycle timing as a cycle-accurate pipeline.
flowchart LR
A[Guest x86 instructions] --> B[x86 architectural decode/semantics]
B --> C[TCG IR]
C --> D[Host machine code]
D --> E[Host CPU executes TB]
E --> F[Architectural guest state]
Consequences:
- A guest loop that costs N cycles on a real Pentium III does not automatically consume an equivalent wall-clock ratio in xemu.
- Translation-block chaining can skip dispatcher work that has no physical Xbox equivalent; it is an emulator fast path.
- MMIO/helper exits add emulator overhead that is also not a physical CPU pipeline event.
- Host cache/TLB behavior affects TCG performance independently of the Xbox cache hierarchy.
- The early MCPX boot ROM cache quirk is handled through a writable overlay because the exact physical write-back-cache behavior is not modeled. See Firmware, Boot & ROM.
flowchart TD
A[Translated guest instruction] --> B{Operation}
B -->|register/ALU| C[Stay in generated host code]
B -->|ordinary RAM| D[TCG memory fast path / TLB]
B -->|MMIO / port I/O| E[Leave cheap memory path]
E --> F[QEMU address-space/device callback]
F --> G[Possible IRQ / timer / DMA / host-backend effect]
A profile dominated by generated guest code is therefore fundamentally different from one dominated by device helpers. Reducing an MMIO exit by caching a value is safe only if the real guest-visible register semantics permit that reuse.
A vCPU profile generally needs to distinguish at least these categories:
| Category | Interpretation |
|---|---|
| Generated guest code | Host CPU is doing useful translated guest execution |
| TB lookup/dispatch | Time spent deciding what translated code to run next |
| Translation | New guest code is being decoded and compiled |
| TLB / memory helpers | Address translation, permission checks, dirty tracking, or memory slow paths |
| I/O/device helpers | Guest operations are entering emulated hardware models |
| Interrupt/exception processing | Required architectural transitions |
| Wait/synchronization | vCPU is blocked or coordinating with another subsystem |
A high percentage in generated guest code is not automatically waste. The useful optimization question is usually why does execution leave generated code, and is the exit avoidable while preserving guest semantics?
- Separate samples on the vCPU thread from graphics/UI/worker threads.
- Measure generated-code share versus dispatcher/helper share.
- Resolve the largest named dispatcher/helper symbols.
- Count or trace exit reasons if dispatch is unexpectedly hot.
- Determine whether the cause is guest behavior, device interaction, TB invalidation, or an emulator implementation detail.
- Change one mechanism at a time and compare identical workload progress.
Before #296, the hard-FPU status-to-AX path entered a helper. Current gen_fnstsw_ax() flushes cached FP registers to preserve the architectural checkpoint before a later fault, composes status using architectural TOP rather than the translation-local stack delta, and emits the AX result without that helper. It preserves the instruction's non-update of FPU instruction pointer and keeps known rounding-control state across the helper-free checkpoint. Memory-form status stores and other FPU helper optimizations are separate paths.
flowchart LR
A[Before: FNSTSW AX] --> B[Hard-FPU helper boundary and checkpoint]
B --> C[Status result to AX]
flowchart LR
A[After: hard-FPU FNSTSW AX] --> B[Flush cached FP registers for architectural checkpoint]
B --> C[Compose status with architectural TOP]
C --> D[Write AX / preserve FIP and known rounding control]
E[Soft-FPU choice] --> F[Existing helper fallback]
The November 1 comparison used parent 47dc9059 and candidate 0bfa34ff; the audit verified source/tree applicability to merged main. The following are medians of four run medians per arm, in guest-QPC microseconds. Positive Improvement % is time reduction: 100 * (parent - candidate) / parent.
| Focused workload | Backend | Parent µs | Candidate µs | Time difference µs (after − before) | Improvement % | Correctness boundary |
|---|---|---|---|---|---|---|
| x87_status_ax | OpenGL | 53,785.25 | 11,920.25 | −41,865.00 | +77.837% | Returned-state checks; retained gaps below |
| x87_status_ax | Vulkan | 54,446.25 | 11,355.25 | −43,091.00 | +79.144% | Returned-state checks; retained gaps below |
| x87_compare_status_ax | OpenGL | 289,622.00 | 228,587.75 | −61,034.25 | +21.074% | Returned-state checks; retained gaps below |
| x87_compare_status_ax | Vulkan | 297,903.25 | 230,984.00 | −66,919.25 | +22.463% | Returned-state checks; retained gaps below |
The retained Deck Vulkan PGR2 whole-window READ3D cadence was 18.1705 → 18.0346 events/s (−0.748%), with essentially flat CPU. p99 rose 77.2801 → 78.2095 ms; median per-run maximum rose 182.3605 → 211.5880 ms, with isolated candidate stalls of 275.443 and 238.581 ms. These focused reductions are not game-FPS gains; the accepted rationale combined architectural repair and focused-work reduction with the disclosed game cost. A favorable last-five-group calculation must not replace the whole-window result.
Forty XISO attempts produced 140 leaf records: 120 PASS and 20 retained captured-context FAIL/ineligible records. The shared fault-context case failed in both arms; direct pre-resume FP state remained unobserved. Status timing reports retain an inconclusive designation and checkpoint comparisons remain ineligible. Passing returned-state checks did not turn that missing contract into a pass. Issue #236 remains open for broader FPU research and the context discrepancy.
See #296 and Measured Results for the compact evidence and campaign identities. Full current raw evidence is referenced in a private workspace; public readers have a reproducibility limit. This documentation repair ran no new emulator benchmarks.
The existing lookup_tb_ptr_i32 entry in cpu-exec.c is already present. #329 remains an open/HOLD cold-path split and must not be described as the delivery of that existing lookup or as part of #296.
| Source | Responsibility |
|---|---|
hw/xbox/xbox.c |
Xbox machine CPU type and single-CPU configuration |
accel/tcg/tcg-accel-ops-mttcg.c |
vCPU thread creation, execution loop, kick/wait behavior |
accel/tcg/cpu-exec.c |
Main TCG CPU execution, interrupt/exception handling, TB dispatch |
accel/tcg/translate-all.c and related TCG code |
Translation-block lifecycle/invalidation infrastructure |
target/i386/* |
x86 guest decoding, CPU semantics, and target-specific translation |
PR #54 was diagnostic work used to classify why TCG returned to its dispatcher in a target workload. It is useful research evidence, not production architecture.
PR #55 investigated chaining around the x86 STI interrupt-shadow boundary. It remained unmerged/HOLD. Its importance to this page is conceptual: CPU performance work around TB exits must preserve exact x86 interrupt visibility, not merely reduce dispatcher calls.
For current branch status, see Development.
For the Release + DWARF workflow used to resolve TCG dispatcher/lookup hot paths and then attribute why translation blocks returned, see Debugging Lag & Hot Paths. PR #54/#55 are covered there as the diagnostic-to-production example.
Recognized Guest Poll-Loop Wait targets one proven side-effect-free polling loop and event source, while Direct-RAM Read Windows remains deferred until a measured translated region justifies a region-level SoftMMU bypass.
- System Architecture
- Clocks & Timers
- NV2A Graphics Architecture
- Performance Methodology
- MCPX overlay and a retracted boot theory — a reset-vector mapping experiment, not a claim about host CPU execution.
vCPU Shortcut Candidates follows the work between a guest instruction and the host instructions that execute it. It documents helper expansion, cached-x87 materialization, host FP-control reloads, SSE helper routes, and repeated string-memory operations.
The required evidence is a Release + matching-DWARF instruction-expansion report weighted by translated-block execution count—not source size alone.
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