From 68c75e5fbbde469ccf46564e576589c6e3152449 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:29:16 +0000 Subject: [PATCH] Use asynchronous wasm instantiation methods where possible (#174) This fixes a problem (introduced in #167) on Chrome, which would complain about the module being too big. It also significantly simplifies the code on the browser entry points, because instantiateStreaming is widely supported. --- CHANGELOG.md | 1 + index.js | 24 +++++---------------- index.mjs | 24 +++++---------------- node.js | 59 ++++++++++++++++++++++++++++++---------------------- node.mjs | 59 ++++++++++++++++++++++++++++++---------------------- 5 files changed, 79 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fafb88de1..6e0a49e7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - The WebAssembly module is now loaded using `fetch` on Web platforms, reducing the bundle size significantly, as well as the time it takes to compile it. ([#167](https://github.com/matrix-org/matrix-rust-sdk-crypto-wasm/pull/167)), + ([#174](https://github.com/matrix-org/matrix-rust-sdk-crypto-wasm/pull/174)), ([#175](https://github.com/matrix-org/matrix-rust-sdk-crypto-wasm/pull/175)) **BREAKING CHANGES** diff --git a/index.js b/index.js index d22c3ec7c..9b27f4e02 100644 --- a/index.js +++ b/index.js @@ -45,32 +45,18 @@ bindings.__wbg_set_wasm( let modPromise = null; /** - * Loads the WASM module asynchronously + * Loads and instantiates the WASM module asynchronously * * @returns {Promise} */ -async function loadModule() { - let mod; - if (typeof WebAssembly.compileStreaming === "function") { - mod = await WebAssembly.compileStreaming(fetch(moduleUrl)); - } else { - // Fallback to fetch and compile - const response = await fetch(moduleUrl); - if (!response.ok) { - throw new Error(`Failed to fetch wasm module: ${moduleUrl}`); - } - const bytes = await response.arrayBuffer(); - mod = await WebAssembly.compile(bytes); - } - - /** @type {{exports: typeof import("./pkg/matrix_sdk_crypto_wasm_bg.wasm.d.ts")}} */ - // @ts-expect-error: Typescript doesn't know what the instance exports exactly - const instance = new WebAssembly.Instance(mod, { +async function loadModuleAsync() { + const { instance } = await WebAssembly.instantiateStreaming(fetch(moduleUrl), { // @ts-expect-error: The bindings don't exactly match the 'ExportValue' type "./matrix_sdk_crypto_wasm_bg.js": bindings, }); bindings.__wbg_set_wasm(instance.exports); + // @ts-expect-error: Typescript doesn't know what the module exports are instance.exports.__wbindgen_start(); } @@ -82,7 +68,7 @@ async function loadModule() { * @returns {Promise} */ async function initAsync() { - if (!modPromise) modPromise = loadModule(); + if (!modPromise) modPromise = loadModuleAsync(); await modPromise; } diff --git a/index.mjs b/index.mjs index f55cc01e7..bf8df6702 100644 --- a/index.mjs +++ b/index.mjs @@ -45,32 +45,18 @@ bindings.__wbg_set_wasm( let modPromise = null; /** - * Loads the WASM module asynchronously + * Loads and instantiates the WASM module asynchronously * * @returns {Promise} */ -async function loadModule() { - let mod; - if (typeof WebAssembly.compileStreaming === "function") { - mod = await WebAssembly.compileStreaming(fetch(moduleUrl)); - } else { - // Fallback to fetch and compile - const response = await fetch(moduleUrl); - if (!response.ok) { - throw new Error(`Failed to fetch wasm module: ${moduleUrl}`); - } - const bytes = await response.arrayBuffer(); - mod = await WebAssembly.compile(bytes); - } - - /** @type {{exports: typeof import("./pkg/matrix_sdk_crypto_wasm_bg.wasm.d.ts")}} */ - // @ts-expect-error: Typescript doesn't know what the instance exports exactly - const instance = new WebAssembly.Instance(mod, { +async function loadModuleAsync() { + const { instance } = await WebAssembly.instantiateStreaming(fetch(moduleUrl), { // @ts-expect-error: The bindings don't exactly match the 'ExportValue' type "./matrix_sdk_crypto_wasm_bg.js": bindings, }); bindings.__wbg_set_wasm(instance.exports); + // @ts-expect-error: Typescript doesn't know what the module exports are instance.exports.__wbindgen_start(); } @@ -82,7 +68,7 @@ async function loadModule() { * @returns {Promise} */ export async function initAsync() { - if (!modPromise) modPromise = loadModule(); + if (!modPromise) modPromise = loadModuleAsync(); await modPromise; } diff --git a/node.js b/node.js index 4c71fc4fe..190b4fd2e 100644 --- a/node.js +++ b/node.js @@ -34,9 +34,9 @@ bindings.__wbg_set_wasm( {}, { get(_target, prop) { - const mod = loadModuleSync(); - // @ts-expect-error: This results to an `any` type, which is fine - return initInstance(mod)[prop]; + const instance = loadModuleSync(); + // @ts-expect-error: This results in an `any` type, which is fine + return instance[prop]; }, }, ), @@ -55,48 +55,57 @@ let modPromise = null; let initialised = false; /** - * Loads the WASM module synchronously + * Loads and instantiates the WASM module synchronously * - * It will throw if there is an attempt to laod the module asynchronously running + * It will throw if there is an attempt to load the module asynchronously running * - * @returns {WebAssembly.Module} + * @returns {typeof import("./pkg/matrix_sdk_crypto_wasm_bg.wasm.d")} */ function loadModuleSync() { - if (modPromise) throw new Error("The WASM module is being loadded asynchronously but hasn't finished"); + if (modPromise) throw new Error("The WASM module is being loaded asynchronously but hasn't finished"); const bytes = readFileSync(filename); - return new WebAssembly.Module(bytes); + const mod = new WebAssembly.Module(bytes); + + const instance = new WebAssembly.Instance(mod, { + // @ts-expect-error: The bindings don't exactly match the 'ExportValue' type + "./matrix_sdk_crypto_wasm_bg.js": bindings, + }); + + initInstance(instance); + + // @ts-expect-error: Typescript doesn't know what the instance exports exactly + return instance.exports; } /** - * Loads the WASM module asynchronously + * Loads and instantiates the WASM module asynchronously * - * @returns {Promise} + * @returns {Promise} */ -async function loadModule() { +async function loadModuleAsync() { const bytes = await readFile(filename); - return await WebAssembly.compile(bytes); + const { instance } = await WebAssembly.instantiate(bytes, { + // @ts-expect-error: The bindings don't exactly match the 'ExportValue' type + "./matrix_sdk_crypto_wasm_bg.js": bindings, + }); + + initInstance(instance); + + // @ts-expect-error: Typescript doesn't know what the instance exports exactly + return instance.exports; } /** * Initializes the WASM module and returns the exports from the WASM module. * - * @param {WebAssembly.Module} mod - * @returns {typeof import("./pkg/matrix_sdk_crypto_wasm_bg.wasm.d")} + * @param {WebAssembly.Instance} instance */ -function initInstance(mod) { +function initInstance(instance) { if (initialised) throw new Error("initInstance called twice"); - - /** @type {{exports: typeof import("./pkg/matrix_sdk_crypto_wasm_bg.wasm.d.ts")}} */ - // @ts-expect-error: Typescript doesn't know what the instance exports exactly - const instance = new WebAssembly.Instance(mod, { - // @ts-expect-error: The bindings don't exactly match the 'ExportValue' type - "./matrix_sdk_crypto_wasm_bg.js": bindings, - }); - bindings.__wbg_set_wasm(instance.exports); + // @ts-expect-error: Typescript doesn't know what the instance exports exactly instance.exports.__wbindgen_start(); initialised = true; - return instance.exports; } /** @@ -108,7 +117,7 @@ function initInstance(mod) { */ async function initAsync() { if (initialised) return; - if (!modPromise) modPromise = loadModule().then(initInstance); + if (!modPromise) modPromise = loadModuleAsync(); await modPromise; } diff --git a/node.mjs b/node.mjs index 96d56136b..65c9cca4d 100644 --- a/node.mjs +++ b/node.mjs @@ -34,9 +34,9 @@ bindings.__wbg_set_wasm( {}, { get(_target, prop) { - const mod = loadModuleSync(); - // @ts-expect-error: This results to an `any` type, which is fine - return initInstance(mod)[prop]; + const instance = loadModuleSync(); + // @ts-expect-error: This results in an `any` type, which is fine + return instance[prop]; }, }, ), @@ -55,48 +55,57 @@ let modPromise = null; let initialised = false; /** - * Loads the WASM module synchronously + * Loads and instantiates the WASM module synchronously * - * It will throw if there is an attempt to laod the module asynchronously running + * It will throw if there is an attempt to load the module asynchronously running * - * @returns {WebAssembly.Module} + * @returns {typeof import("./pkg/matrix_sdk_crypto_wasm_bg.wasm.d.ts")} */ function loadModuleSync() { - if (modPromise) throw new Error("The WASM module is being loadded asynchronously but hasn't finished"); + if (modPromise) throw new Error("The WASM module is being loaded asynchronously but hasn't finished"); const bytes = readFileSync(filename); - return new WebAssembly.Module(bytes); + const mod = new WebAssembly.Module(bytes); + + const instance = new WebAssembly.Instance(mod, { + // @ts-expect-error: The bindings don't exactly match the 'ExportValue' type + "./matrix_sdk_crypto_wasm_bg.js": bindings, + }); + + initInstance(instance); + + // @ts-expect-error: Typescript doesn't know what the module exports are + return instance.exports; } /** - * Loads the WASM module asynchronously + * Loads and instantiates the WASM module asynchronously * - * @returns {Promise} + * @returns {Promise} */ -async function loadModule() { +async function loadModuleAsync() { const bytes = await readFile(filename); - return await WebAssembly.compile(bytes); + const { instance } = await WebAssembly.instantiate(bytes, { + // @ts-expect-error: The bindings don't exactly match the 'ExportValue' type + "./matrix_sdk_crypto_wasm_bg.js": bindings, + }); + + initInstance(instance); + + // @ts-expect-error: Typescript doesn't know what the module exports are + return instance.exports; } /** * Initializes the WASM module and returns the exports from the WASM module. * - * @param {WebAssembly.Module} mod - * @returns {typeof import("./pkg/matrix_sdk_crypto_wasm_bg.wasm.d.ts")} + * @param {WebAssembly.Instance} instance */ -function initInstance(mod) { +function initInstance(instance) { if (initialised) throw new Error("initInstance called twice"); - - /** @type {{exports: typeof import("./pkg/matrix_sdk_crypto_wasm_bg.wasm.d.ts")}} */ - // @ts-expect-error: Typescript doesn't know what the instance exports exactly - const instance = new WebAssembly.Instance(mod, { - // @ts-expect-error: The bindings don't exactly match the 'ExportValue' type - "./matrix_sdk_crypto_wasm_bg.js": bindings, - }); - bindings.__wbg_set_wasm(instance.exports); + // @ts-expect-error: Typescript doesn't know what the module exports are instance.exports.__wbindgen_start(); initialised = true; - return instance.exports; } /** @@ -108,7 +117,7 @@ function initInstance(mod) { */ export async function initAsync() { if (initialised) return; - if (!modPromise) modPromise = loadModule().then(initInstance); + if (!modPromise) modPromise = loadModuleAsync(); await modPromise; }