Skip to content

Support single-threaded WASI: guard std::mutex behind __wasi__ - #5

Merged
AKiranB merged 2 commits into
mainfrom
wasm-build-support
Sep 23, 2026
Merged

AKiranB merged 2 commits into
mainfrom
wasm-build-support

Conversation

@AKiranB

@AKiranB AKiranB commented Jul 29, 2026

Copy link
Copy Markdown

libc++ in the wasm32-wasip1 SDK is built without threads, so std::mutex is unavailable. Gate
the no-thread-branch StrictKey mutex (tvgLock.h) and the SwRenderer global mutex behind wasi so the SW engine compiles for wasm.

libc++ in the wasm32-wasip1 SDK is built without threads, so std::mutex is unavailable. Gate
  the no-thread-branch StrictKey mutex (tvgLock.h) and the SwRenderer global mutex behind __wasi__ so the SW engine compiles for wasm.
@michaelknoch

Copy link
Copy Markdown
Member

Approach looks right and the premise holds: wasm32-wasip1 is single-threaded (no pthreads, libc++ in the SDK is built without threads, so std::mutex is guarded out). Dropping the locks there is semantically correct, not just a compile fix — StrictKey has exactly one user (src/renderer/cpu_engine/tvgSwMemPool.cpp:32), our SwiftPM build doesn't define THORVG_THREAD_SUPPORT so the patched branch is the one we compile, and TaskScheduler already has a no-thread branch, so the wasm build never spawns anything.

Two things I'd change:

1. __wasi__ is the wrong predicate. It's also defined for wasm32-wasip1-threads, where std::mutex exists and the locks are needed — there the locks would silently disappear instead of failing to build. __STDCPP_THREADS__ is the precise check (standard C++11 macro, set by the compiler exactly when the implementation supports more than one thread of execution, which is the same condition libc++ uses to guard std::mutex). Verified with clang:

wasm32-unknown-wasi            -> __wasi__            (no __STDCPP_THREADS__)
wasm32-unknown-wasip1-threads  -> __wasi__, __STDCPP_THREADS__
x86_64-apple-macosx            -> __STDCPP_THREADS__

So:

#ifdef __STDCPP_THREADS__
    std::mutex mtx;
#endif

Not wasm-specific, so it also covers any other freestanding/no-thread target, and it self-corrects if we ever move to the threads triple. (libc++'s _LIBCPP_HAS_NO_THREADS works too but it's internal and was renamed to _LIBCPP_HAS_THREADS in LLVM 19, so you'd need both spellings.)

2. Two different mechanisms for the same problem. tvgSwRenderer.cpp gets a no-op lockable, tvgLock.h gets #ifdefs around every call site. Pick one — the no-op lockable is cleaner: give StrictKey a NoopMutex mtx in the no-threads case and leave ScopedLock untouched. That also avoids the dead key = &k assignment.

Minor: the 3-line comment in tvgSwRenderer.cpp could be one line. #include <mutex> staying unconditional in tvgLock.h is fine — the header exists on wasi, only the class is guarded out.

For the record on which target this affects: we build plain single-threaded wasip1 in both places — web-app/bundler/swiftWasmPlugin.js uses the swift-6.3-RELEASE_wasm SDK, and SwiftWasmDockerfile installs swift-wasm-6.0.2-RELEASE-wasm32-unknown-wasi. NativePlayer is shared between web-app and mobile-app with the same .when(platforms: [.wasi]) conditions. ThorVG is currently linked only for .android and .macOS there, so adding .wasi to that condition is presumably the follow-up this unblocks.

@AKiranB
AKiranB merged commit 5c60c25 into main Sep 23, 2026
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.

2 participants