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.
#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.
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#
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.
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.
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);↗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);↗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);↗Adds two vectors lane by lane.
Subtracts the lanes of b from those of a.
Multiplies two vectors lane by lane.
Divides the lanes of a by those of b.
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.
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.
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.
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.
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.
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.
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.
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.
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);↗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);↗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.
Types#
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);↗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);↗