Skip to content

Commit f2557fa

Browse files
committed
feat(onnxruntime): WebAssembly (Emscripten) static backend
Add an onnx-wasm-static leg that builds a single self-contained libonnxruntime.a for anira compiled to WebAssembly. - CMakePresets: onnx-wasm-static (platform=wasm, arch=wasm32, toolchain=emscripten); auto-joins the onnx matrix via engine-meta. - build-ort.sh: wasm case — init the cmake/external/emsdk submodule (build.py installs+activates its pinned emsdk 4.0.23 itself) and build with --build_wasm_static_lib --enable_wasm_simd --enable_wasm_threads --disable_rtti. --build_wasm_static_lib bundles every transitive dep via emar, so the wasm leg skips the re2 force-build and scripts/bundle-static.sh. - stage.sh: wasm branch ships libonnxruntime_webassembly.a as libonnxruntime.a (same name as every other static target). - _build-backend.yml: smoke (wasm, node) gated on toolchain==emscripten — em++ links the smoke against the .a and runs a real add.onnx forward pass under emsdk's Node (NODERAWFS + PROXY_TO_PTHREAD). - Docs: support-matrix WASM row + COOP/COEP -pthread consumer note. Threads => the consuming wasm app must link -pthread on a cross-origin-isolated page.
1 parent 14379f2 commit f2557fa

7 files changed

Lines changed: 71 additions & 3 deletions

File tree

.github/workflows/_build-backend.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,25 @@ jobs:
148148
adb shell "/data/local/tmp/smoke /data/local/tmp/add.bin" 2>&1 | tee out.txt
149149
grep -q PASS out.txt
150150
151+
# WASM: link the smoke against the staged .a with em++ (proves the archive is
152+
# symbol-complete) and run a real forward pass under emsdk's Node. NODERAWFS lets the
153+
# smoke std::ifstream the model off the real FS; PROXY_TO_PTHREAD runs main() off a
154+
# worker so the threads-enabled lib's threadpool works without blocking node's loop.
155+
# Uses the same emsdk build.py installed+activated under the ort source (4.0.23).
156+
- name: smoke (wasm, node)
157+
if: ${{ matrix.toolchain == 'emscripten' }}
158+
shell: bash
159+
run: |
160+
set -euo pipefail
161+
. engines/${{ inputs.engine }}/onnxruntime-src/cmake/external/emsdk/emsdk_env.sh
162+
st="${{ steps.b.outputs.staging }}"
163+
em++ -std=c++17 -O2 -I "$st/include" \
164+
engines/${{ inputs.engine }}/test/smoke.cpp "$st/lib/lib${{ inputs.libname }}.a" \
165+
-pthread -sPROXY_TO_PTHREAD=1 -sEXIT_RUNTIME=1 \
166+
-sALLOW_MEMORY_GROWTH=1 -sNODERAWFS=1 -o smoke.js
167+
node smoke.js engines/${{ inputs.engine }}/test/add.onnx 2>&1 | tee out.txt
168+
grep -q PASS out.txt
169+
151170
# Publish standalone, except intermediate per-ABI Android legs (toolchain==android),
152171
# which are combined into one multi-ABI archive below. The multi-ABI AAR leg
153172
# (toolchain==none, arch==multi) publishes here directly.

