@@ -191,39 +191,46 @@ impl BitBuffer {
191191 }
192192 }
193193
194- /// Invokes `f` with indexes `0..len` collecting the boolean results into a new [`BitBuffer`].
194+ /// Collects `len` Boolean values from `f` into a packed [`BitBuffer`].
195195 ///
196- /// `f` is invoked exactly once per index, in ascending order, and the results are packed
197- /// with the baseline SIMD byte→bit instruction of the target.
196+ /// Calls `f` exactly once for each index in `0..len`, in order.
197+ ///
198+ /// # Code generation
199+ ///
200+ /// `collect_bool_words` calls `collect_bool_words_inline`, which selects a `pack_bool_word_*`
201+ /// kernel at compile time and passes it to `collect_bool_words_with`. That shared word loop
202+ /// materializes each full 64-value chunk as a byte-per-value `[bool; 64]`, then passes it to the
203+ /// selected kernel. For simple predicates, LLVM vectorizes the loop and removes the physical
204+ /// stack array. On AVX-512, it still combines the comparison masks, expands the result into 64
205+ /// `0` or `1` bytes with `vpbroadcastq` and `vmovdqu8`, then recreates the mask with `vptestmb`.
206+ /// [LLVM issue #219235](https://github.com/llvm/llvm-project/issues/219235) tracks replacing
207+ /// that round trip with a direct `kmovq` store. The conversion is per chunk. This method does
208+ /// not create a full-column byte buffer.
198209 ///
199210 /// # Performance
200211 ///
201- /// The packing is a few instructions per 64 bits, so evaluating `f` is usually the
202- /// bottleneck. In particular, a bounds-checked slice access in `f` (`|i| values[i] > x`)
203- /// blocks vectorization of the gather and can cost ~10x the packing itself. Since `f` only
204- /// ever sees indices `0..len`, callers reading from a slice with `len <= values.len()` may
205- /// soundly use `|i| unsafe { *values.get_unchecked(i) }`.
212+ /// `collect_bool_words_inline` and `collect_bool_words_with` can inline into the caller, so LLVM
213+ /// sees `f`, the fill loop, and the packing kernel together. A retained bounds check inside `f`
214+ /// can prevent vectorization. A caller that proves `len <= values.len()` can use
215+ /// `unsafe { *values.get_unchecked(i) }` because this method only passes indices in `0..len`.
206216 ///
207- /// Prefer this entry point for every predicate. Only switch to
208- /// [`Self::collect_bool_multiversioned`] after carefully checking that your specific `f`
209- /// meets its contract (a trivially cheap, bounds-check-free gather or comparison) —
210- /// ideally with a benchmark.
217+ /// Use this method for general predicates. Use [`Self::collect_bool_multiversioned`] only for
218+ /// the specialized predicates described there.
211219 #[ inline]
212220 pub fn collect_bool < F : FnMut ( usize ) -> bool > ( len : usize , f : F ) -> Self {
213221 BitBufferMut :: collect_bool ( len, f) . freeze ( )
214222 }
215223
216- /// Like [`Self::collect_bool`], but compiles the packing loop — with `f` inside it — once
217- /// per CPU feature level (AVX-512BW/AVX2/baseline) and selects a clone by runtime feature
218- /// detection.
224+ /// Collects Boolean values with a fill-and-pack loop selected for the current CPU.
225+ ///
226+ /// This has the same callback contract as [`Self::collect_bool`]. On x86-64,
227+ /// `collect_bool_words_multiversioned` selects `collect_bool_words_avx512`,
228+ /// `collect_bool_words_avx2`, or `collect_bool_words_inline` at runtime. Each wider version is a
229+ /// `#[target_feature]` function, so Rust cannot inline it into a caller compiled without those
230+ /// features.
219231 ///
220- /// Calling this asserts that `f` is small and simple enough (e.g. a bounds-check-free slice
221- /// gather or comparison) that duplicating it per feature level and paying a
222- /// `#[target_feature]` call boundary beats inlining it once into your function. For any
223- /// non-trivial `f` that assertion is false — the boundary deoptimizes the predicate — so
224- /// unless you have carefully checked (ideally benchmarked) that your specific `f`
225- /// qualifies, use [`Self::collect_bool`]. See
226- /// [`collect_bool_words_multiversioned`](crate::bit::collect_bool_words_multiversioned).
232+ /// Use this method only for a small, bounds-check-free predicate whose wider loop has been
233+ /// benchmarked. Use [`Self::collect_bool`] for general predicates.
227234 #[ inline]
228235 pub fn collect_bool_multiversioned < F : FnMut ( usize ) -> bool > ( len : usize , f : F ) -> Self {
229236 BitBufferMut :: collect_bool_multiversioned ( len, f) . freeze ( )
0 commit comments