#!/usr/bin/env bash # Synapse Agriculture — WASM build pipeline # Run from workspace root inside `nix develop` # # This script enforces the correct build ordering: # 1. Native tests (fastest feedback loop) # 2. WASM compilation (catches target-specific issues) # 3. Size profiling (validates MCU memory budget) # 4. Runtime validation (wasm3 / wasmtime) # # Each step gates the next — fail fast, don't waste time. set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' WASM_TARGET="wasm32-unknown-unknown" WASI_TARGET="wasm32-wasip1" SENSOR_WASM="target/${WASM_TARGET}/release/synapse_sensor.wasm" OPTIMIZED_WASM="target/wasm-opt/synapse_sensor.wasm" # MCU memory budget (RP2350: 520KB total SRAM) # wasm3 runtime: ~64KB, LoRa + HAL: ~48KB, heap: ~100KB # Leaves roughly 300KB for the WASM module binary MAX_WASM_SIZE_KB=300 step() { echo -e "\n${CYAN}═══ $1 ═══${NC}\n"; } pass() { echo -e "${GREEN} ✓ $1${NC}"; } fail() { echo -e "${RED} ✗ $1${NC}"; exit 1; } warn() { echo -e "${YELLOW} ⚠ $1${NC}"; } # ── Step 1: Native tests ────────────────────────────────────────────────── # Run all tests on the host architecture (x86_64). # This validates shared types, calibration math, CBOR serialization, # and ASCII parsing WITHOUT any WASM complexity. # This is your fastest iteration loop — stay here until green. step "Step 1: Native tests (all crates)" cargo test --workspace --quiet 2>&1 pass "All native tests passed" # ── Step 2: Compile sensor module to WASM ───────────────────────────────── # Target: wasm32-unknown-unknown (bare WASM, no WASI) # This is what runs on the MCU via wasm3. # cdylib crate-type in synapse-sensor produces the .wasm file. # release profile uses opt-level=z, LTO, panic=abort, strip=true # for minimum binary size. step "Step 2: Compile synapse-sensor → WASM (MCU target)" cargo build \ --package synapse-sensor \ --target "${WASM_TARGET}" \ --release \ --quiet 2>&1 if [ ! -f "${SENSOR_WASM}" ]; then fail "WASM binary not found at ${SENSOR_WASM}" fi RAW_SIZE=$(wc -c < "${SENSOR_WASM}") RAW_SIZE_KB=$((RAW_SIZE / 1024)) pass "Raw WASM binary: ${RAW_SIZE_KB}KB (${RAW_SIZE} bytes)" # ── Step 3: Optimize for size ───────────────────────────────────────────── # wasm-opt from Binaryen does aggressive dead code elimination, # constant folding, and code deduplication. The -Oz flag optimizes # purely for size (vs -O4 which optimizes for speed). # This typically cuts Rust WASM binaries by 40-60%. step "Step 3: Size optimization (wasm-opt -Oz)" mkdir -p "$(dirname ${OPTIMIZED_WASM})" wasm-opt -Oz \ --strip-debug \ --strip-producers \ -o "${OPTIMIZED_WASM}" \ "${SENSOR_WASM}" 2>&1 OPT_SIZE=$(wc -c < "${OPTIMIZED_WASM}") OPT_SIZE_KB=$((OPT_SIZE / 1024)) SAVINGS=$(( (RAW_SIZE - OPT_SIZE) * 100 / RAW_SIZE )) pass "Optimized WASM: ${OPT_SIZE_KB}KB (${OPT_SIZE} bytes, ${SAVINGS}% reduction)" # ── Step 4: MCU memory budget check ────────────────────────────────────── # Hard gate: if the module exceeds the RP2350 memory budget, stop. # Better to catch this now than when flashing hardware in the field. step "Step 4: MCU memory budget check (max ${MAX_WASM_SIZE_KB}KB)" if [ "${OPT_SIZE_KB}" -gt "${MAX_WASM_SIZE_KB}" ]; then fail "WASM module (${OPT_SIZE_KB}KB) exceeds MCU budget (${MAX_WASM_SIZE_KB}KB)" echo " Run: twiggy top ${OPTIMIZED_WASM}" echo " to find what's taking space" exit 1 fi pass "Module fits in MCU budget: ${OPT_SIZE_KB}KB / ${MAX_WASM_SIZE_KB}KB" # ── Step 5: Size profiling ──────────────────────────────────────────────── # Even if we're under budget, know where the bytes are going. # twiggy shows the top functions by size — if serde or fmt # machinery snuck in, you'll see it here. step "Step 5: Size profile (top 15 largest items)" if command -v twiggy &>/dev/null; then twiggy top "${OPTIMIZED_WASM}" -n 15 2>&1 || warn "twiggy failed (non-fatal)" else warn "twiggy not found — skip size profiling" fi # ── Step 6: WASM validation ────────────────────────────────────────────── # wasm-tools validate checks the module against the WASM spec. # Catches issues like invalid opcodes, type mismatches, or # features the MCU runtime doesn't support. step "Step 6: WASM module validation" if command -v wasm-tools &>/dev/null; then wasm-tools validate "${OPTIMIZED_WASM}" 2>&1 pass "Module passes WASM spec validation" else warn "wasm-tools not found — skip validation" fi # ── Step 7: Export inspection ───────────────────────────────────────────── # Verify the module exports the expected guest functions. # The wasm3 runtime on the MCU will look for these by name. step "Step 7: Export verification" if command -v wasm-tools &>/dev/null; then EXPORTS=$(wasm-tools dump "${OPTIMIZED_WASM}" 2>/dev/null | grep -c "export" || echo "0") echo " Module has ${EXPORTS} exports" # Check for our required guest functions for func in guest_init guest_sample guest_reconfigure; do if wasm2wat "${OPTIMIZED_WASM}" 2>/dev/null | grep -q "\"${func}\""; then pass "Found export: ${func}" else fail "Missing required export: ${func}" fi done else warn "wasm-tools not found — skip export check" fi # ── Step 8: Import verification ─────────────────────────────────────────── # Verify the module imports match what the MCU host firmware provides. # Any import the host doesn't implement = crash at instantiation. step "Step 8: Import verification (host function dependencies)" if command -v wabt &>/dev/null && command -v wasm2wat &>/dev/null; then echo " Required host functions:" wasm2wat "${OPTIMIZED_WASM}" 2>/dev/null \ | grep '(import' \ | sed 's/.*"\([^"]*\)".*/ → \1/' \ || warn "Could not extract imports" else warn "wabt not found — skip import check" fi # ── Summary ─────────────────────────────────────────────────────────────── step "BUILD COMPLETE" echo -e " ${GREEN}Native tests: PASS${NC}" echo -e " ${GREEN}WASM compile: PASS${NC}" echo -e " ${GREEN}Size budget: ${OPT_SIZE_KB}KB / ${MAX_WASM_SIZE_KB}KB${NC}" echo -e " ${GREEN}Spec valid: PASS${NC}" echo "" echo " Artifacts:" echo " Raw: ${SENSOR_WASM}" echo " Optimized: ${OPTIMIZED_WASM}" echo "" echo " Next steps:" echo " wasmtime (WASI): needs wasm32-wasip1 target build" echo " wasm3 (MCU sim): wasm3 ${OPTIMIZED_WASM} --func guest_sample" echo " Browser: trunk serve crates/synapse-web" echo ""