Skip to content

Commit 563dbe4

Browse files
committed
refactor: replace cpu_dispatch! macro with macro-free CpuDispatch<F> slot
Drop the cpu_dispatch! macro in favor of a plain generic struct with the same steady-state codegen. CpuDispatch<F> is a self-patching function pointer slot: it is constructed pointing at a caller-written resolver of the same signature, which detects CPU features once, set()s the chosen kernel into the slot, and forwards the first call. Every later call is a single relaxed atomic load plus an indirect call, with no null check or LazyLock-style initialized branch (verified: the dispatch shim compiles to `mov SELECTED(%rip), %rax; jmp *%rax`). The pointer/fn-pointer transmutes are contained inside CpuDispatch, whose constructor statically asserts F is pointer-sized; call sites are entirely safe plain Rust with function-local statics. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DUFxXZLv4xxEqEYcoGWhfJ
1 parent 4e1a780 commit 563dbe4

4 files changed

Lines changed: 240 additions & 166 deletions

File tree

encodings/fastlanes/src/bit_transpose/mod.rs

Lines changed: 60 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ mod x86;
2828
mod validity;
2929

3030
pub use validity::*;
31+
#[cfg(target_arch = "x86_64")]
32+
use vortex_buffer::CpuDispatch;
33+
34+
/// Signature shared by all 1024-bit transpose kernels.
35+
#[cfg(target_arch = "x86_64")]
36+
type TransposeKernel = fn(&[u8; 128], &mut [u8; 128]);
3137

