fearless_simd 1.1.0

Safer and easier SIMD
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
// Copyright 2025 the Fearless_SIMD Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

#[derive(Clone, Copy, Debug)]
#[repr(C, align(16))]
#[expect(
    unnameable_types,
    reason = "This is used internally, but needs to be `pub` as it's used in a sealed interface"
)]
/// Wrapper for internal native vector types that gives them 128-bit alignment.
pub struct Aligned128<T>(pub T);

#[derive(Clone, Copy, Debug)]
#[repr(C, align(32))]
#[expect(
    unnameable_types,
    reason = "This is used internally, but needs to be `pub` as it's used in a sealed interface"
)]
/// Wrapper for internal native vector types that gives them 256-bit alignment.
pub struct Aligned256<T>(pub T);

#[derive(Clone, Copy, Debug)]
#[repr(C, align(64))]
#[expect(
    unnameable_types,
    reason = "This is used internally, but needs to be `pub` as it's used in a sealed interface"
)]
/// Wrapper for internal native vector types that gives them 512-bit alignment.
pub struct Aligned512<T>(pub T);

/// The actual `Debug` implementation for all `SimdBase` types. This only needs to be monomorphized once per element
/// type, rather than once per vector type.
#[inline(never)]
pub(crate) fn simd_debug_impl<Element: core::fmt::Debug>(
    f: &mut core::fmt::Formatter<'_>,
    type_name: &str,
    token: &dyn core::fmt::Debug,
    items: &[Element],
) -> core::fmt::Result {
    f.debug_struct(type_name)
        .field("val", &items)
        .field("simd", token)
        .finish()
}

/// Selects the input operands to be used for `slignr`/`vext`/etc. when computing a single output block for cross-block
/// "slide" operations. Extracts from [a : b].
#[inline(always)]
#[allow(clippy::allow_attributes, reason = "Only needed in some cfgs.")]
#[allow(dead_code, reason = "Only used in some cfgs.")]
pub(crate) fn cross_block_slide_blocks_at<const N: usize, Block: Copy>(
    a: &[Block; N],
    b: &[Block; N],
    out_idx: usize,
    shift_bytes: usize,
) -> [Block; 2] {
    const BLOCK_BYTES: usize = 16;
    let out_byte_start = out_idx * BLOCK_BYTES + shift_bytes;
    let lo_idx = out_byte_start.div_euclid(BLOCK_BYTES);
    let hi_idx = lo_idx + 1;
    // Concatenation is [a : b], so indices 0..N are from a, indices N..2N are from b
    let lo_block = if lo_idx < N { a[lo_idx] } else { b[lo_idx - N] };
    let hi_block = if hi_idx < N { a[hi_idx] } else { b[hi_idx - N] };
    [lo_block, hi_block]
}

/// Byte-shuffle controls for whole 16-, 32-, or 64-bit elements in a 128-bit vector.
#[allow(
    dead_code,
    reason = "Used only by backends with native byte-table shuffles"
)]
pub(crate) mod compact_128 {
    use super::Aligned128;

    #[allow(
        clippy::cast_possible_truncation,
        reason = "Byte indices are less than 16"
    )]
    const fn controls<const N: usize>(
        element_bytes: usize,
        expand: bool,
    ) -> [Aligned128<[u8; 16]>; N] {
        assert!(
            matches!(element_bytes, 2 | 4 | 8),
            "unsupported element size"
        );
        let lanes = 16 / element_bytes;
        assert!(
            N == 1 << lanes,
            "one control row is required per selection mask"
        );
        let mut table = [Aligned128([0xff; 16]); N];
        let mut mask = 0;
        while mask < N {
            let mut selected = 0;
            let mut lane = 0;
            while lane < lanes {
                if mask & (1 << lane) != 0 {
                    let source = if expand { selected } else { lane };
                    let destination = if expand { lane } else { selected };
                    let mut byte = 0;
                    while byte < element_bytes {
                        table[mask].0[destination * element_bytes + byte] =
                            (source * element_bytes + byte) as u8;
                        byte += 1;
                    }
                    selected += 1;
                }
                lane += 1;
            }
            mask += 1;
        }
        table
    }

    pub(crate) const COMPRESS_16: [Aligned128<[u8; 16]>; 256] = controls(2, false);
    pub(crate) const EXPAND_16: [Aligned128<[u8; 16]>; 256] = controls(2, true);
    pub(crate) const COMPRESS_32: [Aligned128<[u8; 16]>; 16] = controls(4, false);
    pub(crate) const EXPAND_32: [Aligned128<[u8; 16]>; 16] = controls(4, true);
    pub(crate) const COMPRESS_64: [Aligned128<[u8; 16]>; 4] = controls(8, false);
    pub(crate) const EXPAND_64: [Aligned128<[u8; 16]>; 4] = controls(8, true);
}

