구조
ADR-0009: A native backend, if built, runs in an isolated worker process
이 페이지는 영어입니다. 하나의 언어로 유지된 사양은 하나의 내용만 말합니다.
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#
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.
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 asHostValuelimits 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) andexit_group; new user and network namespaces where available. Other systems: the strongest available equivalent, or no native backend therecancellation 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 unaffectedEquivalence 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.
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.