Skip to content

Hot Path vCPU Helper Aware x87 Cache Retention

github-actions[bot] edited this page Sep 14, 2026 · 3 revisions

Helper-Aware x87 Cache Retention

Lifecycle: Performance - Unproven · Type: State-materialization shortcut
State: Source-backed; runtime value is not established.

Status: Source-validated exact-shortcut candidate; not implemented or benchmarked.

Mechanism class: Audited helper-call bypass of canonical x87 state materialization.

Reviewed source: 322986f4c5502b3fa887a9605ac8cd88e9b2c579

Primary files: tcg/tcg.c, target/i386/tcg/translate.c, target/i386/helper.h, target/i386/ops_fpu.h.

Summary

The fork's hard-x87 translator keeps guest stack values in TCG floating-point temporaries. Before every generated helper call, generic TCG call generation invokes gen_bb_epilogue(). The x86 translator implements that hook by calling gen_flush_fp().

Consequently, an unrelated integer helper can force xemu to:

  1. convert and store live cached x87 values into canonical 80-bit guest state;
  2. discard the translator's cached references;
  3. forget that host floating-point control was already prepared;
  4. call the helper;
  5. reload/convert the x87 values and reload control state when later x87 code resumes.

The proposed shortcut permits a very small, explicitly audited helper class to bypass that x87 flush.

This is a real shortcut: the maintained general route materializes all cached state before a helper; the candidate skips it only when the helper cannot observe or invalidate that state.

Source-confirmed interaction

tcg_gen_callN() in tcg/tcg.c invokes:

gen_bb_epilogue();

before allocating the helper-call operation.

The x86 translator hook is:

void gen_bb_epilogue(void)
{
    gen_flush_fp((DisasContext *)tcg_ctx->disas_ctx);
}

gen_flush_fp() flushes all cached x87 temporaries, resets the stack delta, and clears flcr_set.

target/i386/helper.h simultaneously marks helpers such as:

cc_compute_all
cc_compute_c
cc_compute_nz

with TCG_CALL_NO_RWG_SE; they take their condition-code inputs by value instead of receiving CPUX86State *env.

That does not automatically prove every helper with that metadata is safe for cached-FP retention. It does provide concrete candidates for an initial whitelist.

Current route

flowchart TD
    A[Live translated x87 values] --> B[Translator emits unrelated helper call]
    B --> C[Generic tcg_gen_callN invokes gen_bb_epilogue]
    C --> D[gen_flush_fp]
    D --> E[st80f cached stack values and FT0]
    E --> F[Clear cached references, fpstt delta and flcr_set]
    F --> G[Call helper]
    G --> H[Later x87 instruction]
    H --> I[gen_flcr and ld80f reloads]
    I --> J[Continue arithmetic]
Loading

st80f/ld80f are not ordinary same-width spills. The hard-FPU path converts between cached host single/double temporaries and the canonical guest 80-bit representation.

Proposed shortcut

The helper call needs a translator-specific effect decision before the x87 epilogue runs:

/* Illustrative interface, not an existing API. */
if (!x86_helper_preserves_cached_fp(helper_info)) {
    gen_flush_fp(s);
}
emit_helper_call(helper_info, args);

A conservative first version should use an explicit whitelist layered on top of helper metadata:

Helper must:
- take no CPU environment pointer;
- read no canonical guest globals;
- write no guest globals;
- have no side effects relevant to architectural FP state;
- not raise a guest exception or force a dispatcher boundary;
- not depend on or modify host FP control/status state;
- be reviewed for the exact compiled implementation.
flowchart TD
    A[Helper emission requested] --> B{Explicitly audited FP-independent helper?}
    B -->|No| R[Reference: flush cached x87 state]
    R --> C[Emit helper call]
    B -->|Yes| D[Retain logical x87 TCG temporaries and flcr knowledge]
    D --> C
    C --> E[TCG register allocator handles ABI spills as needed]
    E --> F[Continue translated block]
Loading

The shortcut preserves the translator's logical cache. It does not assume physical host registers survive a C call. The TCG register allocator remains responsible for normal call-clobber spills.

Why it may help

The opportunity is not the total number of pure helpers. It is:

helper executions
where cached x87 values are live
× number and precision of live values
× avoided st80f/ld80f/control setup

A helper called millions of times while the x87 cache is empty does not justify this work. Conversely, one helper inside a hot x87 chain may repeatedly expand a small guest sequence into conversions and control-state reloads.

Exactness argument

