jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose
The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
This commit is contained in:
parent
3ee174e9b4
commit
9c475a804e
120 changed files with 1522 additions and 2974 deletions
|
|
@ -1,110 +0,0 @@
|
|||
> **Historical (asyncify era).** These benches timed the Binaryen
|
||||
> `apply-asyncify` post-link tail, which the JSPI migration deleted — the
|
||||
> scripts below that reference it are gone. The VM provisioning pieces
|
||||
> (setup-vm.sh, vm-build.sh, cloud-init) remain useful for any host-side
|
||||
> build benching.
|
||||
|
||||
# wasm-opt allocator/core benchmark
|
||||
|
||||
Fast, local feedback loop for the CI perf issue: the host-side `wasm-opt`/asyncify
|
||||
pass (`scripts/common/apply-asyncify.sh`) is slow on the glibc Linux CI runner
|
||||
because glibc `malloc` collapses into per-arena lock (`futex`) contention under
|
||||
many threads. We preload **jemalloc** to fix it. This harness measures the effect
|
||||
locally instead of paying for a full ~160-min Hetzner CI run per experiment.
|
||||
|
||||
**Key idea:** `wasm-opt`/asyncify is a standalone pass over an *already-compiled*
|
||||
`.wasm` (`docker/build.sh:194`). So we build the eeschema `.wasm` **once** on the
|
||||
Mac, copy it into a Linux VM, and replay only the optimizer across a matrix of
|
||||
`{glibc, jemalloc} × core counts`. No KiCad compile happens in the VM.
|
||||
|
||||
## Caveats (read before trusting numbers)
|
||||
|
||||
- **This Mac is 10 cores / 32 GB.** The local core sweep tops out at ~10 threads.
|
||||
The CI runner is 32 vCPU, so the *full* 32-thread contention is **not**
|
||||
reproducible here — the local run shows the **direction** (jemalloc vs glibc,
|
||||
and how wall-clock scales with cores), not CI's absolute worst case. The final
|
||||
32-thread pick must still be confirmed on the real runner.
|
||||
- **aarch64 ≠ CI's x86_64.** The VM is aarch64 (HVF, near-native speed). The
|
||||
glibc arena-lock pathology is arch-independent, so the **ratios** transfer;
|
||||
**absolute seconds do not** match the x86_64 AMD runner.
|
||||
- asyncify peaks ~10–15 GB RAM → the VM gets 20 GB; close other heavy apps.
|
||||
|
||||
## 1. Build the fixture (once, on the Mac)
|
||||
|
||||
```bash
|
||||
mkdir -p bench
|
||||
./docker/build.sh eeschema --build-deps # long: full cold build
|
||||
# Pull the Docker-side raw (pre-finalize) wasm while the builder container is up:
|
||||
docker compose -f docker/docker-compose.yml cp \
|
||||
kicad-wasm-builder:/workspace/build-wasm/kicad-eeschema/eeschema/eeschema.wasm \
|
||||
bench/eeschema.raw.wasm
|
||||
# Finalize it to match what asyncify actually consumes in the pipeline:
|
||||
./scripts/common/apply-finalize.sh bench/eeschema.raw.wasm bench/eeschema.finalized.wasm
|
||||
```
|
||||
|
||||
Fallback if `compose cp` fails:
|
||||
`docker compose -f docker/docker-compose.yml exec kicad-wasm-builder cat <path> > bench/eeschema.raw.wasm`
|
||||
|
||||
## 2. Provision and boot the VM (Mac)
|
||||
|
||||
```bash
|
||||
brew install qemu # one-time
|
||||
./scripts/bench/setup-vm.sh prepare
|
||||
./scripts/bench/setup-vm.sh run # serial console; quit with Ctrl-a x
|
||||
```
|
||||
|
||||
Wait ~1–2 min for cloud-init (installs git/curl/time/libjemalloc2/strace).
|
||||
|
||||
## 3. Load repo + fixture into the VM
|
||||
|
||||
```bash
|
||||
# from the Mac, in another terminal:
|
||||
scp -P 2222 -o StrictHostKeyChecking=no bench/eeschema.finalized.wasm bench@localhost:~/
|
||||
./scripts/bench/setup-vm.sh ssh
|
||||
# inside the VM:
|
||||
git clone <this-repo-url> repo && cd repo
|
||||
git checkout istvanmatejcsok/feat/ci-hetzner-allcores
|
||||
mkdir -p bench && mv ~/eeschema.finalized.wasm bench/
|
||||
# get-wasm-opt.sh auto-downloads Binaryen v121 aarch64-linux on first use
|
||||
```
|
||||
|
||||
## 4. Run the benchmark (in the VM)
|
||||
|
||||
```bash
|
||||
STRACE=1 ./scripts/bench/wasm-opt-bench.sh
|
||||
```
|
||||
|
||||
Sweeps `CORES="1 4 8 10"` × `{glibc, jemalloc}`, writing `bench/results.csv`
|
||||
(wall-clock + peak RSS per cell) and per-cell logs under `bench/results/`. With
|
||||
`STRACE=1` it also records `futex` syscall share per allocator (expect ~99% on
|
||||
glibc, far lower with jemalloc). Override the sweep with e.g. `CORES="8 10"`.
|
||||
|
||||
## Interpreting
|
||||
|
||||
- jemalloc rows should show **lower wall-clock** than glibc, widening as cores rise.
|
||||
- glibc wall-clock that *stops improving* (or worsens) with more cores = the
|
||||
arena-lock storm; jemalloc should keep scaling.
|
||||
- These ratios justify the `LD_PRELOAD` fix; use them (plus one real-runner
|
||||
confirmation) to choose CI's `BINARYEN_CORES`.
|
||||
|
||||
Artifacts (`bench/*.wasm`, `bench/results*`, `scripts/bench/vm/`) are gitignored.
|
||||
|
||||
## 5. Full Docker build in the VM (CI dry-run) — vm-build.sh
|
||||
|
||||
Verifies CI orchestration changes (docker/build.sh, compose limits, pipelining)
|
||||
on Linux+Docker without burning a Hetzner slot. The guest is aarch64/HVF:
|
||||
a *functional* CI proxy, not an x86 performance proxy.
|
||||
|
||||
```bash
|
||||
# one-time: bigger disk + Docker-enabled cloud-init, then boot
|
||||
VM_DISK=80G ./scripts/bench/setup-vm.sh prepare
|
||||
./scripts/bench/setup-vm.sh run # leave running in its own terminal
|
||||
|
||||
# from the Mac: cold calculator build inside the guest (deps + docker image)
|
||||
./scripts/bench/vm-build.sh # = calculator --build-deps
|
||||
|
||||
# pipeline smoke test (deps already in the guest volume from the previous run)
|
||||
KICAD_PIPELINE=1 ./scripts/bench/vm-build.sh calculator,pl_editor
|
||||
```
|
||||
|
||||
Prints the guest build wall time at the end; build logs stream through ssh.
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
instance-id: kicad-wasmopt-bench
|
||||
local-hostname: kicad-bench
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
#cloud-config
|
||||
# NoCloud seed for the wasm-opt benchmark VM. setup-vm.sh substitutes
|
||||
# __SSH_PUBKEY__ with your ~/.ssh/id_ed25519.pub before building the seed ISO.
|
||||
hostname: kicad-bench
|
||||
users:
|
||||
- name: bench
|
||||
sudo: "ALL=(ALL) NOPASSWD:ALL"
|
||||
shell: /bin/bash
|
||||
# Key auth only: setup-vm.sh requires an SSH pubkey and substitutes it below,
|
||||
# so no password login is needed. Lock the password and disable SSH password
|
||||
# auth (below); recover via the substituted key, not a shared password.
|
||||
lock_passwd: true
|
||||
ssh_authorized_keys:
|
||||
- __SSH_PUBKEY__
|
||||
# Disable SSH password authentication — the seed carries a pubkey.
|
||||
ssh_pwauth: false
|
||||
package_update: true
|
||||
# Docker CE from the official repo (mirrors the CI workflow's install step) so
|
||||
# scripts/bench/vm-build.sh can run the full docker/build.sh pipeline in-guest.
|
||||
apt:
|
||||
sources:
|
||||
docker.list:
|
||||
source: "deb [signed-by=$KEY_FILE] https://download.docker.com/linux/ubuntu noble stable"
|
||||
keyid: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88
|
||||
packages:
|
||||
- git
|
||||
- curl
|
||||
- ca-certificates
|
||||
- time
|
||||
- libjemalloc2
|
||||
- strace
|
||||
- rsync
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-buildx-plugin
|
||||
- docker-compose-plugin
|
||||
runcmd:
|
||||
# The docker group only exists after the package installs, so the user's
|
||||
# `groups:` stanza can't grant it — add membership here (applies to new
|
||||
# ssh sessions, which is all vm-build.sh uses).
|
||||
- usermod -aG docker bench
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
#!/bin/bash
|
||||
# o2-config-sweep.sh — replay wasm-opt over a PREBUILT asyncified fixture under a
|
||||
# matrix of {Binaryen version, optimization passes, thread count, allocator/THP}.
|
||||
# RUNS ON THE LINUX CI BOX.
|
||||
#
|
||||
# WHY: the on-box diagnostic (docs/ci-build-slowness-findings.md) proved the
|
||||
# ~80-min `-O2` cost is ~90% FUTEX LOCK CONTENTION inside wasm-opt (Binaryen's
|
||||
# global type mutex), NOT the allocator and NOT memory management (madvise=0,
|
||||
# THP compaction=0, identical under glibc and mimalloc). So the levers that can
|
||||
# move wall-clock are: a NEWER Binaryen (the devs cut this contention after our
|
||||
# pinned v121) and FEWER threads (less lock contention) — plus reducing the -O2
|
||||
# work itself (lighter passes). Allocator/THP are dead ends, kept only as controls.
|
||||
#
|
||||
# CONFIG NAME GRAMMAR: <preset>[@<cores>]
|
||||
# preset sets Binaryen version + passes + allocator/THP (see config_env).
|
||||
# optional @<cores> overrides BINARYEN_CORES for that one cell (sweep threads).
|
||||
# e.g. "v130-O2@8" = Binaryen 130, -O2, 8 threads.
|
||||
#
|
||||
# TWO MODES:
|
||||
# CAP_SECONDS=0 (default): run to completion → true wall-clock + output size.
|
||||
# CAP_SECONDS>0: WINDOWED sample — the lock storm is steady-state, so measure
|
||||
# it over a [60s, CAP-30s] window and kill the pass. Reports, per cell:
|
||||
# win_sysfrac = system CPU fraction in the window (lock contention; LOWER better)
|
||||
# win_usercores = REAL-work cores in the window (progress rate; HIGHER better
|
||||
# → predicts shorter wall for the same pass set)
|
||||
# win_tlb = TLB-shootdown interrupts in the window
|
||||
# ~CAP/60 min per cell, so the whole matrix fits one CI run.
|
||||
#
|
||||
# Usage: CONFIGS="baseline v130-O2 baseline@8" CAP_SECONDS=600 \
|
||||
# ./scripts/bench/o2-config-sweep.sh <asyncified.wasm>
|
||||
# Env: CONFIGS, CORES (default nproc), CAP_SECONDS (default 0), DIAGNOSTIC (1 =
|
||||
# perf kernel-symbol sample, full mode). Output under /bench/o2-results/.
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
|
||||
FIXTURE="${1:?usage: o2-config-sweep.sh <asyncified-fixture.wasm>}"
|
||||
CONFIGS="${CONFIGS:-baseline}"
|
||||
CORES_DEFAULT="${CORES:-$(nproc)}"
|
||||
CAP_SECONDS="${CAP_SECONDS:-0}"
|
||||
DIAGNOSTIC="${DIAGNOSTIC:-0}"
|
||||
|
||||
[[ "$(uname -s)" == "Linux" ]] || { echo "ERROR: run on the Linux CI box." >&2; exit 1; }
|
||||
[[ -f "${FIXTURE}" ]] || { echo "ERROR: fixture not found: ${FIXTURE}" >&2; exit 1; }
|
||||
command -v /usr/bin/time >/dev/null || { echo "ERROR: apt-get install -y time" >&2; exit 1; }
|
||||
|
||||
ARCH="$(uname -m)"
|
||||
WASM_OPT_121="$("${REPO}/scripts/common/get-wasm-opt.sh" 2>/dev/null)" # pinned v121
|
||||
JEMALLOC="$(ls /usr/lib/${ARCH}-linux-gnu/libjemalloc.so.2 2>/dev/null | head -1 || true)"
|
||||
MIMALLOC="$(ls /usr/lib/${ARCH}-linux-gnu/libmimalloc.so* 2>/dev/null | sort | tail -1 || true)"
|
||||
|
||||
OUTDIR="${REPO}/bench/o2-results"; mkdir -p "${OUTDIR}"
|
||||
CSV="${OUTDIR}/results.csv"
|
||||
echo "config,cores,ver,flags,thp,mode,wall,wall_s,user_s,sys_s,vol_ctxsw,win_sysfrac,win_usercores,win_tlb,madvise,out_bytes,preload" > "${CSV}"
|
||||
|
||||
sudo sysctl -w kernel.perf_event_paranoid=-1 >/dev/null 2>&1 || true
|
||||
PERF=""; command -v perf >/dev/null 2>&1 && perf stat -e syscalls:sys_enter_madvise -- true >/dev/null 2>&1 && PERF="yes"
|
||||
echo "perf syscall counting: ${PERF:-NO}"
|
||||
CLK="$(getconf CLK_TCK 2>/dev/null || echo 100)"
|
||||
|
||||
# Resolve (download+cache) a standalone wasm-opt for a Binaryen version. 121 = the
|
||||
# pinned one from get-wasm-opt.sh; anything else is fetched from GitHub releases.
|
||||
get_wasm_opt_ver() {
|
||||
local ver="$1"
|
||||
if [[ -z "$ver" || "$ver" == "121" ]]; then echo "${WASM_OPT_121}"; return 0; fi
|
||||
local dir="${REPO}/build-wasm/tools/binaryen-${ver}" bin="${REPO}/build-wasm/tools/binaryen-${ver}/bin/wasm-opt"
|
||||
if [[ ! -x "$bin" ]]; then
|
||||
local url="https://github.com/WebAssembly/binaryen/releases/download/version_${ver}/binaryen-version_${ver}-${ARCH}-linux.tar.gz"
|
||||
mkdir -p "${REPO}/build-wasm/tools"
|
||||
echo " downloading Binaryen v${ver}..." >&2
|
||||
curl -fsSL -o "/tmp/binaryen-${ver}.tgz" "$url" || { echo " ERR: download v${ver} failed ($url)" >&2; return 1; }
|
||||
tar -xzf "/tmp/binaryen-${ver}.tgz" -C "${REPO}/build-wasm/tools" || return 1
|
||||
mv "${REPO}/build-wasm/tools/binaryen-version_${ver}" "$dir" 2>/dev/null || true
|
||||
fi
|
||||
[[ -x "$bin" ]] && echo "$bin" || return 1
|
||||
}
|
||||
|
||||
snap_cpu() { awk '/^cpu /{u=$2;s=$4;t=0;for(i=2;i<=NF;i++)t+=$i;print u,s,t}' /proc/stat; }
|
||||
snap_tlb() { awk '/TLB/{for(i=2;i<=NF;i++)if($i~/^[0-9]+$/)s+=$i}END{print s+0}' /proc/interrupts; }
|
||||
snap_vm() { awk '/^compact_stall /{c=$2}/^thp_fault_alloc /{t=$2}END{print c+0,t+0}' /proc/vmstat; }
|
||||
thp_state(){ grep -oP '\[\K[^]]+' /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null || echo "?"; }
|
||||
set_thp() { [[ "$1" == "asis" ]] && return 0
|
||||
echo "$1"|sudo tee /sys/kernel/mm/transparent_hugepage/enabled >/dev/null 2>&1 || true
|
||||
echo "$1"|sudo tee /sys/kernel/mm/transparent_hugepage/defrag >/dev/null 2>&1 || true; }
|
||||
|
||||
# preset -> BIN_VER, OPTARGS (pass list), PRELOAD, THP_WANT, EXTRA env
|
||||
BIN_VER=""; OPTARGS=(); PRELOAD=""; THP_WANT="asis"; EXTRA=()
|
||||
LIGHT=(--flatten --simplify-locals --coalesce-locals --reorder-locals --vacuum)
|
||||
config_env() {
|
||||
BIN_VER="121"; OPTARGS=(-O2); PRELOAD=""; THP_WANT="asis"; EXTRA=()
|
||||
case "$1" in
|
||||
baseline) ;; # v121 -O2 (current CI = control)
|
||||
v130-O2) BIN_VER="130" ;; # newer Binaryen, same passes — lock fixed?
|
||||
v130-O1) BIN_VER="130"; OPTARGS=(-O1) ;;
|
||||
v121-O1) OPTARGS=(-O1) ;; # lighter passes (less work)
|
||||
v130-light) BIN_VER="130"; OPTARGS=("${LIGHT[@]}") ;;
|
||||
v121-light) OPTARGS=("${LIGHT[@]}") ;;
|
||||
mimalloc-retain) PRELOAD="${MIMALLOC}"; EXTRA=(MIMALLOC_PURGE_DELAY=-1 MIMALLOC_ALLOW_THP=0) ;; # control (proven dead)
|
||||
thp-off) THP_WANT="never" ;; # control (proven dead)
|
||||
*) echo "ERROR: unknown preset '$1'" >&2; return 1 ;;
|
||||
esac
|
||||
if [[ -n "${PRELOAD}" && ! -e "${PRELOAD}" ]]; then echo "WARN: preload for '$1' missing — SKIP" >&2; return 2; fi
|
||||
}
|
||||
|
||||
field() { grep -F "$1" "$2" 2>/dev/null | tail -1 | sed 's/.*: //' | tr -d ' '; }
|
||||
|
||||
run_config() {
|
||||
local cfg="$1" base cores
|
||||
base="${cfg%@*}"; if [[ "$cfg" == *"@"* ]]; then cores="${cfg##*@}"; else cores="${CORES_DEFAULT}"; fi
|
||||
if ! config_env "${base}"; then echo "${cfg},${cores},,,skip,skip,SKIPPED,,,,,,,,,," >> "${CSV}"; return 0; fi
|
||||
|
||||
local wopt; wopt="$(get_wasm_opt_ver "${BIN_VER}")" || {
|
||||
echo " cannot resolve Binaryen v${BIN_VER} — SKIP"; echo "${cfg},${cores},${BIN_VER},,skip,skip,SKIP-NOBIN,,,,,,,,,," >> "${CSV}"; return 0; }
|
||||
set_thp "${THP_WANT}"; local thp_now; thp_now="$(thp_state)"
|
||||
local flags="${OPTARGS[*]}"
|
||||
local timef="${OUTDIR}/${cfg//[@ ]/_}.time" statf="${OUTDIR}/${cfg//[@ ]/_}.perfstat" logf="${OUTDIR}/${cfg//[@ ]/_}.log"
|
||||
local outw="/tmp/o2-out.wasm"
|
||||
cp "${FIXTURE}" /tmp/o2-in.wasm
|
||||
local -a runenv=(BINARYEN_CORES="${cores}" "${EXTRA[@]}"); [[ -n "${PRELOAD}" ]] && runenv+=("LD_PRELOAD=${PRELOAD}")
|
||||
|
||||
echo ""; echo "=== ${cfg} (binaryen=$("${wopt}" --version 2>&1 | grep -oE '[0-9]+' | head -1), flags='${flags}', cores=${cores}, THP=${thp_now}, preload=${PRELOAD:-none}, extra=${EXTRA[*]:-none}) ==="
|
||||
|
||||
local mode="full" wall wall_s user sys volcsw sysf usercores tlbd madv outsz
|
||||
sysf=NA; usercores=NA; tlbd=NA; madv=NA
|
||||
|
||||
if [[ "${CAP_SECONDS}" -gt 0 ]]; then
|
||||
mode="cap${CAP_SECONDS}"
|
||||
( /usr/bin/time -v -o "${timef}" timeout "${CAP_SECONDS}" env "${runenv[@]}" "${wopt}" "${OPTARGS[@]}" /tmp/o2-in.wasm -o "${outw}" > "${logf}" 2>&1 ) &
|
||||
local rp=$!
|
||||
sleep 60
|
||||
local cu0 cs0 ct0 tlb0; read -r cu0 cs0 ct0 <<<"$(snap_cpu)"; tlb0="$(snap_tlb)"
|
||||
local win=$(( CAP_SECONDS>120 ? CAP_SECONDS-90 : 30 )); sleep "${win}"
|
||||
local cu1 cs1 ct1 tlb1; read -r cu1 cs1 ct1 <<<"$(snap_cpu)"; tlb1="$(snap_tlb)"
|
||||
wait "${rp}" 2>/dev/null || true
|
||||
local dtot=$((ct1-ct0)) dsys=$((cs1-cs0)) duser=$((cu1-cu0))
|
||||
sysf="$(awk -v s=${dsys} -v t=${dtot} 'BEGIN{print (t>0)?sprintf("%.2f",s/t):"NA"}')"
|
||||
usercores="$(awk -v u=${duser} -v c=${CLK} -v w=${win} 'BEGIN{print (w>0)?sprintf("%.2f",(u/c)/w):"NA"}')"
|
||||
tlbd=$((tlb1-tlb0)); wall="cap@${CAP_SECONDS}"; wall_s="${CAP_SECONDS}"
|
||||
else
|
||||
if [[ -n "${PERF}" ]]; then
|
||||
/usr/bin/time -v -o "${timef}" perf stat -o "${statf}" -e syscalls:sys_enter_madvise,syscalls:sys_enter_munmap \
|
||||
env "${runenv[@]}" "${wopt}" "${OPTARGS[@]}" /tmp/o2-in.wasm -o "${outw}" > "${logf}" 2>&1
|
||||
madv="$(grep -E 'sys_enter_madvise' "${statf}" 2>/dev/null | awk '{gsub(/,/,"",$1);print $1}' | head -1)"
|
||||
else
|
||||
/usr/bin/time -v -o "${timef}" env "${runenv[@]}" "${wopt}" "${OPTARGS[@]}" /tmp/o2-in.wasm -o "${outw}" > "${logf}" 2>&1
|
||||
fi
|
||||
if [[ $? -ne 0 ]]; then echo " FAILED"; tail -4 "${logf}"; echo "${cfg},${cores},${BIN_VER},${flags// /+},${thp_now},full,FAILED,,,,,,,,,," >> "${CSV}"; return 0; fi
|
||||
wall="$(field 'Elapsed (wall clock) time' "${timef}")"
|
||||
wall_s="$(awk -F: '{if(NF==3)print $1*3600+$2*60+$3;else if(NF==2)print $1*60+$2;else print $1}' <<<"${wall}")"
|
||||
fi
|
||||
|
||||
user="$(field 'User time (seconds)' "${timef}")"; sys="$(field 'System time (seconds)' "${timef}")"
|
||||
volcsw="$(field 'Voluntary context switches' "${timef}")"; outsz="$(stat -c %s "${outw}" 2>/dev/null || echo NA)"
|
||||
printf '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n' \
|
||||
"${cfg}" "${cores}" "${BIN_VER}" "${flags// /+}" "${thp_now}" "${mode}" "${wall}" "${wall_s}" \
|
||||
"${user}" "${sys}" "${volcsw}" "${sysf}" "${usercores}" "${tlbd}" "${madv}" "${outsz}" "${PRELOAD:-none}" >> "${CSV}"
|
||||
echo " mode=${mode} wall=${wall} user=${user}s sys=${sys}s vol_ctxsw=${volcsw} out=${outsz}B"
|
||||
echo " WINDOW sys_frac=${sysf} (lock contention) user_cores=${usercores} (real-work rate) tlb=${tlbd} madvise=${madv}"
|
||||
rm -f "${outw}" /tmp/o2-in.wasm
|
||||
}
|
||||
|
||||
echo "Fixture: ${FIXTURE} ($(stat -c %s "${FIXTURE}" 2>/dev/null) bytes)"
|
||||
echo "Configs: ${CONFIGS} CORES_DEFAULT=${CORES_DEFAULT} CAP_SECONDS=${CAP_SECONDS} THP(init)=$(thp_state)"
|
||||
for cfg in ${CONFIGS}; do run_config "${cfg}"; done
|
||||
echo ""; echo "=== results (${CSV}) ==="; column -t -s, "${CSV}" || cat "${CSV}"
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Provision a local QEMU aarch64 + HVF Ubuntu 24.04 VM for the wasm-opt benchmark.
|
||||
# RUNS ON THE macOS HOST (Apple Silicon). See scripts/bench/README.md.
|
||||
#
|
||||
# Subcommands:
|
||||
# prepare download cloud image, build NoCloud seed ISO, copy UEFI vars
|
||||
# run boot the VM (foreground, serial console; ssh on :2222)
|
||||
# ssh ssh into the running VM
|
||||
# (none) = prepare (if needed) then run
|
||||
#
|
||||
# Env overrides: VM_CORES (default 10), VM_MEM (default 20G), SSH_PORT (2222),
|
||||
# VM_DISK (default +30G grow; use VM_DISK=80G for full Docker builds via
|
||||
# scripts/bench/vm-build.sh — emsdk image + deps + build tree need ~50 GB).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VMDIR="${SCRIPT_DIR}/vm"
|
||||
CLOUD_INIT="${SCRIPT_DIR}/cloud-init"
|
||||
|
||||
VM_CORES="${VM_CORES:-10}" # Mac has 10 cores; this caps the local sweep
|
||||
VM_MEM="${VM_MEM:-20G}" # asyncify peaks ~10-15 GB; leave headroom for macOS
|
||||
VM_DISK="${VM_DISK:-30G}" # disk grow beyond the ~3.5 GB cloud image
|
||||
SSH_PORT="${SSH_PORT:-2222}"
|
||||
IMG_URL="https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-arm64.img"
|
||||
|
||||
QEMU_SHARE="$(brew --prefix qemu)/share/qemu"
|
||||
FW_CODE="${QEMU_SHARE}/edk2-aarch64-code.fd"
|
||||
FW_VARS_SRC="${QEMU_SHARE}/edk2-arm-vars.fd"
|
||||
|
||||
BASE_IMG="${VMDIR}/ubuntu-24.04-arm64.img"
|
||||
DISK="${VMDIR}/disk.qcow2"
|
||||
SEED="${VMDIR}/seed.iso"
|
||||
FW_VARS="${VMDIR}/edk2-vars.fd"
|
||||
PUBKEY="${HOME}/.ssh/id_ed25519.pub"
|
||||
|
||||
prepare() {
|
||||
mkdir -p "${VMDIR}"
|
||||
command -v qemu-system-aarch64 >/dev/null || { echo "Install qemu: brew install qemu" >&2; exit 1; }
|
||||
[[ -f "${PUBKEY}" ]] || { echo "No SSH pubkey at ${PUBKEY}" >&2; exit 1; }
|
||||
|
||||
if [[ ! -f "${BASE_IMG}" ]]; then
|
||||
echo "Downloading Ubuntu 24.04 arm64 cloud image..."
|
||||
curl -fL -o "${BASE_IMG}" "${IMG_URL}"
|
||||
fi
|
||||
|
||||
# Fresh working disk each prepare: copy the cloud image and grow it (the
|
||||
# cloud image is ~3.5 GB; binaryen + fixture + scratch need more headroom).
|
||||
echo "Creating working disk (${DISK}, +${VM_DISK})..."
|
||||
cp "${BASE_IMG}" "${DISK}"
|
||||
qemu-img resize "${DISK}" "+${VM_DISK}"
|
||||
|
||||
# Writable UEFI vars store (copy of the template).
|
||||
cp "${FW_VARS_SRC}" "${FW_VARS}"
|
||||
|
||||
# Build the NoCloud seed ISO (label must be CIDATA), injecting the pubkey.
|
||||
echo "Building cloud-init seed ISO..."
|
||||
local tmp; tmp="$(mktemp -d)"
|
||||
sed "s|__SSH_PUBKEY__|$(cat "${PUBKEY}")|" "${CLOUD_INIT}/user-data" > "${tmp}/user-data"
|
||||
cp "${CLOUD_INIT}/meta-data" "${tmp}/meta-data"
|
||||
rm -f "${SEED}"
|
||||
hdiutil makehybrid -iso -joliet -default-volume-name CIDATA -o "${SEED}" "${tmp}" >/dev/null
|
||||
rm -rf "${tmp}"
|
||||
echo "Prepared. Boot with: $0 run"
|
||||
}
|
||||
|
||||
run() {
|
||||
[[ -f "${DISK}" && -f "${SEED}" && -f "${FW_VARS}" ]] || { echo "Run '$0 prepare' first." >&2; exit 1; }
|
||||
echo "Booting VM: ${VM_CORES} vCPU, ${VM_MEM} RAM, ssh -> localhost:${SSH_PORT} (user: bench)"
|
||||
echo "First boot runs cloud-init (installs packages); wait ~1-2 min before ssh."
|
||||
echo "Quit the serial console with: Ctrl-a x"
|
||||
exec qemu-system-aarch64 \
|
||||
-machine virt -accel hvf -cpu host \
|
||||
-smp "${VM_CORES}" -m "${VM_MEM}" \
|
||||
-drive "if=pflash,format=raw,readonly=on,file=${FW_CODE}" \
|
||||
-drive "if=pflash,format=raw,file=${FW_VARS}" \
|
||||
-drive "if=virtio,format=qcow2,file=${DISK}" \
|
||||
-drive "if=virtio,format=raw,file=${SEED}" \
|
||||
-netdev "user,id=n0,hostfwd=tcp::${SSH_PORT}-:22" \
|
||||
-device virtio-net,netdev=n0 \
|
||||
-nographic
|
||||
}
|
||||
|
||||
do_ssh() { exec ssh -p "${SSH_PORT}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null bench@localhost "$@"; }
|
||||
|
||||
case "${1:-}" in
|
||||
prepare) prepare ;;
|
||||
run) run ;;
|
||||
ssh) shift; do_ssh "$@" ;;
|
||||
"") [[ -f "${DISK}" && -f "${SEED}" ]] || prepare; run ;;
|
||||
*) echo "Usage: $0 {prepare|run|ssh}" >&2; exit 1 ;;
|
||||
esac
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# Parameters for the wasm-opt config sweep (.github/workflows/wasm-opt-bench.yml).
|
||||
# Edit, commit, push to a bench/** branch to launch a run.
|
||||
#
|
||||
# Config grammar: <preset>[@<cores>] (see scripts/bench/o2-config-sweep.sh)
|
||||
# presets: baseline(v121 -O2) v130-O2 v130-O1 v121-O1 v130-light v121-light
|
||||
# mimalloc-retain thp-off (last two are proven-dead controls)
|
||||
# @<cores> overrides BINARYEN_CORES for that cell (thread sweep).
|
||||
# CAP_SECONDS_CONF: >0 = windowed storm sample (~CAP/60 min/cell, whole matrix in
|
||||
# one run); 0 = run each to completion (true wall-clock). FIXTURE_RUN_ID_CONF:
|
||||
# reuse a prior run's cached fixture (blank = build it ~40 min).
|
||||
#
|
||||
# VERDICT from run #1 (27197360957): the -O2 cost is ~90% FUTEX LOCK CONTENTION
|
||||
# in Binaryen (type mutex), NOT allocator/THP (madvise=0, compaction=0, identical
|
||||
# under glibc & mimalloc; mimalloc-retain saved only 8%). So allocator/THP are
|
||||
# dead; the levers are NEWER Binaryen (devs fixed this after v121) and FEWER
|
||||
# threads, plus lighter passes. Run #2 triages those windowed (fast):
|
||||
# win_sysfrac = lock contention (LOWER better)
|
||||
# win_usercores = real-work rate (HIGHER better → shorter wall for same passes)
|
||||
|
||||
# ---- Run #2: windowed triage (10 min/cell) on the cached run-#1 fixture ----
|
||||
# baseline = v121 -O2 @32c (control: expect sys_frac~0.88, user_cores~2.6)
|
||||
# v130-O2 = newer Binaryen @32c (does the version fix the lock?)
|
||||
# baseline@8 = v121 -O2 @8c (do fewer threads cut contention?)
|
||||
# v130-O2@8 = newer + fewer threads
|
||||
# v121-O1 = lighter passes @32c (does less work progress faster?)
|
||||
CONFIGS_CONF="baseline v130-O2 baseline@8 v130-O2@8 v121-O1"
|
||||
CORES_CONF=""
|
||||
FIXTURE_RUN_ID_CONF="27197360957"
|
||||
DIAGNOSTIC_CONF="0"
|
||||
CAP_SECONDS_CONF="600"
|
||||
|
||||
# ---- Run #3 (planned): full run (CAP_SECONDS_CONF="0") of the winner(s) for true
|
||||
# wall-clock + output validity, then Chrome e2e. e.g. CONFIGS="v130-O2 v130-O2@8"
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Run a real docker/build.sh build INSIDE the local QEMU Linux VM, as a cheap
|
||||
# stand-in for the Hetzner CI runner (Linux + Docker + the same scripts), so CI
|
||||
# orchestration changes can be verified without burning a Hetzner slot.
|
||||
# RUNS ON THE macOS HOST. The guest is aarch64 under HVF — near-native speed,
|
||||
# a correct *functional* proxy for CI but NOT an x86 performance proxy.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/bench/vm-build.sh [build.sh args...]
|
||||
# default args: calculator --build-deps
|
||||
#
|
||||
# Examples:
|
||||
# ./scripts/bench/vm-build.sh # cold calculator build
|
||||
# KICAD_PIPELINE=1 ./scripts/bench/vm-build.sh calculator,pl_editor # pipeline smoke test
|
||||
#
|
||||
# Prereqs (one-time):
|
||||
# VM_DISK=80G ./scripts/bench/setup-vm.sh prepare # 80G: emsdk image + deps + build tree
|
||||
# ./scripts/bench/setup-vm.sh run # boot in another terminal, wait for cloud-init
|
||||
#
|
||||
# Env: SSH_PORT (2222), KICAD_PIPELINE/KICAD_PIPELINE_JOBS (forwarded to the guest),
|
||||
# VM_BUILD_JOBS (compile -j inside the guest; default = guest nproc).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
|
||||
SSH_PORT="${SSH_PORT:-2222}"
|
||||
GUEST="bench@localhost"
|
||||
GUEST_DIR="kicad-wasm"
|
||||
SSH_OPTS=(-p "${SSH_PORT}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null)
|
||||
|
||||
vssh() { ssh "${SSH_OPTS[@]}" "${GUEST}" "$@"; }
|
||||
|
||||
if ! vssh true 2>/dev/null; then
|
||||
echo "ERROR: VM not reachable on localhost:${SSH_PORT}." >&2
|
||||
echo "Boot it first: ./scripts/bench/setup-vm.sh run (wait ~2 min for cloud-init)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait until cloud-init finished installing Docker (first boot takes a few min).
|
||||
echo "Waiting for cloud-init to finish (installs Docker on first boot)..."
|
||||
vssh "cloud-init status --wait >/dev/null 2>&1 || true"
|
||||
vssh "docker info >/dev/null 2>&1" || {
|
||||
echo "ERROR: Docker not usable in the guest. Re-run setup-vm.sh prepare with the" >&2
|
||||
echo "updated cloud-init (installs docker-ce) and boot a fresh VM." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Sync the working tree (incl. submodule content, no .git) into the guest.
|
||||
# Same exclude set as build.sh's container-sync, plus the VM images themselves.
|
||||
echo "Syncing repo to ${GUEST}:~/${GUEST_DIR} ..."
|
||||
rsync -az --delete \
|
||||
-e "ssh ${SSH_OPTS[*]}" \
|
||||
--exclude="build-wasm" \
|
||||
--exclude="output" \
|
||||
--exclude=".git" \
|
||||
--exclude="logs" \
|
||||
--exclude=".idea" \
|
||||
--exclude="node_modules" \
|
||||
--exclude="tools/emsdk" \
|
||||
--exclude="scripts/bench/vm" \
|
||||
--exclude="tests/test-results" \
|
||||
--exclude="tests/playwright-report" \
|
||||
"${PROJECT_ROOT}/" "${GUEST}:${GUEST_DIR}/"
|
||||
|
||||
BUILD_ARGS=("$@")
|
||||
[ ${#BUILD_ARGS[@]} -eq 0 ] && BUILD_ARGS=(calculator --build-deps)
|
||||
# Append a guest-sized -j unless the caller already passed one.
|
||||
JOBS_FLAG='-j "${JOBS}"'
|
||||
[[ " ${BUILD_ARGS[*]} " == *" -j "* ]] && JOBS_FLAG=""
|
||||
|
||||
# Guest-side env:
|
||||
# - COMPOSE_PROJECT_NAME: no .git in the guest tree, so build.sh can't derive a
|
||||
# branch name — pin the project name explicitly.
|
||||
# - KICAD_DOCKER_CPUS/MEM: the compose dev-Mac caps (10 CPU / 32G) point at a
|
||||
# guest that may have fewer cores and definitely has less RAM; size to guest.
|
||||
# - KICAD_NO_MONITOR/KICAD_LOG_NESTED: no TTY dashboard over ssh, stream output.
|
||||
REMOTE_CMD=$(cat <<EOF
|
||||
set -e
|
||||
cd ${GUEST_DIR}
|
||||
GUEST_CORES=\$(nproc)
|
||||
GUEST_MEM_G=\$(awk '/MemTotal/{printf "%d", \$2/1024/1024 - 3}' /proc/meminfo)
|
||||
export COMPOSE_PROJECT_NAME=kicad-wasm-vm
|
||||
export KICAD_DOCKER_CPUS=\${GUEST_CORES}
|
||||
export KICAD_DOCKER_MEM=\${GUEST_MEM_G}G
|
||||
export KICAD_NO_MONITOR=1 KICAD_LOG_NESTED=1
|
||||
export KICAD_PIPELINE=${KICAD_PIPELINE:-0} KICAD_PIPELINE_JOBS=${KICAD_PIPELINE_JOBS:-2}
|
||||
JOBS=${VM_BUILD_JOBS:-\$GUEST_CORES}
|
||||
echo "=== VM build: \$(uname -m), \${GUEST_CORES} cores, docker mem \${KICAD_DOCKER_MEM}, -j \${JOBS}, args: ${BUILD_ARGS[*]} ==="
|
||||
time ./docker/build.sh ${BUILD_ARGS[*]} ${JOBS_FLAG}
|
||||
ls -lh output/
|
||||
EOF
|
||||
)
|
||||
|
||||
START=$(date +%s)
|
||||
vssh "${REMOTE_CMD}"
|
||||
END=$(date +%s)
|
||||
echo ""
|
||||
echo "=== VM build wall time: $(( (END - START) / 60 ))m $(( (END - START) % 60 ))s (args: ${BUILD_ARGS[*]}) ==="
|
||||
|
|
@ -91,6 +91,7 @@ BEGIN {
|
|||
# Canonical per-app stage rows, in order. Sub-stages (wxwidgets-configure /
|
||||
# -compile) fold into the wxwidgets row via parent[].
|
||||
ROWN = 0
|
||||
ord[++ROWN] = "container-sync"; lab["container-sync"] = "Sync source to container"
|
||||
ord[++ROWN] = "deps"; lab["deps"] = "Dependencies"
|
||||
ord[++ROWN] = "wxwidgets"; lab["wxwidgets"] = "wxWidgets"
|
||||
ord[++ROWN] = "kicad-stubs"; lab["kicad-stubs"] = "Stub libraries"
|
||||
|
|
@ -98,10 +99,7 @@ BEGIN {
|
|||
ord[++ROWN] = "kicad-compile"; lab["kicad-compile"] = "KiCad compile"
|
||||
ord[++ROWN] = "kicad-bitmaps"; lab["kicad-bitmaps"] = "Bitmap resources"
|
||||
ord[++ROWN] = "copy-output"; lab["copy-output"] = "Copy output"
|
||||
ord[++ROWN] = "binaryen"; lab["binaryen"] = "Build Binaryen"
|
||||
ord[++ROWN] = "dyncall-shims"; lab["dyncall-shims"] = "dynCall shims"
|
||||
ord[++ROWN] = "finalize"; lab["finalize"] = "Finalize WASM"
|
||||
ord[++ROWN] = "asyncify"; lab["asyncify"] = "Asyncify"
|
||||
ord[++ROWN] = "env-shim"; lab["env-shim"] = "ENV merge shim"
|
||||
for (i = 1; i <= ROWN; i++) ridx[ord[i]] = i
|
||||
parent["wxwidgets-configure"] = "wxwidgets"
|
||||
parent["wxwidgets-compile"] = "wxwidgets"
|
||||
|
|
@ -152,12 +150,11 @@ curRawKey == "kicad-compile" {
|
|||
if ($0 ~ /Configuring KiCad with CMake/) h_phase = "kicad-configure"
|
||||
if ($0 ~ /\(CMake target:/) h_phase = "kicad-compile"
|
||||
if ($0 ~ /Building bitmap resources/) h_phase = "kicad-bitmaps"
|
||||
if ($0 ~ /Applying asyncify transformation/) h_phase = "asyncify"
|
||||
if (h_phase == "kicad-compile" && match($0, /[0-9]+%\]/)) {
|
||||
p = substr($0, RSTART, RLENGTH); gsub(/[^0-9]/, "", p); h_pct = p + 0
|
||||
}
|
||||
if (h_phase == "kicad-compile" && $0 ~ /Building (C|CXX) object/) h_cc++
|
||||
if ($0 ~ /Build complete\. Output files/ || $0 ~ /Asyncify complete/) h_done = 1
|
||||
if ($0 ~ /Build complete\. Output files/) h_done = 1
|
||||
}
|
||||
END {
|
||||
if (markerCount > 0) { render_markers(); }
|
||||
|
|
@ -173,8 +170,6 @@ function render_markers( state, totalEl, pk, curIdx, i, k, st, det, subdet, en
|
|||
printf "H|-|0|0|%d|%s\n", totalEl, state
|
||||
if (curRawKey == "container-sync") {
|
||||
printf "R|active|Sync source to container|%s\n", fmt(termTs - rowStartTs["container-sync"])
|
||||
} else if (curRawKey == "binaryen") {
|
||||
printf "R|active|Build Binaryen|%s\n", fmt(termTs - rowStartTs["binaryen"])
|
||||
} else {
|
||||
printf "N|waiting for build to start...\n"
|
||||
}
|
||||
|
|
@ -234,13 +229,12 @@ function render_heuristic( state, order, ph, i, k, st, names) {
|
|||
}
|
||||
printf "N|(no progress markers in this log - inferred from log text)\n"
|
||||
# Reduced ordered phase set we can detect heuristically.
|
||||
split("wxwidgets kicad-configure kicad-compile kicad-bitmaps asyncify", order, " ")
|
||||
split("wxwidgets kicad-configure kicad-compile kicad-bitmaps", order, " ")
|
||||
names["wxwidgets"]="wxWidgets"; names["kicad-configure"]="KiCad configure (CMake)"
|
||||
names["kicad-compile"]="KiCad compile"; names["kicad-bitmaps"]="Bitmap resources"
|
||||
names["asyncify"]="Asyncify"
|
||||
ph = 0
|
||||
for (i = 1; i <= 5; i++) if (order[i] == h_phase) ph = i
|
||||
for (i = 1; i <= 5; i++) {
|
||||
for (i = 1; i <= 4; i++) if (order[i] == h_phase) ph = i
|
||||
for (i = 1; i <= 4; i++) {
|
||||
k = order[i]
|
||||
if (h_done) st = "done"
|
||||
else if (i < ph) st = "done"
|
||||
|
|
|
|||
|
|
@ -189,8 +189,8 @@ if [ $NEEDS_CONFIGURE -eq 1 ]; then
|
|||
fi
|
||||
|
||||
# Exception model: native WebAssembly exceptions (legacy binary encoding) + wasm setjmp/longjmp,
|
||||
# single-sourced from scripts/common/env.sh. The catch-arm-hoisting pass (run post-link, see
|
||||
# build-wasm-test.sh) lets Asyncify suspend from inside C++ catch blocks. See docs/features/wasm-exceptions/.
|
||||
# single-sourced from scripts/common/env.sh. -sWASM_LEGACY_EXCEPTIONS=1 pins the EH binary
|
||||
# encoding (exnref is not adopted across our pinned emsdk/browsers). See docs/features/wasm-exceptions/.
|
||||
WX_EH_FLAGS="$DEPS_EH_FLAGS"
|
||||
echo "wx EH model flags: ${WX_EH_FLAGS}"
|
||||
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ export EMSDK_QUIET=1
|
|||
# WebAssembly exceptions (legacy binary encoding) are the only build mode. -sSUPPORT_LONGJMP=wasm is
|
||||
# required because the deps that use setjmp/longjmp (freetype, cairo, OpenCASCADE) must use wasm
|
||||
# setjmp — emscripten's JS-longjmp implementation cannot coexist with -fwasm-exceptions (it would
|
||||
# leave emscripten_longjmp undefined). -sWASM_LEGACY_EXCEPTIONS=1 selects the EH binary encoding our
|
||||
# post-link Asyncify + catch-arm-hoisting pass can consume (Asyncify can't handle exnref).
|
||||
# leave emscripten_longjmp undefined). -sWASM_LEGACY_EXCEPTIONS=1 pins the legacy EH binary encoding:
|
||||
# the exnref encoding is not adopted across our pinned emsdk toolchain and target browsers.
|
||||
export DEPS_EH_FLAGS="-fwasm-exceptions -sSUPPORT_LONGJMP=wasm -sWASM_LEGACY_EXCEPTIONS=1"
|
||||
|
||||
# Emscripten SDK setup
|
||||
|
|
@ -123,23 +123,6 @@ elif [ -z "$JOBS" ]; then
|
|||
export JOBS=1
|
||||
fi
|
||||
|
||||
# Common linker flags for WASM
|
||||
export WASM_LDFLAGS="\
|
||||
-sALLOW_MEMORY_GROWTH=1 \
|
||||
-sINITIAL_MEMORY=256MB \
|
||||
-sSTACK_SIZE=5MB \
|
||||
-sASYNCIFY=1 \
|
||||
-sASYNCIFY_STACK_SIZE=16384 \
|
||||
-sLEGACY_GL_EMULATION \
|
||||
-sMAX_WEBGL_VERSION=2"
|
||||
|
||||
# Threading flags (when enabled)
|
||||
export PTHREAD_LDFLAGS="\
|
||||
-pthread \
|
||||
-sPROXY_TO_PTHREAD=1 \
|
||||
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency \
|
||||
-sOFFSCREENCANVAS_SUPPORT=1"
|
||||
|
||||
# Create output directories
|
||||
mkdir -p "$BUILD_ROOT" "$DEPS_ROOT" "$SYSROOT"/{lib,include,share} "$STAMPS_DIR"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,141 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Get path to Binaryen wasm-opt
|
||||
#
|
||||
# Usage: ./scripts/common/get-wasm-opt.sh
|
||||
# Output: Prints path to wasm-opt executable
|
||||
#
|
||||
# Prefers the emsdk-bundled Binaryen (tools/emsdk/upstream/bin/) so that the
|
||||
# wasm-opt and wasm-emscripten-finalize versions match the Emscripten that
|
||||
# generated the JS glue. A version mismatch between the compiler's Binaryen
|
||||
# (e.g. v121+72 dev) and a standalone release (v121) corrupts asyncify
|
||||
# metadata, causing "func is not a function" errors at runtime.
|
||||
#
|
||||
# Falls back to downloading standalone Binaryen v130 if emsdk is not installed
|
||||
# locally (e.g. CI environments that only use Docker).
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
|
||||
# --- Prefer emsdk-bundled Binaryen (matches the Emscripten that compiled the WASM) ---
|
||||
# Override: set BINARYEN_VERSION in the env to FORCE a specific standalone release
|
||||
# (skips the emsdk preference). Used to A/B Binaryen versions — e.g. v121 carries a
|
||||
# wasm::Type lock-contention bug that makes the host-side -O2 pass ~9x slower than
|
||||
# v130 (see docs/ci-build-slowness-findings.md). Validate any bump with the e2e
|
||||
# suite: a Binaryen/emsdk skew can corrupt asyncify metadata ("func is not a function").
|
||||
EMSDK_WASM_OPT="${PROJECT_ROOT}/tools/emsdk/upstream/bin/wasm-opt"
|
||||
# build-kicad-target.sh replaces emsdk's wasm-opt with a no-op stub (moving the
|
||||
# real binary to wasm-opt.real) so emcc skips the in-link asyncify. That's meant
|
||||
# for the container emsdk, but a host-mode run leaves the HOST emsdk stubbed —
|
||||
# and the stub fakes --version and exits 0, so the host-side asyncify/-O2
|
||||
# "succeed" while doing NOTHING (broken wasm: "asyncify_stop_unwind is not a
|
||||
# function"). Always prefer the preserved real binary when it exists.
|
||||
if [ -x "${EMSDK_WASM_OPT}.real" ]; then
|
||||
EMSDK_WASM_OPT="${EMSDK_WASM_OPT}.real"
|
||||
fi
|
||||
if [ -z "${BINARYEN_VERSION:-}" ] && [ -x "${EMSDK_WASM_OPT}" ]; then
|
||||
EMSDK_VERSION=$("${EMSDK_WASM_OPT}" --version 2>&1 || true)
|
||||
echo "Using emsdk-bundled Binaryen: ${EMSDK_VERSION}" >&2
|
||||
echo "${EMSDK_WASM_OPT}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Fallback: download standalone Binaryen (for CI or environments without local emsdk) ---
|
||||
echo "emsdk Binaryen not found at ${EMSDK_WASM_OPT}, falling back to standalone download..." >&2
|
||||
|
||||
# Default v130: v121 has a wasm::Type lock convoy that makes -O2 ~9x slower on
|
||||
# many-core Linux (docs/ci-build-slowness-findings.md). v130 output validated by
|
||||
# the full e2e suite locally (31/31) and Chromium-green on CI run 27226030304.
|
||||
BINARYEN_VERSION="${BINARYEN_VERSION:-130}"
|
||||
|
||||
# BINARYEN_BUILD_FROM_SOURCE=1: compile wasm-opt ourselves instead of using the
|
||||
# official Linux release tarballs, which are badly built — measured on the
|
||||
# calculator fixture, identical output sha256: asyncify 4x faster on x86
|
||||
# (CI run 27276830256: 3:50 -> 0:58) and 13x on aarch64; -O2 equal. The macOS
|
||||
# tarball is well-built (self-build is ~12% SLOWER there), so this is only
|
||||
# worth enabling on Linux CI. Needs cmake, ninja, g++, git. ~5 min on 32 cores,
|
||||
# cached in build-wasm/tools after the first call.
|
||||
if [[ "${BINARYEN_BUILD_FROM_SOURCE:-0}" == "1" ]]; then
|
||||
SELF_DIR="${PROJECT_ROOT}/build-wasm/tools/binaryen-${BINARYEN_VERSION}-selfbuilt"
|
||||
SELF_WASM_OPT="${SELF_DIR}/bin/wasm-opt"
|
||||
if [ ! -x "${SELF_WASM_OPT}" ]; then
|
||||
SRC_DIR="${PROJECT_ROOT}/build-wasm/tools/binaryen-src-${BINARYEN_VERSION}"
|
||||
BUILD_DIR="${PROJECT_ROOT}/build-wasm/tools/binaryen-build-${BINARYEN_VERSION}"
|
||||
echo "Building Binaryen v${BINARYEN_VERSION} from source (one-time, ~5 min)..." >&2
|
||||
if [ ! -d "${SRC_DIR}" ]; then
|
||||
git clone -q --depth 1 --branch "version_${BINARYEN_VERSION}" \
|
||||
--recurse-submodules --shallow-submodules \
|
||||
https://github.com/WebAssembly/binaryen.git "${SRC_DIR}" >&2
|
||||
fi
|
||||
cmake -S "${SRC_DIR}" -B "${BUILD_DIR}" -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_CXX_FLAGS="-Wno-maybe-uninitialized" \
|
||||
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DBUILD_TESTS=OFF >&2
|
||||
ninja -C "${BUILD_DIR}" wasm-opt wasm-emscripten-finalize >&2
|
||||
# wasm-opt links lib/libbinaryen.so via rpath $ORIGIN/../lib.
|
||||
mkdir -p "${SELF_DIR}/bin" "${SELF_DIR}/lib"
|
||||
cp "${BUILD_DIR}/bin/wasm-opt" "${BUILD_DIR}/bin/wasm-emscripten-finalize" "${SELF_DIR}/bin/"
|
||||
cp "${BUILD_DIR}"/lib/libbinaryen.* "${SELF_DIR}/lib/" 2>/dev/null || true
|
||||
echo "Self-built Binaryen installed to ${SELF_DIR}" >&2
|
||||
fi
|
||||
SELF_VERSION=$("${SELF_WASM_OPT}" --version 2>&1 | grep -o '[0-9]\+' | head -1)
|
||||
if [ "${SELF_VERSION}" != "${BINARYEN_VERSION}" ]; then
|
||||
echo "ERROR: self-built wasm-opt version mismatch (got ${SELF_VERSION}, expected ${BINARYEN_VERSION})" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Using self-built Binaryen v${BINARYEN_VERSION} (${SELF_WASM_OPT})" >&2
|
||||
echo "${SELF_WASM_OPT}"
|
||||
exit 0
|
||||
fi
|
||||
BINARYEN_DIR="${PROJECT_ROOT}/build-wasm/tools/binaryen-${BINARYEN_VERSION}"
|
||||
WASM_OPT="${BINARYEN_DIR}/bin/wasm-opt"
|
||||
|
||||
download_binaryen() {
|
||||
# Detect platform
|
||||
local os=$(uname -s)
|
||||
local arch=$(uname -m)
|
||||
local platform=""
|
||||
|
||||
case "${os}-${arch}" in
|
||||
Darwin-arm64) platform="arm64-macos" ;;
|
||||
Darwin-x86_64) platform="x86_64-macos" ;;
|
||||
Linux-aarch64) platform="aarch64-linux" ;;
|
||||
Linux-x86_64) platform="x86_64-linux" ;;
|
||||
*)
|
||||
echo "ERROR: Unsupported platform: ${os}-${arch}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
local url="https://github.com/WebAssembly/binaryen/releases/download/version_${BINARYEN_VERSION}/binaryen-version_${BINARYEN_VERSION}-${platform}.tar.gz"
|
||||
local tarball="${PROJECT_ROOT}/build-wasm/tools/binaryen-${BINARYEN_VERSION}.tar.gz"
|
||||
|
||||
echo "Downloading Binaryen v${BINARYEN_VERSION} for ${platform}..." >&2
|
||||
mkdir -p "${PROJECT_ROOT}/build-wasm/tools"
|
||||
curl -L -o "${tarball}" "${url}"
|
||||
|
||||
echo "Extracting..." >&2
|
||||
tar -xzf "${tarball}" -C "${PROJECT_ROOT}/build-wasm/tools"
|
||||
mv "${PROJECT_ROOT}/build-wasm/tools/binaryen-version_${BINARYEN_VERSION}" "${BINARYEN_DIR}"
|
||||
rm "${tarball}"
|
||||
|
||||
echo "Binaryen v${BINARYEN_VERSION} installed to ${BINARYEN_DIR}" >&2
|
||||
}
|
||||
|
||||
# Download Binaryen if not cached
|
||||
if [ ! -x "${WASM_OPT}" ]; then
|
||||
download_binaryen
|
||||
fi
|
||||
|
||||
# Verify version
|
||||
INSTALLED_VERSION=$("${WASM_OPT}" --version 2>&1 | grep -o '[0-9]\+' | head -1)
|
||||
if [ "${INSTALLED_VERSION}" != "${BINARYEN_VERSION}" ]; then
|
||||
echo "WARNING: wasm-opt version mismatch (got ${INSTALLED_VERSION}, expected ${BINARYEN_VERSION})" >&2
|
||||
echo "Re-downloading..." >&2
|
||||
rm -rf "${BINARYEN_DIR}"
|
||||
download_binaryen
|
||||
fi
|
||||
|
||||
# Output path to wasm-opt (this is the only stdout output)
|
||||
echo "${WASM_OPT}"
|
||||
|
|
@ -1,3 +1,15 @@
|
|||
# Promising-export census for the KiCad browser apps: every wasm entry export
|
||||
# whose body can reach a suspension (park) under JSPI. Consumed as
|
||||
# -sJSPI_EXPORTS=@thisfile by scripts/kicad/build-kicad-target.sh; emscripten's
|
||||
# symbol-list parser strips lines starting with '#'. Do NOT add blank lines
|
||||
# (they would parse as empty symbol entries).
|
||||
# Regenerate by grep, not memory: the wx entries are the EMSCRIPTEN_KEEPALIVE
|
||||
# exports in wxwidgets/src/wasm/ that can park; pcbjam_libctx_entry is
|
||||
# libcontext's coroutine entry (kicad/thirdparty/libcontext).
|
||||
# Kept in sync BY HAND with two siblings:
|
||||
# - scripts/common/shims/jspi-scheduler.js installExportWraps list (same set
|
||||
# minus main — the runtime calls main before the wraps exist)
|
||||
# - tests/apps/Makefile.wasm WX_JSPI_EXPORTS (comma-joined copy)
|
||||
main
|
||||
wx_dom_event
|
||||
wx_dom_mouse
|
||||
|
|
|
|||
|
|
@ -1,242 +0,0 @@
|
|||
// === Asyncify / fiber / modal diagnostics (LOGGING ONLY — no behavior change) ===
|
||||
//
|
||||
// Injected only when inject-dyncall-shims.sh runs with SHIM_DIAGNOSTICS=1.
|
||||
// Observability for the Chrome/V8 renderer crash on the first coroutine resume
|
||||
// (the main-context Asyncify rewind). Does NOT swallow or alter any call — it
|
||||
// logs and traces, then delegates, so the real crash still happens and can be
|
||||
// observed right up to the faulting point.
|
||||
(function() {
|
||||
var modalActive = false;
|
||||
var glTraceActive = false; // armed at the first main rewind (rewindId===0) below
|
||||
var glCallSeq = 0;
|
||||
var glTraceCap = 8000; // safety cap so a non-crashing run can't log forever
|
||||
var tableLen = function() { return (typeof wasmTable !== "undefined" && wasmTable) ? wasmTable.length : -1; };
|
||||
var asyncState = function() { return (typeof Asyncify !== "undefined") ? Asyncify.state : "N/A"; };
|
||||
|
||||
// 1. Timer scheduling — flag function pointers already out of bounds at schedule time.
|
||||
if (typeof _emscripten_async_call !== "undefined") {
|
||||
var __origAsyncCall = _emscripten_async_call;
|
||||
_emscripten_async_call = function(func, arg, millis) {
|
||||
var inBounds = func >= 0 && func < tableLen();
|
||||
console.log("[DIAG_ASYNC_CALL] func=" + func + " arg=" + arg + " millis=" + millis +
|
||||
" inBounds=" + inBounds + " modalActive=" + modalActive + " state=" + asyncState());
|
||||
if (!inBounds) { console.log("[DIAG_ASYNC_CALL] OUT OF BOUNDS at schedule time! func=" + func); console.trace(); }
|
||||
return __origAsyncCall(func, arg, millis);
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Rewind target selection — which export Asyncify will re-enter on rewind.
|
||||
if (typeof Asyncify !== "undefined" && Asyncify.setDataRewindFunc) {
|
||||
var __origSetRewind = Asyncify.setDataRewindFunc.bind(Asyncify);
|
||||
Asyncify.setDataRewindFunc = function(ptr, forced) {
|
||||
console.log("[DIAG_REWIND_FUNC] ptr=" + ptr + " forced=" + forced + " state=" + Asyncify.state +
|
||||
" modalActive=" + modalActive + " callStack=" + JSON.stringify(Asyncify.exportCallStack));
|
||||
return __origSetRewind(ptr, forced);
|
||||
};
|
||||
}
|
||||
|
||||
// 3. doRewind — the actual rewind that crashes V8. Log the buffer + saved rewind id
|
||||
// immediately before re-entering wasm, so the last line before the crash names it.
|
||||
if (typeof Asyncify !== "undefined" && typeof Asyncify.doRewind === "function") {
|
||||
var __origDoRewind = Asyncify.doRewind.bind(Asyncify);
|
||||
var heap32 = function () { return (typeof GROWABLE_HEAP_I32 === "function") ? GROWABLE_HEAP_I32() : HEAP32; };
|
||||
Asyncify.doRewind = function(ptr) {
|
||||
var H = heap32();
|
||||
var rd = function (off) { try { return H[((ptr + off) >> 2)]; } catch (e) { return -999; } };
|
||||
// asyncify_data layout: [ptr+0]=current stack pos (top of saved data),
|
||||
// [ptr+4]=stack end, [ptr+8]=rewindId. Saved call-index/locals live below [ptr+0].
|
||||
var curPos = rd(0), stackEnd = rd(4), rewindId = rd(8);
|
||||
var name = (Asyncify.callStackIdToName && Asyncify.callStackIdToName[rewindId]) || "?";
|
||||
var usedBytes = curPos - (ptr + 12);
|
||||
console.log("[DIAG_DOREWIND] ptr=" + ptr + " rewindId=" + rewindId + " (" + name + ")" +
|
||||
" curPos=" + curPos + " stackEnd=" + stackEnd + " usedBytes=" + usedBytes +
|
||||
" state=" + asyncState() + " — re-entering wasm now");
|
||||
// Arm the WebGL tracer exactly at the main rewind (the crash window: the silent V8
|
||||
// abort happens right after this rewind returns to main, before coroutine #2/first paint).
|
||||
if (rewindId === 0 && !glTraceActive) {
|
||||
glTraceActive = true;
|
||||
console.log("[DIAG_GL] tracing ARMED at main rewind (rewindId=0)");
|
||||
}
|
||||
// Dump the saved call-index chain (first words of the buffer) so we can see the
|
||||
// depth/shape of what the rewind replays at the crash.
|
||||
try {
|
||||
var words = [];
|
||||
var start = ptr + 12;
|
||||
for (var a = start; a < curPos && a < start + 256; a += 4) words.push(H[(a >> 2)]);
|
||||
console.log("[DIAG_DOREWIND] saved-data[" + words.length + "w]: " + JSON.stringify(words));
|
||||
} catch (e) {}
|
||||
try {
|
||||
return __origDoRewind(ptr);
|
||||
} catch (e) {
|
||||
console.log("[DIAG_DOREWIND] EXCEPTION during rewind: " + e + " | " + (e && e.stack));
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 4. dynCall_vi — log (and trace) out-of-bounds pointers but DO NOT swallow; call through.
|
||||
if (typeof dynCall_vi === "function") {
|
||||
var __origDynCallVi = dynCall_vi;
|
||||
dynCall_vi = function(index, a0) {
|
||||
if (index < 0 || index >= tableLen()) {
|
||||
console.log("[DIAG_DYNCALL_VI] OUT OF BOUNDS index=" + index + " tableLen=" + tableLen() +
|
||||
" modalActive=" + modalActive + " state=" + asyncState());
|
||||
console.trace();
|
||||
}
|
||||
return __origDynCallVi(index, a0);
|
||||
};
|
||||
}
|
||||
|
||||
// 5. Modal lifecycle: poll the scheduler wait registry ("modal" waits).
|
||||
// (The legacy Module._endModal hook was deleted at doc 20 D-1.)
|
||||
if (typeof Module !== "undefined") {
|
||||
var lastModalWaits = 0;
|
||||
setInterval(function() {
|
||||
var S = globalThis.__wxScheduler;
|
||||
if (!S || typeof S.pendingWaits !== "function") return;
|
||||
var n = S.pendingWaits("modal");
|
||||
if (n !== lastModalWaits) {
|
||||
console.log("[DIAG_MODAL] modal waits " + lastModalWaits + " -> " + n +
|
||||
", state=" + asyncState());
|
||||
modalActive = n > 0;
|
||||
lastModalWaits = n;
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// 6. EM_ASYNC_JS sleeps (wxWasmYieldUntilJs, js_enumerateFonts, clipboard, etc.) — log
|
||||
// enter/wake so we can see whether an async sleep is NESTED with a fiber swap
|
||||
// at the crash (the #9153 collision). Logging only; delegates unchanged.
|
||||
if (typeof Asyncify !== "undefined" && typeof Asyncify.handleSleep === "function") {
|
||||
var __diagOrigHandleSleep = Asyncify.handleSleep.bind(Asyncify);
|
||||
var diagSleepId = 0;
|
||||
Asyncify.handleSleep = function(startAsync) {
|
||||
var id = ++diagSleepId;
|
||||
console.log("[DIAG_SLEEP] ENTER id=" + id + " state=" + asyncState() +
|
||||
" currData=" + ((typeof Asyncify.currData !== "undefined" && Asyncify.currData) || "null"));
|
||||
return __diagOrigHandleSleep(function(wakeUp) {
|
||||
return startAsync(function(result) {
|
||||
console.log("[DIAG_SLEEP] WAKE id=" + id + " state=" + asyncState() +
|
||||
" currData=" + ((typeof Asyncify.currData !== "undefined" && Asyncify.currData) || "null"));
|
||||
return wakeUp(result);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// 7. WebGL call tracer — pinpoint the exact GL op that crashes Chrome's renderer.
|
||||
// KiCad runs the GAL on an OffscreenCanvas in the pthread worker (PROXY_TO_PTHREAD +
|
||||
// OFFSCREENCANVAS_SUPPORT), and this diagnostics code runs in that same worker, so we
|
||||
// hook getContext where the context is actually created. Each call logs via
|
||||
// console.error (immediate flush → captured even just before a hard V8 abort), but
|
||||
// only once glTraceActive is set (at the main rewind), so volume = the crash window.
|
||||
function wrapGLContext(ctx, kind) {
|
||||
if (!ctx) return ctx;
|
||||
try { if (ctx.__diagWrapped) return ctx; ctx.__diagWrapped = true; } catch (e) {}
|
||||
console.log("[DIAG_GL] context created kind=" + kind);
|
||||
return new Proxy(ctx, {
|
||||
get: function(target, prop) {
|
||||
var val = target[prop];
|
||||
if (typeof val === "function") {
|
||||
return function() {
|
||||
if (glTraceActive && glCallSeq < glTraceCap) {
|
||||
console.log("[DIAG_GL] #" + (++glCallSeq) + " " + String(prop));
|
||||
}
|
||||
return val.apply(target, arguments);
|
||||
};
|
||||
}
|
||||
return val;
|
||||
}
|
||||
});
|
||||
}
|
||||
function hookGetContext(proto, kind) {
|
||||
if (!proto || typeof proto.getContext !== "function" || proto.__diagGCHooked) return;
|
||||
proto.__diagGCHooked = true;
|
||||
var orig = proto.getContext;
|
||||
proto.getContext = function(type) {
|
||||
// Log the ATTEMPT before calling through, so if getContext itself crashes the
|
||||
// renderer (e.g. a Chrome/ANGLE WebGL-context bug) this is the last line we see.
|
||||
if (type === "webgl2" || type === "webgl" || type === "experimental-webgl") {
|
||||
var attrs = "";
|
||||
try { attrs = JSON.stringify(arguments[1] || {}); } catch (e) {}
|
||||
console.log("[DIAG_GL] getContext(" + kind + ":" + type + ") attrs=" + attrs + " — calling through now");
|
||||
}
|
||||
var ctx = orig.apply(this, arguments);
|
||||
if (type === "webgl2" || type === "webgl" || type === "experimental-webgl") {
|
||||
console.log("[DIAG_GL] getContext returned " + (ctx ? "a context" : "NULL"));
|
||||
try { return wrapGLContext(ctx, kind + ":" + type); } catch (e) { return ctx; }
|
||||
}
|
||||
return ctx;
|
||||
};
|
||||
}
|
||||
if (typeof OffscreenCanvas !== "undefined") hookGetContext(OffscreenCanvas.prototype, "offscreen");
|
||||
if (typeof HTMLCanvasElement !== "undefined") hookGetContext(HTMLCanvasElement.prototype, "html");
|
||||
|
||||
// 8. dynCall_ii / dynCall_vi invocation tracer (logging only). The shim routes the
|
||||
// pthread-entry (ii) and fiber-entry/signal/timer (vi) callbacks through these bound
|
||||
// instrumented dynCall_<sig>. Wrap them to log each invocation + the function pointer,
|
||||
// armed at the main rewind (glTraceActive) so volume = the crash window. The LAST line
|
||||
// before the silent crash names the faulting dispatch + its ptr. Tag thread for context.
|
||||
var __thr = (typeof ENVIRONMENT_IS_PTHREAD !== "undefined" && ENVIRONMENT_IS_PTHREAD) ? "worker" : "main";
|
||||
var __fnName = function(ptr) {
|
||||
try { var f = getWasmTableEntry(ptr); return (f && f.name) ? f.name : "?"; } catch (e) { return "?err"; }
|
||||
};
|
||||
try {
|
||||
if (typeof dynCall_ii === "function") {
|
||||
var __origDCii = dynCall_ii;
|
||||
dynCall_ii = function(ptr, a0) {
|
||||
if (glTraceActive && glCallSeq < glTraceCap)
|
||||
console.log("[DIAG_DC] " + __thr + " dynCall_ii ptr=" + ptr + " name=" + __fnName(ptr) + " #" + (++glCallSeq));
|
||||
return __origDCii(ptr, a0);
|
||||
};
|
||||
}
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (typeof dynCall_vi === "function") {
|
||||
var __origDCvi = dynCall_vi;
|
||||
var __asy = function() {
|
||||
if (typeof Asyncify === "undefined") return "noAsyncify";
|
||||
var st = Asyncify.state;
|
||||
var cd = (Asyncify.currData || 0);
|
||||
return "state=" + st + " currData=" + cd;
|
||||
};
|
||||
dynCall_vi = function(ptr, a0) {
|
||||
var big = glTraceActive && ptr > 15000; // the rare large-index 'vi' dispatches (incl. the stalling 20078)
|
||||
if (glTraceActive && glCallSeq < glTraceCap) {
|
||||
console.log("[DIAG_DC] " + __thr + " dynCall_vi ptr=" + ptr + " name=" + __fnName(ptr) + " arg=" + a0 + " #" + (++glCallSeq));
|
||||
}
|
||||
if (big) {
|
||||
// Asyncify state going IN: if it's non-NORMAL (1=unwinding, 2=rewinding) the
|
||||
// leftover coroutine state is making the instrumented dispatch misbehave.
|
||||
console.log("[DIAG_DC_VI] ENTER ptr=" + ptr + " " + __asy());
|
||||
var r = __origDCvi(ptr, a0);
|
||||
// If this RETURNED line never appears, the dispatch unwound/stalled and never came back.
|
||||
console.log("[DIAG_DC_VI] RETURNED ptr=" + ptr + " " + __asy());
|
||||
return r;
|
||||
}
|
||||
return __origDCvi(ptr, a0);
|
||||
};
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// 9. Periodic asyncify-state monitor (main thread). After dynCall_vi(20078)=
|
||||
// setupUIConditions appears to unwind-and-never-rewind, this timer (which still runs
|
||||
// on the idle event loop) reveals the post-stall Asyncify.state: if it's stuck at
|
||||
// 1 (UNWINDING) or 2 (REWINDING) with a fixed currData, the app yielded and the
|
||||
// rewind was never scheduled. Logs only on change + a heartbeat.
|
||||
if (typeof Asyncify !== "undefined" && __thr === "main") {
|
||||
var __lastSt = -999, __lastCd = -999, __hb = 0;
|
||||
setInterval(function() {
|
||||
var st = Asyncify.state, cd = (Asyncify.currData || 0);
|
||||
if (st !== __lastSt || cd !== __lastCd) {
|
||||
console.log("[DIAG_ASTATE] change -> state=" + st + " currData=" + cd);
|
||||
__lastSt = st; __lastCd = cd;
|
||||
} else if (st !== 0 && (++__hb % 6 === 0)) {
|
||||
console.log("[DIAG_ASTATE] STILL state=" + st + " currData=" + cd + " (stuck?)");
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
console.log("[DIAG] Asyncify/fiber/modal diagnostics installed (logging only) [" + __thr + "]");
|
||||
})();
|
||||
// === End diagnostics ===
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
// jspi-scheduler.js — the JSPI-era successor of asyncify-scheduler.js.
|
||||
// jspi-scheduler.js — the wx scheduler shim for the JSPI runtime.
|
||||
//
|
||||
// Ships as a --pre-js. Keeps the S4 token-wait registry contract byte-for-byte
|
||||
// Ships as a --pre-js. Provides the S4 token-wait registry
|
||||
// (beginWait/waitPromise/resolveWait/resolveTopWait/waitEarlyResolved/
|
||||
// takeWaitResult/pendingWaits/noteContextWait/shutdown) so every C++ bridge
|
||||
// and web caller keeps working, and adds the two things JSPI needs:
|
||||
// takeWaitResult/pendingWaits/shutdown) that every C++ bridge and web
|
||||
// caller relies on, plus the two things JSPI needs:
|
||||
//
|
||||
// 1. ACTIVATION TRACKING. Every promising export the app declares is wrapped
|
||||
// so the shim always knows which activation is executing synchronously
|
||||
|
|
@ -20,30 +20,21 @@
|
|||
// into parked frames' locals (stack-allocated wxDialog members mutated
|
||||
// by a cross-tick EndModal), resurrecting dead state at resume.
|
||||
//
|
||||
// Observability: an event ring + live activation table via __wxWaitDump()
|
||||
// (alias __wxAsyncifyDump kept one release for crash-report consumers).
|
||||
//
|
||||
// Asyncify-era machinery that has NO successor here, by design: currData
|
||||
// single-writer tripwire, deferred-wake queue, stale-fiber quarantine,
|
||||
// trampoline heal, dyncall shims — the states they policed are
|
||||
// unrepresentable under JSPI.
|
||||
// Observability: an event ring + live activation table via __wxWaitDump().
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
if (globalThis.__wxScheduler && globalThis.__wxScheduler.backend === "jspi") {
|
||||
if (globalThis.__wxSchedulerInstalled) {
|
||||
return; // idempotent under double injection
|
||||
}
|
||||
|
||||
var RING_CAP = 256;
|
||||
|
||||
var S = {
|
||||
backend: "jspi",
|
||||
|
||||
// --- mailbox lane (timers/wheel; ordering machinery, mechanism-free) ----
|
||||
// Same contract as the asyncify shim: enqueueAfter queues a C callback,
|
||||
// delivery happens through the dedicated _wxWasmMailboxTick export from a
|
||||
// fresh task, in order. Under JSPI the tick is a promising export — a
|
||||
// enqueueAfter queues a C callback; delivery happens through the dedicated
|
||||
// _wxWasmMailboxTick export from a fresh task, in order. The tick is a
|
||||
// suspension inside a delivered handler parks the tick's own activation,
|
||||
// and the rejection path carries the same containment (a throwing handler
|
||||
// must not leave a parked quasi-modal unresolved).
|
||||
|
|
@ -94,17 +85,12 @@
|
|||
}, 0);
|
||||
},
|
||||
|
||||
// --- S1 embind lane (contract-identical port from asyncify-scheduler) --
|
||||
// --- S1 embind lane --
|
||||
// Mutators (doc 18 classification) must not enter wasm while a load is in
|
||||
// flight: the open activation is suspended mid-load and a collab-apply /
|
||||
// save / theme flip entering between its parks would mutate the board
|
||||
// under it. Semantic exclusion — nothing asyncify-specific about it.
|
||||
// The FIFO drains, in order, once kicadOpenFileBusy clears.
|
||||
//
|
||||
// Retired here, by design: _wrapOpenFile / kicadOpenFileStart (the
|
||||
// asyncify Phase F starter route). Under JSPI kicadOpenFile is an embind
|
||||
// async() export — its own promising activation parks legally and the
|
||||
// call returns a real Promise; no dispatch-context detour exists.
|
||||
// under it. The exclusion is semantic, independent of the suspension
|
||||
// mechanism. The FIFO drains, in order, once kicadOpenFileBusy clears.
|
||||
MUTATOR_NAMES: [
|
||||
"kicadSetChrome", "kicadSetReadOnly",
|
||||
"kicadCollabApply", "kicadCollabApplyItems",
|
||||
|
|
@ -181,16 +167,11 @@
|
|||
},
|
||||
|
||||
// The embind PARKERs (kicadOpenFile / kicadOpenFiles / kicadLibsReload,
|
||||
// registered emscripten::async() under PCBJAM_JSPI): wrap them with the
|
||||
// registered emscripten::async()): wrap them with the
|
||||
// same activation tracking as the raw promising exports, so their parks
|
||||
// (wxWasmYieldUntil inside the load) find a tracked record and get the
|
||||
// green-copy spill-stack discipline. Embind names live on Module WITHOUT
|
||||
// green-region spill-stack discipline. Embind names live on Module WITHOUT
|
||||
// the underscore prefix, hence the separate installer.
|
||||
// NOT here: the kicadTestFiberPark* levers. They are emscripten::async()
|
||||
// (a plain embind call into a suspending body throws on strict-JSPI
|
||||
// Firefox), but the parker wrap's turnstile queueing would DEFER a
|
||||
// mid-park poke until the park drains — the exact race the levers exist
|
||||
// to stage. Their suspensions ride the untracked-anon-record path.
|
||||
PARKER_NAMES: ["kicadOpenFile", "kicadOpenFiles", "kicadLibsReload"],
|
||||
_wrapParkers: function () {
|
||||
var wrapped = 0;
|
||||
|
|
@ -252,12 +233,6 @@
|
|||
return entry.result | 0;
|
||||
},
|
||||
|
||||
// JSPI: context waits do not exist; kept as a loud no-op for transition
|
||||
// callers (nothing registers them — wxWasmYieldUntil suspends in place).
|
||||
noteContextWait: function (token) {
|
||||
console.warn("[wx-scheduler] noteContextWait(" + token + ") under jspi backend");
|
||||
},
|
||||
|
||||
resolveWait: function (token, result) {
|
||||
var entry = this.waits.get(token);
|
||||
if (!entry || entry.resolved) return false;
|
||||
|
|
@ -294,7 +269,7 @@
|
|||
dead: false,
|
||||
shutdown: function (why) {
|
||||
this.dead = true;
|
||||
// S6 teardown contract (same as the asyncify shim): queued-but-
|
||||
// S6 teardown contract: queued-but-
|
||||
// undelivered mutators FAIL LOUDLY instead of hanging their callers,
|
||||
// and undelivered mailbox messages drop — the pumps stop themselves on
|
||||
// the dead flag.
|
||||
|
|
@ -307,8 +282,8 @@
|
|||
if (stranded) {
|
||||
console.warn("[wx-scheduler] shutdown (" + why + ") stranded:" + stranded);
|
||||
} else {
|
||||
// teardown-gate contract (e2e/app-quit.spec.ts, ported from the
|
||||
// asyncify shim): a clean exit must SAY so on the console
|
||||
// teardown-gate contract (e2e/app-quit.spec.ts): a clean exit must
|
||||
// SAY so on the console
|
||||
console.log("[wx-scheduler] shutdown (" + why + ") clean");
|
||||
}
|
||||
this._note("shutdown", why, stranded);
|
||||
|
|
@ -751,7 +726,7 @@
|
|||
return wrapped;
|
||||
},
|
||||
|
||||
// --- observability skeleton (finalized in Phase 7) ---------------------
|
||||
// --- observability ------------------------------------------------------
|
||||
_ring: [],
|
||||
_note: function (ev, a, b) {
|
||||
this._ring.push([Date.now(), ev, String(a), b | 0]);
|
||||
|
|
@ -768,7 +743,6 @@
|
|||
});
|
||||
});
|
||||
return {
|
||||
backend: "jspi",
|
||||
dead: this.dead,
|
||||
waitsBegun: this.waitsBegun,
|
||||
waitsResolved: this.waitsResolved,
|
||||
|
|
@ -787,7 +761,7 @@
|
|||
globalThis.__wxScheduler = S;
|
||||
globalThis.__wxSchedulerInstalled = true; // wxWasmSchedulerAssertInstalled probe
|
||||
|
||||
// --- Phase 7 signals ------------------------------------------------------
|
||||
// --- diagnostic signals ---------------------------------------------------
|
||||
// SuspendError attributor: a SuspendError means a PLAIN (non-promising)
|
||||
// wasm entry tried to park — a missed -sJSPI_EXPORTS/installExportWraps
|
||||
// entry. The engine cannot say WHICH export, but the live dump (what was
|
||||
|
|
@ -825,8 +799,6 @@
|
|||
});
|
||||
}, 10000);
|
||||
globalThis.__wxWaitDump = function () { return S.dump(); };
|
||||
// transition alias: crash-report consumers read __wxAsyncifyDump
|
||||
globalThis.__wxAsyncifyDump = globalThis.__wxWaitDump;
|
||||
|
||||
// Self-install the activation wraps once the runtime is up (this file ships
|
||||
// as a --pre-js, so Module exists here). The name set mirrors the
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ const INPUTS = [
|
|||
// the promising-export census.
|
||||
{ file: "scripts/common/shims/jspi-scheduler.js" },
|
||||
{ file: "scripts/common/jspi-exports.txt" },
|
||||
// Single-sources DEPS_EH_FLAGS (the exception-model link flags).
|
||||
{ file: "scripts/common/env.sh" },
|
||||
|
||||
// Per-tool compile recipes (compile flags / emcc link options).
|
||||
{ dir: "scripts/kicad", match: /^build-.*\.sh$/ },
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
#!/bin/bash
|
||||
# Sourced library. Single source of truth for the 5-repo layout:
|
||||
# Sourced library. Single source of truth for the 4-repo layout:
|
||||
#
|
||||
# root = pcbjam main
|
||||
# ├── kicad (kicad/) wasm-port
|
||||
# ├── wxwidgets (wxwidgets/) wasm-port
|
||||
# ├── binaryen (binaryen/) wasm-port
|
||||
# └── pcbjam-shared (web/pcbjam-shared/) main [MIT contract]
|
||||
#
|
||||
# Bash variable names can't contain '-', so pcbjam-shared's KEY is
|
||||
|
|
@ -13,18 +12,16 @@
|
|||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
|
||||
REPOS=(root kicad wxwidgets binaryen pcbjam_shared)
|
||||
REPOS=(root kicad wxwidgets pcbjam_shared)
|
||||
|
||||
PATH_root="$ROOT_DIR"
|
||||
PATH_kicad="$ROOT_DIR/kicad"
|
||||
PATH_wxwidgets="$ROOT_DIR/wxwidgets"
|
||||
PATH_binaryen="$ROOT_DIR/binaryen"
|
||||
PATH_pcbjam_shared="$ROOT_DIR/web/pcbjam-shared"
|
||||
|
||||
MAIN_root="main"
|
||||
MAIN_kicad="wasm-port"
|
||||
MAIN_wxwidgets="wasm-port"
|
||||
MAIN_binaryen="wasm-port"
|
||||
MAIN_pcbjam_shared="main"
|
||||
|
||||
repo_path() {
|
||||
|
|
|
|||
|
|
@ -296,11 +296,10 @@ else
|
|||
log_info "Building KiCad in RELEASE mode (skipping wasm-opt due to memory limits)"
|
||||
fi
|
||||
|
||||
# Suspension backend: JSPI (native stack switching) since the migration.
|
||||
# The headless CLIs (kicad_tools, occ_service) get NO suspension backend at
|
||||
# all — their targets pin -sASYNCIFY=0 and run in node/worker where nothing
|
||||
# may suspend. coroutine.h/libcontext key on __EMSCRIPTEN__ directly, so no
|
||||
# ABI define is threaded through the TU flags anymore.
|
||||
# Suspension backend: JSPI (native stack switching). The headless CLIs
|
||||
# (kicad_tools, occ_service) get no suspension backend — they run in
|
||||
# node/workers where nothing may suspend. coroutine.h/libcontext key on
|
||||
# __EMSCRIPTEN__ directly, so no ABI define is threaded through the TU flags.
|
||||
|
||||
# Step 6: Create build directory
|
||||
mkdir -p "${KICAD_BUILD}"
|
||||
|
|
@ -365,23 +364,11 @@ fi
|
|||
|
||||
log_info "Stub libraries built"
|
||||
|
||||
# Step 6.2/6.3: wasm-opt + wasm-emscripten-finalize handling.
|
||||
# For the editor apps these tools OOM on the huge debug wasm, so we stub them in
|
||||
# the container and run them on the host (docker/build.sh phase 2). The small,
|
||||
# debug-stripped (-g0) CLI finalizes fine in-container, so for kicad_tools
|
||||
# we restore/keep the real tools and skip host post-processing entirely.
|
||||
# EMSDK is needed below (emscripten sysroot paths in the CMake invocation).
|
||||
if [ -z "${EMSDK}" ]; then
|
||||
log_error "EMSDK environment variable is not set."
|
||||
exit 1
|
||||
fi
|
||||
EMSDK_WASM_OPT="${EMSDK}/upstream/bin/wasm-opt"
|
||||
EMSDK_FINALIZE="${EMSDK}/upstream/bin/wasm-emscripten-finalize"
|
||||
|
||||
# JSPI has no post-link asyncify pass: every app finalizes in-container with
|
||||
# the REAL tools. The .real backups exist on containers that ran the retired
|
||||
# asyncify stub dance — restore them if present.
|
||||
[ -f "${EMSDK_WASM_OPT}.real" ] && cp "${EMSDK_WASM_OPT}.real" "${EMSDK_WASM_OPT}"
|
||||
[ -f "${EMSDK_FINALIZE}.real" ] && cp "${EMSDK_FINALIZE}.real" "${EMSDK_FINALIZE}"
|
||||
|
||||
# Step 6.5: Verify WASM support is in KiCad fork
|
||||
# The kicad submodule should already have WASM port detection and kiplatform support
|
||||
|
|
@ -531,16 +518,16 @@ fi
|
|||
# (scripts/common/jspi-exports.txt: the wx KEEPALIVE entries that can park +
|
||||
# pcbjam_libctx_entry; regenerate by grep, not memory), the jspi-scheduler
|
||||
# pre-js, and the runtime methods its spill-stack discipline needs
|
||||
# (stackSave/stackRestore/HEAPU8). Headless CLIs: nothing — their targets pin
|
||||
# -sASYNCIFY=0 and nothing in them may suspend.
|
||||
# (stackSave/stackRestore/HEAPU8). Headless CLIs: nothing — no suspension
|
||||
# backend; nothing in them may suspend.
|
||||
case "${APP_NAME}" in
|
||||
kicad_tools|occ_service)
|
||||
ASYNC_LINK_FLAGS=""
|
||||
ASYNC_RUNTIME_METHODS="-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString','stringToUTF8','lengthBytesUTF8']"
|
||||
JSPI_LINK_FLAGS=""
|
||||
JSPI_RUNTIME_METHODS="-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString','stringToUTF8','lengthBytesUTF8']"
|
||||
;;
|
||||
*)
|
||||
ASYNC_LINK_FLAGS="-sJSPI -sJSPI_EXPORTS=@${PROJECT_ROOT}/scripts/common/jspi-exports.txt --pre-js ${PROJECT_ROOT}/scripts/common/shims/jspi-scheduler.js"
|
||||
ASYNC_RUNTIME_METHODS="-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString','stringToUTF8','lengthBytesUTF8','stackSave','stackRestore','HEAPU8','HEAP8','HEAP32']"
|
||||
JSPI_LINK_FLAGS="-sJSPI -sJSPI_EXPORTS=@${PROJECT_ROOT}/scripts/common/jspi-exports.txt --pre-js ${PROJECT_ROOT}/scripts/common/shims/jspi-scheduler.js"
|
||||
JSPI_RUNTIME_METHODS="-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString','stringToUTF8','lengthBytesUTF8','stackSave','stackRestore','HEAPU8','HEAP8','HEAP32']"
|
||||
;;
|
||||
esac
|
||||
|
||||
|
|
@ -556,7 +543,7 @@ emcmake cmake "${KICAD_DIR}" \
|
|||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||
-DCMAKE_CXX_FLAGS="${EXTRA_FLAGS} -Xclang -fno-pch-timestamp -pthread --use-port=zlib -DKICAD_USE_PLATFORM_WASM=1${DIAG_DEFINES} -I${SYSROOT}/include -I${STUBS_DIR} -include ${STUBS_DIR}/char_traits_uint16_workaround.h" \
|
||||
-DCMAKE_C_FLAGS="${EXTRA_FLAGS} -pthread --use-port=zlib -I${SYSROOT}/include -I${STUBS_DIR}" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="${LINKER_DEBUG_FLAGS} -pthread ${ASYNC_LINK_FLAGS} -sUSE_PTHREADS=1 -sMALLOC=mimalloc -sPTHREAD_POOL_SIZE='${PTHREAD_POOL_EXPR}' -sPTHREAD_POOL_SIZE_STRICT=0 -sALLOW_MEMORY_GROWTH=1 -sINITIAL_MEMORY=256MB -sMAXIMUM_MEMORY=4GB -sMAX_WEBGL_VERSION=2 ${GL3D_LINK_FLAGS} ${NANOSLEEP_YIELD_LINK} ${MALLINFO_STUB_LINK} ${ASYNC_RUNTIME_METHODS} ${EMBIND_LINK_FLAG} -L${SYSROOT}/lib -L${KICAD_BUILD}/common -L${KICAD_BUILD}/common/gal ${STUBS_BUILD}/libgit2_stub.a ${STUBS_BUILD}/libcurl_stub.a${APP_STUB_LINK} ${STUBS_BUILD}/libnng_stub.a ${EMBIND_OBJ}" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="${LINKER_DEBUG_FLAGS} -pthread ${JSPI_LINK_FLAGS} -sUSE_PTHREADS=1 -sMALLOC=mimalloc -sPTHREAD_POOL_SIZE='${PTHREAD_POOL_EXPR}' -sPTHREAD_POOL_SIZE_STRICT=0 -sALLOW_MEMORY_GROWTH=1 -sINITIAL_MEMORY=256MB -sMAXIMUM_MEMORY=4GB -sMAX_WEBGL_VERSION=2 ${GL3D_LINK_FLAGS} ${NANOSLEEP_YIELD_LINK} ${MALLINFO_STUB_LINK} ${JSPI_RUNTIME_METHODS} ${EMBIND_LINK_FLAG} -L${SYSROOT}/lib -L${KICAD_BUILD}/common -L${KICAD_BUILD}/common/gal ${STUBS_BUILD}/libgit2_stub.a ${STUBS_BUILD}/libcurl_stub.a${APP_STUB_LINK} ${STUBS_BUILD}/libnng_stub.a ${EMBIND_OBJ}" \
|
||||
-DCMAKE_SHARED_LINKER_FLAGS="-Wl,--allow-multiple-definition" \
|
||||
-DCMAKE_MODULE_LINKER_FLAGS="-Wl,--allow-multiple-definition" \
|
||||
-DZLIB_LIBRARY="${EMSDK}/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/pic/libz.a" \
|
||||
|
|
|
|||
Loading…
Reference in a new issue