#!/usr/bin/env bash # wasmc++-glibc: the glibc-arm twin of wasmc++ — the REAL C++ cross driver # for wasm32-hwjs against the wasm32 GLIBC port (PLAN-CXX-STATUS-CHANNEL # Track R §6 item 5; closes the "wasm32-hwjs-linux-g++ is a symlink to the # C driver wasmcc-glibc" gap from the plan §0). # # Everything structural is documented in libexec/brintc++-musl (read that header); # this twin differs exactly where wasmcc-glibc differs from wasmcc: # - COMPILE triple wasm32-hwjs-linux-gnu, glibc sysroot headers + kernel # UAPI headers; # - PIC model (the glibc arm has always built PIC — see the PIC MODEL # note in libexec/brintcc-glibc); # - the staged GLIBC-arm libc++/libc++abi (built against the glibc # sysroot by the llvm-project glibc runtimes phase; staged by # the sysroot build's `make cxx-runtimes` into the glibc sysroot); # - LINK delegates to wasmcc-glibc (glibc pool, crt1_glibc_hwjs, native # .S archive, provenance — inherited verbatim). wasmcc-glibc defaults # WASMLD_DYNAMIC_MAIN=1; export WASMLD_DYNAMIC_MAIN=0 for a # self-contained static image, exactly as with the C driver. # # Exceptions ON always (plan N1; -fno-exceptions refused), check-emitting # by default (HWJS_EH_PRELOWER=1; explicit 0 refused without the RED-knob # acknowledgment) — same refusals, same wording rationale as wasmc++. set -euo pipefail HWJS_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # shellcheck source=libexec/find-llvm.sh source "$HWJS_HOME/libexec/find-llvm.sh" # The sysroot tree, for the reason written out in libexec/brintcc-glibc: the # default that was here named a directory of another repository. TOOLS_DIR="${HWJS_TOOLS_DIR:-${BRINTIUM_SYSROOT:-$HWJS_HOME/sysroot}}" HWJS_LLVM_BIN="$(hwjs_llvm_bin "$HWJS_HOME/libexec")" \ || { echo "wasmc++-glibc: FATAL: pinned LLVM not found (set WASMLD_HWJS_LLVM_BIN, or build it per toolchain/llvm-from-source.md)." >&2; exit 1; } CLANGXX="$HWJS_LLVM_BIN/clang++" WASMCC_GLIBC="${WASMCXX_CC_DRIVER:-$HWJS_HOME/libexec/brintcc-glibc}" # ── glibc sysroot (same env surface + defaults as wasmcc-glibc) ───────────── GLIBC_BUILD="${GLIBC_BUILD:-$TOOLS_DIR/build/glibc-2.43-build}" GLIBC_SYSINC="${GLIBC_SYSINC:-$GLIBC_BUILD/sysroot/usr/include}" # No default, for the reason written out at the same variable in # libexec/brintcc-glibc: the one that used to be here named a directory in # another repository, clang ignores an -isystem that is not there, and a # guessed kernel is the wrong kernel. bin/brintc++ refuses before this runs; # this covers a direct call. GLIBC_UAPI_INC="${GLIBC_UAPI_INC:-${BRINTIUM_KERNEL_HEADERS:-}}" [ -n "$GLIBC_UAPI_INC" ] || { echo "wasmc++-glibc: FATAL: neither BRINTIUM_KERNEL_HEADERS nor GLIBC_UAPI_INC is set, and this arm compiles against the kernel UAPI headers (linux/*.h, asm/*.h) on every line. They are installed by the kernel build, not by this toolchain. Set BRINTIUM_KERNEL_HEADERS to the installed UAPI include directory — the one holding linux/ and asm/." >&2 exit 1 } [ -d "$GLIBC_UAPI_INC" ] || { echo "wasmc++-glibc: FATAL: the kernel UAPI header directory '$GLIBC_UAPI_INC' does not exist. It must be the kernel build's installed UAPI include directory (the one holding linux/ and asm/)." >&2 exit 1 } # ── the staged C++ runtimes (glibc arm) ───────────────────────────────────── LIBCXX_INC="${WACKY_LIBCXX_INC:-$GLIBC_BUILD/sysroot/usr/include/c++/v1}" LIBCXX_LIBDIR="${WACKY_LIBCXX_LIBDIR:-$GLIBC_BUILD/sysroot/usr/lib}" # ── refusals (loud; wording rationale in libexec/brintc++-musl) ───────────── for a in "$@"; do case "$a" in -fno-exceptions) echo "wasmc++-glibc: FATAL: -fno-exceptions on a C++ line — exceptions are ON on wasm32-hwjs (they ride the status channel; PLAN-CXX-STATUS-CHANNEL N1). A -fno-exceptions frame between a raise and its handler would skip its cleanups SILENTLY (the I3 silent-transit shape). If a package only builds with exceptions off, that is a finding to report, not a flag to pass." >&2 exit 1 ;; esac done case "${HWJS_EH_PRELOWER:-}" in '') export HWJS_EH_PRELOWER=1 ;; 1) ;; 0) if [[ "${WASMLD_HWJS_ALLOW_RED_KNOBS:-0}" != "1" ]]; then echo "wasmc++-glibc: FATAL: HWJS_EH_PRELOWER=0 on a C++ line — without the EH pre-lowering the ported __cxa_throw raises on the status channel and RETURNS into code that never checks it (the demonstrated runtime-RED shape). C++ through this driver is check-emitting by default (I3). Set WASMLD_HWJS_ALLOW_RED_KNOBS=1 only for a deliberate mechanism-off RED arm." >&2 exit 1 fi echo "wasmc++-glibc: NOTE (HWJS_EH_PRELOWER=0, RED arm ACKNOWLEDGED): building WITHOUT the EH pre-lowering — the mechanism-off twin. Non-production." >&2 ;; *) echo "wasmc++-glibc: FATAL: HWJS_EH_PRELOWER='${HWJS_EH_PRELOWER}' — this knob is VALUE-keyed (1 = on, 0 = RED arm, unset/empty = default 1); refusing to guess." >&2 exit 1 ;; esac # ── triples + flag sets (wasmcc-glibc parity; PIC, no -fno-pic) ───────────── COMPILE_TRIPLE="wasm32-hwjs-linux-gnu" COMPILE_TARGET="--target=$COMPILE_TRIPLE" export WASMLD_HWJS_COMPILE_TARGET="${WASMLD_HWJS_COMPILE_TARGET:-$COMPILE_TRIPLE}" COMMON_ARGS=( -nostdlibinc -fno-builtin -fexceptions -frtti -fvisibility=default -matomics -mbulk-memory -mmutable-globals -msign-ext -mnontrapping-fptoint -ffunction-sections -fdata-sections ) CXX_ISYSTEM_ARGS=( -nostdinc++ ) [[ -d "$LIBCXX_INC" ]] || { echo "wasmc++-glibc: FATAL: no staged libc++ headers at $LIBCXX_INC — run 'make -C sysroot cxx-runtimes', or set WACKY_LIBCXX_INC." >&2 exit 1 } CXX_ISYSTEM_ARGS+=( -isystem "$LIBCXX_INC" -isystem "$GLIBC_SYSINC" ) [[ -d "$GLIBC_UAPI_INC" ]] && CXX_ISYSTEM_ARGS+=( -isystem "$GLIBC_UAPI_INC" ) # ── mode detection (mirror wasmcc-glibc) ──────────────────────────────────── is_link_mode=1 for a in "$@"; do case "$a" in -c|-E|-S|-M|-MM|-fsyntax-only|--version|-v|-dumpversion|-dumpmachine|-print-*) is_link_mode=0 ;; esac done for a in "$@"; do case "$a" in -print-multi-directory) echo "."; exit 0;; esac; done if [[ "$is_link_mode" == "0" ]]; then emit=() compile=0 have_o=0 src="" prev="" for a in "$@"; do case "$a" in -c) compile=1; [[ "${WASMLD_HWJS_CONFTEST:-0}" == "0" ]] && emit=( -emit-llvm ) ;; # BRINTOS-WORKAROUND: brintos/hwjs-cc#39 revisit # The attached form must count too: missing it synthesises a second # -o after the caller's args, so clang writes ./.o and the # requested path never appears -- silently, exit 0. waf always emits # -o. -o|-o?*) have_o=1 ;; esac if [[ "$prev" != "-o" ]]; then case "$a" in *.c|*.cc|*.cpp|*.cxx|*.C|*.cp|*.c++) src="$a" ;; esac fi prev="$a" done out=() if [[ "$compile" == "1" && "$have_o" == "0" && -n "$src" ]]; then base="${src##*/}"; base="${base%.*}"; out=( -o "${base}.o" ) fi exec "$CLANGXX" \ "$COMPILE_TARGET" \ "${COMMON_ARGS[@]}" \ "${CXX_ISYSTEM_ARGS[@]}" \ "${emit[@]}" \ "$@" \ "${out[@]}" fi # ── LINK: pre-compile C++ sources, inject matcher + archives, delegate ────── stage="$(mktemp -d "${TMPDIR:-/tmp}/wasmcxx-glibc-stage.XXXXXX")" trap 'rm -rf "$stage"' EXIT cxx_flags=() expect="" for a in "$@"; do if [[ -n "$expect" ]]; then [[ "$expect" == "cc" ]] && cxx_flags+=( "$a" ) expect="" continue fi case "$a" in -o|-L) expect="skip" ;; -I|-D|-U|-include|-imacros|-isystem|-idirafter|-iquote|-Xclang) cxx_flags+=( "$a" ); expect="cc" ;; -o*|-L*|-l*|-Wl,*) : ;; -*) cxx_flags+=( "$a" ) ;; *) : ;; esac done link_args=() nostdlibxx=0 srcn=0 for a in "$@"; do case "$a" in -nostdlib|-nodefaultlibs|-nostdlib++) nostdlibxx=1; link_args+=( "$a" ) ;; -lc++|-lc++abi|-lstdc++) : ;; # this driver owns the C++ runtime archives *.c|*.cc|*.cpp|*.cxx|*.C|*.cp|*.c++) [[ -f "$a" ]] || { echo "wasmc++-glibc: FATAL: link-line source '$a' does not exist." >&2; exit 1; } srcn=$((srcn + 1)) sb="$(basename "$a")" so="$stage/linksrc-${srcn}-${sb%.*}.o" "$CLANGXX" \ "$COMPILE_TARGET" \ "${COMMON_ARGS[@]}" \ "${CXX_ISYSTEM_ARGS[@]}" \ "${cxx_flags[@]}" \ -emit-llvm -c "$a" -o "$so" \ || { echo "wasmc++-glibc: FATAL: single-step C++ compile of link-line source '$a' failed (it must join the merged-module pass pipeline; error above)." >&2; exit 1; } link_args+=( "$so" ) ;; *) link_args+=( "$a" ) ;; esac done CXX_LINK_EXTRAS=() # -fexperimental-library support — glibc twin of wasmc++'s block (tzdb/PSTL/ # syncstream; libc++experimental.a joins whenever staged, need-only pull; # an explicit experimental request with no staged archive refuses HERE). wants_experimental=0 for a in "$@"; do [[ "$a" == "-fexperimental-library" ]] && wants_experimental=1 done if [[ "$nostdlibxx" == "0" && "${WASMLD_HWJS_CONFTEST:-0}" == "0" ]]; then MATCHER="${WACKY_LIBCXX_MATCHER:-$LIBCXX_LIBDIR/cxa_hwjs_status_channel.cpp.o}" for f in "$MATCHER" "$LIBCXX_LIBDIR/libc++.a" "$LIBCXX_LIBDIR/libc++abi.a"; do [[ -f "$f" ]] || { echo "wasmc++-glibc: FATAL: missing staged C++ runtime piece '$f' — run 'make -C sysroot cxx-runtimes'." >&2 exit 1 } done LIBCXX_EXP="$LIBCXX_LIBDIR/libc++experimental.a" if [[ "$wants_experimental" == "1" && ! -f "$LIBCXX_EXP" ]]; then echo "wasmc++-glibc: FATAL: -fexperimental-library requested but no staged libc++experimental.a in $LIBCXX_LIBDIR — rebuild the runtimes (LIBCXX_ENABLE_EXPERIMENTAL_LIBRARY=ON since 2026-07-21) and run 'make -C sysroot cxx-runtimes'." >&2 exit 1 fi CXX_LINK_EXTRAS=( "$MATCHER" ) [[ -f "$LIBCXX_EXP" ]] && CXX_LINK_EXTRAS+=( "$LIBCXX_EXP" ) CXX_LINK_EXTRAS+=( "$LIBCXX_LIBDIR/libc++.a" "$LIBCXX_LIBDIR/libc++abi.a" ) # The glibc arm's merge DCE (WASMLD_HWJS_DCE_MERGE, default ON in # wasmcc-glibc) runs internalize+globaldce BEFORE the plugin compile — # but the references to the matcher TU's definitions # (__hwjs_cxa_type_match; the strong __hwjs_uncaught_unwinding that # must override the RT weak fallback) are created BY the pre-lowering # DURING that compile, so at DCE time they look dead and are deleted; # the pass's references then leak as phantom env imports (witnessed: # the import witness refuses the final image loudly). Same class as # the recorded wcslen/bcmp weak-alias precedent — pin them as DCE # roots, EXTENDING wasmcc-glibc's default root set (never replacing a # caller's explicit list). if [[ "${WASMLD_HWJS_DCE_MERGE:-1}" != "0" ]]; then export WASMLD_HWJS_DCE_EXTRA_ROOTS="${WASMLD_HWJS_DCE_EXTRA_ROOTS:-_Fork,wcslen,bcmp},__hwjs_cxa_type_match,__hwjs_uncaught_unwinding" fi elif [[ "$nostdlibxx" == "0" ]]; then for f in "$LIBCXX_LIBDIR/libc++.a" "$LIBCXX_LIBDIR/libc++abi.a"; do [[ -f "$f" ]] && CXX_LINK_EXTRAS+=( "$f" ) done fi exec "$WASMCC_GLIBC" "${link_args[@]}" "${CXX_LINK_EXTRAS[@]}"