synapse-wasm / build.sh
Robbo
Initial: WASM sensor stack β€” core types, MCU module (32KB), WIT contract
7932636 unverified
#!/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 ""