Ir al contenido

Cómo está construido

ADR-0009: A native backend, if built, runs in an isolated worker process

Estas páginas están en inglés. Una especificación en un solo idioma dice una sola cosa.

Status: accepted as design (2026-10-09). Nothing is implemented: no code generator exists, and no generated machine code runs anywhere.

Context#

The reference interpreter (ADR-0002) runs inside TTT's process. That is acceptable because it cannot execute arbitrary machine code: it is memory-safe Rust with unsafe_code = "forbid", no ambient authority, and limits it enforces itself. A native backend (Cranelift, LLVM, or emitting Rust) would produce machine code that none of those guarantees cover: a code-generation bug, or a crafted artifact that slips past validation, becomes memory corruption inside the trading terminal, next to API keys and live order state.

There is also no measured need yet. The workloads T0 runs today spend their time in host operations (dataset reads, TTT's own backtest engine), not in interpreting T0 code; strategies are not interpreted at all (they lower to TTT's strategy graph, ADR-0004).

Decision#

  1. Do not build a native backend until a benchmark shows the interpreter is the bottleneck of a real workload, measured on the release build, with the host's share separated out.

  2. If one is built, its code never runs in the terminal process. It runs in a separate worker process, t0-worker, with this boundary:

    aspect design
    input a validated IR artifact (never source, never machine code from outside); the worker re-validates it
    compilation inside the worker; generated code never leaves it and is never cached on disk where the terminal could load it
    authority none: no files, network or environment beyond what the OS sandbox grants (below); every host operation is a message to the terminal, which checks the permission exactly as today (GuardedHost)
    protocol length-framed messages on a pipe pair: Start{artifact, limits}, HostCall{op, args} ↔ HostReply, Output{line}, Progress, Finished{outcome}; values in the same tagged form as HostValue
    limits fuel and depth compiled into the code (counters at loop back-edges and calls); memory and CPU time as OS limits on the process (setrlimit/cgroups on Linux, job objects on Windows)
    sandbox Linux: a seccomp filter allowing only read/write on the inherited pipes, mmap/munmap/mprotect (W^X: never writable and executable together) and exit_group; new user and network namespaces where available. Other systems: the strongest available equivalent, or no native backend there
    cancellation a message first; if not acknowledged within a deadline, the terminal kills the process
    crash a dead worker is Failed{E-RT-INTERNAL} with its exit status; the terminal is unaffected
  3. Equivalence is tested, not assumed. Every conformance fixture, the standard-library tests and a differential fuzzer run on both the interpreter and the native backend; any difference in output, outcome code or step accounting fails the build. The interpreter stays the reference.

  4. The interpreter remains the default and the only mode inside the terminal process. Native execution would be an explicit choice per run.

Alternatives#

  • Native code in-process "because it is our own compiler". Rejected: a compiler bug is exactly the failure this must survive.
  • WebAssembly in an embedded runtime (wasmtime) in-process. Better than raw native code (a sandboxed linear memory), but it still adds a JIT and a large dependency to the terminal's address space. It is a possible engine inside t0-worker, not a reason to skip the process boundary.
  • Remote execution only. Compatible: the same protocol over a socket is Phase 9.

Consequences#

  • Every host call from native code costs a round trip on a pipe. Programs that call the host in a tight loop gain nothing from native code, which is one more reason to benchmark first.
  • The terminal needs process management for workers (spawn, deadline, kill, reap), which it does not have today.

Revisit when#

A benchmark meets condition 1, or remote execution (Phase 9) builds the same message protocol for other reasons.