diff --git a/CHANGELOG.md b/CHANGELOG.md index dab518b..f452403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,23 +5,50 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] - -### Changed -- Reorganized documentation structure - consolidated 41 root docs into organized `docs/` subdirectories -- Documentation now organized by category: status, sessions, analysis, planning, research, build-systems, validation, architecture +## [0.1.0] - 2026-03-21 ### Added -- GitHub project infrastructure: 40 labels, 4 milestones, 24 issues -- Project board for tracking reorganization progress -## [0.1.0] - TBD +#### Compiler +- WebAssembly-to-ARM Cortex-M AOT compiler +- 197+ WASM opcodes supported (i32, i64, f32, f64, SIMD/Helium) +- Full control flow (block, loop, if/else, br, br_if, br_table, call) +- Sub-word memory operations (load8/16, store8/16) +- memory.size / memory.grow +- Globals, select, i64 register pairs +- ARM Thumb-2 instruction encoding +- VFP/FPU support (f32, f64) +- WASM SIMD to ARM Helium MVE (Cortex-M55) + +#### Output +- ELF binary output with vector table and startup code +- Linker script generation (STM32, nRF52840, generic) +- MPU region configuration +- `synth compile --link` cross-compilation pipeline via arm-none-eabi-gcc + +#### CLI +- `synth compile` -- compile WASM/WAT to ARM ELF +- `synth disasm` -- disassemble ARM ELF binaries +- `synth verify` -- Z3 SMT translation validation +- `synth backends` -- list available compilation backends +- Target profiles: cortex-m3, cortex-m4, cortex-m4f, cortex-m7, cortex-m7dp, cortex-m55 + +#### Verification +- Rocq mechanized proofs (188 Qed / 52 Admitted) +- All i32 operations (arithmetic, division, comparison, bit-manip, shift/rotate) have T1 result-correspondence proofs +- Z3 SMT translation validation (53 verification tests) +- STPA safety analysis (losses, hazards, UCAs, constraints) +- Rivet SDLC artifact traceability (250+ artifacts) + +#### Testing +- 851 tests, all passing +- 227/257 WebAssembly spec test files compile +- Renode ARM Cortex-M4 emulation tests via Bazel -### Planned -- Working CLI: `synth compile input.wasm -o output.elf` -- Calculator demo running on Zephyr/QEMU -- Complete CI/CD pipeline -- Comprehensive documentation +#### Examples & Integrations +- Zephyr RTOS integration examples +- Anti-pinch window controller (automotive safety demo) +- OSxCAR WASM compilation test --- diff --git a/Cargo.toml b/Cargo.toml index 402357a..905f58c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,9 @@ rust-version = "1.88" authors = ["PulseEngine Team"] license = "Apache-2.0" repository = "https://github.com/pulseengine/synth" +homepage = "https://github.com/pulseengine/synth" +keywords = ["webassembly", "arm", "cortex-m", "compiler", "embedded"] +categories = ["compilers", "embedded", "wasm", "no-std"] [workspace.dependencies] # WebAssembly tooling diff --git a/README.md b/README.md index 0702086..965a91d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Synth -WebAssembly-to-ARM compiler with mechanized correctness proofs +WebAssembly-to-ARM Cortex-M AOT compiler with mechanized correctness proofs   @@ -29,7 +29,7 @@   -Synth compiles WebAssembly to native ARM Cortex-M machine code, producing bare-metal ELF binaries for embedded targets. Pattern-based instruction selection, AAPCS calling conventions, and ELF generation -- with mechanized correctness proofs in [Rocq](https://rocq-prover.org/) (formerly Coq) and SMT-based translation validation via Z3. +Synth compiles WebAssembly to native ARM Cortex-M machine code, producing bare-metal ELF binaries for embedded targets. It supports 197+ WASM opcodes (i32, i64, f32, f64, SIMD/Helium), full control flow, memory operations, and globals. Pattern-based instruction selection, AAPCS calling conventions, and ELF generation -- with mechanized correctness proofs in [Rocq](https://rocq-prover.org/) (formerly Coq) and SMT-based translation validation via Z3. Part of [PulseEngine](https://github.com/pulseengine) -- a WebAssembly toolchain for safety-critical systems with mechanized verification: @@ -44,7 +44,7 @@ Part of [PulseEngine](https://github.com/pulseengine) -- a WebAssembly toolchain ### From source (Cargo) -Requires Rust 1.85+ (edition 2024). +Requires Rust 1.88+ (edition 2024). ```bash git clone https://github.com/pulseengine/synth.git @@ -62,6 +62,38 @@ Bazel 8.x builds Rust, Rocq proofs, and Renode emulation tests hermetically via bazel build //crates:synth ``` +## Quick Start + +```bash +# Compile a WAT file to a Cortex-M ELF binary +synth compile examples/wat/simple_add.wat --cortex-m -o firmware.elf + +# Disassemble the result +synth disasm firmware.elf + +# Formally verify the translation +synth verify examples/wat/simple_add.wat firmware.elf +``` + +## Features + +| Category | Status | +|----------|--------| +| i32 arithmetic, bitwise, comparison, shift/rotate | Supported | +| i64 arithmetic (register pairs) | Supported | +| f32/f64 via VFP | Supported | +| WASM SIMD via ARM Helium MVE | Supported (Cortex-M55) | +| Control flow (block, loop, if/else, br, br_if, br_table) | Supported | +| Function calls (direct, indirect) | Supported | +| Memory (load/store, sub-word, size/grow) | Supported | +| Globals, select, unreachable, nop | Supported | +| ELF output with vector table | Supported | +| Linker scripts (STM32, nRF52840, generic) | Supported | +| Cross-compilation via arm-none-eabi-gcc | Supported | +| Rocq mechanized proofs | 188 Qed / 52 Admitted | +| Z3 translation validation | 53 tests passing | +| WebAssembly spec test suite | 227/257 files compile | + ## Usage ### Compile WASM/WAT to ARM ELF @@ -120,7 +152,7 @@ graph LR **Pipeline stages:** 1. **Parse** -- decode WASM binary or WAT text via `wasmparser`/`wat` crates -2. **Instruction selection** -- pattern-match WASM ops to ARM instruction sequences (i32 integer ops; f32/f64/i64 rejected at compile time) +2. **Instruction selection** -- pattern-match WASM ops to ARM instruction sequences (i32, i64, f32, f64, SIMD) 3. **Peephole optimization** -- redundant-op elimination, NOP removal, instruction fusion, constant propagation (0-25% code reduction) 4. **ARM encoding** -- emit 32-bit ARM / Thumb-2 machine code 5. **ELF builder** -- produce ELF32 with `.text`, `.isr_vector`, `.data`, `.bss`, symbol table; optional vector table and reset handler for Cortex-M @@ -134,12 +166,14 @@ Synth employs two complementary verification strategies. Mechanized proofs in Rocq 9 show that `compile_wasm_to_arm` preserves WASM semantics for each operation. The proof suite lives in `coq/Synth/` and covers ARM instruction semantics, WASM stack-machine semantics, and per-operation correctness theorems. ``` -154 Qed / 71 Admitted / 12 axioms - T1: 19 result-correspondence (ARM output = WASM result) +188 Qed / 52 Admitted + T1: 39 result-correspondence (ARM output = WASM result) T2: 95 existence-only (ARM execution succeeds) - T3: 71 admitted (VFP, flags, shifts) + T3: 52 admitted (VFP/float semantics) ``` +All i32 operations (arithmetic, division, comparison, bit-manip, shift/rotate) have full T1 proofs. + Build the proofs: ```bash @@ -173,7 +207,7 @@ The `synth-verify` crate encodes WASM and ARM semantics as Z3 formulas and check ## Testing ```bash -# Run all Rust tests (521 tests across workspace) +# Run all Rust tests (851 tests across workspace) cargo test --workspace # Lint diff --git a/crates/synth-abi/Cargo.toml b/crates/synth-abi/Cargo.toml index da958db..2bf9044 100644 --- a/crates/synth-abi/Cargo.toml +++ b/crates/synth-abi/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "WebAssembly Component Model ABI (lift/lower) for the Synth compiler" [dependencies] synth-wit = { path = "../synth-wit" } diff --git a/crates/synth-analysis/Cargo.toml b/crates/synth-analysis/Cargo.toml index 2323352..867230e 100644 --- a/crates/synth-analysis/Cargo.toml +++ b/crates/synth-analysis/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "SSA and control flow analysis for the Synth compiler" [dependencies] synth-core = { path = "../synth-core" } diff --git a/crates/synth-backend-awsm/Cargo.toml b/crates/synth-backend-awsm/Cargo.toml index 4266f26..4bb6eef 100644 --- a/crates/synth-backend-awsm/Cargo.toml +++ b/crates/synth-backend-awsm/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "aWsm backend integration for the Synth compiler" [dependencies] synth-core = { path = "../synth-core" } diff --git a/crates/synth-backend-wasker/Cargo.toml b/crates/synth-backend-wasker/Cargo.toml index 8dde712..f259e27 100644 --- a/crates/synth-backend-wasker/Cargo.toml +++ b/crates/synth-backend-wasker/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "Wasker backend integration for the Synth compiler" [dependencies] synth-core = { path = "../synth-core" } diff --git a/crates/synth-backend/Cargo.toml b/crates/synth-backend/Cargo.toml index e14e2b6..0cd47f2 100644 --- a/crates/synth-backend/Cargo.toml +++ b/crates/synth-backend/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "ARM encoder, ELF builder, vector table, linker scripts, and MPU configuration" [features] default = ["arm-cortex-m"] diff --git a/crates/synth-cfg/Cargo.toml b/crates/synth-cfg/Cargo.toml index 9d67526..b19d061 100644 --- a/crates/synth-cfg/Cargo.toml +++ b/crates/synth-cfg/Cargo.toml @@ -5,5 +5,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "Control flow graph representation for the Synth compiler" [dependencies] diff --git a/crates/synth-cli/Cargo.toml b/crates/synth-cli/Cargo.toml index b23b6d7..4b2ba8f 100644 --- a/crates/synth-cli/Cargo.toml +++ b/crates/synth-cli/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "CLI for Synth, the WebAssembly-to-ARM Cortex-M AOT compiler" [[bin]] name = "synth" diff --git a/crates/synth-cli/src/main.rs b/crates/synth-cli/src/main.rs index 9f00c30..a95cc7f 100644 --- a/crates/synth-cli/src/main.rs +++ b/crates/synth-cli/src/main.rs @@ -1,6 +1,7 @@ -//! Synth CLI - WebAssembly Component Synthesizer +//! Synth CLI - WebAssembly-to-ARM Cortex-M AOT Compiler //! -//! Command-line interface for the Synth synthesizer. +//! Command-line interface for the Synth compiler. Compiles WASM/WAT files +//! to bare-metal ARM Cortex-M ELF binaries with optional formal verification. use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; @@ -23,7 +24,17 @@ use wast::{Wast, WastDirective}; #[derive(Parser)] #[command(name = "synth")] -#[command(about = "WebAssembly Component Synthesizer for Embedded Systems", long_about = None)] +#[command(about = "WebAssembly-to-ARM Cortex-M AOT compiler")] +#[command( + long_about = "Synth compiles WebAssembly (WASM/WAT) to native ARM Cortex-M machine code,\n\ +producing bare-metal ELF binaries for embedded targets.\n\n\ +Examples:\n \ +synth compile input.wat -o output.elf\n \ +synth compile input.wat --cortex-m -o firmware.elf\n \ +synth compile input.wat --cortex-m --link -o firmware.elf\n \ +synth disasm firmware.elf\n \ +synth verify input.wat firmware.elf" +)] #[command(version)] struct Cli { #[command(subcommand)] @@ -90,7 +101,7 @@ enum Commands { target: String, }, - /// Compile WASM/WAT to ARM ELF + /// Compile WASM/WAT to ARM ELF (e.g., synth compile input.wat --cortex-m -o fw.elf) Compile { /// Input WASM or WAT file (optional, use --demo for built-in demos) #[arg(value_name = "INPUT")] @@ -155,7 +166,7 @@ enum Commands { builtins: Option, }, - /// Disassemble an ARM ELF file + /// Disassemble an ARM ELF file (e.g., synth disasm output.elf) Disasm { /// Input ELF file #[arg(value_name = "INPUT")] @@ -165,7 +176,7 @@ enum Commands { /// List available compilation backends and their status Backends, - /// Verify compilation correctness (translation validation) + /// Verify compilation correctness via Z3 (e.g., synth verify input.wat output.elf) Verify { /// Input WASM or WAT file (source) #[arg(value_name = "WASM")] diff --git a/crates/synth-core/Cargo.toml b/crates/synth-core/Cargo.toml index d12131b..b226628 100644 --- a/crates/synth-core/Cargo.toml +++ b/crates/synth-core/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "Core types, error handling, and backend trait for the Synth compiler" [dependencies] serde.workspace = true diff --git a/crates/synth-frontend/Cargo.toml b/crates/synth-frontend/Cargo.toml index 7572423..0e48fb8 100644 --- a/crates/synth-frontend/Cargo.toml +++ b/crates/synth-frontend/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "WASM/WAT parser and module decoder frontend for the Synth compiler" [dependencies] synth-core = { path = "../synth-core" } diff --git a/crates/synth-memory/Cargo.toml b/crates/synth-memory/Cargo.toml index a05dda0..3ff04d6 100644 --- a/crates/synth-memory/Cargo.toml +++ b/crates/synth-memory/Cargo.toml @@ -2,8 +2,10 @@ name = "synth-memory" version.workspace = true edition.workspace = true +authors.workspace = true license.workspace = true -description = "Portable memory management for Synth WASM-to-ARM compiler" +repository.workspace = true +description = "Portable memory abstraction (Zephyr, Linux, bare-metal) for the Synth compiler" [dependencies] bitflags = "2.4" diff --git a/crates/synth-opt/Cargo.toml b/crates/synth-opt/Cargo.toml index 040ef4f..4cd93e0 100644 --- a/crates/synth-opt/Cargo.toml +++ b/crates/synth-opt/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "Peephole optimization passes for the Synth compiler" [dependencies] synth-cfg = { path = "../synth-cfg" } diff --git a/crates/synth-qemu/Cargo.toml b/crates/synth-qemu/Cargo.toml index 4ed65c1..3aa5361 100644 --- a/crates/synth-qemu/Cargo.toml +++ b/crates/synth-qemu/Cargo.toml @@ -5,5 +5,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "QEMU integration utilities for testing Synth-compiled ARM binaries" [dependencies] diff --git a/crates/synth-synthesis/Cargo.toml b/crates/synth-synthesis/Cargo.toml index e5ce850..5534c99 100644 --- a/crates/synth-synthesis/Cargo.toml +++ b/crates/synth-synthesis/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "WASM-to-ARM instruction selection and peephole optimizer" [dependencies] synth-core = { path = "../synth-core" } diff --git a/crates/synth-test/Cargo.toml b/crates/synth-test/Cargo.toml index 0e429a2..ada0b9c 100644 --- a/crates/synth-test/Cargo.toml +++ b/crates/synth-test/Cargo.toml @@ -4,7 +4,8 @@ version.workspace = true edition.workspace = true authors.workspace = true license.workspace = true -description = "WAST-driven test runner for Synth" +repository.workspace = true +description = "WAST-to-Robot Framework test generator for Renode emulation" [dependencies] wast = { workspace = true } diff --git a/crates/synth-verify/Cargo.toml b/crates/synth-verify/Cargo.toml index 1fae55a..1fcedbb 100644 --- a/crates/synth-verify/Cargo.toml +++ b/crates/synth-verify/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "Z3 SMT translation validation for the Synth compiler" [features] default = ["z3-solver", "arm"] diff --git a/crates/synth-wit/Cargo.toml b/crates/synth-wit/Cargo.toml index 2ab044c..5b11120 100644 --- a/crates/synth-wit/Cargo.toml +++ b/crates/synth-wit/Cargo.toml @@ -5,5 +5,6 @@ edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true +description = "WIT (WebAssembly Interface Types) support for the Synth compiler" [dependencies] diff --git a/docs/design/meld-kiln-integration-instructions.md b/docs/design/meld-kiln-integration-instructions.md new file mode 100644 index 0000000..0c6082b --- /dev/null +++ b/docs/design/meld-kiln-integration-instructions.md @@ -0,0 +1,266 @@ +# Meld & Kiln Integration Instructions for Synth Pipeline + +## Context + +Synth (WASM→ARM Cortex-M AOT compiler) can now compile real-world WASM to ARM ELF binaries. The next step is the full cross-compilation chain: + +``` +WASM Components → meld fuse → Core WASM → synth compile --link → firmware.elf → Cortex-M +``` + +We've verified that the **OSxCAR anti-pinch window controller** (3 WASM components from https://osxcar.de/insights/demonstration/) compiles through synth individually. The goal is to fuse them via meld, then compile+link the fused output into a single firmware.elf that runs on Renode. + +--- + +## What Synth Provides Today + +- `synth compile -o output.elf --cortex-m --all-exports` — AOT compiles core WASM to ARM Cortex-M ELF +- `synth compile ... --link --builtins ` — Compiles AND links into firmware.elf +- Relocatable ELF output with `__meld_dispatch_import` undefined symbol + `.meld_import_table` section +- Linker script generation with Meld integration sections +- 197+ WASM opcodes, 227/257 spec files compile, 851 tests + +--- + +## What Meld Needs To Do + +### Step 1: Fuse the OSxCAR Components + +The 3 OSxCAR WASM modules are already-lowered core modules (not full P2 components). They were lowered by wit-component before being base64-embedded in JavaScript. Meld should be able to fuse them: + +```bash +meld fuse anti_pinch_v2.wasm motor_driver_v2.wasm soft_start_stop.wasm -o fused_antipinch.wasm --memory shared +``` + +**Key requirement:** The output must be a **single-memory core module** (not multi-memory). Synth targets single-memory ARM Cortex-M. Options: +1. `--memory shared` with `--address-rebase` (preferred if stable) +2. OR synth adds multi-memory support (more work) + +### Step 2: Verify the Fused Output + +```bash +meld inspect fused_antipinch.wasm --types --interfaces +``` + +Expected output should show: +- Single core module (not component) +- Merged exports from all 3 components +- Unresolved WASI imports listed as core imports +- Single memory + +### Step 3: Import Map (nice-to-have) + +Add `--emit-import-map imports.json` flag that produces: + +```json +{ + "imports": [ + {"index": 0, "module": "wasi:cli/stderr@0.2.6", "name": "get-stderr"}, + {"index": 1, "module": "wasi:io/streams@0.2.6", "name": "[method]output-stream.blocking-write-and-flush"} + ] +} +``` + +This tells synth (and kiln-builtins) exactly which import index maps to which WASI function, enabling the dispatcher to route calls correctly. + +### Step 4: Test with Synth + +After fusing, synth should be able to compile the output: + +```bash +synth compile fused_antipinch.wasm -o antipinch.elf --cortex-m --all-exports +``` + +If this fails, the error message will tell us what WASM opcodes or features the fused module uses that synth doesn't support yet. + +--- + +## What Kiln Needs To Do + +### Step 1: Create `kiln-builtins` Crate + +A new `no_std` crate in the kiln workspace. This is the **C ABI bridge** between synth-compiled ARM code and kiln's runtime services. + +**Location:** `kiln-builtins/` + +**Cargo.toml:** +```toml +[package] +name = "kiln-builtins" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["staticlib"] # Produces libkiln_builtins.a + +[dependencies] +kiln-foundation = { path = "../kiln-foundation", default-features = false } + +[features] +default = [] +std = ["kiln-foundation/std"] +``` + +**Target:** `thumbv7em-none-eabi` (Cortex-M4F) and `thumbv7em-none-eabihf` (hard-float) + +### Step 2: Implement Core Functions + +The minimum viable set (3 functions): + +```rust +#![no_std] + +/// Called by synth-compiled code: MOV R0, #import_index; BL __meld_dispatch_import +/// Routes to the appropriate WASI/host function based on the import index. +#[unsafe(no_mangle)] +pub extern "C" fn __meld_dispatch_import(import_index: u32) -> u32 { + // Read import table from .meld_import_table section (linker-provided symbols) + // Match module/name to handler + // Call handler with args from linear memory + // Return result + 0 // stub for Phase 1 +} + +/// Returns the base address of WASM linear memory. +#[unsafe(no_mangle)] +pub extern "C" fn __meld_get_memory_base() -> *mut u8 { + extern "C" { static __wasm_memory_start: u8; } + unsafe { &raw const __wasm_memory_start as *mut u8 } +} + +/// Canonical ABI allocator for linear memory. +/// Called by adapter code to allocate space for strings, lists, records. +/// For embedded: bump allocator within linear memory bounds. +#[unsafe(no_mangle)] +pub extern "C" fn cabi_realloc( + old_ptr: u32, + old_size: u32, + align: u32, + new_size: u32, +) -> u32 { + // Bump allocator: maintain a pointer at a known memory location + // Allocate new_size bytes at current pointer, advance pointer + // If old_ptr != 0, this is a realloc (copy old data, return new ptr) + // Return offset from memory base (not absolute address) + 0 // stub for Phase 1 +} +``` + +### Step 3: WASI Embedded Dispatcher + +For the anti-pinch demo, the minimum WASI needed is: +- `wasi:cli/stderr` — error output (can stub to UART/semihosting) +- `wasi:io/streams` — blocking write (route to UART) +- `wasi:clocks/monotonic-clock` — timer (read SysTick) + +Source material exists in `kiln-wasi/src/dispatcher.rs` — extract the dispatch logic into no_std-compatible form. + +### Step 4: Build for ARM Target + +```bash +cargo build --target thumbv7em-none-eabi -p kiln-builtins --release +# Produces: target/thumbv7em-none-eabi/release/libkiln_builtins.a +``` + +### Step 5: Test with Synth + +```bash +# Compile WASM to relocatable object +synth compile fused_antipinch.wasm -o module.o --cortex-m --all-exports + +# Link with kiln-builtins +arm-none-eabi-gcc -nostartfiles -nostdlib -mcpu=cortex-m4 -mthumb \ + -T synth_generated.ld module.o libkiln_builtins.a -o firmware.elf + +# OR use synth's --link flag: +synth compile fused_antipinch.wasm -o firmware.elf \ + --cortex-m --all-exports --link --builtins libkiln_builtins.a +``` + +--- + +## The OSxCAR Test Case + +### What We Have + +Three WASM modules extracted from https://osxcar.de/insights/demonstration/: + +| Component | WASM Size | ARM Code | Functions | +|-----------|-----------|----------|-----------| +| anti_pinch_v2 | 13,294 bytes | 8,392 bytes | initialize, step, terminate | +| motor_driver_v2 | 2,574 bytes | 1,844 bytes | initialize, step, terminate | +| soft_start_stop | 1,785 bytes | 1,728 bytes | step, terminate | + +WIT interface: `aptiv:antipinch/anti-pinch-v2@0.1.0`, `aptiv:antipinch/motor-driver-v2@0.1.0`, `aptiv:antipinch/soft-start-stop@0.1.0` + +### Test Script + +`tests/integration/fetch_osxcar_wasm.sh` fetches these from the website, compiles each individually through synth (all 3 pass). + +### End-to-End Goal + +```bash +# 1. Fetch WASM components +bash tests/integration/fetch_osxcar_wasm.sh # → /tmp/osxcar_*.wasm + +# 2. Fuse with meld (MELD TODO) +meld fuse /tmp/osxcar_antipinch_0.wasm /tmp/osxcar_motor_driver_0.wasm \ + /tmp/osxcar_soft_start_0.wasm -o /tmp/fused.wasm --memory shared + +# 3. Compile + link with synth (SYNTH READY) +synth compile /tmp/fused.wasm -o /tmp/firmware.elf \ + --cortex-m --all-exports --link --builtins libkiln_builtins.a + +# 4. Run on Renode (verification) +renode --execute "machine LoadPlatformDescription @synth_cortex_m.repl; \ + sysbus LoadELF @/tmp/firmware.elf; start" +``` + +--- + +## Synth's Expectations (Contract) + +### From Meld: +1. Output is a **single core WASM module** (not a component, not multi-memory) +2. Unresolved imports have human-readable module/name strings +3. Exports are the merged set of all component exports +4. Memory is single, shared (OR synth needs to add multi-memory — tell us) +5. `cabi_realloc` is either exported by the fused module or provided externally + +### From Kiln-builtins: +1. `__meld_dispatch_import(u32) -> u32` — C ABI, AAPCS calling convention +2. `__meld_get_memory_base() -> *mut u8` — returns `__wasm_memory_start` linker symbol +3. `cabi_realloc(u32, u32, u32, u32) -> u32` — bump allocator in linear memory +4. Compiled as `libkiln_builtins.a` for `thumbv7em-none-eabi` +5. All symbols have `#[unsafe(no_mangle)]` / `extern "C"` + +### Linker Symbols Synth Provides: +- `__wasm_memory_start` / `__wasm_memory_end` — linear memory bounds +- `__meld_import_table_start` / `__meld_import_table_end` — import metadata +- All exported WASM functions as global symbols (with Thumb bit set) + +### Linker Symbols Synth Expects (undefined): +- `__meld_dispatch_import` — provided by kiln-builtins.a +- `__meld_get_memory_base` — provided by kiln-builtins.a + +--- + +## Priority Order + +1. **Kiln: Create kiln-builtins stub** (even returning 0 for all imports — enables linking) +2. **Meld: Test fusing OSxCAR components** (verify single-memory output works) +3. **Kiln: Implement cabi_realloc** (bump allocator — enables canonical ABI) +4. **Meld: Add --emit-import-map** (enables intelligent dispatch) +5. **Kiln: WASI embedded dispatcher** (clocks, stdout — enables output verification) +6. **All: End-to-end Renode test** (the goal) + +## Rivet Artifacts + +All requirements are traced in synth's rivet artifacts: +- `artifacts/kiln-builtins-api.yaml` — KB-001..005 (API requirements) +- `artifacts/compilation-chain.yaml` — CC-001..008 (pipeline stages) +- `artifacts/static-linking.yaml` — SL-001..007 (linker requirements) +- `artifacts/e2e-verification.yaml` — E2E-VER-001..010 (test plan) +- `artifacts/cross-compilation.yaml` — XC-001..009 (toolchain requirements) + +Cross-link with `kiln:` and `meld:` prefixes in rivet externals. diff --git a/tests/wast/BUILD.bazel b/tests/wast/BUILD.bazel index e839316..aab4a03 100644 --- a/tests/wast/BUILD.bazel +++ b/tests/wast/BUILD.bazel @@ -188,6 +188,26 @@ wast_multi_func_test( tags = ["control", "loop", "factorial"], ) +# ============================================================================= +# Example application tests +# ============================================================================= + +# --- Calculator example (arithmetic, bitwise, accumulator, comparison) --- + +wast_multi_func_test( + name = "calculator", + wast = "calculator.wast", + tags = ["example", "calculator"], +) + +# --- Anti-pinch controller example (jam detection, PWM ramping, safety) --- + +wast_multi_func_test( + name = "anti_pinch", + wast = "anti_pinch.wast", + tags = ["example", "anti-pinch", "safety"], +) + # ============================================================================= # Official WebAssembly Spec Tests # ============================================================================= diff --git a/tests/wast/anti_pinch.wast b/tests/wast/anti_pinch.wast new file mode 100644 index 0000000..ebafd50 --- /dev/null +++ b/tests/wast/anti_pinch.wast @@ -0,0 +1,608 @@ +;; Anti-Pinch Window Controller -- WAST test suite +;; +;; Safety-critical automotive use case: prevents window motor from injuring +;; occupants by detecting jam conditions via motor current monitoring. +;; +;; Derived from examples/anti-pinch-controller/anti_pinch.wat +;; +;; Note: Each test runs in a fresh machine with zero-initialized memory. +;; Stateful sequences (init -> set_target -> tick) cannot be tested across +;; separate assertions. Tests focus on: +;; - Pure functions: check_jam (parameter-only logic) +;; - Getter functions: verify zero-initialized state readback +;; - ramp_pwm: reads ramp_rate from memory[28] (0 when not init'd, snaps to target) +;; - init + getter combos via acc_add-style single-invocation tests + +(module + ;; Linear memory: 1 page (64KB) for controller state + (memory (export "memory") 1) + + ;; ========================================================================= + ;; init -- Initialize controller to safe defaults + ;; ========================================================================= + (func (export "init") + ;; window_position = 0 (fully open) + i32.const 0 + i32.const 0 + i32.store + + ;; motor_current_ma = 0 + i32.const 4 + i32.const 0 + i32.store + + ;; pwm_duty = 0 + i32.const 8 + i32.const 0 + i32.store + + ;; direction = 0 (stopped) + i32.const 12 + i32.const 0 + i32.store + + ;; jam_detected = 0 + i32.const 16 + i32.const 0 + i32.store + + ;; current_threshold_ma = 800 (800mA default threshold) + i32.const 20 + i32.const 800 + i32.store + + ;; target_position = 0 (fully open) + i32.const 24 + i32.const 0 + i32.store + + ;; pwm_ramp_rate = 20 (2.0% per tick for soft start/stop) + i32.const 28 + i32.const 20 + i32.store + + ;; consecutive_over = 0 + i32.const 32 + i32.const 0 + i32.store + + ;; jam_debounce_limit = 3 (3 consecutive ticks to confirm jam) + i32.const 36 + i32.const 3 + i32.store + ) + + ;; ========================================================================= + ;; set_target -- Set desired window position (0=open, 1000=closed) + ;; ========================================================================= + (func (export "set_target") (param $target i32) + (local $clamped i32) + + ;; Clamp target to 0-1000 + local.get $target + local.set $clamped + + ;; if target < 0, clamp to 0 + local.get $clamped + i32.const 0 + i32.lt_s + if + i32.const 0 + local.set $clamped + end + + ;; if target > 1000, clamp to 1000 + local.get $clamped + i32.const 1000 + i32.gt_s + if + i32.const 1000 + local.set $clamped + end + + ;; Store clamped target + i32.const 24 + local.get $clamped + i32.store + + ;; Clear jam flag when new target is set (allows retry) + i32.const 16 + i32.const 0 + i32.store + + ;; Reset debounce counter + i32.const 32 + i32.const 0 + i32.store + ) + + ;; ========================================================================= + ;; update_current -- Feed motor current sensor reading (milliamps) + ;; ========================================================================= + (func (export "update_current") (param $current_ma i32) + i32.const 4 + local.get $current_ma + i32.store + ) + + ;; ========================================================================= + ;; check_jam -- Check if current exceeds threshold (jam detection) + ;; Returns: 1 if jam detected, 0 otherwise + ;; ========================================================================= + (func (export "check_jam") (param $current_ma i32) (param $threshold_ma i32) (result i32) + local.get $current_ma + local.get $threshold_ma + i32.gt_s + if (result i32) + i32.const 1 + else + i32.const 0 + end + ) + + ;; ========================================================================= + ;; ramp_pwm -- Soft start/stop: ramp current PWM toward target PWM + ;; Returns: new PWM value after ramping by one step + ;; ========================================================================= + (func (export "ramp_pwm") (param $current_pwm i32) (param $target_pwm i32) (result i32) + (local $ramp_rate i32) + (local $diff i32) + (local $result i32) + + ;; Load ramp rate from memory + i32.const 28 + i32.load + local.set $ramp_rate + + ;; Calculate difference: target - current + local.get $target_pwm + local.get $current_pwm + i32.sub + local.set $diff + + ;; If diff > ramp_rate, step up by ramp_rate + local.get $diff + local.get $ramp_rate + i32.gt_s + if + local.get $current_pwm + local.get $ramp_rate + i32.add + local.set $result + else + ;; If diff < -ramp_rate, step down by ramp_rate + local.get $diff + i32.const 0 + local.get $ramp_rate + i32.sub + i32.lt_s + if + local.get $current_pwm + local.get $ramp_rate + i32.sub + local.set $result + else + ;; Close enough -- snap to target + local.get $target_pwm + local.set $result + end + end + + ;; Clamp result to 0-1000 + local.get $result + i32.const 0 + i32.lt_s + if + i32.const 0 + local.set $result + end + local.get $result + i32.const 1000 + i32.gt_s + if + i32.const 1000 + local.set $result + end + + local.get $result + ) + + ;; ========================================================================= + ;; tick -- Main control cycle (called at 100Hz) + ;; Returns: PWM duty cycle (0-1000) + ;; ========================================================================= + (func (export "tick") (result i32) + (local $position i32) + (local $target i32) + (local $current_ma i32) + (local $threshold i32) + (local $direction i32) + (local $pwm i32) + (local $target_pwm i32) + (local $new_pwm i32) + (local $position_delta i32) + (local $consecutive i32) + (local $debounce_limit i32) + (local $ramp_rate i32) + (local $diff i32) + + ;; Load state from memory + i32.const 0 + i32.load + local.set $position + + i32.const 24 + i32.load + local.set $target + + i32.const 4 + i32.load + local.set $current_ma + + i32.const 20 + i32.load + local.set $threshold + + i32.const 8 + i32.load + local.set $pwm + + i32.const 32 + i32.load + local.set $consecutive + + i32.const 36 + i32.load + local.set $debounce_limit + + ;; Check if jam already flagged -- if so, keep motor stopped + i32.const 16 + i32.load + i32.const 1 + i32.eq + if + ;; Jam flagged: force PWM to 0, direction to stopped + i32.const 8 + i32.const 0 + i32.store + i32.const 12 + i32.const 0 + i32.store + i32.const 0 + return + end + + ;; Determine direction: compare position to target + local.get $target + local.get $position + i32.gt_s + if + ;; target > position: closing (moving toward fully closed) + i32.const 1 + local.set $direction + ;; target PWM = 800 (80% duty for normal closing) + i32.const 800 + local.set $target_pwm + else + local.get $target + local.get $position + i32.lt_s + if + ;; target < position: opening (moving toward fully open) + i32.const 2 + local.set $direction + ;; target PWM = 800 (80% duty for normal opening) + i32.const 800 + local.set $target_pwm + else + ;; target == position: stop motor + i32.const 0 + local.set $direction + i32.const 0 + local.set $target_pwm + end + end + + ;; Store direction + i32.const 12 + local.get $direction + i32.store + + ;; --- JAM DETECTION (only while closing, direction==1) --- + local.get $direction + i32.const 1 + i32.eq + if + ;; Check if current exceeds threshold + local.get $current_ma + local.get $threshold + i32.gt_s + if + ;; Increment consecutive over-threshold counter + local.get $consecutive + i32.const 1 + i32.add + local.set $consecutive + + ;; Store updated counter + i32.const 32 + local.get $consecutive + i32.store + + ;; Check if debounce limit reached + local.get $consecutive + local.get $debounce_limit + i32.ge_s + if + ;; JAM CONFIRMED: emergency stop + ;; Set jam flag + i32.const 16 + i32.const 1 + i32.store + ;; Stop motor + i32.const 8 + i32.const 0 + i32.store + ;; Set direction to stopped + i32.const 12 + i32.const 0 + i32.store + ;; Reset debounce counter + i32.const 32 + i32.const 0 + i32.store + ;; Return 0 PWM immediately + i32.const 0 + return + end + else + ;; Current below threshold: reset debounce counter + i32.const 32 + i32.const 0 + i32.store + i32.const 0 + local.set $consecutive + end + end + + ;; --- PWM RAMPING (soft start/stop) --- + ;; Ramp current PWM toward target PWM + ;; Inline ramp logic (same as ramp_pwm function) + i32.const 28 + i32.load + local.set $ramp_rate + + local.get $target_pwm + local.get $pwm + i32.sub + local.set $diff + + local.get $diff + local.get $ramp_rate + i32.gt_s + if + local.get $pwm + local.get $ramp_rate + i32.add + local.set $new_pwm + else + local.get $diff + i32.const 0 + local.get $ramp_rate + i32.sub + i32.lt_s + if + local.get $pwm + local.get $ramp_rate + i32.sub + local.set $new_pwm + else + local.get $target_pwm + local.set $new_pwm + end + end + + ;; Clamp new_pwm to 0-1000 + local.get $new_pwm + i32.const 0 + i32.lt_s + if + i32.const 0 + local.set $new_pwm + end + local.get $new_pwm + i32.const 1000 + i32.gt_s + if + i32.const 1000 + local.set $new_pwm + end + + ;; Store new PWM + i32.const 8 + local.get $new_pwm + i32.store + + ;; --- POSITION UPDATE --- + ;; Estimate position change based on direction and PWM + ;; position_delta = new_pwm / 100 (scale: at 100% PWM, ~10 units/tick = 1%/tick) + local.get $new_pwm + i32.const 100 + i32.div_s + local.set $position_delta + + local.get $direction + i32.const 1 + i32.eq + if + ;; Closing: position increases + local.get $position + local.get $position_delta + i32.add + local.set $position + ;; Clamp to target (don't overshoot) + local.get $position + local.get $target + i32.gt_s + if + local.get $target + local.set $position + end + else + local.get $direction + i32.const 2 + i32.eq + if + ;; Opening: position decreases + local.get $position + local.get $position_delta + i32.sub + local.set $position + ;; Clamp to target (don't undershoot) + local.get $position + local.get $target + i32.lt_s + if + local.get $target + local.set $position + end + end + end + + ;; Store updated position + i32.const 0 + local.get $position + i32.store + + ;; Check if position reached target + local.get $position + local.get $target + i32.eq + if + ;; Reached target: stop motor + i32.const 12 + i32.const 0 + i32.store + i32.const 8 + i32.const 0 + i32.store + i32.const 0 + return + end + + ;; Return current PWM duty cycle + local.get $new_pwm + ) + + ;; ========================================================================= + ;; Getter functions for reading controller state + ;; ========================================================================= + + (func (export "get_position") (result i32) + i32.const 0 + i32.load + ) + + (func (export "get_pwm") (result i32) + i32.const 8 + i32.load + ) + + (func (export "get_direction") (result i32) + i32.const 12 + i32.load + ) + + (func (export "is_jam_detected") (result i32) + i32.const 16 + i32.load + ) + + ;; ========================================================================= + ;; clear_jam -- Reset jam flag for retry + ;; ========================================================================= + (func (export "clear_jam") + i32.const 16 + i32.const 0 + i32.store + + ;; Reset debounce counter + i32.const 32 + i32.const 0 + i32.store + ) +) + +;; ============================================================================= +;; Jam detection tests -- check_jam is a pure function (no memory side effects) +;; ============================================================================= + +;; Current exceeds threshold: jam detected +(assert_return (invoke "check_jam" (i32.const 1200) (i32.const 800)) (i32.const 1)) + +;; Current well above threshold +(assert_return (invoke "check_jam" (i32.const 5000) (i32.const 800)) (i32.const 1)) + +;; Current below threshold: no jam +(assert_return (invoke "check_jam" (i32.const 500) (i32.const 800)) (i32.const 0)) + +;; Current exactly at threshold: no jam (gt_s, not ge_s) +(assert_return (invoke "check_jam" (i32.const 800) (i32.const 800)) (i32.const 0)) + +;; Current just above threshold +(assert_return (invoke "check_jam" (i32.const 801) (i32.const 800)) (i32.const 1)) + +;; Zero current, zero threshold: no jam (0 is not > 0) +(assert_return (invoke "check_jam" (i32.const 0) (i32.const 0)) (i32.const 0)) + +;; Zero current, positive threshold: no jam +(assert_return (invoke "check_jam" (i32.const 0) (i32.const 800)) (i32.const 0)) + +;; Different threshold values +(assert_return (invoke "check_jam" (i32.const 400) (i32.const 300)) (i32.const 1)) +(assert_return (invoke "check_jam" (i32.const 100) (i32.const 500)) (i32.const 0)) + +;; ============================================================================= +;; PWM ramping tests +;; +;; ramp_pwm reads ramp_rate from memory[28]. In a fresh machine, memory is +;; zero-initialized so ramp_rate = 0. With ramp_rate = 0: +;; diff > 0 is never > 0 (ramp_rate), and diff < -0 is never < 0, +;; so it always snaps to target_pwm (the else branch). +;; ============================================================================= + +;; With ramp_rate=0 (uninit'd memory), ramp_pwm snaps directly to target +(assert_return (invoke "ramp_pwm" (i32.const 0) (i32.const 100)) (i32.const 100)) +(assert_return (invoke "ramp_pwm" (i32.const 0) (i32.const 500)) (i32.const 500)) +(assert_return (invoke "ramp_pwm" (i32.const 500) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "ramp_pwm" (i32.const 100) (i32.const 100)) (i32.const 100)) ;; already at target +(assert_return (invoke "ramp_pwm" (i32.const 800) (i32.const 1000)) (i32.const 1000)) +(assert_return (invoke "ramp_pwm" (i32.const 1000) (i32.const 800)) (i32.const 800)) + +;; Edge: target at clamping boundaries +(assert_return (invoke "ramp_pwm" (i32.const 0) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "ramp_pwm" (i32.const 0) (i32.const 1000)) (i32.const 1000)) +(assert_return (invoke "ramp_pwm" (i32.const 1000) (i32.const 0)) (i32.const 0)) + +;; ============================================================================= +;; Getter functions with zero-initialized memory +;; Each runs in a fresh machine so memory is all zeros +;; ============================================================================= +(assert_return (invoke "get_position") (i32.const 0)) +(assert_return (invoke "get_pwm") (i32.const 0)) +(assert_return (invoke "get_direction") (i32.const 0)) +(assert_return (invoke "is_jam_detected") (i32.const 0)) + +;; ============================================================================= +;; Tick with zero-initialized memory +;; +;; With all memory = 0: +;; position=0, target=0, current_ma=0, threshold=0, pwm=0 +;; jam_detected=0 (no early return) +;; target(0) == position(0) -> direction=0, target_pwm=0 +;; ramp_rate=0, diff=0-0=0, not > 0, not < -0 -> snap: new_pwm=0 +;; position_delta = 0/100 = 0 +;; direction=0: no position update +;; position(0) == target(0) -> reached target, return 0 +;; ============================================================================= +(assert_return (invoke "tick") (i32.const 0)) diff --git a/tests/wast/calculator.wast b/tests/wast/calculator.wast new file mode 100644 index 0000000..54b9ae1 --- /dev/null +++ b/tests/wast/calculator.wast @@ -0,0 +1,256 @@ +;; Calculator -- comprehensive WAST test suite +;; +;; Exercises the calculator example's i32 arithmetic, bitwise ops, +;; accumulator (memory load/store), and comparison/selection logic. +;; Derived from examples/zephyr-calculator/calculator.wat + +(module + ;; One page of linear memory for accumulator state + (memory (export "memory") 1) + + ;; --------------------------------------------------------------- + ;; Basic arithmetic + ;; --------------------------------------------------------------- + + ;; add(a, b) -> a + b + (func (export "add") (param $a i32) (param $b i32) (result i32) + local.get $a + local.get $b + i32.add + ) + + ;; sub(a, b) -> a - b + (func (export "sub") (param $a i32) (param $b i32) (result i32) + local.get $a + local.get $b + i32.sub + ) + + ;; mul(a, b) -> a * b + (func (export "mul") (param $a i32) (param $b i32) (result i32) + local.get $a + local.get $b + i32.mul + ) + + ;; div_safe(a, b) -> a / b, returns 0 when b == 0 + (func (export "div_safe") (param $a i32) (param $b i32) (result i32) + (if (result i32) (i32.eqz (local.get $b)) + (then (i32.const 0)) + (else (i32.div_s (local.get $a) (local.get $b))) + ) + ) + + ;; --------------------------------------------------------------- + ;; Accumulator operations (use linear memory at offset 0) + ;; --------------------------------------------------------------- + + ;; acc_set(v) -- store v to memory[0..3] + (func (export "acc_set") (param $v i32) + i32.const 0 + local.get $v + i32.store + ) + + ;; acc_get() -> value at memory[0..3] + (func (export "acc_get") (result i32) + i32.const 0 + i32.load + ) + + ;; acc_add(v) -> acc += v, return new accumulator value + (func (export "acc_add") (param $v i32) (result i32) + (local $new i32) + ;; new = memory[0] + v + i32.const 0 + i32.load + local.get $v + i32.add + local.set $new + ;; memory[0] = new + i32.const 0 + local.get $new + i32.store + ;; return new + local.get $new + ) + + ;; --------------------------------------------------------------- + ;; Bitwise operations + ;; --------------------------------------------------------------- + + ;; bit_and(a, b) -> a & b + (func (export "bit_and") (param $a i32) (param $b i32) (result i32) + local.get $a + local.get $b + i32.and + ) + + ;; bit_or(a, b) -> a | b + (func (export "bit_or") (param $a i32) (param $b i32) (result i32) + local.get $a + local.get $b + i32.or + ) + + ;; bit_xor(a, b) -> a ^ b + (func (export "bit_xor") (param $a i32) (param $b i32) (result i32) + local.get $a + local.get $b + i32.xor + ) + + ;; bit_not(a) -> ~a (implemented as a XOR 0xFFFFFFFF) + (func (export "bit_not") (param $a i32) (result i32) + local.get $a + i32.const -1 + i32.xor + ) + + ;; --------------------------------------------------------------- + ;; Comparison / selection + ;; --------------------------------------------------------------- + + ;; max(a, b) -> the larger value (signed) + (func (export "max") (param $a i32) (param $b i32) (result i32) + (if (result i32) (i32.gt_s (local.get $a) (local.get $b)) + (then (local.get $a)) + (else (local.get $b)) + ) + ) + + ;; min(a, b) -> the smaller value (signed) + (func (export "min") (param $a i32) (param $b i32) (result i32) + (if (result i32) (i32.lt_s (local.get $a) (local.get $b)) + (then (local.get $a)) + (else (local.get $b)) + ) + ) + + ;; abs(x) -> |x| + (func (export "abs") (param $x i32) (result i32) + (if (result i32) (i32.lt_s (local.get $x) (i32.const 0)) + (then (i32.sub (i32.const 0) (local.get $x))) + (else (local.get $x)) + ) + ) +) + +;; ============================================================================= +;; Addition tests +;; ============================================================================= +(assert_return (invoke "add" (i32.const 5) (i32.const 3)) (i32.const 8)) +(assert_return (invoke "add" (i32.const 0) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "add" (i32.const -1) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "add" (i32.const 100) (i32.const 200)) (i32.const 300)) +(assert_return (invoke "add" (i32.const -5) (i32.const -3)) (i32.const -8)) +(assert_return (invoke "add" (i32.const 0x7FFFFFFF) (i32.const 1)) (i32.const 0x80000000)) ;; overflow wraps + +;; ============================================================================= +;; Subtraction tests +;; ============================================================================= +(assert_return (invoke "sub" (i32.const 10) (i32.const 3)) (i32.const 7)) +(assert_return (invoke "sub" (i32.const 0) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "sub" (i32.const 5) (i32.const 10)) (i32.const -5)) +(assert_return (invoke "sub" (i32.const -1) (i32.const -1)) (i32.const 0)) +(assert_return (invoke "sub" (i32.const 1000) (i32.const 1)) (i32.const 999)) + +;; ============================================================================= +;; Multiplication tests +;; ============================================================================= +(assert_return (invoke "mul" (i32.const 6) (i32.const 7)) (i32.const 42)) +(assert_return (invoke "mul" (i32.const 0) (i32.const 100)) (i32.const 0)) +(assert_return (invoke "mul" (i32.const -2) (i32.const 3)) (i32.const -6)) +(assert_return (invoke "mul" (i32.const -2) (i32.const -3)) (i32.const 6)) +(assert_return (invoke "mul" (i32.const 1) (i32.const 42)) (i32.const 42)) +(assert_return (invoke "mul" (i32.const 100) (i32.const 100)) (i32.const 10000)) + +;; ============================================================================= +;; Safe division tests (returns 0 on divide-by-zero) +;; ============================================================================= +(assert_return (invoke "div_safe" (i32.const 10) (i32.const 3)) (i32.const 3)) +(assert_return (invoke "div_safe" (i32.const 10) (i32.const 0)) (i32.const 0)) ;; safe div by zero +(assert_return (invoke "div_safe" (i32.const 0) (i32.const 5)) (i32.const 0)) +(assert_return (invoke "div_safe" (i32.const 100) (i32.const 10)) (i32.const 10)) +(assert_return (invoke "div_safe" (i32.const -10) (i32.const 3)) (i32.const -3)) ;; signed division +(assert_return (invoke "div_safe" (i32.const 7) (i32.const 2)) (i32.const 3)) ;; truncation +(assert_return (invoke "div_safe" (i32.const -7) (i32.const 2)) (i32.const -3)) ;; signed truncation +(assert_return (invoke "div_safe" (i32.const 1) (i32.const 1)) (i32.const 1)) + +;; ============================================================================= +;; Accumulator tests +;; Memory starts at zero, each test runs in a fresh machine so acc starts at 0 +;; ============================================================================= + +;; acc_get reads memory[0] which starts at 0 +(assert_return (invoke "acc_get") (i32.const 0)) + +;; acc_add adds to memory[0] (starts at 0): 0 + 42 = 42 +(assert_return (invoke "acc_add" (i32.const 42)) (i32.const 42)) + +;; acc_add with 0: 0 + 0 = 0 +(assert_return (invoke "acc_add" (i32.const 0)) (i32.const 0)) + +;; acc_add with negative: 0 + (-10) = -10 +(assert_return (invoke "acc_add" (i32.const -10)) (i32.const -10)) + +;; ============================================================================= +;; Bitwise AND tests +;; ============================================================================= +(assert_return (invoke "bit_and" (i32.const 0xFF) (i32.const 0x0F)) (i32.const 0x0F)) +(assert_return (invoke "bit_and" (i32.const 0x12345678) (i32.const 0x0000FFFF)) (i32.const 0x00005678)) +(assert_return (invoke "bit_and" (i32.const 0) (i32.const 0xFFFFFFFF)) (i32.const 0)) +(assert_return (invoke "bit_and" (i32.const -1) (i32.const -1)) (i32.const -1)) + +;; ============================================================================= +;; Bitwise OR tests +;; ============================================================================= +(assert_return (invoke "bit_or" (i32.const 0xF0) (i32.const 0x0F)) (i32.const 0xFF)) +(assert_return (invoke "bit_or" (i32.const 0) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "bit_or" (i32.const 0xAAAA0000) (i32.const 0x0000BBBB)) (i32.const 0xAAAABBBB)) + +;; ============================================================================= +;; Bitwise XOR tests +;; ============================================================================= +(assert_return (invoke "bit_xor" (i32.const 0xFF) (i32.const 0xFF)) (i32.const 0)) +(assert_return (invoke "bit_xor" (i32.const 0xAAAA) (i32.const 0x5555)) (i32.const 0xFFFF)) +(assert_return (invoke "bit_xor" (i32.const 0) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "bit_xor" (i32.const 123) (i32.const 0)) (i32.const 123)) + +;; ============================================================================= +;; Bitwise NOT tests (~a via XOR -1) +;; ============================================================================= +(assert_return (invoke "bit_not" (i32.const 0)) (i32.const -1)) +(assert_return (invoke "bit_not" (i32.const -1)) (i32.const 0)) +(assert_return (invoke "bit_not" (i32.const 0x0000FFFF)) (i32.const 0xFFFF0000)) +(assert_return (invoke "bit_not" (i32.const 1)) (i32.const -2)) + +;; ============================================================================= +;; Max tests (signed comparison) +;; ============================================================================= +(assert_return (invoke "max" (i32.const 5) (i32.const 10)) (i32.const 10)) +(assert_return (invoke "max" (i32.const 10) (i32.const 5)) (i32.const 10)) +(assert_return (invoke "max" (i32.const 0) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "max" (i32.const -5) (i32.const 5)) (i32.const 5)) +(assert_return (invoke "max" (i32.const -1) (i32.const -10)) (i32.const -1)) +(assert_return (invoke "max" (i32.const 7) (i32.const 7)) (i32.const 7)) ;; equal values + +;; ============================================================================= +;; Min tests (signed comparison) +;; ============================================================================= +(assert_return (invoke "min" (i32.const 5) (i32.const 10)) (i32.const 5)) +(assert_return (invoke "min" (i32.const 10) (i32.const 5)) (i32.const 5)) +(assert_return (invoke "min" (i32.const 0) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "min" (i32.const -5) (i32.const 5)) (i32.const -5)) +(assert_return (invoke "min" (i32.const -1) (i32.const -10)) (i32.const -10)) +(assert_return (invoke "min" (i32.const 7) (i32.const 7)) (i32.const 7)) ;; equal values + +;; ============================================================================= +;; Abs tests +;; ============================================================================= +(assert_return (invoke "abs" (i32.const -5)) (i32.const 5)) +(assert_return (invoke "abs" (i32.const 5)) (i32.const 5)) +(assert_return (invoke "abs" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "abs" (i32.const -1)) (i32.const 1)) +(assert_return (invoke "abs" (i32.const -100)) (i32.const 100)) +(assert_return (invoke "abs" (i32.const 1)) (i32.const 1))