Add epoch-based interruption for cooperative async timeslicing.

This PR introduces a new way of performing cooperative timeslicing that
is intended to replace the "fuel" mechanism. The tradeoff is that this
mechanism interrupts with less precision: not at deterministic points
where fuel runs out, but rather when the Engine enters a new epoch. The
generated code instrumentation is substantially faster, however, because
it does not need to do as much work as when tracking fuel; it only loads
the global "epoch counter" and does a compare-and-branch at backedges
and function prologues.

This change has been measured as ~twice as fast as fuel-based
timeslicing for some workloads, especially control-flow-intensive
workloads such as the SpiderMonkey JS interpreter on Wasm/WASI.

The intended interface is that the embedder of the `Engine` performs an
`engine.increment_epoch()` call periodically, e.g. once per millisecond.
An async invocation of a Wasm guest on a `Store` can specify a number of
epoch-ticks that are allowed before an async yield back to the
executor's event loop. (The initial amount and automatic "refills" are
configured on the `Store`, just as for fuel.) This call does only
signal-safe work (it increments an `AtomicU64`) so could be invoked from
a periodic signal, or from a thread that wakes up once per period.
This commit is contained in:
Chris Fallin
2022-01-18 17:23:09 -08:00
parent ae476fde60
commit 8a55b5c563
19 changed files with 1034 additions and 26 deletions

View File

@@ -631,6 +631,7 @@ impl VMBuiltinFunctionsArray {
ptrs[BuiltinFunctionIndex::memory_atomic_wait64().index() as usize] =
wasmtime_memory_atomic_wait64 as usize;
ptrs[BuiltinFunctionIndex::out_of_gas().index() as usize] = wasmtime_out_of_gas as usize;
ptrs[BuiltinFunctionIndex::new_epoch().index() as usize] = wasmtime_new_epoch as usize;
if cfg!(debug_assertions) {
for i in 0..ptrs.len() {
@@ -694,12 +695,18 @@ pub struct VMInterrupts {
/// turning positive a wasm trap will be generated. This field is only
/// modified if wasm is configured to consume fuel.
pub fuel_consumed: UnsafeCell<i64>,
/// Deadline epoch for interruption: if epoch-based interruption
/// is enabled and the global (per engine) epoch counter is
/// observed to reach or exceed this value, the guest code will
/// yield if running asynchronously.
pub epoch_deadline: UnsafeCell<u64>,
}
// The `VMInterrupts` type is a pod-type with no destructor, and we only access
// `stack_limit` from other threads, so add in these trait impls which are
// otherwise not available due to the `fuel_consumed` variable in
// `VMInterrupts`.
// The `VMInterrupts` type is a pod-type with no destructor, and we
// only access `stack_limit` from other threads, so add in these trait
// impls which are otherwise not available due to the `fuel_consumed`
// and `epoch_deadline` variables in `VMInterrupts`.
//
// Note that users of `fuel_consumed` understand that the unsafety encompasses
// ensuring that it's only mutated/accessed from one thread dynamically.
@@ -719,6 +726,7 @@ impl Default for VMInterrupts {
VMInterrupts {
stack_limit: AtomicUsize::new(usize::max_value()),
fuel_consumed: UnsafeCell::new(0),
epoch_deadline: UnsafeCell::new(0),
}
}
}