# Backends

One Rail compiler, six targets. All driven from the same `rail_native` binary.

| Backend | Verb | Output | Toolchain needed | Status (local-verified) |
|---|---|---|---|---|
| macOS ARM64 (Mach-O) | _(default)_ / `run` | `/tmp/rail_out` | none (host `as`+`ld`) | runs end-to-end |
| Linux ARM64 (ELF) | `linux` | `/tmp/rail_linux` | `aarch64-elf-as`, `aarch64-elf-ld` | needs cross-binutils |
| Linux x86_64 (ELF, source-only) | `x86` | `/tmp/rail_x86.s` | remote `gcc` (or Docker `linux/amd64`) | emits `.s`, 71/79 conformance via Docker harness |
| WebAssembly | `wasm` | `/tmp/rail_out.wasm` | `wat2wasm` (wabt) | compiles end-to-end |
| Cortex-M4 (Thumb-2 ELF) | `cortexm` | `/tmp/rail_m4.elf` | `clang --target=thumbv7em-none-eabi` | compiles end-to-end |
| RISC-V rv32imc (ELF) | `riscv32` | `/tmp/rail_rv32.elf` | `clang --target=riscv32-unknown-none-elf` | compiles end-to-end |

---

## macOS ARM64 (Mach-O)

This is the primary target. No flags, no setup.

```bash
./rail_native run examples/hello.rail
```

```
Compiling examples/hello.rail (244 chars)...
  as: OK
  ld: OK
hello, rail
3628800
42
```

`rail_native` uses the system's `as` and `ld` directly. No libc; the runtime (allocator, GC, string ops, list ops) is ARM64 assembly emitted inline.

## Linux ARM64 (ELF)

```bash
./rail_native linux examples/hello.rail
```

If you have `aarch64-elf-as` and `aarch64-elf-ld` on `PATH` (look for `aarch64-elf-binutils` via Homebrew or your distro), this produces `/tmp/rail_linux`. `scp` it to a Pi Zero 2 W and run.

Without the cross-tools:

```
Cross-compiling examples/hello.rail for Linux ARM64...
  as: /bin/sh: /opt/homebrew/bin/aarch64-elf-as: No such file or directory
  ld: /bin/sh: /opt/homebrew/bin/aarch64-elf-ld: No such file or directory
  Binary: /tmp/rail_linux
  /tmp/rail_linux: cannot open `/tmp/rail_linux' (No such file or directory)
```

To install on macOS:

```bash
brew install aarch64-elf-binutils
```

## Linux x86_64 (source-only)

```bash
./rail_native x86 examples/hello.rail
```

```
Compiling examples/hello.rail (244 chars)...
  Assembly: /tmp/rail_x86.s (1495 chars)
  No local x86_64 assembler. scp to target and build with: gcc -o prog /tmp/rail_x86.s
  Try: scp /tmp/rail_x86.s <user>@<host>:~ && ssh <user>@<host> 'gcc -o rail_x86 rail_x86.s && ./rail_x86'
```

Rail emits the GAS assembly directly; assemble on the target with `gcc -o prog /tmp/rail_x86.s`. This is intentional — the x86_64 backend was bootstrapped for the Razer WSL test fleet rather than for cross-tools-on-mac. The x86 runtime asm lives at `tools/x86_rt.s`.

**Conformance:** `bash tools/test/x86_conformance.sh` (requires Docker + `linux/amd64` image, e.g., Colima/Rosetta) currently passes **71/79** representative tests covering ints, strings, lists, ADTs, closures, floats, FFI, TCO, and arena ops. The 8 remaining failures are 3 ELF-prefix ffi-libc tests and 5 missing str-runtime symbols, all classified in `~/.claude/projects/-Users-user/memory/x86_backend_status.md`.

## WebAssembly

```bash
./rail_native wasm examples/wasm/hello.rail
```

```
Compiling examples/wasm/hello.rail to WASM...
  WAT: 51277 bytes
  wat2wasm: OK
  Binary: /tmp/rail_out.wasm
```

Requires `wat2wasm` from the WebAssembly Binary Toolkit (`brew install wabt`). The output is `/tmp/rail_out.wasm` — drop into any WASM runtime that provides the `env.print` import. The live playground at https://ledatic.org embeds exactly this output.

Known WASM-backend limits (see CLAUDE.md): no `filter`/`map`/`fold` as WASM builtins, 1 MB linear memory. Closures, ADTs, pattern matching, and string ops all work.

## Cortex-M4 (Thumb-2)

```bash
./rail_native cortexm examples/m4_uart_hello.rail
```

```
Compiling examples/m4_uart_hello.rail -> Cortex-M4 (Thumb-2)...
  Assembly: /tmp/rail_m4.s (1084 chars)
  as: OK -> /tmp/rail_m4.o
  startup as: OK
  ld: OK -> /tmp/rail_m4.elf
```

Requires `clang` with the `thumbv7em-none-eabi` target. On macOS: `brew install llvm` (Apple's bundled clang doesn't include the bare-metal targets). The driver also assembles the startup vector (`tools/cortexm_rt/startup.s`) and links into `/tmp/rail_m4.elf`.

To verify in qemu (no real hardware needed for CMSDK UART programs):

```bash
qemu-system-arm -M mps2-an386 -nographic -kernel /tmp/rail_m4.elf
```

## RISC-V rv32imc

```bash
./rail_native riscv32 /tmp/test_riscv.rail
```

```
Compiling /tmp/test_riscv.rail -> RISC-V (rv32imc)...
  Assembly: /tmp/rail_rv32.s (314 chars)
  as: OK
  startup: OK
  ld: OK -> /tmp/rail_rv32.elf
```

Requires `clang` with `riscv32-unknown-none-elf` target. The minimal `main = 42` program boots in `qemu-system-riscv32 -M virt -bios none -kernel /tmp/rail_rv32.elf` and exits 42 via the SiFive test register.

The rv32 backend is v0 — literals only. See [`memory:riscv32_backend_v0`](https://ledatic.org/memory/riscv32_backend_v0) for the supported subset.

---

## Adding a backend

Look at `tools/compile.rail` around line 6605: the `cmd` dispatch table. Each backend is roughly:

1. A new "emit" pass that walks the AST.
2. A driver function (`compile_cortexm`, `compile_riscv32`, etc.) that calls the right assembler/linker via `shell`.
3. A startup file under `tools/cortexm_rt/` or equivalent.

Cortex-M4 took about 600 lines of compiler code; RISC-V was about 300 because Thumb-2 paved the road.
