Skip to content

Intrinsics

Reference compiler-lowered memory, layout, numeric, SIMD, and runtime primitives.

Updated View as Markdown

The intrinsics namespace declares operations recognized directly by the compiler or runtime. Most exist to implement the prelude and should not appear in ordinary application code.

Almost every entry is unsafe and needs an enclosing unsafe { ... }. The exceptions are the layout and classification queries — size_of, align_of, offset_of, is_rc_managed, row_fingerprint — plus cmp, num_to_float, yield_now, and sleep.

Memory

alloc returns an arena-relative ptr[u8]. ptr_cast gives it a pointee type, store and load move values through it, and dealloc returns the block using the same size and alignment that allocated it.

fn main(): void {
    size := intrinsics.size_of[int]()
    align := intrinsics.align_of[int]()

    value := unsafe {
        block := intrinsics.alloc(size, align)
        slot := intrinsics.ptr_cast[u8, int](block)
        intrinsics.store[int](slot, 42)
        read := intrinsics.load[int](slot)
        intrinsics.dealloc(block, size, align)
        read
    }
    println("value ${value}")
}

realloc resizes a block, in place when it can. The new size is part of the layout contract, so it is what a later dealloc must be given:

fn main(): void {
    size := intrinsics.size_of[int]()
    align := intrinsics.align_of[int]()

    value := unsafe {
        block := intrinsics.alloc(size, align)
        bigger := intrinsics.realloc(block, size, size * 2, align)
        slot := intrinsics.ptr_cast[u8, int](bigger)
        intrinsics.store[int](slot, 7)
        read := intrinsics.load[int](slot)
        intrinsics.dealloc(bigger, size * 2, align)
        read
    }
    println("value ${value}")
}

Sizes and alignments are usize. A typed load or store still needs an initialized, aligned range large enough for T; passing the type checker proves none of that.

Pointers

ptr[T] is arena-relative and participates in the compiler’s guest-memory model. rawptr[T] is an absolute WebAssembly address obtained with to_raw, and it is what the bulk memory operations take. raw_add advances a raw pointer by a byte count and raw_cast retypes one.

fn main(): void {
    len := 8usize
    align := intrinsics.align_of[u32]()

    copied := unsafe {
        source := intrinsics.alloc(len, align)
        target := intrinsics.alloc(len, align)
        intrinsics.mem_fill(intrinsics.to_raw[u8](source), 0x7Fu8, len)
        intrinsics.mem_copy(intrinsics.to_raw[u8](target), intrinsics.to_raw[u8](source), len)
        read := intrinsics.load[u32](intrinsics.ptr_cast[u8, u32](target))
        intrinsics.dealloc(target, len, align)
        intrinsics.dealloc(source, len, align)
        read
    }
    println("copied ${copied}")
}

mem_copy requires non-overlapping ranges; use mem_move when they can overlap. mem_fill writes a repeated byte, not a typed default.

A rawptr may not be live across a suspension point, because the runtime can move arena state while execution is yielded. The compiler enforces this at lowering time, so a program can pass atoll check and still be rejected by atoll build:

fn main(): void {
    len := 8usize
    align := intrinsics.align_of[u32]()
    unsafe {
        block := intrinsics.alloc(len, align)
        raw := intrinsics.to_raw[u8](block)
        println("logging mid-region")
        intrinsics.mem_fill(raw, 0u8, len)
        intrinsics.dealloc(block, len, align)
    }
}

ATOLL4011: rawptr value is live across a suspend point. Finish the raw work, produce a managed value, then do the I/O. Converting between ptr and rawptr is a view change, never an ownership transfer.

Layout

Layout queries are safe to call and fold after generic specialization.

struct Packet { id: u32, payload: u64 }

fn main(): void {
    size := intrinsics.size_of[Packet]()
    align := intrinsics.align_of[Packet]()
    offset := intrinsics.offset_of[Packet]("payload")
    println("size ${size} align ${align} offset ${offset}")
}

offset_of requires a real field of the specialized type. The results are backend ABI facts, not a portable serialization format: do not persist a raw struct image and assume another target or a later compiler lays it out the same way.

row_fingerprint[T] supports typed dataframe result registration.

Ownership

is_rc_managed[T] exposes the compiler’s representation classification, which is what prelude code branches on before touching reference counts.

struct Point { x: int, y: int }

fn main(): void {
    println("int    ${intrinsics.is_rc_managed[int]()}")
    println("string ${intrinsics.is_rc_managed[string]()}")
    println("list   ${intrinsics.is_rc_managed[[]int]()}")
    println("point  ${intrinsics.is_rc_managed[Point]()}")
}

The retain, release, clone, and drop operations beneath that classification exist for compiler and prelude implementations:

  • retain[T] adds another managed ownership claim;
  • shallow_release removes one claim and can recursively destroy children;
  • drop_in_place[T] drops a value at a known initialized location;
  • is_unique reports whether a handle has exactly one claim;
  • raw stores express no high-level replacement semantics at all.

Application code should rely on ordinary assignment, arguments, return values, and scoped cleanup. Mixing these intrinsics with compiler-generated ownership leaks, double-releases, or uses freed storage.

Numeric and SIMD

The namespace exposes scalar numeric primitives and the WebAssembly v128 surface: lane splats, extraction and replacement, arithmetic, comparisons, shifts, bitwise operations, float operations, and selected conversions.

fn main(): void {
    mask := unsafe {
        lane := intrinsics.i8x16_splat(3u8)
        intrinsics.i8x16_bitmask(intrinsics.i8x16_eq(lane, lane))
    }
    println("mask ${mask}")
}

That prints 65535: sixteen equal lanes set sixteen mask bits. These names mirror backend instructions rather than portable algorithms, so wrap them behind a feature-appropriate API and keep a scalar path where the target contract needs one. Lane types, wrapping, signedness, and conversion semantics follow the named WebAssembly instruction, not the Numeric trait. v128_load and v128_store take raw pointers and therefore inherit the suspension rule above.

Bit reinterpretation

transmute[F, T] reinterprets a value’s bits between compiler-approved layouts. Same-width integer reinterpretation lowers today:

fn main(): void {
    unsigned := unsafe { intrinsics.transmute[i32, u32](7i32) }
    signed := unsafe { intrinsics.transmute[u32, i32](unsigned) }
    println("${unsigned} ${signed}")
}
Form Status
transmute[i32, u32], transmute[u32, i32] Lowers
transmute[f32, u32], transmute[f64, u64] and the reverse Declared in the prelude, but has no lowering path — the build fails with WasmIR could not lower continuation

Use the float type’s own conversion methods until float/integer bit casts lower.

Runtime plumbing

Scheduler, task, stream, reference-count, and host-ABI intrinsics support standard-library implementation. Their handle encodings and frame offsets are not stable application interfaces. Several are marked private in the prelude; others stay name-resolvable only because standard-library bodies need them, and that visibility is not a supported user contract.

Portability

Before exposing an intrinsic through application code, decide which layer owns the dependency:

Need Preferred layer
Safe reusable operation Standard-library or project wrapper
Target-specific optimization Small tested unsafe module
Compiler representation query Compiler/prelude implementation
Host capability Typed @host adapter

Read Unsafe before calling any unsafe intrinsic. Passing the type checker is not proof that a low-level invariant holds.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close