Return from the jsapi calls when we have a module that satisfies the
state required by the spec, and let jit compilation (if it's happening)
continue in the background.
This also means we no longer do the full compilation pipeline for
validate().
Switches the compiled function table from HashMap<u32, ...> to a Vector
indexed by function index. The keys are densely packed up to
functions.size() so the hash probe wasn't buying us anything.
Also drops the bounds check from MemoryInstance::unsafe_get to match
FunctionInstance::unsafe_get; the caller has already proved the address
is in range by the time we get here.
Introduce MemoryBuffer, a memory backing store that uses mmap to reserve
the full wasm32 address space (4 GiB + guard pages) upfront, growing
without a copy and falling back to a ByteBuffer when mmap fails.
Also let Frame know how to handle non-owned locals (to e.g. allow
allocating them on the native stack.)
Instead of doing a naive O(n^2) liveness detection loop, use a bitmap
for values allocated to registers.
This cuts down validating time from 20% to 1.4% of runtime on the same
game as last commit.
This, along with moving the sources and destination out of the config
object, makes it so we don't have to double-deref to get to them on each
instruction, leading to a ~15% perf improvement on dispatch.
Namely, find an upper bound at validation time so we can allocate the
space when entering the frame.
Also drop labels at once instead of popping them off one at a time now
that we're using a Vector.
This still passes the values on the stack, but registers are now allowed
to cross a call boundary.
This is a very significant (>50%) improvement on the small call
microbenchmarks on my machine.
This commit adds a register allocator, with 8 available "register"
slots.
In testing with various random blobs, this moves anywhere from 30% to
74% of value accesses into predefined slots, and is about a ~20% perf
increase end-to-end.
To actually make this usable, a few structural changes were also made:
- we no longer do one instruction per interpret call
- trapping is an (unlikely) exit condition
- the label and frame stacks are replaced with linked lists with a huge
node cache size, as we only need to touch the last element and
push/pop is very frequent.
The average wasm function rarely goes over these bounds for the labels
(32 nested control structures), and 8 frames is just enough to clear
most initialization code/start section without allocating anything.