/// Dword-permutation controls for whole 32- or 64-bit elements in an AVX2 vector.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub(crate) mod compact_256 {
    use super::Aligned256;

    #[allow(
        clippy::cast_possible_truncation,
        clippy::cast_possible_wrap,
        reason = "Dword indices are less than eight"
    )]
    const fn controls<const N: usize>(
        element_dwords: usize,
        expand: bool,
    ) -> [Aligned256<[i32; 8]>; N] {
        assert!(matches!(element_dwords, 1 | 2), "unsupported element size");
        let lanes = 8 / element_dwords;
        assert!(
            N == 1 << lanes,
            "one control row is required per selection mask"
        );
        // VPERMD only uses the low three index bits. The -1 sentinel separately
        // identifies inactive lanes for zero/merge fill after the permutation.
        let mut table = [Aligned256([-1; 8]); N];
        let mut mask = 0;
        while mask < N {
            let mut selected = 0;
            let mut lane = 0;
            while lane < lanes {
                if mask & (1 << lane) != 0 {
                    let source = if expand { selected } else { lane };
                    let destination = if expand { lane } else { selected };
                    let mut dword = 0;
                    while dword < element_dwords {
                        // Adjacent dwords of a qword always move together.
                        table[mask].0[destination * element_dwords + dword] =
                            (source * element_dwords + dword) as i32;
                        dword += 1;
                    }
                    selected += 1;
                }
                lane += 1;
            }
            mask += 1;
        }
        table
    }

    // Full dword controls avoid the extra widening/validity work of packed byte
    // controls, particularly on Zen 1/3. Signed, unsigned and float ops share them.
    pub(crate) static COMPRESS_32: [Aligned256<[i32; 8]>; 256] = controls(1, false);
    pub(crate) static EXPAND_32: [Aligned256<[i32; 8]>; 256] = controls(1, true);
    pub(crate) static COMPRESS_64: [Aligned256<[i32; 8]>; 16] = controls(2, false);
    pub(crate) static EXPAND_64: [Aligned256<[i32; 8]>; 16] = controls(2, true);

    #[allow(
        clippy::cast_possible_truncation,
        clippy::cast_possible_wrap,
        reason = "Dword indices and offsets are at most eight"
    )]
    const fn shifts() -> [Aligned256<[i32; 8]>; 9] {
        let mut table = [Aligned256([0; 8]); 9];
        let mut offset = 0;
        while offset <= 8 {
            let mut lane = 0;
            while lane < 8 {
                table[offset].0[lane] = lane as i32 - offset as i32;
                lane += 1;
            }
            offset += 1;
        }
        table
    }

    // VPERMD uses the low three bits to rotate by the offset. Negative controls
    // (-8..=-1) have every byte's sign bit set, so the same row is a blend mask
    // for the prefix crossing between two YMM registers. Qwords use even offsets.
    pub(crate) static SHIFTS: [Aligned256<[i32; 8]>; 9] = shifts();
}

const fn compact_control_table(expand: bool) -> [u64; 256] {
    // Expansion adds a lane offset to each byte of the packed control. Use 0x80
    // for inactive lanes so these additions preserve the zeroing bit without
    // carrying into adjacent bytes. Every supported shuffle treats it as zero.
    let empty = if expand {
        0x8080_8080_8080_8080
    } else {
        u64::MAX
    };
    let mut table = [empty; 256];
    let mut mask = 0_usize;
    while mask < table.len() {
        let mut control = empty;
        let mut selected = 0_usize;
        let mut lane = 0_usize;
        while lane < 8 {
            if mask & (1 << lane) != 0 {
                let output_lane = if expand { lane } else { selected };
                control &= !(0xff_u64 << (output_lane * 8));
                let index = if expand { selected } else { lane };
                control |= (index as u64) << (output_lane * 8);
                selected += 1;
            }
            lane += 1;
        }
        table[mask] = control;
        mask += 1;
    }
    table
}

const fn compact_count_table() -> [u8; 256] {
    let mut table = [0; 256];
    let mut mask = 0_usize;
    while mask < table.len() {
        let mut bits = mask;
        let mut count = 0;
        while bits != 0 {
            if bits & 1 != 0 {
                count += 1;
            }
            bits >>= 1;
        }
        table[mask] = count;
        mask += 1;
    }
    table
}

const fn compact_splice_control_table() -> [u128; 9] {
    let mut table = [u128::MAX; 9];
    let mut low_count = 0;
    while low_count <= 8 {
        let mut control = u128::MAX;
        let mut lane = 0;
        while lane < low_count {
            control &= !(0xff_u128 << (lane * 8));
            control |= (lane as u128) << (lane * 8);
            lane += 1;
        }
        lane = 0;
        while lane < 8 {
            let output_lane = low_count + lane;
            if output_lane < 16 {
                control &= !(0xff_u128 << (output_lane * 8));
                control |= ((lane + 8) as u128) << (output_lane * 8);
            }
            lane += 1;
        }
        table[low_count] = control;
        low_count += 1;
    }
    table
}

