Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Size limits with synchronous WebAssembly functions #1499

Open
Pauan opened this issue Jan 27, 2020 · 10 comments
Open

Size limits with synchronous WebAssembly functions #1499

Pauan opened this issue Jan 27, 2020 · 10 comments

Comments

@Pauan
Copy link

Pauan commented Jan 27, 2020

Right now the synchronous new WebAssembly.Module(foo) / new WebAssembly.Instance(foo, bar) functions have a 4 KB size limit on the main thread, because they block the event loop.

However, you can use them in regular workers without the 4 KB limit.

Should they be allowed in service workers with or without the 4 KB limit?

@jakearchibald
Copy link
Contributor

If > 4k is bad for the main thread, it's probably bad for service worker startup & a fetch event too. I'm leaning towards adding the same restrictions on these APIs.

@moshevds
Copy link

Hi @jakearchibald,

Would you mind looking though the discussion that @Pauan and I had, in the issue that he linked?
It is a bit long, so I'll summarize why I think this removes an important feature with no alternative available.

If it is no longer possible to run WebAssembly before registering the listeners, this effectively requires writing business logic in javascript that some people (me among them) currently have in WebAssembly.
In particular, I also think requiring this interplay between js&wasm&sw-api will result in decidedly worse application code.

What kind of solution do you think could be available for that situation?

@Pauan
Copy link
Author

Pauan commented Jan 27, 2020

The summary is that @moshevds wants to write their service worker entirely in Wasm, without any JS at all. Right now that's not possible, because:

  1. esm-integration requires top-level await which has been banned in service workers.

  2. wasm-bindgen can't generate code which uses top-level await to load the .wasm file... because top-level await has been banned.

  3. wasm-bindgen can't generate code which loads the .wasm file synchronously... because sync XHR has been banned.

  4. wasm-bindgen could convert the .wasm file into base64 and embed it inside the .js file and use the synchronous WebAssembly APIs to load it... except that won't work if there is a 4 KB size limit.

So right now it's not really possible to create a service worker which is pure Wasm.

Instead you have to create a .js file which asynchronously loads and caches the .wasm file, and defines the event listeners, like this:

// This fetches a file and automatically caches it, so that way it will work offline.
async function load(url) {
    const cache = await caches.open("offline");

    try {
        const response = await fetch(url);

        if (response.ok) {
            await cache.put(url, response.clone());
            return response;

        } else {
            throw new Error("Invalid status code: " + response.status);
        }

    } catch (e) {
        const response = await cache.match(url);

        if (response) {
            return response;

        } else {
            throw e;
        }
    }
}

// This loads the glue code which is auto-generated by wasm-bindgen.
importScripts("./path/to/foo.js");

// This loads the .wasm file in the background and returns a Promise.
const wasm = wasm_bindgen(load("./path/to/foo_bg.wasm"));

// The `waitUntil` ensures the .wasm file is loaded, so that way we
// can call functions defined within the .wasm file.
self.addEventListener("install", (event) => {
    event.waitUntil(wasm);
});

self.addEventListener("fetch", (event) => {
    // This calls the `fetch` function which is defined within the .wasm file.
    event.waitUntil(wasm.then(() => wasm_bindgen.fetch(event)));
});

wasm-bindgen cannot generate the above code, because wasm-bindgen doesn't know what events the user wants, and wasm-bindgen also doesn't know what caching strategy the user wants, since wasm-bindgen is just a compiler.

So, each user of wasm-bindgen must write the above code, and it's pretty easy to get wrong (especially with regard to caching).

From wasm-bindgen's perspective, the ideal solution is for the browser to have built-in support for .wasm (such as with esm-integration). That would remove the need for all of the above code.

Removing the 4 KB limit also removes the need for the above code, but it isn't a great solution, since it still requires base64-encoding the .wasm file and embedding it into the .js file. But it does at least enable using .wasm today.

@moshevds
Copy link

Thank you @Pauan for that summary and description of my goal.

I do want to add that embedding base64 is what I am already doing, in anticipation of built-in support.
My .wasm is 22k, and the 4k limit is not enforced by the browsers that I see.

@Pauan
Copy link
Author

Pauan commented Jan 28, 2020

P.S. The load function I defined above would be really useful to add to Cache. That would make it a lot easier to load cached assets (such as .wasm files).

But that's a separate orthogonal concern, and that still doesn't remove the need to define a separate .js file.

@Pauan
Copy link
Author

Pauan commented Jan 28, 2020

Also, I want to make it really clear what our situation is:

  1. wasm-bindgen is a compiler which allows you to compile Rust code into .wasm and run that .wasm in the browser.

  2. In order to accomplish this, wasm-bindgen generates a .wasm file and also a .js file which contains glue code.

  3. The user then simply has to load that .js file and wasm-bindgen automatically handles everything else (including loading + running the .wasm file). This means it's incredibly easy for our users.

Right now, this works great... except in service workers. Service workers require you to synchronously define the event listeners, but .wasm files are always loaded asynchronously (with instantiateStreaming).

Therefore, you cannot define the event listeners in your Rust code. Instead, the user of wasm-bindgen must create a separate .js file which defines the event listeners and loads + caches the .wasm file, as I showed above.

Even though wasm-bindgen is already generating .js glue code, we cannot put that code into the .js glue code, because we don't know what events + caching the user wants. Different users will want different events and caching.

So every user of wasm-bindgen must create their own .js file which does all of this. And there isn't really anything wasm-bindgen can do to make that easier on our users.

So from wasm-bindgen's perspective, there are three possible solutions to this:

  1. The browser can have built-in support for importing + caching .wasm files in service workers (ala esm-integration). This is ideal for us.

  2. wasm-bindgen can generate some glue code which runs the Rust code synchronously, thus allowing for the Rust code to define the event listeners.

    Right now that means using the sync new WebAssembly.Module / new WebAssembly.Instance functions. But that doesn't work if there is a 4 KB limit for those functions (hence this issue).

  3. The load function I defined above (or similar) would be added to Cache.

    This still requires the user to create a separate .js file, but at least that .js file would be really small (since it would only be defining the event listeners and nothing else).

    Right now it's too tricky to load the .wasm file in a way that works offline.

Option 1 is ideal for us, option 2 isn't as good but it's okay, and option 3 is the worst but is acceptable.

@annevk
Copy link
Member

annevk commented Jan 29, 2020

I think we should continue to aim for 1, first-class support for Wasm service workers and enabling Wasm to use IDL-defined APIs.

@Pauan
Copy link
Author

Pauan commented Jan 29, 2020

@annevk That's great to hear! But esm-integration is a ways off, so in the meantime what's the plan? As far as I know, the only two solutions (which work today) are option 2 and 3 (as detailed above).

@annevk
Copy link
Member

annevk commented Jan 30, 2020

I don't know.

@moshevds
Copy link

Hi all.

I remain worried about this possible restriction, because I don't see a sensible alternative. Is it safe to assume this limit will not be introduced before first-class wasm support is available in browsers?

Clarity about this would allow me to work with the wasm-bindgen project to allow it to act as a shim for the first-class wasm support being aimed at. If not here, what is the best place to find this clarity?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants