Backend V0
This document describes the current native backend implementation boundary. The public ABI rules live in ABI and Layout.
Target
The v0 native backend targets arm64-darwin and writes Mach-O executables directly. It does not invoke LLVM, clang, as, ld, Xcode Command Line Tools, or an external linker.
Reserved future target names may be recognized by the CLI, but unsupported targets must diagnose explicitly before backend emission.
Pipeline Position
typed compile unit
-> buildability preflight
-> IR lowering
-> ABI classification and validation
-> ARM64 code generation
-> Mach-O image
The backend consumes IR and ABI facts. It should validate inconsistent IR, but ordinary user source outside the v0 runtime subset should be rejected earlier by buildability diagnostics.
ABI Summary
Nocter ABI v0 is register-first:
- one ABI word is 8 bytes
- scalar
i32,u8, andbooloccupy one ABI word but use 32-bit or narrower value operations as appropriate usizeand raw pointers occupy one ABI word- public raw pointer conversions lower raw pointers as address-carrying
usizeABI words; pointer dereference is not part of v0 backend support - ABI layout recognizes additional integer widths for aggregate storage, but standalone runtime scalar values are currently limited to the documented buildable scalar subset
&str,&[T], and&+[T]occupy two ABI words: pointer then length- direct aggregates are aggregate values of 16 bytes or less
- indirect aggregates are aggregate values larger than 16 bytes
- direct aggregate returns use
x0and optionallyx1 - indirect aggregate returns use caller-provided return storage
- normal-call argument words after the first eight use the caller stack argument area
- tail calls remain narrower than normal calls and must not branch with borrow arguments that point into a released caller frame
ABI classification belongs in abi. Lowering and backend validation should reuse that classification rather than duplicating layout rules.
Frame And Call Model
Frameless functions are still valid for simple leaf paths. Functions that need normal calls, stack-passed arguments, saved return addresses, aggregate slots, or parameter spill addresses use a fixed-size 16-byte-aligned stack frame.
The current frame model supports:
- saved
x30for normal calls - conservative scalar spill/reload around calls
- parameter ABI-word spills when later code needs stable parameter values or borrow addresses
- aggregate slots for local aggregate values and staged call results
- caller stack argument space for normal calls with more than eight ABI words
- hidden return storage handling for indirect aggregate calls and returns
The implementation intentionally favors correctness and clear validation over fine-grained liveness optimization.
Runtime-Shipped Codegen
The backend currently supports the documented subset of:
- entry wrappers for
i32,usize,void,i32!,usize!, andvoid! - scalar and view locals, parameters, returns, calls, checked i32 unary negation, arithmetic, numeric compound assignments, comparisons, bool operations, and runtime traps
- static string literals and
&strABI passing - slice length, selected indexing, bounds-checked stores, and numeric scalar/view slice element compound assignments
- same-file and loaded imported calls in the current scalar/view/aggregate subset
- concrete generic specializations whose lowered bodies stay inside the runtime subset
- fallible and optional propagation, force unwrap,
catch, and direct-callotherwisein supported scalar/view value, binding, assignment, and return paths, supported aggregate/fixed-array member root, binding, argument, aggregate-field initializer, assignment, and return paths - terminal and selected non-terminal control flow with cleanup
- payloadless enum tag comparisons,
if is, andmatch - direct and indirect aggregate parameters, arguments, returns, call-result slots, copies, field loads/stores, explicit moves, replacement assignment, and scope cleanup
- owned std values such as
String,Vec<T>in shipped element paths,File, allocator buffers, and their drop members - target-gated std primitives for traps, syscalls, I/O, allocation, process context, and process termination
Aggregate ABI Status
The aggregate path has been promoted beyond scalar-only codegen. Current support includes:
- direct aggregate values up to 16 bytes, including partial final ABI words
- indirect aggregate values larger than 16 bytes through slot pointers or hidden return storage
- aggregate call arguments crossing register and stack argument boundaries
- aggregate returns from literals, local slots, member copies, calls, explicit moves, and terminal branches in the supported subset
- copy aggregate fields loaded from non-copy owners when the field type is
copy struct - drop-aware whole-binding and field replacement in supported paths
- optional and fallible aggregate call results in supported positions
- copy aggregate storage and slice indexing through current
Vec<T>and slice-view paths
Remaining aggregate backend work is concentrated around payload-carrying enum runtime, broader field-level live-state tracking, non-copy aggregate collection elements, arrays, broad control-flow joins, and unsupported expression shapes.
Safety Checks
Runtime checks are part of v0 behavior, not debug-only instrumentation.
The backend emits traps for supported cases such as:
- integer overflow where v0 arithmetic requires trapping
- division or remainder by zero
- invalid signed division overflow
- shift counts outside the type width
- out-of-bounds slice and string indexing
- forced unwrap of absent optional or failed fallible values
- explicit unreachable paths
Future optimizations may remove a check only when the compiler proves the trap condition impossible.
Backend Non-Goals For v0
- external linker integration
- LLVM or assembly output as the normal backend path
- C ABI compatibility
- dynamic linking
- debug info
- register allocation optimization
- broad tail-call lowering
- Linux, Windows, wasm, or x86_64 output
- stable public binary ABI
Tests
Backend changes should use the narrowest useful tests:
- backend unit tests for instruction encodings, frame layout, call emission, and Mach-O invariants
- IR lowering tests for expected instruction shapes
- CLI build/run tests for user-visible runtime behavior
- distributed-home tests for std-backed runtime behavior
ABI behavior changes must also update ABI and Layout and v0-closure.md.