Skip to content

Async compile/instantiate without event loop turn #1462

Open
@kg

Description

@kg

Because the synchronous Compile API has a 4kb limit in real-world implementations and the async/streaming APIs use a Promise, there's currently no way to implement a wasm equivalent of production JITs (that are able to synchronously or asynchronously JIT code while it is executing) - any jitted code cannot run until one or all of your threads have returned to the event loop for the compile promises to complete, and it may require two event loop turns because you first have to wait for compile and then wait for instantiate.

With threading support as it is, it seems like there is no reason why the streaming APIs could not somehow provide the compiled/instantiated module(s) to running code as soon as they are ready, though it would require some mechanism to ensure that any existing code expecting the promise completion to be delayed wouldn't break. This would allow implementing modern async JITs in a wasm environment even if applications spend multiple seconds with the current thread blocked, as is not uncommon in real software during startup, asset loading, etc.

At present the JIT i'm implementing provides big performance gains for software but the 4KB limit means it's not realistic to JIT larger methods or modules containing multiple methods, which means I have to stress various parts of the browser runtime by potentially having hundreds or thousands of tiny modules.

Having to wait for an event loop turn also introduces a potential deadlock or deadlock-adjacent problem where you need to wait for all of your threads to return to the event loop, so that you can ensure the new function pointer(s) you're introducing actually point to a function in every thread. Otherwise, thread 3 might try to call the new jitted function pointer (because it's visible in the shared heap) before the event loop has pumped to actually instantiate the module and allow registering the function in that thread's function pointer table.

My proposed way to signal this to the streaming APIs would be:

  • Set a header and/or custom property on the Response object you pass in to indicate that you want immediate (or as-soon-as-possible) completion of the Promise or want the module surfaced in some other way.
  • Set a custom property on the compiled result to indicate that you are okay with immediate instantiation, or flow the flag through from the original Response.

Another simpler option would be a removal of the 4kb limit on Compile as long as there is some way to realistically utilize it here. I would be OK with being forced to Compile on a worker, but then I'm stuck in event loop hell again because there is (AFAIK) no way to transfer the resulting module/instance to other threads synchronously.

Of course the 4kb limit would also be less of a nuisance if wasm bytecode wasn't so verbose, but that seems hard to fix in a straightforward way, it would require a bunch of new opcodes like 'dup' or some other mechanism to allow making smaller modules that don't secretly take an eternity to compile.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions