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