From aa69450799dfa526c960774b0f1f86194974bc5a Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 28 Apr 2026 17:16:32 -0700 Subject: [PATCH] Extend hybrid pthread/wasm-workers support for Audio Worklets. NFC Prior to this change calling pthread functions from an audio worklet context was undefined behaviour. Followup to #26777 --- src/lib/libsigs.js | 2 +- src/lib/libwebaudio.js | 5 +- system/lib/libc/emscripten_internal.h | 2 +- system/lib/pthread/threading_internal.h | 6 + system/lib/wasm_worker/audio_worklet.c | 9 +- system/lib/wasm_worker/library_wasm_worker.c | 6 +- test/codesize/audio_worklet_wasm.expected.js | 185 +++++++++--------- ...nimal_runtime_code_size_audio_worklet.json | 12 +- 8 files changed, 122 insertions(+), 105 deletions(-) diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index 6e5e5f43e5832..27e5607cd322b 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -314,7 +314,7 @@ sigs = { _embind_register_value_object__sig: 'vpppppp', _embind_register_value_object_field__sig: 'vpppppppppp', _embind_register_void__sig: 'vpp', - _emscripten_create_audio_worklet__sig: 'viipipp', + _emscripten_create_audio_worklet__sig: 'viipippp', _emscripten_create_wasm_worker__sig: 'iipip', _emscripten_dlopen_js__sig: 'vpppp', _emscripten_dlsync_threads__sig: 'v', diff --git a/src/lib/libwebaudio.js b/src/lib/libwebaudio.js index 96c5122213bc0..1da3306e37c53 100644 --- a/src/lib/libwebaudio.js +++ b/src/lib/libwebaudio.js @@ -170,7 +170,7 @@ var LibraryWebAudio = { _emscripten_create_audio_worklet__deps: [ '$_emAudioDispatchProcessorCallback', '$stackAlloc', '$stackRestore', '$stackSave'], - _emscripten_create_audio_worklet: (wwID, contextHandle, stackLowestAddress, stackSize, callback, userData) => { + _emscripten_create_audio_worklet: (wwID, contextHandle, stackLowestAddress, stackSize, pthreadPtr, callback, userData) => { #if ASSERTIONS || WEBAUDIO_DEBUG emAudioExpectContext(contextHandle, '_emscripten_create_audio_worklet'); @@ -256,6 +256,9 @@ var LibraryWebAudio = { wasmMemory, stackLowestAddress, // sb = stack base stackSize, // sz = stack size +#if PTHREADS + pthreadPtr, +#endif }); audioWorklet.port.onmessage = _emAudioDispatchProcessorCallback; {{{ makeDynCall('viip', 'callback') }}}(contextHandle, 1/*EM_TRUE*/, userData); diff --git a/system/lib/libc/emscripten_internal.h b/system/lib/libc/emscripten_internal.h index 482d88dcbfce8..b5ef74c043a56 100644 --- a/system/lib/libc/emscripten_internal.h +++ b/system/lib/libc/emscripten_internal.h @@ -137,7 +137,7 @@ void emscripten_fetch_free(unsigned int); // to perform the wasm worker creation. bool _emscripten_create_wasm_worker(emscripten_wasm_worker_t wwID, void *stackLowestAddress, uint32_t stackSize, void* pthreadPtr); -void _emscripten_create_audio_worklet(emscripten_wasm_worker_t wwID, EMSCRIPTEN_WEBAUDIO_T audioContext, void *stackLowestAddress, uint32_t stackSize, EmscriptenStartWebAudioWorkletCallback callback, void *userData2); +void _emscripten_create_audio_worklet(emscripten_wasm_worker_t wwID, EMSCRIPTEN_WEBAUDIO_T audioContext, void *stackLowestAddress, uint32_t stackSize, void* pthreadPtr, EmscriptenStartWebAudioWorkletCallback callback, void *userData2); void __resumeException(void* exn); void __cxa_call_unexpected(void* exn); diff --git a/system/lib/pthread/threading_internal.h b/system/lib/pthread/threading_internal.h index dca84b299d418..b4474dea206d2 100644 --- a/system/lib/pthread/threading_internal.h +++ b/system/lib/pthread/threading_internal.h @@ -120,6 +120,12 @@ int _emscripten_thread_supports_atomics_wait(void); pid_t _emscripten_get_next_tid(); +// Initialize pthread data, at start of memory region pointed to `base`. +// `size` is an in/out parameter representing the size of the `base` region +// on input, and the size of new/adjusted region on output. +// Return a new/adjusted memory base to be used to stack/tls data. +void* _emscripten_init_pthread(void *base, size_t* size, pid_t tid); + // Wake the target thread in case it is blocked in emscripten_futex_wait. // Note: If threads directly use lower level APIs such // __builtin_wasm_memory_atomic_waitXX then they will not be woken by diff --git a/system/lib/wasm_worker/audio_worklet.c b/system/lib/wasm_worker/audio_worklet.c index a921fcdea7da7..80cbca2358764 100644 --- a/system/lib/wasm_worker/audio_worklet.c +++ b/system/lib/wasm_worker/audio_worklet.c @@ -13,5 +13,12 @@ // Simple wrapper function around the JS _emscripten_create_audio_worklet // function that adds the _emscripten_get_next_tid() as arg0 void emscripten_start_wasm_audio_worklet_thread_async(EMSCRIPTEN_WEBAUDIO_T audioContext, void *stackLowestAddress, uint32_t stackSize, EmscriptenStartWebAudioWorkletCallback callback, void *userData2) { - _emscripten_create_audio_worklet(_emscripten_get_next_tid(), audioContext, stackLowestAddress, stackSize, callback, userData2); + emscripten_wasm_worker_t wwID = _emscripten_get_next_tid(); + void* pthreadPtr = stackLowestAddress; +#ifdef __EMSCRIPTEN_PTHREADS__ + size_t stackSize_ = stackSize; + stackLowestAddress = _emscripten_init_pthread(stackLowestAddress, &stackSize_, wwID); + stackSize = stackSize_; +#endif + _emscripten_create_audio_worklet(wwID, audioContext, stackLowestAddress, stackSize, pthreadPtr, callback, userData2); } diff --git a/system/lib/wasm_worker/library_wasm_worker.c b/system/lib/wasm_worker/library_wasm_worker.c index 00db005032c10..a785bb074d3e8 100644 --- a/system/lib/wasm_worker/library_wasm_worker.c +++ b/system/lib/wasm_worker/library_wasm_worker.c @@ -94,7 +94,7 @@ weak_alias(dummy, __pthread_tsd_size); * In either case these sections are all aligned to `max_alignment()` * which is the max alignment of any of the given chunks. */ -static void* init_pthread_struct(void *stackPlusTLSAddress, pid_t tid, size_t* stackPlusTLSSize) { +void* _emscripten_init_pthread(void *stackPlusTLSAddress, size_t* stackPlusTLSSize, pid_t tid) { // TODO: Remove duplication with pthread_create pthread_t self = pthread_self(); @@ -143,7 +143,7 @@ static void* init_pthread_struct(void *stackPlusTLSAddress, pid_t tid, size_t* s __tl_unlock(); - dbg("init_pthread_struct: base=%#lx, end=%#lx, used=%zu " + dbg("_emscripten_init_pthread: base=%#lx, end=%#lx, used=%zu " "stackold=%zu stacknew=%zu", base, base + *stackPlusTLSSize, @@ -202,7 +202,7 @@ emscripten_wasm_worker_t emscripten_create_wasm_worker(void *stackPlusTLSAddress emscripten_wasm_worker_t wwID = _emscripten_get_next_tid(); void* pthreadPtr = stackPlusTLSAddress; #ifdef __EMSCRIPTEN_PTHREADS__ - stackPlusTLSAddress = init_pthread_struct(stackPlusTLSAddress, wwID, &stackPlusTLSSize); + stackPlusTLSAddress = _emscripten_init_pthread(stackPlusTLSAddress, &stackPlusTLSSize, wwID); #endif if (!_emscripten_create_wasm_worker(wwID, stackPlusTLSAddress, stackPlusTLSSize, pthreadPtr)) return 0; diff --git a/test/codesize/audio_worklet_wasm.expected.js b/test/codesize/audio_worklet_wasm.expected.js index d9178cedb2034..6308117f59f99 100644 --- a/test/codesize/audio_worklet_wasm.expected.js +++ b/test/codesize/audio_worklet_wasm.expected.js @@ -1,8 +1,8 @@ -var m = globalThis.Module || "undefined" != typeof Module ? Module : {}, q = !!globalThis.AudioWorkletGlobalScope, r = "em-ww" == globalThis.name || q, t, z, J, E, G, I, v, X, F, D, C, Y, A, Z; +var m = globalThis.Module || "undefined" != typeof Module ? Module : {}, r = !!globalThis.AudioWorkletGlobalScope, t = "em-ww" == globalThis.name || r, u, z, J, E, G, I, w, X, F, D, C, Y, A, Z; -function u(a) { - t = a; - v = a.H; +function v(a) { + u = a; + w = a.H; x(); m ||= {}; m.wasm = a.G; @@ -10,14 +10,14 @@ function u(a) { a.G = a.H = 0; } -r && !q && (onmessage = a => { +t && !r && (onmessage = a => { onmessage = null; - u(a.data); + v(a.data); }); -if (q) { +if (r) { function a(c) { - class l extends AudioWorkletProcessor { + class k extends AudioWorkletProcessor { constructor(d) { super(); d = d.processorOptions; @@ -25,54 +25,54 @@ if (q) { this.A = d.A; this.u = d.u; this.s = 4 * this.u; - this.B = Array(Math.min((t.F - 16) / this.s | 0, 64)); + this.B = Array(Math.min((u.F - 16) / this.s | 0, 64)); this.L(); } L() { - for (var d = C(), h = D(this.B.length * this.s) >> 2, g = this.B.length - 1; 0 <= g; g--) this.B[g] = E.subarray(h, h += this.u); + for (var d = C(), g = D(this.B.length * this.s) >> 2, f = this.B.length - 1; 0 <= f; f--) this.B[f] = E.subarray(g, g += this.u); F(d); } static get parameterDescriptors() { return c; } - process(d, h, g) { - var e = d.length, w = h.length, f, p, k = 12 * (e + w), n = 0; - for (f of d) n += f.length; - n *= this.s; + process(d, g, f) { + var l = d.length, n = g.length, e, q, h = 12 * (l + n), p = 0; + for (e of d) p += e.length; + p *= this.s; var H = 0; - for (f of h) H += f.length; - n += H * this.s; + for (e of g) H += e.length; + p += H * this.s; var O = 0; - for (f in g) ++O, k += 8, n += g[f].byteLength; - var V = C(), B = k + n + 15 & -16; - k = D(B); - n = k + (B - n); - B = k; - for (f of d) { - G[k >> 2] = f.length; - G[k + 4 >> 2] = this.u; - G[k + 8 >> 2] = n; - k += 12; - for (p of f) E.set(p, n >> 2), n += this.s; + for (e in f) ++O, h += 8, p += f[e].byteLength; + var V = C(), B = h + p + 15 & -16; + h = D(B); + p = h + (B - p); + B = h; + for (e of d) { + G[h >> 2] = e.length; + G[h + 4 >> 2] = this.u; + G[h + 8 >> 2] = p; + h += 12; + for (q of e) E.set(q, p >> 2), p += this.s; } - d = k; - for (f = 0; p = g[f++]; ) G[k >> 2] = p.length, G[k + 4 >> 2] = n, k += 8, E.set(p, n >> 2), - n += 4 * p.length; - g = k; - for (f of h) G[k >> 2] = f.length, G[k + 4 >> 2] = this.u, G[k + 8 >> 2] = n, k += 12, - n += this.s * f.length; - if (e = this.v(e, B, w, g, O, d, this.A)) for (f of h) for (p of f) p.set(this.B[--H]); + d = h; + for (e = 0; q = f[e++]; ) G[h >> 2] = q.length, G[h + 4 >> 2] = p, h += 8, E.set(q, p >> 2), + p += 4 * q.length; + f = h; + for (e of g) G[h >> 2] = e.length, G[h + 4 >> 2] = this.u, G[h + 8 >> 2] = p, h += 12, + p += this.s * e.length; + if (l = this.v(l, B, n, f, O, d, this.A)) for (e of g) for (q of e) q.set(this.B[--H]); F(V); - return !!e; + return !!l; } } - return l; + return k; } var port = globalThis.port || {}; class b extends AudioWorkletProcessor { constructor(c) { super(); - u(c.processorOptions); + v(c.processorOptions); port instanceof MessagePort || (this.port.onmessage = port.onmessage, port = this.port); } process() {} @@ -81,7 +81,7 @@ if (q) { port.onmessage = async c => { await z; c = c.data; - c._boot ? u(c) : c._wpn ? (registerProcessor(c._wpn, a(c.I)), port.postMessage({ + c._boot ? v(c) : c._wpn ? (registerProcessor(c._wpn, a(c.I)), port.postMessage({ _wsc: c.v, C: [ c.J, 1, c.A ] })) : c._wsc && A.get(c._wsc)(...c.C); @@ -89,14 +89,14 @@ if (q) { } function x() { - var a = v.buffer; + var a = w.buffer; I = new Uint8Array(a); J = new Int32Array(a); G = new Uint32Array(a); E = new Float32Array(a); } -r || (v = m.mem || new WebAssembly.Memory({ +t || (w = m.mem || new WebAssembly.Memory({ initial: 256, maximum: 256, shared: !0 @@ -112,50 +112,51 @@ var K = [], L = a => { a = a.data; var b = a._wsc; b && A.get(b)(...a.C); -}, Q = (a, b, c, l, d, h) => { - var g = P[b], e = g.audioWorklet, w = () => { - A.get(d)(b, 0, h); +}, Q = (a, b, c, k, d, g, f) => { + var l = P[b], n = l.audioWorklet; + d = () => { + A.get(g)(b, 0, f); }; - if (!e) return w(); - e.addModule(m.js).then((() => { - e.port || (e.port = { - postMessage: f => { - f._boot ? (e.D = new AudioWorkletNode(g, "em-bootstrap", { - processorOptions: f - }), e.D.port.onmessage = p => { - e.port.onmessage(p); - }) : e.D.port.postMessage(f); + if (!n) return d(); + n.addModule(m.js).then((() => { + n.port || (n.port = { + postMessage: e => { + e._boot ? (n.D = new AudioWorkletNode(l, "em-bootstrap", { + processorOptions: e + }), n.D.port.onmessage = q => { + n.port.onmessage(q); + }) : n.D.port.postMessage(e); } }); - e.port.postMessage({ + n.port.postMessage({ _boot: 1, M: a, G: m.wasm, - H: v, + H: w, K: c, - F: l + F: k }); - e.port.onmessage = N; - A.get(d)(b, 1, h); - })).catch(w); -}, R = (a, b, c, l) => { + n.port.onmessage = N; + A.get(g)(b, 1, f); + })).catch(d); +}, R = (a, b, c, k) => { b = P[b]; - P[a].connect(b.destination || b, c, l); + P[a].connect(b.destination || b, c, k); }, P = {}, S = 0, T = globalThis.TextDecoder && new TextDecoder, U = (a = 0) => { - for (var b = I, c = a, l = c + void 0; b[c] && !(c >= l); ) ++c; + for (var b = I, c = a, k = c + void 0; b[c] && !(c >= k); ) ++c; if (16 < c - a && b.buffer && T) return T.decode(b.slice(a, c)); - for (l = ""; a < c; ) { + for (k = ""; a < c; ) { var d = b[a++]; if (d & 128) { - var h = b[a++] & 63; - if (192 == (d & 224)) l += String.fromCharCode((d & 31) << 6 | h); else { - var g = b[a++] & 63; - d = 224 == (d & 240) ? (d & 15) << 12 | h << 6 | g : (d & 7) << 18 | h << 12 | g << 6 | b[a++] & 63; - 65536 > d ? l += String.fromCharCode(d) : (d -= 65536, l += String.fromCharCode(55296 | d >> 10, 56320 | d & 1023)); + var g = b[a++] & 63; + if (192 == (d & 224)) k += String.fromCharCode((d & 31) << 6 | g); else { + var f = b[a++] & 63; + d = 224 == (d & 240) ? (d & 15) << 12 | g << 6 | f : (d & 7) << 18 | g << 12 | f << 6 | b[a++] & 63; + 65536 > d ? k += String.fromCharCode(d) : (d -= 65536, k += String.fromCharCode(55296 | d >> 10, 56320 | d & 1023)); } - } else l += String.fromCharCode(d); + } else k += String.fromCharCode(d); } - return l; + return k; }, aa = a => { if (a) { var b = G[a >> 2]; @@ -170,24 +171,24 @@ var K = [], L = a => { a = new AudioContext(a); P[++S] = a; return S; -}, ba = (a, b, c, l, d) => { - var h = c ? J[c + 4 >> 2] : 0; +}, ba = (a, b, c, k, d) => { + var g = c ? J[c + 4 >> 2] : 0; if (c) { - var g = J[c >> 2], e = G[c + 8 >> 2], w = h; - if (e) { - e >>= 2; - for (var f = []; w--; ) f.push(G[e++]); - e = f; - } else e = void 0; + var f = J[c >> 2], l = G[c + 8 >> 2], n = g; + if (l) { + l >>= 2; + for (var e = []; n--; ) e.push(G[l++]); + l = e; + } else l = void 0; c = { - numberOfInputs: g, - numberOfOutputs: h, - outputChannelCount: e, + numberOfInputs: f, + numberOfOutputs: g, + outputChannelCount: l, channelCount: G[c + 12 >> 2] || void 0, channelCountMode: [ , "clamped-max", "explicit" ][J[c + 16 >> 2]], channelInterpretation: [ , "discrete" ][J[c + 20 >> 2]], processorOptions: { - v: l, + v: k, A: d, u: P[a].renderQuantumSize || 128 } @@ -196,11 +197,11 @@ var K = [], L = a => { a = new AudioWorkletNode(P[a], b ? U(b) : "", c); P[++S] = a; return S; -}, ca = (a, b, c, l) => { - var d = (d = G[b >> 2]) ? U(d) : "", h = J[b + 4 >> 2]; +}, ca = (a, b, c, k) => { + var d = (d = G[b >> 2]) ? U(d) : "", g = J[b + 4 >> 2]; b = G[b + 8 >> 2]; - for (var g = [], e = 0; h--; ) g.push({ - name: e++, + for (var f = [], l = 0; g--; ) f.push({ + name: l++, defaultValue: E[b >> 2], minValue: E[b + 4 >> 2], maxValue: E[b + 8 >> 2], @@ -208,12 +209,12 @@ var K = [], L = a => { }), b += 16; P[a].audioWorklet.port.postMessage({ _wpn: d, - I: g, + I: f, J: a, v: c, - A: l + A: k }); -}, da = () => !1, ea = () => q ? () => 138 : a => (a.set(crypto.getRandomValues(new Uint8Array(a.byteLength))), +}, da = () => !1, ea = () => r ? () => 138 : a => (a.set(crypto.getRandomValues(new Uint8Array(a.byteLength))), 0), W = a => (W = ea())(a), fa = (a, b) => W(I.subarray(a, a + b)); function ha(a) { @@ -235,7 +236,7 @@ function y() { i: ba, f: ca, b: da, - a: v, + a: w, d: fa }; z = WebAssembly.instantiate(m.wasm, { @@ -248,10 +249,10 @@ function y() { C = a.o; Y = a.p; A = a.l; - r ? (Y(t.M, t.K, t.F), q || (removeEventListener("message", M), K = K.forEach(L), + t ? (Y(u.M, u.K, u.F), r || (removeEventListener("message", M), K = K.forEach(L), addEventListener("message", L))) : a.j(); - r || X(); + t || X(); })); } -r || y(); \ No newline at end of file +t || y(); \ No newline at end of file diff --git a/test/codesize/test_minimal_runtime_code_size_audio_worklet.json b/test/codesize/test_minimal_runtime_code_size_audio_worklet.json index 06c270000d261..d55e92ae0354e 100644 --- a/test/codesize/test_minimal_runtime_code_size_audio_worklet.json +++ b/test/codesize/test_minimal_runtime_code_size_audio_worklet.json @@ -1,10 +1,10 @@ { "a.html": 515, "a.html.gz": 355, - "a.js": 4446, - "a.js.gz": 2299, - "a.wasm": 1405, - "a.wasm.gz": 939, - "total": 6366, - "total_gz": 3593 + "a.js": 4448, + "a.js.gz": 2297, + "a.wasm": 1409, + "a.wasm.gz": 936, + "total": 6372, + "total_gz": 3588 }