Stream: git-wasmtime

Topic: wasmtime / PR #14975 cranelift: preserve live registers i...


view this post on Zulip Wasmtime GitHub notifications bot (Oct 23 2026 at 22:56):

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 PreserveAll builtin trampoline for out_of_gas and new_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.rs passed 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.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 23 2026 at 22:56):

leventov removed the review request for wasmtime-compiler-reviewers from PR #14975.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 23 2026 at 22:56):

leventov requested wasmtime-compiler-reviewers for a review on PR #14975.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 23 2026 at 22:56):

leventov requested fitzgen for a review on PR #14975.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 23 2026 at 22:56):

leventov requested wasmtime-core-reviewers for a review on PR #14975.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 23 2026 at 23:03):

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 PreserveAll builtin trampoline for out_of_gas and new_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.rs passed 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.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 23 2026 at 23:13):

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 PreserveAll builtin trampoline for out_of_gas and new_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.rs passed 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.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 26 2026 at 16:55):

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