2025-12-14 11:59:50 +01:00
|
|
|
#!/bin/bash
|
2026-06-29 19:50:18 +02:00
|
|
|
# Unified post-link Asyncify pass for KiCad AND the wx test apps.
|
2025-12-14 11:59:50 +01:00
|
|
|
#
|
2026-06-29 19:50:18 +02:00
|
|
|
# Usage: apply-asyncify.sh [--no-removelist] <input.wasm> [output.wasm]
|
2025-12-14 11:59:50 +01:00
|
|
|
#
|
2026-06-29 19:50:18 +02:00
|
|
|
# Always: run our --hoist-cpp-catches fork pass FIRST (lets Asyncify suspend from inside C++ catch
|
|
|
|
|
# blocks under native wasm-EH) with all wasm features enabled (-all, so binaryen parses the
|
|
|
|
|
# EH instructions), then --asyncify + remove-list + -O2. Native wasm-EH is the only build mode.
|
|
|
|
|
# --no-removelist skip the KiCad big-function remove-list (the small wx test apps don't contain
|
|
|
|
|
# those symbols, and one bare entry — "match" — could collide).
|
|
|
|
|
#
|
|
|
|
|
# WHY post-link (not emcc's in-link Asyncify): the emsdk-bundled Binaryen crashes asyncifying
|
|
|
|
|
# wasm-EH and a compiler/standalone version skew corrupts asyncify metadata. So the in-link pass is
|
|
|
|
|
# stubbed (build-kicad-target.sh / build-wasm-test.sh) and the real transform runs here, on the host
|
|
|
|
|
# (more RAM), with a pinned Binaryen. The cost: emcc's automatic asyncify-imports generation is
|
|
|
|
|
# bypassed, so the BOUNDARY import list lives in asyncify-imports.txt (see that file).
|
|
|
|
|
#
|
|
|
|
|
# Binaryen selection (env overrides win, set by build-wasm-test.sh): one binaryen everywhere — the
|
|
|
|
|
# submodule fork (version_130 + our --hoist-cpp-catches). Override the path via HOIST_WASMOPT / V130_WASMOPT.
|
2025-12-14 11:59:50 +01:00
|
|
|
|
2026-06-29 19:50:18 +02:00
|
|
|
set -eo pipefail
|
2025-12-14 11:59:50 +01:00
|
|
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
|
|
|
|
|
2026-06-29 19:50:18 +02:00
|
|
|
# --- flags ---
|
|
|
|
|
# Native wasm-EH is the only build mode, so the catch-arm hoist pass always runs.
|
|
|
|
|
DO_HOIST=1
|
|
|
|
|
USE_REMOVELIST=1
|
|
|
|
|
while [[ "${1:-}" == --* ]]; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
--no-removelist) USE_REMOVELIST=0; shift ;;
|
|
|
|
|
*) echo "apply-asyncify: unknown flag: $1" >&2; exit 1 ;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
INPUT_WASM="${1:-output/pcbnew.wasm}"
|
|
|
|
|
OUTPUT_WASM="${2:-${INPUT_WASM}}"
|
|
|
|
|
[ -f "${INPUT_WASM}" ] || { echo "ERROR: Input file not found: ${INPUT_WASM}" >&2; exit 1; }
|
|
|
|
|
|
|
|
|
|
# --- Binaryen tool: ONE binaryen everywhere (incl. docker/CI) — the submodule fork. It IS Binaryen
|
|
|
|
|
# version_130 (its Asyncify.cpp is unmodified upstream) + our HoistCppCatches pass, so the SAME binary
|
|
|
|
|
# does --hoist-cpp-catches AND --asyncify/-O2, for both native-EH and legacy JS-EH wasm. Built once via
|
|
|
|
|
# build-wasm-opt.sh; no separate binaryen downloads (the emsdk-bundled v121 can't even asyncify wasm-EH).
|
|
|
|
|
SUBMODULE_WASMOPT="${HOIST_WASMOPT:-$("${SCRIPT_DIR}/../binaryen-hoist-pass/build-wasm-opt.sh")}"
|
|
|
|
|
WASM_OPT="${V130_WASMOPT:-$SUBMODULE_WASMOPT}"
|
|
|
|
|
[ "$DO_HOIST" = 1 ] && HOIST_OPT="$SUBMODULE_WASMOPT"
|
|
|
|
|
|
|
|
|
|
# native wasm-EH needs -all so binaryen parses the EH instructions; HOIST_KEEP_NAMES keeps the
|
|
|
|
|
# names section through -O2 for callstack debugging. KiCad/JS-EH uses neither (matches old behavior).
|
|
|
|
|
FEAT=()
|
|
|
|
|
[ "$DO_HOIST" = 1 ] && FEAT=(-all)
|
|
|
|
|
G="${HOIST_KEEP_NAMES:+-g}"
|
|
|
|
|
|
|
|
|
|
# The asyncify removelist matches functions by NAME. wasm-opt strips the names section by default, so
|
|
|
|
|
# the hoist pass (run before asyncify) would otherwise hand asyncify nameless functions — the
|
|
|
|
|
# removelist then matches NOTHING and the giant try-dense functions it is meant to exclude
|
|
|
|
|
# (BuildBitmapInfo: ~4986 native tries, etc.) get instrumented, which is the dominant driver of the
|
|
|
|
|
# multi-GB asyncify RAM blowup on native-EH. So force the hoist pass to keep names whenever a
|
|
|
|
|
# removelist is in play, so asyncify can see + exclude them. asyncify/-O2 keep their own G, so the
|
|
|
|
|
# FINAL wasm is unchanged unless HOIST_KEEP_NAMES is set (asyncify matches on its INPUT names, which
|
|
|
|
|
# the hoist output now carries).
|
|
|
|
|
HOIST_G="${G}"
|
|
|
|
|
[ "$USE_REMOVELIST" = 1 ] && HOIST_G="-g"
|
|
|
|
|
|
|
|
|
|
# --- the import boundary (shared) + the KiCad remove-list (opt-out), read from sibling files ---
|
|
|
|
|
_join_list() { grep -vE '^[[:space:]]*#|^[[:space:]]*$' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | tr '\n' ',' | sed 's/,$//'; }
|
|
|
|
|
ASYNCIFY_IMPORTS="${ASYNCIFY_IMPORTS_PASS:-$(_join_list "${SCRIPT_DIR}/asyncify-imports.txt")}"
|
|
|
|
|
REMOVE_ARG=()
|
|
|
|
|
if [ "$USE_REMOVELIST" = 1 ]; then
|
|
|
|
|
REMOVE_ARG=("--pass-arg=asyncify-removelist@$(_join_list "${SCRIPT_DIR}/asyncify-removelist.txt")")
|
|
|
|
|
fi
|
2025-12-14 11:59:50 +01:00
|
|
|
|
2026-06-29 19:50:18 +02:00
|
|
|
# --- memory machinery (identical to before; matters for the host-side KiCad pass) ---
|
|
|
|
|
# Bound Binaryen's host thread pool: peak RAM scales with thread count.
|
2026-06-11 07:46:48 +02:00
|
|
|
export BINARYEN_CORES="${BINARYEN_CORES:-8}"
|
|
|
|
|
|
2026-06-29 19:50:18 +02:00
|
|
|
# Preload a scalable allocator on Linux — glibc malloc collapses into futex lock-spin under
|
|
|
|
|
# wasm-opt's many-thread allocation churn; jemalloc/mimalloc roughly halve wall-clock. macOS
|
|
|
|
|
# already ships a scalable allocator. WASM_OPT_PRELOAD=none|0 forces a clean glibc baseline.
|
2026-06-11 07:46:48 +02:00
|
|
|
if [[ "${WASM_OPT_PRELOAD:-}" == "none" || "${WASM_OPT_PRELOAD:-}" == "0" ]]; then
|
2026-06-29 19:50:18 +02:00
|
|
|
WASM_OPT_PRELOAD=""; _PRELOAD_FORCED_OFF=1
|
2026-06-11 07:46:48 +02:00
|
|
|
fi
|
|
|
|
|
if [[ -z "${WASM_OPT_PRELOAD:-}" && -z "${_PRELOAD_FORCED_OFF:-}" && "$(uname -s)" == "Linux" ]]; then
|
|
|
|
|
for _alloc in \
|
|
|
|
|
"/usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2" \
|
|
|
|
|
"/usr/lib/$(uname -m)-linux-gnu/libmimalloc.so.2" \
|
2026-06-29 19:50:18 +02:00
|
|
|
/usr/lib/libjemalloc.so.2 /usr/lib/libmimalloc.so.2; do
|
|
|
|
|
[[ -e "${_alloc}" ]] && { WASM_OPT_PRELOAD="${_alloc}"; break; }
|
2026-06-11 07:46:48 +02:00
|
|
|
done
|
|
|
|
|
fi
|
|
|
|
|
if [[ -n "${WASM_OPT_PRELOAD:-}" ]]; then
|
|
|
|
|
PRELOAD_CMD=(env "LD_PRELOAD=${WASM_OPT_PRELOAD}${LD_PRELOAD:+:${LD_PRELOAD}}")
|
|
|
|
|
else
|
|
|
|
|
PRELOAD_CMD=()
|
|
|
|
|
fi
|
2026-06-29 19:50:18 +02:00
|
|
|
# GNU `time -v` on Linux CI records peak RSS + wall-clock per pass; macOS `time` lacks -v.
|
|
|
|
|
if /usr/bin/time -v true >/dev/null 2>&1; then TIME_CMD=(/usr/bin/time -v); else TIME_CMD=(); fi
|
2026-06-11 07:46:48 +02:00
|
|
|
|
2026-06-29 19:50:18 +02:00
|
|
|
echo "Applying Asyncify${DO_HOIST:+ (+hoist-cpp-catches)}..."
|
|
|
|
|
echo " Input: ${INPUT_WASM}"
|
|
|
|
|
echo " Output: ${OUTPUT_WASM}"
|
|
|
|
|
echo " asyncify wasm-opt: ${WASM_OPT}"
|
|
|
|
|
[ "$DO_HOIST" = 1 ] && echo " hoist wasm-opt: ${HOIST_OPT}"
|
|
|
|
|
echo " BINARYEN_CORES=${BINARYEN_CORES} LD_PRELOAD=${WASM_OPT_PRELOAD:-<none>}"
|
2026-06-11 07:46:48 +02:00
|
|
|
|
2026-06-29 19:50:18 +02:00
|
|
|
SRC="${INPUT_WASM}"
|
2025-12-14 11:59:50 +01:00
|
|
|
|
2026-07-19 15:59:21 +02:00
|
|
|
# Refuse to double-instrument. A postprocess re-run on an artifact that a
|
|
|
|
|
# previous (killed/partial) run already asyncified re-instruments the
|
|
|
|
|
# instrumented module: the pass balloons to OOM/jetsam death, and the output
|
|
|
|
|
# would be broken anyway. The asyncify export names only exist in a module
|
|
|
|
|
# the pass already touched. Recover by re-copying the pristine post-link
|
|
|
|
|
# artifact from the docker volume (docker/build.sh compile copy step).
|
|
|
|
|
if LC_ALL=C grep -aq "asyncify_start_unwind" "${INPUT_WASM}"; then
|
|
|
|
|
echo "ERROR: ${INPUT_WASM} already contains asyncify exports - refusing to" >&2
|
|
|
|
|
echo "double-instrument. Restore the pristine post-link wasm first." >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
2026-06-29 19:50:18 +02:00
|
|
|
# 1. (native wasm-EH only) hoist C++ catch arms so Asyncify can suspend from inside them.
|
|
|
|
|
if [ "$DO_HOIST" = 1 ]; then
|
|
|
|
|
echo "Running --hoist-cpp-catches${HOIST_G:+ (keeping names for removelist matching)}..."
|
|
|
|
|
"${PRELOAD_CMD[@]}" "${TIME_CMD[@]}" "${HOIST_OPT}" --hoist-cpp-catches "${FEAT[@]}" ${HOIST_G} "${SRC}" -o "${OUTPUT_WASM}"
|
|
|
|
|
SRC="${OUTPUT_WASM}"
|
2025-12-14 11:59:50 +01:00
|
|
|
fi
|
|
|
|
|
|
2026-06-29 19:50:18 +02:00
|
|
|
# 2. The real Asyncify transform. ASYNCIFY_EXTRA_OPTS: optional extra wasm-opt flags (origin/main hook).
|
|
|
|
|
echo "Running wasm-opt --asyncify (several minutes + significant RAM)..."
|
|
|
|
|
"${PRELOAD_CMD[@]}" "${TIME_CMD[@]}" "${WASM_OPT}" --asyncify ${ASYNCIFY_EXTRA_OPTS:-} "${FEAT[@]}" ${G} \
|
2025-12-14 11:59:50 +01:00
|
|
|
"--pass-arg=asyncify-imports@${ASYNCIFY_IMPORTS}" \
|
2026-06-29 19:50:18 +02:00
|
|
|
"${REMOVE_ARG[@]}" \
|
2025-12-14 11:59:50 +01:00
|
|
|
--pass-arg=asyncify-propagate-addlist \
|
2026-06-29 19:50:18 +02:00
|
|
|
"${SRC}" -o "${OUTPUT_WASM}"
|
2025-12-14 11:59:50 +01:00
|
|
|
|
2026-06-29 19:50:18 +02:00
|
|
|
# ASYNCIFY_ONLY=1 stops before -O2 (benchmark harness in scripts/bench/ times just the transform).
|
2026-06-11 07:46:48 +02:00
|
|
|
if [[ "${ASYNCIFY_ONLY:-0}" == "1" ]]; then
|
2026-06-29 19:50:18 +02:00
|
|
|
echo "ASYNCIFY_ONLY=1 → skipping -O2 (benchmark mode)."; ls -lh "${OUTPUT_WASM}"; exit 0
|
2026-06-11 07:46:48 +02:00
|
|
|
fi
|
|
|
|
|
|
2026-06-29 19:50:18 +02:00
|
|
|
# 3. Post-asyncify shrink. Asyncify spills every live local; without coalescing, large
|
|
|
|
|
# coroutine-entry functions exceed V8's per-function locals limit and stall/crash the renderer.
|
2026-07-01 12:18:57 +02:00
|
|
|
# -O1 runs CoalesceLocals (enough to keep instrumented functions under V8's local limit) and is the
|
|
|
|
|
# level we ship EVERYWHERE — main CI, tag releases, and local builds all use it. NOT -Os/-Oz (they
|
|
|
|
|
# break the asyncify runtime — Binaryen #4484). See docs/debugging/DEBUG.md §6-7.
|
|
|
|
|
BINARYEN_OPT_LEVEL="${BINARYEN_OPT_LEVEL:--O1}"
|
2026-06-29 19:50:18 +02:00
|
|
|
echo "Running wasm-opt ${BINARYEN_OPT_LEVEL} (shrink instrumented functions under V8's local limit)..."
|
|
|
|
|
"${PRELOAD_CMD[@]}" "${TIME_CMD[@]}" "${WASM_OPT}" "${BINARYEN_OPT_LEVEL}" "${FEAT[@]}" ${G} "${OUTPUT_WASM}" -o "${OUTPUT_WASM}"
|
2026-06-16 12:55:01 +02:00
|
|
|
|
|
|
|
|
echo "Asyncify + ${BINARYEN_OPT_LEVEL} complete: ${OUTPUT_WASM}"
|
2025-12-14 11:59:50 +01:00
|
|
|
ls -lh "${OUTPUT_WASM}"
|