#!/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[@]}"