diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index baebb61..ae3e9ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,9 @@ jobs: uses: actions/checkout@v4 - name: Install build tools - run: sudo apt-get update && sudo apt-get install -y build-essential + # wabt provides wasm-validate, which we use to confirm every + # example we claim works compiles to a spec-conformant module. + run: sudo apt-get update && sudo apt-get install -y build-essential wabt - name: Build run: make -C compiler @@ -37,6 +39,35 @@ jobs: - name: Bench run: make -C compiler bench + - name: Validate emitted wasm against examples + # Compile every example marked "yes" in examples/README.md and + # confirm wasm-validate accepts the output. Catches codegen bugs + # that pass our byte-level unit tests but produce structurally + # invalid modules (e.g. wrong locals declaration, missing END). + run: | + set -euxo pipefail + mkdir -p /tmp/wasm-out + for src in examples/counter-mvp.cv; do + out=/tmp/wasm-out/$(basename "$src" .cv).wasm + ./compiler/build/cleavec --emit-wasm "$src" -o "$out" + wasm-validate "$out" + done + + - name: Determinism check (compile twice, diff) + # Same source through the full pipeline must produce + # byte-identical wasm across two runs. Guards against + # nondeterminism leaking in from HashMap iteration order, + # pointer addresses, build timestamps, etc. + run: | + set -euxo pipefail + ./compiler/build/cleavec --emit-wasm examples/counter-mvp.cv -o /tmp/det-a.wasm + ./compiler/build/cleavec --emit-wasm examples/counter-mvp.cv -o /tmp/det-b.wasm + if ! cmp -s /tmp/det-a.wasm /tmp/det-b.wasm; then + echo "codegen is not deterministic: two compilations differ" >&2 + cmp /tmp/det-a.wasm /tmp/det-b.wasm || true + exit 1 + fi + runtime: name: Runtime · Build · Test runs-on: ubuntu-latest @@ -55,7 +86,13 @@ jobs: run: make -C compiler - name: Install Rust toolchain + # `clippy` component is added so the lint step below has access + # to it. `dtolnay/rust-toolchain@stable` follows whatever the + # latest stable rustc is; MSRV is pinned in runtime/Cargo.toml + # (rust-version = "1.95") so a downgrade attempt fails fast. uses: dtolnay/rust-toolchain@stable + with: + components: clippy - name: Cache cargo registry + build uses: actions/cache@v4 @@ -68,6 +105,12 @@ jobs: restore-keys: | ${{ runner.os }}-cargo- + - name: Lint runtime (clippy, deny warnings) + # Run lint before build to fail fast on style / correctness + # issues before paying the full Wasmtime + Cranelift compile. + working-directory: runtime + run: cargo clippy --all-targets --release -- -D warnings + - name: Build runtime working-directory: runtime run: cargo build --release diff --git a/CHANGELOG.md b/CHANGELOG.md index d8a5f65..fd25ed6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ Prose references a version as `v0.X.Y`; headings stay bare `[0.X.Y]`. ### Added +- CI gains four lower-cost gates: + - **`wasm-validate` on emitted modules.** The compiler job installs wabt, compiles every example marked "yes" in `examples/README.md`, and runs `wasm-validate` on the output. Catches structurally invalid modules that pass our byte-level codegen unit tests. + - **Determinism check.** The compiler job compiles `examples/counter-mvp.cv` twice and `cmp -s` the outputs; non-zero exit on any difference. Guards against nondeterminism from HashMap iteration order, pointer addresses, build timestamps. + - **`cargo clippy --all-targets --release -- -D warnings`** as the first runtime step. Runs before the full Wasmtime + Cranelift compile so style and correctness lints fail fast. + - **MSRV pin** in `runtime/Cargo.toml`: `rust-version = "1.95"`. Locks the floor after the v0.3 bump (revm 40 + ruint 1.18 need rustc >= 1.91). +- One existing clippy lint fixed inline (`runtime/src/main.rs::hex_decode` now uses `s.len().is_multiple_of(2)` instead of `s.len() % 2 != 0`). - Codegen for `let` bindings (`compiler/src/codegen.c`). The v0.3 codegen rejected `let` with a diagnostic; this lifts the restriction. The fn-body emitter pre-scans the top-level block to count let statements, allocates one WASM local per binding at index `n_params + let_index`, emits a single locals declaration group of N i64s, and lowers each `let name = expr` to `; local.set `. Reads of let-bound names go through the existing `local.get` path; assignments to let names route to `local.set`. `find_local` now scans most-recent-first so shadowing matches user expectation. Closes #44. - 7 codegen tests (`codegen_test.c`): locals declaration byte sequence, `local.set 0` / `local.get 0` opcodes, post-param local index, three-let single-group declaration, let referencing earlier let, reassignment lowering, regression guard that fn with no lets still emits a zero local-groups byte. - `codegen_let_heavy` bench case: 8 let bindings + arithmetic chain. diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index cf9861b..e6a48ae 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -2,6 +2,11 @@ name = "cleave-runtime" version = "0.3.0" edition = "2021" +# MSRV. We bumped from 1.87 -> 1.95 mid-v0.3 because revm 40 + ruint 1.18 +# require rustc >= 1.91. Lock the floor here so CI fails fast if anyone +# tries to downgrade. Older stable releases will not build the runtime +# crate; that's a deliberate consequence of pinning to the modern REVM. +rust-version = "1.95" license = "Apache-2.0" description = "Wasmtime-backed execution engine for Cleave-compiled WASM modules." repository = "https://github.com/cleave-lang/cleave" diff --git a/runtime/src/main.rs b/runtime/src/main.rs index 4c4fc87..fba34eb 100644 --- a/runtime/src/main.rs +++ b/runtime/src/main.rs @@ -151,7 +151,7 @@ fn load_bytecode(spec: &str) -> Result> { } fn hex_decode(s: &str) -> Result> { - if s.len() % 2 != 0 { + if !s.len().is_multiple_of(2) { return Err(anyhow!("hex string has odd length")); } let mut out = Vec::with_capacity(s.len() / 2);