Skip to content

Commit c452fcb

Browse files
committed
refactor: unsafe fn kernel types drop closure wrappers; narrow unreachable allow
Make the per-site kernel type an unsafe fn pointer so unsafe #[target_feature] kernels coerce directly by name in the selector - no |i, o| unsafe { ... } closure wrappers. Safe kernels (scalar, NEON wrapper fns) coerce to unsafe fn pointers implicitly. The single dispatched call is wrapped in one narrow unsafe block whose SAFETY comment states the selector probed the required features. Move #[allow(unreachable_code)] from the enclosing function down onto the portable-default tail block itself, so it covers exactly the expression that is unreachable on aarch64 and nothing else. Verified with cargo check --target aarch64-unknown-linux-gnu on both crates: the aarch64 selector arms compile and the narrowed allow suppresses the warning with no other diagnostics. 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 d64f919 commit c452fcb

3 files changed

Lines changed: 65 additions & 52 deletions

File tree

encodings/fastlanes/src/bit_transpose/mod.rs

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub use validity::*;
3131
use vortex_buffer::CpuKernel;
3232

3333
/// Signature shared by all 1024-bit transpose kernels.
34-
type TransposeKernel = fn(&[u8; 128], &mut [u8; 128]);
34+
type TransposeKernel = unsafe fn(&[u8; 128], &mut [u8; 128]);
3535

3636
/// Base indices for the first 64 output bytes (lanes 0-7).
3737
/// Each entry indicates the starting input byte index for that output byte group.
@@ -50,63 +50,65 @@ const TRANSPOSE_8X8: u64 = 0x0000_0000_F0F0_F0F0;
5050
/// Transpose 1024-bits into FastLanes layout.
5151
///
5252
/// Dispatch to the best available implementation, selected once on first call.
53-
// The aarch64 arm returns unconditionally (NEON needs no probe), making the portable
54-
// tail unreachable there.
55-
#[allow(unreachable_code)]
5653
#[inline]
5754
pub fn transpose_bits(input: &[u8; 128], output: &mut [u8; 128]) {
5855
static KERNEL: CpuKernel<TransposeKernel> = CpuKernel::new();
59-
KERNEL.get(|| {
56+
let kernel = KERNEL.get(|| {
6057
#[cfg(target_arch = "x86_64")]
6158
{
6259
// VBMI is fastest
6360
if x86::has_vbmi() {
64-
// SAFETY: the probe above confirms VBMI.
65-
return |input, output| unsafe { x86::transpose_bits_vbmi(input, output) };
61+
return x86::transpose_bits_vbmi;
6662
}
6763
if x86::has_bmi2() {
68-
// SAFETY: the probe above confirms BMI2.
69-
return |input, output| unsafe { x86::transpose_bits_bmi2(input, output) };
64+
return x86::transpose_bits_bmi2;
7065
}
7166
}
67+
// NEON is architecturally guaranteed on aarch64, so it needs no probe.
7268
#[cfg(target_arch = "aarch64")]
73-
return |input, output| {
74-
// SAFETY: NEON is architecturally guaranteed on aarch64.
75-
unsafe { aarch64::transpose_bits_neon(input, output) }
76-
};
77-
scalar::transpose_bits_scalar
78-
})(input, output)
69+
return aarch64::transpose_bits_neon;
70+
// The aarch64 arm above returns unconditionally, making this portable default
71+
// unreachable there.
72+
#[allow(unreachable_code)]
73+
{
74+
scalar::transpose_bits_scalar
75+
}
76+
});
77+
// SAFETY: the selector only returns kernels that are safe or whose required CPU
78+
// features were probed before selection.
79+
unsafe { kernel(input, output) }
7980
}
8081

