Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .emsdk-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4.0.7
351 changes: 351 additions & 0 deletions .github/workflows/wasm-emscripten.yml

Large diffs are not rendered by default.

35 changes: 25 additions & 10 deletions barretenberg/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,32 +135,47 @@ Various presets are defined in CMakePresets.json for scenarios such as instrumen

#### WASM build

To build:
The wasm preset uses **Emscripten** (emsdk). The exact emsdk version is pinned
in `.emsdk-version` at the repo root; activate it before configuring:

```bash
cmake --preset wasm
cmake --build --preset wasm --target barretenberg.wasm
git clone https://github.com/emscripten-core/emsdk.git ~/emsdk
cd ~/emsdk
./emsdk install $(cat /path/to/repo/.emsdk-version)
./emsdk activate $(cat /path/to/repo/.emsdk-version)
source ./emsdk_env.sh
```

The resulting wasm binary will be at `./build-wasm/bin/barretenberg.wasm`.
You also need **Node.js >= 22** on PATH; tests are launched under Node by
the `cpp/scripts/wasm-run` wrapper.

To run the tests, you'll need to install `wasmtime`.
To build:

```bash
cmake --preset wasm
cmake --build --preset wasm --target barretenberg.wasm
```
curl https://wasmtime.dev/install.sh -sSf | bash
```

Tests can be built and run like:
Emscripten emits a JS loader plus a sibling `.wasm` per executable. The
artifacts are at `./build-wasm/bin/barretenberg.js` + `./build-wasm/bin/barretenberg.wasm`.

Tests can be built and run via the `wasm-run` wrapper:

```bash
cmake --build --preset wasm --target ecc_tests
wasmtime --dir=.. ./bin/ecc_tests
./scripts/wasm-run --dir=.. ./build-wasm/bin/ecc_tests
```

To add gtest filter parameters in a wasm context:

```bash
./scripts/wasm-run --dir=.. ./build-wasm/bin/ecc_tests --gtest_filter=filtertext
```
wasmtime --dir=.. ./bin/ecc_tests run --gtest_filter=filtertext

Or, more idiomatically, build and run via the CMake-generated custom target:

```bash
cmake --build --preset wasm --target run_ecc_tests
```

#### Fuzzing build
Expand Down
30 changes: 30 additions & 0 deletions barretenberg/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,37 @@
#!/usr/bin/env bash
source $(git rev-parse --show-toplevel)/ci3/source

function check_node_floor {
# The wasm test harness depends on Node >= 22 for stable worker_threads
# semantics under Emscripten. Fail clean here rather than deep in cpp/ts.
if command -v node >/dev/null 2>&1; then
local v
v=$(node --version | cut -d 'v' -f 2)
local major=${v%%.*}
if [ "$major" -lt 22 ]; then
echo "Error: Node $v detected; barretenberg requires Node >= 22 (wasm test harness)." >&2
exit 1
fi
fi
}

function ensure_emsdk_active {
# Activate the pinned emsdk if it's installed but not yet on PATH. The
# version pin lives in /.emsdk-version at the repo root.
if command -v emcc >/dev/null 2>&1; then
return
fi
local emsdk_dir=${EMSDK:-/opt/emsdk}
if [ -f "$emsdk_dir/emsdk_env.sh" ]; then
# shellcheck disable=SC1090,SC1091
. "$emsdk_dir/emsdk_env.sh" >/dev/null 2>&1 || true
export EMSDK="$emsdk_dir"
fi
}

function bootstrap_all {
check_node_floor
ensure_emsdk_active
# To run bb we need a crs.
# Download ignition up front to ensure no race conditions at runtime.
[ -n "${SKIP_BB_CRS:-}" ] || ./crs/bootstrap.sh
Expand Down
2 changes: 1 addition & 1 deletion barretenberg/cpp/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.cache/
build*/
src/wasi-sdk*
src/emsdk*
src/barretenberg/honk/proving_key/fixtures
src/barretenberg/rollup/proofs/*/fixtures
srs_db/*/*/transcript*
Expand Down
52 changes: 31 additions & 21 deletions barretenberg/cpp/CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -400,51 +400,42 @@
{
"name": "wasm",
"displayName": "Build for WASM",
"description": "Build with wasi-sdk to create wasm",
"description": "Build with Emscripten to create wasm (single-threaded fallback)",
"binaryDir": "build-wasm",
"generator": "Ninja",
"toolchainFile": "cmake/toolchains/wasm32-wasi.cmake",
"toolchainFile": "cmake/toolchains/wasm-emscripten.cmake",
"environment": {
"WASI_SDK_PREFIX": "/opt/wasi-sdk",
"CC": "$env{WASI_SDK_PREFIX}/bin/clang",
"CXX": "$env{WASI_SDK_PREFIX}/bin/clang++",
"CXXFLAGS": "-DBB_VERBOSE -fvisibility=hidden",
"AR": "$env{WASI_SDK_PREFIX}/bin/llvm-ar",
"RANLIB": "$env{WASI_SDK_PREFIX}/bin/llvm-ranlib"
"CXXFLAGS": "-DBB_VERBOSE -fvisibility=hidden"
},
"cacheVariables": {
"CMAKE_SYSROOT": "$env{WASI_SDK_PREFIX}/share/wasi-sysroot",
"CMAKE_FIND_ROOT_PATH_MODE_PROGRAM": "NEVER",
"CMAKE_FIND_ROOT_PATH_MODE_LIBRARY": "ONLY",
"CMAKE_FIND_ROOT_PATH_MODE_INCLUDE": "ONLY",
"CMAKE_FIND_ROOT_PATH_MODE_PACKAGE": "ONLY",
"CMAKE_C_COMPILER_WORKS": "ON",
"CMAKE_CXX_COMPILER_WORKS": "ON",
"CMAKE_BUILD_TYPE": "Release",
"MULTITHREADING": "OFF",
"CMAKE_CXX_FLAGS": "-DBB_NO_EXCEPTIONS"
"WASM_EXCEPTIONS": "wasm"
}
},
{
"name": "wasm-threads",
"displayName": "Build for pthread enabled WASM",
"description": "Build for pthread enabled WASM",
"description": "Build with Emscripten + pthreads (release)",
"inherits": "wasm",
"binaryDir": "build-wasm-threads",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"MULTITHREADING": "ON",
"ENABLE_WASM_BENCH": "ON"
"ENABLE_WASM_BENCH": "ON",
"WASM_EXCEPTIONS": "wasm"
}
},
{
"name": "wasm-threads-dbg",
"displayName": "Build for debug WASM",
"binaryDir": "build-wasm-threads-dbg",
"description": "Build with wasi-sdk to create debug wasm",
"description": "Build with Emscripten + pthreads (debug, ASSERTIONS=2 + SAFE_HEAP=1)",
"inherits": "wasm",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"MULTITHREADING": "ON"
"MULTITHREADING": "ON",
"WASM_EXCEPTIONS": "wasm"
}
},
{
Expand Down Expand Up @@ -911,7 +902,26 @@
{
"name": "wasm",
"configurePreset": "wasm",
"inheritConfigureEnvironment": true
"inheritConfigureEnvironment": true,
"execution": {
"stopOnFailure": false
}
},
{
"name": "wasm-threads",
"configurePreset": "wasm-threads",
"inheritConfigureEnvironment": true,
"execution": {
"stopOnFailure": false
}
},
{
"name": "wasm-threads-dbg",
"configurePreset": "wasm-threads-dbg",
"inheritConfigureEnvironment": true,
"execution": {
"stopOnFailure": false
}
}
]
}
50 changes: 50 additions & 0 deletions barretenberg/cpp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# barretenberg / cpp

The C++ implementation of barretenberg's proving system. See
[`../README.md`](../README.md) for an overview of supported targets and
[`./CLAUDE.md`](./CLAUDE.md) for development conventions.

## WASM build (Emscripten)

The wasm presets target Emscripten + Node. The exact emsdk version is pinned
in [`../../.emsdk-version`](../../.emsdk-version) at the repo root; install
and activate it before configuring:

```bash
git clone https://github.com/emscripten-core/emsdk.git ~/emsdk
cd ~/emsdk
./emsdk install $(cat /path/to/repo/.emsdk-version)
./emsdk activate $(cat /path/to/repo/.emsdk-version)
source ./emsdk_env.sh
```

You also need **Node.js >= 22**.

Configure and build:

```bash
cmake --preset wasm-threads
cmake --build --preset wasm-threads --target bb
```

Each Emscripten executable lands in `build-wasm-threads/bin/` as a `.js`
loader plus a sibling `.wasm`. Tests are launched via the wrapper at
[`scripts/wasm-run`](./scripts/wasm-run), which forwards to Node with the
right flags (NODERAWFS, threading, memory budget, etc.):

```bash
cmake --build --preset wasm-threads --target ecc_tests
./scripts/wasm-run --dir=. ./build-wasm-threads/bin/ecc_tests

