diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index 9985d0afd0..c78dd3b9ca 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -152,19 +152,38 @@ impl wasmtime_environ::Compiler for Compiler { let mut func_env = FuncEnvironment::new(isa, translation, types, tunables); - // We use these as constant offsets below in - // `stack_limit_from_arguments`, so assert their values here. This - // allows the closure below to get coerced to a function pointer, as - // needed by `ir::Function`. + // The `stack_limit` global value below is the implementation of stack + // overflow checks in Wasmtime. // - // Otherwise our stack limit is specially calculated from the vmctx - // argument, where we need to load the `*const VMRuntimeLimits` - // pointer, and then from that pointer we need to load the stack - // limit itself. Note that manual register allocation is needed here - // too due to how late in the process this codegen happens. + // The Wasm spec defines that stack overflows will raise a trap, and + // there's also an added constraint where as an embedder you frequently + // are running host-provided code called from wasm. WebAssembly and + // native code currently share the same call stack, so Wasmtime needs to + // make sure that host-provided code will have enough call-stack + // available to it. // - // For more information about interrupts and stack checks, see the - // top of this file. + // The way that stack overflow is handled here is by adding a prologue + // check to all functions for how much native stack is remaining. The + // `VMContext` pointer is the first argument to all functions, and the + // first field of this structure is `*const VMRuntimeLimits` and the + // first field of that is the stack limit. Note that the stack limit in + // this case means "if the stack pointer goes below this, trap". Each + // function which consumes stack space or isn't a leaf function starts + // off by loading the stack limit, checking it against the stack + // pointer, and optionally traps. + // + // This manual check allows the embedder to give wasm a relatively + // precise amount of stack allocation. Using this scheme we reserve a + // chunk of stack for wasm code relative from where wasm code was + // called. This ensures that native code called by wasm should have + // native stack space to run, and the numbers of stack spaces here + // should all be configurable for various embeddings. + // + // Note that this check is independent of each thread's stack guard page + // here. If the stack guard page is reached that's still considered an + // abort for the whole program since the runtime limits configured by + // the embedder should cause wasm to trap before it reaches that + // (ensuring the host has enough space as well for its functionality). let vmctx = context .func .create_global_value(ir::GlobalValueData::VMContext); diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index 2d429e6922..e98abefd5b 100644 --- a/crates/cranelift/src/lib.rs +++ b/crates/cranelift/src/lib.rs @@ -3,47 +3,6 @@ //! This crate provides an implementation of the `wasmtime_environ::Compiler` //! and `wasmtime_environ::CompilerBuilder` traits. -// # How does Wasmtime prevent stack overflow? -// -// A few locations throughout the codebase link to this file to explain stack -// overflow. To start off, let's take a look at stack overflow. Wasm code is -// well-defined to have stack overflow being recoverable and raising a trap, so -// we need to handle this somehow! There's also an added constraint where as an -// embedder you frequently are running host-provided code called from wasm. -// WebAssembly and native code currently share the same call stack, so you want -// to make sure that your host-provided code will have enough call-stack -// available to it. -// -// Given all that, the way that stack overflow is handled is by adding a -// prologue check to all JIT functions for how much native stack is remaining. -// The `VMContext` pointer is the first argument to all functions, and the first -// field of this structure is `*const VMRuntimeLimits` and the first field of -// that is the stack limit. Note that the stack limit in this case means "if the -// stack pointer goes below this, trap". Each JIT function which consumes stack -// space or isn't a leaf function starts off by loading the stack limit, -// checking it against the stack pointer, and optionally traps. -// -// This manual check allows the embedder (us) to give wasm a relatively precise -// amount of stack allocation. Using this scheme we reserve a chunk of stack -// for wasm code relative from where wasm code was called. This ensures that -// native code called by wasm should have native stack space to run, and the -// numbers of stack spaces here should all be configurable for various -// embeddings. -// -// Note that we do not consider each thread's stack guard page here. It's -// considered that if you hit that you still abort the whole program. This -// shouldn't happen most of the time because wasm is always stack-bound and -// it's up to the embedder to bound its own native stack. -// -// So all-in-all, that's how we implement stack checks. Note that stack checks -// cannot be disabled because it's a feature of core wasm semantics. This means -// that all functions almost always have a stack check prologue, and it's up to -// us to optimize away that cost as much as we can. -// -// For more information about the tricky bits of managing the reserved stack -// size of wasm, see the implementation in `traphandlers.rs` in the -// `update_stack_limit` function. - use cranelift_codegen::binemit; use cranelift_codegen::ir; use cranelift_codegen::isa::{unwind::UnwindInfo, CallConv, TargetIsa};