leventov opened PR #14975 from convex-in-prod:upstream-preserving-metering to bytecodealliance:main:
Motivation
Fuel and epoch checks expose uncommon builtin calls to the register allocator. Treating those handlers as ordinary caller-clobbering calls imposes register constraints and spills on instrumented code even when the slow branch is not taken.
Change
Use the existing
PreserveAllbuiltin trampoline forout_of_gasandnew_epoch, and mark the fuel slow block cold. The preserving trampoline has no return registers, so epoch checks reload the authoritative deadline from the Store after the callback, including after async suspension. Existing fuel reloads remain in place.This changes neither fuel charges nor epoch-check placement. It adds no polling configuration or new public runtime API. It relies on the existing preserving trampoline and safepoint machinery used for patchable builtins.
Validation
All four focused tests in
tests/all/metering_preserve.rspassed on the extracted branch with Rust 1.97.1 and the copying collector. The exact test file was compiled in a small external harness against this branch with Cranelift, async, GC and call hooks enabled, avoiding unrelated CLI/WASI dependencies. Changed-file formatting and diff checks passed.The focused tests keep integer, floating-point and SIMD values live across refill and epoch callbacks; compare exact fuel consumption; cover yielding, interruption, callback errors, cancellation and store reuse; and retain references across metering yields and moving collection. This is intended to reduce the cost of ordinary execution, not to make the slow handlers themselves cheaper.
leventov removed the review request for wasmtime-compiler-reviewers from PR #14975.
leventov requested wasmtime-compiler-reviewers for a review on PR #14975.
leventov requested fitzgen for a review on PR #14975.
leventov requested wasmtime-core-reviewers for a review on PR #14975.
leventov edited PR #14975:
Motivation
Fuel and epoch checks expose uncommon builtin calls to the register allocator. Treating those handlers as ordinary caller-clobbering calls imposes register constraints and spills on instrumented code even when the slow branch is not taken.
Change
Use the existing
PreserveAllbuiltin trampoline forout_of_gasandnew_epoch, and mark the fuel slow block cold. The preserving trampoline has no return registers, so epoch checks reload the authoritative deadline from the Store after the callback, including after async suspension. Existing fuel reloads remain in place.This changes neither fuel charges nor epoch-check placement. It adds no polling configuration or new public runtime API. It relies on the existing preserving trampoline and safepoint machinery used for patchable builtins.
Illustrative downstream measurements
In a controlled local comparison using convex-wasm-compiler, two TypeScript application handlers compiled through Static Hermes to Wasm improved as follows:
Warm handler execution Native instructions Paired median elapsed time Faster measured rounds Handler A 6.90% fewer 7.32% lower 32 / 32 Handler B 6.44% fewer 6.36% lower 32 / 32 Both engines ran the same Core Wasm with fuel and epoch checks enabled and inlining disabled. Each handler/engine had 16 warmups and 32 measured rounds, with retained instances, alternating execution order and execution pinned to one CPU. All 192 executions, including warmups, matched outputs, effects and per-invocation fuel charges. The candidate included both register preservation and the cold fuel-block annotation, as this PR does; these numbers do not isolate their individual contributions.
A separate nine-case short-handler suite improved paired median elapsed time by 4.47–12.04% across two launches with opposite instance-creation order. All 10,368 executions matched outputs, effects and fuel charges.
These are historical downstream measurements of the change, not a rerun on this PR's current upstream base or a production before/after comparison. They measure warm execution, excluding compilation and startup. A separate compilation check with matched producer build settings, fresh function caches and baseline/change/change/baseline order showed essentially unchanged mean AOT time on a 6.66 MB module (7.767 → 7.740 s). Earlier single observations on two larger modules were 34–35% slower, but used unmatched producer build settings; they do not establish a compilation regression from this change, and the smaller controlled comparison does not settle larger-module behavior.
Validation
All four focused tests in
tests/all/metering_preserve.rspassed on the extracted branch with Rust 1.97.1 and the copying collector. The exact test file was compiled in a small external harness against this branch with Cranelift, async, GC and call hooks enabled, avoiding unrelated CLI/WASI dependencies. Changed-file formatting and diff checks passed.The focused tests keep integer, floating-point and SIMD values live across refill and epoch callbacks; compare exact fuel consumption; cover yielding, interruption, callback errors, cancellation and store reuse; and retain references across metering yields and moving collection. This is intended to reduce the cost of ordinary execution, not to make the slow handlers themselves cheaper.
leventov edited PR #14975:
Fuel and epoch slow-path calls currently impose caller-clobbered register constraints on instrumented code, even when the checks do not invoke a handler. Preserving live registers inside these uncommon handlers reduces spills and register pressure on ordinary execution.
Use the existing
PreserveAllbuiltin trampoline forout_of_gasandnew_epoch, and mark the fuel slow block cold. The preserving trampoline has no return registers, so epoch checks reload the deadline from the Store after the callback, including after an async yield. Fuel charges and epoch-check placement remain unchanged.Performance
Local x86-64 measurements in the downstream convex-wasm-compiler integration, using two TypeScript handlers compiled through Static Hermes:
Handler Native instructions Paired median elapsed time A 6.90% fewer 7.32% lower B 6.44% fewer 6.36% lower Both variants executed the same Core Wasm with fuel and epoch checks enabled and inlining disabled. Each used 16 warmups and 32 measured rounds per handler, alternating execution order on one pinned CPU with retained instances. All 32 measured pairs improved for each handler; outputs, effects and fuel charges matched. These results measure warm execution and include both changes in this PR.
Validation
All four tests in
tests/all/metering_preserve.rspassed in a focused harness against this branch with copying GC enabled. They cover integer, floating-point and SIMD values across callbacks; exact fuel consumption; async yields, interruption and callback errors; cancellation followed by Store reuse; and live GC references across yields and moving collection.
fitzgen commented on PR #14975:
This PR description looks like it was written by an LLM. Can you confirm whether this abides by the AI policy?
Last updated: Oct 31 2026 at 23:03 UTC