8182
/// Untranspose 1024-bits from FastLanes layout.
8283
///
8384
/// Dispatch untranspose to the best available implementation, selected once on first call.
84-
// The aarch64 arm returns unconditionally (NEON needs no probe), making the portable
85-
// tail unreachable there.
86-
#[allow(unreachable_code)]
8785
#[inline]
8886
pub fn untranspose_bits(input: &[u8; 128], output: &mut [u8; 128]) {
8987
static KERNEL: CpuKernel<TransposeKernel> = CpuKernel::new();
90-
KERNEL.get(|| {
88+
let kernel = KERNEL.get(|| {
9189
#[cfg(target_arch = "x86_64")]
9290
{
9391
// VBMI is fastest
9492
if x86::has_vbmi() {
95-
// SAFETY: the probe above confirms VBMI.
96-
return |input, output| unsafe { x86::untranspose_bits_vbmi(input, output) };
93+
return x86::untranspose_bits_vbmi;
9794
}
9895
if x86::has_bmi2() {
99-
// SAFETY: the probe above confirms BMI2.
100-
return |input, output| unsafe { x86::untranspose_bits_bmi2(input, output) };
96+
return x86::untranspose_bits_bmi2;
10197
}
10298
}
99+
// NEON is architecturally guaranteed on aarch64, so it needs no probe.
103100
#[cfg(target_arch = "aarch64")]
104-
return |input, output| {
105-
// SAFETY: NEON is architecturally guaranteed on aarch64.
106-
unsafe { aarch64::untranspose_bits_neon(input, output) }
107-
};
108-
scalar::untranspose_bits_scalar
109-
})(input, output)
101+
return aarch64::untranspose_bits_neon;
102+
// The aarch64 arm above returns unconditionally, making this portable default
103+
// unreachable there.
104+
#[allow(unreachable_code)]
105+
{
106+
scalar::untranspose_bits_scalar
107+
}
108+
});
109+
// SAFETY: the selector only returns kernels that are safe or whose required CPU
110+
// features were probed before selection.
111+
unsafe { kernel(input, output) }
110112
}
111113

112114
#[cfg(test)]

vortex-buffer/src/bit/select.rs

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -83,28 +83,30 @@ pub fn bit_select(bytes: &[u8], offset: usize, len: usize, nth: usize) -> Option
8383
///
8484
/// If `chunk_index < chunks.len()`, the target bit is inside that chunk and `remaining`
8585
/// is the rank *within* that chunk. Otherwise all chunks were consumed.
86-
type ScanChunks = fn(&[[u8; 64]], usize, usize) -> (usize, usize, usize);
86+
type ScanChunks = unsafe fn(&[[u8; 64]], usize, usize) -> (usize, usize, usize);
8787

88-
// The aarch64 arm returns unconditionally (NEON needs no probe), making the portable
89-
// tail unreachable there.
90-
#[allow(unreachable_code)]
9188
#[inline]
9289
fn scan_chunks(chunks: &[[u8; 64]], remaining: usize, pos: usize) -> (usize, usize, usize) {
9390
static KERNEL: CpuKernel<ScanChunks> = CpuKernel::new();
94-
KERNEL.get(|| {
91+
let kernel = KERNEL.get(|| {
9592
#[cfg(target_arch = "x86_64")]
9693
{
9794
if is_x86_feature_detected!("avx512f") && is_x86_feature_detected!("avx512vpopcntdq") {
98-
// SAFETY: the probe above confirms the required target features.
99-
return |chunks, remaining, pos| unsafe {
100-
scan_chunks_avx512_vpopcnt(chunks, remaining, pos)
101-
};
95+
return scan_chunks_avx512_vpopcnt;
10296
}
10397
}
10498
#[cfg(target_arch = "aarch64")]
10599
return scan_chunks_neon;
106-
scan_chunks_scalar
107-
})(chunks, remaining, pos)
100+
// The aarch64 arm above returns unconditionally (NEON needs no probe), making
101+
// this portable default unreachable there.
102+
#[allow(unreachable_code)]
103+
{
104+
scan_chunks_scalar
105+
}
106+
});
107+
// SAFETY: the selector only returns kernels that are safe or whose required CPU
108+
// features were probed before selection.
109+
unsafe { kernel(chunks, remaining, pos) }
108110
}
109111