# or, equivalently, via the CMake-generated custom target:
cmake --build --preset wasm-threads --target run_ecc_tests
```

`wasm-run` accepts `--dir=PATH` (repeatable) for filesystem allowlisting.
The wasm module's `INITIAL_MEMORY` is a link-time constant baked into the
binary by the toolchain (`cmake/toolchains/wasm-emscripten.cmake`); to
change it, edit the toolchain and rebuild. The `--mem=BYTES` flag is
informational only -- it surfaces a requested budget via
`BB_WASM_INITIAL_MEMORY` for any caller that wants to read it, but the
loader does not honor a runtime override under `MODULARIZE=1`. See
`./scripts/wasm-run --help` for the full CLI.
18 changes: 16 additions & 2 deletions barretenberg/cpp/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ function preset_cache_paths {
find $build_dir/bin $build_dir/lib \
-maxdepth 1 \( -name "$t" -o -name "$t.exe" -o -name "$t.node" -o -name "lib${t}.a" \) \
2>/dev/null
# Emscripten emits a .js loader and a .worker.mjs pthread worker as
# side-outputs of any .wasm executable target; cache them next to the
# .wasm so consumers like bb.js see a complete artifact set on cache hit.
if [[ "$t" == *.wasm ]]; then
local stem="${t%.wasm}"
find $build_dir/bin -maxdepth 1 \
\( -name "$stem.js" -o -name "$stem.worker.mjs" \) \
2>/dev/null
fi
done
fi
}
Expand Down Expand Up @@ -269,8 +278,13 @@ function test_cmds_native {
}

function test_cmds_wasm_threads {
# We only want to sanity check that we haven't broken wasm ecc in merge queue.
echo "$hash barretenberg/cpp/scripts/wasmtime.sh barretenberg/cpp/build-wasm-threads/bin/ecc_tests"
# Sanity-check the canonical wasm path didn't regress.
echo "$hash barretenberg/cpp/scripts/wasm-run barretenberg/cpp/build-wasm-threads/bin/ecc_tests"
# Run the regression suite added by the Emscripten migration: pthread pool
# exhaustion + memory.grow under threads. Without this line, a developer
# invoking `./bootstrap.sh test wasm_threads` would not exercise the new
# tests and the bug class is only caught in the dedicated CI workflow.
echo "$hash barretenberg/cpp/scripts/wasm-run barretenberg/cpp/build-wasm-threads/bin/wasm_threads_tests_tests"
}

function test_cmds_asan {
Expand Down
3 changes: 2 additions & 1 deletion barretenberg/cpp/cmake/arch.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ if(WASM)
# Disable SLP vectorization on WASM as it's brokenly slow. To give an idea, with this off it still takes
# 2m:18s to compile scalar_multiplication.cpp, and with it on I estimate it's 50-100 times longer. I never
# had the patience to wait it out...
add_compile_options(-fno-exceptions -fno-slp-vectorize)
# Exception handling is owned by toolchains/wasm-emscripten.cmake (WASM_EXCEPTIONS option).
add_compile_options(-fno-slp-vectorize)
endif()

# Target skylake on x86 for AVX2 etc. ARM is handled by the zig wrapper scripts
Expand Down
36 changes: 35 additions & 1 deletion barretenberg/cpp/cmake/module.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,24 @@ function(barretenberg_module_with_sources MODULE_NAME)
add_dependencies(${MODULE_NAME}_tests lmdb_repo)
endif()
if(NOT WASM)
# Currently haven't found a way to easily wrap the calls in wasmtime when run from ctest.
# gtest_discover_tests can't wrap test invocations in wasm-run from ctest, so for
# the wasm preset we expose a `run_<module>_tests` custom target instead and let
# CI / developers invoke it directly.
gtest_discover_tests(${MODULE_NAME}_tests WORKING_DIRECTORY ${CMAKE_BINARY_DIR} TEST_FILTER -*_SKIP_CI* DISCOVERY_TIMEOUT 30)
else()
# Under Emscripten the test binary lands as bin/<module>_tests.js with sibling
# bin/<module>_tests.wasm. wasm-run resolves the .js automatically.
add_custom_target(
run_${MODULE_NAME}_tests
COMMAND ${CMAKE_SOURCE_DIR}/scripts/wasm-run
--dir=$ENV{HOME}/.bb-crs
--dir=${CMAKE_BINARY_DIR}
${CMAKE_BINARY_DIR}/bin/${MODULE_NAME}_tests
DEPENDS ${MODULE_NAME}_tests
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Running ${MODULE_NAME}_tests under wasm-run"
USES_TERMINAL
)
endif()
endif()

Expand Down Expand Up @@ -294,6 +310,24 @@ function(barretenberg_module_with_sources MODULE_NAME)
${TRACY_LIBS}
${TBB_IMPORTED_TARGETS}
)
if(WASM)
# Benchmarks need real filesystem access to read the CRS.
# NODERAWFS=1 is a link-time setting (the env var of the
# same name set by wasm-run is a no-op without it) that
# routes Emscripten file ops to Node's native fs. Pair it
# with PROXY_TO_PTHREAD=0 because NODERAWFS only services
# the Node main thread; under PROXY_TO_PTHREAD wasm `main`
# runs on a worker and every file op silently returns zero
# (see Emscripten #19330). parallel_for can still spawn its
# own workers for multithreaded proving.
target_link_options(
${BENCHMARK_NAME}_bench
PRIVATE
"SHELL:-sNODERAWFS=1"
"SHELL:-sPROXY_TO_PTHREAD=0"
"SHELL:-sALLOW_BLOCKING_ON_MAIN_THREAD=1"
)
endif()
if(ENABLE_STACKTRACES)
target_link_libraries(
${BENCHMARK_NAME}_bench_objects
Expand Down
13 changes: 11 additions & 2 deletions barretenberg/cpp/cmake/threading.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ if(MULTITHREADING)
add_compile_options(-pthread)
add_link_options(-pthread)
if(WASM)
add_compile_options(--target=wasm32-wasi-threads)
add_link_options(--target=wasm32-wasi-threads -Wl,--shared-memory)
# Emscripten + pthreads. SHARED_MEMORY is implied by -pthread, but
# passing it explicitly makes the target memory shape obvious to
# readers of CMakeCache.txt.
add_link_options("SHELL:-sSHARED_MEMORY=1")
# Prevent indirect call type mismatch errors in thread_local destructors
# (without this the benchmark flow fails at destruction point for WASM)
add_compile_options(-fno-c++-static-destructors)
Expand All @@ -15,6 +17,13 @@ else()
message(STATUS "Multithreading is disabled.")
add_definitions(-DNO_MULTITHREADING)
set(OMP_MULTITHREADING OFF)
if(WASM)
# Single-threaded wasm: bake out the pthread pool entirely. The
# toolchain enables -pthread by default; we override here.
add_compile_options(-pthread)
add_link_options(-pthread)
add_link_options("SHELL:-sPTHREAD_POOL_SIZE=0")
endif()
endif()

if(OMP_MULTITHREADING)
Expand Down
Loading
Loading