1#![allow(static_mut_refs)]
5
6use super::trap::UnwindReason;
10use crate::Trap;
11#[cfg(all(unix, feature = "experimental-host-interrupt"))]
12use crate::interrupt_registry;
13use backtrace::Backtrace;
14use bytesize::ByteSize;
15use core::ptr::{read, read_unaligned};
16use corosensei::stack::{DefaultStack, Stack};
17use corosensei::trap::{CoroutineTrapHandler, TrapHandlerRegs};
18use corosensei::{CoroutineResult, ScopedCoroutine, Yielder};
19use scopeguard::defer;
20use std::any::Any;
21use std::cell::Cell;
22use std::error::Error;
23use std::io;
24use std::mem;
25#[cfg(unix)]
26use std::mem::MaybeUninit;
27use std::ptr::{self, NonNull};
28use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering, compiler_fence};
29use std::sync::{LazyLock, Once};
30use wasmer_types::TrapCode;
31
32trait StackExt: Stack {
34 fn size(&self) -> usize {
36 self.base().get() - self.limit().get()
37 }
38}
39impl<T: Stack> StackExt for T {}
40
41pub struct VMConfig {
44 pub wasm_stack_size: Option<usize>,
46}
47
48static MAGIC: u8 = 0xc0;
52
53static DEFAULT_STACK_SIZE: AtomicUsize = AtomicUsize::new(ByteSize::mib(1).as_u64() as usize);
54
55pub const MAX_STACK_SIZE: usize = ByteSize::mib(100).as_u64() as usize;
58
59#[repr(C)]
62#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
63#[allow(non_camel_case_types)]
64struct ucontext_t {
65 uc_onstack: libc::c_int,
66 uc_sigmask: libc::sigset_t,
67 uc_stack: libc::stack_t,
68 uc_link: *mut libc::ucontext_t,
69 uc_mcsize: usize,
70 uc_mcontext: libc::mcontext_t,
71}
72
73#[cfg(all(unix, not(all(target_arch = "aarch64", target_os = "macos"))))]
74use libc::ucontext_t;
75
76pub fn set_stack_size(size: usize) {
79 DEFAULT_STACK_SIZE.store(
80 size.clamp(ByteSize::kib(8).as_u64() as usize, MAX_STACK_SIZE),
81 Ordering::Relaxed,
82 );
83}
84
85pub fn get_stack_size() -> usize {
87 DEFAULT_STACK_SIZE.load(Ordering::Relaxed)
88}
89
90static STACK_POOL: LazyLock<crossbeam_queue::SegQueue<DefaultStack>> =
94 LazyLock::new(crossbeam_queue::SegQueue::new);
95
96struct StackCache(Cell<Option<DefaultStack>>);
104
105impl Drop for StackCache {
106 fn drop(&mut self) {
107 if let Some(stack) = self.0.take() {
108 STACK_POOL.push(stack);
109 }
110 }
111}
112
113thread_local! {
114 static TLS_STACK: StackCache = const { StackCache(Cell::new(None)) };
115}
116
117fn acquire_stack(min_size: usize) -> DefaultStack {
121 if let Some(stack) = TLS_STACK.with(|cache| cache.0.take()) {
124 if stack.size() >= min_size {
125 return stack;
126 }
127 drop(stack);
130 }
131 STACK_POOL
134 .pop()
135 .filter(|s| s.size() >= min_size)
136 .unwrap_or_else(|| DefaultStack::new(min_size).unwrap())
137}
138
139fn release_stack(stack: DefaultStack) {
143 let displaced = TLS_STACK.with(|cache| cache.0.replace(Some(stack)));
144 if let Some(displaced) = displaced {
145 STACK_POOL.push(displaced);
146 }
147}
148
149pub fn drain_stack_pool() {
165 if let Some(stack) = TLS_STACK.with(|cache| cache.0.take()) {
168 drop(stack);
169 }
170 while STACK_POOL.pop().is_some() {}
171}
172
173cfg_select! {
174 unix => {
175 pub type TrapHandlerFn<'a> = dyn Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool + Send + Sync + 'a;
177 }
178 target_os = "windows" => {
179 pub type TrapHandlerFn<'a> = dyn Fn(*mut windows_sys::Win32::System::Diagnostics::Debug::EXCEPTION_POINTERS) -> bool + Send + Sync + 'a;
181 }
182}
183
184unsafe fn process_illegal_op(addr: usize) -> Option<TrapCode> {
186 let mut val: Option<u8> = None;
187 unsafe {
188 if cfg!(target_arch = "x86_64") {
189 val = if read(addr as *mut u8) & 0xf0 == 0x40
190 && read((addr + 1) as *mut u8) == 0x0f
191 && read((addr + 2) as *mut u8) == 0xb9
192 {
193 Some(read((addr + 3) as *mut u8))
194 } else if read(addr as *mut u8) == 0x0f && read((addr + 1) as *mut u8) == 0xb9 {
195 Some(read((addr + 2) as *mut u8))
196 } else {
197 None
198 }
199 }
200 if cfg!(target_arch = "aarch64") {
201 val = if read_unaligned(addr as *mut u32) & 0xffff0000 == 0 {
202 Some(read(addr as *mut u8))
203 } else {
204 None
205 }
206 }
207 if cfg!(target_arch = "riscv64") {
208 let addr = addr as *mut u32;
209 val = if read(addr) == 0xc0001073 {
211 let prev_insn = read(addr.sub(1));
214 if (prev_insn & 0xffff) == 0x0513 {
215 Some((prev_insn >> 20) as u8)
216 } else {
217 None
218 }
219 } else {
220 None
221 };
222 }
223 }
224
225 if cfg!(target_arch = "x86_64") || cfg!(target_arch = "aarch64") {
227 val = val.and_then(|val| {
228 if val & MAGIC == MAGIC {
229 Some(val & 0xf)
230 } else {
231 None
232 }
233 });
234 }
235
236 match val {
237 None => None,
238 Some(val) => match val {
239 0 => Some(TrapCode::StackOverflow),
240 1 => Some(TrapCode::HeapAccessOutOfBounds),
241 2 => Some(TrapCode::HeapMisaligned),
242 3 => Some(TrapCode::TableAccessOutOfBounds),
243 4 => Some(TrapCode::IndirectCallToNull),
244 5 => Some(TrapCode::BadSignature),
245 6 => Some(TrapCode::IntegerOverflow),
246 7 => Some(TrapCode::IntegerDivisionByZero),
247 8 => Some(TrapCode::BadConversionToInteger),
248 9 => Some(TrapCode::UnreachableCodeReached),
249 10 => Some(TrapCode::UnalignedAtomic),
250 _ => None,
251 },
252 }
253}
254
255cfg_select! {
256 unix => {
257 static mut PREV_SIGSEGV: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
258 static mut PREV_SIGBUS: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
259 static mut PREV_SIGILL: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
260 static mut PREV_SIGFPE: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
261
262 #[cfg(feature = "experimental-host-interrupt")]
263 static mut PREV_SIGUSR1: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
264
265 unsafe fn platform_init() { unsafe {
266 let register = |slot: &mut MaybeUninit<libc::sigaction>, signal: i32, nodefer: bool| {
267 let mut handler: libc::sigaction = mem::zeroed();
268 handler.sa_flags = libc::SA_SIGINFO | libc::SA_ONSTACK;
282 if nodefer {
283 handler.sa_flags |= libc::SA_NODEFER;
284 }
285 handler.sa_sigaction = trap_handler as *const () as usize;
286 libc::sigemptyset(&mut handler.sa_mask);
287 if libc::sigaction(signal, &handler, slot.as_mut_ptr()) != 0 {
288 panic!(
289 "unable to install signal handler: {}",
290 io::Error::last_os_error(),
291 );
292 }
293 };
294
295 register(&mut PREV_SIGSEGV, libc::SIGSEGV, true);
297
298 register(&mut PREV_SIGILL, libc::SIGILL, true);
300
301 #[cfg(feature = "experimental-host-interrupt")]
306 register(&mut PREV_SIGUSR1, libc::SIGUSR1, false);
307
308 if cfg!(target_arch = "x86") || cfg!(target_arch = "x86_64") {
310 register(&mut PREV_SIGFPE, libc::SIGFPE, true);
311 }
312
313 if cfg!(target_arch = "arm") || cfg!(target_vendor = "apple") {
316 register(&mut PREV_SIGBUS, libc::SIGBUS, true);
317 }
318
319 #[cfg(target_vendor = "apple")]
322 {
323 use mach2::exception_types::*;
324 use mach2::kern_return::*;
325 use mach2::port::*;
326 use mach2::thread_status::*;
327 use mach2::traps::*;
328 use mach2::mach_types::*;
329
330 unsafe extern "C" {
331 fn task_set_exception_ports(
332 task: task_t,
333 exception_mask: exception_mask_t,
334 new_port: mach_port_t,
335 behavior: exception_behavior_t,
336 new_flavor: thread_state_flavor_t,
337 ) -> kern_return_t;
338 }
339
340 #[allow(non_snake_case)]
341 #[cfg(target_arch = "x86_64")]
342 let MACHINE_THREAD_STATE = x86_THREAD_STATE64;
343 #[allow(non_snake_case)]
344 #[cfg(target_arch = "aarch64")]
345 let MACHINE_THREAD_STATE = 6;
346
347 task_set_exception_ports(
348 mach_task_self(),
349 EXC_MASK_BAD_ACCESS | EXC_MASK_ARITHMETIC | EXC_MASK_BAD_INSTRUCTION,
350 MACH_PORT_NULL,
351 EXCEPTION_STATE_IDENTITY as exception_behavior_t,
352 MACHINE_THREAD_STATE,
353 );
354 }
355 }}
356
357 unsafe extern "C" fn trap_handler(
358 signum: libc::c_int,
359 siginfo: *mut libc::siginfo_t,
360 context: *mut libc::c_void,
361 ) { unsafe {
362 let previous = match signum {
363 libc::SIGSEGV => &PREV_SIGSEGV,
364 libc::SIGBUS => &PREV_SIGBUS,
365 libc::SIGFPE => &PREV_SIGFPE,
366 libc::SIGILL => &PREV_SIGILL,
367 #[cfg(feature = "experimental-host-interrupt")]
368 libc::SIGUSR1 => &PREV_SIGUSR1,
369 _ => panic!("unknown signal: {signum}"),
370 };
371 let maybe_fault_address = match signum {
373 libc::SIGSEGV | libc::SIGBUS => {
374 Some((*siginfo).si_addr() as usize)
375 }
376 _ => None,
377 };
378 let trap_code = match signum {
379 libc::SIGILL => {
381 let addr = (*siginfo).si_addr() as usize;
382 process_illegal_op(addr)
383 }
384 #[cfg(feature = "experimental-host-interrupt")]
385 libc::SIGUSR1 => {
386 if !interrupt_registry::on_interrupted() {
389 return;
390 }
391 Some(TrapCode::HostInterrupt)
392 }
393 _ => None,
394 };
395 let ucontext = &mut *(context as *mut ucontext_t);
396 let (pc, sp) = get_pc_sp(ucontext);
397 let handled = TrapHandlerContext::handle_trap(
398 pc,
399 sp,
400 maybe_fault_address,
401 trap_code,
402 |regs| update_context(ucontext, regs),
403 |handler| handler(signum, siginfo, context),
404 );
405
406 if handled {
407 return;
408 }
409
410 #[cfg(feature = "experimental-host-interrupt")]
413 if signum == libc::SIGUSR1 {
414 return;
415 }
416
417 let previous = &*previous.as_ptr();
427 if previous.sa_flags & libc::SA_SIGINFO != 0 {
428 mem::transmute::<
429 usize,
430 extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void),
431 >(previous.sa_sigaction)(signum, siginfo, context)
432 } else if previous.sa_sigaction == libc::SIG_DFL
433 {
434 libc::sigaction(signum, previous, ptr::null_mut());
435 } else if previous.sa_sigaction != libc::SIG_IGN {
436 mem::transmute::<usize, extern "C" fn(libc::c_int)>(
437 previous.sa_sigaction
438 )(signum)
439 }
440 }}
441
442 unsafe fn get_pc_sp(context: &ucontext_t) -> (usize, usize) {
443 let (pc, sp);
444 cfg_select! {
445 all(
446 any(target_os = "linux", target_os = "android"),
447 target_arch = "x86_64",
448 ) => {
449 pc = context.uc_mcontext.gregs[libc::REG_RIP as usize] as usize;
450 sp = context.uc_mcontext.gregs[libc::REG_RSP as usize] as usize;
451 }
452 all(
453 any(target_os = "linux", target_os = "android"),
454 target_arch = "x86",
455 ) => {
456 pc = context.uc_mcontext.gregs[libc::REG_EIP as usize] as usize;
457 sp = context.uc_mcontext.gregs[libc::REG_ESP as usize] as usize;
458 }
459 all(target_os = "freebsd", target_arch = "x86") => {
460 pc = context.uc_mcontext.mc_eip as usize;
461 sp = context.uc_mcontext.mc_esp as usize;
462 }
463 all(target_os = "freebsd", target_arch = "x86_64") => {
464 pc = context.uc_mcontext.mc_rip as usize;
465 sp = context.uc_mcontext.mc_rsp as usize;
466 }
467 all(target_vendor = "apple", target_arch = "x86_64") => {
468 let mcontext = unsafe { &*context.uc_mcontext };
469 pc = mcontext.__ss.__rip as usize;
470 sp = mcontext.__ss.__rsp as usize;
471 }
472 all(
473 any(target_os = "linux", target_os = "android"),
474 target_arch = "aarch64",
475 ) => {
476 pc = context.uc_mcontext.pc as usize;
477 sp = context.uc_mcontext.sp as usize;
478 }
479 all(
480 any(target_os = "linux", target_os = "android"),
481 target_arch = "arm",
482 ) => {
483 pc = context.uc_mcontext.arm_pc as usize;
484 sp = context.uc_mcontext.arm_sp as usize;
485 }
486 all(
487 any(target_os = "linux", target_os = "android"),
488 any(target_arch = "riscv64", target_arch = "riscv32"),
489 ) => {
490 pc = context.uc_mcontext.__gregs[libc::REG_PC] as usize;
491 sp = context.uc_mcontext.__gregs[libc::REG_SP] as usize;
492 }
493 all(target_vendor = "apple", target_arch = "aarch64") => {
494 let mcontext = unsafe { &*context.uc_mcontext };
495 pc = mcontext.__ss.__pc as usize;
496 sp = mcontext.__ss.__sp as usize;
497 }
498 all(target_os = "freebsd", target_arch = "aarch64") => {
499 pc = context.uc_mcontext.mc_gpregs.gp_elr as usize;
500 sp = context.uc_mcontext.mc_gpregs.gp_sp as usize;
501 }
502 all(target_os = "linux", target_arch = "loongarch64") => {
503 pc = context.uc_mcontext.__gregs[1] as usize;
504 sp = context.uc_mcontext.__gregs[3] as usize;
505 }
506 all(target_os = "linux", target_arch = "powerpc64") => {
507 pc = (*context.uc_mcontext.regs).nip as usize;
508 sp = (*context.uc_mcontext.regs).gpr[1] as usize;
509 }
510 _ => {
511 compile_error!("Unsupported platform");
512 }
513 };
514 (pc, sp)
515 }
516
517 unsafe fn update_context(context: &mut ucontext_t, regs: TrapHandlerRegs) {
518 cfg_select! {
519 all(
520 any(target_os = "linux", target_os = "android"),
521 target_arch = "x86_64",
522 ) => {
523 let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs;
524 context.uc_mcontext.gregs[libc::REG_RIP as usize] = rip as i64;
525 context.uc_mcontext.gregs[libc::REG_RSP as usize] = rsp as i64;
526 context.uc_mcontext.gregs[libc::REG_RBP as usize] = rbp as i64;
527 context.uc_mcontext.gregs[libc::REG_RDI as usize] = rdi as i64;
528 context.uc_mcontext.gregs[libc::REG_RSI as usize] = rsi as i64;
529 }
530 all(
531 any(target_os = "linux", target_os = "android"),
532 target_arch = "x86",
533 ) => {
534 let TrapHandlerRegs { eip, esp, ebp, ecx, edx } = regs;
535 context.uc_mcontext.gregs[libc::REG_EIP as usize] = eip as i32;
536 context.uc_mcontext.gregs[libc::REG_ESP as usize] = esp as i32;
537 context.uc_mcontext.gregs[libc::REG_EBP as usize] = ebp as i32;
538 context.uc_mcontext.gregs[libc::REG_ECX as usize] = ecx as i32;
539 context.uc_mcontext.gregs[libc::REG_EDX as usize] = edx as i32;
540 }
541 all(target_vendor = "apple", target_arch = "x86_64") => {
542 let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs;
543 let mcontext = unsafe { &mut *context.uc_mcontext };
544 mcontext.__ss.__rip = rip;
545 mcontext.__ss.__rsp = rsp;
546 mcontext.__ss.__rbp = rbp;
547 mcontext.__ss.__rdi = rdi;
548 mcontext.__ss.__rsi = rsi;
549 }
550 all(target_os = "freebsd", target_arch = "x86") => {
551 let TrapHandlerRegs { eip, esp, ebp, ecx, edx } = regs;
552 context.uc_mcontext.mc_eip = eip as libc::register_t;
553 context.uc_mcontext.mc_esp = esp as libc::register_t;
554 context.uc_mcontext.mc_ebp = ebp as libc::register_t;
555 context.uc_mcontext.mc_ecx = ecx as libc::register_t;
556 context.uc_mcontext.mc_edx = edx as libc::register_t;
557 }
558 all(target_os = "freebsd", target_arch = "x86_64") => {
559 let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs;
560 context.uc_mcontext.mc_rip = rip as libc::register_t;
561 context.uc_mcontext.mc_rsp = rsp as libc::register_t;
562 context.uc_mcontext.mc_rbp = rbp as libc::register_t;
563 context.uc_mcontext.mc_rdi = rdi as libc::register_t;
564 context.uc_mcontext.mc_rsi = rsi as libc::register_t;
565 }
566 all(
567 any(target_os = "linux", target_os = "android"),
568 target_arch = "aarch64",
569 ) => {
570 let TrapHandlerRegs { pc, sp, x0, x1, x29, lr } = regs;
571 context.uc_mcontext.pc = pc;
572 context.uc_mcontext.sp = sp;
573 context.uc_mcontext.regs[0] = x0;
574 context.uc_mcontext.regs[1] = x1;
575 context.uc_mcontext.regs[29] = x29;
576 context.uc_mcontext.regs[30] = lr;
577 }
578 all(
579 any(target_os = "linux", target_os = "android"),
580 target_arch = "arm",
581 ) => {
582 let TrapHandlerRegs {
583 pc,
584 r0,
585 r1,
586 r7,
587 r11,
588 r13,
589 r14,
590 cpsr_thumb,
591 cpsr_endian,
592 } = regs;
593 context.uc_mcontext.arm_pc = pc;
594 context.uc_mcontext.arm_r0 = r0;
595 context.uc_mcontext.arm_r1 = r1;
596 context.uc_mcontext.arm_r7 = r7;
597 context.uc_mcontext.arm_fp = r11;
598 context.uc_mcontext.arm_sp = r13;
599 context.uc_mcontext.arm_lr = r14;
600 if cpsr_thumb {
601 context.uc_mcontext.arm_cpsr |= 0x20;
602 } else {
603 context.uc_mcontext.arm_cpsr &= !0x20;
604 }
605 if cpsr_endian {
606 context.uc_mcontext.arm_cpsr |= 0x200;
607 } else {
608 context.uc_mcontext.arm_cpsr &= !0x200;
609 }
610 }
611 all(
612 any(target_os = "linux", target_os = "android"),
613 any(target_arch = "riscv64", target_arch = "riscv32"),
614 ) => {
615 let TrapHandlerRegs { pc, ra, sp, a0, a1, s0 } = regs;
616 context.uc_mcontext.__gregs[libc::REG_PC] = pc as libc::c_ulong;
617 context.uc_mcontext.__gregs[libc::REG_RA] = ra as libc::c_ulong;
618 context.uc_mcontext.__gregs[libc::REG_SP] = sp as libc::c_ulong;
619 context.uc_mcontext.__gregs[libc::REG_A0] = a0 as libc::c_ulong;
620 context.uc_mcontext.__gregs[libc::REG_A0 + 1] = a1 as libc::c_ulong;
621 context.uc_mcontext.__gregs[libc::REG_S0] = s0 as libc::c_ulong;
622 }
623 all(target_vendor = "apple", target_arch = "aarch64") => {
624 let TrapHandlerRegs { pc, sp, x0, x1, x29, lr } = regs;
625 let mcontext = unsafe { &mut *context.uc_mcontext };
626 mcontext.__ss.__pc = pc;
627 mcontext.__ss.__sp = sp;
628 mcontext.__ss.__x[0] = x0;
629 mcontext.__ss.__x[1] = x1;
630 mcontext.__ss.__fp = x29;
631 mcontext.__ss.__lr = lr;
632 }
633 all(target_os = "freebsd", target_arch = "aarch64") => {
634 let TrapHandlerRegs { pc, sp, x0, x1, x29, lr } = regs;
635 context.uc_mcontext.mc_gpregs.gp_elr = pc as libc::register_t;
636 context.uc_mcontext.mc_gpregs.gp_sp = sp as libc::register_t;
637 context.uc_mcontext.mc_gpregs.gp_x[0] = x0 as libc::register_t;
638 context.uc_mcontext.mc_gpregs.gp_x[1] = x1 as libc::register_t;
639 context.uc_mcontext.mc_gpregs.gp_x[29] = x29 as libc::register_t;
640 context.uc_mcontext.mc_gpregs.gp_lr = lr as libc::register_t;
641 }
642 all(target_os = "linux", target_arch = "loongarch64") => {
643 let TrapHandlerRegs { pc, sp, a0, a1, fp, ra } = regs;
644 context.uc_mcontext.__pc = pc;
645 context.uc_mcontext.__gregs[1] = ra;
646 context.uc_mcontext.__gregs[3] = sp;
647 context.uc_mcontext.__gregs[4] = a0;
648 context.uc_mcontext.__gregs[5] = a1;
649 context.uc_mcontext.__gregs[22] = fp;
650 }
651 all(target_os = "linux", target_arch = "powerpc64") => {
652 let TrapHandlerRegs { pc, sp, r3, r4, r31, lr } = regs;
653 (*context.uc_mcontext.regs).nip = pc;
654 (*context.uc_mcontext.regs).gpr[1] = sp;
655 (*context.uc_mcontext.regs).gpr[3] = r3;
656 (*context.uc_mcontext.regs).gpr[4] = r4;
657 (*context.uc_mcontext.regs).gpr[31] = r31;
658 (*context.uc_mcontext.regs).link = lr;
659 }
660 _ => {
661 compile_error!("Unsupported platform");
662 }
663 };
664 }
665 }
666 target_os = "windows" => {
667 use windows_sys::Win32::System::Diagnostics::Debug::{
668 AddVectoredExceptionHandler,
669 CONTEXT,
670 EXCEPTION_CONTINUE_EXECUTION,
671 EXCEPTION_CONTINUE_SEARCH,
672 EXCEPTION_POINTERS,
673 };
674 use windows_sys::Win32::Foundation::{
675 EXCEPTION_ACCESS_VIOLATION,
676 EXCEPTION_ILLEGAL_INSTRUCTION,
677 EXCEPTION_INT_DIVIDE_BY_ZERO,
678 EXCEPTION_INT_OVERFLOW,
679 EXCEPTION_STACK_OVERFLOW,
680 };
681
682 unsafe fn platform_init() {
683 unsafe {
684 let handler = AddVectoredExceptionHandler(1, Some(exception_handler));
688 if handler.is_null() {
689 panic!("failed to add exception handler: {}", io::Error::last_os_error());
690 }
691 }
692 }
693
694 unsafe extern "system" fn exception_handler(
695 exception_info: *mut EXCEPTION_POINTERS
696 ) -> i32 {
697 unsafe {
698 let record = &*(*exception_info).ExceptionRecord;
702 if record.ExceptionCode != EXCEPTION_ACCESS_VIOLATION &&
703 record.ExceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION &&
704 record.ExceptionCode != EXCEPTION_STACK_OVERFLOW &&
705 record.ExceptionCode != EXCEPTION_INT_DIVIDE_BY_ZERO &&
706 record.ExceptionCode != EXCEPTION_INT_OVERFLOW
707 {
708 return EXCEPTION_CONTINUE_SEARCH;
709 }
710
711 let context = &mut *(*exception_info).ContextRecord;
724 let (pc, sp) = get_pc_sp(context);
725
726 let maybe_fault_address = match record.ExceptionCode {
728 EXCEPTION_ACCESS_VIOLATION => Some(record.ExceptionInformation[1]),
729 EXCEPTION_STACK_OVERFLOW => Some(sp),
730 _ => None,
731 };
732 let trap_code = match record.ExceptionCode {
733 EXCEPTION_ILLEGAL_INSTRUCTION => {
735 process_illegal_op(pc)
736 }
737 _ => None,
738 };
739 let handled = TrapHandlerContext::handle_trap(
742 pc,
743 sp,
744 maybe_fault_address,
745 trap_code,
746 |regs| update_context(context, regs),
747 |handler| handler(exception_info),
748 );
749
750 if handled {
751 EXCEPTION_CONTINUE_EXECUTION
752 } else {
753 EXCEPTION_CONTINUE_SEARCH
754 }
755 }
756 }
757
758 unsafe fn get_pc_sp(context: &CONTEXT) -> (usize, usize) {
759 let (pc, sp);
760 cfg_select! {
761 target_arch = "x86_64" => {
762 pc = context.Rip as usize;
763 sp = context.Rsp as usize;
764 }
765 target_arch = "x86" => {
766 pc = context.Eip as usize;
767 sp = context.Esp as usize;
768 }
769 _ => {
770 compile_error!("Unsupported platform");
771 }
772 };
773 (pc, sp)
774 }
775
776 unsafe fn update_context(context: &mut CONTEXT, regs: TrapHandlerRegs) {
777 cfg_select! {
778 target_arch = "x86_64" => {
779 let TrapHandlerRegs { rip, rsp, rbp, rdi, rsi } = regs;
780 context.Rip = rip;
781 context.Rsp = rsp;
782 context.Rbp = rbp;
783 context.Rdi = rdi;
784 context.Rsi = rsi;
785 }
786 target_arch = "x86" => {
787 let TrapHandlerRegs { eip, esp, ebp, ecx, edx } = regs;
788 context.Eip = eip;
789 context.Esp = esp;
790 context.Ebp = ebp;
791 context.Ecx = ecx;
792 context.Edx = edx;
793 }
794 _ => {
795 compile_error!("Unsupported platform");
796 }
797 };
798 }
799 }
800}
801
802pub fn init_traps() {
811 if cfg!(miri) {
816 return;
817 }
818 static INIT: Once = Once::new();
819 INIT.call_once(|| unsafe {
820 platform_init();
821 });
822}
823
824pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! {
837 unsafe { unwind_with(UnwindReason::UserTrap(data)) }
838}
839
840pub unsafe fn raise_lib_trap(trap: Trap) -> ! {
852 unsafe { unwind_with(UnwindReason::LibTrap(trap)) }
853}
854
855pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
864 unsafe { unwind_with(UnwindReason::Panic(payload)) }
865}
866
867pub unsafe fn catch_traps<F, R: 'static>(
874 trap_handler: Option<*const TrapHandlerFn<'static>>,
875 config: &VMConfig,
876 closure: F,
877) -> Result<R, Trap>
878where
879 F: FnOnce() -> R + 'static,
880{
881 lazy_per_thread_init()?;
883 let stack_size = config
884 .wasm_stack_size
885 .unwrap_or_else(|| DEFAULT_STACK_SIZE.load(Ordering::Relaxed));
886 on_wasm_stack(stack_size, trap_handler, closure).map_err(UnwindReason::into_trap)
887}
888
889thread_local! {
898 static YIELDER: Cell<Option<NonNull<Yielder<(), UnwindReason>>>> = const { Cell::new(None) };
899 static TRAP_HANDLER: AtomicPtr<TrapHandlerContext> = const { AtomicPtr::new(ptr::null_mut()) };
900}
901
902#[allow(clippy::type_complexity)]
905struct TrapHandlerContext {
906 inner: *const u8,
907 handle_trap: fn(
908 *const u8,
909 usize,
910 usize,
911 Option<usize>,
912 Option<TrapCode>,
913 &mut dyn FnMut(TrapHandlerRegs),
914 ) -> bool,
915 custom_trap: Option<*const TrapHandlerFn<'static>>,
916}
917struct TrapHandlerContextInner<T> {
918 coro_trap_handler: CoroutineTrapHandler<Result<T, UnwindReason>>,
921}
922
923impl TrapHandlerContext {
924 fn install<T, R>(
927 custom_trap: Option<*const TrapHandlerFn<'static>>,
928 coro_trap_handler: CoroutineTrapHandler<Result<T, UnwindReason>>,
929 f: impl FnOnce() -> R,
930 ) -> R {
931 fn func<T>(
933 ptr: *const u8,
934 pc: usize,
935 sp: usize,
936 maybe_fault_address: Option<usize>,
937 trap_code: Option<TrapCode>,
938 update_regs: &mut dyn FnMut(TrapHandlerRegs),
939 ) -> bool {
940 unsafe {
941 (*(ptr as *const TrapHandlerContextInner<T>)).handle_trap(
942 pc,
943 sp,
944 maybe_fault_address,
945 trap_code,
946 update_regs,
947 )
948 }
949 }
950 let inner = TrapHandlerContextInner { coro_trap_handler };
951 let ctx = Self {
952 inner: &inner as *const _ as *const u8,
953 handle_trap: func::<T>,
954 custom_trap,
955 };
956
957 compiler_fence(Ordering::Release);
958 let prev = TRAP_HANDLER.with(|ptr| {
959 let prev = ptr.load(Ordering::Relaxed);
960 ptr.store(&ctx as *const Self as *mut Self, Ordering::Relaxed);
961 prev
962 });
963
964 defer! {
965 TRAP_HANDLER.with(|ptr| ptr.store(prev, Ordering::Relaxed));
966 compiler_fence(Ordering::Acquire);
967 }
968
969 f()
970 }
971
972 unsafe fn handle_trap(
974 pc: usize,
975 sp: usize,
976 maybe_fault_address: Option<usize>,
977 trap_code: Option<TrapCode>,
978 mut update_regs: impl FnMut(TrapHandlerRegs),
979 call_handler: impl Fn(&TrapHandlerFn<'static>) -> bool,
980 ) -> bool {
981 unsafe {
982 let ptr = TRAP_HANDLER.with(|ptr| ptr.load(Ordering::Relaxed));
983 if ptr.is_null() {
984 return false;
985 }
986
987 let ctx = &*ptr;
988
989 if let Some(trap_handler) = ctx.custom_trap
991 && call_handler(&*trap_handler)
992 {
993 return true;
994 }
995
996 (ctx.handle_trap)(
997 ctx.inner,
998 pc,
999 sp,
1000 maybe_fault_address,
1001 trap_code,
1002 &mut update_regs,
1003 )
1004 }
1005 }
1006}
1007
1008impl<T> TrapHandlerContextInner<T> {
1009 unsafe fn handle_trap(
1010 &self,
1011 pc: usize,
1012 sp: usize,
1013 maybe_fault_address: Option<usize>,
1014 trap_code: Option<TrapCode>,
1015 update_regs: &mut dyn FnMut(TrapHandlerRegs),
1016 ) -> bool {
1017 unsafe {
1018 if !self.coro_trap_handler.stack_ptr_in_bounds(sp) {
1021 return false;
1022 }
1023
1024 let signal_trap = trap_code.or_else(|| {
1025 maybe_fault_address.map(|addr| {
1026 if self.coro_trap_handler.stack_ptr_in_bounds(addr) {
1027 TrapCode::StackOverflow
1028 } else {
1029 TrapCode::HeapAccessOutOfBounds
1030 }
1031 })
1032 });
1033
1034 let backtrace = if signal_trap == Some(TrapCode::StackOverflow) {
1041 Backtrace::from(vec![])
1042 } else {
1043 Backtrace::new_unresolved()
1044 };
1045
1046 let unwind = UnwindReason::WasmTrap {
1049 backtrace,
1050 signal_trap,
1051 pc,
1052 };
1053 let regs = self
1054 .coro_trap_handler
1055 .setup_trap_handler(move || Err(unwind));
1056 update_regs(regs);
1057 true
1058 }
1059 }
1060}
1061
1062unsafe fn unwind_with(reason: UnwindReason) -> ! {
1063 unsafe {
1064 let yielder = YIELDER
1065 .with(|cell| cell.replace(None))
1066 .expect("not running on Wasm stack");
1067
1068 yielder.as_ref().suspend(reason);
1069
1070 unreachable!();
1072 }
1073}
1074
1075fn on_wasm_stack<F: FnOnce() -> T + 'static, T: 'static>(
1079 stack_size: usize,
1080 trap_handler: Option<*const TrapHandlerFn<'static>>,
1081 f: F,
1082) -> Result<T, UnwindReason> {
1083 let stack = acquire_stack(stack_size);
1090 let mut stack = scopeguard::guard(stack, release_stack);
1091
1092 let coro = ScopedCoroutine::with_stack(&mut *stack, move |yielder, ()| {
1094 YIELDER.with(|cell| cell.set(Some(yielder.into())));
1096
1097 Ok(f())
1098 });
1099
1100 defer! {
1102 YIELDER.with(|cell| cell.set(None));
1103 }
1104
1105 coro.scope(|mut coro_ref| {
1106 TrapHandlerContext::install(trap_handler, coro_ref.trap_handler(), || {
1109 match coro_ref.resume(()) {
1110 CoroutineResult::Yield(trap) => {
1111 unsafe {
1114 coro_ref.force_reset();
1115 }
1116 Err(trap)
1117 }
1118 CoroutineResult::Return(result) => result,
1119 }
1120 })
1121 })
1122}
1123
1124pub fn on_host_stack<F: FnOnce() -> T, T>(f: F) -> T {
1133 let yielder_ptr = YIELDER.with(|cell| cell.replace(None));
1136
1137 let yielder = match yielder_ptr {
1140 Some(ptr) => unsafe { ptr.as_ref() },
1141 None => return f(),
1142 };
1143
1144 defer! {
1146 YIELDER.with(|cell| cell.set(yielder_ptr));
1147 }
1148
1149 struct SendWrapper<T>(T);
1153 unsafe impl<T> Send for SendWrapper<T> {}
1154 let wrapped = SendWrapper(f);
1155 yielder.on_parent_stack(move || {
1156 let wrapped = wrapped;
1157 (wrapped.0)()
1158 })
1159}
1160
1161#[cfg(windows)]
1162pub fn lazy_per_thread_init() -> Result<(), Trap> {
1163 use windows_sys::Win32::System::Threading::SetThreadStackGuarantee;
1167 if unsafe { SetThreadStackGuarantee(&mut 0x10000) } == 0 {
1168 panic!("failed to set thread stack guarantee");
1169 }
1170
1171 Ok(())
1172}
1173
1174#[cfg(unix)]
1181pub fn lazy_per_thread_init() -> Result<(), Trap> {
1182 use std::ptr::null_mut;
1183
1184 thread_local! {
1185 static TLS: Tls = unsafe { init_sigstack() };
1188 }
1189
1190 const MIN_STACK_SIZE: usize = ByteSize::kib(64).as_u64() as usize;
1193
1194 enum Tls {
1195 OutOfMemory,
1196 Allocated {
1197 mmap_ptr: *mut libc::c_void,
1198 mmap_size: usize,
1199 },
1200 BigEnough,
1201 }
1202
1203 unsafe fn init_sigstack() -> Tls {
1204 unsafe {
1205 let mut old_stack = mem::zeroed();
1208 let r = libc::sigaltstack(ptr::null(), &mut old_stack);
1209 assert_eq!(r, 0, "learning about sigaltstack failed");
1210 if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
1211 return Tls::BigEnough;
1212 }
1213
1214 let page_size: usize = region::page::size();
1217 let guard_size = page_size;
1218 let alloc_size = guard_size + MIN_STACK_SIZE;
1219
1220 let ptr = libc::mmap(
1221 null_mut(),
1222 alloc_size,
1223 libc::PROT_NONE,
1224 libc::MAP_PRIVATE | libc::MAP_ANON,
1225 -1,
1226 0,
1227 );
1228 if ptr == libc::MAP_FAILED {
1229 return Tls::OutOfMemory;
1230 }
1231
1232 let stack_ptr = (ptr as usize + guard_size) as *mut libc::c_void;
1235 let r = libc::mprotect(
1236 stack_ptr,
1237 MIN_STACK_SIZE,
1238 libc::PROT_READ | libc::PROT_WRITE,
1239 );
1240 assert_eq!(r, 0, "mprotect to configure memory for sigaltstack failed");
1241 let new_stack = libc::stack_t {
1242 ss_sp: stack_ptr,
1243 ss_flags: 0,
1244 ss_size: MIN_STACK_SIZE,
1245 };
1246 let r = libc::sigaltstack(&new_stack, ptr::null_mut());
1247 assert_eq!(r, 0, "registering new sigaltstack failed");
1248
1249 Tls::Allocated {
1250 mmap_ptr: ptr,
1251 mmap_size: alloc_size,
1252 }
1253 }
1254 }
1255
1256 return TLS.with(|tls| {
1259 if let Tls::OutOfMemory = tls {
1260 Err(Trap::oom())
1261 } else {
1262 Ok(())
1263 }
1264 });
1265
1266 impl Drop for Tls {
1267 fn drop(&mut self) {
1268 let (ptr, size) = match self {
1269 Self::Allocated {
1270 mmap_ptr,
1271 mmap_size,
1272 } => (*mmap_ptr, *mmap_size),
1273 _ => return,
1274 };
1275 unsafe {
1276 let r = libc::munmap(ptr, size);
1278 debug_assert_eq!(r, 0, "munmap failed during thread shutdown");
1279 }
1280 }
1281 }
1282}
1283
1284#[cfg(test)]
1285mod tests {
1286 use super::*;
1287 use std::sync::Mutex;
1288
1289 static GLOBAL_STATE: Mutex<()> = Mutex::new(());
1293
1294 struct RestoreStackSize(usize);
1296 impl Drop for RestoreStackSize {
1297 fn drop(&mut self) {
1298 set_stack_size(self.0);
1299 }
1300 }
1301
1302 #[test]
1303 fn max_stack_size_is_100mb() {
1304 assert_eq!(MAX_STACK_SIZE, ByteSize::mib(100).as_u64() as usize);
1305 }
1306
1307 #[test]
1308 fn get_set_stack_size_roundtrip() {
1309 let _lock = GLOBAL_STATE.lock().unwrap();
1310 let _restore = RestoreStackSize(get_stack_size());
1311 let new_size = ByteSize::mib(4).as_u64() as usize;
1312 set_stack_size(new_size);
1313 assert_eq!(get_stack_size(), new_size);
1314 }
1315
1316 #[test]
1317 fn set_stack_size_clamps_to_min() {
1318 let _lock = GLOBAL_STATE.lock().unwrap();
1319 let _restore = RestoreStackSize(get_stack_size());
1320 set_stack_size(1); assert_eq!(get_stack_size(), ByteSize::kib(8).as_u64() as usize);
1322 }
1323
1324 #[test]
1325 fn set_stack_size_clamps_to_max() {
1326 let _lock = GLOBAL_STATE.lock().unwrap();
1327 let _restore = RestoreStackSize(get_stack_size());
1328 set_stack_size(usize::MAX);
1329 assert_eq!(get_stack_size(), MAX_STACK_SIZE);
1330 }
1331
1332 #[test]
1333 fn drain_stack_pool_empties_pool() {
1334 let _lock = GLOBAL_STATE.lock().unwrap();
1335 let stack = DefaultStack::new(ByteSize::mib(1).as_u64() as usize).unwrap();
1336 STACK_POOL.push(stack);
1337 assert!(!STACK_POOL.is_empty());
1338 drain_stack_pool();
1339 assert!(STACK_POOL.is_empty());
1340 }
1341
1342 #[test]
1343 fn drain_stack_pool_is_idempotent() {
1344 let _lock = GLOBAL_STATE.lock().unwrap();
1345 drain_stack_pool();
1346 drain_stack_pool(); assert!(STACK_POOL.is_empty());
1348 }
1349
1350 #[test]
1360 fn pool_returns_stale_stack_without_drain() {
1361 let _lock = GLOBAL_STATE.lock().unwrap();
1362 let _restore = RestoreStackSize(get_stack_size());
1363 drain_stack_pool();
1364
1365 let small_size = ByteSize::kib(500).as_u64() as usize;
1367 let small_stack = DefaultStack::new(small_size).unwrap();
1368 STACK_POOL.push(small_stack);
1369
1370 let big_size = ByteSize::mib(1).as_u64() as usize;
1372 set_stack_size(big_size);
1373 assert_eq!(get_stack_size(), big_size);
1374
1375 let stale = STACK_POOL.pop();
1379 assert!(
1380 stale.is_some(),
1381 "pool should still contain the old stack (the bug scenario)"
1382 );
1383
1384 STACK_POOL.push(stale.unwrap());
1386 drain_stack_pool();
1387 assert!(
1388 STACK_POOL.pop().is_none(),
1389 "after drain, pool must be empty so a fresh stack is allocated at the new size"
1390 );
1391 }
1392
1393 #[test]
1396 fn on_wasm_stack_discards_undersized_stack() {
1397 let _lock = GLOBAL_STATE.lock().unwrap();
1398 let _restore = RestoreStackSize(get_stack_size());
1399 drain_stack_pool();
1400 clear_tls_stack();
1401
1402 let small_size = ByteSize::kib(500).as_u64() as usize;
1404 let small_stack = DefaultStack::new(small_size).unwrap();
1405 STACK_POOL.push(small_stack);
1406
1407 let big_size = ByteSize::mib(1).as_u64() as usize;
1409 let result = on_wasm_stack(big_size, None, || 42);
1410
1411 assert_eq!(result.expect("on_wasm_stack should succeed"), 42);
1412 let returned = TLS_STACK
1416 .with(|cache| cache.0.take())
1417 .or_else(|| STACK_POOL.pop())
1418 .expect("stack should have been returned to TLS cache or pool");
1419 assert!(
1420 returned.size() >= big_size,
1421 "returned stack must be at least as large as the requested size"
1422 );
1423
1424 clear_tls_stack();
1428 }
1429
1430 #[test]
1434 fn tls_stack_caches_after_first_call() {
1435 let _lock = GLOBAL_STATE.lock().unwrap();
1436 let _restore = RestoreStackSize(get_stack_size());
1437 drain_stack_pool();
1438 clear_tls_stack();
1439
1440 let size = get_stack_size();
1441
1442 assert!(on_wasm_stack(size, None, || ()).is_ok());
1444 assert!(
1445 STACK_POOL.is_empty(),
1446 "pool should still be empty after a TLS-served call"
1447 );
1448
1449 let cached_present = TLS_STACK.with(|cache| {
1451 let taken = cache.0.take();
1452 let present = taken.is_some();
1453 cache.0.set(taken);
1454 present
1455 });
1456 assert!(cached_present, "TLS slot should hold the post-call stack");
1457
1458 assert!(on_wasm_stack(size, None, || ()).is_ok());
1460 assert!(
1461 STACK_POOL.is_empty(),
1462 "second call must not push to the global pool"
1463 );
1464 let still_cached = TLS_STACK.with(|cache| {
1465 let taken = cache.0.take();
1466 let present = taken.is_some();
1467 cache.0.set(taken);
1468 present
1469 });
1470 assert!(
1471 still_cached,
1472 "TLS slot should still hold a stack after the second call"
1473 );
1474
1475 clear_tls_stack();
1480 }
1481
1482 #[test]
1485 fn tls_stack_returns_to_pool_on_thread_exit() {
1486 let lock = GLOBAL_STATE.lock().unwrap();
1499 let _restore = RestoreStackSize(get_stack_size());
1500 drain_stack_pool();
1501 clear_tls_stack();
1502
1503 let size = get_stack_size();
1504 drop(lock);
1505
1506 let handle = std::thread::spawn(move || {
1507 assert!(on_wasm_stack(size, None, || ()).is_ok());
1508 });
1509 handle.join().unwrap();
1510
1511 let _lock = GLOBAL_STATE.lock().unwrap();
1512 let returned = STACK_POOL
1515 .pop()
1516 .expect("thread exit should return TLS-cached stack to the global pool");
1517 assert!(returned.size() >= size);
1518 }
1519
1520 fn clear_tls_stack() {
1528 TLS_STACK.with(|cache| cache.0.set(None));
1529 }
1530
1531 fn stack_id(stack: &DefaultStack) -> usize {
1536 stack.base().get()
1537 }
1538
1539 #[test]
1544 fn acquire_allocates_fresh_when_tls_and_pool_empty() {
1545 let _lock = GLOBAL_STATE.lock().unwrap();
1546 let _restore = RestoreStackSize(get_stack_size());
1547 drain_stack_pool();
1548 clear_tls_stack();
1549
1550 let size = get_stack_size();
1551 let stack = acquire_stack(size);
1552 assert!(
1553 stack.size() >= size,
1554 "freshly allocated stack must satisfy min_size"
1555 );
1556
1557 drop(stack);
1558 clear_tls_stack();
1559 drain_stack_pool();
1560 }
1561
1562 #[test]
1563 fn acquire_prefers_tls_over_pool() {
1564 let _lock = GLOBAL_STATE.lock().unwrap();
1565 let _restore = RestoreStackSize(get_stack_size());
1566 drain_stack_pool();
1567 clear_tls_stack();
1568
1569 let size = get_stack_size();
1570 let tls_stack = DefaultStack::new(size).unwrap();
1571 let tls_id = stack_id(&tls_stack);
1572 TLS_STACK.with(|cache| cache.0.set(Some(tls_stack)));
1573
1574 let pool_stack = DefaultStack::new(size).unwrap();
1575 let pool_id = stack_id(&pool_stack);
1576 STACK_POOL.push(pool_stack);
1577
1578 let got = acquire_stack(size);
1579 assert_eq!(stack_id(&got), tls_id, "acquire must prefer TLS over pool");
1580 assert_ne!(stack_id(&got), pool_id);
1581
1582 drop(got);
1583 clear_tls_stack();
1584 drain_stack_pool();
1585 }
1586
1587 #[test]
1588 fn acquire_uses_pool_when_tls_empty() {
1589 let _lock = GLOBAL_STATE.lock().unwrap();
1590 let _restore = RestoreStackSize(get_stack_size());
1591 drain_stack_pool();
1592 clear_tls_stack();
1593
1594 let size = get_stack_size();
1595 let pool_stack = DefaultStack::new(size).unwrap();
1596 let pool_id = stack_id(&pool_stack);
1597 STACK_POOL.push(pool_stack);
1598
1599 let got = acquire_stack(size);
1600 assert_eq!(
1601 stack_id(&got),
1602 pool_id,
1603 "acquire must consume from pool when TLS is empty"
1604 );
1605 assert!(
1606 STACK_POOL.is_empty(),
1607 "pool stack must be removed when used"
1608 );
1609
1610 drop(got);
1611 clear_tls_stack();
1612 drain_stack_pool();
1613 }
1614
1615 #[test]
1616 fn acquire_discards_undersized_tls_then_allocates() {
1617 let _lock = GLOBAL_STATE.lock().unwrap();
1618 let _restore = RestoreStackSize(get_stack_size());
1619 drain_stack_pool();
1620 clear_tls_stack();
1621
1622 let small_size = ByteSize::kib(512).as_u64() as usize;
1623 let undersized = DefaultStack::new(small_size).unwrap();
1624 TLS_STACK.with(|cache| cache.0.set(Some(undersized)));
1625
1626 let big_size = ByteSize::mib(2).as_u64() as usize;
1627 let got = acquire_stack(big_size);
1628
1629 assert!(
1637 got.size() >= big_size,
1638 "acquired stack must satisfy big_size"
1639 );
1640 let tls_empty = TLS_STACK.with(|cache| {
1641 let s = cache.0.take();
1642 let empty = s.is_none();
1643 cache.0.set(s);
1644 empty
1645 });
1646 assert!(
1647 tls_empty,
1648 "undersized TLS stack must have been taken and discarded"
1649 );
1650 assert!(
1651 STACK_POOL.is_empty(),
1652 "undersized TLS stack must be discarded, not pushed to the pool",
1653 );
1654
1655 drop(got);
1656 clear_tls_stack();
1657 drain_stack_pool();
1658 }
1659
1660 #[test]
1661 fn acquire_discards_undersized_pool_then_allocates() {
1662 let _lock = GLOBAL_STATE.lock().unwrap();
1663 let _restore = RestoreStackSize(get_stack_size());
1664 drain_stack_pool();
1665 clear_tls_stack();
1666
1667 let small_size = ByteSize::kib(512).as_u64() as usize;
1668 let undersized = DefaultStack::new(small_size).unwrap();
1669 STACK_POOL.push(undersized);
1670
1671 let big_size = ByteSize::mib(2).as_u64() as usize;
1672 let got = acquire_stack(big_size);
1673
1674 assert!(
1679 got.size() >= big_size,
1680 "acquired stack must satisfy big_size"
1681 );
1682 assert!(
1683 STACK_POOL.is_empty(),
1684 "undersized pool stack must have been popped, filtered out and dropped",
1685 );
1686
1687 drop(got);
1688 clear_tls_stack();
1689 drain_stack_pool();
1690 }
1691
1692 #[test]
1697 fn release_into_empty_tls_caches_there() {
1698 let _lock = GLOBAL_STATE.lock().unwrap();
1699 drain_stack_pool();
1700 clear_tls_stack();
1701
1702 let size = get_stack_size();
1703 let stack = DefaultStack::new(size).unwrap();
1704 let id = stack_id(&stack);
1705 release_stack(stack);
1706
1707 let in_tls = TLS_STACK
1708 .with(|cache| cache.0.take())
1709 .expect("release into empty TLS should leave the stack in TLS");
1710 assert_eq!(stack_id(&in_tls), id);
1711 assert!(
1712 STACK_POOL.is_empty(),
1713 "pool must not be touched when TLS is empty"
1714 );
1715
1716 drain_stack_pool();
1717 }
1718
1719 #[test]
1720 fn release_into_occupied_tls_displaces_older_to_pool() {
1721 let _lock = GLOBAL_STATE.lock().unwrap();
1722 drain_stack_pool();
1723 clear_tls_stack();
1724
1725 let size = get_stack_size();
1726 let older = DefaultStack::new(size).unwrap();
1727 let older_id = stack_id(&older);
1728 TLS_STACK.with(|cache| cache.0.set(Some(older)));
1729
1730 let newer = DefaultStack::new(size).unwrap();
1731 let newer_id = stack_id(&newer);
1732 release_stack(newer);
1733
1734 let in_tls = TLS_STACK
1735 .with(|cache| cache.0.take())
1736 .expect("TLS should hold the newly-released stack");
1737 assert_eq!(
1738 stack_id(&in_tls),
1739 newer_id,
1740 "newer stack must displace into TLS"
1741 );
1742
1743 let displaced = STACK_POOL
1744 .pop()
1745 .expect("older stack should have been pushed to global pool");
1746 assert_eq!(
1747 stack_id(&displaced),
1748 older_id,
1749 "displaced stack must be the older one"
1750 );
1751
1752 drain_stack_pool();
1753 }
1754
1755 #[test]
1760 fn drain_stack_pool_clears_calling_thread_tls_slot() {
1761 let _lock = GLOBAL_STATE.lock().unwrap();
1762 drain_stack_pool();
1763 clear_tls_stack();
1764
1765 let stack = DefaultStack::new(get_stack_size()).unwrap();
1766 TLS_STACK.with(|cache| cache.0.set(Some(stack)));
1767
1768 drain_stack_pool();
1769
1770 let tls_empty = TLS_STACK.with(|cache| cache.0.take().is_none());
1771 assert!(
1772 tls_empty,
1773 "drain_stack_pool must also clear current thread's TLS slot"
1774 );
1775 assert!(STACK_POOL.is_empty());
1776 }
1777
1778 #[test]
1783 fn on_wasm_stack_passes_closure_value_back() {
1784 let _lock = GLOBAL_STATE.lock().unwrap();
1785 let _restore = RestoreStackSize(get_stack_size());
1786 drain_stack_pool();
1787 clear_tls_stack();
1788
1789 let r = on_wasm_stack(get_stack_size(), None, || 12345u32);
1790 assert_eq!(r.ok(), Some(12345));
1791
1792 clear_tls_stack();
1793 drain_stack_pool();
1794 }
1795
1796 #[test]
1797 fn on_wasm_stack_passes_owning_result_back() {
1798 let _lock = GLOBAL_STATE.lock().unwrap();
1799 let _restore = RestoreStackSize(get_stack_size());
1800 drain_stack_pool();
1801 clear_tls_stack();
1802
1803 let r = on_wasm_stack(get_stack_size(), None, || vec![0u8, 1, 2, 3, 4]);
1807 assert_eq!(r.ok(), Some(vec![0u8, 1, 2, 3, 4]));
1808
1809 clear_tls_stack();
1810 drain_stack_pool();
1811 }
1812
1813 #[test]
1814 fn many_calls_do_not_grow_global_pool() {
1815 let _lock = GLOBAL_STATE.lock().unwrap();
1816 let _restore = RestoreStackSize(get_stack_size());
1817 drain_stack_pool();
1818 clear_tls_stack();
1819
1820 for _ in 0..1000 {
1823 assert!(on_wasm_stack(get_stack_size(), None, || ()).is_ok());
1824 }
1825 assert!(
1826 STACK_POOL.is_empty(),
1827 "1000 sequential calls should not grow the global pool (TLS handles reuse)"
1828 );
1829
1830 clear_tls_stack();
1831 drain_stack_pool();
1832 }
1833
1834 #[test]
1839 fn raise_user_trap_yields_err() {
1840 let _lock = GLOBAL_STATE.lock().unwrap();
1841 let _restore = RestoreStackSize(get_stack_size());
1842 drain_stack_pool();
1843 clear_tls_stack();
1844
1845 let r: Result<(), UnwindReason> = on_wasm_stack(get_stack_size(), None, || unsafe {
1846 raise_user_trap(Box::new(io::Error::other("user trap from test")));
1847 });
1848 assert!(r.is_err(), "raise_user_trap must produce Err");
1849
1850 clear_tls_stack();
1851 drain_stack_pool();
1852 }
1853
1854 #[test]
1855 fn raise_lib_trap_yields_err() {
1856 let _lock = GLOBAL_STATE.lock().unwrap();
1857 let _restore = RestoreStackSize(get_stack_size());
1858 drain_stack_pool();
1859 clear_tls_stack();
1860
1861 let r: Result<(), UnwindReason> = on_wasm_stack(get_stack_size(), None, || unsafe {
1862 raise_lib_trap(Trap::lib(TrapCode::IntegerDivisionByZero));
1863 });
1864 assert!(r.is_err(), "raise_lib_trap must produce Err");
1865
1866 clear_tls_stack();
1867 drain_stack_pool();
1868 }
1869
1870 #[test]
1871 fn resume_panic_yields_err_without_unwinding() {
1872 let _lock = GLOBAL_STATE.lock().unwrap();
1877 let _restore = RestoreStackSize(get_stack_size());
1878 drain_stack_pool();
1879 clear_tls_stack();
1880
1881 let r: Result<(), UnwindReason> = on_wasm_stack(get_stack_size(), None, || unsafe {
1882 resume_panic(Box::new("panic payload from test"));
1883 });
1884 assert!(
1885 r.is_err(),
1886 "resume_panic must surface as Err to on_wasm_stack"
1887 );
1888
1889 clear_tls_stack();
1890 drain_stack_pool();
1891 }
1892
1893 #[test]
1894 fn trap_does_not_corrupt_subsequent_calls() {
1895 let _lock = GLOBAL_STATE.lock().unwrap();
1899 let _restore = RestoreStackSize(get_stack_size());
1900 drain_stack_pool();
1901 clear_tls_stack();
1902
1903 let trapped: Result<(), UnwindReason> = on_wasm_stack(get_stack_size(), None, || unsafe {
1904 raise_user_trap(Box::new(io::Error::other("first call traps")));
1905 });
1906 assert!(trapped.is_err());
1907
1908 let ok = on_wasm_stack(get_stack_size(), None, || 7u32);
1910 assert_eq!(ok.ok(), Some(7), "calls after a trap must still work");
1911
1912 clear_tls_stack();
1913 drain_stack_pool();
1914 }
1915
1916 #[test]
1921 fn on_host_stack_outside_coroutine_runs_inline() {
1922 let _lock = GLOBAL_STATE.lock().unwrap();
1925 let n = on_host_stack(|| 99i32);
1926 assert_eq!(n, 99);
1927 }
1928
1929 #[test]
1930 fn on_host_stack_inside_wasm_switches_and_returns() {
1931 let _lock = GLOBAL_STATE.lock().unwrap();
1932 let _restore = RestoreStackSize(get_stack_size());
1933 drain_stack_pool();
1934 clear_tls_stack();
1935
1936 let r = on_wasm_stack(get_stack_size(), None, || on_host_stack(|| 88i32));
1937 assert_eq!(r.ok(), Some(88));
1938
1939 clear_tls_stack();
1940 drain_stack_pool();
1941 }
1942
1943 #[test]
1948 fn reentrant_call_returns_value() {
1949 let _lock = GLOBAL_STATE.lock().unwrap();
1950 let _restore = RestoreStackSize(get_stack_size());
1951 drain_stack_pool();
1952 clear_tls_stack();
1953
1954 let outer = on_wasm_stack(get_stack_size(), None, || {
1955 on_wasm_stack(get_stack_size(), None, || 42i32).expect("inner must succeed")
1956 });
1957 assert_eq!(outer.ok(), Some(42));
1958
1959 clear_tls_stack();
1960 drain_stack_pool();
1961 }
1962
1963 #[test]
1964 fn reentrant_calls_run_to_completion_under_pool_pressure() {
1965 use std::sync::Arc;
1972 use std::sync::atomic::{AtomicUsize, Ordering as O};
1973
1974 let _lock = GLOBAL_STATE.lock().unwrap();
1975 let _restore = RestoreStackSize(get_stack_size());
1976 drain_stack_pool();
1977 clear_tls_stack();
1978
1979 let pre = DefaultStack::new(get_stack_size()).unwrap();
1982 STACK_POOL.push(pre);
1983
1984 let inner_completed = Arc::new(AtomicUsize::new(0));
1985 let inner_completed_outer = inner_completed.clone();
1986 let _ = on_wasm_stack(get_stack_size(), None, move || {
1987 let inner_completed = inner_completed_outer.clone();
1988 let inner = on_wasm_stack(get_stack_size(), None, move || {
1989 inner_completed.fetch_add(1, O::Relaxed);
1990 });
1991 assert!(inner.is_ok(), "inner re-entrant call must succeed");
1992 });
1993 assert_eq!(
1994 inner_completed.load(O::Relaxed),
1995 1,
1996 "inner closure must have executed exactly once",
1997 );
1998
1999 clear_tls_stack();
2000 drain_stack_pool();
2001 }
2002
2003 #[test]
2004 fn reentrant_inner_trap_does_not_kill_outer() {
2005 let _lock = GLOBAL_STATE.lock().unwrap();
2006 let _restore = RestoreStackSize(get_stack_size());
2007 drain_stack_pool();
2008 clear_tls_stack();
2009
2010 let outer = on_wasm_stack(get_stack_size(), None, || {
2011 let inner: Result<i32, UnwindReason> =
2012 on_wasm_stack(get_stack_size(), None, || unsafe {
2013 raise_user_trap(Box::new(io::Error::other("inner trap")));
2014 });
2015 match inner {
2017 Err(_) => 1234i32,
2018 Ok(_) => panic!("inner should have trapped"),
2019 }
2020 });
2021 assert_eq!(
2022 outer.ok(),
2023 Some(1234),
2024 "outer must recover after inner trap and run to completion"
2025 );
2026
2027 clear_tls_stack();
2028 drain_stack_pool();
2029 }
2030
2031 #[test]
2032 fn reentrant_with_on_host_stack_in_between() {
2033 let _lock = GLOBAL_STATE.lock().unwrap();
2037 let _restore = RestoreStackSize(get_stack_size());
2038 drain_stack_pool();
2039 clear_tls_stack();
2040
2041 let r = on_wasm_stack(get_stack_size(), None, || {
2042 on_host_stack(|| {
2043 on_wasm_stack(get_stack_size(), None, || 5i32).expect("nested inner must succeed")
2044 })
2045 });
2046 assert_eq!(r.ok(), Some(5));
2047
2048 clear_tls_stack();
2049 drain_stack_pool();
2050 }
2051
2052 #[test]
2057 fn many_threads_in_parallel_all_succeed() {
2058 let _lock = GLOBAL_STATE.lock().unwrap();
2059 let _restore = RestoreStackSize(get_stack_size());
2060 drain_stack_pool();
2061
2062 use std::sync::Arc;
2063 use std::sync::atomic::{AtomicUsize, Ordering as O};
2064
2065 let counter = Arc::new(AtomicUsize::new(0));
2066 const THREADS: usize = 8;
2067 const CALLS_PER_THREAD: usize = 200;
2068
2069 let handles: Vec<_> = (0..THREADS)
2070 .map(|_| {
2071 let counter = counter.clone();
2072 std::thread::spawn(move || {
2073 let size = get_stack_size();
2074 for _ in 0..CALLS_PER_THREAD {
2075 if on_wasm_stack(size, None, || 1u32).ok() == Some(1) {
2076 counter.fetch_add(1, O::Relaxed);
2077 }
2078 }
2079 })
2080 })
2081 .collect();
2082 for h in handles {
2083 h.join().unwrap();
2084 }
2085
2086 assert_eq!(counter.load(O::Relaxed), THREADS * CALLS_PER_THREAD);
2087 let mut pooled = 0usize;
2091 while STACK_POOL.pop().is_some() {
2092 pooled += 1;
2093 }
2094 assert!(
2095 pooled <= THREADS,
2096 "pool should hold at most one stack per terminated thread (got {pooled} for {THREADS} threads)"
2097 );
2098
2099 clear_tls_stack();
2100 drain_stack_pool();
2101 }
2102
2103 #[test]
2108 fn growing_request_discards_smaller_tls_stack() {
2109 let _lock = GLOBAL_STATE.lock().unwrap();
2110 let _restore = RestoreStackSize(get_stack_size());
2111 drain_stack_pool();
2112 clear_tls_stack();
2113
2114 let small = ByteSize::mib(1).as_u64() as usize;
2116 set_stack_size(small);
2117 assert!(on_wasm_stack(small, None, || ()).is_ok());
2118
2119 let cached_size = TLS_STACK.with(|cache| {
2120 let s = cache.0.take();
2121 let sz = s.as_ref().map_or(0, |s| s.size());
2122 cache.0.set(s);
2123 sz
2124 });
2125 assert!(
2126 cached_size >= small,
2127 "TLS should hold the small-sized stack"
2128 );
2129
2130 let big = ByteSize::mib(4).as_u64() as usize;
2133 set_stack_size(big);
2134 assert!(on_wasm_stack(big, None, || ()).is_ok());
2135
2136 let cached_size = TLS_STACK.with(|cache| {
2138 let s = cache.0.take();
2139 let sz = s.as_ref().map_or(0, |s| s.size());
2140 cache.0.set(s);
2141 sz
2142 });
2143 assert!(
2144 cached_size >= big,
2145 "TLS should hold the bigger stack after size bump"
2146 );
2147
2148 clear_tls_stack();
2149 drain_stack_pool();
2150 }
2151}