#!/bin/bash # Build the wasm32 stdlib archive for moxie-iskra from a moxie source tree. # Requires: moxie, opt, llc, clang (with wasm32 target), wasm-ld # # Usage: MOXIEROOT=../moxie ./tools/build-stdlib-archive-wasm.sh [output-dir] # # The target-dir is the moxie project to compile (e.g., ~/s/smesh). # Output goes to output-dir (default: /tmp/iskra-corpus-wasm). set -e TARGETDIR="${1:?usage: $0 [output-dir]}" OUTDIR="${2:-/tmp/iskra-corpus-wasm}" MOXIEROOT="${MOXIEROOT:?set MOXIEROOT to the moxie compiler source root}" mkdir -p "$OUTDIR" echo "=== Step 1: Generate post-interface-lowering IR (wasm32) ===" >&2 cd "$TARGETDIR" GOOS=js GOARCH=wasm moxie build -internal-printir -o /dev/null . > "$OUTDIR/lowered.ll" 2>/dev/null || true LINES=$(wc -l < "$OUTDIR/lowered.ll") echo " generated $LINES lines of IR" >&2 if [ "$LINES" -lt 10 ]; then echo "ERROR: IR generation produced too few lines ($LINES)" >&2 exit 1 fi echo "=== Step 2: Fix visibility ===" >&2 sed -E \ 's/^define internal /define /; s/^define hidden /define /; s/^define dso_local /define /; s/ = internal unnamed_addr / = unnamed_addr /g; s/ = internal constant/ = constant/g; s/ = internal global/ = global/g; s/ = hidden unnamed_addr / = unnamed_addr /g; s/ = hidden constant/ = constant/g; s/ = hidden global/ = global/g' \ "$OUTDIR/lowered.ll" > "$OUTDIR/lowered-fixed.ll" echo "=== Step 2b: Strip app-level (main.*) definitions ===" >&2 awk ' /^define / && /@main\./ && !/@main\.[^(]*\$/ && !/@main\.init[^A-Za-z]/ { skip=1; next } /^define / && /@"main\./ && !/@"main\.[^"]*\$/ && !/@"main\.init[^A-Za-z]/ { skip=1; next } /^define / && /@\(\*main\./ { skip=1; next } /^define / && /@"\(\*main\./ { skip=1; next } /^define / && /@\(main\./ { skip=1; next } skip && /^}/ { skip=0; next } skip { next } /^@main\./ { next } /^@"main\./ { next } /^@"main\$alloc/ { next } { print } ' "$OUTDIR/lowered-fixed.ll" > "$OUTDIR/lowered-stripped.ll" # Collect all references to main.* and main$* symbols { grep -oP '@"?main\.[^("{\s,)]+' "$OUTDIR/lowered-stripped.ll" grep -oP '@"main\$[^"]+' "$OUTDIR/lowered-stripped.ll" } | sort -u > "$OUTDIR/_main_refs.txt" # Collect all defined symbols (functions + globals) { grep -P '^(define|declare) ' "$OUTDIR/lowered-stripped.ll" | grep -oP '@"?main\.[^("{\s,)]+' grep -P '^@"?main[\.\$]' "$OUTDIR/lowered-stripped.ll" | grep -oP '@"?main[\.\$][^("{\s,= ]+' } | sort -u > "$OUTDIR/_main_defined.txt" comm -23 "$OUTDIR/_main_refs.txt" "$OUTDIR/_main_defined.txt" > "$OUTDIR/_main_missing.txt" # Emit stubs: function declares for main.* and external global for main$* { cat "$OUTDIR/lowered-stripped.ll" while IFS= read -r sym; do case "$sym" in *'$'*) echo "$sym\" = external global i8" ;; *) echo "declare void $sym(...)" ;; esac done < "$OUTDIR/_main_missing.txt" } > "$OUTDIR/lowered-stdlib.ll" BEFORE=$(grep -c '^define ' "$OUTDIR/lowered-fixed.ll") AFTER=$(grep -c '^define ' "$OUTDIR/lowered-stdlib.ll") STUBS=$(wc -l < "$OUTDIR/_main_missing.txt") echo " functions: $BEFORE -> $AFTER (stripped $((BEFORE - AFTER)) main.* defs, added $STUBS stubs)" >&2 rm -f "$OUTDIR/_main_refs.txt" "$OUTDIR/_main_defined.txt" "$OUTDIR/_main_missing.txt" "$OUTDIR/lowered-stripped.ll" echo "=== Step 3: Compile to wasm object ===" >&2 opt -O2 "$OUTDIR/lowered-stdlib.ll" -o "$OUTDIR/lowered.bc" llc -filetype=obj -mtriple=wasm32-unknown-js -O2 --function-sections --data-sections "$OUTDIR/lowered.bc" -o "$OUTDIR/stdlib.o" echo " stdlib.o: $(du -h "$OUTDIR/stdlib.o" | cut -f1)" >&2 echo "=== Step 4: Build wasm asm stubs ===" >&2 TMPDIR=$(mktemp -d) cat > "$TMPDIR/asm_stubs_wasm.c" << 'STUBEOF' #include // No CPU feature detection in wasm void cpuid_stub(uint32_t eax, uint32_t ecx, uint32_t* result) __asm__("internal/cpu.cpuid"); void cpuid_stub(uint32_t eax, uint32_t ecx, uint32_t* result) { result[0] = 0; result[1] = 0; result[2] = 0; result[3] = 0; } uint64_t xgetbv_stub(uint32_t xcr) __asm__("internal/cpu.xgetbv"); uint64_t xgetbv_stub(uint32_t xcr) { return 0; } int32_t getgoamd64_stub(void) __asm__("internal/cpu.getGOAMD64level"); int32_t getgoamd64_stub(void) { return 1; } // Syscall returns ENOSYS - wasm uses WASI imports directly struct syscall6_result { long r1; long r2; long err; }; struct syscall6_result syscall6_stub(long num, long a1, long a2, long a3, long a4, long a5, long a6) __asm__("internal/runtime/syscall.Syscall6"); struct syscall6_result syscall6_stub(long num, long a1, long a2, long a3, long a4, long a5, long a6) { struct syscall6_result res = {-1, 0, 38}; // ENOSYS return res; } void entersyscall_stub(void) __asm__("runtime.entersyscall"); void entersyscall_stub(void) {} void exitsyscall_stub(void) __asm__("runtime.exitsyscall"); void exitsyscall_stub(void) {} void beforefork_stub(void) __asm__("syscall.runtime_BeforeFork"); void beforefork_stub(void) {} void afterfork_stub(void) __asm__("syscall.runtime_AfterFork"); void afterfork_stub(void) {} void afterforkinchild_stub(void) __asm__("syscall.runtime_AfterForkInChild"); void afterforkinchild_stub(void) {} void doallthreadssyscall_stub(void) __asm__("syscall.runtime_doAllThreadsSyscall"); void doallthreadssyscall_stub(void) {} int32_t haswaitingreaders_stub(void) __asm__("syscall.hasWaitingReaders"); int32_t haswaitingreaders_stub(void) { return 0; } void cgocaller_stub(void) __asm__("syscall.cgocaller"); void cgocaller_stub(void) {} void* makestrongfromweak_stub(void* p) __asm__("weak.runtime_makeStrongFromWeak"); void* makestrongfromweak_stub(void* p) { return p; } STUBEOF clang -c --target=wasm32-unknown-js -O2 -ffunction-sections -fdata-sections -o "$OUTDIR/asm_stubs.o" "$TMPDIR/asm_stubs_wasm.c" echo " asm_stubs.o: $(du -h "$OUTDIR/asm_stubs.o" | cut -f1)" >&2 echo "=== Step 5: Build wasm C runtime ===" >&2 RTOBJS=() for f in "$MOXIEROOT"/src/runtime/*.c; do case "$(basename "$f")" in *_windows*|*_darwin*|*_arm64*|*_other*|*_baremetal*|*_linux*|*_unix*) continue ;; gc_boehm*|signal*|spawn_*|secalloc*) continue ;; esac out="$TMPDIR/$(basename "$f").o" clang -c --target=wasm32-unknown-js -O2 -ffunction-sections -fdata-sections -o "$out" "$f" 2>/dev/null && RTOBJS+=("$out") done # Include wasm-specific runtime files for f in "$MOXIEROOT"/src/runtime/*_wasm*; do [ -f "$f" ] || continue case "$(basename "$f")" in *.mx) continue ;; esac out="$TMPDIR/$(basename "$f").o" clang -c --target=wasm32-unknown-js -O2 -ffunction-sections -fdata-sections -o "$out" "$f" 2>/dev/null && RTOBJS+=("$out") done if [ ${#RTOBJS[@]} -gt 0 ]; then wasm-ld -r -o "$OUTDIR/runtime.o" "${RTOBJS[@]}" echo " runtime.o: $(du -h "$OUTDIR/runtime.o" | cut -f1)" >&2 else # WASM runtime is entirely in LLVM IR; create empty placeholder echo 'void __wasm_rt_placeholder(void){}' | clang -c --target=wasm32-unknown-js -O2 -x c - -o "$OUTDIR/runtime.o" echo " runtime.o: empty placeholder" >&2 fi echo "=== Step 6: Create archive ===" >&2 rm -f "$OUTDIR/libstdlib.a" llvm-ar rcs "$OUTDIR/libstdlib.a" "$OUTDIR/stdlib.o" "$OUTDIR/asm_stubs.o" echo " libstdlib.a: $(du -h "$OUTDIR/libstdlib.a" | cut -f1)" >&2 echo "=== Step 7: Install ===" >&2 ISKRA_DIR="$HOME/.local/share/moxie-iskra" mkdir -p "$ISKRA_DIR" if [ -f "$OUTDIR/runtime.o" ]; then cp "$OUTDIR/runtime.o" "$ISKRA_DIR/runtime.wasm32.o" echo " installed: $ISKRA_DIR/runtime.wasm32.o" >&2 fi cp "$OUTDIR/libstdlib.a" "$ISKRA_DIR/libstdlib.wasm32.a" cp "$OUTDIR/lowered.bc" "$ISKRA_DIR/stdlib.wasm32.bc" cp "$OUTDIR/asm_stubs.o" "$ISKRA_DIR/asm_stubs.wasm32.o" echo " installed: $ISKRA_DIR/libstdlib.wasm32.a" >&2 echo " installed: $ISKRA_DIR/stdlib.wasm32.bc ($(du -h "$ISKRA_DIR/stdlib.wasm32.bc" | cut -f1))" >&2 echo " installed: $ISKRA_DIR/asm_stubs.wasm32.o" >&2 echo "=== Done ===" >&2 echo "Archive: $OUTDIR/libstdlib.a" >&2 echo "Bitcode: $ISKRA_DIR/stdlib.wasm32.bc" >&2 echo "Runtime: $ISKRA_DIR/runtime.wasm32.o" >&2 rm -rf "$TMPDIR"