Skip to content

Commit 81c5b50

Browse files
committed
perf: keep the bounded handlers in Windows x64's four argument registers
Windows x64 passes four integer arguments in registers. The bounded handlers (fuel and time budgets) take four besides the 32-bit height, so passing the height put a fifth argument on the stack under either convention: the height under the Rust ABI, the instruction under System V, which passes this enum in memory. On Windows x64 the bounded handlers keep the Rust ABI and leave the height out: they read it from the stack before dispatching, as before the height was passed. The unbudgeted handlers keep the System V convention and the height.
1 parent 54ff113 commit 81c5b50

1 file changed

Lines changed: 57 additions & 15 deletions

File tree

‎crates/tinywasm/src/interpreter/executor/dispatch_become.rs‎

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ struct Bounded;
1313
// On arm64_32 (watchOS) the Rust ABI passes an aggregate larger than a pointer, the 8-byte
1414
// `Instruction`, by reference, so every dispatch would store it and the next handler load it back.
1515
// The C convention passes it in a register; `C-unwind` still lets a host function's panic unwind.
16-
// On Windows x64 the handlers use the System V convention (`sysv64-unwind`), which passes six
17-
// integer arguments in registers where Windows' own passes four, so a dispatch no longer stores the
18-
// height for the next handler to load back. The `Instruction` still goes through the stack there:
19-
// the System V rules pass this enum in memory. Elsewhere the handlers keep the Rust ABI, which
16+
// On Windows x64 the unbudgeted handlers use the System V convention (`sysv64-unwind`), which
17+
// passes six integer arguments in registers where Windows' own passes four, so a dispatch no longer
18+
// stores the height for the next handler to load back. The `Instruction` still goes through the
19+
// stack there: the System V rules pass this enum in memory. The bounded handlers keep the Rust ABI
20+
// on Windows x64 and do not pass the height, so their four arguments, the `Instruction` among them,
21+
// stay in Windows' four argument registers. Elsewhere the handlers keep the Rust ABI, which
2022
// passes eight integer arguments in registers on arm64 and six on x86-64 System V, with the
2123
// `Instruction` in one of them.
2224
macro_rules! handler_fn {
@@ -32,17 +34,57 @@ macro_rules! handler_fn {
3234
};
3335
}
3436

