Skip to content
Back to skills

Wasm Rust Compiler

ASecurity

Surveys JS and Python sources for CPU-bound hot loops, then provides a complete wasm-bindgen Rust implementation, Cargo manifest, wasm-pack build scripts, and browser loader glue for porting the bottlenecks to WebAssembly.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 27, 2026
developmentpythonrustgoshellbashdebuggingperformance

Works with

  • cli

Security analysis

A100/100

Scanned September 27, 2026

npx -y skills add RMAzrim/opencode-artes --skill wasm-rust-compiler --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Wasm Rust Compiler?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Wasm Rust Compiler
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/rmazrim-wasm-rust-compiler/badge)](https://www.skillsdirectory.com/skills/rmazrim-wasm-rust-compiler)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: WebAssembly Rust Compiler
description: Surveys JS and Python sources for CPU-bound hot loops, then provides a complete wasm-bindgen Rust implementation, Cargo manifest, wasm-pack build scripts, and browser loader glue for porting the bottlenecks to WebAssembly.
metadata:
  source: skills/wasm-rust-compiler/wasm-rust-compiler.md
---

# WebAssembly Rust Compiler

## 1. System Architecture & Prerequisites

The skill identifies CPU-heavy bottleneck functions in JS/Python code, ports
them to memory-safe Rust compiled to WebAssembly with `wasm-bindgen`, and
wires the module into the web app with generated loader glue.

Toolchain prerequisites:

- **Rust toolchain** (rustup) with the `wasm32-unknown-unknown` target:
  `rustup target add wasm32-unknown-unknown`
- **wasm-pack** CLI (`cargo install wasm-pack`) — builds/optimizes the crate
  and emits the JS bindings + `.wasm` binary.
- **wasm-bindgen** crate at `0.2` in `Cargo.toml`, kept aligned with the
  `wasm-bindgen` runtime the browser loads.
- **Python 3.9+** (stdlib only) for `bottleneck_survey.py` (the candidate
  scanner).
- A static web host able to serve `.wasm` with `Content-Type: application/wasm`.

Artifacts produced by this skill:

| Path | Role |
| ---- | ---- |
| `src/lib.rs`      | Memory-safe WASM exports (`fib`, `sum_primes`) |
| `Cargo.toml`      | Crate manifest (`cdylib`, `wasm-bindgen = "0.2"`) |
| `loader.js`       | Browser glue: init, error wrapping, async `load()` |
| `build.sh`        | POSIX build + copy `pkg/` to web root |
| `build.ps1`       | Windows build + copy `pkg/` to web root |
| `bottleneck_survey.py` | Static hot-loop scanner -> JSON candidate report |

## 2. Input/Output Data Contracts

### Input: source tree for the survey

`bottleneck_survey.py --src <root> [--globs 'src/**/*.py'] [--min-score 6] [--out wasm_candidates.json]`

Scans `.py`/`.js` (or glob-filtered) files. Each candidate function gets a
static score:

```
score = loops * 2 + max_nesting * 3 + min(numeric_ops, 50)
```

Verdict thresholds: `score >= 12` -> `PRIME CANDIDATE`, `>= 6` -> `candidate`,
otherwise `watch`.

### Output: `wasm_candidates.json` (JSON Schema)

```json
{
  "type": "object",
  "required": ["generator", "min_score", "candidates"],
  "properties": {
    "generator": { "const": "bottleneck_survey.py" },
    "min_score": { "type": "integer" },
    "candidates": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["file", "function", "line", "loops", "max_nesting", "numeric_ops", "score", "verdict"],
        "properties": {
          "file": { "type": "string" },
          "function": { "type": "string" },
          "line": { "type": "integer" },
          "loops": { "type": "integer" },
          "max_nesting": { "type": "integer" },
          "numeric_ops": { "type": "integer" },
          "score": { "type": "integer" },
          "verdict": { "enum": ["PRIME CANDIDATE", "candidate", "watch"] }
        }
      }
    }
  }
}
```

### Output: built WASM package

`pkg/` (generated by `wasm-pack build --target web`) containing
`bottleneck_wasm.js`, `bottleneck_wasm_bg.wasm`,
`bottleneck_wasm_bg.wasm.d.ts`, `package.json` — copied to the web root
(`web/pkg/` by default) and loaded via `loader.js`.

## 3. Production Reference Implementation

### 3.1 `Cargo.toml`

```toml
[package]
name = "bottleneck_wasm"
version = "0.1.0"
edition = "2021"
authors = ["opencode-core"]
description = "CPU-heavy numeric kernels compiled to WebAssembly."

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
# Keep the wasm-bindgen crate version fully aligned with the runtime the
# browser loads. "0.2" resolves to the latest 0.2.x; pin if you need
# reproducibility (e.g. = "0.2.100").
wasm-bindgen = "0.2"

[profile.release]
# Size-optimized, inlined, single codegen unit: smaller + faster wasm.
opt-level = "s"
lto = true
codegen-units = 1
strip = "debuginfo"
```

### 3.2 `src/lib.rs`

```rust
//! CPU-heavy numeric kernels reimplemented in memory-safe Rust for Wasm.
//!
//! Exports mirror the bottleneck functions identified by
//! `bottleneck_survey.py`. No `unsafe` anywhere: plain `u32`/`u64` math and
//! `Vec<bool>` sieves cross the ABI as scalars, so no raw pointers exist.

use wasm_bindgen::prelude::*;

/// Iterative O(n) Fibonacci — replaces a naive recursive JS implementation
/// that would stack-overflow near n = 10_000.
#[wasm_bindgen]
pub fn fib(n: u32) -> u64 {
    if n == 0 {
        return 0;
    }
    if n == 1 {
        return 1;
    }
    let mut a: u64 = 0;
    let mut b: u64 = 1;
    for _ in 2..=n {
        let next = a.wrapping_add(b);
        a = b;
        b = next;
    }
    b
}

/// Sum of all primes <= `limit` via a boolean sieve. O(n log log n) time,
/// O(n) memory. Verified against Python's naive trial division for n <= 10^6.
#[wasm_bindgen]
pub fn sum_primes(limit: u64) -> u64 {
    if limit < 2 {
        return 0;
    }
    let n = limit as usize;
    let mut is_prime = vec![true; n + 1];
    is_prime[0] = false;
    is_prime[1] = false;

    let mut p: usize = 2;
    while p * p <= n {
        if is_prime[p] {
            let mut multiple = p * p;
            while multiple <= n {
                is_prime[multiple] = false;
                multiple += p;
            }
        }
        p += 1;
    }

    is_prime
        .iter()
        .enumerate()
        .skip(2)
        .filter(|(_, &prime)| prime)
        .fold(0u64, |acc, (value, _)| acc.wrapping_add(value as u64))
}

/// Convenience entry point for benchmarks: returns both results together.
#[wasm_bindgen]
pub fn benchmarks(n: u32, limit: u64) -> u64 {
    fib(n).wrapping_add(sum_primes(limit))
}
```

### 3.3 JS glue — `loader.js`

```js
// loader.js — browser glue for the built Wasm package.
// Expects pkg/ to be copied next to this file (see build.sh / build.ps1).
import init, { fib, sum_primes } from "./pkg/bottleneck_wasm.js";

let readyPromise = null;

/**
 * Async bootstrap of the Wasm module. Singleton: `init()` runs once,
 * subsequent calls resolve the same exported functions object.
 * Rejections are wrapped with a descriptive prefix for easier debugging.
 */
export async function load() {
  if (!readyPromise) {
    readyPromise = init()
      .then(() => ({ fib, sum_primes }))
      .catch((err) => {
        readyPromise = null; // allow a retry after a transient failure
        const detail = err && err.message ? err.message : String(err);
        throw new Error(`[wasm] bootstrap failed: ${detail}`);
      });
  }
  return readyPromise;
}

/**
 * Demo: run both kernels and report wall-clock time. Callers wire this up
 * to a <button onclick="runDemo()"> to A/B against the JS originals.
 */
export async function demo(n = 40, limit = 1_000_000) {
  const wasm = await load();
  const started = performance.now();
  const out = {
    fib_n: n,
    fib: wasm.fib(n),
    sum_primes_limit: limit,
    sum: wasm.sum_primes(limit),
  };
  out.elapsedMs = Math.round((performance.now() - started) * 100) / 100;
  return out;
}

// Re-export for callers that skip `load()` and import the functions directly.
export { fib, sum_primes };
```

### 3.4 Build scripts

`build.sh`:

```bash
#!/usr/bin/env bash
# Build the Wasm crate and stage it into the web root.
set -euo pipefail

TARGET="${TARGET:-web}"
PROFILE="${PROFILE:-release}"
OUT_DIR="${OUT_DIR:-web/pkg}"

if ! command -v wasm-pack >/dev/null 2>&1; then
  echo "error: wasm-pack not found — install with: cargo install wasm-pack" >&2
  exit 1
fi

echo "==> wasm-pack build --target $TARGET --$PROFILE"
wasm-pack build --target "$TARGET" --"$PROFILE"

mkdir -p "$OUT_DIR"
cp -r pkg/* "$OUT_DIR"/
echo "==> WASM package staged in $OUT_DIR"
```

`build.ps1`:

```powershell
# Build the Wasm crate and stage it into the web root.
param(
    [string]$Target = "web",
    [string]$Profile = "release",
    [string]$OutDir = "web/pkg"
)
$ErrorActionPreference = "Stop"

if (-not (Get-Command wasm-pack -ErrorAction SilentlyContinue)) {
    throw "wasm-pack not found — install with: cargo install wasm-pack"
}

& wasm-pack build --target $Target --$Profile
if (-not $?) { throw "wasm-pack build failed" }

New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
Copy-Item -Path "pkg\*" -Destination $OutDir -Recurse -Force
Write-Host "WASM package staged in $OutDir"
```

### 3.5 `bottleneck_survey.py`

```python
#!/usr/bin/env python3
"""Locate CPU-bound hot loops that are the best WASM port candidates.

Static heuristic over .py/.js sources. Functions with deeply nested loops
and dense numeric operands score higher; the report JSON is the input for
the porting step of this skill.
"""

from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path
from typing import List, Optional

FN_HEADER_RE = re.compile(
    r"^\s*(?:async\s+function|function|def)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*"
)
LOOP_RE = re.compile(r"^\s*(?:for|while)\s+\S+")
NUMERIC_RE = re.compile(r"[+\-*%]|//|\*\*")
IGNORED_PREFIXES = ("#", "//", "/*", "*", "--", '"""')


class FunctionScan:
    __slots__ = ("file", "name", "line", "func_indent", "loops",
                 "max_nesting", "numeric_ops")

    def __init__(self, file: Path, name: str, line: int, func_indent: int):
        self.file = file
        self.name = name
        self.line = line
        self.func_indent = func_indent
        self.loops = 0
        self.max_nesting = 0
        self.numeric_ops = 0

    @property
    def score(self) -> int:
        return self.loops * 2 + self.max_nesting * 3 + min(self.numeric_ops, 50)

    def verdict(self) -> str:
        if self.score >= 12:
            return "PRIME CANDIDATE"
        if self.score >= 6:
            return "candidate"
        return "watch"

    def to_report(self, root: Path) -> dict:
        rel = str(self.file.relative_to(root)).replace("\\", "/")
        return {
            "file": rel,
            "function": self.name,
            "line": self.line,
            "loops": self.loops,
            "max_nesting": self.max_nesting,
            "numeric_ops": self.numeric_ops,
            "score": self.score,
            "verdict": self.verdict(),
        }


def collect_files(root: Path, globs: List[str]) -> List[Path]:
    if globs:
        found: List[Path] = []
        for pattern in globs:
            resolved = root / pattern
            if resolved.is_dir():
                continue
            for candidate in (root.rglob(pattern) if any(
                c in pattern for c in ("*", "?", "[")
            ) else [resolved]):
                if candidate.is_file():
                    found.append(candidate)
            if "*" not in pattern and "?" not in pattern and "[" not in pattern:
                continue
        if found:
            return sorted(set(found))
    return sorted(
        p for p in root.rglob("*")
        if p.is_file() and p.suffix in (".py", ".js")
    )


def scan_file(path: Path) -> List[FunctionScan]:
    scans: List[FunctionScan] = []
    current: Optional[FunctionScan] = None
    loop_stack: List[int] = []

    try:
        text = path.read_text(encoding="utf-8", errors="replace")
    except OSError as exc:
        print(f"[skip] {path}: {exc}", file=sys.stderr)
        return scans

    for line_no, raw in enumerate(text.splitlines(), start=1):
        stripped = raw.strip()
        if not stripped or stripped.startswith(IGNORED_PREFIXES):
            continue
        indent = len(raw) - len(raw.lstrip(" "))

        func_match = FN_HEADER_RE.match(raw)
        if func_match:
            current = FunctionScan(path, func_match.group(1), line_no, indent)
            scans.append(current)
            loop_stack = []
            continue

        while loop_stack and loop_stack[-1] >= indent:
            loop_stack.pop()

        if current is None or indent <= current.func_indent:
            continue

        if LOOP_RE.match(raw):
            current.loops += 1
            loop_stack.append(indent)
            if len(loop_stack) > current.max_nesting:
                current.max_nesting = len(loop_stack)

        if loop_stack:
            current.numeric_ops += len(NUMERIC_RE.findall(stripped))

    return scans


def build_report(root: Path, globs: List[str], min_score: int) -> List[dict]:
    report: List[dict] = []
    for path in collect_files(root, globs):
        for scan in scan_file(path):
            if scan.score >= min_score:
                report.append(scan.to_report(root))
    report.sort(key=lambda d: d["score"], reverse=True)
    return report


def main(argv: Optional[List[str]] = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--src", default=".", help="source tree root")
    parser.add_argument(
        "--globs", action="append", default=[],
        help="include pattern (repeatable), e.g. --globs 'src/**/*.py'",
    )
    parser.add_argument("--min-score", type=int, default=6,
                        help="only report functions scoring >= N (default 6)")
    parser.add_argument("--out", default="wasm_candidates.json",
                        help="JSON report path (default wasm_candidates.json)")
    args = parser.parse_args(argv)

    root = Path(args.src)
    if not root.is_dir():
        print(f"error: not a directory: {root}", file=sys.stderr)
        return 2

    candidates = build_report(root, args.globs, args.min_score)
    payload = {
        "generator": "bottleneck_survey.py",
        "min_score": args.min_score,
        "candidate_count": len(candidates),
        "candidates": candidates,
    }
    with open(args.out, "w", encoding="utf-8") as handle:
        json.dump(payload, handle, indent=2)
        handle.write("\n")

    print(f"{len(candidates)} candidate function(s) -> {args.out}")
    for entry in candidates[:25]:
        print(
            "  [{score:>3}] {file}:{line} {function} ({verdict})".format(**entry)
        )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
```

## 4. Execution Protocol & Step-by-Step Workflow

1. **Survey.** Run `python bottleneck_survey.py --src js/ --globs 'src/**/*.js'`
   (Python or JS roots; repeat the `--globs` flag to add trees). Open the JSON
   report and pick `PRIME CANDIDATE` functions — deep loops with heavy numeric
   operators are the reliable wins.
2. **Port the top functions.** Translate the selected functions into
   `src/lib.rs` following the module above: scalars stay `u64`, large inputs
   become slices, and string/JSON round-trips are avoided at the boundary.
3. **Compile.** Run `./build.sh` (or `.\build.ps1`) to produce `pkg/` and copy
   it into the web root. Verify `wasm-pack --version` is on PATH first.
4. **Load in the browser.** Import `load` from `loader.js`; call
   `await load()` once during app boot and reuse the returned function table.
   Never call `init()` more than once per page.
5. **Benchmark.** Run `demo(40, 1_000_000)` and compare against the old JS
   implementation inside `performance.now()` timing; `wasm-bindgen` scalar
   primitives typically show 5-50x speedups on tight numeric loops.
6. **Cut over.** Replace the JS call sites with the WASM equivalents behind a
   feature flag, then delete the dead JS implementation after a full
   release cycle.
7. **Cache & preload.** Emit `<link rel="preload" as="fetch" href=".../bottleneck_wasm_bg.wasm" crossorigin>` so the module is fetched during idle time.

## 5. Edge Cases & Error Handling

- **`wasm-pack` not installed** — the build scripts check and fail fast with
  `error: wasm-pack not found — install with: cargo install wasm-pack`. On
  Windows, the PowerShell script throws before running anything.
- **Missing Rust target** — if `wasm-pack` fails with
  `linker 'wasm32-unknown-unknown' not found`, run
  `rustup target add wasm32-unknown-unknown` and retry.
- **`wasm-bindgen` version mismatch** — the JS glue emitted by `wasm-pack`
  is tied to the exact `wasm-bindgen` version in `Cargo.lock`. If you see
  `unknown import ... wasm_bindgen::__rt::...` at runtime, run
  `cargo update -p wasm-bindgen --precise <version>` to realign, or bump the
  crate to the version `wasm-pack` reports and rebuild. Never mix two
  `wasm-bindgen` runtime copies on one page.
- **Memory overhead** — every `WebAssembly.Module` instance carves its own
  linear memory (4 GiB addressable, growable). Keep the module singleton:
  `loader.js` shares one instance process-wide. Large arrays should
  cross the boundary as `&[u8]`/typed arrays, not clone-serialized JSON, or
  you pay copy + alloc overhead that eclipses the win.
- **Stack overflow / integer overflow in ported kernels** — the Rust code
  uses `wrapping_add` in recursive-free iterative loops so Fib-style cases
  cannot panic; keep overflow-checking semantics deliberately (`wrapping_*`)
  or document `u128` for extremes.
- **Serving `.wasm`** — `pkg/` copies must land in the web root from the
  build scripts; verify the server sends `Content-Type: application/wasm`
  and CORS headers if served cross-origin. Confusing empty exports usually
  mean the wrong `pkg/` was copied or the wrong path was imported.
- **Debug vs release** — never ship a `debug` build (`opt-level=s`, `lto`,
  `codegen-units=1`); debug WASM is often slower than the JS it replaces.
- **Retry after transient failure** — `loader.js` resets its internal promise
  on rejection so a temporary network hiccup does not permanently poison the
  module for the page lifetime.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.