spacewasm 0.7.1

A no_std WebAssembly 1.0 decoder, validator, and interpreter for on-board spacecraft use
Documentation
name: CI

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

permissions:
  contents: read

env:
  RUST_BACKTRACE: 1
  CARGO_TERM_COLOR: always

jobs:
  test:
    name: Test (${{ matrix.os }}, ${{ matrix.profile }})
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        include:
          # macOS ARM64
          - os: macos-arm64
            runner: macos-14
            profile: debug
          - os: macos-arm64
            runner: macos-14
            profile: release

          # Linux x86_64
          - os: linux-x86
            runner: ubuntu-latest
            profile: debug
          - os: linux-x86
            runner: ubuntu-latest
            profile: release

          # Linux ARM64
          - os: linux-arm64
            runner: ubuntu-24.04-arm
            profile: debug
          - os: linux-arm64
            runner: ubuntu-24.04-arm
            profile: release

    steps:
      - name: Checkout code
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
        with:
          components: rustfmt, clippy

      - name: Install WABT (WebAssembly Binary Toolkit)
        uses: ./.github/actions/install-wabt

      - name: Cache Rust dependencies
        # Pinned commit resolved from the annotated Swatinem/rust-cache@v2 tag.
        uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
        with:
          key: ${{ matrix.profile }}
          save-if: ${{ github.ref == 'refs/heads/main' }}
          cache-bin: false

      - name: Run tests (debug)
        if: matrix.profile == 'debug'
        run: cargo test --workspace --verbose

      - name: Run tests (release)
        if: matrix.profile == 'release'
        run: cargo test --workspace --release --verbose

      - name: Run tests with strict-assertions (debug)
        if: matrix.profile == 'debug'
        run: cargo test --workspace --features strict-assertions --verbose

      - name: Run tests with strict-assertions (release)
        if: matrix.profile == 'release'
        run: cargo test --workspace --release --features strict-assertions --verbose

  test-32bit:
    name: Test (i686-unknown-linux-gnu, ${{ matrix.profile }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        profile:
          - debug
          - release

    steps:
      - name: Checkout code
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

      - name: Install 32-bit system libraries
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-multilib

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
        with:
          targets: i686-unknown-linux-gnu

      - name: Install WABT (WebAssembly Binary Toolkit)
        uses: ./.github/actions/install-wabt

      # Only the interpreter library is deployed to the 32-bit target, so the
      # host-only workspace tooling is not built here. Running the suite on a
      # 32-bit `usize` guards pointer-width regressions (e.g. memory
      # effective-address overflow) that the 64-bit test matrix cannot observe.
      - name: Run tests (debug)
        if: matrix.profile == 'debug'
        run: cargo test -p spacewasm --target i686-unknown-linux-gnu --verbose

      - name: Run tests (release)
        if: matrix.profile == 'release'
        run: cargo test -p spacewasm --release --target i686-unknown-linux-gnu --verbose

  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

      - name: Install Rust toolchain
        # Pinned to the commit the `stable` branch resolved to (its action.yml
        # defaults `toolchain: stable`); immutable ref for supply-chain safety.
        uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
        with:
          components: rustfmt, clippy

      - name: Check formatting
        run: cargo fmt --all -- --check

      - name: Run clippy
        run: cargo clippy --workspace --all-targets --all-features -- -D warnings

      - name: Check C header codegen is up to date
        run: |
          # Regenerate include/spacewasm.h from the Rust source. The build
          # script writes the header in place when the `codegen` feature is on.
          cargo build -p spacewasm_c_api --features codegen
          # Fail if the regenerated header differs from what is tracked in git.
          if ! git diff --exit-code -- crates/spacewasm_c_api/include/spacewasm.h; then
            echo "::error::spacewasm.h is out of date. Run 'cargo build -p spacewasm_c_api --features codegen' and commit the result."
            exit 1
          fi

  build:
    name: Build (${{ matrix.os }})
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: macos-arm64
            runner: macos-14
          - os: linux-x86
            runner: ubuntu-latest
          - os: linux-arm64
            runner: ubuntu-24.04-arm

    steps:
      - name: Checkout code
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c

      - name: Build all targets
        run: cargo build --workspace --all-targets --verbose

      - name: Build release
        run: cargo build --workspace --release --verbose

  benchmark:
    name: Benchmark
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          fetch-depth: 0

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c

      - name: Install WABT (WebAssembly Binary Toolkit)
        uses: ./.github/actions/install-wabt

      - name: Cache Rust dependencies
        # Pinned commit resolved from the annotated Swatinem/rust-cache@v2 tag.
        uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
          cache-bin: false

      - name: Run CoreMark benchmark
        id: benchmark
        run: |
          # Build and run the benchmark from workspace root, capturing output
          output=$(cargo bench -p spacewasm_std --bench coremark --no-fail-fast 2>&1)
          echo "$output"

          # Extract the CoreMark score from the output
          score=$(echo "$output" | grep -m1 "CoreMark Score:" | awk '{print $3}')
          if [ -z "$score" ]; then
            echo "Failed to extract CoreMark score"
            exit 1
          fi

          echo "score=$score" >> $GITHUB_OUTPUT
          echo "Benchmark score: $score"

      - name: Download baseline benchmark
        id: baseline
        continue-on-error: true
        run: |
          # Try to get baseline from main branch
          if [ "${{ github.event_name }}" == "pull_request" ]; then
            git fetch origin main:main
            git checkout main
            baseline_output=$(cargo bench -p spacewasm_std --bench coremark --no-fail-fast 2>&1) || true
            baseline_score=$(echo "$baseline_output" | grep -m1 "CoreMark Score:" | awk '{print $3}')
            git checkout -

            if [ -n "$baseline_score" ]; then
              echo "baseline=$baseline_score" >> $GITHUB_OUTPUT
              echo "Baseline score: $baseline_score"
            else
              echo "baseline=" >> $GITHUB_OUTPUT
            fi
          else
            echo "baseline=" >> $GITHUB_OUTPUT
          fi

      - name: Save benchmark results
        if: github.event_name == 'pull_request'
        run: |
          cat > benchmark-results.json <<EOF
          {
            "prNumber": "${{ github.event.pull_request.number }}",
            "currentScore": "${{ steps.benchmark.outputs.score }}",
            "baselineScore": "${{ steps.baseline.outputs.baseline }}"
          }
          EOF

      - name: Upload benchmark results
        if: github.event_name == 'pull_request'
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
        with:
          name: benchmark-results
          path: benchmark-results.json

  model-checking:
    name: Formal Verification
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
    steps:
      - name: Checkout code
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          fetch-depth: 0

      - name: Run Kani on repo
        uses: model-checking/kani-github-action@f838096619a707b0f6b2118cf435eaccfa33e51f # v1.1
        with:
          args: "-j --output-format=terse"

  miri:
    name: Miri
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          fetch-depth: 0

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
        with:
          toolchain: nightly-2026-08-30
          components: miri

      - name: Run Miri
        env:
          MIRIFLAGS: -Zmiri-disable-isolation
        run: cargo miri test --lib --no-fail-fast --verbose --features miri-soft-floats

  coverage:
    name: Code Coverage
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          fetch-depth: 0

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
        with:
          components: llvm-tools-preview

      - name: Install WABT (WebAssembly Binary Toolkit)
        uses: ./.github/actions/install-wabt

      - name: Install cargo-llvm-cov
        uses: taiki-e/install-action@37f7c5781271959fb65b6b35224e28652ff2b63d # v2.87.0
        with:
          tool: cargo-llvm-cov

      - name: Cache Rust dependencies
        uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
          cache-bin: false

      - name: Generate coverage
        run: |
          # Cover the main library and the C-API layer. spacewasm_c_api's Rust
          # `#[cfg(test)]` suite drives the `spacewasm_*` entry points directly,
          # so its coverage is attributed like any other crate.
          cargo llvm-cov \
            --package spacewasm \
            --package spacewasm_c_api \
            --lcov --output-path lcov.info
          cargo llvm-cov \
            --package spacewasm \
            --package spacewasm_c_api \
            --json --output-path coverage.json

      - name: Extract coverage percentage
        id: coverage
        run: |
          # Extract coverage percentage from JSON output
          coverage=$(jq -r '.data[0].totals.lines.percent' coverage.json)
          echo "percentage=$coverage" >> $GITHUB_OUTPUT
          echo "Current coverage: $coverage%"

      - name: Download baseline coverage
        id: baseline-coverage
        continue-on-error: true
        run: |
          # Try to get baseline from main branch
          if [ "${{ github.event_name }}" == "pull_request" ]; then
            git fetch origin main:main
            git checkout main
            cargo llvm-cov \
              --package spacewasm \
              --package spacewasm_c_api \
              --json --output-path baseline-coverage.json 2>&1 || true
            git checkout -

            if [ -f baseline-coverage.json ]; then
              baseline=$(jq -r '.data[0].totals.lines.percent' baseline-coverage.json)
              echo "baseline=$baseline" >> $GITHUB_OUTPUT
              echo "Baseline coverage: $baseline%"
            else
              echo "baseline=" >> $GITHUB_OUTPUT
            fi
          else
            echo "baseline=" >> $GITHUB_OUTPUT
          fi

      - name: Save coverage results
        if: github.event_name == 'pull_request'
        run: |
          cat > coverage-results.json <<EOF
          {
            "prNumber": "${{ github.event.pull_request.number }}",
            "currentCoverage": "${{ steps.coverage.outputs.percentage }}",
            "baselineCoverage": "${{ steps.baseline-coverage.outputs.baseline }}"
          }
          EOF

      - name: Upload coverage results
        if: github.event_name == 'pull_request'
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
        with:
          name: coverage-results
          path: coverage-results.json

      - name: Upload coverage to Codecov
        if: github.event_name == 'push' && github.ref == 'refs/heads/main'
        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7
        with:
          files: lcov.info
          token: ${{ secrets.CODECOV_TOKEN }}
          fail_ci_if_error: true
          verbose: true