37+
macro_rules! bounded_handler_fn {
38+
($(#[$meta:meta])* fn $($rest:tt)*) => {
39+
#[cfg(all(target_arch = "aarch64", target_pointer_width = "32"))]
40+
#[allow(improper_ctypes_definitions)]
41+
$(#[$meta])* extern "C-unwind" fn $($rest)*
42+
#[cfg(not(all(target_arch = "aarch64", target_pointer_width = "32")))]
43+
$(#[$meta])* fn $($rest)*
44+
};
45+
}
46+
47+
/// The height a bounded handler passes on: nothing on Windows x64.
48+
#[cfg(not(all(target_arch = "x86_64", windows)))]
49+
type BoundedHeight = usize;
50+
#[cfg(all(target_arch = "x86_64", windows))]
51+
type BoundedHeight = ();
52+
53+
#[inline(always)]
54+
fn bounded_height(executor: &Executor<'_, '_>) -> BoundedHeight {
55+
#[cfg(not(all(target_arch = "x86_64", windows)))]
56+
return executor.value_stack.stack_32.len();
57+
#[cfg(all(target_arch = "x86_64", windows))]
58+
{
59+
let _ = executor;
60+
}
61+
}
62+
63+
/// Writes a passed height back to the stack.
64+
#[inline(always)]
65+
fn enter_bounded_height(executor: &mut Executor<'_, '_>, height32: BoundedHeight) {
66+
#[cfg(not(all(target_arch = "x86_64", windows)))]
67+
executor.value_stack.stack_32.set_len(height32);
68+
#[cfg(all(target_arch = "x86_64", windows))]
69+
{
70+
let _ = (executor, height32);
71+
}
72+
}
73+
3574
macro_rules! handler_types {
3675
($($abi:literal)?) => {
3776
// Both sides are Rust, so the C convention's view of these types need not be FFI-safe.
3877
#[allow(improper_ctypes_definitions)]
3978
type UnbudgetedHandler =
4079
for<'store, 'module> $(extern $abi)? fn(&mut Executor<'store, 'module>, &[Instruction], usize, Instruction, usize) -> ExecResult<()>;
41-
#[allow(improper_ctypes_definitions)]
42-
type BoundedHandler = for<'store, 'module> $(extern $abi)? fn(&mut Executor<'store, 'module>, usize, Instruction, u32, usize) -> ExecResult<()>;
4380
};
4481
}
4582
#[cfg(all(target_arch = "aarch64", target_pointer_width = "32"))]
83+
#[allow(improper_ctypes_definitions)]
84+
type BoundedHandler = for<'store, 'module> extern "C-unwind" fn(&mut Executor<'store, 'module>, usize, Instruction, u32, BoundedHeight) -> ExecResult<()>;
85+
#[cfg(not(all(target_arch = "aarch64", target_pointer_width = "32")))]
86+
type BoundedHandler = for<'store, 'module> fn(&mut Executor<'store, 'module>, usize, Instruction, u32, BoundedHeight) -> ExecResult<()>;
87+
#[cfg(all(target_arch = "aarch64", target_pointer_width = "32"))]
4688
handler_types!("C-unwind");
4789
#[cfg(all(target_arch = "x86_64", windows))]
4890
handler_types!("sysv64-unwind");
@@ -127,19 +169,19 @@ macro_rules! define_bounded_tail_dispatch {
127169
HANDLERS[opcode as usize]
128170
}
129171

130-
$(handler_fn! {
172+
$(bounded_handler_fn! {
131173
#[allow(non_snake_case, unreachable_code, unused_imports, unused_macros, unused_variables)]
132174
fn $variant(
133175
$executor: &mut Executor<'_, '_>,
134176
$instr_ptr: usize,
135177
instruction: Instruction,
136178
instructions_until_checkpoint: u32,
137-
height32: usize,
179+
height32: BoundedHeight,
138180
) -> ExecResult<()> {
139181
macro_rules! $dispatch_next {
140182
($next_instr_ptr:expr) => {{
141183
let next_instr_ptr = $next_instr_ptr;
142-
let height32 = $executor.value_stack.stack_32.len();
184+
let height32 = bounded_height($executor);
143185
if instructions_until_checkpoint == 0 {
144186
return cold!({
145187
$executor.cf.instr_ptr = next_instr_ptr;
@@ -176,7 +218,7 @@ macro_rules! define_bounded_tail_dispatch {
176218
$(let $variant { $($field),* } = &instruction else {
177219
become Self::handler_mismatch($executor, $instr_ptr, instruction, instructions_until_checkpoint, height32);
178220
};)?
179-
$executor.value_stack.stack_32.set_len(height32);
221+
enter_bounded_height($executor, height32);
180222
$body;
181223
$dispatch_next!($instr_ptr + 1)
182224
}
@@ -212,18 +254,18 @@ impl Bounded {
212254

213255
// Tail-called like `Unbudgeted`'s.
214256

215-
handler_fn! {
257+
bounded_handler_fn! {
216258
#[cold]
217259
#[inline(never)]
218-
fn handler_mismatch(_: &mut Executor<'_, '_>, _: usize, _: Instruction, _: u32, _: usize) -> ExecResult<()> {
260+
fn handler_mismatch(_: &mut Executor<'_, '_>, _: usize, _: Instruction, _: u32, _: BoundedHeight) -> ExecResult<()> {
219261
unreachable!("instruction handler mismatch")
220262
}
221263
}
222264

223-
handler_fn! {
265+
bounded_handler_fn! {
224266
#[cold]
225267
#[inline(never)]
226-
fn invalid_instr_ptr(_: &mut Executor<'_, '_>, instr_ptr: usize, _: Instruction, _: u32, _: usize) -> ExecResult<()> {
268+
fn invalid_instr_ptr(_: &mut Executor<'_, '_>, instr_ptr: usize, _: Instruction, _: u32, _: BoundedHeight) -> ExecResult<()> {
227269
unreachable!("instruction pointer {instr_ptr} out of range, this is a bug")
228270
}
229271
}
@@ -234,7 +276,7 @@ impl Bounded {
234276
let instr_ptr = executor.cf.instr_ptr;
235277
let instruction = executor.func.instructions[instr_ptr];
236278
let handler = Self::handler_for(instruction.opcode());
237-
let height32 = executor.value_stack.stack_32.len();
279+
let height32 = bounded_height(executor);
238280
handler(executor, instr_ptr, instruction, chunk_left - 1, height32)
239281
}
240282
}

0 commit comments

Comments
 (0)