From 42cc1de47c639980ebae27c7adaf0f542296fcdf Mon Sep 17 00:00:00 2001 From: kvp <223931578+kvpanch@users.noreply.github.com> Date: Wed, 6 May 2026 11:13:16 -0400 Subject: [PATCH 1/7] Switch interpreter dispatch to musttail (`become`) threading Replace the central loop in `InterpretedInstance::run_impl` with direct-threaded dispatch: each handler now ends in `become dispatch(self, next_off)` and `dispatch` (inlined) tail-jumps to the next handler. This collapses the per-instruction call/ret pair into a single indirect tail jump, and moves the indirect-branch site from one shared loop to the tail of every handler so the BTB learns per-edge transition patterns. Requires nightly + `#![feature(explicit_tail_calls)]`. The recompiler is untouched. Measured deltas on bench-pinky / bench-prime-sieve / bench-memset / bench-minimal (host: x86_64, best-of-N runs): | Benchmark | Baseline | Patched | Delta | |------------------------------|------------|------------|---------| | runtime/memset/32 | 43.66 ms | 37.99 ms | -13.0% | | runtime/memset/64 | 45.02 ms | 37.40 ms | -16.9% | | runtime/minimal/64 | 15.60 us | 12.07 us | -22.6% | | runtime/pinky/32 | 128.05 ms | 97.69 ms | -23.7% | | runtime/pinky/64 | 122.43 ms | 91.45 ms | -25.3% | | runtime/prime-sieve/32 | 96.20 ms | 60.76 ms | -36.8% | | runtime/prime-sieve/64 | 75.67 ms | 49.90 ms | -34.1% | | interpreter runtime mean | | | -20.3% | | compiler runtime mean (ctrl) | | | -0.6% | --- crates/polkavm/src/interpreter.rs | 30 +++++++++++++++-------------- crates/polkavm/src/lib.rs | 2 ++ rust-toolchain.toml | 2 +- tools/benchtool/rust-toolchain.toml | 2 +- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/crates/polkavm/src/interpreter.rs b/crates/polkavm/src/interpreter.rs index a6d0f1f2..46ac25f6 100644 --- a/crates/polkavm/src/interpreter.rs +++ b/crates/polkavm/src/interpreter.rs @@ -25,7 +25,7 @@ use polkavm_common::program::{ use polkavm_common::utils::{align_to_next_page_usize, slice_assume_init_mut, ArcBytes, GasVisitorT}; type Target = u32; -type HandlerResult = Target; +type HandlerResult = InterruptKind; #[derive(Copy, Clone)] pub enum RegImm { @@ -1711,18 +1711,7 @@ impl InterpretedInstance { log::trace!("Implicitly resuming at: [{}]", self.next_compiled_offset); } - let mut offset = self.next_compiled_offset; - loop { - if DEBUG { - self.cycle_counter += 1; - } - - if let Some(handler) = self.compiled_handlers.get(cast(offset).to_usize()) { - offset = handler(self, offset); - } else { - return self.interrupt.clone(); - } - } + dispatch::(self, self.next_compiled_offset) } pub fn reset_memory(&mut self) { @@ -2504,6 +2493,18 @@ struct Args { type Handler = for<'a> fn(visitor: &'a mut InterpretedInstance, compiled_offset: Target) -> HandlerResult; +#[inline(always)] +fn dispatch(visitor: &mut InterpretedInstance, off: Target) -> InterruptKind { + if DEBUG { + visitor.cycle_counter += 1; + } + if let Some(&handler) = visitor.compiled_handlers.get(cast(off).to_usize()) { + become handler(visitor, off); + } else { + visitor.interrupt.clone() + } +} + macro_rules! define_interpreter { (@define $handler_name:ident $body:block $self:ident $compiled_offset:ident) => {{ impl Args { @@ -2949,7 +2950,8 @@ macro_rules! define_interpreter { #[allow(clippy::needless_lifetimes)] pub fn $handler_name<'a, $(M: $M_ty,)? $(const $const: $const_ty),+>($self: &'a mut InterpretedInstance, compiled_offset: Target) -> HandlerResult { let $compiled_offset = compiled_offset; - define_interpreter!(@define $handler_name $body $self $compiled_offset $($arg)*) + let next_off: Target = define_interpreter!(@define $handler_name $body $self $compiled_offset $($arg)*); + become dispatch::($self, next_off); } )+ } diff --git a/crates/polkavm/src/lib.rs b/crates/polkavm/src/lib.rs index f6a3481e..8236db1d 100644 --- a/crates/polkavm/src/lib.rs +++ b/crates/polkavm/src/lib.rs @@ -1,4 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] +#![feature(explicit_tail_calls)] +#![allow(incomplete_features)] #![forbid(unused_must_use)] #![forbid(clippy::missing_safety_doc)] #![deny(clippy::undocumented_unsafe_blocks)] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index cf6d0f55..f17841b9 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.86.0" +channel = "nightly-2026-05-03" diff --git a/tools/benchtool/rust-toolchain.toml b/tools/benchtool/rust-toolchain.toml index cf6d0f55..f17841b9 100644 --- a/tools/benchtool/rust-toolchain.toml +++ b/tools/benchtool/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.86.0" +channel = "nightly-2026-05-03" From ee767b7767e24cf5be0814875b94f3d485603c9c Mon Sep 17 00:00:00 2001 From: kvp <223931578+kvpanch@users.noreply.github.com> Date: Wed, 6 May 2026 14:22:37 -0400 Subject: [PATCH 2/7] switched back to stable rust. Added special CI job to test `become` --- .github/workflows/rust.yml | 8 ++++ ci/jobs/build-and-test-nightly-musttail.sh | 14 +++++++ crates/polkavm/Cargo.toml | 3 ++ crates/polkavm/src/interpreter.rs | 46 ++++++++++++++++------ crates/polkavm/src/interpreter_musttail.rs | 19 +++++++++ crates/polkavm/src/lib.rs | 4 +- rust-toolchain.toml | 2 +- tools/benchtool/rust-toolchain.toml | 2 +- 8 files changed, 83 insertions(+), 15 deletions(-) create mode 100755 ci/jobs/build-and-test-nightly-musttail.sh create mode 100644 crates/polkavm/src/interpreter_musttail.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index eba1779b..a7bb4793 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -143,3 +143,11 @@ jobs: run: rustup run nightly-2024-07-10 cargo install --locked --version 0.12.0 cargo-fuzz - name: Fuzz run: ./ci/jobs/fuzz.sh + nightly-musttail: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Install latest nightly + run: rustup toolchain install nightly --profile minimal + - name: Build and test with experimental-musttail + run: ./ci/jobs/build-and-test-nightly-musttail.sh diff --git a/ci/jobs/build-and-test-nightly-musttail.sh b/ci/jobs/build-and-test-nightly-musttail.sh new file mode 100755 index 00000000..8405a5b4 --- /dev/null +++ b/ci/jobs/build-and-test-nightly-musttail.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -euo pipefail +cd -- "$(dirname -- "${BASH_SOURCE[0]}")" +cd ../.. + +echo ">> cargo +nightly build (polkavm, experimental-musttail)" +cargo +nightly build -p polkavm --features experimental-musttail + +echo ">> cargo +nightly test (polkavm, experimental-musttail)" +cargo +nightly test -p polkavm --features experimental-musttail + +echo ">> cargo +nightly test --release (polkavm, experimental-musttail)" +cargo +nightly test --release -p polkavm --features experimental-musttail diff --git a/crates/polkavm/Cargo.toml b/crates/polkavm/Cargo.toml index 9baf1799..fc4ad94e 100644 --- a/crates/polkavm/Cargo.toml +++ b/crates/polkavm/Cargo.toml @@ -50,3 +50,6 @@ generic-sandbox = [] # Internal feature for testing. DO NOT USE. export-internals-for-testing = [] + +# Musttail (`become`) interpreter dispatch. Requires nightly. +experimental-musttail = [] diff --git a/crates/polkavm/src/interpreter.rs b/crates/polkavm/src/interpreter.rs index 46ac25f6..8baf2eca 100644 --- a/crates/polkavm/src/interpreter.rs +++ b/crates/polkavm/src/interpreter.rs @@ -25,7 +25,11 @@ use polkavm_common::program::{ use polkavm_common::utils::{align_to_next_page_usize, slice_assume_init_mut, ArcBytes, GasVisitorT}; type Target = u32; + +#[cfg(feature = "experimental-musttail")] type HandlerResult = InterruptKind; +#[cfg(not(feature = "experimental-musttail"))] +type HandlerResult = Target; #[derive(Copy, Clone)] pub enum RegImm { @@ -1711,7 +1715,25 @@ impl InterpretedInstance { log::trace!("Implicitly resuming at: [{}]", self.next_compiled_offset); } - dispatch::(self, self.next_compiled_offset) + #[cfg(feature = "experimental-musttail")] + { + dispatch::(self, self.next_compiled_offset) + } + #[cfg(not(feature = "experimental-musttail"))] + { + let mut offset = self.next_compiled_offset; + loop { + if DEBUG { + self.cycle_counter += 1; + } + + if let Some(handler) = self.compiled_handlers.get(cast(offset).to_usize()) { + offset = handler(self, offset); + } else { + return self.interrupt.clone(); + } + } + } } pub fn reset_memory(&mut self) { @@ -2493,16 +2515,18 @@ struct Args { type Handler = for<'a> fn(visitor: &'a mut InterpretedInstance, compiled_offset: Target) -> HandlerResult; +// `become` is parse-time feature-gated, so the musttail dispatcher must live in a separately-loaded file. +#[cfg(feature = "experimental-musttail")] +#[path = "interpreter_musttail.rs"] +mod musttail; + +#[cfg(feature = "experimental-musttail")] +use musttail::{dispatch, handler_tail}; + +#[cfg(not(feature = "experimental-musttail"))] #[inline(always)] -fn dispatch(visitor: &mut InterpretedInstance, off: Target) -> InterruptKind { - if DEBUG { - visitor.cycle_counter += 1; - } - if let Some(&handler) = visitor.compiled_handlers.get(cast(off).to_usize()) { - become handler(visitor, off); - } else { - visitor.interrupt.clone() - } +fn handler_tail(_visitor: &mut InterpretedInstance, next_off: Target) -> HandlerResult { + next_off } macro_rules! define_interpreter { @@ -2951,7 +2975,7 @@ macro_rules! define_interpreter { pub fn $handler_name<'a, $(M: $M_ty,)? $(const $const: $const_ty),+>($self: &'a mut InterpretedInstance, compiled_offset: Target) -> HandlerResult { let $compiled_offset = compiled_offset; let next_off: Target = define_interpreter!(@define $handler_name $body $self $compiled_offset $($arg)*); - become dispatch::($self, next_off); + handler_tail::($self, next_off) } )+ } diff --git a/crates/polkavm/src/interpreter_musttail.rs b/crates/polkavm/src/interpreter_musttail.rs new file mode 100644 index 00000000..7c9144e2 --- /dev/null +++ b/crates/polkavm/src/interpreter_musttail.rs @@ -0,0 +1,19 @@ +use super::{HandlerResult, InterpretedInstance, Target}; +use polkavm_common::cast::cast; + +#[inline(always)] +pub(super) fn handler_tail(visitor: &mut InterpretedInstance, next_off: Target) -> HandlerResult { + become dispatch::(visitor, next_off); +} + +#[inline(always)] +pub(super) fn dispatch(visitor: &mut InterpretedInstance, off: Target) -> HandlerResult { + if DEBUG { + visitor.cycle_counter += 1; + } + if let Some(&handler) = visitor.compiled_handlers.get(cast(off).to_usize()) { + become handler(visitor, off); + } else { + visitor.interrupt.clone() + } +} diff --git a/crates/polkavm/src/lib.rs b/crates/polkavm/src/lib.rs index 8236db1d..f13d2b81 100644 --- a/crates/polkavm/src/lib.rs +++ b/crates/polkavm/src/lib.rs @@ -1,6 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] -#![feature(explicit_tail_calls)] -#![allow(incomplete_features)] +#![cfg_attr(feature = "experimental-musttail", feature(explicit_tail_calls))] +#![cfg_attr(feature = "experimental-musttail", allow(incomplete_features))] #![forbid(unused_must_use)] #![forbid(clippy::missing_safety_doc)] #![deny(clippy::undocumented_unsafe_blocks)] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f17841b9..cf6d0f55 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2026-05-03" +channel = "1.86.0" diff --git a/tools/benchtool/rust-toolchain.toml b/tools/benchtool/rust-toolchain.toml index f17841b9..cf6d0f55 100644 --- a/tools/benchtool/rust-toolchain.toml +++ b/tools/benchtool/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2026-05-03" +channel = "1.86.0" From 9ccc38ff2b2147e246b7c81f69722b6f422514dd Mon Sep 17 00:00:00 2001 From: kvp <223931578+kvpanch@users.noreply.github.com> Date: Wed, 6 May 2026 17:27:02 -0400 Subject: [PATCH 3/7] fix -O0 build due to stack growth issue that is caused due to missed inlining --- crates/polkavm/src/interpreter.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/polkavm/src/interpreter.rs b/crates/polkavm/src/interpreter.rs index 8baf2eca..a0d8ec1b 100644 --- a/crates/polkavm/src/interpreter.rs +++ b/crates/polkavm/src/interpreter.rs @@ -2529,6 +2529,18 @@ fn handler_tail(_visitor: &mut InterpretedInstance, next_off: next_off } +// `become` from the handler itself, so the tail call doesn't depend on +// `handler_tail`/`dispatch` being inlined (which `-O0` may skip). +#[cfg(feature = "experimental-musttail")] +macro_rules! tail_dispatch { + ($self:expr, $next_off:expr) => { become handler_tail::($self, $next_off) }; +} + +#[cfg(not(feature = "experimental-musttail"))] +macro_rules! tail_dispatch { + ($self:expr, $next_off:expr) => { handler_tail::($self, $next_off) }; +} + macro_rules! define_interpreter { (@define $handler_name:ident $body:block $self:ident $compiled_offset:ident) => {{ impl Args { @@ -2975,7 +2987,7 @@ macro_rules! define_interpreter { pub fn $handler_name<'a, $(M: $M_ty,)? $(const $const: $const_ty),+>($self: &'a mut InterpretedInstance, compiled_offset: Target) -> HandlerResult { let $compiled_offset = compiled_offset; let next_off: Target = define_interpreter!(@define $handler_name $body $self $compiled_offset $($arg)*); - handler_tail::($self, next_off) + tail_dispatch!($self, next_off) } )+ } From 37a5d6d3877e4465fb1b3b096c38a0cb0a81a41b Mon Sep 17 00:00:00 2001 From: kvp <223931578+kvpanch@users.noreply.github.com> Date: Wed, 6 May 2026 21:24:30 -0400 Subject: [PATCH 4/7] code format --- crates/polkavm/src/interpreter.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/polkavm/src/interpreter.rs b/crates/polkavm/src/interpreter.rs index a0d8ec1b..a1334b44 100644 --- a/crates/polkavm/src/interpreter.rs +++ b/crates/polkavm/src/interpreter.rs @@ -2533,12 +2533,16 @@ fn handler_tail(_visitor: &mut InterpretedInstance, next_off: // `handler_tail`/`dispatch` being inlined (which `-O0` may skip). #[cfg(feature = "experimental-musttail")] macro_rules! tail_dispatch { - ($self:expr, $next_off:expr) => { become handler_tail::($self, $next_off) }; + ($self:expr, $next_off:expr) => { + become handler_tail::($self, $next_off) + }; } #[cfg(not(feature = "experimental-musttail"))] macro_rules! tail_dispatch { - ($self:expr, $next_off:expr) => { handler_tail::($self, $next_off) }; + ($self:expr, $next_off:expr) => { + handler_tail::($self, $next_off) + }; } macro_rules! define_interpreter { From 6412c99702dddb2e553ebe40bfc4d6012c25befc Mon Sep 17 00:00:00 2001 From: kvp <223931578+kvpanch@users.noreply.github.com> Date: Wed, 6 May 2026 22:12:12 -0400 Subject: [PATCH 5/7] enable unprivileged_userfaultfd --- ci/jobs/build-and-test-nightly-musttail.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ci/jobs/build-and-test-nightly-musttail.sh b/ci/jobs/build-and-test-nightly-musttail.sh index 8405a5b4..85192a99 100755 --- a/ci/jobs/build-and-test-nightly-musttail.sh +++ b/ci/jobs/build-and-test-nightly-musttail.sh @@ -4,6 +4,10 @@ set -euo pipefail cd -- "$(dirname -- "${BASH_SOURCE[0]}")" cd ../.. +if [ "$(cat /proc/sys/vm/unprivileged_userfaultfd)" != "1" ]; then + echo "1" | sudo tee /proc/sys/vm/unprivileged_userfaultfd > /dev/null +fi + echo ">> cargo +nightly build (polkavm, experimental-musttail)" cargo +nightly build -p polkavm --features experimental-musttail From e3a88b8c20519eb206ec1fff180e24216a608ab6 Mon Sep 17 00:00:00 2001 From: kvp <223931578+kvpanch@users.noreply.github.com> Date: Thu, 14 May 2026 10:50:48 -0400 Subject: [PATCH 6/7] addressed comments --- .github/workflows/rust.yml | 3 +- ci/jobs/build-and-test-nightly-musttail.sh | 15 ++----- crates/polkavm/Cargo.toml | 4 +- crates/polkavm/src/interpreter.rs | 48 +++++++++++++--------- crates/polkavm/src/interpreter_musttail.rs | 19 --------- crates/polkavm/src/lib.rs | 4 +- 6 files changed, 38 insertions(+), 55 deletions(-) delete mode 100644 crates/polkavm/src/interpreter_musttail.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a7bb4793..a21b6de8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -145,9 +145,10 @@ jobs: run: ./ci/jobs/fuzz.sh nightly-musttail: runs-on: ubuntu-22.04 + continue-on-error: true steps: - uses: actions/checkout@v4 - name: Install latest nightly run: rustup toolchain install nightly --profile minimal - - name: Build and test with experimental-musttail + - name: Build and test with interpreter-musttail-dispatch run: ./ci/jobs/build-and-test-nightly-musttail.sh diff --git a/ci/jobs/build-and-test-nightly-musttail.sh b/ci/jobs/build-and-test-nightly-musttail.sh index 85192a99..bf208556 100755 --- a/ci/jobs/build-and-test-nightly-musttail.sh +++ b/ci/jobs/build-and-test-nightly-musttail.sh @@ -4,15 +4,8 @@ set -euo pipefail cd -- "$(dirname -- "${BASH_SOURCE[0]}")" cd ../.. -if [ "$(cat /proc/sys/vm/unprivileged_userfaultfd)" != "1" ]; then - echo "1" | sudo tee /proc/sys/vm/unprivileged_userfaultfd > /dev/null -fi +echo ">> cargo +nightly test (polkavm, interpreter-musttail-dispatch, interpreter tests only)" +cargo +nightly test -p polkavm --features interpreter-musttail-dispatch -- tests::interpreter_ -echo ">> cargo +nightly build (polkavm, experimental-musttail)" -cargo +nightly build -p polkavm --features experimental-musttail - -echo ">> cargo +nightly test (polkavm, experimental-musttail)" -cargo +nightly test -p polkavm --features experimental-musttail - -echo ">> cargo +nightly test --release (polkavm, experimental-musttail)" -cargo +nightly test --release -p polkavm --features experimental-musttail +echo ">> cargo +nightly test --release (polkavm, interpreter-musttail-dispatch, interpreter tests only)" +cargo +nightly test --release -p polkavm --features interpreter-musttail-dispatch -- tests::interpreter_ diff --git a/crates/polkavm/Cargo.toml b/crates/polkavm/Cargo.toml index fc4ad94e..ffd029fb 100644 --- a/crates/polkavm/Cargo.toml +++ b/crates/polkavm/Cargo.toml @@ -51,5 +51,5 @@ generic-sandbox = [] # Internal feature for testing. DO NOT USE. export-internals-for-testing = [] -# Musttail (`become`) interpreter dispatch. Requires nightly. -experimental-musttail = [] +# Musttail (`become`) direct-threaded interpreter dispatch. Requires nightly. +interpreter-musttail-dispatch = [] diff --git a/crates/polkavm/src/interpreter.rs b/crates/polkavm/src/interpreter.rs index a1334b44..03f6f62a 100644 --- a/crates/polkavm/src/interpreter.rs +++ b/crates/polkavm/src/interpreter.rs @@ -26,9 +26,9 @@ use polkavm_common::utils::{align_to_next_page_usize, slice_assume_init_mut, Arc type Target = u32; -#[cfg(feature = "experimental-musttail")] +#[cfg(feature = "interpreter-musttail-dispatch")] type HandlerResult = InterruptKind; -#[cfg(not(feature = "experimental-musttail"))] +#[cfg(not(feature = "interpreter-musttail-dispatch"))] type HandlerResult = Target; #[derive(Copy, Clone)] @@ -1715,11 +1715,11 @@ impl InterpretedInstance { log::trace!("Implicitly resuming at: [{}]", self.next_compiled_offset); } - #[cfg(feature = "experimental-musttail")] + #[cfg(feature = "interpreter-musttail-dispatch")] { dispatch::(self, self.next_compiled_offset) } - #[cfg(not(feature = "experimental-musttail"))] + #[cfg(not(feature = "interpreter-musttail-dispatch"))] { let mut offset = self.next_compiled_offset; loop { @@ -2515,33 +2515,41 @@ struct Args { type Handler = for<'a> fn(visitor: &'a mut InterpretedInstance, compiled_offset: Target) -> HandlerResult; -// `become` is parse-time feature-gated, so the musttail dispatcher must live in a separately-loaded file. -#[cfg(feature = "experimental-musttail")] -#[path = "interpreter_musttail.rs"] -mod musttail; - -#[cfg(feature = "experimental-musttail")] -use musttail::{dispatch, handler_tail}; +// `become` is parse-gated; hiding it in a macro keeps the parser happy when the feature is off. +#[cfg(feature = "interpreter-musttail-dispatch")] +macro_rules! tail_call { + ($e:expr) => { + become $e + }; +} -#[cfg(not(feature = "experimental-musttail"))] +#[cfg(feature = "interpreter-musttail-dispatch")] #[inline(always)] -fn handler_tail(_visitor: &mut InterpretedInstance, next_off: Target) -> HandlerResult { - next_off +fn dispatch(visitor: &mut InterpretedInstance, off: Target) -> HandlerResult { + if DEBUG { + visitor.cycle_counter += 1; + } + if let Some(&handler) = visitor.compiled_handlers.get(cast(off).to_usize()) { + tail_call!(handler(visitor, off)) + } else { + visitor.interrupt.clone() + } } -// `become` from the handler itself, so the tail call doesn't depend on -// `handler_tail`/`dispatch` being inlined (which `-O0` may skip). -#[cfg(feature = "experimental-musttail")] +// Tail-jump from a handler into the next one. `become` lives at the handler tail so the chain +// stays musttail even if `dispatch` isn't inlined (e.g. at `-O0`). With the feature off, the +// handler returns the new offset and the central loop drives the next call. +#[cfg(feature = "interpreter-musttail-dispatch")] macro_rules! tail_dispatch { ($self:expr, $next_off:expr) => { - become handler_tail::($self, $next_off) + tail_call!(dispatch::($self, $next_off)) }; } -#[cfg(not(feature = "experimental-musttail"))] +#[cfg(not(feature = "interpreter-musttail-dispatch"))] macro_rules! tail_dispatch { ($self:expr, $next_off:expr) => { - handler_tail::($self, $next_off) + $next_off }; } diff --git a/crates/polkavm/src/interpreter_musttail.rs b/crates/polkavm/src/interpreter_musttail.rs deleted file mode 100644 index 7c9144e2..00000000 --- a/crates/polkavm/src/interpreter_musttail.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::{HandlerResult, InterpretedInstance, Target}; -use polkavm_common::cast::cast; - -#[inline(always)] -pub(super) fn handler_tail(visitor: &mut InterpretedInstance, next_off: Target) -> HandlerResult { - become dispatch::(visitor, next_off); -} - -#[inline(always)] -pub(super) fn dispatch(visitor: &mut InterpretedInstance, off: Target) -> HandlerResult { - if DEBUG { - visitor.cycle_counter += 1; - } - if let Some(&handler) = visitor.compiled_handlers.get(cast(off).to_usize()) { - become handler(visitor, off); - } else { - visitor.interrupt.clone() - } -} diff --git a/crates/polkavm/src/lib.rs b/crates/polkavm/src/lib.rs index f13d2b81..62914a19 100644 --- a/crates/polkavm/src/lib.rs +++ b/crates/polkavm/src/lib.rs @@ -1,6 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(feature = "experimental-musttail", feature(explicit_tail_calls))] -#![cfg_attr(feature = "experimental-musttail", allow(incomplete_features))] +#![cfg_attr(feature = "interpreter-musttail-dispatch", feature(explicit_tail_calls))] +#![cfg_attr(feature = "interpreter-musttail-dispatch", allow(incomplete_features))] #![forbid(unused_must_use)] #![forbid(clippy::missing_safety_doc)] #![deny(clippy::undocumented_unsafe_blocks)] From c3dcbe5f12344f0bbd1e843bb256d21c1dd88b0e Mon Sep 17 00:00:00 2001 From: kvp <223931578+kvpanch@users.noreply.github.com> Date: Tue, 19 May 2026 14:17:25 -0400 Subject: [PATCH 7/7] simplified new code --- crates/polkavm/src/interpreter.rs | 74 ++++++++++++++----------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/crates/polkavm/src/interpreter.rs b/crates/polkavm/src/interpreter.rs index 03f6f62a..7ee5a9d5 100644 --- a/crates/polkavm/src/interpreter.rs +++ b/crates/polkavm/src/interpreter.rs @@ -31,6 +31,14 @@ type HandlerResult = InterruptKind; #[cfg(not(feature = "interpreter-musttail-dispatch"))] type HandlerResult = Target; +// `become` is parse-gated; hiding it in a macro keeps the parser happy when the feature is off. +#[cfg(feature = "interpreter-musttail-dispatch")] +macro_rules! tail_call { + ($e:expr) => { + become $e + }; +} + #[derive(Copy, Clone)] pub enum RegImm { Reg(Reg), @@ -1717,7 +1725,17 @@ impl InterpretedInstance { #[cfg(feature = "interpreter-musttail-dispatch")] { - dispatch::(self, self.next_compiled_offset) + // Normal call, not `become`: `run_impl`'s signature doesn't match a handler's. + // The musttail chain runs inside the handlers and unwinds back here. + let off = self.next_compiled_offset; + if DEBUG { + self.cycle_counter += 1; + } + if let Some(&handler) = self.compiled_handlers.get(cast(off).to_usize()) { + handler(self, off) + } else { + self.interrupt.clone() + } } #[cfg(not(feature = "interpreter-musttail-dispatch"))] { @@ -2515,44 +2533,6 @@ struct Args { type Handler = for<'a> fn(visitor: &'a mut InterpretedInstance, compiled_offset: Target) -> HandlerResult; -// `become` is parse-gated; hiding it in a macro keeps the parser happy when the feature is off. -#[cfg(feature = "interpreter-musttail-dispatch")] -macro_rules! tail_call { - ($e:expr) => { - become $e - }; -} - -#[cfg(feature = "interpreter-musttail-dispatch")] -#[inline(always)] -fn dispatch(visitor: &mut InterpretedInstance, off: Target) -> HandlerResult { - if DEBUG { - visitor.cycle_counter += 1; - } - if let Some(&handler) = visitor.compiled_handlers.get(cast(off).to_usize()) { - tail_call!(handler(visitor, off)) - } else { - visitor.interrupt.clone() - } -} - -// Tail-jump from a handler into the next one. `become` lives at the handler tail so the chain -// stays musttail even if `dispatch` isn't inlined (e.g. at `-O0`). With the feature off, the -// handler returns the new offset and the central loop drives the next call. -#[cfg(feature = "interpreter-musttail-dispatch")] -macro_rules! tail_dispatch { - ($self:expr, $next_off:expr) => { - tail_call!(dispatch::($self, $next_off)) - }; -} - -#[cfg(not(feature = "interpreter-musttail-dispatch"))] -macro_rules! tail_dispatch { - ($self:expr, $next_off:expr) => { - $next_off - }; -} - macro_rules! define_interpreter { (@define $handler_name:ident $body:block $self:ident $compiled_offset:ident) => {{ impl Args { @@ -2999,7 +2979,21 @@ macro_rules! define_interpreter { pub fn $handler_name<'a, $(M: $M_ty,)? $(const $const: $const_ty),+>($self: &'a mut InterpretedInstance, compiled_offset: Target) -> HandlerResult { let $compiled_offset = compiled_offset; let next_off: Target = define_interpreter!(@define $handler_name $body $self $compiled_offset $($arg)*); - tail_dispatch!($self, next_off) + #[cfg(feature = "interpreter-musttail-dispatch")] + { + if DEBUG { + $self.cycle_counter += 1; + } + if let Some(&handler) = $self.compiled_handlers.get(cast(next_off).to_usize()) { + tail_call!(handler($self, next_off)) + } else { + $self.interrupt.clone() + } + } + #[cfg(not(feature = "interpreter-musttail-dispatch"))] + { + next_off + } } )+ }