ABI and Layout
This file is part of the Nocter language specification. The specification entry point is README.md.
Nocter ABI v0
Adopted: Nocter uses its own internal ABI for Nocter functions and primitives.
Nocter ABI v0 is defined only for the initial arm64-darwin target. It is not a C ABI, and it does not guarantee binary compatibility with C, Swift, Objective-C, or the platform dynamic linker ABI. Future C interop, if added, should use an explicit separate ABI form such as an extern "c"-style declaration.
Scope:
- target:
arm64-darwin - word size: 64-bit
- endian: little-endian
- stack alignment: 16 bytes at call boundaries
- compilation model: whole-program compilation in the initial implementation
Rules:
- All ordinary Nocter functions use Nocter ABI.
- Associated functions and methods use Nocter ABI.
dropfunctions use Nocter ABI.primitivedeclarations use Nocter ABI at the Nocter boundary.- OS syscall ABI details stay inside target-specific primitive lowering.
- C ABI compatibility is not promised.
- ABI definitions are target-specific; future targets define their own Nocter ABI variant.
- Type aliases do not affect ABI. ABI classification always uses the alias target type.
Registers
Nocter ABI v0 uses the following ARM64 register roles:
x0-x7 argument registers and direct return registers
x8 indirect return pointer
x9-x15 caller-saved scratch registers
x16-x17 compiler / primitive scratch registers
x18 reserved
x19-x28 callee-saved registers
x29 frame pointer
x30 link register
sp stack pointer, 16-byte aligned at call boundaries
The caller must assume x0-x17 may be clobbered by a call, except for return values. The callee must preserve x19-x28, x29, and stack alignment according to this ABI.
ABI Values
ABI classification uses 64-bit words.
One-word values:
bool- integer types up to 64 bits
- raw pointers
&T&+T
Two-word values:
&str&[T]&+[T]
Layout of these view types:
word 0: ptr
word 1: len
ptr points to the first byte or element. len is a usize count. For &str, len is the number of UTF-8 bytes.
The unsized data forms str and [T] do not have a standalone by-value ABI. They are passed and returned only through an indirection such as &str, &[T], &+[T], or an owning standard-library type such as String or Vec<T>.
Values larger than 16 bytes are classified as indirect values.
Arguments
Arguments are assigned left to right.
Rules:
- Values of 16 bytes or less are passed directly.
- Direct values use
x0-x7when enough consecutive argument registers remain. - A two-word direct value must fit entirely in registers; otherwise the whole argument is passed on the stack.
- Values larger than 16 bytes are passed indirectly by pointer.
- Stack arguments are placed in ABI-sized slots and keep their natural alignment, with the stack 16-byte aligned at the call boundary.
- For indirect arguments, ownership and borrowing rules are still source-level Nocter rules. The pointer passing mechanism does not by itself transfer ownership.
Small integer arguments are extended to one ABI word. Unsigned integers are zero-extended. Signed integers are sign-extended. bool uses 0 for false and 1 for true; other bit patterns are invalid for a live bool value.
When a live bool or enum value can enter from a primitive or ABI boundary and the compiler cannot prove that its bit pattern or tag is valid, the required validation is an always-on safety check. The general safety-check policy is specified in Control Flow.
Returns
Return rules:
voidreturns no value.neverdoes not return to the caller.- Values of 16 bytes or less return directly in
x0andx1. - Values larger than 16 bytes return indirectly.
For indirect returns, the caller allocates the destination storage and passes its pointer in x8. The callee writes the result into that storage and returns normally. The callee does not allocate the return storage.
Struct Layout
struct layout is deterministic.
Rules:
- Fields are laid out in declaration order.
- The compiler does not reorder fields.
- Each field is placed at the next offset satisfying that field's alignment.
- The struct alignment is the maximum alignment of its fields.
- The final struct size is rounded up to the struct alignment.
copy structand ordinarystructuse the same layout rules.droppresence does not change layout.
Field alignment follows the field's ABI type. Initial alignments:
bool, u8, i8 align 1
u16, i16 align 2
u32, i32 align 4
u64, i64 align 8
usize, isize align 8
pointer, borrow align 8
Aggregates use their computed aggregate alignment.
Fixed-Size Array Layout
[T; N] stores N elements of T contiguously in index order.
Rules:
- The element type
Tmust have a sized ABI layout. - The array alignment is the element alignment.
- The element stride is the element size rounded up to the element alignment.
- Element
istarts at byte offseti * stride. - The array size is
stride * N. [T; 0]has size0and the same alignment asT.- Array ABI classification uses the total array size and alignment, following the ordinary direct-versus-indirect rules.
- Drop glue for a fixed array drops initialized owned elements in reverse index order.
- Fixed arrays of copy element types are copyable. Fixed arrays of move-only element types are move-only.
Enum Layout
The v0 backend only ships payloadless enum values. They are represented as a single tag byte.
payloadless enum = u8 tag
Payload-carrying enum values are reserved for a later backend phase and will use a tag plus a payload union once promoted.
enum = tag + payload union
Rules:
- Payloadless enums in the v0 backend use a
u8tag and may have at most 256 variants. - Variant tag values are assigned by declaration order starting at
0. - Payload-carrying enum runtime layout is deferred until the backend promotes payload enum values. That promotion must define the tag width, payload alignment, inactive-byte behavior, and active-payload drop rules in this document before code generation relies on them.
Built-In Error Layout
The built-in error payload is represented as two borrowed string slices:
error:
code: &str
message: &str
Layout:
word 0: code.ptr
word 1: code.len
word 2: message.ptr
word 3: message.len
Rules:
error.codeanderror.messageare the user-facing fields oferror.- Both fields have type
&str. - The field order is
code, thenmessage. - The built-in
errortype has size 32 bytes and alignment 8 on ABI v0. errordoes not own its string storage and has no drop member.erroris copyable because both fields are copyable borrowed views.- An
errorvalue carries borrow-like provenance from both&strfields. - Returning or storing an
errormust satisfy the same borrow-like escape rules as any aggregate containing&str. - The compiler-generated entry wrapper may report
error.codeanderror.messagedirectly from these fields without allocating or calling a fallible standard-library API.
Optional Layout
T? is represented as a tag plus a payload.
T?:
tag 0 = none
tag 1 = value
Rules:
- The tag type is
u32. - The payload layout is the layout of
T. - The payload is live only when the tag is
1. nonehas no live payload.- Drop code drops the payload only when the tag is
1.
No niche optimization is part of ABI v0. Even if a type has unused bit patterns, T? still uses the explicit tag representation.
Fallible Layout
T! is represented as a tag plus a payload union.
T!:
tag 0 = success
tag 1 = failure
Rules:
- The tag type is
u32. - Success payload layout is the layout of
T. - Failure payload layout is the layout of the built-in
errortype. - The payload area is large enough and aligned enough for either payload.
- The payload is live only for the active tag.
- Drop code drops only the active payload.
Nocter source uses return value for success and return error_value for failure. ABI v0 does not reserve the identifier success; it defines only the binary tag meaning.
Composed optional and fallible values use the same layout rules recursively. For example, T?! is laid out as a fallible value whose success payload is the explicit-tag layout of T?.
Drop ABI
Source syntax:
drop &+self {
...
}
ABI form:
x0 &+Self
return void
Rules:
dropis not fallible.dropmust not return a value.- The caller passes a readwrite borrow of the value being destroyed.
- After
dropreturns, the caller treats that value as dead. - Drop glue for structs, fixed arrays, enums, optionals, and fallible values must follow active-field, initialized-element, and active-payload rules.
Primitive ABI
primitive declarations use Nocter ABI at the Nocter call boundary.
pub(nocter) primitive syscall3(...): SyscallResult
pub(nocter) primitive trap(): never
Rules:
- After visibility and trusted-boundary restrictions have allowed the call, the source-level call to a primitive is type checked like a normal function call.
- The primitive's parameters and return type are lowered using Nocter ABI.
- The backend then lowers the primitive body to target-specific machine code or target-specific calling sequences.
- A primitive may internally use OS syscall conventions, but those conventions are not exposed as the source-level ABI.
- The compiler validates primitive declarations against either the target-independent core primitive set or the active target's closed primitive set.
ABI Stability
ABI v0 is an internal compiler ABI. It is stable enough for the initial compiler, standard library, and primitive boundary, but it is not a public binary compatibility promise across compiler versions.
The ABI should not be changed casually. Changes require updating:
- code generation
- data layout
- type checking assumptions for layout-sensitive constructs
- primitive lowering
- target-gated standard-library internals
- tests for calls, returns, aggregate layout, optionals, fallible values, and drop glue