diff --git a/wasm/build.sh b/wasm/build.sh index e70e44f..9c04a2d 100755 --- a/wasm/build.sh +++ b/wasm/build.sh @@ -1,17 +1,20 @@ #!/bin/bash -# Build quant.cpp WASM demo +# Build quant.cpp WASM demo (multi-threaded + SIMD) # Requires: Emscripten SDK (emcc) # # Usage: cd wasm && bash build.sh # Then: python3 -m http.server 8080 # Open: http://localhost:8080 +# +# Multi-threading requires Cross-Origin-Isolation headers. +# coi-serviceworker.js injects them on GitHub Pages / static hosts. set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" -echo "=== Building quant.cpp WASM ===" +echo "=== Building quant.cpp WASM (pthreads + SIMD) ===" # Check emcc if ! command -v emcc &>/dev/null; then @@ -23,13 +26,14 @@ fi echo "emcc version: $(emcc --version | head -1)" -# Build +# Build with pthreads + SIMD128 + ASYNCIFY emcc "$SCRIPT_DIR/quant_wasm.c" \ -I"$PROJECT_DIR" \ -o "$SCRIPT_DIR/quant.js" \ -O3 \ -msimd128 \ -flto \ + -pthread \ -s WASM=1 \ -s ALLOW_MEMORY_GROWTH=1 \ -s MAXIMUM_MEMORY=4GB \ @@ -38,13 +42,15 @@ emcc "$SCRIPT_DIR/quant_wasm.c" \ -s EXPORTED_RUNTIME_METHODS='["UTF8ToString","allocateUTF8","FS"]' \ -s FORCE_FILESYSTEM=1 \ -s MODULARIZE=0 \ - -s ENVIRONMENT=web \ + -s ENVIRONMENT='web,worker' \ -s NO_EXIT_RUNTIME=1 \ -s ASSERTIONS=0 \ -s STACK_SIZE=1MB \ -s ASYNCIFY \ -s 'ASYNCIFY_IMPORTS=["emscripten_sleep"]' \ -s ASYNCIFY_STACK_SIZE=65536 \ + -s PTHREAD_POOL_SIZE=4 \ + -s PTHREAD_POOL_SIZE_STRICT=0 \ -lm \ -DNDEBUG \ -D__EMSCRIPTEN__ \ @@ -53,11 +59,14 @@ emcc "$SCRIPT_DIR/quant_wasm.c" \ echo "" echo "=== Build complete ===" -echo "Files: quant.js ($(du -h "$SCRIPT_DIR/quant.js" | cut -f1)), quant.wasm ($(du -h "$SCRIPT_DIR/quant.wasm" | cut -f1))" +echo "Files:" +for f in quant.js quant.wasm quant.worker.js; do + [ -f "$SCRIPT_DIR/$f" ] && echo " $f ($(du -h "$SCRIPT_DIR/$f" | cut -f1))" +done echo "" echo "To serve locally:" echo " cd $SCRIPT_DIR && python3 -m http.server 8080" echo " Open http://localhost:8080" echo "" -echo "For HTTPS (required for SharedArrayBuffer):" -echo " npx serve -s $SCRIPT_DIR --ssl-cert cert.pem --ssl-key key.pem" +echo "Note: Multi-threading requires Cross-Origin-Isolation." +echo "coi-serviceworker.js handles this automatically on GitHub Pages." diff --git a/wasm/coi-serviceworker.js b/wasm/coi-serviceworker.js new file mode 100644 index 0000000..ae0530b --- /dev/null +++ b/wasm/coi-serviceworker.js @@ -0,0 +1,63 @@ +/*! coi-serviceworker v0.1.7 - Guido Zuidhof, licensed under MIT */ +/* + * Service Worker that injects Cross-Origin-Opener-Policy and + * Cross-Origin-Embedder-Policy headers into all responses. + * This enables SharedArrayBuffer on hosts that don't support + * custom HTTP headers (e.g., GitHub Pages). + * + * Required for WASM pthreads (multi-threaded inference). + */ +if (typeof window === 'undefined') { + // Service Worker scope + self.addEventListener("install", () => self.skipWaiting()); + self.addEventListener("activate", (e) => e.waitUntil(self.clients.claim())); + + self.addEventListener("fetch", (e) => { + // Only intercept same-origin or navigation requests + if ( + e.request.cache === "only-if-cached" && + e.request.mode !== "same-origin" + ) { + return; + } + + e.respondWith( + fetch(e.request).then((response) => { + // Can't modify opaque responses + if (response.status === 0) return response; + + const newHeaders = new Headers(response.headers); + newHeaders.set("Cross-Origin-Embedder-Policy", "credentialless"); + newHeaders.set("Cross-Origin-Opener-Policy", "same-origin"); + + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: newHeaders, + }); + }).catch((err) => { + console.error("coi-serviceworker fetch error:", err); + return new Response("Service Worker fetch error", { status: 500 }); + }) + ); + }); +} else { + // Window scope — register the service worker + (async () => { + if (!window.crossOriginIsolated) { + const reg = await navigator.serviceWorker.register( + window.document.currentScript.src + ); + if (reg.active && !navigator.serviceWorker.controller) { + // Service worker installed but not controlling — reload to activate + window.location.reload(); + } else if (!reg.active) { + // Wait for the service worker to activate, then reload + const sw = reg.installing || reg.waiting; + sw.addEventListener("statechange", () => { + if (sw.state === "activated") window.location.reload(); + }); + } + } + })(); +} diff --git a/wasm/index.html b/wasm/index.html index 925bdc9..716d0ee 100644 --- a/wasm/index.html +++ b/wasm/index.html @@ -7,6 +7,8 @@