110112
#[cfg(target_arch = "aarch64")]
@@ -282,25 +284,27 @@ fn scan_words_scalar(
282284

283285
// ── In-chunk select ─────────────────────────────────────────────────────
284286

285-
type SelectInChunk = fn(&[u8; 64], usize) -> usize;
287+
type SelectInChunk = unsafe fn(&[u8; 64], usize) -> usize;
286288

287289
/// Position of the `nth` set bit inside a 64-byte chunk (0-indexed).
288290
#[inline]
289291
fn select_in_chunk(chunk: &[u8; 64], nth: usize) -> usize {
290292
static KERNEL: CpuKernel<SelectInChunk> = CpuKernel::new();
291-
KERNEL.get(|| {
293+
let kernel = KERNEL.get(|| {
292294
#[cfg(target_arch = "x86_64")]
293295
{
294296
if is_x86_feature_detected!("avx512f")
295297
&& is_x86_feature_detected!("avx512vpopcntdq")
296298
&& is_x86_feature_detected!("avx512vbmi2")
297299
{
298-
// SAFETY: the probe above confirms the required target features.
299-
return |chunk, nth| unsafe { select_in_chunk_vbmi2(chunk, nth) };
300+
return select_in_chunk_vbmi2;
300301
}
301302
}
302303
select_in_chunk_scalar
303-
})(chunk, nth)
304+
});
305+
// SAFETY: the selector only returns kernels that are safe or whose required CPU
306+
// features were probed before selection.
307+
unsafe { kernel(chunk, nth) }
304308
}
305309

306310
#[cfg(target_arch = "x86_64")]
@@ -364,22 +368,24 @@ fn count_ones_chunk(chunk: &[u8; 64]) -> usize {
364368

365369
// ── In-word select ──────────────────────────────────────────────────────
366370

367-
type SelectInWord = fn(u64, usize) -> usize;
371+
type SelectInWord = unsafe fn(u64, usize) -> usize;
368372

369373
/// Position of the `nth` set bit inside a u64 (0-indexed, little-endian bit order).
370374
#[inline]
371375
fn select_in_word(word: u64, nth: usize) -> usize {
372376
static KERNEL: CpuKernel<SelectInWord> = CpuKernel::new();
373-
KERNEL.get(|| {
377+
let kernel = KERNEL.get(|| {
374378
#[cfg(target_arch = "x86_64")]
375379
{
376380
if is_x86_feature_detected!("bmi2") {
377-
// SAFETY: the probe above confirms the required target feature.
378-
return |word, nth| unsafe { select_in_word_bmi2(word, nth) };
381+
return select_in_word_bmi2;
379382
}
380383
}
381384
select_in_word_scalar
382-
})(word, nth)
385+
});
386+
// SAFETY: the selector only returns kernels that are safe or whose required CPU
387+
// features were probed before selection.
388+
unsafe { kernel(word, nth) }
383389
}
384390

385391
/// BMI2: deposit a single bit at the nth set-bit position, then count trailing zeros.

vortex-buffer/src/dispatch.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@
1717
//! * The **portable default** is the plain tail expression. Because the architecture
1818
//! arms return early, no `#[cfg(not(any(...)))]` negation is ever needed. An arm
1919
//! that returns unconditionally (e.g. NEON, architecturally guaranteed on aarch64)
20-
//! makes the tail unreachable on that architecture — silence it with
21-
//! `#[allow(unreachable_code)]` on the enclosing function.
20+
//! makes the tail unreachable on that architecture — wrap just the tail in an
21+
//! `#[allow(unreachable_code)]` block.
22+
//!
23+
//! When kernels are `unsafe` `#[target_feature]` functions, make `F` an
24+
//! `unsafe fn(...)` pointer type: the bare kernel names then coerce directly (no
25+
//! closure wrappers), and the one dispatched call is wrapped in `unsafe` with a
26+
//! SAFETY comment stating that the selector probed the required features.
2227
//!
2328
//! Races are benign: the slot only ever holds valid function pointers of the same
2429
//! type, and every candidate must compute the same result.

0 commit comments

Comments
 (0)