3238
/// Base indices for the first 64 output bytes (lanes 0-7).
3339
/// Each entry indicates the starting input byte index for that output byte group.
@@ -49,28 +55,35 @@ const TRANSPOSE_8X8: u64 = 0x0000_0000_F0F0_F0F0;
4955
#[inline]
5056
pub fn transpose_bits(input: &[u8; 128], output: &mut [u8; 128]) {
5157
#[cfg(target_arch = "x86_64")]
52-
return transpose_bits_x86(input, output);
53-
// SAFETY: NEON is architecturally guaranteed on aarch64.
58+
{
59+
static SELECTED: CpuDispatch<TransposeKernel> = CpuDispatch::new(resolve);
60+
61+
#[cold]
62+
fn resolve(input: &[u8; 128], output: &mut [u8; 128]) {
63+
// VBMI is fastest
64+
let kernel: TransposeKernel = if x86::has_vbmi() {
65+
// SAFETY: VBMI support was just detected.
66+
|input, output| unsafe { x86::transpose_bits_vbmi(input, output) }
67+
} else if x86::has_bmi2() {
68+
// SAFETY: BMI2 support was just detected.
69+
|input, output| unsafe { x86::transpose_bits_bmi2(input, output) }
70+
} else {
71+
scalar::transpose_bits_scalar
72+
};
73+
SELECTED.set(kernel);
74+
kernel(input, output);
75+
}
76+
77+
SELECTED.get()(input, output)
78+
}
5479
#[cfg(target_arch = "aarch64")]
55-
return unsafe { aarch64::transpose_bits_neon(input, output) };
80+
{
81+
// SAFETY: NEON is architecturally guaranteed on aarch64.
82+
unsafe { aarch64::transpose_bits_neon(input, output) }
83+
}
5684
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
57-
scalar::transpose_bits_scalar(input, output);
58-
}
59-
60-
#[cfg(target_arch = "x86_64")]
61-
vortex_buffer::cpu_dispatch! {
62-
fn transpose_bits_x86(input: &[u8; 128], output: &mut [u8; 128]);
63-
select {
64-
// VBMI is fastest
65-
if x86::has_vbmi() {
66-
// SAFETY: VBMI support was just detected.
67-
return |input, output| unsafe { x86::transpose_bits_vbmi(input, output) };
68-
}
69-
if x86::has_bmi2() {
70-
// SAFETY: BMI2 support was just detected.
71-
return |input, output| unsafe { x86::transpose_bits_bmi2(input, output) };
72-
}
73-
scalar::transpose_bits_scalar
85+
{
86+
scalar::transpose_bits_scalar(input, output)
7487
}
7588
}
7689

@@ -80,28 +93,35 @@ vortex_buffer::cpu_dispatch! {
8093
#[inline]
8194
pub fn untranspose_bits(input: &[u8; 128], output: &mut [u8; 128]) {
8295
#[cfg(target_arch = "x86_64")]
83-
return untranspose_bits_x86(input, output);
84-
// SAFETY: NEON is architecturally guaranteed on aarch64.
96+
{
97+
static SELECTED: CpuDispatch<TransposeKernel> = CpuDispatch::new(resolve);
98+
99+
#[cold]
100+
fn resolve(input: &[u8; 128], output: &mut [u8; 128]) {
101+
// VBMI is fastest
102+
let kernel: TransposeKernel = if x86::has_vbmi() {
103+
// SAFETY: VBMI support was just detected.
104+
|input, output| unsafe { x86::untranspose_bits_vbmi(input, output) }
105+
} else if x86::has_bmi2() {
106+
// SAFETY: BMI2 support was just detected.
107+
|input, output| unsafe { x86::untranspose_bits_bmi2(input, output) }
108+
} else {
109+
scalar::untranspose_bits_scalar
110+
};
111+
SELECTED.set(kernel);
112+
kernel(input, output);
113+
}
114+
115+
SELECTED.get()(input, output)
116+
}
85117
#[cfg(target_arch = "aarch64")]
86-
return unsafe { aarch64::untranspose_bits_neon(input, output) };
118+
{
119+
// SAFETY: NEON is architecturally guaranteed on aarch64.
120+
unsafe { aarch64::untranspose_bits_neon(input, output) }
121+
}
87122
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
88-
scalar::untranspose_bits_scalar(input, output);
89-
}
90-
91-
#[cfg(target_arch = "x86_64")]
92-
vortex_buffer::cpu_dispatch! {
93-
fn untranspose_bits_x86(input: &[u8; 128], output: &mut [u8; 128]);
94-
select {
95-
// VBMI is fastest
96-
if x86::has_vbmi() {
97-
// SAFETY: VBMI support was just detected.
98-
return |input, output| unsafe { x86::untranspose_bits_vbmi(input, output) };
99-
}
100-
if x86::has_bmi2() {
101-
// SAFETY: BMI2 support was just detected.
102-
return |input, output| unsafe { x86::untranspose_bits_bmi2(input, output) };
103-
}
104-
scalar::untranspose_bits_scalar
123+
{
124+
scalar::untranspose_bits_scalar(input, output)
105125
}
106126
}
107127

vortex-buffer/src/bit/select.rs

Lines changed: 72 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

44
use super::count_ones::align_offset_len;
5+
#[cfg(target_arch = "x86_64")]
6+
use crate::dispatch::CpuDispatch;
57

68
/// Returns the position of the `nth` set bit (0-indexed) within the logical range
79
/// `[offset, offset + len)` of the given byte slice.
@@ -84,34 +86,43 @@ pub fn bit_select(bytes: &[u8], offset: usize, len: usize, nth: usize) -> Option
8486
/// is the rank *within* that chunk. Otherwise all chunks were consumed.
8587
#[inline]
8688
fn scan_chunks(chunks: &[[u8; 64]], remaining: usize, pos: usize) -> (usize, usize, usize) {
87-
scan_chunks_impl(chunks, remaining, pos)
88-
}
89-
90-
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
91-
#[inline]
92-
fn scan_chunks_impl(chunks: &[[u8; 64]], remaining: usize, pos: usize) -> (usize, usize, usize) {
93-
scan_chunks_scalar(chunks, remaining, pos)
94-
}
95-
96-
#[cfg(target_arch = "x86_64")]
97-
crate::cpu_dispatch! {
98-
fn scan_chunks_impl(chunks: &[[u8; 64]], remaining: usize, pos: usize) -> (usize, usize, usize);
99-
select {
100-
if is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("avx512vpopcntdq") {
101-
// SAFETY: runtime detection guarantees the required target features.
102-
return |chunks, remaining, pos| unsafe {
103-
scan_chunks_avx512_vpopcnt(chunks, remaining, pos)
89+
#[cfg(target_arch = "x86_64")]
90+
{
91+
type ScanChunks = fn(&[[u8; 64]], usize, usize) -> (usize, usize, usize);
92+
static SELECTED: CpuDispatch<ScanChunks> = CpuDispatch::new(resolve);
93+
94+
#[cold]
95+
fn resolve(chunks: &[[u8; 64]], remaining: usize, pos: usize) -> (usize, usize, usize) {
96+
let kernel: ScanChunks = if is_x86_feature_detected!("avx512f")
97+
&& is_x86_feature_detected!("avx512vpopcntdq")
98+
{
99+
// SAFETY: runtime detection guarantees the required target features.
100+
|chunks, remaining, pos| unsafe {
101+
scan_chunks_avx512_vpopcnt(chunks, remaining, pos)
102+
}
103+
} else {
104+
scan_chunks_scalar
104105
};
106+
SELECTED.set(kernel);
107+
kernel(chunks, remaining, pos)
105108
}
106109

107-
scan_chunks_scalar
110+
SELECTED.get()(chunks, remaining, pos)
111+
}
112+
#[cfg(target_arch = "aarch64")]
113+
{
114+
scan_chunks_neon(chunks, remaining, pos)
115+
}
116+
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
117+
{
118+
scan_chunks_scalar(chunks, remaining, pos)
108119
}
109120
}
110121

111122
#[cfg(target_arch = "aarch64")]
112123
#[allow(clippy::cast_possible_truncation)] // u64 → usize is lossless on aarch64 (64-bit)
113124
#[inline]
114-
fn scan_chunks_impl(
125+
fn scan_chunks_neon(
115126
chunks: &[[u8; 64]],
116127
mut remaining: usize,
117128
mut pos: usize,
@@ -285,26 +296,32 @@ fn scan_words_scalar(
285296
// ── In-chunk select ─────────────────────────────────────────────────────
286297

287298
/// Position of the `nth` set bit inside a 64-byte chunk (0-indexed).
288-
#[cfg(not(target_arch = "x86_64"))]
289299
#[inline]
290300
fn select_in_chunk(chunk: &[u8; 64], nth: usize) -> usize {
291-
select_in_chunk_scalar(chunk, nth)
292-
}
293-
294-
#[cfg(target_arch = "x86_64")]
295-
crate::cpu_dispatch! {
296-
/// Position of the `nth` set bit inside a 64-byte chunk (0-indexed).
297-
fn select_in_chunk(chunk: &[u8; 64], nth: usize) -> usize;
298-
select {
299-
if is_x86_feature_detected!("avx512f")
300-
&& is_x86_feature_detected!("avx512vpopcntdq")
301-
&& is_x86_feature_detected!("avx512vbmi2")
302-
{
303-
// SAFETY: runtime detection guarantees the required target features.
304-
return |chunk, nth| unsafe { select_in_chunk_vbmi2(chunk, nth) };
301+
#[cfg(target_arch = "x86_64")]
302+
{
303+
static SELECTED: CpuDispatch<fn(&[u8; 64], usize) -> usize> = CpuDispatch::new(resolve);
304+
305+
#[cold]
306+
fn resolve(chunk: &[u8; 64], nth: usize) -> usize {
307+
let kernel: fn(&[u8; 64], usize) -> usize = if is_x86_feature_detected!("avx512f")
308+
&& is_x86_feature_detected!("avx512vpopcntdq")
309+
&& is_x86_feature_detected!("avx512vbmi2")
310+
{
311+
// SAFETY: runtime detection guarantees the required target features.
312+
|chunk, nth| unsafe { select_in_chunk_vbmi2(chunk, nth) }
313+
} else {
314+
select_in_chunk_scalar
315+
};
316+
SELECTED.set(kernel);
317+
kernel(chunk, nth)
305318
}
306319

307-
select_in_chunk_scalar
320+
SELECTED.get()(chunk, nth)
321+
}
322+
#[cfg(not(target_arch = "x86_64"))]
323+
{
324+
select_in_chunk_scalar(chunk, nth)
308325
}
309326
}
310327

@@ -371,22 +388,29 @@ fn count_ones_chunk(chunk: &[u8; 64]) -> usize {
371388
// ── In-word select ──────────────────────────────────────────────────────
372389

373390
/// Position of the `nth` set bit inside a u64 (0-indexed, little-endian bit order).
374-
#[cfg(not(target_arch = "x86_64"))]
375391
#[inline]
376392
fn select_in_word(word: u64, nth: usize) -> usize {
377-
select_in_word_scalar(word, nth)
378-
}
379-
380-
#[cfg(target_arch = "x86_64")]
381-
crate::cpu_dispatch! {
382-
/// Position of the `nth` set bit inside a u64 (0-indexed, little-endian bit order).
383-
fn select_in_word(word: u64, nth: usize) -> usize;
384-
select {
385-
if is_x86_feature_detected!("bmi2") {
386-
// SAFETY: runtime detection guarantees the required target feature.
387-
return |word, nth| unsafe { select_in_word_bmi2(word, nth) };
393+
#[cfg(target_arch = "x86_64")]
394+
{
395+
static SELECTED: CpuDispatch<fn(u64, usize) -> usize> = CpuDispatch::new(resolve);
396+
397+
#[cold]
398+
fn resolve(word: u64, nth: usize) -> usize {
399+
let kernel: fn(u64, usize) -> usize = if is_x86_feature_detected!("bmi2") {
400+
// SAFETY: runtime detection guarantees the required target feature.
401+
|word, nth| unsafe { select_in_word_bmi2(word, nth) }
402+
} else {
403+
select_in_word_scalar
404+
};
405+
SELECTED.set(kernel);
406+
kernel(word, nth)
388407
}
389-
select_in_word_scalar
408+
409+
SELECTED.get()(word, nth)
410+
}
411+
#[cfg(not(target_arch = "x86_64"))]
412+
{
413+
select_in_word_scalar(word, nth)
390414
}
391415
}
392416

0 commit comments

Comments
 (0)