The candidate can be equivalent only when all of these remain true:

  • the helper cannot read canonical x87 stack contents, tags, status, or control state;
  • the helper cannot write or invalidate them;
  • the helper cannot raise an exception that requires complete architectural FP state;
  • guest-visible x87 state is materialized at every real architectural observation or exit boundary;
  • TCG's call-clobber/liveness rules preserve temporary values correctly;
  • host floating-point environment assumptions remain valid across the helper;
  • debugging, plugins, or instrumentation do not introduce an observation omitted by the admission rule.

The helper metadata is an input to this proof, not the complete proof.

Code and ownership map

File/area Role
tcg/tcg.c::tcg_gen_callN() Unconditional call to gen_bb_epilogue()
target/i386/tcg/translate.c::gen_bb_epilogue() Maps generic call boundary to x87 flush
target/i386/tcg/translate.c::gen_flush_fp() Flushes values and clears flcr_set
target/i386/ops_fpu.h::flush_fp_regs_*() Emits canonical st80f stores and clears cached temporaries
target/i386/helper.h Helper effect declarations and initial whitelist candidates
TCG call/liveness machinery Preserves temporaries across call-clobber boundaries

Instrumentation required before implementation

Record helper calls grouped by:

  • helper identity;
  • guest PC / translated block;
  • dynamic block execution count;
  • number of live cached x87 stack values;
  • whether FT0 is live;
  • cached precision (f32 or f64 path);
  • whether flcr_set was true;
  • emitted st80f, ld80f, and LDMXCSR operations attributable to the boundary.

Suggested counters:

Counter Unit Meaning
helper_calls_total calls All dynamic helper calls
helper_calls_with_live_fp calls Helper encountered with retained x87 values
helper_calls_whitelist_eligible calls Candidate admission
live_fp_values_at_eligible_call values Conversion pressure
fp_flushes_bypassed boundaries Shortcut entries
st80f_avoided / ld80f_avoided operations Generated work removed
fp_flush_fallbacks boundaries Conservative route retained

Translation-time counts must be weighted by block execution count. One emitted flush in a hot loop may execute millions of times.

Differential tests

Positive control

Construct a block that:

  1. loads several live x87 values;
  2. performs arithmetic;
  3. invokes an audited by-value integer helper;
  4. performs more x87 arithmetic;
  5. stores the final guest result and status.

The candidate-generated code should omit the intermediate canonical st80f/ld80f sequence while matching the reference output and stack state.

Mandatory negative controls

Substitute helpers that:

  • take CPUX86State *env and read x87 state;
  • write x87 control/status or stack state;
  • raise a guest exception;
  • end the translated block;
  • change host FP control state;
  • invoke code paths not present in the original whitelist.

All must take the reference flush route.

Also deliberately misclassify one helper in a test build. The differential harness must detect stale or unmaterialized state. If it cannot, the test does not qualify the assumption.

Interaction with FP control-state retention

This candidate and Floating-Point Control-State Retention overlap but are not identical:

Value materialization question:
Must cached guest x87 values be written to canonical storage?

Control-state question:
Must xemu forget that the current host MXCSR/control setup is valid?

Benchmark them independently:

  1. retain values only;
  2. retain control-state knowledge only where possible;
  3. combine only after each contribution is understood.

Otherwise the same eliminated LDMXCSR can be counted twice.

Expected profile signature

A successful candidate should show:

eligible helper calls > 0
intermediate st80f/ld80f operations fall
LDMXCSR may fall in the combined follow-up
helper work itself remains unchanged
host samples in conversion/load-store code fall
fixed guest output and x87 status remain equal

Failure modes

  • Treating TCG_CALL_NO_RWG_SE as a universal FP-safety certificate.
  • Preserving cached values across a helper that observes canonical state.
  • Forgetting guest exception and dispatcher-exit boundaries.
  • Assuming host registers survive instead of relying on TCG call-clobber handling.
  • Retaining flcr_set across code that changed MXCSR.
  • Changing generic TCG semantics for every target instead of introducing a narrow x86 contract.
  • Measuring helper counts without live-FP context.
  • Combining this with multiple backend changes before attribution.

Qualification plan

  1. Add translation/dynamic evidence for helper identity and live x87 state.
  2. Select one or two by-value condition-code helpers with high measured reachability.
  3. Introduce the narrowest x86-specific effect interface or whitelist.
  4. Verify generated code before and after with matching Release + DWARF builds.
  5. Run positive and deliberately broken negative controls.
  6. Compare exact result bits, x87 stack/tag/status/control state, exceptions, and exits.
  7. Exercise mixed x87, SSE, integer-helper, interrupt, snapshot, and self-modifying-code flows.
  8. Run uninstrumented Release ABBA/BAAB on workloads that actually reach the candidate.
  9. Keep the reference flush route permanent for all unaudited helpers.

Related pages

Full technical reference