#[allow(
    clippy::cast_possible_truncation,
    reason = "lane is bounded to 0..16 by the table-construction loop"
)]
const fn compact_16_splice_control_table() -> [Aligned256<[u8; 32]>; 17] {
    let mut table = [Aligned256([0x80; 32]); 17];
    let mut low_count = 0;
    while low_count <= 16 {
        let mut lane = 0;
        while lane < 16 {
            let output_lane = low_count + lane;
            if output_lane < 32 {
                table[low_count].0[output_lane] = lane as u8;
            }
            lane += 1;
        }
        low_count += 1;
    }
    table
}

const fn compact_prefix_mask_table() -> [Aligned256<[u8; 32]>; 33] {
    let mut table = [Aligned256([0; 32]); 33];
    let mut count = 0;
    while count <= 32 {
        let mut lane = 0;
        while lane < count {
            table[count].0[lane] = u8::MAX;
            lane += 1;
        }
        count += 1;
    }
    table
}

#[derive(Clone, Copy)]
#[repr(C, align(32))]
pub(crate) struct Compact32StitchControls {
    pub(crate) left_same: [u8; 32],
    pub(crate) left_cross: [u8; 32],
    pub(crate) right_same: [u8; 32],
    pub(crate) right_cross: [u8; 32],
}

/// Controls for concatenating two compacted 32-byte vectors entirely in registers.
///
/// For a first-vector length `count`, `left_*` shifts the second vector left by `count`
/// bytes and `right_*` shifts it right by `32 - count` bytes. Separate same-lane and
/// cross-lane controls account for AVX2 `vpshufb` operating independently in each
/// 128-bit lane.
#[allow(
    clippy::cast_possible_truncation,
    reason = "shuffle indices are reduced modulo 16"
)]
const fn compact_32_stitch_control_table() -> [Compact32StitchControls; 33] {
    const EMPTY: Compact32StitchControls = Compact32StitchControls {
        left_same: [0x80; 32],
        left_cross: [0x80; 32],
        right_same: [0x80; 32],
        right_cross: [0x80; 32],
    };

    let mut table = [EMPTY; 33];
    let mut count = 0;
    while count <= 32 {
        let mut lane = 0;
        while lane < 32 {
            if lane >= count {
                let source = lane - count;
                if lane / 16 == source / 16 {
                    table[count].left_same[lane] = (source % 16) as u8;
                } else {
                    table[count].left_cross[lane] = (source % 16) as u8;
                }
            }

            let source = lane + (32 - count);
            if source < 32 {
                if lane / 16 == source / 16 {
                    table[count].right_same[lane] = (source % 16) as u8;
                } else {
                    table[count].right_cross[lane] = (source % 16) as u8;
                }
            }
            lane += 1;
        }
        count += 1;
    }
    table
}

/// Eight-byte shuffle controls indexed by an eight-bit selection mask.
#[allow(
    dead_code,
    reason = "Used only by SIMD backends with byte-table shuffles"
)]
pub(crate) const COMPRESS_8_CONTROLS: [u64; 256] = compact_control_table(false);

/// Eight-byte inverse-shuffle controls indexed by an eight-bit selection mask.
#[allow(
    dead_code,
    reason = "Used only by SIMD backends with byte-table shuffles"
)]
pub(crate) const EXPAND_8_CONTROLS: [u64; 256] = compact_control_table(true);

/// Population counts indexed by an eight-bit selection mask.
#[allow(
    dead_code,
    reason = "Used only by SIMD backends with byte-table shuffles"
)]
pub(crate) const COMPACT_8_COUNTS: [u8; 256] = compact_count_table();

/// Shuffle controls that splice two independently compacted eight-byte halves.
#[allow(dead_code, reason = "Used only by x86 byte compression")]
pub(crate) const COMPACT_8_SPLICE_CONTROLS: [u128; 9] = compact_splice_control_table();

/// Shuffle controls that append a compacted 16-byte high lane after a compacted low lane.
#[allow(dead_code, reason = "Used only by AVX2 byte compression")]
pub(crate) const COMPACT_16_SPLICE_CONTROLS: [Aligned256<[u8; 32]>; 17] =
    compact_16_splice_control_table();

/// Shuffle controls that concatenate two independently compacted 32-byte vectors.
#[allow(dead_code, reason = "Used only by AVX2 64-byte compression")]
pub(crate) const COMPACT_32_STITCH_CONTROLS: [Compact32StitchControls; 33] =
    compact_32_stitch_control_table();

/// Byte prefix masks indexed by the number of active lanes.
#[allow(dead_code, reason = "Used only by x86 byte compression")]
pub(crate) const COMPACT_PREFIX_MASKS: [Aligned256<[u8; 32]>; 33] = compact_prefix_mask_table();