Skip to content

Add a WebAssembly module - #632

Merged
fontanf merged 1 commit into
masterfrom
wasm-prototype
Oct 3, 2026
Merged

fontanf merged 1 commit into
masterfrom
wasm-prototype

Conversation

@fontanf

@fontanf fontanf commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Motivation

First step towards a website where users can solve their own instances. This PR builds PackingSolver to WebAssembly and adds a minimal JavaScript API. The web worker, the visualization, the input forms and the deployment will come in follow-up PRs.

Build

emcmake cmake -S . -B build_wasm -DCMAKE_BUILD_TYPE=Release -DPACKINGSOLVER_BUILD_WASM=ON
cmake --build build_wasm --target PackingSolver_wasm
node --test wasm/tests/

PACKINGSOLVER_BUILD_WASM requires the Emscripten toolchain. It disables CLP, whose prebuilt archives can't be linked, and builds neither the CLI nor the C++ tests. Every dependency, HiGHS included, compiles with Emscripten unchanged. Only the bindings are compiled as C++17, which embind requires; the libraries stay C++14.

API

const module = await require("./packingsolver.js")();
const result = JSON.parse(module.solve("rectangle", instanceJson, JSON.stringify({
    optimization_mode: "anytime",  // default; or "not-anytime", "not-anytime-deterministic", "not-anytime-sequential"
    time_limit: 10,
})));
// result.output: 'Output::to_json()'; result.certificate: the CSV certificate; or result.error.
  • The instance is read with InstanceBuilder::read(std::istream&) (Read JSON instances from a stream #631).
  • Errors, such as an invalid instance or parameter, are returned as {"error": ...}. Through embind, C++ exceptions would only reach JavaScript as opaque pointers.
  • For now, only the rectangle problem type is available.

Threads

Every optimization mode works, with anytime by default as in the library:

  • Worker pool: the module is built with thread support, and each thread runs in a Web Worker from a pool of 16 started when the module loads.
    • solve blocks the calling thread, which then can't start new workers. If the algorithms needed more threads than the pool has, the thread creation would fail and solve would return an error rather than hang. This path isn't tested: on the instances below, the rectangle solver uses at most 8 threads natively.
    • In a browser, solve must be called from a Web Worker, since the main thread can't block, and the page needs the two headers that enable SharedArrayBuffer.
  • Allocator: the module uses mimalloc. With Emscripten's default allocator, which has a single lock, the multi-threaded modes were up to 7 times slower than natively.

Performance

Node 18, the same JSON instances as the native CLI. The 107-item instance has random items.

Instance, mode WebAssembly Native
README example, not-anytime 1.68 s 1.36 s
README example, not-anytime-deterministic 0.98 s 0.89 s
README example, not-anytime-sequential 2.45 s 1.97 s
107 items, not-anytime 0.91 s 0.54 s
107 items, not-anytime-sequential 18.4 s 16.9 s
107 items, anytime (proven optimal) 0.52 s 0.64 s

Every mode found the same solutions as natively. The module is 4.8 MB of WebAssembly, 1.6 MB gzipped, plus a 97 KB JavaScript loader. It loads in about 0.2 s, including the worker pool.

Testing

node --test wasm/tests/: 7 tests, all passing.

  • The anytime mode, the default, on an instance proven optimal, so that it ends without a time limit.
  • The README example in the three non-anytime modes.
  • Invalid optimization mode, invalid instance, unsupported problem type.

CI doesn't build the module yet; that will come with the deployment workflow.

New 'PACKINGSOLVER_BUILD_WASM' CMake option, to build the libraries with
Emscripten ('emcmake cmake -DPACKINGSOLVER_BUILD_WASM=ON'), with the
'wasm/' module 'packingsolver.js' / 'packingsolver.wasm'. Its JavaScript
API, 'solve(problemType, instanceJson, parametersJson)', reads an instance
in the JSON format, solves it and returns the output and the solution
certificate as a JSON string, or an error.

- Every optimization mode is available, with 'anytime' by default as in
  the library. The module is built with thread support: the threads run
  in a pool of Web Workers started when the module is loaded. 'solve'
  blocks the calling thread, so in a browser it must be called from a Web
  Worker.
- It uses mimalloc: with the default allocator, which has a single lock,
  the multi-threaded modes were up to 7 times slower than natively.
- It uses HiGHS (CLP isn't available) and enables C++ exceptions. Only the
  bindings are compiled as C++17 (required by embind).
- For now, only the 'rectangle' problem type is available.

Tests: 'node --test wasm/tests/'.
@fontanf
fontanf merged commit 34eefba into master Oct 3, 2026
11 checks passed
@fontanf
fontanf deleted the wasm-prototype branch October 3, 2026 19:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant