|
2 | 2 |
|
3 | 3 | use crate::prelude::*; |
4 | 4 | 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}; |
6 | 7 | use core::arch::naked_asm; |
| 8 | +use core::hint::cold_path; |
7 | 9 | use std::cell::RefCell; |
8 | 10 | use std::io; |
9 | 11 | use std::mem; |
10 | | -use std::ptr::{self, null_mut}; |
| 12 | +use std::ptr::{self, NonNull, null_mut}; |
11 | 13 | use wasmtime_unwinder::Handler; |
12 | 14 |
|
13 | 15 | /// Function which may handle custom signals while processing traps. |
@@ -134,23 +136,157 @@ now. |
134 | 136 | } |
135 | 137 | } |
136 | 138 |
|
| 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 | + |
137 | 178 | /// Switches tasks in response to a signal thrown under MMU-based epoch |
138 | 179 | /// interruption. |
139 | 180 | /// |
140 | 181 | /// 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. |
142 | 186 | #[unsafe(naked)] |
143 | 187 | unsafe extern "C" fn task_switch_trampoline(_vmctx: usize) { |
144 | 188 | naked_asm!( |
145 | 189 | " |
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 |
149 | 284 |
|
150 | 285 | // Resume right after the load instruction that triggered the signal |
151 | 286 | // handler. |
152 | 287 | jmp r10 |
153 | | - " |
| 288 | + ", |
| 289 | + sym yield_current_fiber |
154 | 290 | ); |
155 | 291 | } |
156 | 292 |
|
|
0 commit comments