During the work on slacked/async fuel metering, see #4109, we converged on an idea of checking the fuel asynchronously. TL;DR: Slacked fuel metering allows to achieve overhead close to zero in many cases while preserving determinism if the user can accept some concessions.
It works roughly like this. We reserve a register for the fuel counter. The fuel counter is incremented at each BB. The thread executing wasm will be periodically interrupted. The interrupt handler would check if the thread was interrupted in wasm based on the value of the PC register. If it was, then the handler can read the fuel register. Then, the handler can initiate a trap. Semantically, that means a wasm module can be interrupted at any wasm PC.
For macOS and Windows we would stop the thread, inspect its thread state, and if there is out-of-gas then modify the thread state, to land on a handler that would initiate unwinding.
Which made me realize: this looks equivalent to what epoch-interruption aims to achieve. Specifically, it provides an efficient way to interrupt wasm code non-deterministically (but 2-3x faster) based on some periodic signal by the embedder.
Unlike epoch-interruption, async traps do not require inline synchronous checks on each epilogue and back edges. Instead, if we don't care about pretty stack traces during such async traps, then we don't need any modifications of compiled code. Or we can follow suggestion by @Alex Crichton here. In any case, this has ≈0 overhead.
Also, I think async traps are a simpler and less-opinionated. Instead of having a single per-Engine
counter that interrupts code in all stores that reached the deadline, per thread interruption is possible.
Probably I am missing something here and it might have been perhaps discussed. If so, why did the mechanism that involves synchronous checks won compared to async traps? Similar to Alex's logic in the linked issue?
cc @Chris Fallin
I wondered the same thing myself as the async traps work unfolded. To me though the answer is not clear. Integration of epochs is quite simple where a thread simply increments a single counter. Integration of async traps seems more complicated where handles to threads-running-wasm need to be maintained and the periodic thread iterates this list. This implies at least some degree of synchronization depending on system setup.
Cost-wise given the benefits of async traps it may be worth investing in this design given how low-overhead they are!
For a little more context, at least in our embedding there was serious concern about any async thread interruption at all; partly due to historical experience and partly due to the real inherent subtleties one has to worry about (signal-safety, etc). So I guess epoch interruption fills a niche where we want pretty-fast interruptible code but no dependence on OS-specific thread interruption mechanisms
others may very well have different tradeoffs or complexity profiles though and be willing to build that out; it'd be interesting to see more options supported by wasmtime in general
Last updated: Dec 23 2024 at 13:07 UTC