CMakePresets.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@
161161
"cacheVariables": { "BACKENDS_ENGINE": "onnxruntime", "BACKENDS_PLATFORM": "android", "BACKENDS_ARCH": "x86_64", "BACKENDS_CONFIG": "Release", "BACKENDS_KIND": "static", "BACKENDS_SOURCE": "build" },
162162
"vendor": { "anira": { "name": "Android-x86_64", "runner": "ubuntu-24.04", "arch": "x86_64", "kind": "static", "config": "Release", "canRun": "0", "toolchain": "android", "source": "build" } }
163163
},
164+
{
165+
"name": "onnx-wasm-static", "inherits": "orch-base",
166+
"cacheVariables": { "BACKENDS_ENGINE": "onnxruntime", "BACKENDS_PLATFORM": "wasm", "BACKENDS_ARCH": "wasm32", "BACKENDS_CONFIG": "Release", "BACKENDS_KIND": "static", "BACKENDS_SOURCE": "build" },
167+
"vendor": { "anira": { "name": "WASM", "runner": "ubuntu-24.04", "arch": "wasm32", "kind": "static", "config": "Release", "canRun": "1", "toolchain": "emscripten", "source": "build" } }
168+
},
164169
{
165170
"name": "onnx-macos-x86_64-shared", "inherits": "orch-base",
166171
"cacheVariables": { "BACKENDS_ENGINE": "onnxruntime", "BACKENDS_PLATFORM": "macos", "BACKENDS_ARCH": "x86_64", "BACKENDS_CONFIG": "Release", "BACKENDS_KIND": "shared", "BACKENDS_SOURCE": "build" },

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,16 @@ What ships per target — `shared` and/or `static`:
3434
| Windows arm64 | shared · static ¹ | shared · static ¹ | shared · static ¹ | shared |
3535
| Android (`arm64-v8a` + `x86_64`)| shared · static | shared · static | shared · static ||
3636
| iOS (xcframework) | static | shared | static ||
37+
| WASM (Emscripten) ||| static ² ||
3738

3839
macOS `shared` dylibs are **Developer ID code-signed** (Hardened Runtime, timestamped); the
3940
consuming app re-signs/notarizes on embed.
4041

4142
> ¹ Windows `static` also ships a `Debug` variant.
4243
44+
> ² WASM is a single Emscripten static archive built with SIMD + pthreads (`--disable_rtti`);
45+
> link it into an anira-on-WASM build with `-pthread` on a cross-origin-isolated (COOP/COEP) page.
46+
4347
> `` = not provided.
4448
4549
Per-backend build details (e.g. LiteRT's `LiteRt*` vs `TfLite*` API split, Windows-arm64

TODO.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
- Android `arm64-v8a` smoke is compile+link only (software emulation hangs; `x86_64` runs the real forward pass).
44
- Android shared run-on-emulator smoke (currently compile+link only — the `.so` may need `libc++_shared.so` on-device).
5-
- wasm — Emscripten target/preset (re-add the deferred `wasm` row; `-matomics -msimd128 -mbulk-memory`).
5+
- wasm — ONNXRuntime done (`onnx-wasm-static`, `--build_wasm_static_lib` + simd/threads). TFLite next
6+
(Emscripten CMake build, `-matomics -msimd128 -mbulk-memory`); LiteRT deferred (Bazel+emscripten — use the TFLite C API on WASM).
67
- ExecuTorch backend — researching (static + AOT `.pte` model).
78
- Linux `armv7l` (Bela) — needs `-DTFLITE_ENABLE_XNNPACK=OFF`.

engines/onnxruntime/README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ Builds `onnxruntime` (C API, **full operator set** — any model works, CPU prov
44
for [anira](https://github.com/anira-project/anira). ONNX Runtime ships only *shared*
55
libs upstream, so **static is built from source** (no op-reduction — every operator
66
ships); **shared** is built for macOS but **repackaged** from Microsoft's prebuilts
7-
elsewhere.
7+
elsewhere. A **WASM** (Emscripten) static lib is built from source too — for anira
8+
compiled to WebAssembly.
89

910
## Files
1011

@@ -43,12 +44,22 @@ elsewhere.
4344
DLLs next to the exe; macOS needs `-framework Foundation -framework CoreFoundation`; Linux
4445
needs `rt`/`dl`/`m`.
4546
- **iOS** `build.py` flag is `--apple_sysroot` (renamed from `--ios_sysroot` in 1.26).
47+
- **WASM** (`build-ort.sh wasm`): `--build_wasm_static_lib` builds one **self-contained**
48+
`libonnxruntime_webassembly.a` (all deps bundled by `bundle_static_library` via `emar`) —
49+
so the wasm leg skips the re2 force-build *and* `bundle-static.sh`; `stage.sh` just renames
50+
it to `libonnxruntime.a`. Built with `--enable_wasm_simd --enable_wasm_threads --disable_rtti`
51+
(the ort-builder recipe). `build.py` installs+activates its own pinned **emsdk 4.0.23** from
52+
the `cmake/external/emsdk` submodule (init'd in `build-ort.sh`) — no external emsdk needed.
53+
**Threads** ⇒ the consuming wasm app must link `-pthread` and be served cross-origin-isolated
54+
(COOP/COEP). The CI smoke links + runs the forward pass under emsdk's Node (`NODERAWFS` +
55+
`PROXY_TO_PTHREAD`).
4656

4757
## Local build
4858

4959
```bash
5060
# from this directory
5161
bash build-ort.sh macos arm64 Release build # <platform> <arch> <config> <build-dir> [kind]
5262
bash build-ort.sh macos arm64 Release build shared # shared variant (libonnxruntime.dylib)
53-
bash ../../scripts/bundle-static.sh build/Release /tmp/out/lib/libonnxruntime.a # static only
63+
bash build-ort.sh wasm wasm32 Release build # WASM static lib (needs emsdk submodule)
64+
bash ../../scripts/bundle-static.sh build/Release /tmp/out/lib/libonnxruntime.a # static only (not wasm)
5465
```

engines/onnxruntime/build-ort.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@ case "$PLATFORM" in
9595
ios-sim)
9696
ARGS+=(--ios --use_xcode --apple_sysroot iphonesimulator --osx_arch "$ARCH" --apple_deploy_target 13.0 --build_apple_framework)
9797
;;
98+
wasm)
99+
# --build_wasm_static_lib bundles EVERY transitive dep (onnx/protobuf/re2/mlas/xnnpack)
100+
# into one self-contained libonnxruntime_webassembly.a via emar (onnxruntime_webassembly.cmake
101+
# bundle_static_library) — so the wasm leg needs neither the re2 force-build nor
102+
# scripts/bundle-static.sh below. build.py installs+activates its OWN pinned emsdk (4.0.23)
103+
# from the cmake/external/emsdk submodule (hardcoded toolchain path), so init it first —
104+
# the shallow clone above fetches no submodules. simd + threads (the ort-builder recipe);
105+
# threads => the consumer must link -pthread on a cross-origin-isolated (COOP/COEP) page.
106+
git -C "$SRC" submodule update --init --depth 1 cmake/external/emsdk
107+
ARGS+=(--build_wasm_static_lib --enable_wasm_simd --enable_wasm_threads --disable_rtti)
108+
;;
98109
*) echo "ERROR: unknown platform '$PLATFORM'"; exit 1 ;;
99110
esac
100111

@@ -111,6 +122,11 @@ if [ "$KIND" = "shared" ]; then
111122
echo "onnxruntime $VER ($PLATFORM/$ARCH/$CONFIG, shared) built -> $OUT/$CONFIG"
112123
exit 0
113124
fi
125+
if [ "$PLATFORM" = "wasm" ]; then
126+
# --build_wasm_static_lib already produced a self-contained libonnxruntime_webassembly.a.
127+
echo "onnxruntime $VER (wasm static lib) built -> $OUT/$CONFIG"
128+
exit 0
129+
fi
114130
echo "+ force-build re2 (static bundle needs libre2.a / re2.lib)"
115131
# re2 is fetched from source (CMAKE_DISABLE_FIND_PACKAGE_re2=ON + onnxruntime_USE_VCPKG=OFF).
116132
# With Ninja on every platform, the target builds by name (the old Windows msbuild-by-path

engines/onnxruntime/stage.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ fi
3535
# ---- build from source --------------------------------------------------------------------
3636
cp "$HERE"/include/*.h "$ST/include/"
3737

38+
if [ "$PLATFORM" = "wasm" ]; then
39+
# Emscripten static lib: build-ort.sh emits ONE self-contained libonnxruntime_webassembly.a
40+
# (deps bundled by --build_wasm_static_lib) — no re2 force-build / bundle-static.sh. Ship it
41+
# as libonnxruntime.a so consumers link the same name as every other static target.
42+
bash "$HERE/build-ort.sh" wasm wasm32 "$CONFIG" "$HERE/build" static
43+
a="$(find "$HERE/build/$CONFIG" -maxdepth 2 -name 'libonnxruntime_webassembly.a' | head -1)"
44+
[ -n "$a" ] || { echo "ERROR: no wasm static lib under $HERE/build/$CONFIG"; exit 1; }
45+
cp "$a" "$ST/lib/libonnxruntime.a"
46+
echo "staged onnxruntime (wasm/wasm32/static) -> $ST"
47+
exit 0
48+
fi
49+
3850
if [ "$KIND" = "shared" ]; then
3951
# macOS only (Linux/Windows/Android shared come from prebuilt). Builds libonnxruntime.dylib
4052
# directly — one self-contained lib, no re2 force-build / no bundling.

0 commit comments

Comments
 (0)