Skip to content

Commit

Permalink
Use a StoreOpaque during backtraces for metadata (#4325)
Browse files Browse the repository at this point in the history
Previous to this commit Wasmtime would use the `GlobalModuleRegistry`
when learning information about a trap such as its trap code, the
symbols for each frame, etc. This has a downside though of holding a
global read-write lock for the duration of this operation which hinders
registration of new modules in parallel. In addition there was a fair
amount of internal duplication between this "global module registry" and
the store-local module registry. Finally relying on global state for
information like this gets a bit more brittle over time as it seems best
to scope global queries to precisely what's necessary rather than
holding extra information.

With the refactoring in wasm backtraces done in #4183 it's now possible
to always have a `StoreOpaque` reference when a backtrace is collected
for symbolication and otherwise Trap-identification purposes. This
commit adds a `StoreOpaque` parameter to the `Trap::from_runtime`
constructor and then plumbs that everywhere. Note that while doing this
I changed the internal `traphandlers::lazy_per_thread_init` function to
no longer return a `Result` and instead just `panic!` on Unix if memory
couldn't be allocated for a stack. This removed quite a lot of
error-handling code for a case that's expected to quite rarely happen.
If necessary in the future we can add a fallible initialization point
but this feels like a better default balance for the code here.

With a `StoreOpaque` in use when a trap is being symbolicated that means
we have a `ModuleRegistry` which can be used for queries and such. This
meant that the `GlobalModuleRegistry` state could largely be dismantled
and moved to per-`Store` state (within the `ModuleRegistry`, mostly just
moving methods around).

The final state is that the global rwlock is not exclusively scoped
around insertions/deletions/`is_wasm_trap_pc` which is just a lookup and
atomic add. Otherwise symbolication for a backtrace exclusively uses
store-local state now (as intended).

The original motivation for this commit was that frame information
lookup and pieces were looking to get somewhat complicated with the
addition of components which are a new vector of traps coming out of
Cranelift-generated code. My hope is that by having a `Store` around for
more operations it's easier to plumb all this through.
  • Loading branch information
alexcrichton authored Jun 27, 2022
1 parent 5c2c285 commit 82a3168
Show file tree
Hide file tree
Showing 12 changed files with 398 additions and 446 deletions.
2 changes: 1 addition & 1 deletion benches/thread_eager_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ fn lazy_thread_instantiate(engine: Engine, module: Module) -> Duration {
fn eager_thread_instantiate(engine: Engine, module: Module) -> (Duration, Duration) {
thread::spawn(move || {
let init_start = Instant::now();
Engine::tls_eager_initialize().expect("eager init");
Engine::tls_eager_initialize();
let init_duration = init_start.elapsed();

(init_duration, duration_of_call(&engine, &module))
Expand Down
55 changes: 17 additions & 38 deletions crates/runtime/src/traphandlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,22 +132,6 @@ pub enum TrapReason {

/// A trap raised from a wasm libcall
Wasm(TrapCode),

/// A trap indicating that the runtime was unable to allocate sufficient memory.
OOM,
}

impl Trap {
/// Construct a new OOM trap.
///
/// Internally saves a backtrace when passed across a setjmp boundary, if the
/// engine is configured to save backtraces.
pub fn oom() -> Self {
Trap {
reason: TrapReason::OOM,
backtrace: None,
}
}
}

/// Catches any wasm traps that happen within the execution of `closure`,
Expand Down Expand Up @@ -213,7 +197,7 @@ impl CallThreadState {
}

fn with(self, closure: impl FnOnce(&CallThreadState) -> i32) -> Result<(), Box<Trap>> {
let ret = tls::set(&self, || closure(&self))?;
let ret = tls::set(&self, || closure(&self));
if ret != 0 {
Ok(())
} else {
Expand Down Expand Up @@ -329,7 +313,6 @@ impl<T: Copy> Drop for ResetCell<'_, T> {
// the caller to the trap site.
mod tls {
use super::CallThreadState;
use crate::Trap;
use std::ptr;

pub use raw::Ptr;
Expand All @@ -350,7 +333,6 @@ mod tls {
// these functions are free to be inlined.
mod raw {
use super::CallThreadState;
use crate::Trap;
use std::cell::Cell;
use std::ptr;

Expand All @@ -365,33 +347,32 @@ mod tls {

#[cfg_attr(feature = "async", inline(never))] // see module docs
#[cfg_attr(not(feature = "async"), inline)]
pub fn replace(val: Ptr) -> Result<Ptr, Box<Trap>> {
pub fn replace(val: Ptr) -> Ptr {
PTR.with(|p| {
// When a new value is configured that means that we may be
// entering WebAssembly so check to see if this thread has
// performed per-thread initialization for traps.
let (prev, initialized) = p.get();
if !initialized {
super::super::sys::lazy_per_thread_init()?;
super::super::sys::lazy_per_thread_init();
}
p.set((val, true));
Ok(prev)
prev
})
}

/// Eagerly initialize thread-local runtime functionality. This will be performed
/// lazily by the runtime if users do not perform it eagerly.
#[cfg_attr(feature = "async", inline(never))] // see module docs
#[cfg_attr(not(feature = "async"), inline)]
pub fn initialize() -> Result<(), Box<Trap>> {
pub fn initialize() {
PTR.with(|p| {
let (state, initialized) = p.get();
if initialized {
return Ok(());
return;
}
super::super::sys::lazy_per_thread_init()?;
super::super::sys::lazy_per_thread_init();
p.set((state, true));
Ok(())
})
}

Expand All @@ -414,7 +395,7 @@ mod tls {
///
/// This is not a safe operation since it's intended to only be used
/// with stack switching found with fibers and async wasmtime.
pub unsafe fn take() -> Result<TlsRestore, Box<Trap>> {
pub unsafe fn take() -> TlsRestore {
// Our tls pointer must be set at this time, and it must not be
// null. We need to restore the previous pointer since we're
// removing ourselves from the call-stack, and in the process we
Expand All @@ -423,52 +404,50 @@ mod tls {
let raw = raw::get();
if !raw.is_null() {
let prev = (*raw).prev.replace(ptr::null());
raw::replace(prev)?;
raw::replace(prev);
}
// Null case: we aren't in a wasm context, so theres no tls
// to save for restoration.
Ok(TlsRestore(raw))
TlsRestore(raw)
}

/// Restores a previous tls state back into this thread's TLS.
///
/// This is unsafe because it's intended to only be used within the
/// context of stack switching within wasmtime.
pub unsafe fn replace(self) -> Result<(), Box<super::Trap>> {
pub unsafe fn replace(self) {
// Null case: we aren't in a wasm context, so theres no tls
// to restore.
if self.0.is_null() {
return Ok(());
return;
}
// We need to configure our previous TLS pointer to whatever is in
// TLS at this time, and then we set the current state to ourselves.
let prev = raw::get();
assert!((*self.0).prev.get().is_null());
(*self.0).prev.set(prev);
raw::replace(self.0)?;
Ok(())
raw::replace(self.0);
}
}

/// Configures thread local state such that for the duration of the
/// execution of `closure` any call to `with` will yield `ptr`, unless this
/// is recursively called again.
#[inline]
pub fn set<R>(state: &CallThreadState, closure: impl FnOnce() -> R) -> Result<R, Box<Trap>> {
pub fn set<R>(state: &CallThreadState, closure: impl FnOnce() -> R) -> R {
struct Reset<'a>(&'a CallThreadState);

impl Drop for Reset<'_> {
#[inline]
fn drop(&mut self) {
raw::replace(self.0.prev.replace(ptr::null()))
.expect("tls should be previously initialized");
raw::replace(self.0.prev.replace(ptr::null()));
}
}

let prev = raw::replace(state)?;
let prev = raw::replace(state);
state.prev.set(prev);
let _reset = Reset(state);
Ok(closure())
closure()
}

/// Returns the last pointer configured with `set` above. Panics if `set`
Expand Down
5 changes: 2 additions & 3 deletions crates/runtime/src/traphandlers/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
#![allow(non_snake_case)]

use crate::traphandlers::{tls, wasmtime_longjmp, Trap};
use crate::traphandlers::{tls, wasmtime_longjmp};
use mach::exception_types::*;
use mach::kern_return::*;
use mach::mach_init::*;
Expand Down Expand Up @@ -410,7 +410,7 @@ unsafe extern "C" fn unwind(wasm_pc: *const u8) -> ! {
/// task-level port which is where we'd expected things like breakpad/crashpad
/// exception handlers to get registered.
#[cold]
pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
pub fn lazy_per_thread_init() {
unsafe {
assert!(WASMTIME_PORT != MACH_PORT_NULL);
let this_thread = mach_thread_self();
Expand All @@ -424,5 +424,4 @@ pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
mach_port_deallocate(mach_task_self(), this_thread);
assert_eq!(kret, KERN_SUCCESS, "failed to set thread exception port");
}
Ok(())
}
17 changes: 8 additions & 9 deletions crates/runtime/src/traphandlers/unix.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::traphandlers::{tls, wasmtime_longjmp, Trap};
use crate::traphandlers::{tls, wasmtime_longjmp};
use std::cell::RefCell;
use std::io;
use std::mem::{self, MaybeUninit};
Expand Down Expand Up @@ -252,7 +252,7 @@ unsafe fn set_pc(cx: *mut libc::c_void, pc: usize, arg1: usize) {
/// and registering our own alternate stack that is large enough and has a guard
/// page.
#[cold]
pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
pub fn lazy_per_thread_init() {
// This thread local is purely used to register a `Stack` to get deallocated
// when the thread exists. Otherwise this function is only ever called at
// most once per-thread.
Expand All @@ -270,11 +270,10 @@ pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
}

return STACK.with(|s| {
*s.borrow_mut() = unsafe { allocate_sigaltstack()? };
Ok(())
*s.borrow_mut() = unsafe { allocate_sigaltstack() };
});

unsafe fn allocate_sigaltstack() -> Result<Option<Stack>, Box<Trap>> {
unsafe fn allocate_sigaltstack() -> Option<Stack> {
// Check to see if the existing sigaltstack, if it exists, is big
// enough. If so we don't need to allocate our own.
let mut old_stack = mem::zeroed();
Expand All @@ -286,7 +285,7 @@ pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
io::Error::last_os_error()
);
if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
return Ok(None);
return None;
}

// ... but failing that we need to allocate our own, so do all that
Expand All @@ -301,7 +300,7 @@ pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
rustix::mm::ProtFlags::empty(),
rustix::mm::MapFlags::PRIVATE,
)
.map_err(|_| Box::new(Trap::oom()))?;
.expect("failed to allocate memory for sigaltstack");

// Prepare the stack with readable/writable memory and then register it
// with `sigaltstack`.
Expand All @@ -325,10 +324,10 @@ pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
io::Error::last_os_error()
);

Ok(Some(Stack {
Some(Stack {
mmap_ptr: ptr,
mmap_size: alloc_size,
}))
})
}

impl Drop for Stack {
Expand Down
5 changes: 2 additions & 3 deletions crates/runtime/src/traphandlers/windows.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::traphandlers::{tls, wasmtime_longjmp, Trap};
use crate::traphandlers::{tls, wasmtime_longjmp};
use std::io;
use winapi::um::errhandlingapi::*;
use winapi::um::minwinbase::*;
Expand Down Expand Up @@ -74,7 +74,6 @@ unsafe extern "system" fn exception_handler(exception_info: PEXCEPTION_POINTERS)
})
}

pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
pub fn lazy_per_thread_init() {
// Unused on Windows
Ok(())
}
8 changes: 4 additions & 4 deletions crates/wasmtime/src/engine.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::signatures::SignatureRegistry;
use crate::{Config, Trap};
use crate::Config;
use anyhow::Result;
use once_cell::sync::OnceCell;
#[cfg(feature = "parallel-compilation")]
Expand Down Expand Up @@ -71,7 +71,7 @@ impl Engine {
// Ensure that wasmtime_runtime's signal handlers are configured. This
// is the per-program initialization required for handling traps, such
// as configuring signals, vectored exception handlers, etc.
wasmtime_runtime::init_traps(crate::module::GlobalModuleRegistry::is_wasm_trap_pc);
wasmtime_runtime::init_traps(crate::module::is_wasm_trap_pc);
debug_builtins::ensure_exported();

let registry = SignatureRegistry::new();
Expand Down Expand Up @@ -117,8 +117,8 @@ impl Engine {
/// on calls into WebAssembly. This is provided for use cases where the
/// latency of WebAssembly calls are extra-important, which is not
/// necessarily true of all embeddings.
pub fn tls_eager_initialize() -> Result<(), Trap> {
wasmtime_runtime::tls_eager_initialize().map_err(Trap::from_runtime_box)
pub fn tls_eager_initialize() {
wasmtime_runtime::tls_eager_initialize();
}

/// Returns the configuration settings that this engine is using.
Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1241,7 +1241,7 @@ pub(crate) fn invoke_wasm_and_catch_traps<T>(
);
exit_wasm(store, exit);
store.0.call_hook(CallHook::ReturningFromWasm)?;
result.map_err(Trap::from_runtime_box)
result.map_err(|t| Trap::from_runtime_box(store.0, t))
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/wasmtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ pub use crate::instance::{Instance, InstancePre};
pub use crate::limits::*;
pub use crate::linker::*;
pub use crate::memory::*;
pub use crate::module::{FrameInfo, FrameSymbol, Module};
pub use crate::module::Module;
pub use crate::r#ref::ExternRef;
#[cfg(feature = "async")]
pub use crate::store::CallHookHandler;
Expand Down
4 changes: 2 additions & 2 deletions crates/wasmtime/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use wasmtime_runtime::{
mod registry;
mod serialization;

pub use registry::{FrameInfo, FrameSymbol, GlobalModuleRegistry, ModuleRegistry};
pub use registry::{is_wasm_trap_pc, ModuleRegistry};
pub use serialization::SerializedModule;

/// A compiled WebAssembly module, ready to be instantiated.
Expand Down Expand Up @@ -511,7 +511,7 @@ impl Module {
// into the global registry of modules so we can resolve traps
// appropriately. Note that the corresponding `unregister` happens below
// in `Drop for ModuleInner`.
registry::register(engine, &module);
registry::register(&module);

Ok(Self {
inner: Arc::new(ModuleInner {
Expand Down
Loading

0 comments on commit 82a3168

Please sign in to comment.