Add “try-load” and “try-store” instructions
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.
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.
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.
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.tryloadfunctions as atype.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), theinstructionsare 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.trystorefunctions as atype.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), theinstructionsare executed instead. The size of the stack after theinstructionsexecute must be the same as the size of the stack before theinstructionsexecute (i.e. theinstructionsreturn void).
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).