#!/bin/bash # Post-process the Emscripten-generated .js for KiCad WASM (pcbnew, eeschema, # pl_editor, calculator, …). # # The actual JavaScript that gets injected lives in readable, standalone files in # scripts/common/shims/ (not inline heredocs): # - dyncall-binding.js.tmpl per-signature dynCall_ binding (templated) # - handlesleep.js nested-Asyncify handleSleep currData save/restore (#9153) # - diagnostics.js optional logging-only instrumentation (see SHIM_DIAGNOSTICS) # # Why bind dynCall_* to the real wasm exports: the build links -sDYNCALLS=1, so # asyncify-INSTRUMENTED dynCall_* trampolines exist as wasm exports. The bare # dynCall_(index, ...) call sites (invoke_* wrappers, the empty-callback fixes # below) aren't bound to top-level names, so we bind them to wasmExports["dynCall_"]. # Binding to getWasmTableEntry() instead bypasses the instrumentation and breaks # unwind/rewind through indirect calls ("indirect call signature mismatch" — caught # every frame in Firefox; a hard renderer crash in Chrome/V8). # # Usage: # inject-dyncall-shims.sh # SHIM_DIAGNOSTICS=1 inject-dyncall-shims.sh # also inject diagnostics.js set -e JS_FILE="$1" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SHIM_DIR="$SCRIPT_DIR/shims" # One-line toggle for the diagnostics module (default OFF). SHIM_DIAGNOSTICS="${SHIM_DIAGNOSTICS:-0}" if [ -z "$JS_FILE" ] || [ ! -f "$JS_FILE" ]; then echo "Error: JS file not found: $JS_FILE" echo "Usage: $0 " exit 1 fi for f in dyncall-binding.js.tmpl handlesleep.js diagnostics.js; do if [ ! -f "$SHIM_DIR/$f" ]; then echo "Error: missing shim source $SHIM_DIR/$f" exit 1 fi done # --- 1. dynCall_ bindings ------------------------------------------------- echo "Extracting dynCall signatures from $JS_FILE..." SIGNATURES=$(grep -oE 'dynCall_[a-zA-Z0-9]+' "$JS_FILE" | sort -u | sed 's/dynCall_//') if [ -z "$SIGNATURES" ]; then echo "No dynCall signatures found - nothing to inject" exit 0 fi SIG_COUNT=$(echo "$SIGNATURES" | wc -l | tr -d ' ') echo "Found $SIG_COUNT unique signatures" # The template body is everything from the `function` line onward (skip its comments). TEMPLATE_BODY=$(sed -n '/^function /,$p' "$SHIM_DIR/dyncall-binding.js.tmpl") SHIM_FILE=$(mktemp) { echo "" echo "// === dynCall bindings (bind bare names to the real DYNCALLS=1 wasm exports) ===" } > "$SHIM_FILE" for sig in $SIGNATURES; do argcount=$((${#sig} - 1)) args="index" call_args="" for ((i=0; i> "$SHIM_FILE" done echo "// === End dynCall bindings ===" >> "$SHIM_FILE" # Insert right after the getWasmTableEntry definition. GWTL_LINE=$(grep -n '^var getWasmTableEntry = funcPtr => {' "$JS_FILE" | head -1 | cut -d: -f1) if [ -z "$GWTL_LINE" ]; then GWTL_LINE=$(grep -n 'var getWasmTableEntry' "$JS_FILE" | head -1 | cut -d: -f1) fi if [ -z "$GWTL_LINE" ]; then echo "Error: Could not find getWasmTableEntry in $JS_FILE"; rm "$SHIM_FILE"; exit 1 fi INSERT_LINE="" for ((i=GWTL_LINE; i<=GWTL_LINE+10; i++)); do [ "$(sed -n "${i}p" "$JS_FILE")" == "};" ] && { INSERT_LINE=$i; break; } done [ -z "$INSERT_LINE" ] && INSERT_LINE=$GWTL_LINE echo "Injecting dynCall bindings after line $INSERT_LINE..." head -n "$INSERT_LINE" "$JS_FILE" > "${JS_FILE}.tmp" cat "$SHIM_FILE" >> "${JS_FILE}.tmp" tail -n +$((INSERT_LINE + 1)) "$JS_FILE" >> "${JS_FILE}.tmp" mv "${JS_FILE}.tmp" "$JS_FILE" rm "$SHIM_FILE" echo "Injected $SIG_COUNT dynCall bindings" # --- 2. Empty-callback fixes --------------------------------------------------- # Emscripten+pthreads emits some direct-call paths as no-op ((a1)=>{}) stubs that # ARE used. Rewire each to the (now-bound) dynCall_*. (Kept inline: simple sed one-liners.) echo "Fixing empty callback arrow functions..." TOTAL_FIXED=0 apply_fix() { #