Modules

SIMD

moduleSource
macOSLinuxWindowswasm

#import "SIMD";

Fixed-width vectors of lanes with lane-wise arithmetic, comparison, min/max and memory access.

A SIMD_Array(N, T) holds N lanes of T and is a plain value: it is copied by assignment and no operation allocates. Every operation is an ordinary loop over the lanes. There are no hardware vector instructions behind them and no platform-specific path, so results are identical on every target; the compiler may still vectorise the loops. Integer arithmetic wraps on overflow. Comparisons return a vector of 1 and 0 lanes, and get_mask packs those into a bit mask.

ExampleOpen in playground ↗
a := set(4, cast(s32) 3);
b := a + set(4, cast(s32) 4);
assert(horizontal_sum(b) == 28);
assert(get_mask(b > a) == 0b1111);

Procedures#

($N: s64, factor: $T) -> SIMD_Array(N, T)

Returns a vector of N lanes with every lane equal to factor. The lane type is the type of factor, so cast literals (cast(s32) 7); a bare 7 makes s64 lanes. N must be a compile-time constant.

v := set(4, cast(s32) 7);
assert(v.data[0] == 7 && v.data[3] == 7);
↗
(base: *void, a: $T/SIMD_Array)

Writes the lanes of a to memory starting at byte address base, in lane order, as one contiguous block of size_of(a) bytes. No alignment is needed.

storage: [4] s32;
store(storage.data, set(4, cast(s32) 9));
assert(storage[0] == 9 && storage[3] == 9);
↗
(base: *void, $N: int, $F: Type) -> SIMD_Array(N, F)

Reads N lanes of type F from consecutive memory starting at base, lane 0 from the lowest address. Both the lane count and the lane type are explicit: load(pointer, 4, s32). No alignment is needed.

numbers: [4] s32 = .[10, 20, 30, 40];
v := load(numbers.data, 4, s32);
assert(v.data[0] == 10 && v.data[3] == 40);
↗
(base: *void, offsets: $O/SIMD_Array, $F: Type) -> SIMD_Array(offsets.count, F)

Gathers one F per lane from base + offsets.data[i], where each offset is a signed byte displacement (not an element index), and returns them in lane order. The result has as many lanes as offsets, whose lane type can be any integer type.

numbers: [4] s32 = .[10, 20, 30, 40];
offsets := set(3, cast(s64) 0);
offsets.data = .[12, 0, 4]; // byte offsets: 3 * size_of(s32), 0, 1 * size_of(s32)
v := load_from_offsets(numbers.data, offsets, s32);
assert(v.data[0] == 40 && v.data[1] == 10 && v.data[2] == 20);
↗
(a: $T/SIMD_Array, base: *void, offsets: $O/SIMD_Array)

Scatters the lanes of a to base + offsets.data[i], each offset a signed byte displacement. a and offsets must have the same lane count (checked at compile time).

numbers: [4] s32;
values := set(2, cast(s32) 0);
values.data = .[7, 8];
offsets := set(2, cast(s64) 0);
offsets.data = .[8, 0];
store_with_offsets(values, numbers.data, offsets);
assert(numbers[2] == 7 && numbers[0] == 8);
↗
($N: int, $I: Type, $STEP := 1) -> SIMD_Array(N, I)

Returns N lanes of type I holding 0, STEP, 2 * STEP, ...: lane i is i * STEP. STEP can be negative. Useful as the offsets of a gather or scatter. N must be positive.

indices := get_looping_indices(4, s32);
assert(indices.data[0] == 0 && indices.data[3] == 3);
stepped := get_looping_indices(4, s32, 3);
assert(stepped.data[2] == 6);
↗
operator+2 overloads
(a: $T/SIMD_Array, b: T) -> T

Adds two vectors lane by lane.

a := set(4, cast(s32) 3);
sum := a + set(4, cast(s32) 5);
assert(sum.data[0] == 8 && sum.data[3] == 8);
assert((a + cast(s32) 1).data[0] == 4);
assert((cast(s32) 1 + a).data[0] == 4);
↗
operator-3 overloads
(a: $T/SIMD_Array, b: T) -> T

Subtracts the lanes of b from those of a.

a := set(4, cast(s32) 3);
assert((a - set(4, cast(s32) 5)).data[0] == -2);
assert((a - cast(s32) 1).data[0] == 2);
assert((cast(s32) 10 - a).data[0] == 7);
↗
operator*2 overloads
(a: $T/SIMD_Array, b: T) -> T

Multiplies two vectors lane by lane.

a := set(4, cast(s32) 3);
assert((a * set(4, cast(s32) 5)).data[0] == 15);
assert((a * cast(s32) 2).data[3] == 6);
assert((cast(s32) 2 * a).data[3] == 6);
↗
operator/3 overloads
(a: $T/SIMD_Array, b: T) -> T

Divides the lanes of a by those of b.

a := set(4, cast(s32) 15);
assert((a / set(4, cast(s32) 5)).data[0] == 3);
assert((a / cast(s32) 2).data[0] == 7);
assert((cast(s32) 30 / a).data[0] == 2);
↗
operator==2 overloads
(a: $T/SIMD_Array, b: T) -> T