Primary reference: This subject page is the design and research reference for Helper-Aware x87 Cache Retention. Pull requests are implementation/test history only.
Lifecycle: Performance - Unproven · Mechanism: State-materialization bypass
Reviewed source: 322986f4c5502b3fa887a9605ac8cd88e9b2c579 · Acceptance class: intended exact or behavior-preserving for its admitted scope

1. Scope

Keep cached x87 temporaries live across explicitly audited FP-independent helpers instead of unconditionally converting them to canonical 80-bit guest state before every helper call.

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.

2. Source seams

File / function Role
tcg/tcg.c helper-call generation and gen_bb_epilogue()
target/i386/tcg/translate.c gen_flush_fp(), cached x87 temporaries and flcr_set
target/i386/ops_fpu.h helper metadata and FP consumers

3. Current execution path

flowchart TD
    N0["Live cached x87 values"]
    N1["Every helper call triggers block epilogue"]
    N0 --> N1
    N2["st80f materialization"]
    N1 --> N2
    N3["Clear cached references/control knowledge"]
    N2 --> N3
    N4["Call helper"]
    N3 --> N4
    N5["ld80f/control reload later"]
    N4 --> N5
Loading

4. Candidate execution path

flowchart TD
    N0["Inspect audited helper effect class at translation"]
    N1["FP-independent helper: emit call without canonical x87 flush"]
    N0 --> N1
    N2["TCG spills registers as ABI requires"]
    N1 --> N2
    N3["FP-touching/unknown helper: reference flush"]
    N2 --> N3
Loading

5. Why it may help

This is not about assuming host registers survive a C call. TCG's allocator/call-clobber handling remains active. The avoided work is conversion to and from canonical guest 80-bit storage plus translator cache invalidation around a helper that cannot observe it.

6. Work removed

  • Unnecessary st80f/ld80f conversions
  • Unnecessary cache invalidation
  • Some repeated FP-control setup

7. Work that remains mandatory

  • Normal ABI spills
  • Flush for helpers that inspect/modify FP state or raise guest exceptions
  • Canonical state at real architectural exits
  • Reference route

8. Implementation sketch

static bool helper_preserves_cached_fp(const HelperInfo *info)
{
    return info->explicit_fp_effect == HELPER_FP_NONE &&
           !info->takes_cpu_env &&
           !info->may_raise_guest_exception;
}

if (!helper_preserves_cached_fp(info))
    gen_flush_fp(s);
emit_helper_call(info, args);

The sketch defines the intended boundary; it is not a complete patch and does not replace source review, ownership design, or focused tests.

9. Correctness contract

  • Whitelist begins with by-value helpers proven not to access CPU/FP state.
  • Unknown/pointer-taking helpers flush conservatively.
  • Architectural exceptions/exits see canonical state.
  • Host register clobbers are handled by TCG independently.

10. State, ownership, invalidation, and fallback

The translator owns logical cache validity. Helper metadata must represent FP effects explicitly rather than infer from generic purity alone. Any helper behavior change invalidates its audit/whitelist status.

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.

11. Advantages

  • Targets an unconditional fork interaction
  • Exact reference path
  • Potentially removes expensive 80-bit conversions
  • Can be proven in generated code

12. Costs and failure modes

  • Helper audits can become stale
  • Live x87 values may be uncommon at pure-helper calls
  • Interaction with exceptions/control state
  • Backend-specific spill behavior may limit gain

13. Evidence counters

Counters must be cheap, subsystem-owned, and snapshotted outside the hot loop. Record at least:

  • helper_calls_with_live_x87
  • live_values_per_helper
  • helpers_by_identity
  • st80f/ld80f_executed
  • cache_retained_calls
  • fallback_flushes
  • generated_code_bytes
  • 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.

14. Qualification matrix

  • Translation fixture with live x87 values around audited integer helper
  • Exact x87 values/stack tags
  • Helper that reads FP state must flush
  • Helper that changes control/raises exception
  • Register pressure/spills
  • Long gameplay block counts weighted by execution

Correctness runs may use validation layers, detailed comparisons, and fault injection. Performance acceptance must use optimized Release builds with matching symbols and minimal instrumentation.

15. Acceptance rule

Promote one helper class at a time when generated conversions disappear, differential state tests pass, and weighted runtime evidence is meaningful.

16. Rejection rule

Reject broad metadata inference or global flush removal; keep Unproven if pure helpers rarely occur with live cached values.

17. Related architecture and history

CPU-and-vCPU-Execution · Performance-Unproven · Hot-Path-vCPU-FP-Control-State-Retention · Debugging-Tools-and-Commands

PR pages may be linked as supporting evidence, but they are not substitutes for this reference.

Clone this wiki locally