Open Manual

Native boundary

Native boundary rules cover layouts, extern blocks, pointers, handles, strings,

ownership, and ABI behavior.

Native backend capability boundary

--native-only selects the host encoder on x86-64 and AArch64. Both paths

lower NYIR, encode machine code, relocate an in-memory W^X image, and support

persistent REPL definitions without constructing LLVM state. AArch64 ELF64

objects and internal-link runtime probes cover AAPCS64 scalar/f32/f64 calls,

eight integer arguments, local pointer memory, branches, and signed div/mod.

i386 has its separately tested ELF32 object/link slice. ARM, RISC-V, MIPS,

PowerPC, BPF, AVR, and WebAssembly target names are assembly/inspection paths

unless their capability record explicitly enables an object format. An

assembly-only target fails object or JIT requests instead of invoking a hidden

fallback. General C aggregate classification remains target-specific: the

documented nested INTEGER/SSE/MEMORY aggregate ABI is the x86-64 System V

boundary, not an implied cross-ABI promise.

Layouts

layout Name {
   Type field,
   Type field2
}

Layout field order is part of the ABI. A layout describes memory shape at a

native boundary. Reordering fields changes the ABI.

Extern blocks

extern "library" {
   fn symbol(Type arg) Type
}

extern {
   fn process_symbol(Type arg) Type
}

An extern block declares native symbols. The library string selects the linked

or loaded library. A bare extern block names symbols already available from the

current process; extern "" is accepted as the older spelling.

Header imports

#include imports declarations from C headers through the selected C frontend.

The Nytrix-owned frontend handles the supported preprocessor, declaration,

layout, callback, and aggregate-ABI subset; unsupported shapes fail explicitly

unless an explicitly selected adapter is responsible for that import.

#include <stdlib.h> as "c"
#include "./ffi.h"

An explicit namespace alias exposes declarations through that namespace. An

unaliased import (or as "") exposes constants, macros, enum values, layouts,

and non-conflicting functions directly; it does not create an implicit

namespace. Existing Nytrix names remain authoritative on collisions; an

explicit namespace alias makes the C declaration separately addressable.

Header imports follow transitive includes. Object-like integer macros,

shift/bitwise macro expressions, enum constants, typedef structs, and

pointer-bearing structs become visible when the selected frontend supports

their declarations. The internal frontend resolves installed system include

roots and recovers past unsupported system declarations while retaining strict

diagnostics for project headers.

Imported typedef structs are available as layout constructors. Use &value

when a C function expects an out pointer:

#include <sys/time.h> as ""

mut timeval tv = timeval(0, 0)
gettimeofday(&tv, NULL)

NULL is accepted as the C null pointer spelling and lowers to nil/0.

For production native calls, prefer extern, #include, and layout. The

dynamic std.os.ffi helpers are useful at the REPL or for exploratory probes,

but they trade compile-time ABI knowledge for runtime descriptors and capped

dynamic dispatch.

Pointers

*T is an addressable pointer to T. Pointer values model memory addresses.

They require correct lifetime, alignment, and element type.

Handles

handle is an opaque native scalar. A handle is not a pointer unless the API

documents that conversion. Handle cleanup uses the close, destroy, or release

function documented by the owning API.

Layout helpers

Runtime layout helpers expose ABI metadata and field access:

__layout_size("Name")
__layout_align("Name")
__layout_offset("Name", "field")
store_layout(ptr, "Name", values...)
load_layout(ptr, "Name", "field")

Typed raw loads and stores include integer, float, bool, pointer, and handle

forms such as load8, load16, load32, load32_f32, load64_f64,

load64_h, store8, store32, and store_layout.

Raw memory helpers use byte offsets. The public wrappers default the offset to

zero:

HelperShapeUse
load8(p, i=0) / store8(p, v, i=0)byteRaw bytes.
load16, load32, load64tagged int/value loadNytrix scalar slots and raw integer data.
load64_i(p, i=0) / store64_i(p, v, i=0)int viewTyped integer reads and writes.
load64_h(p, i=0) / store64_h(p, v, i=0)handle viewPointer or handle-sized native values.
load32_f32, load64_f64float viewNative float fields.
store32_f32, store64_f64float storeNative float fields.

The runtime intrinsics behind these wrappers use (p, offset, value) store

order. User code should call the std.core wrappers above.

std.os.ffi.CStruct is a dynamic descriptor form. It is intentionally flexible

and slower than layout; compiled layout access resolves offsets directly.

Inline assembly and intrinsics

asm("mov $1, $0", "=r,r", value)
llvm("ctpop.i64", value)
llvm("llvm.cttz.i64", value, false)

asm lowers inline assembly for the active backend and target architecture.

@naked functions can contain complete target-specific assembly bodies. The

llvm builtin calls LLVM intrinsics; the llvm. prefix is optional for

intrinsic names.

Strings and bytes

FFI text handling is a boundary. APIs document whether strings are:

These forms are not interchangeable.

Ownership

Ownership attributes and API docs define who allocates and who frees native

values. A wrapper can provide scoped cleanup, but raw native values require

explicit ownership handling.

When a native call returns owned memory or a handle, model that in the wrapper

with ownership contracts and cleanup:

@returns_owned
fn make_buffer(){ malloc(64) }

@consumes(p)
@releases(p)
fn free_buffer(p) int {
   free(p)
   0
}

Related