aver-lang 0.9.0

VM and transpiler for Aver, a statically-typed language designed for AI-assisted development
Documentation
# WASM Backend

This backend compiles Aver to standalone `.wasm` modules with a typed ABI:

- `Int -> i64`
- `Float -> f64`
- `Bool -> i32`
- heap-backed values (`String`, `List`, `Map`, `Vector`, records, variants, wrappers) -> `i32` pointer

Default output uses the `aver/*` import ABI. That keeps the generated module host-neutral: the same `.wasm` can run under the built-in wasmtime host, a browser JS shim, or a custom embedder.

## Adapters

- `aver compile app.av --target wasm`
  Emits `aver/*` imports such as `aver/console_print`, `aver/time_unixMs`, `aver/format_value`.
- `aver compile app.av --target wasm --wasm-opt oz`
  Post-processes the emitted module with `wasm-opt -Oz` for smaller binaries.
- `aver compile app.av --target wasm --wasm-opt o3`
  Post-processes the emitted module with `wasm-opt -O3` for speed-oriented optimization.
- `aver compile app.av --target wasm --adapter wasi`
  Emits WASI imports for standalone `wasmtime`.

`--wasm-opt` requires `binaryen` (`wasm-opt`) to be installed. The toolchain passes the required WASM feature flags automatically because Aver modules use bulk-memory ops and multi-value imports.

## Built-in Host

`aver run app.av --wasm` compiles with the `aver/*` ABI and executes the module with a built-in wasmtime host in [src/main/commands.rs](/Users/szymon.tezewski/PycharmProjects/lumen-rs/src/main/commands.rs).

The built-in host currently provides:

- `Console.*`
- `Terminal.*`
- `Random.int`
- `Time.now`, `Time.unixMs`, `Time.sleep`
- `Print.value`, `Format.value`
- `Float.sin`, `Float.cos`, `Float.atan2`, `Float.pow`

## Host ABI Shape

The canonical import table lives in [abi.rs](/Users/szymon.tezewski/PycharmProjects/lumen-rs/src/codegen/wasm/abi.rs).

A few practical rules:

- strings cross the boundary as `ptr + len`
- heap strings inside Aver memory are string objects: 8-byte header, then UTF-8 bytes
- `Print.value` / `Format.value` take `(tag: i32, value: i64)`
- `Terminal.readKey` returns `(ptr, len)` for `Some(String)` and `(-1, 0)` for `Option.None`
- `Terminal.size` returns `(width: i32, height: i32)` and codegen wraps that into `Terminal.Size`

## Memory Model

The WASM backend uses a single bump-heap allocator (`$alloc`) with boundary compaction at function return and TCO iteration boundaries. No separate GC runtime — the full model is ~1.5 KB of emitted WASM.

**Function return**: `collect_begin(mark)` → `retain_i32(result)` deep-copies reachable objects to a temp area → `collect_end()` rebases internal pointers and copies back → `rebase_i32(result)`. Dead objects between mark and heap_ptr are reclaimed.

**TCO iterations (yard semantics)**: an `iter_mark` is saved at each loop iteration. If the iteration allocated very little (≤256 bytes), compaction is skipped entirely (O(1) — accumulator pattern like list building). Otherwise, full compaction from the function's `fn_mark` reclaims dead objects from previous iterations (replacement pattern like game loops).

**Thin/parent-thin frames**: small pure functions (leaf computations, dispatch) skip boundary work entirely — no mark saved, no compaction on return.

**Exported `alloc`**: modules export `$alloc(size: i32) -> i32` so hosts can allocate guest memory safely for strings returned from host imports.

## Optimized Patterns

- `Option.withDefault(Vector.get(v, i), literal)` → inline bounds check + direct `i64.load`, no Option wrapper allocation
- `Map.set` with unique keys → prepend only (no rebuild), O(1) insert
- String concatenation uses `memory.copy` for bulk byte transfer

## Minimal Browser Host

This is enough to run console-style examples compiled with `aver compile hello.av --target wasm`:

```html
<pre id="out"></pre>
<script type="module">
const out = document.querySelector("#out");
const td = new TextDecoder();
const te = new TextEncoder();
let instance;

const mem = () => new Uint8Array(instance.exports.memory.buffer);
const readBytes = (ptr, len) => mem().slice(ptr, ptr + len);
const readStringObj = (ptr) => {
  const view = new DataView(instance.exports.memory.buffer);
  const len = Number(view.getBigUint64(ptr, true) & 0xffffffffn);
  return td.decode(readBytes(ptr + 8, len));
};
const formatTagged = (tag, val) => {
  switch (tag) {
    case 0: return BigInt.asIntN(64, val).toString();
    case 1: {
      const buf = new ArrayBuffer(8);
      const view = new DataView(buf);
      view.setBigUint64(0, BigInt.asUintN(64, val), true);
      return String(view.getFloat64(0, true));
    }
    case 2: return val !== 0n ? "true" : "false";
    case 3: return readStringObj(Number(val));
    default: return String(val);
  }
};
const writeGuestString = (text) => {
  const bytes = te.encode(text);
  if (bytes.length <= 32) { mem().set(bytes, 96); return [96, bytes.length]; }
  const ptr = instance.exports.alloc(bytes.length);
  mem().set(bytes, ptr);
  return [ptr, bytes.length];
};

const imports = {
  aver: {
    console_print(ptr, len) { out.textContent += td.decode(readBytes(ptr, len)); },
    console_error(ptr, len) { out.textContent += td.decode(readBytes(ptr, len)); },
    console_readLine() { return writeGuestString(""); },
    print_value(tag, val) { out.textContent += formatTagged(tag, val); },
    format_value(tag, val) { return writeGuestString(formatTagged(tag, val)); },
    random_int(min) { return min; },
    time_now() { return writeGuestString(new Date().toISOString()); },
    time_unixMs() { return BigInt(Date.now()); },
    time_sleep() {},
    math_sin(x) { return Math.sin(x); },
    math_cos(x) { return Math.cos(x); },
    math_atan2(y, x) { return Math.atan2(y, x); },
    math_pow(base, exp) { return Math.pow(base, exp); },
  },
};

({ instance } = await WebAssembly.instantiateStreaming(fetch("/hello.wasm"), imports));
instance.exports._start();
</script>
```

The same shape works in Node via `WebAssembly.instantiate(...)` with a `Buffer`.

There is also a real browser host in [tools/wasm-runner/README.md](/Users/szymon.tezewski/PycharmProjects/lumen-rs/tools/wasm-runner/README.md) that lets you upload a compiled `.wasm` module and run it in-page. For interactive `Terminal.readKey()` workloads, serve it with `python3 serve.py 4173` so the page gets the isolation headers needed for shared-memory input.

## Terminal In Browsers

`Terminal.*` is available in the built-in wasmtime host and in the static browser runner.

For browsers, the rendering policy is still host-specific:

- `Terminal.print` can target a `<pre>`, DOM tree, `<canvas>`, or terminal emulator widget
- `Terminal.moveTo` / `clear` / `setColor` / `flush` map naturally to a retained text grid
- `Terminal.readKey` should be driven from browser keyboard events into a small queue
- `enableRawMode` / `disableRawMode` are usually "capture keyboard events" rather than literal TTY mode

The runner in `tools/wasm-runner` is the reference host for that mapping.