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

Improving wasm loading logic #1996

Merged
merged 2 commits into from
Feb 11, 2020
Merged

Conversation

Pauan
Copy link
Contributor

@Pauan Pauan commented Feb 10, 2020

This improves the loading logic for the web and no-modules targets in the following ways:

  • It's now possible to pass a Promise<WebAssembly.Module>, Promise<ArrayBuffer>, or Promise<Response> to the wasm_bindgen function.

    That also means that it's now possible to do wasm_bindgen(fetch("foo")) which previously wasn't possible.

    This makes it easier to use wasm-bindgen when serving the .wasm from cache (e.g. in service workers).

  • Some code duplication has been removed.

  • The logic is now much clearer and easier to understand.

The old logic
function init(module) {
    if (typeof module === 'undefined') {
        let src;
        if (self.document === undefined) {
            src = self.location.href;
        } else {
            src = self.document.currentScript.src;
        }
        module = src.replace(/\.js$/, '_bg.wasm');
    }
    let result;
    const imports = {};
    imports.wbg = {};
    imports.wbg.__wbg_alert_f5f3a7ed098bf3ca = function(arg0, arg1) {
        alert(getStringFromWasm0(arg0, arg1));
    };

    if ((typeof URL === 'function' && module instanceof URL) || typeof module === 'string' || (typeof Request === 'function' && module instanceof Request)) {

        const response = fetch(module);
        if (typeof WebAssembly.instantiateStreaming === 'function') {
            result = WebAssembly.instantiateStreaming(response, imports)
            .catch(e => {
                return response
                .then(r => {
                    if (r.headers.get('Content-Type') != 'application/wasm') {
                        console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
                        return r.arrayBuffer();
                    } else {
                        throw e;
                    }
                })
                .then(bytes => WebAssembly.instantiate(bytes, imports));
            });
        } else {
            result = response
            .then(r => r.arrayBuffer())
            .then(bytes => WebAssembly.instantiate(bytes, imports));
        }
    } else {

        result = WebAssembly.instantiate(module, imports)
        .then(result => {
            if (result instanceof WebAssembly.Instance) {
                return { instance: result, module };
            } else {
                return result;
            }
        });
    }
    return result.then(({instance, module}) => {
        wasm = instance.exports;
        init.__wbindgen_wasm_module = module;

        return wasm;
    });
}
The new logic
async function load(module, imports) {
    if (typeof Response === 'function' && module instanceof Response) {

        if (typeof WebAssembly.instantiateStreaming === 'function') {
            try {
                return await WebAssembly.instantiateStreaming(module, imports);

            } catch (e) {
                if (module.headers.get('Content-Type') != 'application/wasm') {
                    console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);

                } else {
                    throw e;
                }
            }
        }

        const bytes = await module.arrayBuffer();
        return await WebAssembly.instantiate(bytes, imports);

    } else {

        const instance = await WebAssembly.instantiate(module, imports);

        if (instance instanceof WebAssembly.Instance) {
            return { instance, module };

        } else {
            return instance;
        }
    }
}

async function init(input) {
    if (typeof input === 'undefined') {
        let src;
        if (self.document === undefined) {
            src = self.location.href;
        } else {
            src = self.document.currentScript.src;
        }
        input = src.replace(/\.js$/, '_bg.wasm');
    }
    const imports = {};
    imports.wbg = {};
    imports.wbg.__wbg_alert_f5f3a7ed098bf3ca = function(arg0, arg1) {
        alert(getStringFromWasm0(arg0, arg1));
    };

    if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
        input = fetch(input);
    }

    const { instance, module } = await load(await input, imports);

    wasm = instance.exports;
    init.__wbindgen_wasm_module = module;

    return wasm;
}

@alexcrichton
Copy link
Contributor

Nice!

Mind adding some documentation in the book as well indicating the new types that can be passed into the initialization function?

@Pauan
Copy link
Contributor Author

Pauan commented Feb 11, 2020

@alexcrichton Done.

@alexcrichton alexcrichton merged commit ca742a8 into rustwasm:master Feb 11, 2020
@alexcrichton
Copy link
Contributor

👎

@TethysSvensson
Copy link

Am I missing something, or does this not negate the benefits of instantiateStreaming? As far as I can tell, this awaits for the fetch to be finished before passing it to instantiateStreaming.

@Pauan
Copy link
Contributor Author

Pauan commented Jan 26, 2021

@TethysSvensson No, because fetch only returns a Response (which just contains the headers, not the body). So the body is still streamed as it should be.

And even if you pass in a fetch Promise directly, the spec will simply wait for it to return a Response before continuing, so it's exactly the same as using await:

https://webassembly.org/docs/web/#process-a-potential-webassembly-response

  1. Upon fulfillment of sourceAsPromise with value unwrappedSource:

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

Successfully merging this pull request may close these issues.

3 participants