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 static INIT: Once = Once::new();
812 INIT.call_once(|| unsafe {
813 platform_init();
814 });
815}
816
817pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! {
830 unsafe { unwind_with(UnwindReason::UserTrap(data)) }
831}
832
833pub unsafe fn raise_lib_trap(trap: Trap) -> ! {
845 unsafe { unwind_with(UnwindReason::LibTrap(trap)) }
846}
847
848pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
857 unsafe { unwind_with(UnwindReason::Panic(payload)) }
858}
859
860pub unsafe fn catch_traps<F, R: 'static>(
867 trap_handler: Option<*const TrapHandlerFn<'static>>,
868 config: &VMConfig,
869 closure: F,
870) -> Result<R, Trap>
871where
872 F: FnOnce() -> R + 'static,
873{
874 lazy_per_thread_init()?;
876 let stack_size = config
877 .wasm_stack_size
878 .unwrap_or_else(|| DEFAULT_STACK_SIZE.load(Ordering::Relaxed));
879 on_wasm_stack(stack_size, trap_handler, closure).map_err(UnwindReason::into_trap)
880}
881
882thread_local! {
891 static YIELDER: Cell<Option<NonNull<Yielder<(), UnwindReason>>>> = const { Cell::new(None) };
892 static TRAP_HANDLER: AtomicPtr<TrapHandlerContext> = const { AtomicPtr::new(ptr::null_mut()) };
893}
894
895#[allow(clippy::type_complexity)]
898struct TrapHandlerContext {
899 inner: *const u8,
900 handle_trap: fn(
901 *const u8,
902 usize,
903 usize,
904 Option<usize>,
905 Option<TrapCode>,
906 &mut dyn FnMut(TrapHandlerRegs),
907 ) -> bool,
908 custom_trap: Option<*const TrapHandlerFn<'static>>,
909}
910struct TrapHandlerContextInner<T> {
911 coro_trap_handler: CoroutineTrapHandler<Result<T, UnwindReason>>,
914}
915
916impl TrapHandlerContext {
917 fn install<T, R>(
920 custom_trap: Option<*const TrapHandlerFn<'static>>,
921 coro_trap_handler: CoroutineTrapHandler<Result<T, UnwindReason>>,
922 f: impl FnOnce() -> R,
923 ) -> R {
924 fn func<T>(
926 ptr: *const u8,
927 pc: usize,
928 sp: usize,
929 maybe_fault_address: Option<usize>,
930 trap_code: Option<TrapCode>,
931 update_regs: &mut dyn FnMut(TrapHandlerRegs),
932 ) -> bool {
933 unsafe {
934 (*(ptr as *const TrapHandlerContextInner<T>)).handle_trap(
935 pc,
936 sp,
937 maybe_fault_address,
938 trap_code,
939 update_regs,
940 )
941 }
942 }
943 let inner = TrapHandlerContextInner { coro_trap_handler };
944 let ctx = Self {
945 inner: &inner as *const _ as *const u8,
946 handle_trap: func::<T>,
947 custom_trap,
948 };
949
950 compiler_fence(Ordering::Release);
951 let prev = TRAP_HANDLER.with(|ptr| {
952 let prev = ptr.load(Ordering::Relaxed);
953 ptr.store(&ctx as *const Self as *mut Self, Ordering::Relaxed);
954 prev
955 });
956
957 defer! {
958 TRAP_HANDLER.with(|ptr| ptr.store(prev, Ordering::Relaxed));
959 compiler_fence(Ordering::Acquire);
960 }
961
962 f()
963 }
964
965 unsafe fn handle_trap(
967 pc: usize,
968 sp: usize,
969 maybe_fault_address: Option<usize>,
970 trap_code: Option<TrapCode>,
971 mut update_regs: impl FnMut(TrapHandlerRegs),
972 call_handler: impl Fn(&TrapHandlerFn<'static>) -> bool,
973 ) -> bool {
974 unsafe {
975 let ptr = TRAP_HANDLER.with(|ptr| ptr.load(Ordering::Relaxed));
976 if ptr.is_null() {
977 return false;
978 }
979
980 let ctx = &*ptr;
981
982 if let Some(trap_handler) = ctx.custom_trap
984 && call_handler(&*trap_handler)
985 {
986 return true;
987 }
988
989 (ctx.handle_trap)(
990 ctx.inner,
991 pc,
992 sp,
993 maybe_fault_address,
994 trap_code,
995 &mut update_regs,
996 )
997 }
998 }
999}
1000
1001impl<T> TrapHandlerContextInner<T> {
1002 unsafe fn handle_trap(
1003 &self,
1004 pc: usize,
1005 sp: usize,
1006 maybe_fault_address: Option<usize>,
1007 trap_code: Option<TrapCode>,
1008 update_regs: &mut dyn FnMut(TrapHandlerRegs),
1009 ) -> bool {
1010 unsafe {
1011 if !self.coro_trap_handler.stack_ptr_in_bounds(sp) {
1014 return false;
1015 }
1016
1017 let signal_trap = trap_code.or_else(|| {
1018 maybe_fault_address.map(|addr| {
1019 if self.coro_trap_handler.stack_ptr_in_bounds(addr) {
1020 TrapCode::StackOverflow
1021 } else {
1022 TrapCode::HeapAccessOutOfBounds
1023 }
1024 })
1025 });
1026
1027 let backtrace = if signal_trap == Some(TrapCode::StackOverflow) {
1034 Backtrace::from(vec![])
1035 } else {
1036 Backtrace::new_unresolved()
1037 };
1038
1039 let unwind = UnwindReason::WasmTrap {
1042 backtrace,
1043 signal_trap,
1044 pc,
1045 };
1046 let regs = self
1047 .coro_trap_handler
1048 .setup_trap_handler(move || Err(unwind));
1049 update_regs(regs);
1050 true
1051 }
1052 }
1053}
1054
1055unsafe fn unwind_with(reason: UnwindReason) -> ! {
1056 unsafe {
1057 let yielder = YIELDER
1058 .with(|cell| cell.replace(None))
1059 .expect("not running on Wasm stack");
1060
1061 yielder.as_ref().suspend(reason);
1062
1063 unreachable!();
1065 }
1066}
1067
1068fn on_wasm_stack<F: FnOnce() -> T + 'static, T: 'static>(
1072 stack_size: usize,
1073 trap_handler: Option<*const TrapHandlerFn<'static>>,
1074 f: F,
1075) -> Result<T, UnwindReason> {
1076 let stack = acquire_stack(stack_size);
1083 let mut stack = scopeguard::guard(stack, release_stack);
1084
1085 let coro = ScopedCoroutine::with_stack(&mut *stack, move |yielder, ()| {
1087 YIELDER.with(|cell| cell.set(Some(yielder.into())));
1089
1090 Ok(f())
1091 });
1092
1093 defer! {
1095 YIELDER.with(|cell| cell.set(None));
1096 }
1097
1098 coro.scope(|mut coro_ref| {
1099 TrapHandlerContext::install(trap_handler, coro_ref.trap_handler(), || {
1102 match coro_ref.resume(()) {
1103 CoroutineResult::Yield(trap) => {
1104 unsafe {
1107 coro_ref.force_reset();
1108 }
1109 Err(trap)
1110 }
1111 CoroutineResult::Return(result) => result,
1112 }
1113 })
1114 })
1115}
1116
1117pub fn on_host_stack<F: FnOnce() -> T, T>(f: F) -> T {
1126 let yielder_ptr = YIELDER.with(|cell| cell.replace(None));
1129
1130 let yielder = match yielder_ptr {
1133 Some(ptr) => unsafe { ptr.as_ref() },
1134 None => return f(),
1135 };
1136
1137 defer! {
1139 YIELDER.with(|cell| cell.set(yielder_ptr));
1140 }
1141
1142 struct SendWrapper<T>(T);
1146 unsafe impl<T> Send for SendWrapper<T> {}
1147 let wrapped = SendWrapper(f);
1148 yielder.on_parent_stack(move || {
1149 let wrapped = wrapped;
1150 (wrapped.0)()
1151 })
1152}
1153
1154#[cfg(windows)]
1155pub fn lazy_per_thread_init() -> Result<(), Trap> {
1156 use windows_sys::Win32::System::Threading::SetThreadStackGuarantee;
1160 if unsafe { SetThreadStackGuarantee(&mut 0x10000) } == 0 {
1161 panic!("failed to set thread stack guarantee");
1162 }
1163
1164 Ok(())
1165}
1166
1167#[cfg(unix)]
1174pub fn lazy_per_thread_init() -> Result<(), Trap> {
1175 use std::ptr::null_mut;
1176
1177 thread_local! {
1178 static TLS: Tls = unsafe { init_sigstack() };
1181 }
1182
1183 const MIN_STACK_SIZE: usize = ByteSize::kib(64).as_u64() as usize;
1186
1187 enum Tls {
1188 OutOfMemory,
1189 Allocated {
1190 mmap_ptr: *mut libc::c_void,
1191 mmap_size: usize,
1192 },
1193 BigEnough,
1194 }
1195
1196 unsafe fn init_sigstack() -> Tls {
1197 unsafe {
1198 let mut old_stack = mem::zeroed();
1201 let r = libc::sigaltstack(ptr::null(), &mut old_stack);
1202 assert_eq!(r, 0, "learning about sigaltstack failed");
1203 if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
1204 return Tls::BigEnough;
1205 }
1206
1207 let page_size: usize = region::page::size();
1210 let guard_size = page_size;
1211 let alloc_size = guard_size + MIN_STACK_SIZE;
1212
1213 let ptr = libc::mmap(
1214 null_mut(),
1215 alloc_size,
1216 libc::PROT_NONE,
1217 libc::MAP_PRIVATE | libc::MAP_ANON,
1218 -1,
1219 0,
1220 );
1221 if ptr == libc::MAP_FAILED {
1222 return Tls::OutOfMemory;
1223 }
1224
1225 let stack_ptr = (ptr as usize + guard_size) as *mut libc::c_void;
1228 let r = libc::mprotect(
1229 stack_ptr,
1230 MIN_STACK_SIZE,
1231 libc::PROT_READ | libc::PROT_WRITE,
1232 );
1233 assert_eq!(r, 0, "mprotect to configure memory for sigaltstack failed");
1234 let new_stack = libc::stack_t {
1235 ss_sp: stack_ptr,
1236 ss_flags: 0,
1237 ss_size: MIN_STACK_SIZE,
1238 };
1239 let r = libc::sigaltstack(&new_stack, ptr::null_mut());
1240 assert_eq!(r, 0, "registering new sigaltstack failed");
1241
1242 Tls::Allocated {
1243 mmap_ptr: ptr,
1244 mmap_size: alloc_size,
1245 }
1246 }
1247 }
1248
1249 return TLS.with(|tls| {
1252 if let Tls::OutOfMemory = tls {
1253 Err(Trap::oom())
1254 } else {
1255 Ok(())
1256 }
1257 });
1258
1259 impl Drop for Tls {
1260 fn drop(&mut self) {
1261 let (ptr, size) = match self {
1262 Self::Allocated {
1263 mmap_ptr,
1264 mmap_size,
1265 } => (*mmap_ptr, *mmap_size),
1266 _ => return,
1267 };
1268 unsafe {
1269 let r = libc::munmap(ptr, size);
1271 debug_assert_eq!(r, 0, "munmap failed during thread shutdown");
1272 }
1273 }
1274 }
1275}
1276
1277#[cfg(test)]
1278mod tests {
1279 use super::*;
1280 use std::sync::Mutex;
1281
1282 static GLOBAL_STATE: Mutex<()> = Mutex::new(());
1286
1287 struct RestoreStackSize(usize);
1289 impl Drop for RestoreStackSize {
1290 fn drop(&mut self) {
1291 set_stack_size(self.0);
1292 }
1293 }
1294
1295 #[test]
1296 fn max_stack_size_is_100mb() {
1297 assert_eq!(MAX_STACK_SIZE, ByteSize::mib(100).as_u64() as usize);
1298 }
1299
1300 #[test]
1301 fn get_set_stack_size_roundtrip() {
1302 let _lock = GLOBAL_STATE.lock().unwrap();
1303 let _restore = RestoreStackSize(get_stack_size());
1304 let new_size = ByteSize::mib(4).as_u64() as usize;
1305 set_stack_size(new_size);
1306 assert_eq!(get_stack_size(), new_size);
1307 }
1308
1309 #[test]
1310 fn set_stack_size_clamps_to_min() {
1311 let _lock = GLOBAL_STATE.lock().unwrap();
1312 let _restore = RestoreStackSize(get_stack_size());
1313 set_stack_size(1); assert_eq!(get_stack_size(), ByteSize::kib(8).as_u64() as usize);
1315 }
1316
1317 #[test]
1318 fn set_stack_size_clamps_to_max() {
1319 let _lock = GLOBAL_STATE.lock().unwrap();
1320 let _restore = RestoreStackSize(get_stack_size());
1321 set_stack_size(usize::MAX);
1322 assert_eq!(get_stack_size(), MAX_STACK_SIZE);
1323 }
1324
1325 #[test]
1326 fn drain_stack_pool_empties_pool() {
1327 let _lock = GLOBAL_STATE.lock().unwrap();
1328 let stack = DefaultStack::new(ByteSize::mib(1).as_u64() as usize).unwrap();
1329 STACK_POOL.push(stack);
1330 assert!(!STACK_POOL.is_empty());
1331 drain_stack_pool();
1332 assert!(STACK_POOL.is_empty());
1333 }
1334
1335 #[test]
1336 fn drain_stack_pool_is_idempotent() {
1337 let _lock = GLOBAL_STATE.lock().unwrap();
1338 drain_stack_pool();
1339 drain_stack_pool(); assert!(STACK_POOL.is_empty());
1341 }
1342
1343 #[test]
1353 fn pool_returns_stale_stack_without_drain() {
1354 let _lock = GLOBAL_STATE.lock().unwrap();
1355 let _restore = RestoreStackSize(get_stack_size());
1356 drain_stack_pool();
1357
1358 let small_size = ByteSize::kib(500).as_u64() as usize;
1360 let small_stack = DefaultStack::new(small_size).unwrap();
1361 STACK_POOL.push(small_stack);
1362
1363 let big_size = ByteSize::mib(1).as_u64() as usize;
1365 set_stack_size(big_size);
1366 assert_eq!(get_stack_size(), big_size);
1367
1368 let stale = STACK_POOL.pop();
1372 assert!(
1373 stale.is_some(),
1374 "pool should still contain the old stack (the bug scenario)"
1375 );
1376
1377 STACK_POOL.push(stale.unwrap());
1379 drain_stack_pool();
1380 assert!(
1381 STACK_POOL.pop().is_none(),
1382 "after drain, pool must be empty so a fresh stack is allocated at the new size"
1383 );
1384 }
1385
1386 #[test]
1389 fn on_wasm_stack_discards_undersized_stack() {
1390 let _lock = GLOBAL_STATE.lock().unwrap();
1391 let _restore = RestoreStackSize(get_stack_size());
1392 drain_stack_pool();
1393 clear_tls_stack();
1394
1395 let small_size = ByteSize::kib(500).as_u64() as usize;
1397 let small_stack = DefaultStack::new(small_size).unwrap();
1398 STACK_POOL.push(small_stack);
1399
1400 let big_size = ByteSize::mib(1).as_u64() as usize;
1402 let result = on_wasm_stack(big_size, None, || 42);
1403
1404 assert_eq!(result.expect("on_wasm_stack should succeed"), 42);
1405 let returned = TLS_STACK
1409 .with(|cache| cache.0.take())
1410 .or_else(|| STACK_POOL.pop())
1411 .expect("stack should have been returned to TLS cache or pool");
1412 assert!(
1413 returned.size() >= big_size,
1414 "returned stack must be at least as large as the requested size"
1415 );
1416
1417 clear_tls_stack();
1421 }
1422
1423 #[test]
1427 fn tls_stack_caches_after_first_call() {
1428 let _lock = GLOBAL_STATE.lock().unwrap();
1429 let _restore = RestoreStackSize(get_stack_size());
1430 drain_stack_pool();
1431 clear_tls_stack();
1432
1433 let size = get_stack_size();
1434
1435 assert!(on_wasm_stack(size, None, || ()).is_ok());
1437 assert!(
1438 STACK_POOL.is_empty(),
1439 "pool should still be empty after a TLS-served call"
1440 );
1441
1442 let cached_present = TLS_STACK.with(|cache| {
1444 let taken = cache.0.take();
1445 let present = taken.is_some();
1446 cache.0.set(taken);
1447 present
1448 });
1449 assert!(cached_present, "TLS slot should hold the post-call stack");
1450
1451 assert!(on_wasm_stack(size, None, || ()).is_ok());
1453 assert!(
1454 STACK_POOL.is_empty(),
1455 "second call must not push to the global pool"
1456 );
1457 let still_cached = TLS_STACK.with(|cache| {
1458 let taken = cache.0.take();
1459 let present = taken.is_some();
1460 cache.0.set(taken);
1461 present
1462 });
1463 assert!(
1464 still_cached,
1465 "TLS slot should still hold a stack after the second call"
1466 );
1467
1468 clear_tls_stack();
1473 }
1474
1475 #[test]
1478 fn tls_stack_returns_to_pool_on_thread_exit() {
1479 let lock = GLOBAL_STATE.lock().unwrap();
1492 let _restore = RestoreStackSize(get_stack_size());
1493 drain_stack_pool();
1494 clear_tls_stack();
1495
1496 let size = get_stack_size();
1497 drop(lock);
1498
1499 let handle = std::thread::spawn(move || {
1500 assert!(on_wasm_stack(size, None, || ()).is_ok());
1501 });
1502 handle.join().unwrap();
1503
1504 let _lock = GLOBAL_STATE.lock().unwrap();
1505 let returned = STACK_POOL
1508 .pop()
1509 .expect("thread exit should return TLS-cached stack to the global pool");
1510 assert!(returned.size() >= size);
1511 }
1512
1513 fn clear_tls_stack() {
1521 TLS_STACK.with(|cache| cache.0.set(None));
1522 }
1523
1524 fn stack_id(stack: &DefaultStack) -> usize {
1529 stack.base().get()
1530 }
1531
1532 #[test]
1537 fn acquire_allocates_fresh_when_tls_and_pool_empty() {
1538 let _lock = GLOBAL_STATE.lock().unwrap();
1539 let _restore = RestoreStackSize(get_stack_size());
1540 drain_stack_pool();
1541 clear_tls_stack();
1542
1543 let size = get_stack_size();
1544 let stack = acquire_stack(size);
1545 assert!(
1546 stack.size() >= size,
1547 "freshly allocated stack must satisfy min_size"
1548 );
1549
1550 drop(stack);
1551 clear_tls_stack();
1552 drain_stack_pool();
1553 }
1554
1555 #[test]
1556 fn acquire_prefers_tls_over_pool() {
1557 let _lock = GLOBAL_STATE.lock().unwrap();
1558 let _restore = RestoreStackSize(get_stack_size());
1559 drain_stack_pool();
1560 clear_tls_stack();
1561
1562 let size = get_stack_size();
1563 let tls_stack = DefaultStack::new(size).unwrap();
1564 let tls_id = stack_id(&tls_stack);
1565 TLS_STACK.with(|cache| cache.0.set(Some(tls_stack)));
1566
1567 let pool_stack = DefaultStack::new(size).unwrap();
1568 let pool_id = stack_id(&pool_stack);
1569 STACK_POOL.push(pool_stack);
1570
1571 let got = acquire_stack(size);
1572 assert_eq!(stack_id(&got), tls_id, "acquire must prefer TLS over pool");
1573 assert_ne!(stack_id(&got), pool_id);
1574
1575 drop(got);
1576 clear_tls_stack();
1577 drain_stack_pool();
1578 }
1579
1580 #[test]
1581 fn acquire_uses_pool_when_tls_empty() {
1582 let _lock = GLOBAL_STATE.lock().unwrap();
1583 let _restore = RestoreStackSize(get_stack_size());
1584 drain_stack_pool();
1585 clear_tls_stack();
1586
1587 let size = get_stack_size();
1588 let pool_stack = DefaultStack::new(size).unwrap();
1589 let pool_id = stack_id(&pool_stack);
1590 STACK_POOL.push(pool_stack);
1591
1592 let got = acquire_stack(size);
1593 assert_eq!(
1594 stack_id(&got),
1595 pool_id,
1596 "acquire must consume from pool when TLS is empty"
1597 );
1598 assert!(
1599 STACK_POOL.is_empty(),
1600 "pool stack must be removed when used"
1601 );
1602
1603 drop(got);
1604 clear_tls_stack();
1605 drain_stack_pool();
1606 }
1607
1608 #[test]
1609 fn acquire_discards_undersized_tls_then_allocates() {
1610 let _lock = GLOBAL_STATE.lock().unwrap();
1611 let _restore = RestoreStackSize(get_stack_size());
1612 drain_stack_pool();
1613 clear_tls_stack();
1614
1615 let small_size = ByteSize::kib(512).as_u64() as usize;
1616 let undersized = DefaultStack::new(small_size).unwrap();
1617 TLS_STACK.with(|cache| cache.0.set(Some(undersized)));
1618
1619 let big_size = ByteSize::mib(2).as_u64() as usize;
1620 let got = acquire_stack(big_size);
1621
1622 assert!(
1630 got.size() >= big_size,
1631 "acquired stack must satisfy big_size"
1632 );
1633 let tls_empty = TLS_STACK.with(|cache| {
1634 let s = cache.0.take();
1635 let empty = s.is_none();
1636 cache.0.set(s);
1637 empty
1638 });
1639 assert!(
1640 tls_empty,
1641 "undersized TLS stack must have been taken and discarded"
1642 );
1643 assert!(
1644 STACK_POOL.is_empty(),
1645 "undersized TLS stack must be discarded, not pushed to the pool",
1646 );
1647
1648 drop(got);
1649 clear_tls_stack();
1650 drain_stack_pool();
1651 }
1652
1653 #[test]
1654 fn acquire_discards_undersized_pool_then_allocates() {
1655 let _lock = GLOBAL_STATE.lock().unwrap();
1656 let _restore = RestoreStackSize(get_stack_size());
1657 drain_stack_pool();
1658 clear_tls_stack();
1659
1660 let small_size = ByteSize::kib(512).as_u64() as usize;
1661 let undersized = DefaultStack::new(small_size).unwrap();
1662 STACK_POOL.push(undersized);
1663
1664 let big_size = ByteSize::mib(2).as_u64() as usize;
1665 let got = acquire_stack(big_size);
1666
1667 assert!(
1672 got.size() >= big_size,
1673 "acquired stack must satisfy big_size"
1674 );
1675 assert!(
1676 STACK_POOL.is_empty(),
1677 "undersized pool stack must have been popped, filtered out and dropped",
1678 );
1679
1680 drop(got);
1681 clear_tls_stack();
1682 drain_stack_pool();
1683 }
1684
1685 #[test]
1690 fn release_into_empty_tls_caches_there() {
1691 let _lock = GLOBAL_STATE.lock().unwrap();
1692 drain_stack_pool();
1693 clear_tls_stack();
1694
1695 let size = get_stack_size();
1696 let stack = DefaultStack::new(size).unwrap();
1697 let id = stack_id(&stack);
1698 release_stack(stack);
1699
1700 let in_tls = TLS_STACK
1701 .with(|cache| cache.0.take())
1702 .expect("release into empty TLS should leave the stack in TLS");
1703 assert_eq!(stack_id(&in_tls), id);
1704 assert!(
1705 STACK_POOL.is_empty(),
1706 "pool must not be touched when TLS is empty"
1707 );
1708
1709 drain_stack_pool();
1710 }
1711
1712 #[test]
1713 fn release_into_occupied_tls_displaces_older_to_pool() {
1714 let _lock = GLOBAL_STATE.lock().unwrap();
1715 drain_stack_pool();
1716 clear_tls_stack();
1717
1718 let size = get_stack_size();
1719 let older = DefaultStack::new(size).unwrap();
1720 let older_id = stack_id(&older);
1721 TLS_STACK.with(|cache| cache.0.set(Some(older)));
1722
1723 let newer = DefaultStack::new(size).unwrap();
1724 let newer_id = stack_id(&newer);
1725 release_stack(newer);
1726
1727 let in_tls = TLS_STACK
1728 .with(|cache| cache.0.take())
1729 .expect("TLS should hold the newly-released stack");
1730 assert_eq!(
1731 stack_id(&in_tls),
1732 newer_id,
1733 "newer stack must displace into TLS"
1734 );
1735
1736 let displaced = STACK_POOL
1737 .pop()
1738 .expect("older stack should have been pushed to global pool");
1739 assert_eq!(
1740 stack_id(&displaced),
1741 older_id,
1742 "displaced stack must be the older one"
1743 );
1744
1745 drain_stack_pool();
1746 }
1747
1748 #[test]
1753 fn drain_stack_pool_clears_calling_thread_tls_slot() {
1754 let _lock = GLOBAL_STATE.lock().unwrap();
1755 drain_stack_pool();
1756 clear_tls_stack();
1757
1758 let stack = DefaultStack::new(get_stack_size()).unwrap();
1759 TLS_STACK.with(|cache| cache.0.set(Some(stack)));
1760
1761 drain_stack_pool();
1762
1763 let tls_empty = TLS_STACK.with(|cache| cache.0.take().is_none());
1764 assert!(
1765 tls_empty,
1766 "drain_stack_pool must also clear current thread's TLS slot"
1767 );
1768 assert!(STACK_POOL.is_empty());
1769 }
1770
1771 #[test]
1776 fn on_wasm_stack_passes_closure_value_back() {
1777 let _lock = GLOBAL_STATE.lock().unwrap();
1778 let _restore = RestoreStackSize(get_stack_size());
1779 drain_stack_pool();
1780 clear_tls_stack();
1781
1782 let r = on_wasm_stack(get_stack_size(), None, || 12345u32);
1783 assert_eq!(r.ok(), Some(12345));
1784
1785 clear_tls_stack();
1786 drain_stack_pool();
1787 }
1788
1789 #[test]
1790 fn on_wasm_stack_passes_owning_result_back() {
1791 let _lock = GLOBAL_STATE.lock().unwrap();
1792 let _restore = RestoreStackSize(get_stack_size());
1793 drain_stack_pool();
1794 clear_tls_stack();
1795
1796 let r = on_wasm_stack(get_stack_size(), None, || vec![0u8, 1, 2, 3, 4]);
1800 assert_eq!(r.ok(), Some(vec![0u8, 1, 2, 3, 4]));
1801
1802 clear_tls_stack();
1803 drain_stack_pool();
1804 }
1805
1806 #[test]
1807 fn many_calls_do_not_grow_global_pool() {
1808 let _lock = GLOBAL_STATE.lock().unwrap();
1809 let _restore = RestoreStackSize(get_stack_size());
1810 drain_stack_pool();
1811 clear_tls_stack();
1812
1813 for _ in 0..1000 {
1816 assert!(on_wasm_stack(get_stack_size(), None, || ()).is_ok());
1817 }
1818 assert!(
1819 STACK_POOL.is_empty(),
1820 "1000 sequential calls should not grow the global pool (TLS handles reuse)"
1821 );
1822
1823 clear_tls_stack();
1824 drain_stack_pool();
1825 }
1826
1827 #[test]
1832 fn raise_user_trap_yields_err() {
1833 let _lock = GLOBAL_STATE.lock().unwrap();
1834 let _restore = RestoreStackSize(get_stack_size());
1835 drain_stack_pool();
1836 clear_tls_stack();
1837
1838 let r: Result<(), UnwindReason> = on_wasm_stack(get_stack_size(), None, || unsafe {
1839 raise_user_trap(Box::new(io::Error::other("user trap from test")));
1840 });
1841 assert!(r.is_err(), "raise_user_trap must produce Err");
1842
1843 clear_tls_stack();
1844 drain_stack_pool();
1845 }
1846
1847 #[test]
1848 fn raise_lib_trap_yields_err() {
1849 let _lock = GLOBAL_STATE.lock().unwrap();
1850 let _restore = RestoreStackSize(get_stack_size());
1851 drain_stack_pool();
1852 clear_tls_stack();
1853
1854 let r: Result<(), UnwindReason> = on_wasm_stack(get_stack_size(), None, || unsafe {
1855 raise_lib_trap(Trap::lib(TrapCode::IntegerDivisionByZero));
1856 });
1857 assert!(r.is_err(), "raise_lib_trap must produce Err");
1858
1859 clear_tls_stack();
1860 drain_stack_pool();
1861 }
1862
1863 #[test]
1864 fn resume_panic_yields_err_without_unwinding() {
1865 let _lock = GLOBAL_STATE.lock().unwrap();
1870 let _restore = RestoreStackSize(get_stack_size());
1871 drain_stack_pool();
1872 clear_tls_stack();
1873
1874 let r: Result<(), UnwindReason> = on_wasm_stack(get_stack_size(), None, || unsafe {
1875 resume_panic(Box::new("panic payload from test"));
1876 });
1877 assert!(
1878 r.is_err(),
1879 "resume_panic must surface as Err to on_wasm_stack"
1880 );
1881
1882 clear_tls_stack();
1883 drain_stack_pool();
1884 }
1885
1886 #[test]
1887 fn trap_does_not_corrupt_subsequent_calls() {
1888 let _lock = GLOBAL_STATE.lock().unwrap();
1892 let _restore = RestoreStackSize(get_stack_size());
1893 drain_stack_pool();
1894 clear_tls_stack();
1895
1896 let trapped: Result<(), UnwindReason> = on_wasm_stack(get_stack_size(), None, || unsafe {
1897 raise_user_trap(Box::new(io::Error::other("first call traps")));
1898 });
1899 assert!(trapped.is_err());
1900
1901 let ok = on_wasm_stack(get_stack_size(), None, || 7u32);
1903 assert_eq!(ok.ok(), Some(7), "calls after a trap must still work");
1904
1905 clear_tls_stack();
1906 drain_stack_pool();
1907 }
1908
1909 #[test]
1914 fn on_host_stack_outside_coroutine_runs_inline() {
1915 let _lock = GLOBAL_STATE.lock().unwrap();
1918 let n = on_host_stack(|| 99i32);
1919 assert_eq!(n, 99);
1920 }
1921
1922 #[test]
1923 fn on_host_stack_inside_wasm_switches_and_returns() {
1924 let _lock = GLOBAL_STATE.lock().unwrap();
1925 let _restore = RestoreStackSize(get_stack_size());
1926 drain_stack_pool();
1927 clear_tls_stack();
1928
1929 let r = on_wasm_stack(get_stack_size(), None, || on_host_stack(|| 88i32));
1930 assert_eq!(r.ok(), Some(88));
1931
1932 clear_tls_stack();
1933 drain_stack_pool();
1934 }
1935
1936 #[test]
1941 fn reentrant_call_returns_value() {
1942 let _lock = GLOBAL_STATE.lock().unwrap();
1943 let _restore = RestoreStackSize(get_stack_size());
1944 drain_stack_pool();
1945 clear_tls_stack();
1946
1947 let outer = on_wasm_stack(get_stack_size(), None, || {
1948 on_wasm_stack(get_stack_size(), None, || 42i32).expect("inner must succeed")
1949 });
1950 assert_eq!(outer.ok(), Some(42));
1951
1952 clear_tls_stack();
1953 drain_stack_pool();
1954 }
1955
1956 #[test]
1957 fn reentrant_calls_run_to_completion_under_pool_pressure() {
1958 use std::sync::Arc;
1965 use std::sync::atomic::{AtomicUsize, Ordering as O};
1966
1967 let _lock = GLOBAL_STATE.lock().unwrap();
1968 let _restore = RestoreStackSize(get_stack_size());
1969 drain_stack_pool();
1970 clear_tls_stack();
1971
1972 let pre = DefaultStack::new(get_stack_size()).unwrap();
1975 STACK_POOL.push(pre);
1976
1977 let inner_completed = Arc::new(AtomicUsize::new(0));
1978 let inner_completed_outer = inner_completed.clone();
1979 let _ = on_wasm_stack(get_stack_size(), None, move || {
1980 let inner_completed = inner_completed_outer.clone();
1981 let inner = on_wasm_stack(get_stack_size(), None, move || {
1982 inner_completed.fetch_add(1, O::Relaxed);
1983 });
1984 assert!(inner.is_ok(), "inner re-entrant call must succeed");
1985 });
1986 assert_eq!(
1987 inner_completed.load(O::Relaxed),
1988 1,
1989 "inner closure must have executed exactly once",
1990 );
1991
1992 clear_tls_stack();
1993 drain_stack_pool();
1994 }
1995
1996 #[test]
1997 fn reentrant_inner_trap_does_not_kill_outer() {
1998 let _lock = GLOBAL_STATE.lock().unwrap();
1999 let _restore = RestoreStackSize(get_stack_size());
2000 drain_stack_pool();
2001 clear_tls_stack();
2002
2003 let outer = on_wasm_stack(get_stack_size(), None, || {
2004 let inner: Result<i32, UnwindReason> =
2005 on_wasm_stack(get_stack_size(), None, || unsafe {
2006 raise_user_trap(Box::new(io::Error::other("inner trap")));
2007 });
2008 match inner {
2010 Err(_) => 1234i32,
2011 Ok(_) => panic!("inner should have trapped"),
2012 }
2013 });
2014 assert_eq!(
2015 outer.ok(),
2016 Some(1234),
2017 "outer must recover after inner trap and run to completion"
2018 );
2019
2020 clear_tls_stack();
2021 drain_stack_pool();
2022 }
2023
2024 #[test]
2025 fn reentrant_with_on_host_stack_in_between() {
2026 let _lock = GLOBAL_STATE.lock().unwrap();
2030 let _restore = RestoreStackSize(get_stack_size());
2031 drain_stack_pool();
2032 clear_tls_stack();
2033
2034 let r = on_wasm_stack(get_stack_size(), None, || {
2035 on_host_stack(|| {
2036 on_wasm_stack(get_stack_size(), None, || 5i32).expect("nested inner must succeed")
2037 })
2038 });
2039 assert_eq!(r.ok(), Some(5));
2040
2041 clear_tls_stack();
2042 drain_stack_pool();
2043 }
2044
2045 #[test]
2050 fn many_threads_in_parallel_all_succeed() {
2051 let _lock = GLOBAL_STATE.lock().unwrap();
2052 let _restore = RestoreStackSize(get_stack_size());
2053 drain_stack_pool();
2054
2055 use std::sync::Arc;
2056 use std::sync::atomic::{AtomicUsize, Ordering as O};
2057
2058 let counter = Arc::new(AtomicUsize::new(0));
2059 const THREADS: usize = 8;
2060 const CALLS_PER_THREAD: usize = 200;
2061
2062 let handles: Vec<_> = (0..THREADS)
2063 .map(|_| {
2064 let counter = counter.clone();
2065 std::thread::spawn(move || {
2066 let size = get_stack_size();
2067 for _ in 0..CALLS_PER_THREAD {
2068 if on_wasm_stack(size, None, || 1u32).ok() == Some(1) {
2069 counter.fetch_add(1, O::Relaxed);
2070 }
2071 }
2072 })
2073 })
2074 .collect();
2075 for h in handles {
2076 h.join().unwrap();
2077 }
2078
2079 assert_eq!(counter.load(O::Relaxed), THREADS * CALLS_PER_THREAD);
2080 let mut pooled = 0usize;
2084 while STACK_POOL.pop().is_some() {
2085 pooled += 1;
2086 }
2087 assert!(
2088 pooled <= THREADS,
2089 "pool should hold at most one stack per terminated thread (got {pooled} for {THREADS} threads)"
2090 );
2091
2092 clear_tls_stack();
2093 drain_stack_pool();
2094 }
2095
2096 #[test]
2101 fn growing_request_discards_smaller_tls_stack() {
2102 let _lock = GLOBAL_STATE.lock().unwrap();
2103 let _restore = RestoreStackSize(get_stack_size());
2104 drain_stack_pool();
2105 clear_tls_stack();
2106
2107 let small = ByteSize::mib(1).as_u64() as usize;
2109 set_stack_size(small);
2110 assert!(on_wasm_stack(small, None, || ()).is_ok());
2111
2112 let cached_size = TLS_STACK.with(|cache| {
2113 let s = cache.0.take();
2114 let sz = s.as_ref().map_or(0, |s| s.size());
2115 cache.0.set(s);
2116 sz
2117 });
2118 assert!(
2119 cached_size >= small,
2120 "TLS should hold the small-sized stack"
2121 );
2122
2123 let big = ByteSize::mib(4).as_u64() as usize;
2126 set_stack_size(big);
2127 assert!(on_wasm_stack(big, None, || ()).is_ok());
2128
2129 let cached_size = TLS_STACK.with(|cache| {
2131 let s = cache.0.take();
2132 let sz = s.as_ref().map_or(0, |s| s.size());
2133 cache.0.set(s);
2134 sz
2135 });
2136 assert!(
2137 cached_size >= big,
2138 "TLS should hold the bigger stack after size bump"
2139 );
2140
2141 clear_tls_stack();
2142 drain_stack_pool();
2143 }
2144}