memory-control icon indicating copy to clipboard operation
memory-control copied to clipboard

Add “try-load” and “try-store” instructions

Open pcwalton opened this issue 1 month ago • 4 comments

JITting emulators like http://copy.sh/v86 incur significant overhead from having to check page tables on every memory access. Traditionally, emulators implement this by trapping SIGSEGV. The signal handler isn’t necessary, however. One could imagine a “try load” and “try store” pair of instructions that take a block of code to execute if the load would otherwise trap. A simple implementation of WebAssembly could emit a compare against the page protections and jump. A more optimizing implementation could catch the SIGSEGV and alter the wasm program counter.

pcwalton avatar Nov 07 '25 06:11 pcwalton

A simple implementation of WebAssembly could emit a compare against the page protections and jump.

What page protections are you referring to? I could imagine this being related to either the static memory protection or virtual mode sub-proposals, but I'm not exactly sure what you have in mind. Currently today it seems like you could simply do a bounds check against memory.length.

bvisness avatar Nov 07 '25 16:11 bvisness

This would be an enhancement to virtual mode. Static memory protection isn't usable for emulators of systems with MMUs as the guest OS can configure memory however it wants.

I believe that, when combined with #22, this would be sufficient to efficiently implement JITs of systems with MMUs on top of wasm. See https://github.com/copy/v86/blob/master/docs/how-it-works.md for how wasm-based JITs deal with memory loads and stores today: you can see that they essentially implement "try-load" and "try-store" manually by checking against per-page "valid" bits. This proposal would essentially lift those "try-load" and "try-store" patterns into wasm instructions, which a wasm VM could then optimize into the most efficient patterns if the host system allows it.

pcwalton avatar Nov 07 '25 17:11 pcwalton

I imagine it would look something like this at the WAT level:

(type.tryload ... instructions ...)
(type.trystore ... instructions ...)

where type is a type that can be used in a load or store and ... instructions ... is a list of instructions.

The semantics are:

  • type.tryload functions as a type.load, except that if and only if the instruction would trap due to a memory fault (out of bounds or reading from a no-access location), the instructions are executed instead. The instructions are expected to push a value of the appropriate type to the stack (i.e. they end in an instruction of the same type as the memory load).
  • type.trystore functions as a type.store, except that if and only if the instruction would trap due to a memory fault (out of bounds or writing to a no-access or read-only location), the instructions are executed instead. The size of the stack after the instructions execute must be the same as the size of the stack before the instructions execute (i.e. the instructions return void).

pcwalton avatar Nov 08 '25 02:11 pcwalton

I'm told that this would indeed be sufficient for v86 when combined with the virtual memory proposal, as long as the instruction blocks can be exited just as an if block can (i.e. there can be jumps out of that block).

pcwalton avatar Nov 08 '25 21:11 pcwalton