Skip to content

CPU and vCPU Execution

OpenAI Codex edited this page Nov 1, 2026 · 7 revisions

CPU & 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.

Hard-FPU control and platform scope

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.

High-level path

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
Loading

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.

Machine configuration

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.

vCPU thread creation

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.

cpu_exec()

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

Translation blocks

A TranslationBlock is TCG's reusable compiled representation of a region of guest code under a particular set of execution assumptions.

Lookup

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.

Generation

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.

Execution

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.

Chaining

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.

Translation lifecycle

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
Loading

Why the vCPU leaves generated code

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 boundaries

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]
Loading

This is why apparently small changes around interrupt shadows, STI, or block termination can affect dispatch frequency and performance: correctness requires precise architectural boundaries.

Memory access

Guest memory accesses fall into several broad classes.

Directly translatable RAM

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.

MMIO and device access

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.

Self-modifying code and invalidation

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:

  1. Keep translated blocks alive long enough to get reuse and chaining benefits.
  2. 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.

Thread ownership

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.

What is not cycle-accurate

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]
Loading

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.

CPU fast path versus device slow path

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]
Loading

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.

Where CPU performance goes

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?

Profiling workflow for the vCPU

  1. Separate samples on the vCPU thread from graphics/UI/worker threads.
  2. Measure generated-code share versus dispatcher/helper share.
  3. Resolve the largest named dispatcher/helper symbols.
  4. Count or trace exit reasons if dispatch is unexpectedly hot.
  5. Determine whether the cause is guest behavior, device interaction, TB invalidation, or an emulator implementation detail.
  6. Change one mechanism at a time and compare identical workload progress.

See Performance Methodology.

Generated FNSTSW AX: bounded change and retained cost

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]
Loading
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]
Loading

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.

Important source files

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

Related Development

PR #54 — TCG return attribution

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 — STI interrupt-shadow chaining experiment

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.

Profiling the vCPU without mistaking heat for cause

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.

Guest-execution bypass research

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.

Related pages

vCPU instruction-expansion research

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.

Clone this wiki locally