mod backtrace;
use crate::{VMContext, VMRuntimeLimits};
use anyhow::Error;
use std::any::Any;
use std::cell::{Cell, UnsafeCell};
use std::mem::MaybeUninit;
use std::ptr;
use std::sync::Once;
pub use self::backtrace::Backtrace;
pub use self::tls::{tls_eager_initialize, TlsRestore};
#[link(name = "wasmtime-helpers")]
extern "C" {
#[allow(improper_ctypes)]
fn wasmtime_setjmp(
jmp_buf: *mut *const u8,
callback: extern "C" fn(*mut u8, *mut VMContext),
payload: *mut u8,
callee: *mut VMContext,
) -> i32;
fn wasmtime_longjmp(jmp_buf: *const u8) -> !;
}
cfg_if::cfg_if! {
if #[cfg(all(target_os = "macos", not(feature = "posix-signals-on-macos")))] {
mod macos;
use macos as sys;
} else if #[cfg(unix)] {
mod unix;
use unix as sys;
} else if #[cfg(target_os = "windows")] {
mod windows;
use windows as sys;
}
}
pub use sys::SignalHandler;
static mut IS_WASM_PC: fn(usize) -> bool = |_| false;
pub fn init_traps(is_wasm_pc: fn(usize) -> bool) {
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
IS_WASM_PC = is_wasm_pc;
sys::platform_init();
});
}
pub unsafe fn raise_trap(reason: TrapReason) -> ! {
tls::with(|info| info.unwrap().unwind_with(UnwindReason::Trap(reason)))
}
pub unsafe fn raise_user_trap(error: Error, needs_backtrace: bool) -> ! {
raise_trap(TrapReason::User {
error,
needs_backtrace,
})
}
pub unsafe fn raise_lib_trap(trap: wasmtime_environ::Trap) -> ! {
raise_trap(TrapReason::Wasm(trap))
}
pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
tls::with(|info| info.unwrap().unwind_with(UnwindReason::Panic(payload)))
}
#[derive(Debug)]
pub struct Trap {
pub reason: TrapReason,
pub backtrace: Option<Backtrace>,
}
#[derive(Debug)]
pub enum TrapReason {
User {
error: Error,
needs_backtrace: bool,
},
Jit(usize),
Wasm(wasmtime_environ::Trap),
}
impl TrapReason {
pub fn user_without_backtrace(error: Error) -> Self {
TrapReason::User {
error,
needs_backtrace: true,
}
}
pub fn user_with_backtrace(error: Error) -> Self {
TrapReason::User {
error,
needs_backtrace: false,
}
}
pub fn is_jit(&self) -> bool {
matches!(self, TrapReason::Jit(_))
}
}
impl From<Error> for TrapReason {
fn from(err: Error) -> Self {
TrapReason::user_without_backtrace(err)
}
}
impl From<wasmtime_environ::Trap> for TrapReason {
fn from(code: wasmtime_environ::Trap) -> Self {
TrapReason::Wasm(code)
}
}
pub unsafe fn catch_traps<'a, F>(
signal_handler: Option<*const SignalHandler<'static>>,
capture_backtrace: bool,
caller: *mut VMContext,
mut closure: F,
) -> Result<(), Box<Trap>>
where
F: FnMut(*mut VMContext),
{
let limits = (*caller).instance_mut().runtime_limits();
let result = CallThreadState::new(signal_handler, capture_backtrace, *limits).with(|cx| {
wasmtime_setjmp(
cx.jmp_buf.as_ptr(),
call_closure::<F>,
&mut closure as *mut F as *mut u8,
caller,
)
});
return match result {
Ok(x) => Ok(x),
Err((UnwindReason::Trap(reason), backtrace)) => Err(Box::new(Trap { reason, backtrace })),
Err((UnwindReason::Panic(panic), _)) => std::panic::resume_unwind(panic),
};
extern "C" fn call_closure<F>(payload: *mut u8, caller: *mut VMContext)
where
F: FnMut(*mut VMContext),
{
unsafe { (*(payload as *mut F))(caller) }
}
}
mod call_thread_state {
use super::*;
use std::mem;
pub struct CallThreadState {
pub(super) unwind: UnsafeCell<MaybeUninit<(UnwindReason, Option<Backtrace>)>>,
pub(super) jmp_buf: Cell<*const u8>,
pub(super) signal_handler: Option<*const SignalHandler<'static>>,
pub(super) capture_backtrace: bool,
pub(crate) limits: *const VMRuntimeLimits,
prev: Cell<tls::Ptr>,
old_last_wasm_exit_fp: Cell<usize>,
old_last_wasm_exit_pc: Cell<usize>,
old_last_wasm_entry_sp: Cell<usize>,
}
impl CallThreadState {
#[inline]
pub(super) fn new(
signal_handler: Option<*const SignalHandler<'static>>,
capture_backtrace: bool,
limits: *const VMRuntimeLimits,
) -> CallThreadState {
CallThreadState {
unwind: UnsafeCell::new(MaybeUninit::uninit()),
jmp_buf: Cell::new(ptr::null()),
signal_handler,
capture_backtrace,
limits,
prev: Cell::new(ptr::null()),
old_last_wasm_exit_fp: Cell::new(0),
old_last_wasm_exit_pc: Cell::new(0),
old_last_wasm_entry_sp: Cell::new(0),
}
}
pub fn old_last_wasm_exit_fp(&self) -> usize {
self.old_last_wasm_exit_fp.get()
}
pub fn old_last_wasm_exit_pc(&self) -> usize {
self.old_last_wasm_exit_pc.get()
}
pub fn old_last_wasm_entry_sp(&self) -> usize {
self.old_last_wasm_entry_sp.get()
}
pub fn prev(&self) -> tls::Ptr {
self.prev.get()
}
pub unsafe fn set_prev(&self, prev: tls::Ptr) -> tls::Ptr {
let old_prev = self.prev.get();
if let Some(old_prev) = old_prev.as_ref() {
*(*old_prev.limits).last_wasm_exit_fp.get() = self.old_last_wasm_exit_fp();
*(*old_prev.limits).last_wasm_exit_pc.get() = self.old_last_wasm_exit_pc();
*(*old_prev.limits).last_wasm_entry_sp.get() = self.old_last_wasm_entry_sp();
}
self.prev.set(prev);
let mut old_last_wasm_exit_fp = 0;
let mut old_last_wasm_exit_pc = 0;
let mut old_last_wasm_entry_sp = 0;
if let Some(prev) = prev.as_ref() {
old_last_wasm_exit_fp =
mem::replace(&mut *(*prev.limits).last_wasm_exit_fp.get(), 0);
old_last_wasm_exit_pc =
mem::replace(&mut *(*prev.limits).last_wasm_exit_pc.get(), 0);
old_last_wasm_entry_sp =
mem::replace(&mut *(*prev.limits).last_wasm_entry_sp.get(), 0);
}
self.old_last_wasm_exit_fp.set(old_last_wasm_exit_fp);
self.old_last_wasm_exit_pc.set(old_last_wasm_exit_pc);
self.old_last_wasm_entry_sp.set(old_last_wasm_entry_sp);
old_prev
}
}
}
pub use call_thread_state::*;
enum UnwindReason {
Panic(Box<dyn Any + Send>),
Trap(TrapReason),
}
impl CallThreadState {
fn with(
mut self,
closure: impl FnOnce(&CallThreadState) -> i32,
) -> Result<(), (UnwindReason, Option<Backtrace>)> {
let ret = tls::set(&mut self, |me| closure(me));
if ret != 0 {
Ok(())
} else {
Err(unsafe { self.read_unwind() })
}
}
#[cold]
unsafe fn read_unwind(&self) -> (UnwindReason, Option<Backtrace>) {
(*self.unwind.get()).as_ptr().read()
}
fn unwind_with(&self, reason: UnwindReason) -> ! {
let backtrace = match reason {
UnwindReason::Panic(_)
| UnwindReason::Trap(TrapReason::User {
needs_backtrace: false,
..
}) => None,
UnwindReason::Trap(_) => self.capture_backtrace(None),
};
unsafe {
(*self.unwind.get()).as_mut_ptr().write((reason, backtrace));
wasmtime_longjmp(self.jmp_buf.get());
}
}
#[cfg_attr(target_os = "macos", allow(dead_code))] fn take_jmp_buf_if_trap(
&self,
pc: *const u8,
call_handler: impl Fn(&SignalHandler) -> bool,
) -> *const u8 {
if self.jmp_buf.get().is_null() {
return ptr::null();
}
if let Some(handler) = self.signal_handler {
if unsafe { call_handler(&*handler) } {
return 1 as *const _;
}
}
if unsafe { !IS_WASM_PC(pc as usize) } {
return ptr::null();
}
self.jmp_buf.replace(ptr::null())
}
fn set_jit_trap(&self, pc: *const u8, fp: usize) {
let backtrace = self.capture_backtrace(Some((pc as usize, fp)));
unsafe {
(*self.unwind.get())
.as_mut_ptr()
.write((UnwindReason::Trap(TrapReason::Jit(pc as usize)), backtrace));
}
}
fn capture_backtrace(&self, pc_and_fp: Option<(usize, usize)>) -> Option<Backtrace> {
if !self.capture_backtrace {
return None;
}
Some(unsafe { Backtrace::new_with_trap_state(self, pc_and_fp) })
}
pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = &Self> + 'a {
let mut state = Some(self);
std::iter::from_fn(move || {
let this = state?;
state = unsafe { this.prev().as_ref() };
Some(this)
})
}
}
struct ResetCell<'a, T: Copy>(&'a Cell<T>, T);
impl<T: Copy> Drop for ResetCell<'_, T> {
#[inline]
fn drop(&mut self) {
self.0.set(self.1);
}
}
mod tls {
use super::CallThreadState;
use std::ptr;
pub use raw::Ptr;
mod raw {
use super::CallThreadState;
use std::cell::Cell;
use std::ptr;
pub type Ptr = *const CallThreadState;
thread_local!(static PTR: Cell<(Ptr, bool)> = const { Cell::new((ptr::null(), false)) });
#[cfg_attr(feature = "async", inline(never))] #[cfg_attr(not(feature = "async"), inline)]
pub fn replace(val: Ptr) -> Ptr {
PTR.with(|p| {
let (prev, initialized) = p.get();
if !initialized {
super::super::sys::lazy_per_thread_init();
}
p.set((val, true));
prev
})
}
#[cfg_attr(feature = "async", inline(never))] #[cfg_attr(not(feature = "async"), inline)]
pub fn initialize() {
PTR.with(|p| {
let (state, initialized) = p.get();
if initialized {
return;
}
super::super::sys::lazy_per_thread_init();
p.set((state, true));
})
}
#[cfg_attr(feature = "async", inline(never))] #[cfg_attr(not(feature = "async"), inline)]
pub fn get() -> Ptr {
PTR.with(|p| p.get().0)
}
}
pub use raw::initialize as tls_eager_initialize;
pub struct TlsRestore {
state: raw::Ptr,
}
impl TlsRestore {
pub unsafe fn take() -> TlsRestore {
let state = raw::get();
if let Some(state) = state.as_ref() {
let prev_state = state.set_prev(ptr::null());
raw::replace(prev_state);
} else {
}
TlsRestore { state }
}
pub unsafe fn replace(self) {
if self.state.is_null() {
return;
}
let prev = raw::get();
assert!((*self.state).prev().is_null());
(*self.state).set_prev(prev);
raw::replace(self.state);
}
}
#[inline]
pub fn set<R>(state: &mut CallThreadState, closure: impl FnOnce(&CallThreadState) -> R) -> R {
struct Reset<'a> {
state: &'a CallThreadState,
}
impl Drop for Reset<'_> {
#[inline]
fn drop(&mut self) {
unsafe {
let prev = self.state.set_prev(ptr::null());
let old_state = raw::replace(prev);
debug_assert!(std::ptr::eq(old_state, self.state));
}
}
}
let prev = raw::replace(state);
unsafe {
state.set_prev(prev);
let reset = Reset { state };
closure(reset.state)
}
}
pub fn with<R>(closure: impl FnOnce(Option<&CallThreadState>) -> R) -> R {
let p = raw::get();
unsafe { closure(if p.is_null() { None } else { Some(&*p) }) }
}
}