diff --git a/crates/environ/src/vmoffsets.rs b/crates/environ/src/vmoffsets.rs index 36ff9c6c75..f6f67cab92 100644 --- a/crates/environ/src/vmoffsets.rs +++ b/crates/environ/src/vmoffsets.rs @@ -4,6 +4,7 @@ // Currently the `VMContext` allocation by field looks like this: // // struct VMContext { +// magic: u32, // runtime_limits: *const VMRuntimeLimits, // externref_activations_table: *mut VMExternRefActivationsTable, // store: *mut dyn Store, @@ -74,6 +75,7 @@ pub struct VMOffsets

{ pub num_escaped_funcs: u32, // precalculated offsets of various member fields + magic: u32, runtime_limits: u32, epoch_ptr: u32, externref_activations_table: u32, @@ -222,6 +224,7 @@ impl VMOffsets

{ externref_activations_table: "jit host externref state", epoch_ptr: "jit current epoch state", runtime_limits: "jit runtime limits state", + magic: "magic value", } } } @@ -239,6 +242,7 @@ impl From> for VMOffsets

{ num_defined_memories: fields.num_defined_memories, num_defined_globals: fields.num_defined_globals, num_escaped_funcs: fields.num_escaped_funcs, + magic: 0, runtime_limits: 0, epoch_ptr: 0, externref_activations_table: 0, @@ -278,7 +282,7 @@ impl From> for VMOffsets

{ next_field_offset = cadd(next_field_offset, u32::from($size)); fields!($($rest)*); }; - (align($align:literal), $($rest:tt)*) => { + (align($align:expr), $($rest:tt)*) => { next_field_offset = align(next_field_offset, $align); fields!($($rest)*); }; @@ -286,6 +290,8 @@ impl From> for VMOffsets

{ } fields! { + size(magic) = 4u32, + align(u32::from(ret.ptr.size())), size(runtime_limits) = ret.ptr.size(), size(epoch_ptr) = ret.ptr.size(), size(externref_activations_table) = ret.ptr.size(), @@ -315,6 +321,11 @@ impl From> for VMOffsets

{ ret.size = next_field_offset; + // This is required by the implementation of `VMContext::instance` and + // `VMContext::instance_mut`. If this value changes then those locations + // need to be updated. + assert_eq!(ret.magic, 0); + return ret; } } @@ -535,6 +546,12 @@ impl VMOffsets

{ /// Offsets for `VMContext`. impl VMOffsets

{ + /// Return the offset to the `magic` value in this `VMContext`. + #[inline] + pub fn vmctx_magic(&self) -> u32 { + self.magic + } + /// Return the offset to the `VMRuntimeLimits` structure #[inline] pub fn vmctx_runtime_limits(&self) -> u32 { diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index b08af11a17..854d4e0179 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -9,8 +9,8 @@ use crate::table::{Table, TableElement, TableElementType}; use crate::traphandlers::Trap; use crate::vmcontext::{ VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionImport, - VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMRuntimeLimits, - VMTableDefinition, VMTableImport, + VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMOpaqueContext, + VMRuntimeLimits, VMTableDefinition, VMTableImport, VMCONTEXT_MAGIC, }; use crate::{ ExportFunction, ExportGlobal, ExportMemory, ExportTable, Imports, ModuleRuntimeInfo, Store, @@ -488,7 +488,7 @@ impl Instance { (self.runtime_info.image_base() + self.runtime_info.function_info(def_index).start as usize) as *mut _, - self.vmctx_ptr(), + VMOpaqueContext::from_vmcontext(self.vmctx_ptr()), ) } else { let import = self.imported_function(index); @@ -879,6 +879,8 @@ impl Instance { unsafe fn initialize_vmctx(&mut self, module: &Module, store: StorePtr, imports: Imports) { assert!(std::ptr::eq(module, self.module().as_ref())); + *self.vmctx_plus_offset(self.offsets.vmctx_magic()) = VMCONTEXT_MAGIC; + if let Some(store) = store.as_raw() { *self.runtime_limits() = (*store).vmruntime_limits(); *self.epoch_ptr() = (*store).epoch_ptr(); diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index d7d7d0ec92..b8eba8e3bc 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -65,8 +65,9 @@ pub use crate::traphandlers::{ }; pub use crate::vmcontext::{ VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, - VMGlobalImport, VMInvokeArgument, VMMemoryDefinition, VMMemoryImport, VMRuntimeLimits, - VMSharedSignatureIndex, VMTableDefinition, VMTableImport, VMTrampoline, ValRaw, + VMGlobalImport, VMInvokeArgument, VMMemoryDefinition, VMMemoryImport, VMOpaqueContext, + VMRuntimeLimits, VMSharedSignatureIndex, VMTableDefinition, VMTableImport, VMTrampoline, + ValRaw, }; mod module_id; diff --git a/crates/runtime/src/vmcontext.rs b/crates/runtime/src/vmcontext.rs index 22a148a2b0..fc650ed9c7 100644 --- a/crates/runtime/src/vmcontext.rs +++ b/crates/runtime/src/vmcontext.rs @@ -9,6 +9,8 @@ use std::marker; use std::ptr::NonNull; use std::u32; +pub const VMCONTEXT_MAGIC: u32 = u32::from_le_bytes(*b"core"); + /// An imported function. #[derive(Debug, Copy, Clone)] #[repr(C)] @@ -16,8 +18,13 @@ pub struct VMFunctionImport { /// A pointer to the imported function body. pub body: NonNull, - /// A pointer to the `VMContext` that owns the function. - pub vmctx: *mut VMContext, + /// The VM state associated with this function. + /// + /// For core wasm instances this will be `*mut VMContext` but for the + /// upcoming implementation of the component model this will be something + /// else. The actual definition of what this pointer points to depends on + /// the definition of `func_ptr` and what compiled it. + pub vmctx: *mut VMOpaqueContext, } // Declare that this type is send/sync, it's the responsibility of users of @@ -546,8 +553,13 @@ pub struct VMCallerCheckedAnyfunc { pub func_ptr: NonNull, /// Function signature id. pub type_index: VMSharedSignatureIndex, - /// Function `VMContext`. - pub vmctx: *mut VMContext, + /// The VM state associated with this function. + /// + /// For core wasm instances this will be `*mut VMContext` but for the + /// upcoming implementation of the component model this will be something + /// else. The actual definition of what this pointer points to depends on + /// the definition of `func_ptr` and what compiled it. + pub vmctx: *mut VMOpaqueContext, // If more elements are added here, remember to add offset_of tests below! } @@ -746,6 +758,29 @@ pub struct VMContext { } impl VMContext { + /// Helper function to cast between context types using a debug assertion to + /// protect against some mistakes. + #[inline] + pub unsafe fn from_opaque(opaque: *mut VMOpaqueContext) -> *mut VMContext { + // Note that in general the offset of the "magic" field is stored in + // `VMOffsets::vmctx_magic`. Given though that this is a sanity check + // about converting this pointer to another type we ideally don't want + // to read the offset from potentially corrupt memory. Instead it would + // be better to catch errors here as soon as possible. + // + // To accomplish this the `VMContext` structure is laid out with the + // magic field at a statically known offset (here it's 0 for now). This + // static offset is asserted in `VMOffsets::from` and needs to be kept + // in sync with this line for this debug assertion to work. + // + // Also note that this magic is only ever invalid in the presence of + // bugs, meaning we don't actually read the magic and act differently + // at runtime depending what it is, so this is a debug assertion as + // opposed to a regular assertion. + debug_assert_eq!((*opaque).magic, VMCONTEXT_MAGIC); + opaque.cast() + } + /// Return a mutable reference to the associated `Instance`. /// /// # Safety @@ -968,10 +1003,69 @@ impl ValRaw { } } -/// Trampoline function pointer type. -pub type VMTrampoline = unsafe extern "C" fn( - *mut VMContext, // callee vmctx - *mut VMContext, // caller vmctx - *const VMFunctionBody, // function we're actually calling - *mut ValRaw, // space for arguments and return values -); +/// Type definition of the trampoline used to enter WebAssembly from the host. +/// +/// This function type is what's generated for the entry trampolines that are +/// compiled into a WebAssembly module's image. Note that trampolines are not +/// always used by Wasmtime since the `TypedFunc` API allows bypassing the +/// trampoline and directly calling the underlying wasm function (at the time of +/// this writing). +/// +/// The trampoline's arguments here are: +/// +/// * `*mut VMOpaqueContext` - this a contextual pointer defined within the +/// context of the receiving function pointer. For now this is always `*mut +/// VMContext` but with the component model it may be the case that this is a +/// different type of pointer. +/// +/// * `*mut VMContext` - this is the "caller" context, which at this time is +/// always unconditionally core wasm (even in the component model). This +/// contextual pointer cannot be `NULL` and provides information necessary to +/// resolve the caller's context for the `Caller` API in Wasmtime. +/// +/// * `*const VMFunctionBody` - this is the indirect function pointer which is +/// the actual target function to invoke. This function uses the System-V ABI +/// for its argumenst and a semi-custom ABI for the return values (one return +/// value is returned directly, multiple return values have the first one +/// returned directly and remaining ones returned indirectly through a +/// stack pointer). This function pointer may be Cranelift-compiled code or it +/// may also be a host-compiled trampoline (e.g. when a host function calls a +/// host function through the `wasmtime::Func` wrapper). The definition of the +/// first argument of this function depends on what this receiving function +/// pointer desires. +/// +/// * `*mut ValRaw` - this is storage space for both arguments and results of +/// the function. The trampoline will read the arguments from this array to +/// pass to the function pointer provided. The results are then written to the +/// array afterwards (both reads and writes start at index 0). It's the +/// caller's responsibility to make sure this array is appropriately sized. +pub type VMTrampoline = + unsafe extern "C" fn(*mut VMOpaqueContext, *mut VMContext, *const VMFunctionBody, *mut ValRaw); + +/// An "opaque" version of `VMContext` which must be explicitly casted to a +/// target context. +/// +/// This context is used to represent that contexts specified in +/// `VMCallerCheckedAnyfunc` can have any type and don't have an implicit +/// structure. Neither wasmtime nor cranelift-generated code can rely on the +/// structure of an opaque context in general and only the code which configured +/// the context is able to rely on a particular structure. This is because the +/// context pointer configured for `VMCallerCheckedAnyfunc` is guaranteed to be +/// the first parameter passed. +/// +/// Note that Wasmtime currently has a layout where all contexts that are casted +/// to an opaque context start with a 32-bit "magic" which can be used in debug +/// mode to debug-assert that the casts here are correct and have at least a +/// little protection against incorrect casts. +pub struct VMOpaqueContext { + magic: u32, + _marker: marker::PhantomPinned, +} + +impl VMOpaqueContext { + /// Helper function to clearly indicate that cast desired + #[inline] + pub fn from_vmcontext(ptr: *mut VMContext) -> *mut VMOpaqueContext { + ptr.cast() + } +} diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index 6c8b9c1d50..2a9d8b5e31 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -13,8 +13,8 @@ use std::sync::Arc; use wasmtime_environ::FuncIndex; use wasmtime_runtime::{ raise_user_trap, ExportFunction, InstanceAllocator, InstanceHandle, OnDemandInstanceAllocator, - VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMSharedSignatureIndex, - VMTrampoline, + VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMOpaqueContext, + VMSharedSignatureIndex, VMTrampoline, }; /// A WebAssembly function which can be called. @@ -1852,7 +1852,7 @@ macro_rules! impl_into_func { /// by Cranelift, since Cranelift is generating raw function /// calls directly to this function. unsafe extern "C" fn wasm_to_host_shim( - vmctx: *mut VMContext, + vmctx: *mut VMOpaqueContext, caller_vmctx: *mut VMContext, $( $args: $args::Abi, )* retptr: R::Retptr, @@ -1875,6 +1875,7 @@ macro_rules! impl_into_func { // should be part of this block, and the long-jmp-ing // happens after the block in handling `CallResult`. let result = Caller::with(caller_vmctx, |mut caller| { + let vmctx = VMContext::from_opaque(vmctx); let state = (*vmctx).host_state(); // Double-check ourselves in debug mode, but we control // the `Any` here so an unsafe downcast should also @@ -1940,7 +1941,7 @@ macro_rules! impl_into_func { /// calls the given function pointer, and then stores the result /// back into the `args` array. unsafe extern "C" fn host_trampoline<$($args,)* R>( - callee_vmctx: *mut VMContext, + callee_vmctx: *mut VMOpaqueContext, caller_vmctx: *mut VMContext, ptr: *const VMFunctionBody, args: *mut ValRaw, @@ -1952,7 +1953,7 @@ macro_rules! impl_into_func { let ptr = mem::transmute::< *const VMFunctionBody, unsafe extern "C" fn( - *mut VMContext, + *mut VMOpaqueContext, *mut VMContext, $( $args::Abi, )* R::Retptr, diff --git a/crates/wasmtime/src/func/typed.rs b/crates/wasmtime/src/func/typed.rs index d16d3e1a42..1c3d795b68 100644 --- a/crates/wasmtime/src/func/typed.rs +++ b/crates/wasmtime/src/func/typed.rs @@ -5,7 +5,9 @@ use anyhow::{bail, Result}; use std::marker; use std::mem::{self, MaybeUninit}; use std::ptr; -use wasmtime_runtime::{VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMSharedSignatureIndex}; +use wasmtime_runtime::{ + VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMOpaqueContext, VMSharedSignatureIndex, +}; /// A statically typed WebAssembly function. /// @@ -464,7 +466,7 @@ pub unsafe trait WasmParams: Send { #[doc(hidden)] unsafe fn invoke( func: *const VMFunctionBody, - vmctx1: *mut VMContext, + vmctx1: *mut VMOpaqueContext, vmctx2: *mut VMContext, abi: Self::Abi, ) -> R::ResultAbi; @@ -494,7 +496,7 @@ where unsafe fn invoke( func: *const VMFunctionBody, - vmctx1: *mut VMContext, + vmctx1: *mut VMOpaqueContext, vmctx2: *mut VMContext, abi: Self::Abi, ) -> R::ResultAbi { @@ -554,14 +556,14 @@ macro_rules! impl_wasm_params { unsafe fn invoke( func: *const VMFunctionBody, - vmctx1: *mut VMContext, + vmctx1: *mut VMOpaqueContext, vmctx2: *mut VMContext, abi: Self::Abi, ) -> R::ResultAbi { let fnptr = mem::transmute::< *const VMFunctionBody, unsafe extern "C" fn( - *mut VMContext, + *mut VMOpaqueContext, *mut VMContext, $($t::Abi,)* ::Retptr, diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs index e373950f5d..b548f6016b 100644 --- a/crates/wasmtime/src/instance.rs +++ b/crates/wasmtime/src/instance.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use wasmtime_environ::{EntityType, FuncIndex, GlobalIndex, MemoryIndex, PrimaryMap, TableIndex}; use wasmtime_runtime::{ Imports, InstanceAllocationRequest, InstantiationError, StorePtr, VMContext, VMFunctionBody, - VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport, + VMFunctionImport, VMGlobalImport, VMMemoryImport, VMOpaqueContext, VMTableImport, }; /// An instantiated WebAssembly module. @@ -345,7 +345,7 @@ impl Instance { super::func::invoke_wasm_and_catch_traps(store, |_default_callee| { mem::transmute::< *const VMFunctionBody, - unsafe extern "C" fn(*mut VMContext, *mut VMContext), + unsafe extern "C" fn(*mut VMOpaqueContext, *mut VMContext), >(f.anyfunc.as_ref().func_ptr.as_ptr())( f.anyfunc.as_ref().vmctx, vmctx ) diff --git a/crates/wasmtime/src/trampoline/func.rs b/crates/wasmtime/src/trampoline/func.rs index 3a07f7067e..bb77dbe531 100644 --- a/crates/wasmtime/src/trampoline/func.rs +++ b/crates/wasmtime/src/trampoline/func.rs @@ -12,8 +12,8 @@ use wasmtime_environ::{ use wasmtime_jit::{CodeMemory, ProfilingAgent}; use wasmtime_runtime::{ Imports, InstanceAllocationRequest, InstanceAllocator, InstanceHandle, - OnDemandInstanceAllocator, StorePtr, VMContext, VMFunctionBody, VMSharedSignatureIndex, - VMTrampoline, + OnDemandInstanceAllocator, StorePtr, VMContext, VMFunctionBody, VMOpaqueContext, + VMSharedSignatureIndex, VMTrampoline, }; struct TrampolineState { @@ -23,7 +23,7 @@ struct TrampolineState { } unsafe extern "C" fn stub_fn( - vmctx: *mut VMContext, + vmctx: *mut VMOpaqueContext, caller_vmctx: *mut VMContext, values_vec: *mut ValRaw, values_vec_len: usize, @@ -44,6 +44,7 @@ unsafe extern "C" fn stub_fn( // have any. To prevent leaks we avoid having any local destructors by // avoiding local variables. let result = panic::catch_unwind(AssertUnwindSafe(|| { + let vmctx = VMContext::from_opaque(vmctx); // Double-check ourselves in debug mode, but we control // the `Any` here so an unsafe downcast should also // work. diff --git a/tests/all/pooling_allocator.rs b/tests/all/pooling_allocator.rs index 0509df7825..b68dd1602a 100644 --- a/tests/all/pooling_allocator.rs +++ b/tests/all/pooling_allocator.rs @@ -630,11 +630,10 @@ fn instance_too_large() -> Result<()> { let engine = Engine::new(&config)?; let expected = "\ -instance allocation for this module requires 304 bytes which exceeds the \ +instance allocation for this module requires 320 bytes which exceeds the \ configured maximum of 16 bytes; breakdown of allocation requirement: - * 78.95% - 240 bytes - instance state management - * 5.26% - 16 bytes - jit store state + * 80.00% - 256 bytes - instance state management "; match Module::new(&engine, "(module)") { Ok(_) => panic!("should have failed to compile"), @@ -648,11 +647,11 @@ configured maximum of 16 bytes; breakdown of allocation requirement: lots_of_globals.push_str(")"); let expected = "\ -instance allocation for this module requires 1904 bytes which exceeds the \ +instance allocation for this module requires 1920 bytes which exceeds the \ configured maximum of 16 bytes; breakdown of allocation requirement: - * 12.61% - 240 bytes - instance state management - * 84.03% - 1600 bytes - defined globals + * 13.33% - 256 bytes - instance state management + * 83.33% - 1600 bytes - defined globals "; match Module::new(&engine, &lots_of_globals) { Ok(_) => panic!("should have failed to compile"),