SIMD, arrays, and pointers
API summary (generated from the Beans source by npm run coverage): 7 types · 6 static methods · 31 instance methods.
This page covers the low-level builtins: SIMD vectors, fixed-size arrays, slices, and raw pointers. These are for tight, hardware-close code.
SIMD vectors
Section titled “SIMD vectors”SIMD (“single instruction, multiple data”) does the same math on several numbers at once. Each SIMD type packs a fixed number of equal-typed numbers, called lanes.
A SIMD type is named Simd + lane count + element type, for example:
Simd4i32: 4 lanes ofi32Simd16u8: 16 lanes ofu8Simd2f64: 2 lanes off64Simd4f32: 4 lanes off32
Element types are i8/i16/i32/i64, their u forms, and f32/f64. The
lane count is a power of two. Total width is 128 bits everywhere. A 256-bit width
needs CPU features, for example Simd8i32 needs --features +avx2.
Making a vector
Section titled “Making a vector”| Constructor | Notes |
|---|---|
splat(x) | every lane set to x |
of(...) | one value per lane |
load(ptr) | read lanes from memory |
load_unaligned(ptr) | read from memory that may not be aligned |
| Method | Returns | Notes |
|---|---|---|
lane(i) | element | read lane i |
with_lane(i, v) | vector | a copy with lane i set to v |
lane_count() | int | how many lanes |
Arithmetic works lane by lane. You can use the operators + - * / or the named
methods add, sub, mul, div, plus min and max.
Integer families also have bit_and, bit_or, bit_xor, bit_not, shl, and
shr.
Comparisons and masks
Section titled “Comparisons and masks”The comparisons eq, ne, lt, le, gt, ge return a mask (a per-lane
true/false). With a mask you can:
mask.select(a, b): pick fromawhere true, elsebmask.any_true(): is any lane truemask.all_true(): are all lanes true
Reductions and storing
Section titled “Reductions and storing”| Method | Notes |
|---|---|
sum() | add all lanes |
product() | multiply all lanes |
store(ptr) | write lanes to memory |
store_unaligned(ptr) | write to memory that may not be aligned |
A SIMD value cannot be a Map key: it has no Hash.
Fixed arrays: [T; N]
Section titled “Fixed arrays: [T; N]”[T; N] is a fixed inline array of N items of type T, where 1 <= N <= 4096.
Unlike a List, it is not a handle: it copies by value.
- Indexing is checked (panics if out of range).
- You can assign an element when the binding is
var. array.len() -> intgivesN.- Two arrays compare equal with
==. - You can loop over one with
for.
A stable for loop reads the inline array directly when its item binding cannot
escape. If the loop can change the array, the compiler keeps the previous safe
snapshot behavior. This is an optimizer choice, not new array syntax.
A list literal takes on fixed-array meaning from the declared type:
var lanes: [f32; 4] = [1, 2, 3, 4]lanes[0] = 9let n: int = lanes.len()Slice<T>
Section titled “Slice<T>”Slice<T> is a non-owning view: a pointer plus a length. It does not own the data
it points at. All of its operations require unsafe and are bounds-checked.
Slice.from_raw(ptr, len)
Slice<T>.get(int) -> TSlice<T>.set(int, T)Slice<T>.subslice(int, int) -> Slice<T>Slice<T>.as_ptr() -> RawPtr<T>Slice<T>.len() -> intSlice.from_raw(ptr, len)makes a slice overlenitems atptr. A non-empty slice rejects a null pointer.get(i)reads itemiandset(i, v)writes it;s[i]does the same by index.subslice(from, to)is a smaller view over the same storage.as_ptr()hands back the underlying pointer;len()is the number of items.- You can loop over a slice with
for.
unsafe { let view: Slice<i32> = Slice.from_raw(ptr, 4) let first: i32 = view.get(0)}RawPtr<T>
Section titled “RawPtr<T>”RawPtr<T> is a raw pointer to memory of type T. Every use is inside an
unsafe {} block. This is a brief list; see the unsafe guide for
the full detail and the rules you must follow.
Statics make or name a pointer:
RawPtr.alloc(count)RawPtr.alloc_aligned(count, align)RawPtr.null()RawPtr.from_address(u64)RawPtr.with_local(inout local, fn(RawPtr<T>))alloc(count)allocates room forcountitems;alloc_aligned(count, align)does the same with a chosen alignment.null()is a null pointer;from_address(u64)is a pointer at a raw address.with_local(inout local, fn(RawPtr<T>))runs your function with a raw pointer to a stack local. The pointer is valid only for that call.
The instance methods:
RawPtr<T>.read() -> TRawPtr<T>.write(T)RawPtr<T>.read_volatile() -> TRawPtr<T>.write_volatile(T)RawPtr<T>.offset(int) -> RawPtr<T>RawPtr<T>.address() -> u64RawPtr<T>.is_null() -> boolRawPtr<T>.element_size() -> intRawPtr<T>.element_align() -> intRawPtr<T>.copy_from(RawPtr<T>, int)RawPtr<T>.fill_zero(int)RawPtr<T>.free()RawPtr<T>.atomic_load() -> TRawPtr<T>.atomic_store(T)RawPtr<T>.atomic_fetch_add(T) -> TRawPtr<T>.atomic_compare_exchange(T, T) -> boolread/writemove a value in and out; the_volatilepair does the same without letting the compiler reorder or drop the access.offset(n)movesnitems along;address()is the raw address andis_null()tells you whether the pointer is null.element_size()andelement_align()are the size and alignment ofT.copy_from(src, n)copiesnitems fromsrc;fill_zero(n)zerosnitems;free()releases memory that came fromalloc.- The
atomic_*methods do sequentially consistent atomic access through the pointer.
unsafe { let p: RawPtr<i32> = RawPtr.alloc(4) p.write(7) let x: i32 = p.read() p.free()}RawSlice
Section titled “RawSlice”RawSlice is the untyped, two-word {pointer, length} value that backs a
Slice<T>. Slice<T> is the typed view you normally use; RawSlice is the
element-agnostic form the compiler and low-level code use for the same
pointer-plus-length shape. Like the other raw types it is only meaningful inside
unsafe, and size_of(RawSlice) folds to two pointers for the selected target.
Prefer Slice<T> in your own code, it carries the element type and
bounds-checks its accesses.
A worked example
Section titled “A worked example”Four f32 lanes multiplied and added, then stored through a RawPtr and read
back. Everything low-level here sits inside unsafe:
import std.io
fn main() { unsafe { let source: Simd4f32 = Simd4f32.of(1.0, 2.0, 3.0, 4.0) let scale: Simd4f32 = Simd4f32.splat(2.0) let result: Simd4f32 = source * scale + source io.println("lane0 {result.lane(0)} sum {result.sum()}")
let memory: RawPtr<f32> = RawPtr.alloc(4) result.store(memory) let view: Slice<f32> = Slice.from_raw(memory, 4) io.println("view len {view.len()} first {view.get(0)}") memory.free() }}See also
Section titled “See also”- The unsafe guide, the full model and every
RawPtrrule. - FFI, calling C code.
- Atomics, the safe
Atomic<T>type.