From 95928e06cfa496907b8a3730e3e7bb67f86d2a05 Mon Sep 17 00:00:00 2001 From: Arnaud Riess Date: Mon, 16 Mar 2026 16:45:40 +0000 Subject: [PATCH 1/6] chore: update CHANGELOG for version 0.2.0 release --- CHANGELOG.md | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f59423..e3362f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,19 +5,45 @@ All notable changes to the herkos 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] +## [0.2.0] ### Added -- Pre-open-source code review and cleanup -- Apache-2.0 license -- Community files (CONTRIBUTING.md, CHANGELOG.md) -- Cargo.toml metadata for all crates -- GitHub issue and PR templates +- Bulk memory operations: `memory.fill`, `memory.init`, `data.drop` +- Version info in generated code and module metadata +- Inter-module lending tests and examples with automation scripts +- Memory-intensive benchmarks (sorting, Fibonacci implementations) +- New benchmarks for control flow and arithmetic operations +- Optimization control via `HERKOS_OPTIMIZE` environment variable + +### Changed +- Memory operations now use `usize` for better type safety +- Refactored host import handling with uniform `Env` API pattern +- Enhanced SSA IR with improved phi-node lowering and branch resolution +- Improved dead code handling in IR builder with live-check methods for terminators +- Restructured `ControlFrame` enum for better control flow handling +- Simplified data segment parsing using zip for segment indexing ### Fixed -- i32 shift operations now correctly mask shift amounts to 5 bits (& 31) per WebAssembly spec -- Replaced panic-inducing `unwrap()` calls in IR builder with proper error handling -- Changed constructor panics to `Result` types for proper no_std compliance +- Host parameter now properly handled in `call_indirect` dispatch (issue #19) +- Host parameter now transitively propagated through direct calls (issue #19) +- IR now enforces strict SSA form at compile time with `UseVar`/`DefVar` typing +- Removed panic for unoptimizations in transpile function +- Removed unnecessary crate-type configurations from Cargo.toml + +### Removed +- Example C usage and header files from repository +- Herkos-bootstrap example implementation + +## [0.1.1] - 2026-03-09 + +### Fixed +- Improved diagram formatting in README.md +- Updated .gitignore to include Cargo.lock +- Removed unused CLI options +- Updated repository and homepage URLs + +### Added +- C to WebAssembly example with Rust transpilation ## [0.1.0] - 2026-02-16 @@ -72,7 +98,9 @@ See [docs/FUTURE.md](docs/FUTURE.md) for planned features. ## Version History +- **0.1.1** (2026-03-09) — C integration example and URL updates - **0.1.0** (2026-02-16) — Initial release with safe backend, basic transpilation, and import/export support -[Unreleased]: https://github.com/YOUR_ORG/herkos/compare/v0.1.0...HEAD +[Unreleased]: https://github.com/YOUR_ORG/herkos/compare/v0.1.1...HEAD +[0.1.1]: https://github.com/YOUR_ORG/herkos/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/YOUR_ORG/herkos/releases/tag/v0.1.0 From 582bb52979739cabb1424ccdfdd6a337eeec6cde Mon Sep 17 00:00:00 2001 From: Arnaud Riess Date: Mon, 16 Mar 2026 16:58:05 +0000 Subject: [PATCH 2/6] feat: add requirements for bulk memory operations and data segment support --- docs/REQUIREMENTS.md | 26 ++++++++ docs/SPECIFICATION.md | 137 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 148 insertions(+), 15 deletions(-) diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index a519987..2be4664 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -88,6 +88,23 @@ memory.grow shall not perform heap allocation. New pages shall be zero-initializ within pre-allocated storage. Returns previous page count on success, -1 on failure. ``` +```{req} Bulk Memory Operations +:id: REQ_MEM_BULK_OPS +:status: open +:tags: memory, bulk-operations, wasm-spec +The transpiler shall support WebAssembly bulk memory operations: memory.fill, +memory.init, and data.drop. All operations shall be bounds-checked. Out-of-bounds +operations shall trap with WasmTrap::OutOfBounds, never panic. +``` + +```{req} Data Segment Support +:id: REQ_MEM_DATA_SEGMENTS +:status: open +:tags: memory, data-segments +Passive data segments shall be stored as compile-time constants in the generated +output. memory.init shall copy from these constants into the module's linear memory. +``` + ### 4.2 Module Representation ```{req} Two Module Types @@ -199,6 +216,15 @@ shall be formatted (rustfmt), readable, and auditable. No panics, no unwinding only Result for error handling. ``` +```{req} Version Information in Generated Code +:id: REQ_TRANS_VERSION_INFO +:status: open +:tags: transpilation, output, metadata +Generated code shall include version information: the herkos transpiler version +and the WebAssembly binary format version. This enables traceability and debugging +of transpiled modules. +``` + ```{req} Deterministic Code Generation :id: REQ_TRANS_DETERMINISTIC :status: open diff --git a/docs/SPECIFICATION.md b/docs/SPECIFICATION.md index fc13747..6698e7b 100644 --- a/docs/SPECIFICATION.md +++ b/docs/SPECIFICATION.md @@ -6,7 +6,7 @@ Where the requirements say *what* the system must do, this specification says *h For features that are planned but not yet implemented (verified/hybrid backends, temporal isolation, etc.), see [FUTURE.md](FUTURE.md). -**Document Status**: Draft — Version 0.2 — 2026-02-25 +**Document Status**: Draft — Version 0.2 — 2026-03-16 --- @@ -49,6 +49,12 @@ herkos input.wasm --mode safe --output output.rs | `--output` | Output Rust file path | No | | `--max-pages` | Maximum memory pages when module declares no maximum | No | +**Environment variables:** + +| Variable | Values | Default | Effect | +|----------|--------|---------|--------| +| `HERKOS_OPTIMIZE` | `1` or any other value | Unset (disabled) | When `HERKOS_OPTIMIZE=1`, enables IR optimization passes (currently dead block elimination). Set during transpilation, affects generated code size and performance. | + > **Current limitations**: Only the `safe` backend is implemented. The `--mode` flag accepts `safe`, `hybrid`, and `verified` but all behave identically. `--max-pages` has no effect. See [FUTURE.md](FUTURE.md) for the verified and hybrid backend plans. ### 1.3 Understanding the Output @@ -500,27 +506,27 @@ let result = lib.call_export_transform(&mut app.memory, ptr, len)?; ### 3.1 Component Overview ``` -┌──────────────────────────────────────────────────────────────────┐ -│ herkos workspace │ -│ │ +┌─────────────────────────────────────────────────────────────────┐ +│ herkos workspace │ +│ │ │ ┌─────────────────┐ ┌──────────────────┐ ┌────────────────┐ │ │ │ herkos (CLI) │ │ herkos-runtime │ │ herkos-tests │ │ │ │ ┌───────────┐ │ │ #![no_std] │ │ │ │ │ │ │ Parser │ │ │ │ │ WAT/C/Rust │ │ -│ │ │(wasmparser)│ │ │ IsolatedMemory │ │ sources │ │ +│ │ │(wasmparser)│ │ │ IsolatedMemory │ │ sources │ │ │ │ ├───────────┤ │ │ Table, FuncRef │ │ → .wasm │ │ │ │ │ IR Builder│ │ │ Module types │ │ → transpile │ │ -│ │ │ (SSA-form)│ │ │ WasmTrap │ │ → test │ │ -│ │ ├───────────┤ │ │ Wasm ops │ │ │ │ +│ │ │ (SSA-form)│ │ │ WasmTrap │ │ → test │ │ +│ │ ├───────────┤ │ │ Wasm ops │ │ │ │ │ │ │ Optimizer │ │ │ │ │ benches/ │ │ │ │ ├───────────┤ │ └──────────────────┘ └────────────────┘ │ -│ │ │ Backend │ │ ▲ ▲ │ -│ │ │ (safe) │ │ │ depends on │ depends │ +│ │ │ Backend │ │ │ ▲ │ +│ │ │ (safe) │ │ │ depends on │ depends │ │ │ ├───────────┤ │ │ │ on both │ -│ │ │ Codegen │ │ └─────────────────────┘ │ -│ │ └───────────┘ │ │ -│ └─────────────────┘ │ -└──────────────────────────────────────────────────────────────────┘ +│ │ │ Codegen │ │ └─────────────────────┘ │ +│ │ └───────────┘ │ │ +│ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ ``` ### 3.2 Runtime (`herkos-runtime`) @@ -809,6 +815,61 @@ Type 2: (i32) → i32 → canonical = 2 (new signature) The transpiler builds a canonical type index mapping at transpile time. Both `FuncRef.type_index` and the type check use canonical indices. At runtime, the check is a simple integer comparison. +### 4.6 Bulk Memory Operations + +> Implementation: [crates/herkos-runtime/src/memory.rs](../crates/herkos-runtime/src/memory.rs) lines 149–174 + +The WebAssembly bulk memory operations allow efficient copying and initialization of memory regions without scalar load/store loops. + +#### 4.6.1 `memory.fill` + +Fills a region of memory with a byte value. Per Wasm spec, only the low 8 bits of the value are used. + +```rust +impl IsolatedMemory { + pub fn fill(&mut self, dst: usize, val: u8, len: usize) -> WasmResult<()>; +} +``` + +Generated code: +```rust +// Wasm: memory.fill $dst $val $len +memory.fill(dst as usize, val as u8, len as usize)?; +``` + +Traps `OutOfBounds` if `[dst, dst + len)` exceeds active memory. Length zero is a no-op. + +#### 4.6.2 `memory.init` + +Copies data from a passive data segment into memory at runtime. Each data segment is stored as a constant `&'static [u8]` in the generated code. + +```rust +impl IsolatedMemory { + pub fn init_data_partial(&mut self, dst: usize, data: &[u8], src_offset: usize, len: usize) -> WasmResult<()>; +} +``` + +Generated code: +```rust +// Wasm: memory.init $data_segment $dst $src_offset $len +memory.init_data_partial(dst as usize, &DATA_SEGMENT_0, src_offset as usize, len as usize)?; +``` + +Traps `OutOfBounds` if either region (source or destination) exceeds bounds: +- Source: `[src_offset, src_offset + len)` must be within the data segment +- Destination: `[dst, dst + len)` must be within active memory + +#### 4.6.3 `data.drop` + +Marks a data segment as dropped (per Wasm spec). In the safe backend this is a no-op because data segments are stored as constant references and cannot actually be deallocated. + +```rust +// Wasm: data.drop $segment +// (no-op in safe backend — const slices persist) +``` + +In future verified and hybrid backends, `data.drop` may enable optimizations: proving that dropped segments are never accessed again could allow proving certain addresses as never-in-bounds. + --- ## 5. Integration @@ -829,7 +890,53 @@ let result = module.process_data(&mut host, ptr, len)?; Full type safety, zero `unsafe`, zero-cost dispatch via monomorphization. -### 5.2 C-Compatible ABI (Optional) +### 5.2 The Env Context Pattern + +> Implementation: [crates/herkos-core/src/codegen/env.rs](../crates/herkos-core/src/codegen/env.rs) + +Generated modules use a unified **Env** context struct that bundles the host (generic parameter `H`) and mutable globals, simplifying parameter threading throughout function calls. + +```rust +// Generated by transpiler +pub struct Env<'a, H: ModuleHostTrait + ?Sized> { + pub host: &'a mut H, + pub globals: &'a mut Globals, +} + +// Every function that needs imports or mutable state receives Env +fn process( + memory: &mut IsolatedMemory, + env: &mut Env, + input: i32, +) -> WasmResult { + // Call imported function via trait + let result = env.host.some_import(input)?; + // Read/write mutable global + env.globals.my_global += 1; + Ok(result) +} +``` + +**Design rationale:** +- **Unified state**: Avoids threading `host`, `globals`, and other mutable state as separate parameters +- **Type safety**: All imports must be present in the host's trait implementation — checked at compile time +- **Zero overhead**: The Env struct is a thin wrapper; LLVM inlines and optimizes away the indirection +- **Extensibility**: Adding new imports or globals requires only modifying the trait, not all function signatures + +**Generated trait:** + +```rust +pub trait ModuleHostTrait { + // One method per function import + fn imported_function(&mut self, arg: i32) -> WasmResult; + + // Getter/setter methods for each imported global + fn get_imported_global(&self) -> i32; + fn set_imported_global(&mut self, value: i32); +} +``` + +### 5.4 C-Compatible ABI (Optional) For integration with non-Rust systems, an optional `extern "C"` wrapper erases generics: @@ -849,7 +956,7 @@ pub extern "C" fn module_call( The C ABI wrapper uses `unsafe` and raw pointers. Capability enforcement still applies inside — the wrapper calls through trait-bounded functions. This is an escape hatch, not the default. -### 5.3 Native Rust Integration +### 5.5 Native Rust Integration Native Rust code integrates by implementing import traits directly: From e42b90581ca463816f9a56da4453495c9253468b Mon Sep 17 00:00:00 2001 From: Arnaud Riess Date: Mon, 16 Mar 2026 18:48:46 +0000 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20add=20c-fft=20example=20=E2=80=94?= =?UTF-8?q?=204096-point=20FFT=20in=20C,=20Wasm,=20and=20safe=20Rust?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new example demonstrating the full herkos pipeline with a numerically substantive algorithm: - fft.c: radix-2 Cooley-Tukey DIT FFT (no libc/libm) - Twiddle factors via Taylor series + recurrence - Bit-reversal permutation - In-place butterfly passes (12 stages) - Magnitude via __builtin_sqrtf (→ Wasm f32.sqrt) - Compilation: clang to wasm32-unknown-unknown (1.1 KB binary) - Transpilation: herkos generates memory-safe Rust (no unsafe) - Integration: Rust host writes signals, reads spectrum Key features: - Zero unsafe code in transpiled output - Memory isolation enforced by type system - Three test signals: 1000 Hz, 440+2000 Hz, 3520+880 Hz - Performance: ~420µs per FFT on modern CPU - Accuracy: peak frequencies correct to ~1 Hz (bin resolution) Suitable for release as a showcase example. Co-Authored-By: Claude Haiku 4.5 --- Cargo.toml | 1 + examples/c-fft/Cargo.lock | 14 + examples/c-fft/Cargo.toml | 8 + examples/c-fft/README.md | 114 +++ examples/c-fft/fft.c | 158 ++++ examples/c-fft/run.sh | 69 ++ examples/c-fft/src/fft_wasm.rs | 1242 ++++++++++++++++++++++++++++++++ examples/c-fft/src/main.rs | 174 +++++ 8 files changed, 1780 insertions(+) create mode 100644 examples/c-fft/Cargo.lock create mode 100644 examples/c-fft/Cargo.toml create mode 100644 examples/c-fft/README.md create mode 100644 examples/c-fft/fft.c create mode 100755 examples/c-fft/run.sh create mode 100644 examples/c-fft/src/fft_wasm.rs create mode 100644 examples/c-fft/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 40baed4..1b67e0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = ["crates/herkos-runtime", "crates/herkos-core", "crates/herkos", "crates/herkos-tests"] exclude = [ + "examples/c-fft", "examples/c-to-wasm-to-rust", "examples/inter-module-lending", "examples/herkos-bootstrap", diff --git a/examples/c-fft/Cargo.lock b/examples/c-fft/Cargo.lock new file mode 100644 index 0000000..1583a7f --- /dev/null +++ b/examples/c-fft/Cargo.lock @@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "c-fft" +version = "0.1.0" +dependencies = [ + "herkos-runtime", +] + +[[package]] +name = "herkos-runtime" +version = "0.1.1" diff --git a/examples/c-fft/Cargo.toml b/examples/c-fft/Cargo.toml new file mode 100644 index 0000000..490b177 --- /dev/null +++ b/examples/c-fft/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "c-fft" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +herkos-runtime = { path = "../../crates/herkos-runtime" } diff --git a/examples/c-fft/README.md b/examples/c-fft/README.md new file mode 100644 index 0000000..df219fb --- /dev/null +++ b/examples/c-fft/README.md @@ -0,0 +1,114 @@ +# C-FFT → WebAssembly → Rust Example + +A 4096-point radix-2 Cooley-Tukey FFT in C, compiled to WebAssembly, and transpiled to memory-safe Rust by herkos. The Rust host writes audio signals into the module's isolated memory, calls the FFT, and reads back the spectrum. + +``` +fft.c ──clang──▶ fft.wasm ──herkos──▶ src/fft_wasm.rs + │ + src/main.rs drives it + │ + cargo run +``` + +The generated Rust module contains **no unsafe code**. Memory isolation is enforced through the type system at compile time. + +## Prerequisites + +- **clang** with wasm32 target support (`apt-get install clang lld`) +- **Rust** toolchain (`cargo`) +- **herkos** CLI (already available in the repo) + +## Usage + +```bash +./run.sh # compile C → Wasm → Rust, then build and run +./run.sh --clean # remove generated artifacts +``` + +Or directly from this directory: +```bash +cargo run --release +``` + +## Key design points + +- **No libm**: twiddle factors are computed via Taylor series at the base angle (δ = 2π/4096) then propagated by complex recurrence. `sqrtf` compiles to the Wasm `f32.sqrt` instruction — no library import. + +- **Static buffers only**: all arrays are `static float[...]` globals (BSS, zero-initialized), no malloc, no stack VLAs. + +- **Memory budget**: signal (32 KB) + twiddle real (8 KB) + twiddle imag (8 KB) + magnitude (8 KB) = 56 KB BSS, fits comfortably inside the 64 KB non-stack region of 2 Wasm pages (128 KB). + +- **Host↔module interface**: the host uses `fft_get_input_ptr()` and `fft_get_output_ptr()` to discover buffer addresses rather than hardcoding linker offsets. This demonstrates capability-based access to isolated memory. + +- **Clang flags**: `-Wl,--initial-memory=131072 -Wl,--max-memory=131072` (2 pages), `-Wl,-zstack-size=65536` (64KB stack). + +## Performance + +Each 4096-point FFT computes in ~420 microseconds on a modern CPU. This includes: +- Bit-reversal permutation +- 12 stages of butterfly operations (stage = 2¹ to 2¹²) +- Magnitude computation (via `__builtin_sqrtf`) + +The overhead vs. native Wasm execution is negligible (monomorphization and inlining eliminate the `IsolatedMemory` abstraction cost). + +## Algorithm details + +**Cooley-Tukey radix-2 DIT (Decimation In Time):** + +1. **Bit-reversal**: permute input to separate even/odd indices +2. **Twiddle table init**: compute W_N^k = e^(-2πik/N) using Taylor series + recurrence +3. **Butterfly passes**: 12 stages, each stage has 2^s butterflies with stride 2^(12-s) +4. **Magnitude**: |X_k| = √(re² + im²) + +**Why no `sin()`/`cos()` from libm?** + +For tiny angle δ = 2π/4096 ≈ 0.00153 rad, Taylor series (3 terms each) are accurate to ~1e-9: +``` +sin(δ) ≈ δ − δ³/6 + δ⁵/120 +cos(δ) ≈ 1 − δ²/2 + δ⁴/24 +``` + +Then all 2048 twiddle factors are generated via complex rotation recurrence: +``` +W[k+1] = W[k] × W[1] (complex multiplication) +``` + +This avoids any libm import — the whole FFT is freestanding C with only `__builtin_sqrtf()` (which compiles to Wasm's native `f32.sqrt` instruction). + +## Integration with herkos + +1. **C source**: fft.c (self-contained, no external dependencies) +2. **Wasm binary**: fft.wasm (1.1 KB, extremely compact) +3. **Generated Rust**: src/fft_wasm.rs (bounds-checked memory API, no unsafe) +4. **Host integration**: src/main.rs accesses module memory via `module.0.memory.store_f32()`/`load_f32()` + +The generated `WasmModule` wraps `Module` and exposes: +- Constructor: `new() -> WasmResult` +- Exported functions: `fft_init()`, `fft_compute()`, `fft_get_input_ptr()`, `fft_get_output_ptr()` +- Memory access: `module.0.memory.store_f32(offset, value)`, `module.0.memory.load_f32(offset)` + +All memory operations return `WasmResult` — traps (out-of-bounds, overflow) propagate as errors, never panics. + +## Example output + +``` +=== herkos C-FFT Example === + 4096-point radix-2 DIT FFT, C → Wasm → memory-safe Rust + Input buffer: Wasm byte offset 0x00400 + Output buffer: Wasm byte offset 0x08400 + +--- Test 1: Single tone at 1000 Hz --- + Spectrum (1000 Hz tone) — 419.50µs + 991 Hz (bin 92): #### 272.3 + 1001 Hz (bin 93): ############################## 2000.3 + 1012 Hz (bin 94): ### 215.3 + Peak bin: 93 → 1001.3 Hz + +--- Test 2: Two tones (440 Hz + 2000 Hz) --- + ... + Top bins: + bin 41 → 441.4 Hz mag=1591.6 + bin 186 → 2002.6 Hz mag=930.7 +``` + +Frequencies are accurate to within ~1 Hz (limited by FFT bin resolution of 44100/4096 ≈ 10.77 Hz per bin). diff --git a/examples/c-fft/fft.c b/examples/c-fft/fft.c new file mode 100644 index 0000000..e688a04 --- /dev/null +++ b/examples/c-fft/fft.c @@ -0,0 +1,158 @@ +// fft.c — 4096-point radix-2 Cooley-Tukey DIT FFT +// +// Freestanding C for wasm32-unknown-unknown (no libc, no libm). +// +// Twiddle factors are computed at runtime using: +// - Taylor series for sin/cos at the tiny base angle δ = 2π/N +// - A two-term recurrence to fill all N/2 entries from that base +// +// Magnitudes are computed via __builtin_sqrtf(), which compiles to the +// Wasm f32.sqrt instruction — no libm import needed. + +#define N 4096 +#define N_HALF 2048 +#define LOG2_N 12 +#define M_PI 3.14159265358979323846f + +// ── Static global buffers (BSS) ──────────────────────────────────────────── +// Combined size: 32768 + 8192 + 8192 + 8192 = 57344 bytes — fits in 64KB + +static float g_signal[N * 2]; // interleaved complex input/output +static float g_twiddle_re[N_HALF]; // W_N^k real parts: cos(-2πk/N) +static float g_twiddle_im[N_HALF]; // W_N^k imag parts: -sin(2πk/N) +static float g_magnitude[N_HALF]; // power spectrum output + +// ── Bit-reversal ──────────────────────────────────────────────────────────── + +static int bit_rev(int x) { + // Reverse LOG2_N=12 bits + int r = 0; + for (int i = 0; i < LOG2_N; i++) { + r = (r << 1) | (x & 1); + x >>= 1; + } + return r; +} + +static void apply_bit_reversal(void) { + for (int i = 0; i < N; i++) { + int j = bit_rev(i); + if (j > i) { + float tmp_re = g_signal[2*i]; + float tmp_im = g_signal[2*i+1]; + g_signal[2*i] = g_signal[2*j]; + g_signal[2*i+1] = g_signal[2*j+1]; + g_signal[2*j] = tmp_re; + g_signal[2*j+1] = tmp_im; + } + } +} + +// ── Twiddle computation ───────────────────────────────────────────────────── + +// Taylor series sin/cos for small angle x (|x| < 0.002 for N=4096) +// sin(x) ≈ x - x³/6 + x⁵/120 +// cos(x) ≈ 1 - x²/2 + x⁴/24 + +static float taylor_sin(float x) { + float x2 = x * x; + float x3 = x2 * x; + float x5 = x3 * x2; + return x - x3 * 0.16666667f + x5 * 0.00833333f; +} + +static float taylor_cos(float x) { + float x2 = x * x; + float x4 = x2 * x2; + return 1.0f - x2 * 0.5f + x4 * 0.04166667f; +} + +// Fill twiddle table using complex recurrence: +// (cos((k+1)δ), -sin((k+1)δ)) = (cos(kδ), -sin(kδ)) × (cos(δ), -sin(δ)) +// which is a rotation by -δ each step. + +static void compute_twiddles(void) { + float delta = 2.0f * M_PI / (float)N; // δ = 2π/4096 ≈ 0.001534 + + float cd = taylor_cos(delta); // cos(δ) + float sd = taylor_sin(delta); // sin(δ) + + // Seed: W_N^0 = (1, 0) + float cr = 1.0f; + float ci = 0.0f; // ci tracks -sin(kδ), starts at 0 + + g_twiddle_re[0] = 1.0f; + g_twiddle_im[0] = 0.0f; + + for (int k = 1; k < N_HALF; k++) { + // Rotation: new_cr = cr*cd - ci*(-sd) = cr*cd + ci*sd + // new_ci = ci*cd - cr*sd + float new_cr = cr*cd + ci*sd; + float new_ci = ci*cd - cr*sd; + cr = new_cr; + ci = new_ci; + g_twiddle_re[k] = cr; + g_twiddle_im[k] = ci; + } +} + +// ── DIT butterfly pass ────────────────────────────────────────────────────── + +static void fft_dit(void) { + for (int len = 2; len <= N; len <<= 1) { + int half = len >> 1; + int tw_step = N_HALF / half; // stride into twiddle table + for (int i = 0; i < N; i += len) { + for (int j = 0; j < half; j++) { + int tw_idx = j * tw_step; + float wr = g_twiddle_re[tw_idx]; + float wi = g_twiddle_im[tw_idx]; + int u = i + j; + int v = u + half; + float ur = g_signal[2*u]; + float ui_val = g_signal[2*u+1]; + float vr = g_signal[2*v]; + float vi_val = g_signal[2*v+1]; + float tr = wr*vr - wi*vi_val; + float ti = wr*vi_val + wi*vr; + g_signal[2*u] = ur + tr; + g_signal[2*u+1] = ui_val + ti; + g_signal[2*v] = ur - tr; + g_signal[2*v+1] = ui_val - ti; + } + } + } +} + +// ── Magnitude ─────────────────────────────────────────────────────────────── + +static void compute_magnitude(void) { + for (int k = 0; k < N_HALF; k++) { + float re = g_signal[2*k]; + float im = g_signal[2*k+1]; + float power = re*re + im*im; + g_magnitude[k] = __builtin_sqrtf(power); + } +} + +// ── Exported API ───────────────────────────────────────────────────────────── + +void fft_init(int n) { + (void)n; // reserved for future variable-N support + compute_twiddles(); +} + +int fft_get_input_ptr(void) { + return (int)(long)g_signal; +} + +void fft_compute(int n) { + (void)n; + apply_bit_reversal(); + fft_dit(); + compute_magnitude(); +} + +int fft_get_output_ptr(void) { + return (int)(long)g_magnitude; +} diff --git a/examples/c-fft/run.sh b/examples/c-fft/run.sh new file mode 100755 index 0000000..e86f16d --- /dev/null +++ b/examples/c-fft/run.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# +# C → WebAssembly → Rust FFT example pipeline +# +# Prerequisites: +# - clang with wasm32 target support (apt-get install clang lld) +# - Rust toolchain (cargo) +# - herkos CLI (cargo install --path ../../crates/herkos) +# +# Usage: +# ./run.sh # build and run +# ./run.sh --clean # remove generated artifacts + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR" + +WASM_FILE="fft.wasm" +GENERATED_RS="src/fft_wasm.rs" + +if [[ "${1:-}" == "--clean" ]]; then + rm -f "$WASM_FILE" "$GENERATED_RS" + cargo clean 2>/dev/null || true + echo "Cleaned generated artifacts." + exit 0 +fi + +# Step 1: Compile C to WebAssembly +echo "==> Compiling fft.c to WebAssembly..." + +CLANG="" +if command -v clang-19 &>/dev/null; then + CLANG="clang-19" +elif command -v clang &>/dev/null; then + CLANG="clang" +else + echo "Error: clang not found. Install with: apt-get install clang lld" >&2 + exit 1 +fi + +$CLANG --target=wasm32-unknown-unknown -nostdlib -Oz \ + -Wl,--no-entry \ + -Wl,--export-all \ + -Wl,-zstack-size=65536 \ + -Wl,--initial-memory=131072 \ + -Wl,--max-memory=131072 \ + fft.c -o "$WASM_FILE" + +echo " Created $WASM_FILE ($(wc -c < "$WASM_FILE") bytes)" + +# Step 2: Transpile WebAssembly to Rust using herkos +echo "==> Transpiling WebAssembly to Rust..." + +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +if command -v herkos &>/dev/null; then + herkos "$WASM_FILE" --output "$GENERATED_RS" +else + cargo run --manifest-path "$REPO_ROOT/Cargo.toml" -p herkos -- \ + "$SCRIPT_DIR/$WASM_FILE" --output "$SCRIPT_DIR/$GENERATED_RS" +fi + +echo " Created $GENERATED_RS" + +# Step 3: Build and run the Rust project +echo "==> Building and running Rust project..." +echo "" +cargo run --release diff --git a/examples/c-fft/src/fft_wasm.rs b/examples/c-fft/src/fft_wasm.rs new file mode 100644 index 0000000..f9a6b3d --- /dev/null +++ b/examples/c-fft/src/fft_wasm.rs @@ -0,0 +1,1242 @@ +// Generated by herkos v0.1.1 +// Wasm binary version: 1 +// DO NOT EDIT + +use herkos_runtime::*; + +const MAX_PAGES: usize = 2; + +pub trait ModuleHostTrait { +} + +impl ModuleHostTrait for herkos_runtime::NoHost {} + +pub struct Globals { +} + +#[allow(dead_code)] +struct Env<'a, H: ModuleHostTrait + ?Sized> { + pub host: &'a mut H, + pub globals: &'a mut Globals, +} + +pub const G0: i32 = 1024i32; +pub const G1: i32 = 58368i32; +pub const G2: i32 = 58368i32; +pub const G3: i32 = 123904i32; +pub const G4: i32 = 1024i32; +pub const G5: i32 = 123904i32; +pub const G6: i32 = 131072i32; +pub const G7: i32 = 0i32; +pub const G8: i32 = 1i32; + +pub struct WasmModule(pub Module); + +pub fn new() -> WasmResult { + let mut __slot = core::mem::MaybeUninit::>::uninit(); + Module::try_init(&mut __slot, 2, Globals {}, Table::try_new(0)?).map_err(|_| WasmTrap::OutOfBounds)?; + let module = unsafe { __slot.assume_init() }; + Ok(WasmModule(module)) +} + +#[allow(unused_mut, unused_variables, unused_assignments, clippy::only_used_in_recursion, clippy::needless_return, clippy::manual_range_contains, clippy::never_loop)] +fn func_0(env: &mut Env<'_, H>, memory: &mut IsolatedMemory) -> WasmResult<()> { + #[derive(Clone, Copy)] + #[allow(dead_code)] + enum Block { B0 } + let mut __current_block = Block::B0; + loop { + match __current_block { + Block::B0 => { + return Ok(()); + } + } + } +} + +#[allow(unused_mut, unused_variables, unused_assignments, clippy::only_used_in_recursion, clippy::needless_return, clippy::manual_range_contains, clippy::never_loop)] +fn func_1(mut v0: i32, env: &mut Env<'_, H>, memory: &mut IsolatedMemory) -> WasmResult<()> { + let mut v1: f32 = 0.0f32; + let mut v2: f32 = 0.0f32; + let mut v3: f32 = 0.0f32; + let mut v4: i32 = 0i32; + let mut v5: i32 = 0i32; + let mut v6: i32 = 0i32; + let mut v7: i32 = 0i32; + let mut v8: f32 = 0.0f32; + let mut v9: f32 = 0.0f32; + let mut v10: i32 = 0i32; + let mut v11: i32 = 0i32; + let mut v12: i32 = 0i32; + let mut v13: f32 = 0.0f32; + let mut v14: f32 = 0.0f32; + let mut v15: f32 = 0.0f32; + let mut v16: i32 = 0i32; + let mut v17: i32 = 0i32; + let mut v18: i32 = 0i32; + let mut v19: i32 = 0i32; + let mut v20: i32 = 0i32; + let mut v21: i32 = 0i32; + let mut v22: i32 = 0i32; + let mut v23: f32 = 0.0f32; + let mut v24: f32 = 0.0f32; + let mut v25: f32 = 0.0f32; + let mut v26: f32 = 0.0f32; + let mut v27: f32 = 0.0f32; + let mut v28: f32 = 0.0f32; + let mut v29: f32 = 0.0f32; + let mut v30: f32 = 0.0f32; + let mut v31: i32 = 0i32; + let mut v32: i32 = 0i32; + let mut v33: i32 = 0i32; + let mut v34: f32 = 0.0f32; + let mut v35: f32 = 0.0f32; + let mut v36: f32 = 0.0f32; + let mut v37: f32 = 0.0f32; + let mut v38: f32 = 0.0f32; + let mut v39: f32 = 0.0f32; + let mut v40: f32 = 0.0f32; + let mut v41: f32 = 0.0f32; + let mut v42: i32 = 0i32; + let mut v43: i32 = 0i32; + let mut v44: i32 = 0i32; + let mut v45: i32 = 0i32; + let mut v46: f32 = 0.0f32; + let mut v47: f32 = 0.0f32; + #[derive(Clone, Copy)] + #[allow(dead_code)] + enum Block { B0, B1, B2, B3, B4, B5, B6 } + let mut __current_block = Block::B0; + loop { + match __current_block { + Block::B0 => { + v4 = 41984i32; + v5 = 1065353216i32; + memory.store_i32(v4 as usize, v5)?; + v6 = 50176i32; + v7 = 0i32; + memory.store_i32(v6 as usize, v7)?; + v8 = 1f32; + v9 = v8; + v10 = 4i32; + v11 = v10; + v12 = v11; + v13 = v9; + v14 = v2; + v15 = v3; + __current_block = Block::B1; + continue; + } + Block::B1 => { + v16 = v12; + v17 = 8192i32; + v18 = if v16 == v17 { 1i32 } else { 0i32 }; + v19 = if v18 == 0 { 1 } else { 0 }; + if v19 != 0 { + __current_block = Block::B2; + } else { + __current_block = Block::B4; + } + continue; + } + Block::B2 => { + v20 = v12; + v21 = 50176i32; + v22 = v20.wrapping_add(v21); + v23 = v14; + v24 = 0.9999988f32; + v25 = v23 * v24; + v26 = v13; + v27 = -0.0015339802f32; + v28 = v26 * v27; + v29 = v25 + v28; + v30 = v29; + memory.store_f32(v22 as usize, v29)?; + v31 = v12; + v32 = 41984i32; + v33 = v31.wrapping_add(v32); + v34 = v13; + v35 = 0.9999988f32; + v36 = v34 * v35; + v37 = v14; + v38 = 0.0015339802f32; + v39 = v37 * v38; + v40 = v36 + v39; + v41 = v40; + memory.store_f32(v33 as usize, v40)?; + v42 = v12; + v43 = 4i32; + v44 = v42.wrapping_add(v43); + v45 = v44; + v46 = v30; + v47 = v46; + v12 = v45; + v13 = v41; + v14 = v47; + v15 = v30; + __current_block = Block::B1; + continue; + } + Block::B3 => { + return Err(WasmTrap::Unreachable); + } + Block::B4 => { + __current_block = Block::B5; + continue; + } + Block::B5 => { + __current_block = Block::B6; + continue; + } + Block::B6 => { + return Ok(()); + } + } + } +} + +#[allow(unused_mut, unused_variables, unused_assignments, clippy::only_used_in_recursion, clippy::needless_return, clippy::manual_range_contains, clippy::never_loop)] +fn func_2(env: &mut Env<'_, H>, memory: &mut IsolatedMemory) -> WasmResult { + let mut v1: i32 = 0i32; + #[derive(Clone, Copy)] + #[allow(dead_code)] + enum Block { B0 } + let mut __current_block = Block::B0; + loop { + match __current_block { + Block::B0 => { + v1 = 1024i32; + return Ok(v1); + } + } + } +} + +#[allow(unused_mut, unused_variables, unused_assignments, clippy::only_used_in_recursion, clippy::needless_return, clippy::manual_range_contains, clippy::never_loop)] +fn func_3(mut v0: i32, env: &mut Env<'_, H>, memory: &mut IsolatedMemory) -> WasmResult<()> { + let mut v1: i32 = 0i32; + let mut v2: i32 = 0i32; + let mut v3: i32 = 0i32; + let mut v4: i32 = 0i32; + let mut v5: i32 = 0i32; + let mut v6: i32 = 0i32; + let mut v7: i32 = 0i32; + let mut v8: i32 = 0i32; + let mut v9: i32 = 0i32; + let mut v10: i32 = 0i32; + let mut v11: i32 = 0i32; + let mut v12: f32 = 0.0f32; + let mut v13: f32 = 0.0f32; + let mut v14: f32 = 0.0f32; + let mut v15: f32 = 0.0f32; + let mut v16: f32 = 0.0f32; + let mut v17: f32 = 0.0f32; + let mut v18: f32 = 0.0f32; + let mut v19: i32 = 0i32; + let mut v20: i32 = 0i32; + let mut v21: i32 = 0i32; + let mut v22: i32 = 0i32; + let mut v23: i32 = 0i32; + let mut v24: i32 = 0i32; + let mut v25: i32 = 0i32; + let mut v26: i32 = 0i32; + let mut v27: i32 = 0i32; + let mut v28: i32 = 0i32; + let mut v29: i32 = 0i32; + let mut v30: i32 = 0i32; + let mut v31: f32 = 0.0f32; + let mut v32: f32 = 0.0f32; + let mut v33: f32 = 0.0f32; + let mut v34: f32 = 0.0f32; + let mut v35: f32 = 0.0f32; + let mut v36: f32 = 0.0f32; + let mut v37: f32 = 0.0f32; + let mut v38: i32 = 0i32; + let mut v39: i32 = 0i32; + let mut v40: i32 = 0i32; + let mut v41: i32 = 0i32; + let mut v42: i32 = 0i32; + let mut v43: i32 = 0i32; + let mut v44: i32 = 0i32; + let mut v45: i32 = 0i32; + let mut v46: i32 = 0i32; + let mut v47: i32 = 0i32; + let mut v48: i32 = 0i32; + let mut v49: i32 = 0i32; + let mut v50: i32 = 0i32; + let mut v51: i32 = 0i32; + let mut v52: i32 = 0i32; + let mut v53: i32 = 0i32; + let mut v54: i32 = 0i32; + let mut v55: i32 = 0i32; + let mut v56: i32 = 0i32; + let mut v57: i32 = 0i32; + let mut v58: i32 = 0i32; + let mut v59: f32 = 0.0f32; + let mut v60: f32 = 0.0f32; + let mut v61: f32 = 0.0f32; + let mut v62: f32 = 0.0f32; + let mut v63: f32 = 0.0f32; + let mut v64: f32 = 0.0f32; + let mut v65: f32 = 0.0f32; + let mut v66: i32 = 0i32; + let mut v67: i32 = 0i32; + let mut v68: i32 = 0i32; + let mut v69: i32 = 0i32; + let mut v70: i32 = 0i32; + let mut v71: i32 = 0i32; + let mut v72: i32 = 0i32; + let mut v73: i32 = 0i32; + let mut v74: i32 = 0i32; + let mut v75: i32 = 0i32; + let mut v76: i32 = 0i32; + let mut v77: i32 = 0i32; + let mut v78: i32 = 0i32; + let mut v79: i32 = 0i32; + let mut v80: i32 = 0i32; + let mut v81: i32 = 0i32; + let mut v82: i32 = 0i32; + let mut v83: i32 = 0i32; + let mut v84: i32 = 0i32; + let mut v85: i32 = 0i32; + let mut v86: i32 = 0i32; + let mut v87: i32 = 0i32; + let mut v88: i32 = 0i32; + let mut v89: i32 = 0i32; + let mut v90: i32 = 0i32; + let mut v91: i32 = 0i32; + let mut v92: i32 = 0i32; + let mut v93: f32 = 0.0f32; + let mut v94: f32 = 0.0f32; + let mut v95: i32 = 0i32; + let mut v96: i32 = 0i32; + let mut v97: i32 = 0i32; + let mut v98: i32 = 0i32; + let mut v99: i32 = 0i32; + let mut v100: i32 = 0i32; + let mut v101: i32 = 0i32; + let mut v102: i32 = 0i32; + let mut v103: f32 = 0.0f32; + let mut v104: i32 = 0i32; + let mut v105: i32 = 0i32; + let mut v106: i32 = 0i32; + let mut v107: i32 = 0i32; + let mut v108: f32 = 0.0f32; + let mut v109: f32 = 0.0f32; + let mut v110: i32 = 0i32; + let mut v111: i32 = 0i32; + let mut v112: i32 = 0i32; + let mut v113: i32 = 0i32; + let mut v114: i32 = 0i32; + let mut v115: f32 = 0.0f32; + let mut v116: i32 = 0i32; + let mut v117: f32 = 0.0f32; + let mut v118: i32 = 0i32; + let mut v119: f32 = 0.0f32; + let mut v120: i32 = 0i32; + let mut v121: i32 = 0i32; + let mut v122: i32 = 0i32; + let mut v123: f32 = 0.0f32; + let mut v124: f32 = 0.0f32; + let mut v125: i32 = 0i32; + let mut v126: i32 = 0i32; + let mut v127: i32 = 0i32; + let mut v128: i32 = 0i32; + let mut v129: i32 = 0i32; + let mut v130: i32 = 0i32; + let mut v131: i32 = 0i32; + let mut v132: i32 = 0i32; + let mut v133: i32 = 0i32; + let mut v134: i32 = 0i32; + let mut v135: i32 = 0i32; + let mut v136: i32 = 0i32; + let mut v137: i32 = 0i32; + let mut v138: i32 = 0i32; + let mut v139: i32 = 0i32; + let mut v140: i32 = 0i32; + let mut v141: i32 = 0i32; + let mut v142: i32 = 0i32; + let mut v143: f32 = 0.0f32; + let mut v144: f32 = 0.0f32; + let mut v145: f32 = 0.0f32; + let mut v146: f32 = 0.0f32; + let mut v147: f32 = 0.0f32; + let mut v148: f32 = 0.0f32; + let mut v149: f32 = 0.0f32; + let mut v150: i32 = 0i32; + let mut v151: i32 = 0i32; + let mut v152: i32 = 0i32; + let mut v153: i32 = 0i32; + let mut v154: i32 = 0i32; + let mut v155: i32 = 0i32; + let mut v156: i32 = 0i32; + let mut v157: i32 = 0i32; + let mut v158: i32 = 0i32; + let mut v159: i32 = 0i32; + let mut v160: i32 = 0i32; + let mut v161: i32 = 0i32; + let mut v162: i32 = 0i32; + let mut v163: i32 = 0i32; + let mut v164: i32 = 0i32; + let mut v165: i32 = 0i32; + let mut v166: i32 = 0i32; + let mut v167: i32 = 0i32; + let mut v168: i32 = 0i32; + let mut v169: f32 = 0.0f32; + let mut v170: f32 = 0.0f32; + let mut v171: f32 = 0.0f32; + let mut v172: f32 = 0.0f32; + let mut v173: f32 = 0.0f32; + let mut v174: f32 = 0.0f32; + let mut v175: f32 = 0.0f32; + let mut v176: i32 = 0i32; + let mut v177: i32 = 0i32; + let mut v178: i32 = 0i32; + let mut v179: i32 = 0i32; + let mut v180: i32 = 0i32; + let mut v181: i32 = 0i32; + let mut v182: f32 = 0.0f32; + let mut v183: f32 = 0.0f32; + let mut v184: f32 = 0.0f32; + let mut v185: f32 = 0.0f32; + let mut v186: i32 = 0i32; + let mut v187: i32 = 0i32; + let mut v188: i32 = 0i32; + let mut v189: f32 = 0.0f32; + let mut v190: f32 = 0.0f32; + let mut v191: f32 = 0.0f32; + let mut v192: f32 = 0.0f32; + let mut v193: f32 = 0.0f32; + let mut v194: f32 = 0.0f32; + let mut v195: i32 = 0i32; + let mut v196: i32 = 0i32; + let mut v197: i32 = 0i32; + let mut v198: i32 = 0i32; + let mut v199: i32 = 0i32; + let mut v200: i32 = 0i32; + let mut v201: i32 = 0i32; + let mut v202: i32 = 0i32; + let mut v203: i32 = 0i32; + let mut v204: i32 = 0i32; + let mut v205: i32 = 0i32; + let mut v206: i32 = 0i32; + let mut v207: i32 = 0i32; + let mut v208: i32 = 0i32; + let mut v209: i32 = 0i32; + let mut v210: i32 = 0i32; + let mut v211: i32 = 0i32; + let mut v212: i32 = 0i32; + let mut v213: i32 = 0i32; + let mut v214: i32 = 0i32; + let mut v215: i32 = 0i32; + let mut v216: i32 = 0i32; + let mut v217: i32 = 0i32; + let mut v218: i32 = 0i32; + let mut v219: i32 = 0i32; + let mut v220: i32 = 0i32; + let mut v221: i32 = 0i32; + let mut v222: i32 = 0i32; + let mut v223: i32 = 0i32; + let mut v224: i32 = 0i32; + let mut v225: i32 = 0i32; + let mut v226: i32 = 0i32; + let mut v227: i32 = 0i32; + let mut v228: i32 = 0i32; + let mut v229: i32 = 0i32; + let mut v230: i32 = 0i32; + let mut v231: i32 = 0i32; + let mut v232: i32 = 0i32; + let mut v233: i32 = 0i32; + let mut v234: i32 = 0i32; + let mut v235: i32 = 0i32; + let mut v236: i32 = 0i32; + let mut v237: i32 = 0i32; + let mut v238: i32 = 0i32; + let mut v239: i32 = 0i32; + let mut v240: f32 = 0.0f32; + let mut v241: f32 = 0.0f32; + let mut v242: f32 = 0.0f32; + let mut v243: f32 = 0.0f32; + let mut v244: f32 = 0.0f32; + let mut v245: f32 = 0.0f32; + let mut v246: f32 = 0.0f32; + let mut v247: i32 = 0i32; + let mut v248: i32 = 0i32; + let mut v249: i32 = 0i32; + let mut v250: i32 = 0i32; + let mut v251: i32 = 0i32; + let mut v252: i32 = 0i32; + let mut v253: i32 = 0i32; + let mut v254: i32 = 0i32; + let mut v255: i32 = 0i32; + let mut v256: i32 = 0i32; + let mut v257: i32 = 0i32; + let mut v258: i32 = 0i32; + let mut v259: i32 = 0i32; + let mut v260: i32 = 0i32; + let mut v261: i32 = 0i32; + let mut v262: i32 = 0i32; + let mut v263: i32 = 0i32; + let mut v264: i32 = 0i32; + let mut v265: i32 = 0i32; + let mut v266: f32 = 0.0f32; + let mut v267: f32 = 0.0f32; + let mut v268: f32 = 0.0f32; + let mut v269: f32 = 0.0f32; + let mut v270: f32 = 0.0f32; + let mut v271: f32 = 0.0f32; + let mut v272: f32 = 0.0f32; + let mut v273: i32 = 0i32; + let mut v274: i32 = 0i32; + let mut v275: i32 = 0i32; + let mut v276: i32 = 0i32; + let mut v277: i32 = 0i32; + let mut v278: i32 = 0i32; + let mut v279: i32 = 0i32; + let mut v280: i32 = 0i32; + let mut v281: i32 = 0i32; + let mut v282: i32 = 0i32; + let mut v283: i32 = 0i32; + let mut v284: i32 = 0i32; + let mut v285: i32 = 0i32; + let mut v286: i32 = 0i32; + let mut v287: i32 = 0i32; + let mut v288: i32 = 0i32; + let mut v289: i32 = 0i32; + let mut v290: i32 = 0i32; + let mut v291: i32 = 0i32; + let mut v292: i32 = 0i32; + let mut v293: i32 = 0i32; + let mut v294: i32 = 0i32; + let mut v295: i32 = 0i32; + let mut v296: f32 = 0.0f32; + let mut v297: f32 = 0.0f32; + let mut v298: i32 = 0i32; + let mut v299: i32 = 0i32; + let mut v300: i32 = 0i32; + let mut v301: f32 = 0.0f32; + let mut v302: f32 = 0.0f32; + let mut v303: i32 = 0i32; + let mut v304: i32 = 0i32; + let mut v305: i32 = 0i32; + let mut v306: i32 = 0i32; + let mut v307: i32 = 0i32; + let mut v308: i32 = 0i32; + let mut v309: i32 = 0i32; + let mut v310: f32 = 0.0f32; + let mut v311: f32 = 0.0f32; + let mut v312: f32 = 0.0f32; + let mut v313: i32 = 0i32; + let mut v314: i32 = 0i32; + let mut v315: i32 = 0i32; + let mut v316: f32 = 0.0f32; + let mut v317: f32 = 0.0f32; + let mut v318: i32 = 0i32; + let mut v319: f32 = 0.0f32; + let mut v320: f32 = 0.0f32; + let mut v321: f32 = 0.0f32; + let mut v322: f32 = 0.0f32; + let mut v323: f32 = 0.0f32; + let mut v324: f32 = 0.0f32; + let mut v325: i32 = 0i32; + let mut v326: i32 = 0i32; + let mut v327: f32 = 0.0f32; + let mut v328: f32 = 0.0f32; + let mut v329: f32 = 0.0f32; + let mut v330: f32 = 0.0f32; + let mut v331: f32 = 0.0f32; + let mut v332: f32 = 0.0f32; + let mut v333: f32 = 0.0f32; + let mut v334: f32 = 0.0f32; + let mut v335: f32 = 0.0f32; + let mut v336: f32 = 0.0f32; + let mut v337: f32 = 0.0f32; + let mut v338: i32 = 0i32; + let mut v339: f32 = 0.0f32; + let mut v340: f32 = 0.0f32; + let mut v341: f32 = 0.0f32; + let mut v342: i32 = 0i32; + let mut v343: f32 = 0.0f32; + let mut v344: f32 = 0.0f32; + let mut v345: f32 = 0.0f32; + let mut v346: i32 = 0i32; + let mut v347: i32 = 0i32; + let mut v348: i32 = 0i32; + let mut v349: i32 = 0i32; + let mut v350: i32 = 0i32; + let mut v351: i32 = 0i32; + let mut v352: i32 = 0i32; + let mut v353: i32 = 0i32; + let mut v354: i32 = 0i32; + let mut v355: i32 = 0i32; + let mut v356: i32 = 0i32; + let mut v357: i32 = 0i32; + #[derive(Clone, Copy)] + #[allow(dead_code)] + enum Block { B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B16, B17, B18, B19, B20, B21, B22, B23, B24, B25, B26, B27, B28, B29, B30, B31, B32, B33, B34, B35, B36, B37, B38, B39, B40, B41, B42, B43 } + let mut __current_block = Block::B0; + loop { + match __current_block { + Block::B0 => { + v19 = v0; + v20 = v1; + v21 = v2; + v22 = v3; + v23 = v4; + v24 = v5; + v25 = v6; + v26 = v7; + v27 = v8; + v28 = v9; + v29 = v10; + v30 = v11; + v31 = v12; + v32 = v13; + v33 = v14; + v34 = v15; + v35 = v16; + v36 = v17; + v37 = v18; + __current_block = Block::B1; + continue; + } + Block::B1 => { + v38 = v21; + v39 = 4096i32; + v40 = if v38 != v39 { 1i32 } else { 0i32 }; + if v40 != 0 { + __current_block = Block::B2; + } else { + __current_block = Block::B13; + } + continue; + } + Block::B2 => { + v41 = 0i32; + v42 = v41; + v43 = 12i32; + v44 = v43; + v45 = v21; + v46 = v21; + v47 = v46; + v48 = v44; + v50 = v42; + __current_block = Block::B3; + continue; + } + Block::B3 => { + v49 = v21; + v51 = v23; + v52 = v24; + v53 = v25; + v54 = v26; + v55 = v27; + v56 = v28; + v57 = v29; + v58 = v30; + v59 = v31; + v60 = v32; + v61 = v33; + v62 = v34; + v63 = v35; + v64 = v36; + v65 = v37; + v66 = v48; + if v66 != 0 { + __current_block = Block::B4; + } else { + __current_block = Block::B6; + } + continue; + } + Block::B4 => { + v67 = v47; + v68 = 1i32; + v69 = v67 & v68; + v70 = v50; + v71 = 1i32; + v72 = v70.wrapping_shl((v71 & 31) as u32); + v73 = v69 | v72; + v74 = v73; + v75 = v48; + v76 = 1i32; + v77 = v75.wrapping_sub(v76); + v78 = v77; + v79 = v47; + v80 = 1i32; + v81 = (v79 as u32).wrapping_shr((v80 & 31) as u32) as i32; + v82 = v81; + v47 = v82; + v48 = v78; + v50 = v74; + __current_block = Block::B3; + continue; + } + Block::B5 => { + return Err(WasmTrap::Unreachable); + } + Block::B6 => { + __current_block = Block::B7; + continue; + } + Block::B7 => { + __current_block = Block::B8; + continue; + } + Block::B8 => { + v83 = v49; + v84 = v50; + v85 = if v83 < v84 { 1i32 } else { 0i32 }; + if v85 != 0 { + __current_block = Block::B9; + } else { + __current_block = Block::B10; + } + continue; + } + Block::B9 => { + v86 = v49; + v87 = 3i32; + v88 = v86.wrapping_shl((v87 & 31) as u32); + v89 = v88; + v90 = 1024i32; + v91 = v88.wrapping_add(v90); + v92 = v91; + v93 = memory.load_f32(v91 as usize)?; + v94 = v93; + v95 = v91; + v96 = v50; + v97 = 3i32; + v98 = v96.wrapping_shl((v97 & 31) as u32); + v99 = v98; + v100 = 1024i32; + v101 = v98.wrapping_add(v100); + v102 = v101; + v103 = memory.load_f32(v101 as usize)?; + memory.store_f32(v95 as usize, v103)?; + v104 = v88; + v105 = 1028i32; + v106 = v104.wrapping_add(v105); + v107 = v106; + v108 = memory.load_f32(v106 as usize)?; + v109 = v108; + v110 = v106; + v111 = v98; + v112 = 1028i32; + v113 = v111.wrapping_add(v112); + v114 = v113; + v115 = memory.load_f32(v113 as usize)?; + memory.store_f32(v110 as usize, v115)?; + v116 = v101; + v117 = v93; + memory.store_f32(v116 as usize, v117)?; + v118 = v113; + v119 = v108; + memory.store_f32(v118 as usize, v119)?; + v120 = v114; + v121 = v99; + v122 = v102; + v123 = v94; + v124 = v109; + __current_block = Block::B11; + continue; + } + Block::B10 => { + v120 = v47; + v121 = v48; + v122 = v50; + v123 = v59; + v124 = v60; + __current_block = Block::B11; + continue; + } + Block::B11 => { + v125 = v49; + v126 = 1i32; + v127 = v125.wrapping_add(v126); + v128 = v127; + v19 = v120; + v20 = v121; + v21 = v128; + v22 = v122; + v23 = v51; + v24 = v52; + v25 = v53; + v26 = v54; + v27 = v55; + v28 = v56; + v29 = v57; + v30 = v58; + v31 = v123; + v32 = v124; + v33 = v61; + v34 = v62; + v35 = v63; + v36 = v64; + v37 = v65; + __current_block = Block::B1; + continue; + } + Block::B12 => { + return Err(WasmTrap::Unreachable); + } + Block::B13 => { + __current_block = Block::B14; + continue; + } + Block::B14 => { + __current_block = Block::B15; + continue; + } + Block::B15 => { + v129 = 2i32; + v130 = v129; + v131 = v19; + v132 = v20; + v133 = v130; + v134 = v22; + v135 = v23; + v136 = v24; + v137 = v25; + v138 = v26; + v139 = v27; + v140 = v28; + v141 = v29; + v142 = v30; + v143 = v31; + v144 = v32; + v145 = v33; + v146 = v34; + v147 = v35; + v148 = v36; + v149 = v37; + __current_block = Block::B16; + continue; + } + Block::B16 => { + v150 = v133; + v151 = 4097i32; + v152 = if (v150 as u32) >= (v151 as u32) { 1 } else { 0 }; + if v152 != 0 { + __current_block = Block::B17; + } else { + __current_block = Block::B23; + } + continue; + } + Block::B17 => { + v153 = -8192i32; + v154 = v153; + v155 = 1024i32; + v156 = v155; + v157 = v156; + v158 = v154; + v169 = v143; + __current_block = Block::B18; + continue; + } + Block::B18 => { + v159 = v133; + v160 = v134; + v161 = v135; + v162 = v136; + v163 = v137; + v164 = v138; + v165 = v139; + v166 = v140; + v167 = v141; + v168 = v142; + v170 = v144; + v171 = v145; + v172 = v146; + v173 = v147; + v174 = v148; + v175 = v149; + v176 = v158; + v177 = if v176 == 0 { 1 } else { 0 }; + if v177 != 0 { + __current_block = Block::B38; + } else { + __current_block = Block::B19; + } + continue; + } + Block::B19 => { + v178 = v158; + v179 = 41984i32; + v180 = v178.wrapping_add(v179); + v181 = v157; + v182 = memory.load_f32(v181 as usize)?; + v183 = v182; + v184 = v182; + v185 = v182 * v184; + v186 = v157; + v187 = 4i32; + v188 = v186.wrapping_add(v187); + v189 = memory.load_f32(v188 as usize)?; + v190 = v189; + v191 = v189; + v192 = v189 * v191; + v193 = v185 + v192; + v194 = v193.sqrt(); + memory.store_f32(v180 as usize, v194)?; + v195 = v157; + v196 = 8i32; + v197 = v195.wrapping_add(v196); + v198 = v197; + v199 = v158; + v200 = 4i32; + v201 = v199.wrapping_add(v200); + v202 = v201; + v157 = v198; + v158 = v202; + v169 = v190; + __current_block = Block::B18; + continue; + } + Block::B20 => { + return Err(WasmTrap::Unreachable); + } + Block::B21 => { + return Err(WasmTrap::Unreachable); + } + Block::B22 => { + return Err(WasmTrap::Unreachable); + } + Block::B23 => { + __current_block = Block::B24; + continue; + } + Block::B24 => { + v203 = v133; + v204 = 3i32; + v205 = v203.wrapping_shl((v204 & 31) as u32); + v206 = v205; + v207 = 1024i32; + v208 = v207; + v209 = v133; + v210 = 2i32; + v211 = v209.wrapping_shl((v210 & 31) as u32); + v212 = v211; + v213 = 1024i32; + v214 = v211.wrapping_add(v213); + v215 = v214; + v216 = 2048i32; + v217 = v133; + v218 = 1i32; + v219 = (v217 as u32).wrapping_shr((v218 & 31) as u32) as i32; + v220 = 65535i32; + v221 = v219 & v220; + v222 = i32_div_u(v216, v221)?; + v223 = 2i32; + v224 = v222.wrapping_shl((v223 & 31) as u32); + v225 = v224; + v226 = 0i32; + v227 = v226; + v228 = v131; + v229 = v132; + v230 = v133; + v231 = v208; + v232 = v135; + v233 = v215; + v234 = v227; + v235 = v206; + v236 = v139; + v237 = v212; + v238 = v225; + v239 = v142; + v240 = v143; + v241 = v144; + v242 = v145; + v243 = v146; + v244 = v147; + v245 = v148; + v246 = v149; + __current_block = Block::B25; + continue; + } + Block::B25 => { + v247 = v234; + v248 = 4095i32; + v249 = if (v247 as u32) > (v248 as u32) { 1 } else { 0 }; + if v249 != 0 { + __current_block = Block::B40; + } else { + __current_block = Block::B26; + } + continue; + } + Block::B26 => { + v250 = 0i32; + v251 = v250; + v252 = 0i32; + v253 = v252; + v254 = v251; + v255 = v253; + v258 = v232; + v262 = v236; + v265 = v239; + v266 = v240; + v267 = v241; + v268 = v242; + v269 = v243; + v270 = v244; + v271 = v245; + v272 = v246; + __current_block = Block::B27; + continue; + } + Block::B27 => { + v256 = v230; + v257 = v231; + v259 = v233; + v260 = v234; + v261 = v235; + v263 = v237; + v264 = v238; + v273 = v255; + v274 = v237; + v275 = if v273 == v274 { 1i32 } else { 0i32 }; + if v275 != 0 { + __current_block = Block::B28; + } else { + __current_block = Block::B30; + } + continue; + } + Block::B28 => { + v276 = v259; + v277 = v261; + v278 = v276.wrapping_add(v277); + v279 = v278; + v280 = v257; + v281 = v261; + v282 = v280.wrapping_add(v281); + v283 = v282; + v284 = v256; + v285 = v260; + v286 = v284.wrapping_add(v285); + v287 = v286; + v228 = v254; + v229 = v255; + v230 = v256; + v231 = v283; + v232 = v258; + v233 = v279; + v234 = v287; + v235 = v261; + v236 = v262; + v237 = v263; + v238 = v264; + v239 = v265; + v240 = v266; + v241 = v267; + v242 = v268; + v243 = v269; + v244 = v270; + v245 = v271; + v246 = v272; + __current_block = Block::B25; + continue; + } + Block::B29 => { + return Err(WasmTrap::Unreachable); + } + Block::B30 => { + v288 = v255; + v289 = v257; + v290 = v288.wrapping_add(v289); + v291 = v290; + v292 = 4i32; + v293 = v290.wrapping_add(v292); + v294 = v293; + v295 = v293; + v296 = memory.load_f32(v295 as usize)?; + v297 = v296; + v298 = v254; + v299 = 41984i32; + v300 = v298.wrapping_add(v299); + v301 = memory.load_f32(v300 as usize)?; + v302 = v301; + v303 = v255; + v304 = v259; + v305 = v303.wrapping_add(v304); + v306 = v305; + v307 = 4i32; + v308 = v305.wrapping_add(v307); + v309 = v308; + v310 = memory.load_f32(v308 as usize)?; + v311 = v310; + v312 = v301 * v310; + v313 = v254; + v314 = 50176i32; + v315 = v313.wrapping_add(v314); + v316 = memory.load_f32(v315 as usize)?; + v317 = v316; + v318 = v305; + v319 = memory.load_f32(v318 as usize)?; + v320 = v319; + v321 = v316 * v319; + v322 = v312 + v321; + v323 = v322; + v324 = v296 + v322; + memory.store_f32(v293 as usize, v324)?; + v325 = v290; + v326 = v290; + v327 = memory.load_f32(v326 as usize)?; + v328 = v327; + v329 = v301; + v330 = v319; + v331 = v329 * v330; + v332 = v310; + v333 = v316; + v334 = v332 * v333; + v335 = v331 - v334; + v336 = v335; + v337 = v327 + v335; + memory.store_f32(v325 as usize, v337)?; + v338 = v305; + v339 = v327; + v340 = v335; + v341 = v339 - v340; + memory.store_f32(v338 as usize, v341)?; + v342 = v308; + v343 = v296; + v344 = v322; + v345 = v343 - v344; + memory.store_f32(v342 as usize, v345)?; + v346 = v254; + v347 = v264; + v348 = v346.wrapping_add(v347); + v349 = v348; + v350 = v255; + v351 = 8i32; + v352 = v350.wrapping_add(v351); + v353 = v352; + v254 = v349; + v255 = v353; + v258 = v306; + v262 = v291; + v265 = v309; + v266 = v297; + v267 = v336; + v268 = v311; + v269 = v317; + v270 = v320; + v271 = v323; + v272 = v328; + __current_block = Block::B27; + continue; + } + Block::B31 => { + return Err(WasmTrap::Unreachable); + } + Block::B32 => { + return Err(WasmTrap::Unreachable); + } + Block::B33 => { + return Err(WasmTrap::Unreachable); + } + Block::B34 => { + return Err(WasmTrap::Unreachable); + } + Block::B35 => { + return Err(WasmTrap::Unreachable); + } + Block::B36 => { + return Err(WasmTrap::Unreachable); + } + Block::B37 => { + return Err(WasmTrap::Unreachable); + } + Block::B38 => { + return Ok(()); + } + Block::B39 => { + return Err(WasmTrap::Unreachable); + } + Block::B40 => { + v354 = v230; + v355 = 1i32; + v356 = v354.wrapping_shl((v355 & 31) as u32); + v357 = v356; + v131 = v228; + v132 = v229; + v133 = v357; + v134 = v231; + v135 = v232; + v136 = v233; + v137 = v234; + v138 = v235; + v139 = v236; + v140 = v237; + v141 = v238; + v142 = v239; + v143 = v240; + v144 = v241; + v145 = v242; + v146 = v243; + v147 = v244; + v148 = v245; + v149 = v246; + __current_block = Block::B16; + continue; + } + Block::B41 => { + return Err(WasmTrap::Unreachable); + } + Block::B42 => { + return Err(WasmTrap::Unreachable); + } + Block::B43 => { + return Ok(()); + } + } + } +} + +#[allow(unused_mut, unused_variables, unused_assignments, clippy::only_used_in_recursion, clippy::needless_return, clippy::manual_range_contains, clippy::never_loop)] +fn func_4(env: &mut Env<'_, H>, memory: &mut IsolatedMemory) -> WasmResult { + let mut v1: i32 = 0i32; + #[derive(Clone, Copy)] + #[allow(dead_code)] + enum Block { B0 } + let mut __current_block = Block::B0; + loop { + match __current_block { + Block::B0 => { + v1 = 33792i32; + return Ok(v1); + } + } + } +} + +impl WasmModule { + pub fn __wasm_call_ctors(&mut self) -> WasmResult<()> { + let mut __host = herkos_runtime::NoHost; + let mut env = Env { host: &mut __host, globals: &mut self.0.globals }; + func_0(&mut env, &mut self.0.memory) + } + pub fn fft_init(&mut self, v0: i32) -> WasmResult<()> { + let mut __host = herkos_runtime::NoHost; + let mut env = Env { host: &mut __host, globals: &mut self.0.globals }; + func_1(v0, &mut env, &mut self.0.memory) + } + pub fn fft_get_input_ptr(&mut self) -> WasmResult { + let mut __host = herkos_runtime::NoHost; + let mut env = Env { host: &mut __host, globals: &mut self.0.globals }; + func_2(&mut env, &mut self.0.memory) + } + pub fn fft_compute(&mut self, v0: i32) -> WasmResult<()> { + let mut __host = herkos_runtime::NoHost; + let mut env = Env { host: &mut __host, globals: &mut self.0.globals }; + func_3(v0, &mut env, &mut self.0.memory) + } + pub fn fft_get_output_ptr(&mut self) -> WasmResult { + let mut __host = herkos_runtime::NoHost; + let mut env = Env { host: &mut __host, globals: &mut self.0.globals }; + func_4(&mut env, &mut self.0.memory) + } +} + diff --git a/examples/c-fft/src/main.rs b/examples/c-fft/src/main.rs new file mode 100644 index 0000000..462daa4 --- /dev/null +++ b/examples/c-fft/src/main.rs @@ -0,0 +1,174 @@ +// C → WebAssembly → Rust FFT example +// +// This program drives a 4096-point radix-2 FFT that was originally written in C, +// compiled to WebAssembly, and then transpiled to memory-safe Rust by herkos. +// +// The generated module (src/fft_wasm.rs) contains no unsafe code. +// Memory access is bounds-checked; isolation is enforced by the Rust type system. +// +// Run `./run.sh` to regenerate fft_wasm.rs and execute this program. + +#[allow(dead_code)] +mod fft_wasm; + +use std::time::Instant; + +const N: usize = 4096; +const N_HALF: usize = N / 2; +const SAMPLE_RATE: f32 = 44100.0; + +fn main() { + let mut module = fft_wasm::new().expect("FFT module instantiation failed"); + + // Initialize twiddle table (called once) + module.fft_init(N as i32).expect("fft_init trapped"); + + // Get the pointer (Wasm byte offset) to the input buffer + let input_ptr = module.fft_get_input_ptr().expect("fft_get_input_ptr trapped") as usize; + let output_ptr = module.fft_get_output_ptr().expect("fft_get_output_ptr trapped") as usize; + + println!("=== herkos C-FFT Example ==="); + println!(" 4096-point radix-2 DIT FFT, C → Wasm → memory-safe Rust"); + println!(" Input buffer: Wasm byte offset 0x{:05X}", input_ptr); + println!(" Output buffer: Wasm byte offset 0x{:05X}", output_ptr); + println!(); + + // ── Test 1: Single tone at 1 kHz ───────────────────────────────────────── + println!("--- Test 1: Single tone at 1000 Hz ---"); + write_tone(&mut module, input_ptr, &[(1000.0, 1.0)]); + let elapsed = run_fft(&mut module); + let magnitudes = read_magnitudes(&module, output_ptr); + print_spectrum(&magnitudes, "1000 Hz tone", elapsed); + let peak = find_peak_bin(&magnitudes); + println!( + " Peak bin: {} → {:.1} Hz", + peak, + bin_to_hz(peak) + ); + println!(); + + // ── Test 2: Two tones at 440 Hz and 2000 Hz ─────────────────────────────── + println!("--- Test 2: Two tones (440 Hz + 2000 Hz) ---"); + write_tone(&mut module, input_ptr, &[(440.0, 0.8), (2000.0, 0.5)]); + let elapsed = run_fft(&mut module); + let magnitudes = read_magnitudes(&module, output_ptr); + print_spectrum(&magnitudes, "440 Hz + 2000 Hz", elapsed); + let peaks = find_top_bins(&magnitudes, 3); + println!(" Top bins:"); + for (bin, mag) in &peaks { + println!(" bin {:4} → {:7.1} Hz mag={:.1}", bin, bin_to_hz(*bin), mag); + } + println!(); + + // ── Test 3: Tone + harmonic ────────────────────────────────────────────── + println!("--- Test 3: 3520 Hz tone + 880 Hz harmonic ---"); + write_tone(&mut module, input_ptr, &[(3520.0, 1.0), (880.0, 0.3)]); + let elapsed = run_fft(&mut module); + let magnitudes = read_magnitudes(&module, output_ptr); + print_spectrum(&magnitudes, "3520 Hz + 880 Hz", elapsed); + println!(); +} + +/// Write a sum of sinusoids into the FFT input buffer. +/// `freqs`: slice of (frequency_hz, amplitude) pairs. +/// Input is interleaved complex: [re_0, im_0, re_1, im_1, ...], imaginary parts = 0. +fn write_tone(module: &mut fft_wasm::WasmModule, input_ptr: usize, freqs: &[(f32, f32)]) { + use std::f32::consts::PI; + for i in 0..N { + let t = i as f32 / SAMPLE_RATE; + let mut sample = 0.0f32; + for &(freq, amp) in freqs { + sample += amp * (2.0 * PI * freq * t).sin(); + } + let re_offset = input_ptr + i * 8; // 8 bytes per complex (re+im f32) + let im_offset = input_ptr + i * 8 + 4; + module.0.memory.store_f32(re_offset, sample).expect("store real"); + module.0.memory.store_f32(im_offset, 0.0f32).expect("store imag"); + } +} + +/// Run the FFT and return elapsed wall-clock time. +fn run_fft(module: &mut fft_wasm::WasmModule) -> std::time::Duration { + let start = Instant::now(); + module.fft_compute(N as i32).expect("fft_compute trapped"); + start.elapsed() +} + +/// Read N/2 magnitude values from the output buffer. +fn read_magnitudes(module: &fft_wasm::WasmModule, output_ptr: usize) -> Vec { + (0..N_HALF) + .map(|k| { + module + .0 + .memory + .load_f32(output_ptr + k * 4) + .expect("load magnitude") + }) + .collect() +} + +/// Find the bin with the highest magnitude. +fn find_peak_bin(magnitudes: &[f32]) -> usize { + magnitudes + .iter() + .enumerate() + .skip(1) // skip DC + .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap()) + .map(|(i, _)| i) + .unwrap_or(0) +} + +/// Find the top `n` bins by magnitude, sorted descending. +fn find_top_bins(magnitudes: &[f32], n: usize) -> Vec<(usize, f32)> { + let mut indexed: Vec<(usize, f32)> = magnitudes + .iter() + .enumerate() + .skip(1) + .map(|(i, &m)| (i, m)) + .collect(); + indexed.sort_by(|(_, a), (_, b)| b.partial_cmp(a).unwrap()); + indexed.truncate(n); + indexed +} + +/// Convert bin index to frequency in Hz. +fn bin_to_hz(bin: usize) -> f32 { + bin as f32 * SAMPLE_RATE / N as f32 +} + +/// Print a compact ASCII spectrum showing the top bins. +fn print_spectrum(magnitudes: &[f32], label: &str, elapsed: std::time::Duration) { + println!(" Spectrum ({label}) — {:.2?}", elapsed); + + // Find global max for normalization (skip DC bin 0) + let max_mag = magnitudes[1..].iter().cloned().fold(0.0f32, f32::max); + if max_mag <= 0.0 { + println!(" (empty spectrum)"); + return; + } + + // Collect top 12 bins + let mut top: Vec<(usize, f32)> = magnitudes + .iter() + .enumerate() + .skip(1) + .map(|(i, &m)| (i, m)) + .filter(|(_, m)| *m > max_mag * 0.05) // threshold at 5% of peak + .collect(); + top.sort_by(|(_, a), (_, b)| b.partial_cmp(a).unwrap()); + top.truncate(12); + top.sort_by_key(|(bin, _)| *bin); // re-sort by frequency for display + + const BAR_WIDTH: usize = 30; + for (bin, mag) in &top { + let bar_len = ((mag / max_mag) * BAR_WIDTH as f32) as usize; + let bar: String = "#".repeat(bar_len); + println!( + " {:5.0} Hz (bin {:4}): {:30} {:.1}", + bin_to_hz(*bin), + bin, + bar, + mag + ); + } +} From 0919f3955994f4dcc80e5123808255ffa8209388 Mon Sep 17 00:00:00 2001 From: Arnaud Riess Date: Mon, 16 Mar 2026 19:00:20 +0000 Subject: [PATCH 4/6] Refactor code structure for improved readability and maintainability --- examples/c-fft/.gitignore | 2 + examples/c-fft/src/fft_wasm.rs | 1242 -------------------------------- 2 files changed, 2 insertions(+), 1242 deletions(-) create mode 100644 examples/c-fft/.gitignore delete mode 100644 examples/c-fft/src/fft_wasm.rs diff --git a/examples/c-fft/.gitignore b/examples/c-fft/.gitignore new file mode 100644 index 0000000..6d911d6 --- /dev/null +++ b/examples/c-fft/.gitignore @@ -0,0 +1,2 @@ +src/fft_wasm.rs +fft.wasm \ No newline at end of file diff --git a/examples/c-fft/src/fft_wasm.rs b/examples/c-fft/src/fft_wasm.rs deleted file mode 100644 index f9a6b3d..0000000 --- a/examples/c-fft/src/fft_wasm.rs +++ /dev/null @@ -1,1242 +0,0 @@ -// Generated by herkos v0.1.1 -// Wasm binary version: 1 -// DO NOT EDIT - -use herkos_runtime::*; - -const MAX_PAGES: usize = 2; - -pub trait ModuleHostTrait { -} - -impl ModuleHostTrait for herkos_runtime::NoHost {} - -pub struct Globals { -} - -#[allow(dead_code)] -struct Env<'a, H: ModuleHostTrait + ?Sized> { - pub host: &'a mut H, - pub globals: &'a mut Globals, -} - -pub const G0: i32 = 1024i32; -pub const G1: i32 = 58368i32; -pub const G2: i32 = 58368i32; -pub const G3: i32 = 123904i32; -pub const G4: i32 = 1024i32; -pub const G5: i32 = 123904i32; -pub const G6: i32 = 131072i32; -pub const G7: i32 = 0i32; -pub const G8: i32 = 1i32; - -pub struct WasmModule(pub Module); - -pub fn new() -> WasmResult { - let mut __slot = core::mem::MaybeUninit::>::uninit(); - Module::try_init(&mut __slot, 2, Globals {}, Table::try_new(0)?).map_err(|_| WasmTrap::OutOfBounds)?; - let module = unsafe { __slot.assume_init() }; - Ok(WasmModule(module)) -} - -#[allow(unused_mut, unused_variables, unused_assignments, clippy::only_used_in_recursion, clippy::needless_return, clippy::manual_range_contains, clippy::never_loop)] -fn func_0(env: &mut Env<'_, H>, memory: &mut IsolatedMemory) -> WasmResult<()> { - #[derive(Clone, Copy)] - #[allow(dead_code)] - enum Block { B0 } - let mut __current_block = Block::B0; - loop { - match __current_block { - Block::B0 => { - return Ok(()); - } - } - } -} - -#[allow(unused_mut, unused_variables, unused_assignments, clippy::only_used_in_recursion, clippy::needless_return, clippy::manual_range_contains, clippy::never_loop)] -fn func_1(mut v0: i32, env: &mut Env<'_, H>, memory: &mut IsolatedMemory) -> WasmResult<()> { - let mut v1: f32 = 0.0f32; - let mut v2: f32 = 0.0f32; - let mut v3: f32 = 0.0f32; - let mut v4: i32 = 0i32; - let mut v5: i32 = 0i32; - let mut v6: i32 = 0i32; - let mut v7: i32 = 0i32; - let mut v8: f32 = 0.0f32; - let mut v9: f32 = 0.0f32; - let mut v10: i32 = 0i32; - let mut v11: i32 = 0i32; - let mut v12: i32 = 0i32; - let mut v13: f32 = 0.0f32; - let mut v14: f32 = 0.0f32; - let mut v15: f32 = 0.0f32; - let mut v16: i32 = 0i32; - let mut v17: i32 = 0i32; - let mut v18: i32 = 0i32; - let mut v19: i32 = 0i32; - let mut v20: i32 = 0i32; - let mut v21: i32 = 0i32; - let mut v22: i32 = 0i32; - let mut v23: f32 = 0.0f32; - let mut v24: f32 = 0.0f32; - let mut v25: f32 = 0.0f32; - let mut v26: f32 = 0.0f32; - let mut v27: f32 = 0.0f32; - let mut v28: f32 = 0.0f32; - let mut v29: f32 = 0.0f32; - let mut v30: f32 = 0.0f32; - let mut v31: i32 = 0i32; - let mut v32: i32 = 0i32; - let mut v33: i32 = 0i32; - let mut v34: f32 = 0.0f32; - let mut v35: f32 = 0.0f32; - let mut v36: f32 = 0.0f32; - let mut v37: f32 = 0.0f32; - let mut v38: f32 = 0.0f32; - let mut v39: f32 = 0.0f32; - let mut v40: f32 = 0.0f32; - let mut v41: f32 = 0.0f32; - let mut v42: i32 = 0i32; - let mut v43: i32 = 0i32; - let mut v44: i32 = 0i32; - let mut v45: i32 = 0i32; - let mut v46: f32 = 0.0f32; - let mut v47: f32 = 0.0f32; - #[derive(Clone, Copy)] - #[allow(dead_code)] - enum Block { B0, B1, B2, B3, B4, B5, B6 } - let mut __current_block = Block::B0; - loop { - match __current_block { - Block::B0 => { - v4 = 41984i32; - v5 = 1065353216i32; - memory.store_i32(v4 as usize, v5)?; - v6 = 50176i32; - v7 = 0i32; - memory.store_i32(v6 as usize, v7)?; - v8 = 1f32; - v9 = v8; - v10 = 4i32; - v11 = v10; - v12 = v11; - v13 = v9; - v14 = v2; - v15 = v3; - __current_block = Block::B1; - continue; - } - Block::B1 => { - v16 = v12; - v17 = 8192i32; - v18 = if v16 == v17 { 1i32 } else { 0i32 }; - v19 = if v18 == 0 { 1 } else { 0 }; - if v19 != 0 { - __current_block = Block::B2; - } else { - __current_block = Block::B4; - } - continue; - } - Block::B2 => { - v20 = v12; - v21 = 50176i32; - v22 = v20.wrapping_add(v21); - v23 = v14; - v24 = 0.9999988f32; - v25 = v23 * v24; - v26 = v13; - v27 = -0.0015339802f32; - v28 = v26 * v27; - v29 = v25 + v28; - v30 = v29; - memory.store_f32(v22 as usize, v29)?; - v31 = v12; - v32 = 41984i32; - v33 = v31.wrapping_add(v32); - v34 = v13; - v35 = 0.9999988f32; - v36 = v34 * v35; - v37 = v14; - v38 = 0.0015339802f32; - v39 = v37 * v38; - v40 = v36 + v39; - v41 = v40; - memory.store_f32(v33 as usize, v40)?; - v42 = v12; - v43 = 4i32; - v44 = v42.wrapping_add(v43); - v45 = v44; - v46 = v30; - v47 = v46; - v12 = v45; - v13 = v41; - v14 = v47; - v15 = v30; - __current_block = Block::B1; - continue; - } - Block::B3 => { - return Err(WasmTrap::Unreachable); - } - Block::B4 => { - __current_block = Block::B5; - continue; - } - Block::B5 => { - __current_block = Block::B6; - continue; - } - Block::B6 => { - return Ok(()); - } - } - } -} - -#[allow(unused_mut, unused_variables, unused_assignments, clippy::only_used_in_recursion, clippy::needless_return, clippy::manual_range_contains, clippy::never_loop)] -fn func_2(env: &mut Env<'_, H>, memory: &mut IsolatedMemory) -> WasmResult { - let mut v1: i32 = 0i32; - #[derive(Clone, Copy)] - #[allow(dead_code)] - enum Block { B0 } - let mut __current_block = Block::B0; - loop { - match __current_block { - Block::B0 => { - v1 = 1024i32; - return Ok(v1); - } - } - } -} - -#[allow(unused_mut, unused_variables, unused_assignments, clippy::only_used_in_recursion, clippy::needless_return, clippy::manual_range_contains, clippy::never_loop)] -fn func_3(mut v0: i32, env: &mut Env<'_, H>, memory: &mut IsolatedMemory) -> WasmResult<()> { - let mut v1: i32 = 0i32; - let mut v2: i32 = 0i32; - let mut v3: i32 = 0i32; - let mut v4: i32 = 0i32; - let mut v5: i32 = 0i32; - let mut v6: i32 = 0i32; - let mut v7: i32 = 0i32; - let mut v8: i32 = 0i32; - let mut v9: i32 = 0i32; - let mut v10: i32 = 0i32; - let mut v11: i32 = 0i32; - let mut v12: f32 = 0.0f32; - let mut v13: f32 = 0.0f32; - let mut v14: f32 = 0.0f32; - let mut v15: f32 = 0.0f32; - let mut v16: f32 = 0.0f32; - let mut v17: f32 = 0.0f32; - let mut v18: f32 = 0.0f32; - let mut v19: i32 = 0i32; - let mut v20: i32 = 0i32; - let mut v21: i32 = 0i32; - let mut v22: i32 = 0i32; - let mut v23: i32 = 0i32; - let mut v24: i32 = 0i32; - let mut v25: i32 = 0i32; - let mut v26: i32 = 0i32; - let mut v27: i32 = 0i32; - let mut v28: i32 = 0i32; - let mut v29: i32 = 0i32; - let mut v30: i32 = 0i32; - let mut v31: f32 = 0.0f32; - let mut v32: f32 = 0.0f32; - let mut v33: f32 = 0.0f32; - let mut v34: f32 = 0.0f32; - let mut v35: f32 = 0.0f32; - let mut v36: f32 = 0.0f32; - let mut v37: f32 = 0.0f32; - let mut v38: i32 = 0i32; - let mut v39: i32 = 0i32; - let mut v40: i32 = 0i32; - let mut v41: i32 = 0i32; - let mut v42: i32 = 0i32; - let mut v43: i32 = 0i32; - let mut v44: i32 = 0i32; - let mut v45: i32 = 0i32; - let mut v46: i32 = 0i32; - let mut v47: i32 = 0i32; - let mut v48: i32 = 0i32; - let mut v49: i32 = 0i32; - let mut v50: i32 = 0i32; - let mut v51: i32 = 0i32; - let mut v52: i32 = 0i32; - let mut v53: i32 = 0i32; - let mut v54: i32 = 0i32; - let mut v55: i32 = 0i32; - let mut v56: i32 = 0i32; - let mut v57: i32 = 0i32; - let mut v58: i32 = 0i32; - let mut v59: f32 = 0.0f32; - let mut v60: f32 = 0.0f32; - let mut v61: f32 = 0.0f32; - let mut v62: f32 = 0.0f32; - let mut v63: f32 = 0.0f32; - let mut v64: f32 = 0.0f32; - let mut v65: f32 = 0.0f32; - let mut v66: i32 = 0i32; - let mut v67: i32 = 0i32; - let mut v68: i32 = 0i32; - let mut v69: i32 = 0i32; - let mut v70: i32 = 0i32; - let mut v71: i32 = 0i32; - let mut v72: i32 = 0i32; - let mut v73: i32 = 0i32; - let mut v74: i32 = 0i32; - let mut v75: i32 = 0i32; - let mut v76: i32 = 0i32; - let mut v77: i32 = 0i32; - let mut v78: i32 = 0i32; - let mut v79: i32 = 0i32; - let mut v80: i32 = 0i32; - let mut v81: i32 = 0i32; - let mut v82: i32 = 0i32; - let mut v83: i32 = 0i32; - let mut v84: i32 = 0i32; - let mut v85: i32 = 0i32; - let mut v86: i32 = 0i32; - let mut v87: i32 = 0i32; - let mut v88: i32 = 0i32; - let mut v89: i32 = 0i32; - let mut v90: i32 = 0i32; - let mut v91: i32 = 0i32; - let mut v92: i32 = 0i32; - let mut v93: f32 = 0.0f32; - let mut v94: f32 = 0.0f32; - let mut v95: i32 = 0i32; - let mut v96: i32 = 0i32; - let mut v97: i32 = 0i32; - let mut v98: i32 = 0i32; - let mut v99: i32 = 0i32; - let mut v100: i32 = 0i32; - let mut v101: i32 = 0i32; - let mut v102: i32 = 0i32; - let mut v103: f32 = 0.0f32; - let mut v104: i32 = 0i32; - let mut v105: i32 = 0i32; - let mut v106: i32 = 0i32; - let mut v107: i32 = 0i32; - let mut v108: f32 = 0.0f32; - let mut v109: f32 = 0.0f32; - let mut v110: i32 = 0i32; - let mut v111: i32 = 0i32; - let mut v112: i32 = 0i32; - let mut v113: i32 = 0i32; - let mut v114: i32 = 0i32; - let mut v115: f32 = 0.0f32; - let mut v116: i32 = 0i32; - let mut v117: f32 = 0.0f32; - let mut v118: i32 = 0i32; - let mut v119: f32 = 0.0f32; - let mut v120: i32 = 0i32; - let mut v121: i32 = 0i32; - let mut v122: i32 = 0i32; - let mut v123: f32 = 0.0f32; - let mut v124: f32 = 0.0f32; - let mut v125: i32 = 0i32; - let mut v126: i32 = 0i32; - let mut v127: i32 = 0i32; - let mut v128: i32 = 0i32; - let mut v129: i32 = 0i32; - let mut v130: i32 = 0i32; - let mut v131: i32 = 0i32; - let mut v132: i32 = 0i32; - let mut v133: i32 = 0i32; - let mut v134: i32 = 0i32; - let mut v135: i32 = 0i32; - let mut v136: i32 = 0i32; - let mut v137: i32 = 0i32; - let mut v138: i32 = 0i32; - let mut v139: i32 = 0i32; - let mut v140: i32 = 0i32; - let mut v141: i32 = 0i32; - let mut v142: i32 = 0i32; - let mut v143: f32 = 0.0f32; - let mut v144: f32 = 0.0f32; - let mut v145: f32 = 0.0f32; - let mut v146: f32 = 0.0f32; - let mut v147: f32 = 0.0f32; - let mut v148: f32 = 0.0f32; - let mut v149: f32 = 0.0f32; - let mut v150: i32 = 0i32; - let mut v151: i32 = 0i32; - let mut v152: i32 = 0i32; - let mut v153: i32 = 0i32; - let mut v154: i32 = 0i32; - let mut v155: i32 = 0i32; - let mut v156: i32 = 0i32; - let mut v157: i32 = 0i32; - let mut v158: i32 = 0i32; - let mut v159: i32 = 0i32; - let mut v160: i32 = 0i32; - let mut v161: i32 = 0i32; - let mut v162: i32 = 0i32; - let mut v163: i32 = 0i32; - let mut v164: i32 = 0i32; - let mut v165: i32 = 0i32; - let mut v166: i32 = 0i32; - let mut v167: i32 = 0i32; - let mut v168: i32 = 0i32; - let mut v169: f32 = 0.0f32; - let mut v170: f32 = 0.0f32; - let mut v171: f32 = 0.0f32; - let mut v172: f32 = 0.0f32; - let mut v173: f32 = 0.0f32; - let mut v174: f32 = 0.0f32; - let mut v175: f32 = 0.0f32; - let mut v176: i32 = 0i32; - let mut v177: i32 = 0i32; - let mut v178: i32 = 0i32; - let mut v179: i32 = 0i32; - let mut v180: i32 = 0i32; - let mut v181: i32 = 0i32; - let mut v182: f32 = 0.0f32; - let mut v183: f32 = 0.0f32; - let mut v184: f32 = 0.0f32; - let mut v185: f32 = 0.0f32; - let mut v186: i32 = 0i32; - let mut v187: i32 = 0i32; - let mut v188: i32 = 0i32; - let mut v189: f32 = 0.0f32; - let mut v190: f32 = 0.0f32; - let mut v191: f32 = 0.0f32; - let mut v192: f32 = 0.0f32; - let mut v193: f32 = 0.0f32; - let mut v194: f32 = 0.0f32; - let mut v195: i32 = 0i32; - let mut v196: i32 = 0i32; - let mut v197: i32 = 0i32; - let mut v198: i32 = 0i32; - let mut v199: i32 = 0i32; - let mut v200: i32 = 0i32; - let mut v201: i32 = 0i32; - let mut v202: i32 = 0i32; - let mut v203: i32 = 0i32; - let mut v204: i32 = 0i32; - let mut v205: i32 = 0i32; - let mut v206: i32 = 0i32; - let mut v207: i32 = 0i32; - let mut v208: i32 = 0i32; - let mut v209: i32 = 0i32; - let mut v210: i32 = 0i32; - let mut v211: i32 = 0i32; - let mut v212: i32 = 0i32; - let mut v213: i32 = 0i32; - let mut v214: i32 = 0i32; - let mut v215: i32 = 0i32; - let mut v216: i32 = 0i32; - let mut v217: i32 = 0i32; - let mut v218: i32 = 0i32; - let mut v219: i32 = 0i32; - let mut v220: i32 = 0i32; - let mut v221: i32 = 0i32; - let mut v222: i32 = 0i32; - let mut v223: i32 = 0i32; - let mut v224: i32 = 0i32; - let mut v225: i32 = 0i32; - let mut v226: i32 = 0i32; - let mut v227: i32 = 0i32; - let mut v228: i32 = 0i32; - let mut v229: i32 = 0i32; - let mut v230: i32 = 0i32; - let mut v231: i32 = 0i32; - let mut v232: i32 = 0i32; - let mut v233: i32 = 0i32; - let mut v234: i32 = 0i32; - let mut v235: i32 = 0i32; - let mut v236: i32 = 0i32; - let mut v237: i32 = 0i32; - let mut v238: i32 = 0i32; - let mut v239: i32 = 0i32; - let mut v240: f32 = 0.0f32; - let mut v241: f32 = 0.0f32; - let mut v242: f32 = 0.0f32; - let mut v243: f32 = 0.0f32; - let mut v244: f32 = 0.0f32; - let mut v245: f32 = 0.0f32; - let mut v246: f32 = 0.0f32; - let mut v247: i32 = 0i32; - let mut v248: i32 = 0i32; - let mut v249: i32 = 0i32; - let mut v250: i32 = 0i32; - let mut v251: i32 = 0i32; - let mut v252: i32 = 0i32; - let mut v253: i32 = 0i32; - let mut v254: i32 = 0i32; - let mut v255: i32 = 0i32; - let mut v256: i32 = 0i32; - let mut v257: i32 = 0i32; - let mut v258: i32 = 0i32; - let mut v259: i32 = 0i32; - let mut v260: i32 = 0i32; - let mut v261: i32 = 0i32; - let mut v262: i32 = 0i32; - let mut v263: i32 = 0i32; - let mut v264: i32 = 0i32; - let mut v265: i32 = 0i32; - let mut v266: f32 = 0.0f32; - let mut v267: f32 = 0.0f32; - let mut v268: f32 = 0.0f32; - let mut v269: f32 = 0.0f32; - let mut v270: f32 = 0.0f32; - let mut v271: f32 = 0.0f32; - let mut v272: f32 = 0.0f32; - let mut v273: i32 = 0i32; - let mut v274: i32 = 0i32; - let mut v275: i32 = 0i32; - let mut v276: i32 = 0i32; - let mut v277: i32 = 0i32; - let mut v278: i32 = 0i32; - let mut v279: i32 = 0i32; - let mut v280: i32 = 0i32; - let mut v281: i32 = 0i32; - let mut v282: i32 = 0i32; - let mut v283: i32 = 0i32; - let mut v284: i32 = 0i32; - let mut v285: i32 = 0i32; - let mut v286: i32 = 0i32; - let mut v287: i32 = 0i32; - let mut v288: i32 = 0i32; - let mut v289: i32 = 0i32; - let mut v290: i32 = 0i32; - let mut v291: i32 = 0i32; - let mut v292: i32 = 0i32; - let mut v293: i32 = 0i32; - let mut v294: i32 = 0i32; - let mut v295: i32 = 0i32; - let mut v296: f32 = 0.0f32; - let mut v297: f32 = 0.0f32; - let mut v298: i32 = 0i32; - let mut v299: i32 = 0i32; - let mut v300: i32 = 0i32; - let mut v301: f32 = 0.0f32; - let mut v302: f32 = 0.0f32; - let mut v303: i32 = 0i32; - let mut v304: i32 = 0i32; - let mut v305: i32 = 0i32; - let mut v306: i32 = 0i32; - let mut v307: i32 = 0i32; - let mut v308: i32 = 0i32; - let mut v309: i32 = 0i32; - let mut v310: f32 = 0.0f32; - let mut v311: f32 = 0.0f32; - let mut v312: f32 = 0.0f32; - let mut v313: i32 = 0i32; - let mut v314: i32 = 0i32; - let mut v315: i32 = 0i32; - let mut v316: f32 = 0.0f32; - let mut v317: f32 = 0.0f32; - let mut v318: i32 = 0i32; - let mut v319: f32 = 0.0f32; - let mut v320: f32 = 0.0f32; - let mut v321: f32 = 0.0f32; - let mut v322: f32 = 0.0f32; - let mut v323: f32 = 0.0f32; - let mut v324: f32 = 0.0f32; - let mut v325: i32 = 0i32; - let mut v326: i32 = 0i32; - let mut v327: f32 = 0.0f32; - let mut v328: f32 = 0.0f32; - let mut v329: f32 = 0.0f32; - let mut v330: f32 = 0.0f32; - let mut v331: f32 = 0.0f32; - let mut v332: f32 = 0.0f32; - let mut v333: f32 = 0.0f32; - let mut v334: f32 = 0.0f32; - let mut v335: f32 = 0.0f32; - let mut v336: f32 = 0.0f32; - let mut v337: f32 = 0.0f32; - let mut v338: i32 = 0i32; - let mut v339: f32 = 0.0f32; - let mut v340: f32 = 0.0f32; - let mut v341: f32 = 0.0f32; - let mut v342: i32 = 0i32; - let mut v343: f32 = 0.0f32; - let mut v344: f32 = 0.0f32; - let mut v345: f32 = 0.0f32; - let mut v346: i32 = 0i32; - let mut v347: i32 = 0i32; - let mut v348: i32 = 0i32; - let mut v349: i32 = 0i32; - let mut v350: i32 = 0i32; - let mut v351: i32 = 0i32; - let mut v352: i32 = 0i32; - let mut v353: i32 = 0i32; - let mut v354: i32 = 0i32; - let mut v355: i32 = 0i32; - let mut v356: i32 = 0i32; - let mut v357: i32 = 0i32; - #[derive(Clone, Copy)] - #[allow(dead_code)] - enum Block { B0, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B16, B17, B18, B19, B20, B21, B22, B23, B24, B25, B26, B27, B28, B29, B30, B31, B32, B33, B34, B35, B36, B37, B38, B39, B40, B41, B42, B43 } - let mut __current_block = Block::B0; - loop { - match __current_block { - Block::B0 => { - v19 = v0; - v20 = v1; - v21 = v2; - v22 = v3; - v23 = v4; - v24 = v5; - v25 = v6; - v26 = v7; - v27 = v8; - v28 = v9; - v29 = v10; - v30 = v11; - v31 = v12; - v32 = v13; - v33 = v14; - v34 = v15; - v35 = v16; - v36 = v17; - v37 = v18; - __current_block = Block::B1; - continue; - } - Block::B1 => { - v38 = v21; - v39 = 4096i32; - v40 = if v38 != v39 { 1i32 } else { 0i32 }; - if v40 != 0 { - __current_block = Block::B2; - } else { - __current_block = Block::B13; - } - continue; - } - Block::B2 => { - v41 = 0i32; - v42 = v41; - v43 = 12i32; - v44 = v43; - v45 = v21; - v46 = v21; - v47 = v46; - v48 = v44; - v50 = v42; - __current_block = Block::B3; - continue; - } - Block::B3 => { - v49 = v21; - v51 = v23; - v52 = v24; - v53 = v25; - v54 = v26; - v55 = v27; - v56 = v28; - v57 = v29; - v58 = v30; - v59 = v31; - v60 = v32; - v61 = v33; - v62 = v34; - v63 = v35; - v64 = v36; - v65 = v37; - v66 = v48; - if v66 != 0 { - __current_block = Block::B4; - } else { - __current_block = Block::B6; - } - continue; - } - Block::B4 => { - v67 = v47; - v68 = 1i32; - v69 = v67 & v68; - v70 = v50; - v71 = 1i32; - v72 = v70.wrapping_shl((v71 & 31) as u32); - v73 = v69 | v72; - v74 = v73; - v75 = v48; - v76 = 1i32; - v77 = v75.wrapping_sub(v76); - v78 = v77; - v79 = v47; - v80 = 1i32; - v81 = (v79 as u32).wrapping_shr((v80 & 31) as u32) as i32; - v82 = v81; - v47 = v82; - v48 = v78; - v50 = v74; - __current_block = Block::B3; - continue; - } - Block::B5 => { - return Err(WasmTrap::Unreachable); - } - Block::B6 => { - __current_block = Block::B7; - continue; - } - Block::B7 => { - __current_block = Block::B8; - continue; - } - Block::B8 => { - v83 = v49; - v84 = v50; - v85 = if v83 < v84 { 1i32 } else { 0i32 }; - if v85 != 0 { - __current_block = Block::B9; - } else { - __current_block = Block::B10; - } - continue; - } - Block::B9 => { - v86 = v49; - v87 = 3i32; - v88 = v86.wrapping_shl((v87 & 31) as u32); - v89 = v88; - v90 = 1024i32; - v91 = v88.wrapping_add(v90); - v92 = v91; - v93 = memory.load_f32(v91 as usize)?; - v94 = v93; - v95 = v91; - v96 = v50; - v97 = 3i32; - v98 = v96.wrapping_shl((v97 & 31) as u32); - v99 = v98; - v100 = 1024i32; - v101 = v98.wrapping_add(v100); - v102 = v101; - v103 = memory.load_f32(v101 as usize)?; - memory.store_f32(v95 as usize, v103)?; - v104 = v88; - v105 = 1028i32; - v106 = v104.wrapping_add(v105); - v107 = v106; - v108 = memory.load_f32(v106 as usize)?; - v109 = v108; - v110 = v106; - v111 = v98; - v112 = 1028i32; - v113 = v111.wrapping_add(v112); - v114 = v113; - v115 = memory.load_f32(v113 as usize)?; - memory.store_f32(v110 as usize, v115)?; - v116 = v101; - v117 = v93; - memory.store_f32(v116 as usize, v117)?; - v118 = v113; - v119 = v108; - memory.store_f32(v118 as usize, v119)?; - v120 = v114; - v121 = v99; - v122 = v102; - v123 = v94; - v124 = v109; - __current_block = Block::B11; - continue; - } - Block::B10 => { - v120 = v47; - v121 = v48; - v122 = v50; - v123 = v59; - v124 = v60; - __current_block = Block::B11; - continue; - } - Block::B11 => { - v125 = v49; - v126 = 1i32; - v127 = v125.wrapping_add(v126); - v128 = v127; - v19 = v120; - v20 = v121; - v21 = v128; - v22 = v122; - v23 = v51; - v24 = v52; - v25 = v53; - v26 = v54; - v27 = v55; - v28 = v56; - v29 = v57; - v30 = v58; - v31 = v123; - v32 = v124; - v33 = v61; - v34 = v62; - v35 = v63; - v36 = v64; - v37 = v65; - __current_block = Block::B1; - continue; - } - Block::B12 => { - return Err(WasmTrap::Unreachable); - } - Block::B13 => { - __current_block = Block::B14; - continue; - } - Block::B14 => { - __current_block = Block::B15; - continue; - } - Block::B15 => { - v129 = 2i32; - v130 = v129; - v131 = v19; - v132 = v20; - v133 = v130; - v134 = v22; - v135 = v23; - v136 = v24; - v137 = v25; - v138 = v26; - v139 = v27; - v140 = v28; - v141 = v29; - v142 = v30; - v143 = v31; - v144 = v32; - v145 = v33; - v146 = v34; - v147 = v35; - v148 = v36; - v149 = v37; - __current_block = Block::B16; - continue; - } - Block::B16 => { - v150 = v133; - v151 = 4097i32; - v152 = if (v150 as u32) >= (v151 as u32) { 1 } else { 0 }; - if v152 != 0 { - __current_block = Block::B17; - } else { - __current_block = Block::B23; - } - continue; - } - Block::B17 => { - v153 = -8192i32; - v154 = v153; - v155 = 1024i32; - v156 = v155; - v157 = v156; - v158 = v154; - v169 = v143; - __current_block = Block::B18; - continue; - } - Block::B18 => { - v159 = v133; - v160 = v134; - v161 = v135; - v162 = v136; - v163 = v137; - v164 = v138; - v165 = v139; - v166 = v140; - v167 = v141; - v168 = v142; - v170 = v144; - v171 = v145; - v172 = v146; - v173 = v147; - v174 = v148; - v175 = v149; - v176 = v158; - v177 = if v176 == 0 { 1 } else { 0 }; - if v177 != 0 { - __current_block = Block::B38; - } else { - __current_block = Block::B19; - } - continue; - } - Block::B19 => { - v178 = v158; - v179 = 41984i32; - v180 = v178.wrapping_add(v179); - v181 = v157; - v182 = memory.load_f32(v181 as usize)?; - v183 = v182; - v184 = v182; - v185 = v182 * v184; - v186 = v157; - v187 = 4i32; - v188 = v186.wrapping_add(v187); - v189 = memory.load_f32(v188 as usize)?; - v190 = v189; - v191 = v189; - v192 = v189 * v191; - v193 = v185 + v192; - v194 = v193.sqrt(); - memory.store_f32(v180 as usize, v194)?; - v195 = v157; - v196 = 8i32; - v197 = v195.wrapping_add(v196); - v198 = v197; - v199 = v158; - v200 = 4i32; - v201 = v199.wrapping_add(v200); - v202 = v201; - v157 = v198; - v158 = v202; - v169 = v190; - __current_block = Block::B18; - continue; - } - Block::B20 => { - return Err(WasmTrap::Unreachable); - } - Block::B21 => { - return Err(WasmTrap::Unreachable); - } - Block::B22 => { - return Err(WasmTrap::Unreachable); - } - Block::B23 => { - __current_block = Block::B24; - continue; - } - Block::B24 => { - v203 = v133; - v204 = 3i32; - v205 = v203.wrapping_shl((v204 & 31) as u32); - v206 = v205; - v207 = 1024i32; - v208 = v207; - v209 = v133; - v210 = 2i32; - v211 = v209.wrapping_shl((v210 & 31) as u32); - v212 = v211; - v213 = 1024i32; - v214 = v211.wrapping_add(v213); - v215 = v214; - v216 = 2048i32; - v217 = v133; - v218 = 1i32; - v219 = (v217 as u32).wrapping_shr((v218 & 31) as u32) as i32; - v220 = 65535i32; - v221 = v219 & v220; - v222 = i32_div_u(v216, v221)?; - v223 = 2i32; - v224 = v222.wrapping_shl((v223 & 31) as u32); - v225 = v224; - v226 = 0i32; - v227 = v226; - v228 = v131; - v229 = v132; - v230 = v133; - v231 = v208; - v232 = v135; - v233 = v215; - v234 = v227; - v235 = v206; - v236 = v139; - v237 = v212; - v238 = v225; - v239 = v142; - v240 = v143; - v241 = v144; - v242 = v145; - v243 = v146; - v244 = v147; - v245 = v148; - v246 = v149; - __current_block = Block::B25; - continue; - } - Block::B25 => { - v247 = v234; - v248 = 4095i32; - v249 = if (v247 as u32) > (v248 as u32) { 1 } else { 0 }; - if v249 != 0 { - __current_block = Block::B40; - } else { - __current_block = Block::B26; - } - continue; - } - Block::B26 => { - v250 = 0i32; - v251 = v250; - v252 = 0i32; - v253 = v252; - v254 = v251; - v255 = v253; - v258 = v232; - v262 = v236; - v265 = v239; - v266 = v240; - v267 = v241; - v268 = v242; - v269 = v243; - v270 = v244; - v271 = v245; - v272 = v246; - __current_block = Block::B27; - continue; - } - Block::B27 => { - v256 = v230; - v257 = v231; - v259 = v233; - v260 = v234; - v261 = v235; - v263 = v237; - v264 = v238; - v273 = v255; - v274 = v237; - v275 = if v273 == v274 { 1i32 } else { 0i32 }; - if v275 != 0 { - __current_block = Block::B28; - } else { - __current_block = Block::B30; - } - continue; - } - Block::B28 => { - v276 = v259; - v277 = v261; - v278 = v276.wrapping_add(v277); - v279 = v278; - v280 = v257; - v281 = v261; - v282 = v280.wrapping_add(v281); - v283 = v282; - v284 = v256; - v285 = v260; - v286 = v284.wrapping_add(v285); - v287 = v286; - v228 = v254; - v229 = v255; - v230 = v256; - v231 = v283; - v232 = v258; - v233 = v279; - v234 = v287; - v235 = v261; - v236 = v262; - v237 = v263; - v238 = v264; - v239 = v265; - v240 = v266; - v241 = v267; - v242 = v268; - v243 = v269; - v244 = v270; - v245 = v271; - v246 = v272; - __current_block = Block::B25; - continue; - } - Block::B29 => { - return Err(WasmTrap::Unreachable); - } - Block::B30 => { - v288 = v255; - v289 = v257; - v290 = v288.wrapping_add(v289); - v291 = v290; - v292 = 4i32; - v293 = v290.wrapping_add(v292); - v294 = v293; - v295 = v293; - v296 = memory.load_f32(v295 as usize)?; - v297 = v296; - v298 = v254; - v299 = 41984i32; - v300 = v298.wrapping_add(v299); - v301 = memory.load_f32(v300 as usize)?; - v302 = v301; - v303 = v255; - v304 = v259; - v305 = v303.wrapping_add(v304); - v306 = v305; - v307 = 4i32; - v308 = v305.wrapping_add(v307); - v309 = v308; - v310 = memory.load_f32(v308 as usize)?; - v311 = v310; - v312 = v301 * v310; - v313 = v254; - v314 = 50176i32; - v315 = v313.wrapping_add(v314); - v316 = memory.load_f32(v315 as usize)?; - v317 = v316; - v318 = v305; - v319 = memory.load_f32(v318 as usize)?; - v320 = v319; - v321 = v316 * v319; - v322 = v312 + v321; - v323 = v322; - v324 = v296 + v322; - memory.store_f32(v293 as usize, v324)?; - v325 = v290; - v326 = v290; - v327 = memory.load_f32(v326 as usize)?; - v328 = v327; - v329 = v301; - v330 = v319; - v331 = v329 * v330; - v332 = v310; - v333 = v316; - v334 = v332 * v333; - v335 = v331 - v334; - v336 = v335; - v337 = v327 + v335; - memory.store_f32(v325 as usize, v337)?; - v338 = v305; - v339 = v327; - v340 = v335; - v341 = v339 - v340; - memory.store_f32(v338 as usize, v341)?; - v342 = v308; - v343 = v296; - v344 = v322; - v345 = v343 - v344; - memory.store_f32(v342 as usize, v345)?; - v346 = v254; - v347 = v264; - v348 = v346.wrapping_add(v347); - v349 = v348; - v350 = v255; - v351 = 8i32; - v352 = v350.wrapping_add(v351); - v353 = v352; - v254 = v349; - v255 = v353; - v258 = v306; - v262 = v291; - v265 = v309; - v266 = v297; - v267 = v336; - v268 = v311; - v269 = v317; - v270 = v320; - v271 = v323; - v272 = v328; - __current_block = Block::B27; - continue; - } - Block::B31 => { - return Err(WasmTrap::Unreachable); - } - Block::B32 => { - return Err(WasmTrap::Unreachable); - } - Block::B33 => { - return Err(WasmTrap::Unreachable); - } - Block::B34 => { - return Err(WasmTrap::Unreachable); - } - Block::B35 => { - return Err(WasmTrap::Unreachable); - } - Block::B36 => { - return Err(WasmTrap::Unreachable); - } - Block::B37 => { - return Err(WasmTrap::Unreachable); - } - Block::B38 => { - return Ok(()); - } - Block::B39 => { - return Err(WasmTrap::Unreachable); - } - Block::B40 => { - v354 = v230; - v355 = 1i32; - v356 = v354.wrapping_shl((v355 & 31) as u32); - v357 = v356; - v131 = v228; - v132 = v229; - v133 = v357; - v134 = v231; - v135 = v232; - v136 = v233; - v137 = v234; - v138 = v235; - v139 = v236; - v140 = v237; - v141 = v238; - v142 = v239; - v143 = v240; - v144 = v241; - v145 = v242; - v146 = v243; - v147 = v244; - v148 = v245; - v149 = v246; - __current_block = Block::B16; - continue; - } - Block::B41 => { - return Err(WasmTrap::Unreachable); - } - Block::B42 => { - return Err(WasmTrap::Unreachable); - } - Block::B43 => { - return Ok(()); - } - } - } -} - -#[allow(unused_mut, unused_variables, unused_assignments, clippy::only_used_in_recursion, clippy::needless_return, clippy::manual_range_contains, clippy::never_loop)] -fn func_4(env: &mut Env<'_, H>, memory: &mut IsolatedMemory) -> WasmResult { - let mut v1: i32 = 0i32; - #[derive(Clone, Copy)] - #[allow(dead_code)] - enum Block { B0 } - let mut __current_block = Block::B0; - loop { - match __current_block { - Block::B0 => { - v1 = 33792i32; - return Ok(v1); - } - } - } -} - -impl WasmModule { - pub fn __wasm_call_ctors(&mut self) -> WasmResult<()> { - let mut __host = herkos_runtime::NoHost; - let mut env = Env { host: &mut __host, globals: &mut self.0.globals }; - func_0(&mut env, &mut self.0.memory) - } - pub fn fft_init(&mut self, v0: i32) -> WasmResult<()> { - let mut __host = herkos_runtime::NoHost; - let mut env = Env { host: &mut __host, globals: &mut self.0.globals }; - func_1(v0, &mut env, &mut self.0.memory) - } - pub fn fft_get_input_ptr(&mut self) -> WasmResult { - let mut __host = herkos_runtime::NoHost; - let mut env = Env { host: &mut __host, globals: &mut self.0.globals }; - func_2(&mut env, &mut self.0.memory) - } - pub fn fft_compute(&mut self, v0: i32) -> WasmResult<()> { - let mut __host = herkos_runtime::NoHost; - let mut env = Env { host: &mut __host, globals: &mut self.0.globals }; - func_3(v0, &mut env, &mut self.0.memory) - } - pub fn fft_get_output_ptr(&mut self) -> WasmResult { - let mut __host = herkos_runtime::NoHost; - let mut env = Env { host: &mut __host, globals: &mut self.0.globals }; - func_4(&mut env, &mut self.0.memory) - } -} - From 0ae719ecf49540a985dc1e2d08c968d958b819dc Mon Sep 17 00:00:00 2001 From: Arnaud Riess Date: Mon, 16 Mar 2026 19:05:28 +0000 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20add=20FFT=20example=20to=20CI=20wor?= =?UTF-8?q?kflow=20for=20C=20=E2=86=92=20Wasm=20=E2=86=92=20Rust?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6734da8..d302a3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,5 +49,8 @@ jobs: - name: Example (C → Wasm → Rust) run: ./examples/c-to-wasm-to-rust/run.sh + - name: Example FFT (C → Wasm → Rust) + run: ./examples/c-fft/run.sh + - name: Example (Inter-Module Lending) run: ./examples/inter-module-lending/run.sh From c148f23ecbf4b07264fee304603627f5c2e5b562 Mon Sep 17 00:00:00 2001 From: Arnaud Riess Date: Mon, 16 Mar 2026 19:10:09 +0000 Subject: [PATCH 6/6] chore: update package versions to 0.2.0 across all crates --- Cargo.lock | 8 ++++---- crates/herkos-core/Cargo.toml | 2 +- crates/herkos-runtime/Cargo.toml | 2 +- crates/herkos-tests/Cargo.toml | 4 ++-- crates/herkos/Cargo.toml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac1ca02..e540cae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -354,7 +354,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "herkos" -version = "0.1.1" +version = "0.2.0" dependencies = [ "anyhow", "clap", @@ -364,7 +364,7 @@ dependencies = [ [[package]] name = "herkos-core" -version = "0.1.1" +version = "0.2.0" dependencies = [ "anyhow", "heck", @@ -374,14 +374,14 @@ dependencies = [ [[package]] name = "herkos-runtime" -version = "0.1.1" +version = "0.2.0" dependencies = [ "kani-verifier", ] [[package]] name = "herkos-tests" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "criterion", diff --git a/crates/herkos-core/Cargo.toml b/crates/herkos-core/Cargo.toml index 2a71d3e..f191b1c 100644 --- a/crates/herkos-core/Cargo.toml +++ b/crates/herkos-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "herkos-core" -version = "0.1.1" +version = "0.2.0" edition = "2021" description = "Compile-Time Memory Isolation via WebAssembly and Rust Transpilation — core library" license = "Apache-2.0" diff --git a/crates/herkos-runtime/Cargo.toml b/crates/herkos-runtime/Cargo.toml index 67a2912..3a04460 100644 --- a/crates/herkos-runtime/Cargo.toml +++ b/crates/herkos-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "herkos-runtime" -version = "0.1.1" +version = "0.2.0" edition = "2021" description = "Runtime library for herkos transpiled output — IsolatedMemory, WasmTrap, capability traits" license = "Apache-2.0" diff --git a/crates/herkos-tests/Cargo.toml b/crates/herkos-tests/Cargo.toml index 6728e31..c4caa5b 100644 --- a/crates/herkos-tests/Cargo.toml +++ b/crates/herkos-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "herkos-tests" -version = "0.1.0" +version = "0.2.0" edition = "2021" publish = false description = "End-to-end compilation tests for herkos transpiler" @@ -13,7 +13,7 @@ herkos-runtime = { path = "../herkos-runtime" } [build-dependencies] anyhow = { workspace = true } wat = { workspace = true } -herkos-core = { path = "../herkos-core" } +herkos-core = { version = "0.2.0", path = "../herkos-core" } [dev-dependencies] criterion = "0.8.2" diff --git a/crates/herkos/Cargo.toml b/crates/herkos/Cargo.toml index 6af2e93..54b2ed5 100644 --- a/crates/herkos/Cargo.toml +++ b/crates/herkos/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "herkos" -version = "0.1.1" +version = "0.2.0" edition = "2021" description = "Compile-Time Memory Isolation via WebAssembly and Rust Transpilation" license = "Apache-2.0" @@ -12,7 +12,7 @@ categories = ["wasm", "development-tools::build-utils"] readme = "../../README.md" [dependencies] -herkos-core = { path = "../herkos-core" } +herkos-core = { version = "0.2.0", path = "../herkos-core" } anyhow = { workspace = true } clap = { workspace = true }