Skip to content

Commit 21863e6

Browse files
committed
Implement asm trampoline and fiber-switching routine that actually effects the task switch when an epoch ends.
Trampoline cribs generously from https://github.com/cfallin/wasmtime/blob/f6476d3174e0ffbe59f807385b5518691eeacffd/crates/wasmtime/src/runtime/vm/traphandlers/inject_call/x86_64.rs#L11.
1 parent 2abd963 commit 21863e6

3 files changed

Lines changed: 154 additions & 18 deletions

File tree

crates/wasmtime/src/runtime/vm/sys/unix/signals.rs

Lines changed: 143 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
33
use crate::prelude::*;
44
use crate::runtime::module::lookup_code;
5-
use crate::runtime::vm::traphandlers::{TrapRegisters, TrapTest, tls};
5+
use crate::runtime::vm::traphandlers::{TrapRegisters, TrapTest, raise_preexisting_trap, tls};
6+
use crate::runtime::vm::{Instance, VMContext, Yield};
67
use core::arch::naked_asm;
8+
use core::hint::cold_path;
79
use std::cell::RefCell;
810
use std::io;
911
use std::mem;
10-
use std::ptr::{self, null_mut};
12+
use std::ptr::{self, NonNull, null_mut};
1113
use wasmtime_unwinder::Handler;
1214

1315
/// Function which may handle custom signals while processing traps.
@@ -134,23 +136,157 @@ now.
134136
}
135137
}
136138

139+
140+
/// Causes the active fiber to yield.
141+
///
142+
/// Consequently, this returns only after this fiber resumes, appearing to be a
143+
/// normal synchronous function from the standpoint of the caller.
144+
///
145+
/// If this fiber gets cancelled during the duration of our yield, this function
146+
/// never returns, instead initiating an unwind.
147+
#[cfg(feature = "async")]
148+
unsafe extern "C" fn yield_current_fiber(vmctx: NonNull<VMContext>) {
149+
unsafe {
150+
// is_cancelled means an error occurred and unwind info has been stored
151+
// in TLS.
152+
let is_cancelled = !Instance::enter_host_from_wasm(vmctx, |store, _instance| {
153+
// Mirror the behavior of `block_on!`, which would panic in
154+
// `assert_ready()` if async_support were off because `Yield::new()`
155+
// is always initially unready.
156+
if !store.async_support() {
157+
cold_path();
158+
panic!("shouldn't try to run async wasm on a store with async_support off");
159+
}
160+
store.with_blocking(|_store, cx| cx.block_on(async { Yield::new().await }))
161+
});
162+
if is_cancelled {
163+
// `block_on()` returned an Err, meaning this fiber has been
164+
// cancelled and needs to exit. We unwind it using
165+
// `raise_preexisting_trap()`, which, on native targets (the only
166+
// ones MMU epochs apply to), never returns, doing a longjmp out and
167+
// thus making anything in stack frames above irrelevant. Thus, it
168+
// doesn't matter that we never get to restore registers in
169+
// `task_switch_trampoline()` or jmp back to the signal-raising
170+
// address.
171+
Instance::enter_host_from_wasm(vmctx, |store, _instance| {
172+
raise_preexisting_trap(store);
173+
});
174+
}
175+
}
176+
}
177+
137178
/// Switches tasks in response to a signal thrown under MMU-based epoch
138179
/// interruption.
139180
///
140181
/// Saves register state, makes a host call to switch tasks, restores state, and
141-
/// returns.
182+
/// jmps back to the instruction after the one that triggered the signal. The
183+
/// address of that instruction has been squirreled away in r10 by the signal
184+
/// handler. The signal handler has also placed the address of the vmctx into
185+
/// rdi, the first argument register.
142186
#[unsafe(naked)]
143187
unsafe extern "C" fn task_switch_trampoline(_vmctx: usize) {
144188
naked_asm!(
145189
"
146-
// Save regs.
147-
// Call hostcall to do task switch, passing in vmctx.
148-
// Restore regs (including r10).
190+
// When control reaches here, we have just returned from a signal
191+
// handler after rewriting PC to point to this trampoline but updating
192+
// no other register state.
193+
//
194+
// The stack has enough space for this state-saving, ensured by the
195+
// stack-limit checks in Cranelift-compiled code.
196+
197+
// Push a fake return address just to keep the stack 16b-aligned for the
198+
// call, as SysV x64 demands.
199+
push 0
200+
// This is an ordinary frame as seen by stack-walks.
201+
push rbp
202+
203+
// Save all GPRs except rbp/rsp (saved above and by normal stack
204+
// discipline, respectively).
205+
push rax
206+
push rbx
207+
push rcx
208+
push rdx
209+
push rdi
210+
push rsi
211+
push r8
212+
push r9
213+
push r10
214+
push r11
215+
push r12
216+
push r13
217+
push r14
218+
push r15
219+
220+
// N.B.: we don't save rflags; Cranelift-compiled code
221+
// never assumes it is saved across instructions outside of
222+
// flag-generation / flag-consumption pairs, and the only
223+
// resumable traps we are interested in are not flags-related.
224+
225+
sub rsp, 256 // enough for all 16 XMM registers.
226+
movdqu [rsp + 0 * 16], xmm0
227+
movdqu [rsp + 1 * 16], xmm1
228+
movdqu [rsp + 2 * 16], xmm2
229+
movdqu [rsp + 3 * 16], xmm3
230+
movdqu [rsp + 4 * 16], xmm4
231+
movdqu [rsp + 5 * 16], xmm5
232+
movdqu [rsp + 6 * 16], xmm6
233+
movdqu [rsp + 7 * 16], xmm7
234+
movdqu [rsp + 8 * 16], xmm8
235+
movdqu [rsp + 9 * 16], xmm9
236+
movdqu [rsp + 10 * 16], xmm10
237+
movdqu [rsp + 11 * 16], xmm11
238+
movdqu [rsp + 12 * 16], xmm12
239+
movdqu [rsp + 13 * 16], xmm13
240+
movdqu [rsp + 14 * 16], xmm14
241+
movdqu [rsp + 15 * 16], xmm15
242+
243+
// Call yield_current_fiber() to do task switch. vmctx is already in
244+
// rdi, care of the signal handler.
245+
call {}
246+
247+
// Restore registers.
248+
movdqu xmm0, [rsp + 0 * 16]
249+
movdqu xmm1, [rsp + 1 * 16]
250+
movdqu xmm2, [rsp + 2 * 16]
251+
movdqu xmm3, [rsp + 3 * 16]
252+
movdqu xmm4, [rsp + 4 * 16]
253+
movdqu xmm5, [rsp + 5 * 16]
254+
movdqu xmm6, [rsp + 6 * 16]
255+
movdqu xmm7, [rsp + 7 * 16]
256+
movdqu xmm8, [rsp + 8 * 16]
257+
movdqu xmm9, [rsp + 9 * 16]
258+
movdqu xmm10, [rsp + 10 * 16]
259+
movdqu xmm11, [rsp + 11 * 16]
260+
movdqu xmm12, [rsp + 12 * 16]
261+
movdqu xmm13, [rsp + 13 * 16]
262+
movdqu xmm14, [rsp + 14 * 16]
263+
movdqu xmm15, [rsp + 15 * 16]
264+
add rsp, 256
265+
266+
pop r15
267+
pop r14
268+
pop r13
269+
pop r12
270+
pop r11
271+
pop r10
272+
pop r9
273+
pop r8
274+
pop rsi
275+
pop rdi
276+
pop rdx
277+
pop rcx
278+
pop rbx
279+
pop rax
280+
281+
pop rbp
282+
// Pop off the fake return address.
283+
add rsp, 8
149284
150285
// Resume right after the load instruction that triggered the signal
151286
// handler.
152287
jmp r10
153-
"
288+
",
289+
sym yield_current_fiber
154290
);
155291
}
156292

