Skip to content

Commit 3d5375f

Browse files
committed
playground: embed C/C++ stdlib headers in WASM virtual filesystem
1 parent c62b866 commit 3d5375f

2 files changed

Lines changed: 76 additions & 2 deletions

File tree

.github/workflows/build-playground.yml

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,64 @@ jobs:
5656
restore-keys: |
5757
ccache-wasm-
5858
59-
- name: Configure WASM build (Null-Safe Clang)
59+
- name: Collect headers for WASM virtual filesystem
60+
id: sysroot
6061
run: |
62+
SYSROOT="$(pwd)/playground-sysroot"
63+
mkdir -p "$SYSROOT/include"
64+
65+
# --- Emscripten libc (musl) + libc++ headers ---
66+
# Trigger Emscripten cache population if needed
67+
EMSDK_SYSROOT="$(em-config CACHE)/sysroot/include"
68+
if [ ! -d "$EMSDK_SYSROOT" ]; then
69+
echo "int main(){}" > /tmp/hello.c
70+
emcc /tmp/hello.c -o /tmp/hello.js
71+
EMSDK_SYSROOT="$(em-config CACHE)/sysroot/include"
72+
fi
73+
cp -r "$EMSDK_SYSROOT"/* "$SYSROOT/include/"
74+
75+
# --- Clang resource headers (stddef.h, stdarg.h, etc.) ---
76+
# These are in clang/lib/Headers/ in the source tree.
77+
# Clang looks for them at: <resource-dir>/include/
78+
CLANG_VER=$(grep 'set(LLVM_VERSION_MAJOR' cmake/Modules/LLVMVersion.cmake | grep -oP '[0-9]+')
79+
RESOURCE_DST="${SYSROOT}/lib/clang/${CLANG_VER}/include"
80+
mkdir -p "$RESOURCE_DST"
81+
82+
# Copy essential C/C++ headers only — skip architecture intrinsics
83+
# (avx*, arm*, amx*, etc.) to save ~14MB of embedded space
84+
for pattern in \
85+
'__stddef*' '__stdarg*' '__stdc*' \
86+
'stddef.h' 'stdarg.h' 'stdbool.h' 'stdnoreturn.h' 'stdatomic.h' \
87+
'stdalign.h' 'stdint.h' \
88+
'limits.h' 'float.h' 'inttypes.h' 'iso646.h' \
89+
'__clang_hip_*.h' '__wasm*.h' \
90+
'unwind.h' 'tgmath.h' 'varargs.h'; do
91+
for h in clang/lib/Headers/$pattern; do
92+
[ -f "$h" ] && cp "$h" "$RESOURCE_DST/"
93+
done
94+
done
95+
96+
echo "=== Embedded sysroot ==="
97+
echo "Clang resource headers: $(find "$RESOURCE_DST" -type f | wc -l) files, $(du -sh "$RESOURCE_DST" | cut -f1)"
98+
echo "Libc/libc++ headers: $(find "$SYSROOT/include" -type f | wc -l) files, $(du -sh "$SYSROOT/include" | cut -f1)"
99+
echo "Total: $(du -sh "$SYSROOT" | cut -f1)"
100+
101+
echo "sysroot=$SYSROOT" >> "$GITHUB_OUTPUT"
102+
echo "clang_ver=$CLANG_VER" >> "$GITHUB_OUTPUT"
103+
104+
- name: Configure WASM build
105+
run: |
106+
SYSROOT="${{ steps.sysroot.outputs.sysroot }}"
107+
CLANG_VER="${{ steps.sysroot.outputs.clang_ver }}"
108+
109+
# --embed-file bakes files into the WASM binary's virtual filesystem.
110+
# host_path@virtual_path maps where files go in the VFS:
111+
# /lib/clang/<ver>/include — clang resource headers (-resource-dir)
112+
# /include — C stdlib + C++ stdlib + compat headers (-isystem)
113+
EMBED_FLAGS=""
114+
EMBED_FLAGS+=" --embed-file ${SYSROOT}/lib/clang/${CLANG_VER}/include@/lib/clang/${CLANG_VER}/include"
115+
EMBED_FLAGS+=" --embed-file ${SYSROOT}/include@/include"
116+
61117
mkdir -p build-wasm
62118
cd build-wasm
63119
emcmake cmake -G Ninja \
@@ -76,7 +132,7 @@ jobs:
76132
-DLLVM_ENABLE_LIBXML2=OFF \
77133
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
78134
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
79-
-DCMAKE_EXE_LINKER_FLAGS="-sEXPORTED_RUNTIME_METHODS=callMain -sEXIT_RUNTIME=0 -sALLOW_MEMORY_GROWTH=1" \
135+
-DCMAKE_EXE_LINKER_FLAGS="-sEXPORTED_RUNTIME_METHODS=callMain -sEXIT_RUNTIME=0 -sALLOW_MEMORY_GROWTH=1 ${EMBED_FLAGS}" \
80136
../llvm
81137
82138
- name: Build Null-Safe Clang WASM
@@ -94,6 +150,7 @@ jobs:
94150
gzip -k artifacts/clang-nullsafe.wasm
95151
gzip -k artifacts/clang-nullsafe.js
96152
153+
echo "=== Artifact sizes ==="
97154
ls -lh artifacts/
98155
99156
- name: Upload artifacts
@@ -119,6 +176,9 @@ jobs:
119176
Built from ${{ github.sha }} on ${{ github.ref_name }}.
120177
This release is automatically updated on every push that changes compiler source.
121178
179+
Includes embedded C/C++ standard library headers — `#include <memory>`,
180+
`#include <vector>`, `#include <cassert>`, etc. all work in the playground.
181+
122182
### Files:
123183
- `clang-nullsafe.wasm` - WebAssembly compiler binary
124184
- `clang-nullsafe.js` - Emscripten JavaScript glue code

nullsafe-playground/compiler-worker.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,25 @@ self.onmessage = function(e) {
7979

8080
// Build arguments: callers can override the default base flags
8181
// (e.g. the static analyzer panel uses --analyze instead of -fsyntax-only)
82+
// Find clang resource dir from embedded filesystem.
83+
// Embedded at /lib/clang/<ver>/ — version changes with LLVM releases.
84+
let resourceDir = '/lib/clang/23';
85+
try {
86+
const vers = FS.readdir('/lib/clang').filter(d => d !== '.' && d !== '..');
87+
if (vers.length > 0) resourceDir = `/lib/clang/${vers[0]}`;
88+
} catch (e) {
89+
// Fall back to default
90+
}
91+
8292
const defaultBase = [
8393
'-fsyntax-only',
8494
'--target=wasm32-unknown-emscripten',
8595
'-fflow-sensitive-nullability',
8696
'-fnullability-default=nullable',
97+
'-resource-dir', resourceDir,
98+
'-isystem', '/include/c++/v1',
99+
'-isystem', '/include',
100+
'-isystem', '/include/compat',
87101
];
88102
const args = [
89103
...(baseFlags || defaultBase),

0 commit comments

Comments
 (0)