diff --git a/src/audio_worklet.js b/src/audio_worklet.js index e427e4ee3519a..e91e0b0d11335 100644 --- a/src/audio_worklet.js +++ b/src/audio_worklet.js @@ -153,6 +153,8 @@ class BootstrapMessages extends AudioWorkletProcessor { messagePort = this.port; /** @suppress {checkTypes} */ messagePort.onmessage = async (msg) => { + // Wait for the Wasm module to be instantiated. + await readyPromise; let d = msg.data; if (d['_wpn']) { // '_wpn' is short for 'Worklet Processor Node', using an identifier diff --git a/src/lib/libcore.js b/src/lib/libcore.js index f0cb1c87f5e47..3870cd676d86d 100644 --- a/src/lib/libcore.js +++ b/src/lib/libcore.js @@ -117,9 +117,9 @@ addToLibrary({ // if exit() was called explicitly, warn the user if the runtime isn't actually being shut down if (keepRuntimeAlive() && !implicit) { var msg = `program exited (with status: ${status}), but keepRuntimeAlive() is set (counter=${runtimeKeepaliveCounter}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`; -#if MODULARIZE +#if USE_READY_PROMISE readyPromiseReject?.(msg); -#endif // MODULARIZE +#endif err(msg); } #endif // ASSERTIONS diff --git a/src/postamble.js b/src/postamble.js index 88f1588ee14a2..e7e2801c41997 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -139,7 +139,7 @@ function run() { #if PTHREADS || WASM_WORKERS if ({{{ ENVIRONMENT_IS_WORKER_THREAD() }}}) { -#if MODULARIZE +#if USE_READY_PROMISE readyPromiseResolve?.(Module); #endif initRuntime(); @@ -179,7 +179,7 @@ function run() { preMain(); #endif -#if MODULARIZE +#if USE_READY_PROMISE readyPromiseResolve?.(Module); #endif #if expectToReceiveOnModule('onRuntimeInitialized') diff --git a/src/postamble_modularize.js b/src/postamble_modularize.js index 090f6f4323b4d..d6975e4e419f1 100644 --- a/src/postamble_modularize.js +++ b/src/postamble_modularize.js @@ -11,10 +11,7 @@ if (runtimeInitialized) { moduleRtn = Module; } else { // Set up the promise that indicates the Module is initialized - moduleRtn = new Promise((resolve, reject) => { - readyPromiseResolve = resolve; - readyPromiseReject = reject; - }); + moduleRtn = readyPromise = createReadyPromise(); } #else moduleRtn = {}; diff --git a/src/preamble.js b/src/preamble.js index 6b34051bdef31..c4a06beff0f9b 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -363,7 +363,7 @@ function abort(what) { /** @suppress {checkTypes} */ var e = new WebAssembly.RuntimeError(what); -#if MODULARIZE +#if USE_READY_PROMISE readyPromiseReject?.(e); #endif // Throw the error whether or not MODULARIZE is set because abort is used diff --git a/src/runtime_common.js b/src/runtime_common.js index 6ef8e7194fd46..fce5ae700d993 100644 --- a/src/runtime_common.js +++ b/src/runtime_common.js @@ -20,8 +20,21 @@ #include "runtime_asan.js" #endif -#if MODULARIZE && USE_READY_PROMISE -var readyPromiseResolve, readyPromiseReject; +#if USE_READY_PROMISE +var readyPromise, readyPromiseResolve, readyPromiseReject; +function createReadyPromise() { +#if ASSERTIONS + assert(!readyPromise, 'Ready promise already initialized.'); +#endif + return new Promise((resolve, reject) => { + readyPromiseResolve = resolve; + readyPromiseReject = reject; + }); +} +#if !MODULARIZE +// Modularize mode handles initializing the ready promise. +readyPromise = createReadyPromise(); +#endif #endif #if PTHREADS || WASM_WORKERS diff --git a/src/settings_internal.js b/src/settings_internal.js index 60d48bf0e7acf..3ea86605f6939 100644 --- a/src/settings_internal.js +++ b/src/settings_internal.js @@ -191,9 +191,10 @@ var WASM_EXCEPTIONS = false; // EXPORTED_FUNCTIONS then this gets set to 0. var EXPECT_MAIN = true; -// Return a "ready" Promise from the MODULARIZE factory function. -// We disable this under some circumstance if we know its not needed. -var USE_READY_PROMISE = true; +// Create a "ready" Promise that is resolved when the Wasm instance is ready. +// In MODULARIZE mode this Promise is returned from the factory function. +// We omit the Promise under some circumstance if we know it's not needed. +var USE_READY_PROMISE = false; // If true, building against Emscripten's wasm heap memory profiler. var MEMORYPROFILER = false; diff --git a/src/shell_minimal.js b/src/shell_minimal.js index da34243573128..906b616af8d96 100644 --- a/src/shell_minimal.js +++ b/src/shell_minimal.js @@ -116,9 +116,9 @@ var err = (...args) => console.error(...args); // compilation is ready. In that callback, call the function run() to start // the program. function ready() { -#if MODULARIZE && USE_READY_PROMISE +#if USE_READY_PROMISE readyPromiseResolve?.(Module); -#endif // MODULARIZE +#endif #if INVOKE_RUN && HAS_MAIN {{{ runIfMainThread("run();") }}} #elif ASSERTIONS diff --git a/test/code_size/audio_worklet_wasm.js b/test/code_size/audio_worklet_wasm.js index 80e2b3ab5538c..c12bbfaeae794 100644 --- a/test/code_size/audio_worklet_wasm.js +++ b/test/code_size/audio_worklet_wasm.js @@ -1,20 +1,24 @@ -var k = globalThis.Module || "undefined" != typeof Module ? Module : {}, p = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, r, v, L, M, G, I, C, X, J, E, D, Y, Z; +var k = globalThis.Module || "undefined" != typeof Module ? Module : {}, p = "em-ww" == globalThis.name, q = "undefined" !== typeof AudioWorkletGlobalScope, u, v, y, N, O, J, K, E, W, L, H, F, X, Z; q && (p = !0); -function u(a) { - r = a; - v = a.I; - x(); +var r = new Promise((a => { + u = a; +})); + +function x(a) { + v = a; + y = a.I; + C(); k ||= {}; k.wasm = a.C; - y(); + D(); a.C = a.J = 0; } p && !q && (onmessage = a => { onmessage = null; - u(a.data); + x(a.data); }); if (q) { @@ -23,7 +27,7 @@ if (q) { constructor(e) { super(); e = e.processorOptions; - this.u = C.get(e.u); + this.u = E.get(e.u); this.v = e.v; this.s = e.s; } @@ -31,48 +35,49 @@ if (q) { return c; } process(e, h, f) { - let m = e.length, w = h.length, F = 0, l, z, n, t = 4 * this.s, g = 12 * (m + w), W = D(), A, H, B; + let m = e.length, w = h.length, G = 0, l, z, n, t = 4 * this.s, g = 12 * (m + w), Y = F(), A, I, B; for (l of e) g += l.length * t; for (l of h) g += l.length * t; - for (l in f) g += f[l].byteLength + 8, ++F; - A = E(g); + for (l in f) g += f[l].byteLength + 8, ++G; + A = H(g); g = A >> 2; n = A + 12 * m; for (l of e) { - G[g] = l.length; - G[g + 1] = this.s; - G[g + 2] = n; + J[g] = l.length; + J[g + 1] = this.s; + J[g + 2] = n; g += 3; - for (z of l) I.set(z, n >> 2), n += t; + for (z of l) K.set(z, n >> 2), n += t; } - H = n; - g = H >> 2; + I = n; + g = I >> 2; e = (n += 12 * w) >> 2; - for (l of h) G[g] = l.length, G[g + 1] = this.s, G[g + 2] = n, g += 3, n += t * l.length; + for (l of h) J[g] = l.length, J[g + 1] = this.s, J[g + 2] = n, g += 3, n += t * l.length; t = n; g = t >> 2; - n += 8 * F; - for (l = 0; B = f[l++]; ) G[g] = B.length, G[g + 1] = n, g += 2, I.set(B, n >> 2), + n += 8 * G; + for (l = 0; B = f[l++]; ) J[g] = B.length, J[g + 1] = n, g += 2, K.set(B, n >> 2), n += 4 * B.length; - if (f = this.u(m, A, w, H, F, t, this.v)) for (l of h) for (z of l) for (g = 0; g < this.s; ++g) z[g] = I[e++]; - J(W); + if (f = this.u(m, A, w, I, G, t, this.v)) for (l of h) for (z of l) for (g = 0; g < this.s; ++g) z[g] = K[e++]; + L(Y); return !!f; } } return d; } - var K; + var M; class b extends AudioWorkletProcessor { constructor(c) { super(); - u(c.processorOptions); - K = this.port; - K.onmessage = async d => { + x(c.processorOptions); + M = this.port; + M.onmessage = async d => { + await r; d = d.data; - d._wpn ? (registerProcessor(d._wpn, a(d.D)), K.postMessage({ + d._wpn ? (registerProcessor(d._wpn, a(d.D)), M.postMessage({ _wsc: d.u, A: [ d.F, 1, d.v ] - })) : d._wsc && C.get(d._wsc)(...d.A); + })) : d._wsc && (await r, E.get(d._wsc)(...d.A)); }; } process() {} @@ -80,31 +85,31 @@ if (q) { registerProcessor("em-bootstrap", b); } -function x() { - var a = v.buffer; - L = new Uint8Array(a); - M = new Int32Array(a); - G = new Uint32Array(a); - I = new Float32Array(a); +function C() { + var a = y.buffer; + N = new Uint8Array(a); + O = new Int32Array(a); + J = new Uint32Array(a); + K = new Float32Array(a); } -p || (v = k.mem || new WebAssembly.Memory({ +p || (y = k.mem || new WebAssembly.Memory({ initial: 256, maximum: 256, shared: !0 -}), x()); +}), C()); -var N = [], O = a => { +var P = [], Q = a => { a = a.data; let b = a._wsc; - b && C.get(b)(...a.x); -}, P = a => { - N.push(a); -}, Q = a => J(a), R = () => D(), aa = (a, b, c, d) => { + b && E.get(b)(...a.x); +}, R = a => { + P.push(a); +}, aa = a => L(a), ba = () => F(), ca = (a, b, c, d) => { b = S[b]; S[a].connect(b.destination || b, c, d); }, S = {}, T = 0, U = "undefined" != typeof TextDecoder ? new TextDecoder : void 0, V = (a = 0) => { - for (var b = L, c = a, d = c + void 0; b[c] && !(c >= d); ) ++c; + for (var b = N, c = a, d = c + void 0; b[c] && !(c >= d); ) ++c; if (16 < c - a && b.buffer && U) return U.decode(b.slice(a, c)); for (d = ""; a < c; ) { var e = b[a++]; @@ -118,25 +123,25 @@ var N = [], O = a => { } else d += String.fromCharCode(e); } return d; -}, ba = a => { +}, da = a => { var b = window.AudioContext || window.webkitAudioContext; if (a >>= 2) { - var c = G[a] ? (c = G[a]) ? V(c) : "" : void 0; + var c = J[a] ? (c = J[a]) ? V(c) : "" : void 0; a = { latencyHint: c, - sampleRate: M[a + 1] || void 0 + sampleRate: O[a + 1] || void 0 }; } else a = void 0; if (c = b) b = new b(a), S[++T] = b, c = T; return c; -}, ca = (a, b, c, d, e) => { +}, ea = (a, b, c, d, e) => { if (c >>= 2) { - var h = M[c], f = M[c + 1]; - if (G[c + 2]) { - var m = G[c + 2] >> 2; - c = M[c + 1]; + var h = O[c], f = O[c + 1]; + if (J[c + 2]) { + var m = J[c + 2] >> 2; + c = O[c + 1]; let w = []; - for (;c--; ) w.push(G[m++]); + for (;c--; ) w.push(J[m++]); m = w; } else m = void 0; d = { @@ -153,19 +158,19 @@ var N = [], O = a => { a = new AudioWorkletNode(S[a], b ? V(b) : "", d); S[++T] = a; return T; -}, da = (a, b, c, d) => { +}, fa = (a, b, c, d) => { b >>= 2; - let e = [], h = G[b + 1], f = G[b + 2] >> 2, m = 0; + let e = [], h = J[b + 1], f = J[b + 2] >> 2, m = 0; for (;h--; ) e.push({ name: m++, - defaultValue: I[f++], - minValue: I[f++], - maxValue: I[f++], - automationRate: [ "a", "k" ][G[f++]] + "-rate" + defaultValue: K[f++], + minValue: K[f++], + maxValue: K[f++], + automationRate: [ "a", "k" ][J[f++]] + "-rate" }); h = S[a].audioWorklet.B.port; f = h.postMessage; - b = (b = G[b]) ? V(b) : ""; + b = (b = J[b]) ? V(b) : ""; f.call(h, { _wpn: b, D: e, @@ -173,31 +178,31 @@ var N = [], O = a => { u: c, v: d }); -}, ea = () => !1, fa = 1, ha = a => { +}, ha = () => !1, ia = 1, ja = a => { a = a.data; let b = a._wsc; - b && C.get(b)(...a.A); -}, ia = a => E(a), ja = (a, b, c, d, e) => { + b && E.get(b)(...a.A); +}, ka = a => H(a), la = (a, b, c, d, e) => { let h = S[a], f = h.audioWorklet, m = () => { - C.get(d)(a, 0, e); + E.get(d)(a, 0, e); }; if (!f) return m(); f.addModule(k.js).then((() => { f.B = new AudioWorkletNode(h, "em-bootstrap", { processorOptions: { - K: fa++, + K: ia++, C: k.wasm, - I: v, + I: y, G: b, H: c } }); - f.B.port.onmessage = ha; - C.get(d)(a, 1, e); + f.B.port.onmessage = ja; + E.get(d)(a, 1, e); })).catch(m); }; -function ka(a) { +function ma(a) { let b = document.createElement("button"); b.innerHTML = "Toggle playback"; document.body.appendChild(b); @@ -207,35 +212,36 @@ function ka(a) { }; } -function y() { +function D() { Z = { - f: ka, - g: aa, - d: ba, - h: ca, - e: da, - b: ea, - c: ja, - a: v + f: ma, + g: ca, + d: da, + h: ea, + e: fa, + b: ha, + c: la, + a: y }; WebAssembly.instantiate(k.wasm, { a: Z }).then((a => { a = a.instance.exports; - X = a.j; - J = a.l; - E = a.m; - D = a.n; - Y = a.o; - C = a.k; - k.stackSave = R; - k.stackAlloc = ia; - k.stackRestore = Q; - k.wasmTable = C; - p ? (Y(r.G, r.H), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", P), - N = N.forEach(O), addEventListener("message", O))) : a.i(); - p || X(); + W = a.j; + L = a.l; + H = a.m; + F = a.n; + X = a.o; + E = a.k; + k.stackSave = ba; + k.stackAlloc = ka; + k.stackRestore = aa; + k.wasmTable = E; + p ? (X(v.G, v.H), "undefined" === typeof AudioWorkletGlobalScope && (removeEventListener("message", R), + P = P.forEach(Q), addEventListener("message", Q))) : a.i(); + u?.(k); + p || W(); })); } -p || y(); \ No newline at end of file +p || D(); \ No newline at end of file diff --git a/test/code_size/audio_worklet_wasm.json b/test/code_size/audio_worklet_wasm.json index 9648aff3d49a7..a64187a4caf38 100644 --- a/test/code_size/audio_worklet_wasm.json +++ b/test/code_size/audio_worklet_wasm.json @@ -1,10 +1,10 @@ { "a.html": 519, "a.html.gz": 357, - "a.js": 3853, - "a.js.gz": 2045, + "a.js": 3912, + "a.js.gz": 2078, "a.wasm": 1288, "a.wasm.gz": 860, - "total": 5660, - "total_gz": 3262 + "total": 5719, + "total_gz": 3295 } diff --git a/test/other/codesize/test_codesize_minimal_esm.gzsize b/test/other/codesize/test_codesize_minimal_esm.gzsize index 39ccce4049fa0..7b6369b1040ed 100644 --- a/test/other/codesize/test_codesize_minimal_esm.gzsize +++ b/test/other/codesize/test_codesize_minimal_esm.gzsize @@ -1 +1 @@ -1240 +1237 diff --git a/test/other/codesize/test_codesize_minimal_esm.jssize b/test/other/codesize/test_codesize_minimal_esm.jssize index b04af7c81b6a4..d765d59d6220d 100644 --- a/test/other/codesize/test_codesize_minimal_esm.jssize +++ b/test/other/codesize/test_codesize_minimal_esm.jssize @@ -1 +1 @@ -2566 +2556 diff --git a/tools/link.py b/tools/link.py index 452f4fe22aad7..a758027e5e191 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1221,15 +1221,20 @@ def limit_incoming_module_api(): if settings.STACK_OVERFLOW_CHECK >= 2: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$setStackLimits'] - if settings.MODULARIZE: - if settings.PROXY_TO_WORKER: + if settings.MODULARIZE and settings.PROXY_TO_WORKER: exit_with_error('-sMODULARIZE is not compatible with --proxy-to-worker (if you want to run in a worker with -sMODULARIZE, you likely want to do the worker side setup manually)') - # in MINIMAL_RUNTIME we may not need to emit the Promise code, as the - # HTML output creates a singleton instance, and it does so without the - # Promise. However, in Pthreads mode the Promise is used for worker - # creation. - if settings.MINIMAL_RUNTIME and options.oformat == OFormat.HTML and not settings.PTHREADS: - settings.USE_READY_PROMISE = 0 + + if settings.AUDIO_WORKLET: + # The audio worklet needs to wait for the Wasm module to be instantiated + # before it begins processing. + settings.USE_READY_PROMISE = 1 + elif settings.MODULARIZE and (not settings.MINIMAL_RUNTIME or options.oformat != OFormat.HTML or settings.PTHREADS): + # Modularize usually requires the ready promise code, but in certain cases + # can be omitted. In MINIMAL_RUNTIME we may not need to emit the Promise + # code, as the HTML output creates a singleton instance, and it does so + # without the Promise. However, in Pthreads mode the Promise is used for + # worker creation. + settings.USE_READY_PROMISE = 1 check_browser_versions()