Every lane set to value.
#import "Extensions/SIMD";
Portable fixed-width vectors.
SIMD :: #import "Extensions/SIMD";
using SIMD;
text := "hello, wide world!";
// Where is the first space in these 16 bytes?
chunk := load(u8x16, text.data);
spaces := eq(chunk, #char " ");
if any(spaces) print("at %\n", first_true(spaces));A Vector(T, N) is N lanes of T, at most 64 bytes. Every operation works lane by lane, and its result is the same on every target: the interpreter, x86-64, arm64 and wasm. Compiled with optimization, the operations become the target's vector instructions. Integer arithmetic wraps. Comparisons give a Mask, which selects lanes, reduces to a bool, or becomes a bit per lane.
Module parameters#
- VECTOR_BYTES := 16
- The vector width code should aim for when it can choose: 16 bytes is SSE2, NEON and wasm simd128, the widest every target of those has. Set 32 when building for AVX2.
Procedures#
Lane i holds i.
V.N values starting at source, which needs no particular alignment.
The first count lanes from source (0 <= count <= V.N), the rest fill. For the tail of a
buffer that has fewer than V.N items left.
Arithmetic. Integer lanes wrap; use saturating_add/saturating_sub to clamp instead.
Integer division by a zero lane is an error, as for scalars.
The absolute value of each lane. The most negative integer stays itself, as it has no positive counterpart.
a + b per lane, clamped to T's range instead of wrapping.
a - b per lane, clamped to T's range instead of wrapping.
Bitwise operations, on integer lanes.
Each lane shifted by count bits, which must be less than the lane's width. >> keeps the sign
of signed lanes.
Comparisons, each giving a Mask. Float lanes compare as scalars do, so NaN is unequal to everything (ne holds, the others don't).
Bit i set where lane i is set.
A mask whose lane i is set where bit i of bits is.
How many lanes are set.
The lowest set lane, or -1 if none is.
The highest set lane, or -1 if none is.
Lane i from a where the mask is set, from b where it isn't.
Reductions: all lanes combined into one value.
Lane i is a[order[i]]. The order is a constant, so this becomes one shuffle instruction.
N lanes of a followed by b, starting at lane start: window(a, b, 0) is a, window(a, b, N) is
b. With a the previous block of a stream and b the current one, window(a, b, N - k) is the
current block moved k lanes later, with the end of the previous one in front.
A byte table lookup: lane i is table[indices[i]] when that index is below 16, else 0. This is one instruction on SSSE3, NEON and wasm simd128, and the base of fast byte classification: split each byte into nibbles and look both up.
Each lane converted with a cast: floats to integers truncate toward zero, integers to narrower integers keep the low bits.
The same bytes seen as another vector type of the same size.
low then high, as one vector of twice the lanes.
Bits of a u64, for the masks above.
The number of 0 bits below the lowest 1 bit; 64 for 0.
The number of 0 bits above the highest 1 bit; 64 for 0.
What the processor has, less whatever limit_vector_tier took away. Detected on the first call.
The best tier the processor offers.
Pretend the processor has nothing above tier: for tests and for comparing versions on one
machine. It applies to procedures picked afterwards, so call it before the first call of
anything dispatched.
The best version the processor can run: the highest tier with a version given, else baseline.
A version that needs a feature is compiled with #target_features and listed under its tier.
table = pick_target(count_base, sse42 = count_sse42, avx2 = count_avx2);Not run: the example is marked ignore.
Types#
- lanes: [N] T
One lane per lane of Vector(T, N): all ones where a comparison held, all zeros where it didn't. The lanes are as wide as T, so masks and vectors line up the way the hardware wants.
- lanes: [N] #run lane_bits(T)
- sse42: bool
- x64: SSE4.2 (and everything below it).
- avx2: bool
- x64: AVX2, and the operating system saves the vector registers it uses.
- avx512: bool
- x64: AVX-512 F, DQ, BW and VL, and the operating system saves the registers they use.
- neon: bool
- arm64: NEON, which every arm64 processor has.
- sve: bool
- arm64: SVE, reported only where it is cheap to ask (Linux).
The best vector instruction set a processor has, in the order code is picked: each tier has everything below it. On arm64 the baseline is NEON.
- BASELINE
- SSE42
- AVX2
- AVX512
- SVE