machdocs
Home GitHub

Intrinsics

Intrinsics are compiler-shipped comptime functions. They share the ordinary $name(args) call shape, but their names are reserved and their bodies live in the compiler. The set is closed: adding one requires a compiler change.

Value intrinsics

These fold to a comptime constant unsigned integer. The storage type is whatever the binding declares - Mach has no compiler-known usize.

$size_of(T)             # byte size of type T
$align_of(T)            # byte alignment of type T
$offset_of(T, field)    # byte offset of T's field
pub val POINT_SIZE: i64 = $size_of(Point);
pub val POINT_X:    i64 = $offset_of(Point, x);

Type intrinsic

$type_of(expr) produces a comptime type value: the resolved type of its argument. Type values have no runtime representation; they are meaningful only as operands in comptime type comparisons.

$type_of(expr)          # comptime type value of expr

Compare type values with == or != inside a $if condition. A bare type name (i64, str, Point) is the other valid operand. The comparison selects one branch at compile time per monomorphization instance - useful for per-element type dispatch inside $each bodies.

$if ($type_of(arg) == i64) { write_i64(w, arg); }
$or ($type_of(arg) == str) { write_str(w, arg); }
$or { $error("unsupported type"); }
Note

Provably-dead arms are pruned before type-checking, so each arm uses arg at its own concrete type with no per-arm cast: the str arm above is never checked against a u64 element. Only the selected arm is type-checked and emitted.

Field intrinsic and projection

$fields(T) produces a comptime sequence of field descriptors for record or union type T. Each descriptor carries three readable properties.

PropertyTypeValue
f.name*u8field name as a NUL-terminated string
f.typetype valuecomptime type value of the field's type
f.offsetintegerbyte offset of the field in T's layout

The sequence is consumed by $each f in $fields(T). Inside the body, v.[f] projects the concrete field off an instance v. It is an lvalue: readable and writable, including through a pointer receiver.

$fields(T)              # comptime field sequence for record/union T
v.[f]                   # comptime field projection: access the field f on v
rec Pair { x: i64; y: i64; }

fun sum(p: Pair) i64 {
    var total: i64 = 0;
    $each f in $fields(Pair) {
        total = total + p.[f];      # p.x on iteration 1, p.y on iteration 2
    }
    ret total;
}

$each f in $fields(Empty) expands to nothing when T has no fields.

Heterogeneous fields

Because each iteration re-types v.[f] to the concrete field type, heterogeneous records work naturally - cast each field as you fold it.

rec Mixed { a: i64; b: u8; }

fun total(m: Mixed) i64 {
    var t: i64 = 0;
    $each f in $fields(Mixed) {
        t = t + m.[f]::i64;     # m.a (i64) on iter 1, m.b (u8) cast to i64 on iter 2
    }
    ret t;
}

Descriptor reads

A descriptor's properties can be read inside the loop body - f.offset for layout math, f.type for type comparisons.

fun offsum(m: Mixed) i64 {
    var s: i64 = 0;
    $each f in $fields(Mixed) {
        s = s + f.offset::i64;    # 0 + 8 = 8 for Mixed { a: i64; b: u8; }
    }
    ret s;
}

fun count_i64(m: Mixed) i64 {
    var n: i64 = 0;
    $each f in $fields(Mixed) {
        $if (f.type == i64) { n = n + 1; }
        $or { }
    }
    ret n;
}
Note

A field literally named type is unaffected: ordinary v.type access still works. The v.[f] projection uses the $each loop variable, which is always a field descriptor, never a regular member.

Nested $each

$each can be nested to walk a record's fields against another's.

fun cross(p: Pair, q: Pair) i64 {
    var t: i64 = 0;
    $each f in $fields(Pair) {
        $each g in $fields(Pair) {
            t = t + p.[f] * q.[g];
        }
    }
    ret t;
}

$each: compile-time unroll

$each is a statement form that splices its body once per element of a comptime sequence. There are two sequence forms.

$each f in $fields(T) { ... }    # one iteration per field of T
$each a in va { ... }            # one iteration per element of pack va

It is valid only in statement scope, inside a function body. It is not a loop: the body is duplicated at compile time, not iterated at runtime. Enclosing runtime variables (an index, an accumulator) are shared across all unrolled copies. See Variadic packs for the pack form.

Diagnostic intrinsics

$error("msg") fails compilation with msg when it is reached on a live path: an unconditional position, or a $if / $or arm the compiler selects. A $error in a discarded arm never fires, so it is the natural total-coverage fallback for a $type_of dispatch: the unhandled-type $or {} arm fails the build at compile time instead of falling through to a runtime error. It is valid in both declaration and statement scope and takes one string-literal message.

$error("msg")           # fails compilation when reached

$if (!supported) {
    $error("this target is not supported");
}

$if ($type_of(arg) == i64) { write_i64(w, arg); }
$or ($type_of(arg) == str) { write_str(w, arg); }
$or { $error("no writer for this argument type"); }    # compile error on an unhandled type
Not yet implemented

$assert planned parses as a comptime directive but the compiler does not yet evaluate it. It is intended as sugar over $if plus $error: $assert(cond, "msg") is equivalent to $if (!cond) { $error("msg"); }, e.g. $assert($size_of(i64) == 8, "expected 64-bit i64");.

Not provided as intrinsics

Code intrinsics - runtime-instruction emitters like trap, fence, and pause - are not in the compiler-shipped set. They belong in the standard library as functions with per-arch asm bodies.

See also