build-stdlib-archive-wasm.sh raw
1 #!/bin/bash
2 # Build the wasm32 stdlib archive for moxie-iskra from a moxie source tree.
3 # Requires: moxie, opt, llc, clang (with wasm32 target), wasm-ld
4 #
5 # Usage: MOXIEROOT=../moxie ./tools/build-stdlib-archive-wasm.sh <target-dir> [output-dir]
6 #
7 # The target-dir is the moxie project to compile (e.g., ~/s/smesh).
8 # Output goes to output-dir (default: /tmp/iskra-corpus-wasm).
9 set -e
10
11 TARGETDIR="${1:?usage: $0 <target-dir> [output-dir]}"
12 OUTDIR="${2:-/tmp/iskra-corpus-wasm}"
13 MOXIEROOT="${MOXIEROOT:?set MOXIEROOT to the moxie compiler source root}"
14
15 mkdir -p "$OUTDIR"
16
17 echo "=== Step 1: Generate post-interface-lowering IR (wasm32) ===" >&2
18 cd "$TARGETDIR"
19 GOOS=js GOARCH=wasm moxie build -internal-printir -o /dev/null . > "$OUTDIR/lowered.ll" 2>/dev/null || true
20 LINES=$(wc -l < "$OUTDIR/lowered.ll")
21 echo " generated $LINES lines of IR" >&2
22 if [ "$LINES" -lt 10 ]; then
23 echo "ERROR: IR generation produced too few lines ($LINES)" >&2
24 exit 1
25 fi
26
27 echo "=== Step 2: Fix visibility ===" >&2
28 sed -E \
29 's/^define internal /define /;
30 s/^define hidden /define /;
31 s/^define dso_local /define /;
32 s/ = internal unnamed_addr / = unnamed_addr /g;
33 s/ = internal constant/ = constant/g;
34 s/ = internal global/ = global/g;
35 s/ = hidden unnamed_addr / = unnamed_addr /g;
36 s/ = hidden constant/ = constant/g;
37 s/ = hidden global/ = global/g' \
38 "$OUTDIR/lowered.ll" > "$OUTDIR/lowered-fixed.ll"
39
40 echo "=== Step 2b: Strip app-level (main.*) definitions ===" >&2
41 awk '
42 /^define / && /@main\./ && !/@main\.[^(]*\$/ && !/@main\.init[^A-Za-z]/ { skip=1; next }
43 /^define / && /@"main\./ && !/@"main\.[^"]*\$/ && !/@"main\.init[^A-Za-z]/ { skip=1; next }
44 /^define / && /@\(\*main\./ { skip=1; next }
45 /^define / && /@"\(\*main\./ { skip=1; next }
46 /^define / && /@\(main\./ { skip=1; next }
47 skip && /^}/ { skip=0; next }
48 skip { next }
49 /^@main\./ { next }
50 /^@"main\./ { next }
51 /^@"main\$alloc/ { next }
52 { print }
53 ' "$OUTDIR/lowered-fixed.ll" > "$OUTDIR/lowered-stripped.ll"
54
55 # Collect all references to main.* and main$* symbols
56 { grep -oP '@"?main\.[^("{\s,)]+' "$OUTDIR/lowered-stripped.ll"
57 grep -oP '@"main\$[^"]+' "$OUTDIR/lowered-stripped.ll"
58 } | sort -u > "$OUTDIR/_main_refs.txt"
59 # Collect all defined symbols (functions + globals)
60 { grep -P '^(define|declare) ' "$OUTDIR/lowered-stripped.ll" | grep -oP '@"?main\.[^("{\s,)]+'
61 grep -P '^@"?main[\.\$]' "$OUTDIR/lowered-stripped.ll" | grep -oP '@"?main[\.\$][^("{\s,= ]+'
62 } | sort -u > "$OUTDIR/_main_defined.txt"
63 comm -23 "$OUTDIR/_main_refs.txt" "$OUTDIR/_main_defined.txt" > "$OUTDIR/_main_missing.txt"
64
65 # Emit stubs: function declares for main.* and external global for main$*
66 {
67 cat "$OUTDIR/lowered-stripped.ll"
68 while IFS= read -r sym; do
69 case "$sym" in
70 *'$'*) echo "$sym\" = external global i8" ;;
71 *) echo "declare void $sym(...)" ;;
72 esac
73 done < "$OUTDIR/_main_missing.txt"
74 } > "$OUTDIR/lowered-stdlib.ll"
75
76 BEFORE=$(grep -c '^define ' "$OUTDIR/lowered-fixed.ll")
77 AFTER=$(grep -c '^define ' "$OUTDIR/lowered-stdlib.ll")
78 STUBS=$(wc -l < "$OUTDIR/_main_missing.txt")
79 echo " functions: $BEFORE -> $AFTER (stripped $((BEFORE - AFTER)) main.* defs, added $STUBS stubs)" >&2
80 rm -f "$OUTDIR/_main_refs.txt" "$OUTDIR/_main_defined.txt" "$OUTDIR/_main_missing.txt" "$OUTDIR/lowered-stripped.ll"
81
82 echo "=== Step 3: Compile to wasm object ===" >&2
83 opt -O2 "$OUTDIR/lowered-stdlib.ll" -o "$OUTDIR/lowered.bc"
84 llc -filetype=obj -mtriple=wasm32-unknown-js -O2 --function-sections --data-sections "$OUTDIR/lowered.bc" -o "$OUTDIR/stdlib.o"
85 echo " stdlib.o: $(du -h "$OUTDIR/stdlib.o" | cut -f1)" >&2
86
87 echo "=== Step 4: Build wasm asm stubs ===" >&2
88 TMPDIR=$(mktemp -d)
89 cat > "$TMPDIR/asm_stubs_wasm.c" << 'STUBEOF'
90 #include <stdint.h>
91
92 // No CPU feature detection in wasm
93 void cpuid_stub(uint32_t eax, uint32_t ecx, uint32_t* result)
94 __asm__("internal/cpu.cpuid");
95 void cpuid_stub(uint32_t eax, uint32_t ecx, uint32_t* result) {
96 result[0] = 0; result[1] = 0; result[2] = 0; result[3] = 0;
97 }
98
99 uint64_t xgetbv_stub(uint32_t xcr) __asm__("internal/cpu.xgetbv");
100 uint64_t xgetbv_stub(uint32_t xcr) { return 0; }
101
102 int32_t getgoamd64_stub(void) __asm__("internal/cpu.getGOAMD64level");
103 int32_t getgoamd64_stub(void) { return 1; }
104
105 // Syscall returns ENOSYS - wasm uses WASI imports directly
106 struct syscall6_result { long r1; long r2; long err; };
107 struct syscall6_result syscall6_stub(long num, long a1, long a2, long a3, long a4, long a5, long a6)
108 __asm__("internal/runtime/syscall.Syscall6");
109 struct syscall6_result syscall6_stub(long num, long a1, long a2, long a3, long a4, long a5, long a6) {
110 struct syscall6_result res = {-1, 0, 38}; // ENOSYS
111 return res;
112 }
113
114 void entersyscall_stub(void) __asm__("runtime.entersyscall");
115 void entersyscall_stub(void) {}
116 void exitsyscall_stub(void) __asm__("runtime.exitsyscall");
117 void exitsyscall_stub(void) {}
118 void beforefork_stub(void) __asm__("syscall.runtime_BeforeFork");
119 void beforefork_stub(void) {}
120 void afterfork_stub(void) __asm__("syscall.runtime_AfterFork");
121 void afterfork_stub(void) {}
122 void afterforkinchild_stub(void) __asm__("syscall.runtime_AfterForkInChild");
123 void afterforkinchild_stub(void) {}
124 void doallthreadssyscall_stub(void) __asm__("syscall.runtime_doAllThreadsSyscall");
125 void doallthreadssyscall_stub(void) {}
126 int32_t haswaitingreaders_stub(void) __asm__("syscall.hasWaitingReaders");
127 int32_t haswaitingreaders_stub(void) { return 0; }
128 void cgocaller_stub(void) __asm__("syscall.cgocaller");
129 void cgocaller_stub(void) {}
130 void* makestrongfromweak_stub(void* p) __asm__("weak.runtime_makeStrongFromWeak");
131 void* makestrongfromweak_stub(void* p) { return p; }
132 STUBEOF
133 clang -c --target=wasm32-unknown-js -O2 -ffunction-sections -fdata-sections -o "$OUTDIR/asm_stubs.o" "$TMPDIR/asm_stubs_wasm.c"
134 echo " asm_stubs.o: $(du -h "$OUTDIR/asm_stubs.o" | cut -f1)" >&2
135
136 echo "=== Step 5: Build wasm C runtime ===" >&2
137 RTOBJS=()
138 for f in "$MOXIEROOT"/src/runtime/*.c; do
139 case "$(basename "$f")" in
140 *_windows*|*_darwin*|*_arm64*|*_other*|*_baremetal*|*_linux*|*_unix*) continue ;;
141 gc_boehm*|signal*|spawn_*|secalloc*) continue ;;
142 esac
143 out="$TMPDIR/$(basename "$f").o"
144 clang -c --target=wasm32-unknown-js -O2 -ffunction-sections -fdata-sections -o "$out" "$f" 2>/dev/null && RTOBJS+=("$out")
145 done
146 # Include wasm-specific runtime files
147 for f in "$MOXIEROOT"/src/runtime/*_wasm*; do
148 [ -f "$f" ] || continue
149 case "$(basename "$f")" in *.mx) continue ;; esac
150 out="$TMPDIR/$(basename "$f").o"
151 clang -c --target=wasm32-unknown-js -O2 -ffunction-sections -fdata-sections -o "$out" "$f" 2>/dev/null && RTOBJS+=("$out")
152 done
153 if [ ${#RTOBJS[@]} -gt 0 ]; then
154 wasm-ld -r -o "$OUTDIR/runtime.o" "${RTOBJS[@]}"
155 echo " runtime.o: $(du -h "$OUTDIR/runtime.o" | cut -f1)" >&2
156 else
157 # WASM runtime is entirely in LLVM IR; create empty placeholder
158 echo 'void __wasm_rt_placeholder(void){}' | clang -c --target=wasm32-unknown-js -O2 -x c - -o "$OUTDIR/runtime.o"
159 echo " runtime.o: empty placeholder" >&2
160 fi
161
162 echo "=== Step 6: Create archive ===" >&2
163 rm -f "$OUTDIR/libstdlib.a"
164 llvm-ar rcs "$OUTDIR/libstdlib.a" "$OUTDIR/stdlib.o" "$OUTDIR/asm_stubs.o"
165 echo " libstdlib.a: $(du -h "$OUTDIR/libstdlib.a" | cut -f1)" >&2
166
167 echo "=== Step 7: Install ===" >&2
168 ISKRA_DIR="$HOME/.local/share/moxie-iskra"
169 mkdir -p "$ISKRA_DIR"
170 if [ -f "$OUTDIR/runtime.o" ]; then
171 cp "$OUTDIR/runtime.o" "$ISKRA_DIR/runtime.wasm32.o"
172 echo " installed: $ISKRA_DIR/runtime.wasm32.o" >&2
173 fi
174 cp "$OUTDIR/libstdlib.a" "$ISKRA_DIR/libstdlib.wasm32.a"
175 cp "$OUTDIR/lowered.bc" "$ISKRA_DIR/stdlib.wasm32.bc"
176 cp "$OUTDIR/asm_stubs.o" "$ISKRA_DIR/asm_stubs.wasm32.o"
177 echo " installed: $ISKRA_DIR/libstdlib.wasm32.a" >&2
178 echo " installed: $ISKRA_DIR/stdlib.wasm32.bc ($(du -h "$ISKRA_DIR/stdlib.wasm32.bc" | cut -f1))" >&2
179 echo " installed: $ISKRA_DIR/asm_stubs.wasm32.o" >&2
180
181 echo "=== Done ===" >&2
182 echo "Archive: $OUTDIR/libstdlib.a" >&2
183 echo "Bitcode: $ISKRA_DIR/stdlib.wasm32.bc" >&2
184 echo "Runtime: $ISKRA_DIR/runtime.wasm32.o" >&2
185 rm -rf "$TMPDIR"
186