Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
4bb5fb6
Cursor: Refactor using best practices so that the build can create wa…
tameware Jun 1, 2026
d0928c3
Cursor: get rid of the "--config=wasm"
tameware Jun 1, 2026
7348cd6
Restore or update some comments that Cursor had decided were unnecess…
tameware Jun 1, 2026
1fb0e37
Initial commit - tries to connect to a backend that no longer exists.
tameware Jun 1, 2026
bc917ec
Added a wasm target for calc_dd_table_pbn
tameware Jun 1, 2026
6a41569
Added a script to move wasm code into dds_mvp_wasm_bin.js to avoid CO…
tameware Jun 1, 2026
8d507de
Fixed stray text.
tameware Jun 1, 2026
573de85
Eliminate the Windows unreachable-code warning (treated as error)
tameware Jun 2, 2026
d7e3629
Use platform constraints instead of deprecated cpu matching:
tameware Jun 2, 2026
5726a9d
build_windows now also requires cpu = x64_windows
tameware Jun 2, 2026
fb98480
Removed remaining global source of MSVC flags.
tameware Jun 2, 2026
166868b
Didn’t set copts, so it fell back to MSVC’s default language mode (to…
tameware Jun 2, 2026
66e9583
Fixed missing include issue exposed by Emscripten/libc++.
tameware Jun 2, 2026
6f34403
std::jthread isn’t available in the Emscripten libc++ used on the Win…
tameware Jun 2, 2026
3c0deea
West/East/South inputs now labeled with the correct direction.
tameware Jun 2, 2026
417bf43
Ignore files that will be generated by update_wasm.sh
tameware Jun 2, 2026
9d75ee5
Added recovery for failed initialization:
tameware Jun 2, 2026
3b8fa7c
Removed unnecessary ensure_initialized(). Rely on DDS's default initi…
tameware Jun 2, 2026
4360e1d
Replaced std::strcpy with std::memcpy to avoid static-analyzer warnings.
tameware Jun 2, 2026
c960116
Replaced "..." with … for proper rendering.
tameware Jun 2, 2026
cf07c52
Data structure tweeks suggested by Copilot.
tameware Jun 2, 2026
6fa7a81
Attenpt to deal with possible changes due to Emscripten version updates.
tameware Jun 2, 2026
4946a43
Added argv checks to all three scripts. Each now prints a clear usage…
tameware Jun 2, 2026
9997a40
Removed #ifdef guard on fallback code, to deal with Copilot unreachab…
tameware Jun 2, 2026
9919ae5
Added unit tests.
tameware Jun 2, 2026
5a2bf95
Added system tests for the web component.
tameware Jun 2, 2026
bcb8d19
Added end-to-end tests for web.
tameware Jun 2, 2026
31796e1
Better fix for Windows unreachable code warning.
tameware Jun 2, 2026
7f0c276
Change the table order to North, East, South, West.
tameware Jun 2, 2026
8571a45
Added a wrapper script to clean up the wasm build artifacts.
tameware Jun 2, 2026
18afc69
Include hashes for playright and its dependencies.
tameware Jun 2, 2026
7cc1985
Removed redundant conditional check
tameware Jun 3, 2026
0c253cc
Merge branch 'web' of https://github.com/tameware/dds into web
tameware Jun 3, 2026
0f62d34
Removed extra #endif
tameware Jun 3, 2026
7edff05
Don't write to /tmp - not portable.
tameware Jun 3, 2026
50f6863
Fix typo in comment.
tameware Jun 3, 2026
dfd41b1
Updated brace style for consistency.
tameware Jun 3, 2026
44b59a7
Updated the sendJSON catch block so it no longer assumes an Error:
tameware Jun 3, 2026
7653b58
Aligned the lock file with Bazel’s Python toolchain by regenerating w…
tameware Jun 3, 2026
a6345ce
examples/wasm/calc_dd_table_pbn_test.cpp only gated SetMaxThreads(0) …
tameware Jun 3, 2026
08342f1
Replace Assert with if and raise
tameware Jun 3, 2026
163cb69
DdsMvpHtmlE2eTest was using 2-space indentation for the class and its…
tameware Jun 3, 2026
80fb1e9
Updated so thread creation is exception-safe. If emplace_back throws …
tameware Jun 3, 2026
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
13 changes: 2 additions & 11 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ build:macos --cxxopt=-std=c++20
build:macos --host_cxxopt=-std=c++20
build:linux --cxxopt=-std=c++20
build:linux --host_cxxopt=-std=c++20
build:windows --cxxopt=/std:c++20
build:windows --host_cxxopt=/std:c++20
# Keep Windows C++ standard selection in CPPVARIABLES.bzl selects.
# Avoid global /std flags here so wasm transitions on Windows don't inherit MSVC opts.

# Performance optimization flags
build:opt --compilation_mode=opt
Expand Down Expand Up @@ -45,12 +45,3 @@ build:tsan --linkopt=-fsanitize=thread
build:tsan --strip=never
build:tsan --features=dbg
build:tsan --compilation_mode=dbg

# WebAssembly (WASM) configuration
# Usage: bazel build --config=wasm //...
# Compiles using the hermetic emsdk toolchain downloaded automatically by Bazel.
build:wasm --platforms=@emsdk//:platform_wasm
build:wasm --cpu=wasm
build:wasm --cxxopt=-std=c++20
build:wasm --host_cxxopt=-std=c++20
build:wasm --compilation_mode=opt
9 changes: 6 additions & 3 deletions .github/workflows/ci_wasm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ jobs:
with:
bazelisk-version: "1.29.0"

# 3️⃣ Build WASM targets (hermetic emsdk toolchain downloaded by Bazel)
# 3️⃣ Unit tests and WASM builds (hermetic emsdk toolchain downloaded by Bazel)
- name: Test web helpers, system tests, e2e, and CalcDDtablePBN
run: bazel test --verbose_failures //web:web_tests //web:web_system_tests //examples/wasm:all

- name: Build WASM targets
run: bazel build --config=wasm --verbose_failures //examples:all_examples_wasm
run: bazel build --verbose_failures //examples/wasm:all_examples_wasm

# 4️⃣ Smoke test: run solve_board under Node.js — pass if it does not crash
- name: Smoke test solve_board_wasm
run: node bazel-bin/examples/solve_board.js
run: node bazel-bin/examples/wasm/solve_board.js
11 changes: 8 additions & 3 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ config_setting(
name = "build_windows",
constraint_values = [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
)

config_setting(
name = "debug_build_windows",
constraint_values = [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
values = {"compilation_mode": "dbg"},
)
Expand Down Expand Up @@ -72,11 +74,14 @@ config_setting(
define_values = {"asan": "true"},
)

# WebAssembly (WASM) build configuration
# Usage: bazel build --config=wasm //...
# Matches cc_* targets built under the wasm_cc_binary platform transition.
# Use platform constraints (not deprecated `cpu` select) so this works
# consistently across host OSes, including Windows.
config_setting(
name = "build_wasm",
values = {"cpu": "wasm"},
constraint_values = [
"@platforms//cpu:wasm32",
],
)


Expand Down
10 changes: 10 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ bazel_dep(name = "googletest", version = "1.17.0.bcr.2")
bazel_dep(name = "pybind11_bazel", version = "3.0.1")
bazel_dep(name = "rules_python", version = "2.0.1")
bazel_dep(name = "toolchains_llvm", version = "1.7.0")
# WASM builds (//examples/wasm:*, //web:dds_mvp_wasm). If you bump this version,
# rebuild and run web/update_wasm.sh; see docs/wasm_build.md and patch_mvp_wasm.py.
bazel_dep(name = "emsdk", version = "5.0.7")

llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm")
Expand All @@ -30,5 +32,13 @@ python.toolchain(
)
use_repo(python, "python_3_14")

pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
hub_name = "pypi",
python_version = "3.14",
requirements_lock = "//web:requirements_lock.txt",
)
Comment thread
tameware marked this conversation as resolved.
use_repo(pip, "pypi")

register_toolchains("@llvm_toolchain//:all")
register_toolchains("@emsdk//:all")
497 changes: 496 additions & 1 deletion MODULE.bazel.lock

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions clean.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Run Bazel clean, then remove web/ WASM copies (not part of bazel-out).
# Usage: ./clean.sh [--expunge] [other bazel clean flags...]
set -euo pipefail
cd "$(dirname "$0")"
if command -v bazelisk >/dev/null 2>&1; then
bazelisk clean "$@"
else
bazel clean "$@"
fi
./web/clean_wasm.sh
166 changes: 92 additions & 74 deletions docs/wasm_build.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# WebAssembly (WASM) Build Guide

This document explains how to build the DDS library and examples for WebAssembly using Bazel.
This document explains how to build DDS examples for WebAssembly using Bazel.

## Prerequisites

Expand All @@ -9,128 +9,146 @@ This document explains how to build the DDS library and examples for WebAssembly
Bazel 7.x or later is required. Install using your package manager or download from:
https://bazel.build/install

The Emscripten SDK (emsdk) does NOT need to be manually installed. Bazel will automatically download, configure, and cache the appropriate hermetic Emscripten toolchain for your host platform as part of the build process.
The Emscripten SDK (emsdk) does NOT need to be manually installed. Bazel downloads and caches a hermetic Emscripten toolchain when you build a `wasm_cc_binary` target.

## Building WASM Examples
### Emscripten / emsdk version

### Build All Examples
WASM builds use the Bazel Central Registry package pinned in `MODULE.bazel`:

```bash
cd /workspaces/dds
bazel build //examples:all_examples_wasm
```
- `bazel_dep(name = "emsdk", version = "5.0.7")` (Emscripten 3.1.x toolchain)

### Build Specific Example
If you upgrade `emsdk`, rebuild WASM targets and the web MVP (`./web/update_wasm.sh`). The post-build script `web/patch_mvp_wasm.py` patches one generated `isFileURI` helper for browser/file URL safety; if Emscripten changes that line, update the regex in that script and this note.

## Building WASM Examples

WASM targets use `wasm_cc_binary`, which applies an Emscripten **platform transition** to the underlying `cc_binary`. You do **not** need `--config=wasm` or any other `.bazelrc` profile.

### Build all WASM examples

```bash
bazel build //examples:solve_board_wasm
bazel build //examples/wasm:all_examples_wasm
```

### Output Files
The alias `//examples:all_examples_wasm` points at the same filegroup.

the output files will be located in:
```
bazel-bin/examples/
```
### Build a specific example

Depending on the target, you'll get:
- `target.js` - JavaScript bindings
- `target.wasm` - WebAssembly binary
```bash
bazel build //examples/wasm:solve_board_wasm
```

### Output files

## Available WASM Targets
Outputs are under `bazel-bin/examples/wasm/`:

The following example targets are available for WASM builds:
- `solve_board.js` / `solve_board.wasm`
- `AnalysePlayBin.js` / `AnalysePlayBin.wasm`
- `calc_dd_table_pbn.js` / `calc_dd_table_pbn.wasm`

### Implemented Examples
- `solve_board` - Solves a single board (produces .js and .wasm)
## Available WASM targets

- `analyse_play_bin` - Analyze play from binary format
Rules in `examples/wasm/BUILD.bazel` wrap native examples in `examples/`:

- `solve_board_wasm` — solves a single board
- `analyse_play_bin_wasm` — analyze play from binary format
- `calc_dd_table_pbn_wasm` — double-dummy table from PBN

## WASM Build Configuration
## How it works

The WASM build is configured through:
1. **`wasm_cc_binary`** (from `@emsdk`) transitions its `cc_target` to `@emsdk//:platform_wasm` and sets `--cpu=wasm`.
2. **`//:build_wasm`** in the root `BUILD.bazel` matches `@platforms//cpu:wasm32` for `select()` in `CPPVARIABLES.bzl` and example link flags.
3. **`wasm_compat.bzl`** — shared Emscripten link flags (`WASM_LINKOPTS`) on the WASM-capable `cc_binary` targets.

1. **BUILD.bazel** - Defines the WASM config_setting:
```python
config_setting(
name = "build_wasm",
values = {"cpu": "wasm"},
)
```
Native builds (`bazel build //...`, `bazel test //library/tests/...`, Python bindings) are unchanged and use the host LLVM toolchain.

2. **CPPVARIABLES.bzl** - Specifies WASM-specific compiler flags:
- Optimization: `-O3 -flto`
- Exceptions: `-fexceptions`
- Define: `-D__WASM__`
## Running WASM examples

3. **.bazelrc** - Contains the `wasm` profile:
```
build:wasm --platforms=@emsdk//:platform_wasm
build:wasm --cpu=wasm
build:wasm --cxxopt=-std=c++20
build:wasm --host_cxxopt=-std=c++20
build:wasm --compilation_mode=opt
```
### Node.js

## Running WASM Examples
```bash
node bazel-bin/examples/wasm/solve_board.js
```

After building, you can run the examples:
### Web browser (DDS MVP)

### Node.js (requires Node.js installed)
The `web/` demo calls `CalcDDtablePBN` in the browser via `//web:dds_mvp_wasm`:

```bash
node bazel-bin/examples/solve_board.js
./web/update_wasm.sh
python3 -m http.server 8080 --directory web
# open http://localhost:8080/dds_mvp.html
```

### Web Browser (TODO)
The MVP loads wasm from `dds_mvp_wasm_bin.js` (base64, no network fetch), so `file://` and HTTP both work. Run `./web/update_wasm.sh` to refresh `dds_mvp_wasm.{js,wasm,bin.js}` (includes a small post-process step for Emscripten `isFileURI`; see **Emscripten / emsdk version** above).

For HTML targets, open the generated HTML file in a web browser:
```bash
# Copy the output files to a web-accessible location
cp bazel-bin/examples/solve_board.* /path/to/webserver/
`bazel clean` does not delete those copied files under `web/` (they live outside `bazel-out`). Use either:

# Then open in browser at http://localhost:8000/solve_board.html
```bash
./clean.sh # bazel clean + remove web/dds_mvp_wasm.*
./clean.sh --expunge
./web/clean_wasm.sh # web artifacts only
```

## Compilation Flags
For other experiments, copy built `.js` / `.wasm` files from `bazel-bin/examples/wasm/` to any static file server.

The WASM build uses the following key flags:
## Compilation flags

| Flag | Purpose |
|------|---------|
| `-O3` | Aggressive optimization |
| `-flto` | Link-time optimization |
| `-fexceptions` | Enable C++ exceptions |
| `-D__WASM__` | Defines `__WASM__` preprocessor constant |
| `-D__WASM__` | Preprocessor constant for WASM builds |
| `-sWASM=1` | Emscripten WASM output (link flag) |
| `-sALLOW_MEMORY_GROWTH=1` | Allow heap growth at runtime |
| `-sINITIAL_MEMORY=268435456` | Configure 256MB initial memory |
| `-sINITIAL_MEMORY=268435456` | 256MB initial memory |
| `-sSTACK_SIZE=8388608` | 8MB stack (default 64KB is too small for DDS search) |

## C++ standard

WASM example binaries are built as C++20. The Emscripten toolchain (via `wasm_cc_binary`) compiles the transitioned `cc_target`; project flags for that configuration come from `CPPVARIABLES.bzl` when `//:build_wasm` matches (the Emscripten platform transition sets `wasm32`).

## C++ Standard
Host-side Bazel actions still use the normal platform flags in `.bazelrc`:

The WASM build uses C++20 as specified in `.bazelrc`:
```
build:wasm --cxxopt=-std=c++20
build:macos --cxxopt=-std=c++20
build:linux --cxxopt=-std=c++20
```

## Related Documentation
There is no separate `build:wasm` profile in `.bazelrc`; WASM builds are selected by targeting `//examples/wasm:*`.

- [Emscripten Documentation](https://emscripten.org/docs/)
- [Bazel Build System](https://bazel.build/docs)
- [DDS C++ API](c++_interface.md)
- [Build System Overview](BUILD_SYSTEM.md)
## Tests

Unit and system tests (Node.js required for system tests; skipped if `node` is not on `PATH`):

```bash
bazel test //web:web_tests //web:web_system_tests //web:web_e2e_tests
bazel test //examples/wasm:all
```

- **`//web:dds_mvp_wasm_system_test`** — builds `//web:dds_mvp_wasm`, runs `patch_mvp_wasm` / `gen_wasm_bin_js` / `verify_wasm_js`, then calls `dds_mvp_calc_table` via Node (`web/tests/dds_mvp_wasm_node.mjs`).
- **`//web:dds_mvp_e2e_test`** — Playwright tests for `dds_mvp.html` over `file://` and HTTP (part-score deal table, validation error). Requires Node, network (Chromium download on first run), and `tags = ["no-sandbox"]`.
- **`//examples/wasm:wasm_examples_system_test`** — runs `calc_dd_table_pbn.js` under Node and checks for `OK` on all three example hands.

## Next Steps
The MVP link flags include `-sENVIRONMENT=web,node` so the same `.js` / `.wasm` artifacts work in the browser and in Node system tests.

## Development notes

- The `__WASM__` preprocessor constant is defined for WASM builds (`CPPVARIABLES.bzl`). It was added to work around platform-specific code paths; revisit whether it can be narrowed or removed as WASM support matures.
- Some threading and platform-specific features are disabled or stubbed when `__WASM__` is set.
- A reusable `cc_library` WASM artifact (not only example binaries) is not yet provided; today only `wasm_cc_binary` example targets are wired up.
- The browser MVP lives under `web/`; see **Web browser (DDS MVP)** above and `//web:web_system_tests`.

## Next steps

To integrate WASM builds into CI/CD:
1. See `.github/workflows/ci_linux.yml` and `.github/workflows/ci_macos.yml`
2. Store WASM artifacts for download/release

## Development Notes
1. See `.github/workflows/ci_wasm.yml`
2. Store WASM artifacts (`*.js`, `*.wasm`) for download or release as needed

## Related documentation

- The `__WASM__` preprocessor constant is added initially to bypass error, need to check again whether we can remove
- Some threading and platform-specific features are disabled for WASM
- The build flow for a reusable library wasm
- A good HTML example
- [Emscripten Documentation](https://emscripten.org/docs/)
- [Bazel Build System](https://bazel.build/docs)
- [DDS C++ API](c++_interface.md)
- [Build System Overview](BUILD_SYSTEM.md)
Loading
Loading