Delete historical interruptable support in Wasmtime (#3925)
* Delete historical interruptable support in Wasmtime This commit removes the `Config::interruptable` configuration along with the `InterruptHandle` type from the `wasmtime` crate. The original support for adding interruption to WebAssembly was added pretty early on in the history of Wasmtime when there was no other method to prevent an infinite loop from the host. Nowadays, however, there are alternative methods for interruption such as fuel or epoch-based interruption. One of the major downsides of `Config::interruptable` is that even when it's not enabled it forces an atomic swap to happen when entering WebAssembly code. This technically could be a non-atomic swap if the configuration option isn't enabled but that produces even more branch-y code on entry into WebAssembly which is already something we try to optimize. Calling into WebAssembly is on the order of a dozens of nanoseconds at this time and an atomic swap, even uncontended, can add up to 5ns on some platforms. The main goal of this PR is to remove this atomic swap on entry into WebAssembly. This is done by removing the `Config::interruptable` field entirely, moving all existing consumers to epochs instead which are suitable for the same purposes. This means that the stack overflow check is no longer entangled with the interruption check and perhaps one day we could continue to optimize that further as well. Some consequences of this change are: * Epochs are now the only method of remote-thread interruption. * There are no more Wasmtime traps that produces the `Interrupted` trap code, although we may wish to move future traps to this so I left it in place. * The C API support for interrupt handles was also removed and bindings for epoch methods were added. * Function-entry checks for interruption are a tiny bit less efficient since one check is performed for the stack limit and a second is performed for the epoch as opposed to the `Config::interruptable` style of bundling the stack limit and the interrupt check in one. It's expected though that this is likely to not really be measurable. * The old `VMInterrupts` structure is renamed to `VMRuntimeLimits`.
This commit is contained in:
@@ -257,16 +257,6 @@ impl Config {
|
||||
/// and the periodic yields with epochs should ensure that when the
|
||||
/// timeout is reached it's appropriately recognized.
|
||||
///
|
||||
/// * Finally you can spawn futures into a thread pool. By doing this in a
|
||||
/// thread pool you are relaxing the requirement that `Future::poll` must
|
||||
/// be fast because your future is executing on a separate thread. This
|
||||
/// strategy, however, would likely still require some form of
|
||||
/// cancellation via [`Config::epoch_interruption`] or
|
||||
/// [`crate::Store::interrupt_handle`] to ensure wasm doesn't take *too*
|
||||
/// long to execute. This solution is generally not recommended for its
|
||||
/// complexity and instead one of the previous solutions should likely be
|
||||
/// used.
|
||||
///
|
||||
/// In all cases special care needs to be taken when integrating
|
||||
/// asynchronous wasm into your application. You should carefully plan where
|
||||
/// WebAssembly will execute and what compute resources will be allotted to
|
||||
@@ -314,27 +304,13 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures whether functions and loops will be interruptable via the
|
||||
/// [`Store::interrupt_handle`](crate::Store::interrupt_handle) method.
|
||||
///
|
||||
/// For more information see the documentation on
|
||||
/// [`Store::interrupt_handle`](crate::Store::interrupt_handle).
|
||||
///
|
||||
/// By default this option is `false`.
|
||||
pub fn interruptable(&mut self, enable: bool) -> &mut Self {
|
||||
self.tunables.interruptable = enable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures whether execution of WebAssembly will "consume fuel" to
|
||||
/// either halt or yield execution as desired.
|
||||
///
|
||||
/// This option is similar in purpose to [`Config::interruptable`] where
|
||||
/// you can prevent infinitely-executing WebAssembly code. The difference
|
||||
/// is that this option allows deterministic execution of WebAssembly code
|
||||
/// by instrumenting generated code consume fuel as it executes. When fuel
|
||||
/// runs out the behavior is defined by configuration within a [`Store`],
|
||||
/// and by default a trap is raised.
|
||||
/// This can be used to deterministically prevent infinitely-executing
|
||||
/// WebAssembly code by instrumenting generated code to consume fuel as it
|
||||
/// executes. When fuel runs out the behavior is defined by configuration
|
||||
/// within a [`Store`], and by default a trap is raised.
|
||||
///
|
||||
/// Note that a [`Store`] starts with no fuel, so if you enable this option
|
||||
/// you'll have to be sure to pour some fuel into [`Store`] before
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::store::{StoreData, StoreOpaque, Stored};
|
||||
use crate::{
|
||||
AsContext, AsContextMut, CallHook, Engine, Extern, FuncType, Instance, InterruptHandle,
|
||||
StoreContext, StoreContextMut, Trap, Val, ValRaw, ValType,
|
||||
AsContext, AsContextMut, CallHook, Engine, Extern, FuncType, Instance, StoreContext,
|
||||
StoreContextMut, Trap, Val, ValRaw, ValType,
|
||||
};
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use std::future::Future;
|
||||
@@ -9,7 +9,6 @@ use std::mem;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::pin::Pin;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::{EntityIndex, FuncIndex};
|
||||
use wasmtime_runtime::{
|
||||
@@ -1198,14 +1197,13 @@ pub(crate) fn invoke_wasm_and_catch_traps<T>(
|
||||
closure: impl FnMut(*mut VMContext),
|
||||
) -> Result<(), Trap> {
|
||||
unsafe {
|
||||
let exit = enter_wasm(store)?;
|
||||
let exit = enter_wasm(store);
|
||||
|
||||
if let Err(trap) = store.0.call_hook(CallHook::CallingWasm) {
|
||||
exit_wasm(store, exit);
|
||||
return Err(trap);
|
||||
}
|
||||
let result = wasmtime_runtime::catch_traps(
|
||||
store.0.vminterrupts(),
|
||||
store.0.signal_handler(),
|
||||
store.0.default_callee(),
|
||||
closure,
|
||||
@@ -1232,7 +1230,7 @@ pub(crate) fn invoke_wasm_and_catch_traps<T>(
|
||||
///
|
||||
/// This function may fail if the the stack limit can't be set because an
|
||||
/// interrupt already happened.
|
||||
fn enter_wasm<T>(store: &mut StoreContextMut<'_, T>) -> Result<Option<usize>, Trap> {
|
||||
fn enter_wasm<T>(store: &mut StoreContextMut<'_, T>) -> Option<usize> {
|
||||
// If this is a recursive call, e.g. our stack canary is already set, then
|
||||
// we may be able to skip this function.
|
||||
//
|
||||
@@ -1252,7 +1250,7 @@ fn enter_wasm<T>(store: &mut StoreContextMut<'_, T>) -> Result<Option<usize>, Tr
|
||||
.is_some()
|
||||
&& !store.0.async_support()
|
||||
{
|
||||
return Ok(None);
|
||||
return None;
|
||||
}
|
||||
|
||||
let stack_pointer = psm::stack_pointer() as usize;
|
||||
@@ -1270,34 +1268,13 @@ fn enter_wasm<T>(store: &mut StoreContextMut<'_, T>) -> Result<Option<usize>, Tr
|
||||
// (a million bytes) the slop shouldn't matter too much.
|
||||
//
|
||||
// After we've got the stack limit then we store it into the `stack_limit`
|
||||
// variable. Note that the store is an atomic swap to ensure that we can
|
||||
// consume any previously-sent interrupt requests. If we found that wasm was
|
||||
// previously interrupted then we immediately return a trap (after resetting
|
||||
// the stack limit). Otherwise we're good to keep on going.
|
||||
//
|
||||
// Note the usage of `Relaxed` memory orderings here. This is specifically
|
||||
// an optimization in the `Drop` below where a `Relaxed` store is speedier
|
||||
// than a `SeqCst` store. The rationale for `Relaxed` here is that the
|
||||
// atomic orderings here aren't actually protecting any memory, we're just
|
||||
// trying to be atomic with respect to this one location in memory (for when
|
||||
// `InterruptHandle` sends us a signal). Due to the lack of needing to
|
||||
// synchronize with any other memory it's hoped that the choice of `Relaxed`
|
||||
// here should be correct for our use case.
|
||||
// variable.
|
||||
let wasm_stack_limit = stack_pointer - store.engine().config().max_wasm_stack;
|
||||
let interrupts = store.0.interrupts();
|
||||
let prev_stack = match interrupts.stack_limit.swap(wasm_stack_limit, Relaxed) {
|
||||
wasmtime_environ::INTERRUPTED => {
|
||||
// This means that an interrupt happened before we actually
|
||||
// called this function, which means that we're now
|
||||
// considered interrupted.
|
||||
interrupts.stack_limit.store(usize::max_value(), Relaxed);
|
||||
return Err(Trap::new_wasm(
|
||||
None,
|
||||
wasmtime_environ::TrapCode::Interrupt,
|
||||
backtrace::Backtrace::new_unresolved(),
|
||||
));
|
||||
}
|
||||
n => n,
|
||||
let prev_stack = unsafe {
|
||||
mem::replace(
|
||||
&mut *store.0.runtime_limits().stack_limit.get(),
|
||||
wasm_stack_limit,
|
||||
)
|
||||
};
|
||||
|
||||
// The `usize::max_value()` sentinel is present on recursive calls to
|
||||
@@ -1315,7 +1292,7 @@ fn enter_wasm<T>(store: &mut StoreContextMut<'_, T>) -> Result<Option<usize>, Tr
|
||||
.set_stack_canary(Some(stack_pointer));
|
||||
}
|
||||
|
||||
Ok(Some(prev_stack))
|
||||
Some(prev_stack)
|
||||
}
|
||||
|
||||
fn exit_wasm<T>(store: &mut StoreContextMut<'_, T>, prev_stack: Option<usize>) {
|
||||
@@ -1333,8 +1310,9 @@ fn exit_wasm<T>(store: &mut StoreContextMut<'_, T>, prev_stack: Option<usize>) {
|
||||
store.0.externref_activations_table().set_stack_canary(None);
|
||||
}
|
||||
|
||||
// see docs above for why this uses `Relaxed`
|
||||
store.0.interrupts().stack_limit.store(prev_stack, Relaxed);
|
||||
unsafe {
|
||||
*store.0.runtime_limits().stack_limit.get() = prev_stack;
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait implemented for types which can be returned from closures passed to
|
||||
@@ -1746,14 +1724,6 @@ impl<T> Caller<'_, T> {
|
||||
self.store.engine()
|
||||
}
|
||||
|
||||
/// Returns an [`InterruptHandle`] to interrupt wasm execution.
|
||||
///
|
||||
/// See [`Store::interrupt_handle`](crate::Store::interrupt_handle) for more
|
||||
/// information.
|
||||
pub fn interrupt_handle(&self) -> Result<InterruptHandle> {
|
||||
self.store.interrupt_handle()
|
||||
}
|
||||
|
||||
/// Perform garbage collection of `ExternRef`s.
|
||||
///
|
||||
/// Same as [`Store::gc`](crate::Store::gc).
|
||||
|
||||
@@ -415,9 +415,7 @@ pub use crate::linker::*;
|
||||
pub use crate::memory::*;
|
||||
pub use crate::module::{FrameInfo, FrameSymbol, Module};
|
||||
pub use crate::r#ref::ExternRef;
|
||||
pub use crate::store::{
|
||||
AsContext, AsContextMut, CallHook, InterruptHandle, Store, StoreContext, StoreContextMut,
|
||||
};
|
||||
pub use crate::store::{AsContext, AsContextMut, CallHook, Store, StoreContext, StoreContextMut};
|
||||
pub use crate::trap::*;
|
||||
pub use crate::types::*;
|
||||
pub use crate::values::*;
|
||||
@@ -439,7 +437,6 @@ fn _assert_send_sync() {
|
||||
fn _assert_send<T: Send>(_t: T) {}
|
||||
_assert::<Engine>();
|
||||
_assert::<Config>();
|
||||
_assert::<InterruptHandle>();
|
||||
_assert::<(Func, TypedFunc<(), ()>, Global, Table, Memory)>();
|
||||
_assert::<Instance>();
|
||||
_assert::<Module>();
|
||||
|
||||
@@ -562,7 +562,6 @@ impl<'a> SerializedModule<'a> {
|
||||
dynamic_memory_offset_guard_size,
|
||||
generate_native_debuginfo,
|
||||
parse_wasm_debuginfo,
|
||||
interruptable,
|
||||
consume_fuel,
|
||||
epoch_interruption,
|
||||
static_memory_bound_is_maximum,
|
||||
@@ -603,7 +602,6 @@ impl<'a> SerializedModule<'a> {
|
||||
other.parse_wasm_debuginfo,
|
||||
"WebAssembly backtrace support",
|
||||
)?;
|
||||
Self::check_bool(interruptable, other.interruptable, "interruption support")?;
|
||||
Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
|
||||
Self::check_bool(
|
||||
epoch_interruption,
|
||||
@@ -823,36 +821,36 @@ Caused by:
|
||||
#[test]
|
||||
fn test_tunables_bool_mismatch() -> Result<()> {
|
||||
let mut config = Config::new();
|
||||
config.interruptable(true);
|
||||
config.epoch_interruption(true);
|
||||
|
||||
let engine = Engine::new(&config)?;
|
||||
let module = Module::new(&engine, "(module)")?;
|
||||
|
||||
let mut serialized = SerializedModule::new(&module);
|
||||
serialized.metadata.tunables.interruptable = false;
|
||||
serialized.metadata.tunables.epoch_interruption = false;
|
||||
|
||||
match serialized.into_module(&engine) {
|
||||
Ok(_) => unreachable!(),
|
||||
Err(e) => assert_eq!(
|
||||
e.to_string(),
|
||||
"Module was compiled without interruption support but it is enabled for the host"
|
||||
"Module was compiled without epoch interruption but it is enabled for the host"
|
||||
),
|
||||
}
|
||||
|
||||
let mut config = Config::new();
|
||||
config.interruptable(false);
|
||||
config.epoch_interruption(false);
|
||||
|
||||
let engine = Engine::new(&config)?;
|
||||
let module = Module::new(&engine, "(module)")?;
|
||||
|
||||
let mut serialized = SerializedModule::new(&module);
|
||||
serialized.metadata.tunables.interruptable = true;
|
||||
serialized.metadata.tunables.epoch_interruption = true;
|
||||
|
||||
match serialized.into_module(&engine) {
|
||||
Ok(_) => unreachable!(),
|
||||
Err(e) => assert_eq!(
|
||||
e.to_string(),
|
||||
"Module was compiled with interruption support but it is not enabled for the host"
|
||||
"Module was compiled with epoch interruption but it is not enabled for the host"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,8 @@ use std::task::{Context, Poll};
|
||||
use wasmtime_runtime::{
|
||||
InstanceAllocationRequest, InstanceAllocator, InstanceHandle, ModuleInfo,
|
||||
OnDemandInstanceAllocator, SignalHandler, StorePtr, VMCallerCheckedAnyfunc, VMContext,
|
||||
VMExternRef, VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex, VMTrampoline,
|
||||
VMExternRef, VMExternRefActivationsTable, VMRuntimeLimits, VMSharedSignatureIndex,
|
||||
VMTrampoline,
|
||||
};
|
||||
|
||||
mod context;
|
||||
@@ -255,7 +256,7 @@ pub struct StoreOpaque {
|
||||
_marker: marker::PhantomPinned,
|
||||
|
||||
engine: Engine,
|
||||
interrupts: Arc<VMInterrupts>,
|
||||
runtime_limits: VMRuntimeLimits,
|
||||
instances: Vec<StoreInstance>,
|
||||
signal_handler: Option<Box<SignalHandler<'static>>>,
|
||||
externref_activations_table: VMExternRefActivationsTable,
|
||||
@@ -273,7 +274,7 @@ pub struct StoreOpaque {
|
||||
memory_limit: usize,
|
||||
table_count: usize,
|
||||
table_limit: usize,
|
||||
/// An adjustment to add to the fuel consumed value in `interrupts` above
|
||||
/// An adjustment to add to the fuel consumed value in `runtime_limits` above
|
||||
/// to get the true amount of fuel consumed.
|
||||
fuel_adj: i64,
|
||||
#[cfg(feature = "async")]
|
||||
@@ -434,7 +435,7 @@ impl<T> Store<T> {
|
||||
inner: StoreOpaque {
|
||||
_marker: marker::PhantomPinned,
|
||||
engine: engine.clone(),
|
||||
interrupts: Default::default(),
|
||||
runtime_limits: Default::default(),
|
||||
instances: Vec::new(),
|
||||
signal_handler: None,
|
||||
externref_activations_table: VMExternRefActivationsTable::new(),
|
||||
@@ -628,89 +629,6 @@ impl<T> Store<T> {
|
||||
self.inner.engine()
|
||||
}
|
||||
|
||||
/// Creates an [`InterruptHandle`] which can be used to interrupt the
|
||||
/// execution of instances within this `Store`.
|
||||
///
|
||||
/// An [`InterruptHandle`] handle is a mechanism of ensuring that guest code
|
||||
/// doesn't execute for too long. For example it's used to prevent wasm
|
||||
/// programs for executing infinitely in infinite loops or recursive call
|
||||
/// chains.
|
||||
///
|
||||
/// The [`InterruptHandle`] type is sendable to other threads so you can
|
||||
/// interact with it even while the thread with this `Store` is executing
|
||||
/// wasm code.
|
||||
///
|
||||
/// There's one method on an interrupt handle:
|
||||
/// [`InterruptHandle::interrupt`]. This method is used to generate an
|
||||
/// interrupt and cause wasm code to exit "soon".
|
||||
///
|
||||
/// ## When are interrupts delivered?
|
||||
///
|
||||
/// The term "interrupt" here refers to one of two different behaviors that
|
||||
/// are interrupted in wasm:
|
||||
///
|
||||
/// * The head of every loop in wasm has a check to see if it's interrupted.
|
||||
/// * The prologue of every function has a check to see if it's interrupted.
|
||||
///
|
||||
/// This interrupt mechanism makes no attempt to signal interrupts to
|
||||
/// native code. For example if a host function is blocked, then sending
|
||||
/// an interrupt will not interrupt that operation.
|
||||
///
|
||||
/// Interrupts are consumed as soon as possible when wasm itself starts
|
||||
/// executing. This means that if you interrupt wasm code then it basically
|
||||
/// guarantees that the next time wasm is executing on the target thread it
|
||||
/// will return quickly (either normally if it were already in the process
|
||||
/// of returning or with a trap from the interrupt). Once an interrupt
|
||||
/// trap is generated then an interrupt is consumed, and further execution
|
||||
/// will not be interrupted (unless another interrupt is set).
|
||||
///
|
||||
/// When implementing interrupts you'll want to ensure that the delivery of
|
||||
/// interrupts into wasm code is also handled in your host imports and
|
||||
/// functionality. Host functions need to either execute for bounded amounts
|
||||
/// of time or you'll need to arrange for them to be interrupted as well.
|
||||
///
|
||||
/// ## Return Value
|
||||
///
|
||||
/// This function returns a `Result` since interrupts are not always
|
||||
/// enabled. Interrupts are enabled via the
|
||||
/// [`Config::interruptable`](crate::Config::interruptable) method, and if
|
||||
/// this store's [`Config`](crate::Config) hasn't been configured to enable
|
||||
/// interrupts then an error is returned.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use anyhow::Result;
|
||||
/// # use wasmtime::*;
|
||||
/// # fn main() -> Result<()> {
|
||||
/// // Enable interruptable code via `Config` and then create an interrupt
|
||||
/// // handle which we'll use later to interrupt running code.
|
||||
/// let engine = Engine::new(Config::new().interruptable(true))?;
|
||||
/// let mut store = Store::new(&engine, ());
|
||||
/// let interrupt_handle = store.interrupt_handle()?;
|
||||
///
|
||||
/// // Compile and instantiate a small example with an infinite loop.
|
||||
/// let module = Module::new(&engine, r#"
|
||||
/// (func (export "run") (loop br 0))
|
||||
/// "#)?;
|
||||
/// let instance = Instance::new(&mut store, &module, &[])?;
|
||||
/// let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
|
||||
///
|
||||
/// // Spin up a thread to send us an interrupt in a second
|
||||
/// std::thread::spawn(move || {
|
||||
/// std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
/// interrupt_handle.interrupt();
|
||||
/// });
|
||||
///
|
||||
/// let trap = run.call(&mut store, ()).unwrap_err();
|
||||
/// assert!(trap.to_string().contains("wasm trap: interrupt"));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn interrupt_handle(&self) -> Result<InterruptHandle> {
|
||||
self.inner.interrupt_handle()
|
||||
}
|
||||
|
||||
/// Perform garbage collection of `ExternRef`s.
|
||||
///
|
||||
/// Note that it is not required to actively call this function. GC will
|
||||
@@ -921,13 +839,6 @@ impl<'a, T> StoreContext<'a, T> {
|
||||
self.0.engine()
|
||||
}
|
||||
|
||||
/// Returns an [`InterruptHandle`] to interrupt wasm execution.
|
||||
///
|
||||
/// See [`Store::interrupt_handle`] for more information.
|
||||
pub fn interrupt_handle(&self) -> Result<InterruptHandle> {
|
||||
self.0.interrupt_handle()
|
||||
}
|
||||
|
||||
/// Access the underlying data owned by this `Store`.
|
||||
///
|
||||
/// Same as [`Store::data`].
|
||||
@@ -963,13 +874,6 @@ impl<'a, T> StoreContextMut<'a, T> {
|
||||
self.0.engine()
|
||||
}
|
||||
|
||||
/// Returns an [`InterruptHandle`] to interrupt wasm execution.
|
||||
///
|
||||
/// See [`Store::interrupt_handle`] for more information.
|
||||
pub fn interrupt_handle(&self) -> Result<InterruptHandle> {
|
||||
self.0.interrupt_handle()
|
||||
}
|
||||
|
||||
/// Perform garbage collection of `ExternRef`s.
|
||||
///
|
||||
/// Same as [`Store::gc`].
|
||||
@@ -1111,16 +1015,6 @@ impl StoreOpaque {
|
||||
&mut self.store_data
|
||||
}
|
||||
|
||||
pub fn interrupt_handle(&self) -> Result<InterruptHandle> {
|
||||
if self.engine.config().tunables.interruptable {
|
||||
Ok(InterruptHandle {
|
||||
interrupts: self.interrupts.clone(),
|
||||
})
|
||||
} else {
|
||||
bail!("interrupts aren't enabled for this `Store`")
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn modules_mut(&mut self) -> &mut ModuleRegistry {
|
||||
&mut self.modules
|
||||
@@ -1148,8 +1042,8 @@ impl StoreOpaque {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn interrupts(&self) -> &VMInterrupts {
|
||||
&self.interrupts
|
||||
pub fn runtime_limits(&self) -> &VMRuntimeLimits {
|
||||
&self.runtime_limits
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1263,7 +1157,7 @@ impl StoreOpaque {
|
||||
if !self.engine.config().tunables.consume_fuel {
|
||||
return None;
|
||||
}
|
||||
let consumed = unsafe { *self.interrupts.fuel_consumed.get() };
|
||||
let consumed = unsafe { *self.runtime_limits.fuel_consumed.get() };
|
||||
Some(u64::try_from(self.fuel_adj + consumed).unwrap())
|
||||
}
|
||||
|
||||
@@ -1335,7 +1229,7 @@ impl StoreOpaque {
|
||||
// reasonable amount of time anyway.
|
||||
let fuel = i64::try_from(fuel).unwrap_or(i64::max_value());
|
||||
let adj = self.fuel_adj;
|
||||
let consumed_ptr = unsafe { &mut *self.interrupts.fuel_consumed.get() };
|
||||
let consumed_ptr = unsafe { &mut *self.runtime_limits.fuel_consumed.get() };
|
||||
|
||||
match (consumed_ptr.checked_sub(fuel), adj.checked_add(fuel)) {
|
||||
// If we succesfully did arithmetic without overflowing then we can
|
||||
@@ -1358,7 +1252,7 @@ impl StoreOpaque {
|
||||
}
|
||||
|
||||
fn consume_fuel(&mut self, fuel: u64) -> Result<u64> {
|
||||
let consumed_ptr = unsafe { &mut *self.interrupts.fuel_consumed.get() };
|
||||
let consumed_ptr = unsafe { &mut *self.runtime_limits.fuel_consumed.get() };
|
||||
match i64::try_from(fuel)
|
||||
.ok()
|
||||
.and_then(|fuel| consumed_ptr.checked_add(fuel))
|
||||
@@ -1394,8 +1288,8 @@ impl StoreOpaque {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn vminterrupts(&self) -> *mut VMInterrupts {
|
||||
&*self.interrupts as *const VMInterrupts as *mut VMInterrupts
|
||||
pub fn vmruntime_limits(&self) -> *mut VMRuntimeLimits {
|
||||
&self.runtime_limits as *const VMRuntimeLimits as *mut VMRuntimeLimits
|
||||
}
|
||||
|
||||
pub unsafe fn insert_vmexternref_without_gc(&mut self, r: VMExternRef) {
|
||||
@@ -1731,8 +1625,8 @@ impl AsyncCx {
|
||||
}
|
||||
|
||||
unsafe impl<T> wasmtime_runtime::Store for StoreInner<T> {
|
||||
fn vminterrupts(&self) -> *mut VMInterrupts {
|
||||
<StoreOpaque>::vminterrupts(self)
|
||||
fn vmruntime_limits(&self) -> *mut VMRuntimeLimits {
|
||||
<StoreOpaque>::vmruntime_limits(self)
|
||||
}
|
||||
|
||||
fn epoch_ptr(&self) -> *const AtomicU64 {
|
||||
@@ -1912,13 +1806,13 @@ impl<T> StoreInner<T> {
|
||||
// Set a new deadline based on the "epoch deadline delta".
|
||||
//
|
||||
// Safety: this is safe because the epoch deadline in the
|
||||
// `VMInterrupts` is accessed only here and by Wasm guest code
|
||||
// `VMRuntimeLimits` is accessed only here and by Wasm guest code
|
||||
// running in this store, and we have a `&mut self` here.
|
||||
//
|
||||
// Also, note that when this update is performed while Wasm is
|
||||
// on the stack, the Wasm will reload the new value once we
|
||||
// return into it.
|
||||
let epoch_deadline = unsafe { (*self.vminterrupts()).epoch_deadline.get_mut() };
|
||||
let epoch_deadline = unsafe { (*self.vmruntime_limits()).epoch_deadline.get_mut() };
|
||||
*epoch_deadline = self.engine().current_epoch() + delta;
|
||||
}
|
||||
|
||||
@@ -1926,7 +1820,7 @@ impl<T> StoreInner<T> {
|
||||
// Safety: this is safe because, as above, it is only invoked
|
||||
// from within `new_epoch` which is called from guest Wasm
|
||||
// code, which will have an exclusive borrow on the Store.
|
||||
let epoch_deadline = unsafe { (*self.vminterrupts()).epoch_deadline.get_mut() };
|
||||
let epoch_deadline = unsafe { (*self.vmruntime_limits()).epoch_deadline.get_mut() };
|
||||
*epoch_deadline
|
||||
}
|
||||
}
|
||||
@@ -1983,28 +1877,6 @@ impl wasmtime_runtime::ModuleInfoLookup for ModuleRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// A threadsafe handle used to interrupt instances executing within a
|
||||
/// particular `Store`.
|
||||
///
|
||||
/// This structure is created by the [`Store::interrupt_handle`] method.
|
||||
#[derive(Debug)]
|
||||
pub struct InterruptHandle {
|
||||
interrupts: Arc<VMInterrupts>,
|
||||
}
|
||||
|
||||
impl InterruptHandle {
|
||||
/// Flags that execution within this handle's original [`Store`] should be
|
||||
/// interrupted.
|
||||
///
|
||||
/// This will not immediately interrupt execution of wasm modules, but
|
||||
/// rather it will interrupt wasm execution of loop headers and wasm
|
||||
/// execution of function entries. For more information see
|
||||
/// [`Store::interrupt_handle`].
|
||||
pub fn interrupt(&self) {
|
||||
self.interrupts.interrupt()
|
||||
}
|
||||
}
|
||||
|
||||
struct Reset<T: Copy>(*mut T, T);
|
||||
|
||||
impl<T: Copy> Drop for Reset<T> {
|
||||
|
||||
@@ -171,19 +171,12 @@ impl Trap {
|
||||
pub(crate) fn from_runtime(runtime_trap: wasmtime_runtime::Trap) -> Self {
|
||||
match runtime_trap {
|
||||
wasmtime_runtime::Trap::User(error) => Trap::from(error),
|
||||
wasmtime_runtime::Trap::Jit {
|
||||
pc,
|
||||
backtrace,
|
||||
maybe_interrupted,
|
||||
} => {
|
||||
let mut code = GlobalModuleRegistry::with(|modules| {
|
||||
wasmtime_runtime::Trap::Jit { pc, backtrace } => {
|
||||
let code = GlobalModuleRegistry::with(|modules| {
|
||||
modules
|
||||
.lookup_trap_code(pc)
|
||||
.unwrap_or(EnvTrapCode::StackOverflow)
|
||||
});
|
||||
if maybe_interrupted && code == EnvTrapCode::StackOverflow {
|
||||
code = EnvTrapCode::Interrupt;
|
||||
}
|
||||
Trap::new_wasm(Some(pc), code, backtrace)
|
||||
}
|
||||
wasmtime_runtime::Trap::Wasm {
|
||||
|
||||
Reference in New Issue
Block a user