Compares lane by lane: each lane of the result is 1 where the lane of a is equal to the lane of b, else 0 (1.0 and 0.0 for float lanes). The result has the type of the operands, not bool, and is not an all-ones mask; pass it to get_mask to get one bit per lane.

a := set(4, cast(s32) 3);
b := a;
b.data[2] = 9;
same := a == b;
assert(same.data[0] == 1 && same.data[2] == 0);
assert(get_mask(a < b) == 4);
assert(get_mask(a == cast(s32) 3) == 15);
↗
operator!=2 overloads
(a: $T/SIMD_Array, b: T) -> T

Compares lane by lane: each lane of the result is 1 where the lane of a is different from the lane of b, else 0 (1.0 and 0.0 for float lanes). The result has the type of the operands, not bool, and is not an all-ones mask; pass it to get_mask to get one bit per lane.

operator>2 overloads
(a: $T/SIMD_Array, b: T) -> T

Compares lane by lane: each lane of the result is 1 where the lane of a is greater than the lane of b, else 0 (1.0 and 0.0 for float lanes). The result has the type of the operands, not bool, and is not an all-ones mask; pass it to get_mask to get one bit per lane.

operator>=2 overloads
(a: $T/SIMD_Array, b: T) -> T

Compares lane by lane: each lane of the result is 1 where the lane of a is greater than or equal to the lane of b, else 0 (1.0 and 0.0 for float lanes). The result has the type of the operands, not bool, and is not an all-ones mask; pass it to get_mask to get one bit per lane.

operator<2 overloads
(a: $T/SIMD_Array, b: T) -> T

Compares lane by lane: each lane of the result is 1 where the lane of a is less than the lane of b, else 0 (1.0 and 0.0 for float lanes). The result has the type of the operands, not bool, and is not an all-ones mask; pass it to get_mask to get one bit per lane.

operator<=2 overloads
(a: $T/SIMD_Array, b: T) -> T

Compares lane by lane: each lane of the result is 1 where the lane of a is less than or equal to the lane of b, else 0 (1.0 and 0.0 for float lanes). The result has the type of the operands, not bool, and is not an all-ones mask; pass it to get_mask to get one bit per lane.

min2 overloads
(a: $T/SIMD_Array, b: T) -> T

The lane-wise smaller of two vectors. Each lane is Basic.min of the two values, so with a NaN lane the answer depends on the operand order, as it does in scalar code.

a := set(4, cast(s32) 3);
b := set(4, cast(s32) 5);
assert(min(a, b).data[0] == 3);
assert(min(a, cast(s32) 4).data[0] == 3);
↗
max2 overloads
(a: $T/SIMD_Array, b: T) -> T

The lane-wise larger of two vectors. Each lane is Basic.max of the two values, so with a NaN lane the answer depends on the operand order, as it does in scalar code.

a := set(4, cast(s32) 3);
b := set(4, cast(s32) 5);
assert(max(a, b).data[0] == 5);
assert(max(a, cast(s32) 4).data[0] == 4);
↗
(a: $T/SIMD_Array) -> a.type

The sum of all lanes, added in lane order from the first lane, as a single value of the lane type. Integer lanes wrap on overflow; for floats the order of addition fixes the rounding.

v := set(4, cast(s32) 3);
assert(horizontal_sum(v) == 12);
↗
(a: $T/SIMD_Array, b: T) -> interleaved_result: T

Adds neighbouring lane pairs of a and of b and interleaves the sums: the result is [a0 + a1, b0 + b1, a2 + a3, b2 + b3, ...]. The lane count must be even (checked at compile time).

a := set(4, cast(s32) 0);
b := set(4, cast(s32) 0);
a.data = .[1, 2, 3, 4];
b.data = .[10, 20, 30, 40];
r := horizontal_add(a, b);
assert(r.data[0] == 3 && r.data[1] == 30 && r.data[2] == 7 && r.data[3] == 70);
↗
(a: $T/SIMD_Array) -> s64

Packs which lanes are nonzero into the bits of a number: bit i is set when lane i is not zero (-0.0 counts as zero). Pair it with a comparison to test many lanes at once.

v := set(4, cast(s32) 0);
v.data = .[0, 5, 0, 7];
assert(get_mask(v) == 0b1010);
assert(get_mask(v > cast(s32) 6) == 0b1000);
assert(get_mask(set(64, cast(u8) 1)) == -1);
↗

Types#

:: struct

A fixed number of lanes of one type, held in data: SIMD_Array(4, float32) is four floats in a row.

v: SIMD_Array(4, s32);
v.data = .[1, 2, 3, 4];
assert(size_of(SIMD_Array(4, s32)) == 16);
assert(horizontal_sum(v + cast(s32) 1) == 14);
↗
:: union

One 16-byte block viewed as every lane layout that fits it: _u8 (16 lanes) through _u64 (2 lanes), _float32 (4) and _float64 (2). All members share the same bytes, so writing one lane type and reading another reinterprets the bits.

block: Simd_128;
block._u32.data[0] = 0x04030201;
assert(block._u8.data[0] == 1 && block._u8.data[3] == 4);
assert(size_of(Simd_128) == 16);
↗