Compiler Intrinsics
Compiler intrinsics are low-level functions built directly into the Sun compiler. They provide direct access to memory operations, type information, and other primitives that cannot be expressed in regular Sun code.
Intrinsics are unsafe operations intended for implementing standard library primitives like Vec<T> and Map<K,V>. Most user code should use the standard library instead.
Generic Intrinsics
These intrinsics require a type parameter <T>.
_sizeof<T>()
Returns the byte size of type T as i64.
var size = _sizeof<i32>(); // 4
var size2 = _sizeof<i64>(); // 8
class Point { x: f64; y: f64; }
var pointSize = _sizeof<Point>(); // 16 (two 8-byte floats)_init<T>(ptr, args...)
Constructs an instance of type T at the memory location pointed to by ptr, forwarding args to the constructor.
// Allocate raw memory for a Point
var mem = _malloc(_sizeof<Point>());
// Construct Point at that location
_init<Point>(mem, 3.0, 4.0);
// mem now points to an initialized PointFor non-class types, _init is a no-op since primitives don't have constructors.
_load<T>(ptr, index)
Loads an element of type T from ptr at the given element index. Equivalent to ptr[index] in C.
// Assuming data points to an array of i32
var value = _load<i32>(data, 5); // Load data[5]_store<T>(ptr, index, value)
Stores value of type T at ptr[index].
// Store 42 at data[5]
_store<i32>(data, 5, 42);_ptr_as_raw<T>(ptr<T>)
Converts an owning ptr<T> to a non-owning raw_ptr<T> without transferring ownership. Similar to C++'s unique_ptr::get().
var owned: ptr<Point> = new Point(1.0, 2.0);
var raw: raw_ptr<Point> = _ptr_as_raw<Point>(owned);
// raw points to the same memory, but owned still manages lifetimeBe careful: the raw pointer becomes invalid if the owning pointer is freed or goes out of scope.
_is<T>(value)
Compile-time type check that returns true or false based on whether the type of value matches T. This intrinsic is always resolved at compile time — no runtime overhead.
T can be:
- A concrete type (e.g.,
i32,f64,Point) — exact type match - A type trait — checks type category (see below)
- An interface — checks if a class implements the interface
function example(x: i32, y: f64) {
_is<i32>(x); // true - exact type match
_is<i64>(x); // false - i32 is not i64
_is<_Integer>(x); // true - i32 is an integer
_is<_Float>(y); // true - f64 is a float
}Type Traits
Type traits are pseudo-types that categorize primitives:
| Trait | Matches |
|---|---|
_Integer | i8, i16, i32, i64, u8, u16, u32, u64 |
_Signed | i8, i16, i32, i64 |
_Unsigned | u8, u16, u32, u64 |
_Float | f32, f64 |
_Numeric | All integers and floats |
_Primitive | All numeric types plus bool |
Use in Generic Code
_is<T> is particularly useful in generic functions to branch based on type:
function processValue<T>(x: T) i32 {
if (_is<_Integer>(x)) {
// Integer-specific logic
return 1;
}
if (_is<_Float>(x)) {
// Float-specific logic
return 2;
}
return 0;
}
function main() i32 {
var a = processValue<i32>(42); // Returns 1
var b = processValue<f64>(3.14); // Returns 2
return a + b; // 3
}Since Sun uses monomorphization, each instantiation of processValue compiles to code with the dead branches eliminated by LLVM.
Interface Checks
For class types, _is<T> can check interface implementation:
interface IHashable {
function hash() i64;
}
class MyKey implements IHashable {
function hash() i64 { return 42; }
}
function example(key: MyKey) bool {
return _is<IHashable>(key); // true
}_convert<T>(value)
Converts a numeric value to another numeric type T, unchecked. Integers
truncate or extend (sign-extending from a signed source, zero-extending from an
unsigned one); integer-to-float and float-to-integer convert by value, dropping
any fraction; f32/f64 convert between each other. This is the only way to
narrow an integer — an assignment never does it on its own.
var ms: i64 = 200;
var t: i32 = _convert<i32>(ms); // 200
var f: f64 = _convert<f64>(t); // 200.0
var b: u8 = _convert<u8>(300); // 44 — the high bits are droppedUse the stdlib's safe_convert<T>(value) when the value may not fit; it throws
ConversionError instead of wrapping (see Standard Library).
_bitcast<T>(value)
Reinterprets the bits of a numeric value as another numeric type of the same
size — f32 ↔ u32/i32, f64 ↔ u64/i64. The sizes must match, which is
checked at compile time. Used by binary wire formats to read and write floats.
var bits: u64 = _bitcast<u64>(1.0); // 0x3FF0000000000000
var back: f64 = _bitcast<f64>(bits); // 1.0Non-Generic Intrinsics
These intrinsics operate on specific, fixed types.
_malloc(size)
Allocates size bytes of heap memory and returns a raw_ptr<i8>. This is a direct wrapper around the C library's malloc.
var mem = _malloc(1024); // Allocate 1024 bytesPrefer using allocators. Direct _malloc calls bypass Sun's memory management. Use HeapAllocator.alloc_raw(size) in standard library code.
_free(ptr)
Frees memory previously allocated with _malloc. Direct wrapper around C's free.
var mem = _malloc(1024);
// ... use memory ...
_free(mem);_address_of<T>(ref T)
Returns a raw_ptr<T> pointing to the memory location of a reference. Useful for interfacing with low-level operations that require a pointer (e.g., atomics).
var x: i32 = 42;
var ptr = _address_of<i32>(x); // raw_ptr<i32> to x's memoryThe returned pointer is only valid while the referenced variable is in scope. Do not store it beyond the variable's lifetime.
_to_ref<T>(raw_ptr<T>)
The reverse of _address_of: turns a raw_ptr<T> into a ref T so the
pointed-to value can be read, borrowed or assigned through like any reference.
Must be used inside unsafe { }. The borrow checker gives the reference the
lifetime of the pointer, so the pointer must stay valid for as long as the
reference is used.
var x: i32 = 1;
var p: raw_ptr<i32> = _address_of<i32>(x);
var r: ref i32 = unsafe { _to_ref<i32>(p); };
r = 2; // x is now 2_load_i64(ptr, index)
Loads an i64 from ptr at element offset index. A non-generic version of _load<i64>.
var value = _load_i64(data, 0); // Load first i64_store_i64(ptr, index, value)
Stores an i64 value at ptr[index]. A non-generic version of _store<i64>.
_store_i64(data, 0, 42); // Store 42 at first positionPrint Intrinsics
These intrinsics provide low-level output capabilities. They are used internally by the standard library's print.sun module.
_print_i32(value)
Prints an i32 value to stdout (no newline).
_print_i64(value)
Prints an i64 value to stdout (no newline).
_print_f64(value)
Prints an f64 value to stdout (no newline).
_print_newline()
Prints a newline character to stdout.
_print_bytes(ptr, len)
Prints len bytes from the memory at ptr to stdout.
var s: static_ptr<u8> = "hello";
_print_bytes(s.raw(), s.length()); // Prints "hello"The pointer and length of a static_ptr<T> come from its raw() and
length() methods, not from an intrinsic (see
Builtin Types).
_println_str(str)
Prints a static_ptr<u8> string followed by a newline.
_println_str("Hello, world!");Atomic Intrinsics
Atomic intrinsics provide lock-free thread-safe operations. Used internally by Mutex and other synchronization primitives.
_atomic_cmpxchg_i32(ptr, expected, desired)
Atomic compare-and-exchange on an i32. If the value at ptr equals expected, it is replaced with desired. Returns the old value at ptr.
var state: i32 = 0;
var old = _atomic_cmpxchg_i32(_address_of<i32>(state), 0, 1);
// If state was 0, it's now 1. old == 0_atomic_store_i32(ptr, value)
Atomically stores value at the memory location ptr.
_atomic_store_i32(_address_of<i32>(state), 0); // Reset to 0_atomic_load_i32(ptr)
Atomically loads the i32 value at ptr.
var current = _atomic_load_i32(_address_of<i32>(state));Bit Intrinsics
Wide multiplication and zero counts, the building blocks of multi-limb integer
arithmetic (BigUint) and table-driven float conversion.
_mul_hi_u64(a, b)
The high 64 bits of the 128-bit product a * b (the low 64 bits are just a * b).
var lo: u64 = a * b;
var hi: u64 = _mul_hi_u64(a, b);_ctlz_u64(x) / _cttz_u64(x)
Number of leading / trailing zero bits in a u64; both return 64 for 0.
var bits: u64 = 64 - _ctlz_u64(x); // bit length of xFutex Intrinsics
Linux-specific thread synchronization primitives. Used by Mutex for efficient blocking.
_futex_wait(ptr, expected)
Blocks the calling thread if the value at ptr equals expected. The thread sleeps until woken by _futex_wake.
_futex_wait(_address_of<i32>(state), 2); // Sleep if state == 2_futex_wake(ptr)
Wakes one thread blocked on _futex_wait at the given address.
_futex_wake(_address_of<i32>(state)); // Wake one waiterAtomic and futex intrinsics are platform-specific (Linux) and intended for implementing synchronization primitives. Use Mutex from the standard library instead.
Usage in Standard Library
The standard library uses intrinsics to implement generic containers. Here's a simplified example of how Vec<T> might use them:
class Vec<T> {
data: raw_ptr<i8>;
len: i64;
cap: i64;
alloc: HeapAllocator;
function get(index: i64) T {
return _load<T>(this.data, index);
}
function set(index: i64, value: T) void {
_store<T>(this.data, index, value);
}
function push(value: T) void {
if (this.len >= this.cap) {
this.grow();
}
_store<T>(this.data, this.len, value);
this.len = this.len + 1;
}
function grow() void {
var newCap = this.cap * 2;
var newData = this.alloc.alloc_raw(newCap * _sizeof<T>());
// Copy old data to new buffer...
_free(this.data);
this.data = newData;
this.cap = newCap;
}
}Summary Table
| Intrinsic | Parameters | Returns | Description |
|---|---|---|---|
_sizeof<T> | none | i64 | Byte size of type T |
_init<T> | ptr, args... | void | Construct T at ptr |
_load<T> | ptr, index | T | Load element at index |
_store<T> | ptr, index, value | void | Store element at index |
_ptr_as_raw<T> | ptr<T> | raw_ptr<T> | Get raw pointer without ownership transfer |
_is<T> | value | bool | Compile-time type check |
_convert<T> | value | T | Unchecked numeric conversion (truncate, extend, int ↔ float) |
_bitcast<T> | value | T | Reinterpret bits as a same-size numeric type |
_address_of<T> | ref T | raw_ptr<T> | Get pointer to referenced memory |
_to_ref<T> | raw_ptr<T> | ref T | Reference the pointed-to value (unsafe) |
_malloc | size: i64 | raw_ptr<i8> | Allocate heap memory |
_free | ptr | void | Free heap memory |
_load_i64 | ptr, index | i64 | Load i64 at index |
_store_i64 | ptr, index, value | void | Store i64 at index |
_print_i32 | value: i32 | void | Print i32 to stdout |
_print_i64 | value: i64 | void | Print i64 to stdout |
_print_f64 | value: f64 | void | Print f64 to stdout |
_print_newline | none | void | Print newline |
_print_bytes | ptr, len | void | Print bytes to stdout |
_println_str | static_ptr<u8> | void | Print string with newline |
_atomic_cmpxchg_i32 | ptr, expected, desired | i32 | Atomic compare-and-exchange |
_atomic_store_i32 | ptr, value | void | Atomic store |
_atomic_load_i32 | ptr | i32 | Atomic load |
_mul_hi_u64 | a: u64, b: u64 | u64 | High 64 bits of the 128-bit product |
_ctlz_u64 | x: u64 | u64 | Leading zero bits (64 for 0) |
_cttz_u64 | x: u64 | u64 | Trailing zero bits (64 for 0) |
_futex_wait | ptr, expected | void | Block until woken |
_futex_wake | ptr | void | Wake one blocked thread |