crates/wasmtime/src/runtime/vm/traphandlers.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,11 @@ fn lazy_per_thread_init() {
7474
/// activation on the stack, or the entry trampoline back to the the host, if
7575
/// the exception is uncaught.
7676
///
77-
/// This is currently only called from the `raise` builtin of
78-
/// Wasmtime. This builtin is only used when the host returns back to
79-
/// wasm and indicates that a trap or exception should be raised. In
80-
/// this situation the host has already stored trap or exception
81-
/// information within the `CallThreadState` and this is the low-level
82-
/// operation to actually perform an unwind.
77+
/// This is called from the `raise` builtin of Wasmtime. This builtin is only
78+
/// used when the host returns back to wasm and indicates that a trap or
79+
/// exception should be raised. In this situation, the host has already stored
80+
/// trap or exception information within the `CallThreadState`, and this is the
81+
/// low-level operation to actually perform an unwind.
8382
///
8483
/// Note that this function is used both for Pulley and for native execution.
8584
/// For Pulley this function will return and the interpreter will be
@@ -92,7 +91,7 @@ fn lazy_per_thread_init() {
9291
/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
9392
/// have been previously called. Additionally no Rust destructors can be on the
9493
/// stack. They will be skipped and not executed.
95-
pub(super) unsafe fn raise_preexisting_trap(store: &mut dyn VMStore) {
94+
pub(crate) unsafe fn raise_preexisting_trap(store: &mut dyn VMStore) {
9695
tls::with(|info| unsafe { info.unwrap().unwind(store) })
9796
}
9897

tests/all/epoch_mmu.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,13 @@ fn epoch_check_offsets() {
6161
);
6262
}
6363

64-
#[test]
65-
fn epoch_mmu_trap_via_signal_handler() {
6664
/// Runs a wasm function with MMU-based epoch interruption enabled and the epoch
6765
/// ended. Make sure the function returns happily after the interruption.
66+
#[tokio::test]
67+
async fn epoch_mmu_trap_via_signal_handler() {
6868
let mut config = Config::new();
6969
config.epoch_interruption_via_mmu(true);
70+
config.async_support(true);
7071
let engine = Engine::new(&config).unwrap();
7172
let module = Module::new(
7273
&engine,
@@ -88,11 +89,11 @@ fn epoch_mmu_trap_via_signal_handler() {
8889
// Protect that page:
8990
store.end_mmu_epoch();
9091

91-
let instance = Instance::new(&mut store, &module, &[]).unwrap();
92+
let instance = Instance::new_async(&mut store, &module, &[]).await.unwrap();
9293
let func = instance
9394
.get_typed_func::<(), i32>(&mut store, "answer")
9495
.unwrap();
9596

96-
let result = func.call(&mut store, ()).unwrap();
97+
let result = func.call_async(&mut store, ()).await.unwrap();
9798
assert_eq!(result, 42);
9899
}

0 commit comments

Comments
 (0)