refactor(lexer): remove cpu_select! macro (#28773)

Pure refactor - no changes to codegen.

Now that `DynCpu` has all its variants on every platform (#28769), and all arms are compiled everywhere (#28772), `cpu_select!` macro does nothing which a plain `match` doesn't.

Replace it with `match to_dyn(cpu) { ... }` everywhere, and remove the macro.

A `match` is just Rust, so there's no macro syntax to learn, and rustfmt and Rust Analyzer handle it fully. Arms which do the same thing can also be combined with `_`, which `cpu_select!` didn't allow:

```rust
// Before
cpu_select!(cpu, {
    Avx2(cpu) => bm_any_avx2(bm, nw, cpu),
    Neon(_) => bm_any_generic(bm, nw),
    Generic(_) => bm_any_generic(bm, nw),
})

// After
match to_dyn(cpu) {
    DynCpu::Avx2(cpu) => bm_any_avx2(bm, nw, cpu),
    _ => bm_any_generic(bm, nw),
}
```

This uses the `to_dyn` function, not the `Cpu::to_dyn` method. The method is an extra `#[inline(always)]` function around `to_dyn`, which rustc's MIR inliner can't inline into code which is generic over the CPU type. That changes the codegen of some callers. Those codegen changes may be incidental and have no perf impact, but impossible to know for sure without benchmarking, so using `to_dyn` is the conservative choice to avoid any accidental perf regressions.
This commit is contained in:
overlookmotel committed 2026-11-03 18:02:35 +00:00
1 parent 187b995274
commit 84a5cd9906
13 files changed
+143 -389

No files matched your search

+23 -7
View File
@@ -13,7 +13,25 @@
//! It's the best one which is known at compile time to be available.
//!
//! Code which has SIMD implementations takes a type implementing [`Cpu`],
//! and uses [`cpu_select!`] to pick an implementation.
//! and picks an implementation with a `match` on [`to_dyn`]:
//!
//! ```ignore
//! match to_dyn(cpu) {
//! DynCpu::Avx2(cpu) => classify_avx2(self, cpu),
//! DynCpu::Neon(cpu) => classify_neon(self, cpu),
//! DynCpu::Generic(_) => classify_generic(self),
//! }
//! ```
//!
//! [`DynCpu`] has all 3 variants on every platform. On a platform which doesn't have
//! a feature set, its type is uninhabited, so its arm can never run, and the compiler removes it.
//! The arm is still needed, because the type's field is private, so outside this module
//! rustc doesn't know that it's uninhabited.
//!
//! Use the [`to_dyn`] function, not the [`Cpu::to_dyn`] method. The method is an extra
//! `#[inline(always)]` function around it, and that changes the code which some callers
//! compile to, because rustc's MIR inliner treats them differently.
//!
//! [`#[cpu_version]`] marks each implementation with the CPU feature set it's for.
//! [`#[cpu_multiversion]`] turns a function into one with a separate entry point for each CPU feature set.
//! [`#[cpu_impl]`] marks methods which only work on one CPU feature set's platform.
@@ -23,7 +41,7 @@
//! * Given a specific type (e.g. `Avx2`): the choice is made at compile time.
//! * Given a [`DynCpu`]: the choice is made at runtime.
//!
//! Functions which use [`cpu_select!`] typically take a generic `impl Cpu`.
//! Functions which pick an implementation typically take a generic `impl Cpu`.
//! This compiles a separate version of the function for each [`Cpu`] type that callers use with it.
//!
//! [`DynCpu`] also implements [`Cpu`], so functions that receive an `impl Cpu`
@@ -66,13 +84,9 @@ use std::{convert::Infallible as Never, mem};
pub(crate) use oxc_lexer_macros::{cpu_impl, cpu_multiversion, cpu_version};
mod select_macro;
#[cfg(test)]
mod tests;
pub(crate) use select_macro::cpu_select;
/// Trait for types representing a CPU feature set.
///
/// Implemented by all types representing specific CPU feature sets, and by [`DynCpu`].
@@ -143,7 +157,9 @@ impl CpuPrivate for DynCpu {
///
/// Same as [`Cpu::to_dyn`], but this works in `const` code too.
/// A `const fn` can't call a trait method on a generic type, so it can't call [`Cpu::to_dyn`].
/// [`cpu_select!`] and [`#[cpu_multiversion]`] use this function, so they work in `const` code.
/// [`#[cpu_multiversion]`] uses this function, so it works in `const` code.
/// Code which picks an implementation uses it too: `match to_dyn(cpu) { ... }`.
/// See the module docs for why it's preferred to [`Cpu::to_dyn`] there.
///
/// Only [`DynCpu`] needs `cpu`. It's taken for all types so that this can only be called
/// by code which already has one of these types.
-248
View File
@@ -1,248 +0,0 @@
/// Macro for either static or dynamic feature selection.
///
/// Arms use normal `match` syntax, but there must be exactly these 3 arms, in this order:
///
/// - `Avx2(...)`
/// - `Neon(...)`
/// - `Generic(...)`
///
/// All 3 arms are compiled and type-checked on every platform.
/// [`DynCpu`] has all 3 variants on every platform. On a platform which doesn't have
/// a feature set, its type is uninhabited, so its arm can never run, and the compiler removes it.
/// e.g. `Avx2` on aarch64, or [`Generic`] on aarch64 with Neon enabled at compile time.
/// The arm is still needed, because the type's field is private, so outside the module which
/// defines the type, rustc doesn't know that it's uninhabited.
///
/// Each arm binds its CPU feature set type (e.g. `Avx2`), so it can be passed on.
/// Fallback arms (e.g. `_ => ...`) and match guards are not allowed.
///
/// Requiring all 3 arms means every call site says what to do on every platform.
/// It also makes the `match` exhaustive on every platform, without a fallback arm.
///
/// # Examples
///
/// ## Static selection
///
/// ```ignore
/// use crate::cpu::{Cpu, cpu_select};
///
/// fn classify(&mut self, cpu: impl Cpu) {
/// // Compiles down to a single arm (no runtime branch)
/// cpu_select!(cpu, {
/// Avx2(cpu) => classify_avx2(self, cpu),
/// Neon(cpu) => classify_neon(self, cpu),
/// Generic(_) => classify_generic(self),
/// });
/// }
/// ```
///
/// ## Dynamic selection
///
/// ```ignore
/// use crate::cpu::{DynCpu, cpu_select};
///
/// fn classify(cpu: DynCpu) {
/// // Runtime match over options available on the current platform
/// cpu_select!(cpu, {
/// Avx2(cpu) => classify_avx2(self, cpu),
/// Neon(cpu) => classify_neon(self, cpu),
/// Generic(_) => classify_generic(self),
/// });
/// }
/// ```
///
/// ## Expansion
///
/// Macro expands to:
///
/// ```ignore
/// match to_dyn(cpu) {
/// DynCpu::Avx2(cpu) => classify_avx2(self, cpu),
/// DynCpu::Neon(cpu) => classify_neon(self, cpu),
/// DynCpu::Generic(_) => classify_generic(self),
/// }
/// ```
///
/// ## Implementation
///
/// Tokens of the arms are consumed one at a time, and copied into `arms`.
/// `Avx2(...) =>` is rewritten to `DynCpu::Avx2(...) =>`, and the same for `Neon` and `Generic`.
/// All other tokens are copied unchanged, so the arm bodies are parsed by the `match` itself.
///
/// State is a set of named slots:
///
/// * `rest` is the tokens which haven't been consumed yet.
/// * `seen` lists the arms which have been seen so far (e.g. `[ avx2, neon ]`).
/// * `arms` is the tokens of the rewritten arms so far.
/// * `cpu` is the expression being matched on.
///
/// The slots which tell the rules apart (`rest`, then `seen`) come first, and `cpu`, which is only
/// passed on, comes last. So when a rule doesn't match, the compiler finds out early.
///
/// Each arm's rule only matches if the arms before it have been seen, and it hasn't.
/// So the arms must come in that order, once each. Anything else is a compile error.
///
/// [`DynCpu`]: super::DynCpu
/// [`Generic`]: super::Generic
macro_rules! cpu_select {
// Entry point
($cpu:expr, { $($arms:tt)* }) => {
crate::cpu::cpu_select!(
@munch
rest = [ $($arms)* ]
seen = []
arms = []
cpu = $cpu
)
};
// `Avx2` arm. Must be first.
(
@munch
rest = [
Avx2($($pat:tt)*) =>
$($rest:tt)*
]
seen = []
arms = [ $($arms:tt)* ]
cpu = $cpu:tt
) => {
crate::cpu::cpu_select!(
@munch
rest = [ $($rest)* ]
seen = [ avx2 ]
arms = [
$($arms)*
crate::cpu::DynCpu::Avx2($($pat)*) =>
]
cpu = $cpu
)
};
// `Neon` arm. Must follow `Avx2` arm.
(
@munch
rest = [
Neon($($pat:tt)*) =>
$($rest:tt)*
]
seen = [ avx2 ]
arms = [ $($arms:tt)* ]
cpu = $cpu:tt
) => {
crate::cpu::cpu_select!(
@munch
rest = [ $($rest)* ]
seen = [ avx2, neon ]
arms = [
$($arms)*
crate::cpu::DynCpu::Neon($($pat)*) =>
]
cpu = $cpu
)
};
// `Generic` arm. Must follow `Neon` arm.
(
@munch
rest = [
Generic($($pat:tt)*) =>
$($rest:tt)*
]
seen = [ avx2, neon ]
arms = [ $($arms:tt)* ]
cpu = $cpu:tt
) => {
crate::cpu::cpu_select!(
@munch
rest = [ $($rest)* ]
seen = [ avx2, neon, generic ]
arms = [
$($arms)*
crate::cpu::DynCpu::Generic($($pat)*) =>
]
cpu = $cpu
)
};
// Any other `=>` - an arm which is out of order, repeated, not one of the 3 names,
// a fallback (e.g. `_ =>`), or has a match guard
(
@munch
rest = [
=>
$($_rest:tt)*
]
$($_any:tt)*
) => {
crate::cpu::cpu_select!(@error)
};
// Any other token.
//
// Copy 1 token, and go round again.
//
// The arm rules only consume `Avx2(...) =>` etc, not the arm's body.
// So the remaining tokens can't all be copied in one go. They start with the body,
// and a `Neon(...) =>` which still needs rewriting may come after it.
//
// `macro_rules!` can't match "all tokens up to the next `Neon`",
// because `$($x:tt)*` would also match `Neon`.
// The only way to find the next arm is to step through the tokens.
//
// Consuming the body in the arm rules would avoid this, but would need 2 rules for each arm
// (block body, or expression followed by `,`), and would reject comma-free bodies like `unsafe { ... }`.
//
// A delimited group (e.g. a block body) is a single token, so its contents are never inspected.
// This rule must come after the arm rules and the error rule above,
// because `$next:tt` also matches `Avx2` and `=>`.
(
@munch
rest = [
$next:tt
$($rest:tt)*
]
seen = $seen:tt
arms = [ $($arms:tt)* ]
cpu = $cpu:tt
) => {
crate::cpu::cpu_select!(
@munch
rest = [ $($rest)* ]
seen = $seen
arms = [
$($arms)*
$next
]
cpu = $cpu
)
};
// All tokens consumed, and all 3 arms seen
(
@munch
rest = []
seen = [ avx2, neon, generic ]
arms = [ $($arms:tt)* ]
cpu = $cpu:tt
) => {
match crate::cpu::to_dyn($cpu) {
$($arms)*
}
};
// All tokens consumed, but an arm is missing.
// The rules above handle every other case, so this rule matches anything.
(@munch $($_any:tt)*) => {
crate::cpu::cpu_select!(@error)
};
// Error for any arms which don't follow the rules
(@error) => {
compile_error!(
"`cpu_select!` must have arms `Avx2(...)`, `Neon(...)` and `Generic(...)`, in that order, once each"
)
};
}
pub(crate) use cpu_select;
+36 -36
View File
@@ -1,6 +1,6 @@
use std::panic::Location;
use super::{Avx2, BASELINE, Cpu, DynCpu, Neon, cpu_multiversion, cpu_select, cpu_version, to_dyn};
use super::{Avx2, BASELINE, Cpu, DynCpu, Neon, cpu_multiversion, cpu_version, to_dyn};
use crate::simd::{Vec128, Vec256};
#[cpu_multiversion]
@@ -323,11 +323,11 @@ fn const_fn_at_runtime() {
assert_eq!(Counter::of(5, cpu).0, 5);
assert_eq!(Counter::double_of(6, cpu), 12);
cpu_select!(cpu, {
Avx2(cpu) => assert_eq!(add_one(1, cpu), 2),
Neon(cpu) => assert_eq!(add_one(1, cpu), 2),
Generic(cpu) => assert_eq!(add_one(1, cpu), 2),
});
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => assert_eq!(add_one(1, cpu), 2),
DynCpu::Neon(cpu) => assert_eq!(add_one(1, cpu), 2),
DynCpu::Generic(cpu) => assert_eq!(add_one(1, cpu), 2),
}
}
#[test]
@@ -394,11 +394,11 @@ fn double_generic(x: u32) -> u32 {
/// `double_avx2` and `double_neon` have a parameter which proves the CPU supports the feature set,
/// so calling them is safe.
fn double(x: u32, cpu: impl Cpu) -> u32 {
cpu_select!(cpu, {
Avx2(cpu) => double_avx2(x, cpu),
Neon(cpu) => double_neon(x, cpu),
Generic(_) => double_generic(x),
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => double_avx2(x, cpu),
DynCpu::Neon(cpu) => double_neon(x, cpu),
DynCpu::Generic(_) => double_generic(x),
}
}
#[cpu_version(Avx2)]
@@ -418,11 +418,11 @@ fn triple_neon(x: u32, _cpu: Neon) -> u32 {
/// `triple_avx2` and `triple_neon` are `#[inline(always)]`, so have no `#[target_feature]`,
/// and calling them is safe.
fn triple(x: u32, cpu: impl Cpu) -> u32 {
cpu_select!(cpu, {
Avx2(cpu) => triple_avx2(x, cpu),
Neon(cpu) => triple_neon(x, cpu),
Generic(_) => x * 3,
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => triple_avx2(x, cpu),
DynCpu::Neon(cpu) => triple_neon(x, cpu),
DynCpu::Generic(_) => x * 3,
}
}
/// A `cpu` parameter doesn't have to be the last parameter.
@@ -439,11 +439,11 @@ fn quadruple_neon(_cpu: Neon, x: u32) -> u32 {
}
fn quadruple(x: u32, cpu: impl Cpu) -> u32 {
cpu_select!(cpu, {
Avx2(cpu) => quadruple_avx2(cpu, x),
Neon(cpu) => quadruple_neon(cpu, x),
Generic(_) => x * 4,
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => quadruple_avx2(cpu, x),
DynCpu::Neon(cpu) => quadruple_neon(cpu, x),
DynCpu::Generic(_) => x * 4,
}
}
/// A vector parameter proves the CPU supports the feature set, in place of a `cpu` parameter.
@@ -460,11 +460,11 @@ fn is_zero_neon(v: Vec128<Neon>) -> bool {
}
fn is_zero(cpu: impl Cpu) -> bool {
cpu_select!(cpu, {
Avx2(cpu) => is_zero_avx2(Vec128::splat(0, cpu)),
Neon(cpu) => is_zero_neon(Vec128::splat(0, cpu)),
Generic(_) => true,
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => is_zero_avx2(Vec128::splat(0, cpu)),
DynCpu::Neon(cpu) => is_zero_neon(Vec128::splat(0, cpu)),
DynCpu::Generic(_) => true,
}
}
/// A vector parameter doesn't have to be the last parameter.
@@ -483,11 +483,11 @@ fn add_if_zero_neon(x: u32, v: Vec256<Neon>, y: u32) -> u32 {
}
fn add_if_zero(x: u32, y: u32, cpu: impl Cpu) -> u32 {
cpu_select!(cpu, {
Avx2(cpu) => add_if_zero_avx2(x, Vec256::splat(0, cpu), y),
Neon(cpu) => add_if_zero_neon(x, Vec256::splat(0, cpu), y),
Generic(_) => x + y,
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => add_if_zero_avx2(x, Vec256::splat(0, cpu), y),
DynCpu::Neon(cpu) => add_if_zero_neon(x, Vec256::splat(0, cpu), y),
DynCpu::Generic(_) => x + y,
}
}
/// An `unsafe` function isn't wrapped, so it has `#[target_feature]`, as well as being unsafe.
@@ -504,13 +504,13 @@ unsafe fn halve_neon(x: u32, _cpu: Neon) -> u32 {
}
fn halve(x: u32, cpu: impl Cpu) -> u32 {
cpu_select!(cpu, {
match to_dyn(cpu) {
// SAFETY: `Avx2` type guarantees the CPU supports the features `halve_avx2` uses
Avx2(cpu) => unsafe { halve_avx2(x, cpu) },
DynCpu::Avx2(cpu) => unsafe { halve_avx2(x, cpu) },
// SAFETY: `Neon` type guarantees the CPU supports the features `halve_neon` uses
Neon(cpu) => unsafe { halve_neon(x, cpu) },
Generic(_) => x / 2,
})
DynCpu::Neon(cpu) => unsafe { halve_neon(x, cpu) },
DynCpu::Generic(_) => x / 2,
}
}
/// Tests of functions which `cpu_version!` wraps in a safe function, for one CPU feature set.
+5 -6
View File
@@ -1,5 +1,5 @@
use crate::{
cpu::{Avx2, Cpu, cpu_multiversion, cpu_select, cpu_version},
cpu::{Avx2, Cpu, DynCpu, cpu_multiversion, cpu_version, to_dyn},
simd::Vec256,
};
@@ -149,11 +149,10 @@ pub(super) unsafe fn bm_clear_range(bm: *mut u64, a: usize, b: usize, _cpu: impl
#[cpu_multiversion]
#[inline(always)]
pub(super) unsafe fn bm_any(bm: *const u64, nw: usize, cpu: impl Cpu) -> bool {
cpu_select!(cpu, {
Avx2(cpu) => bm_any_avx2(bm, nw, cpu),
Neon(_) => bm_any_generic(bm, nw),
Generic(_) => bm_any_generic(bm, nw),
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => bm_any_avx2(bm, nw, cpu),
_ => bm_any_generic(bm, nw),
}
}
/// [`bm_any`] for AVX2.
@@ -1,5 +1,5 @@
use crate::{
cpu::{Avx2, BASELINE, Cpu, cpu_multiversion, cpu_select, cpu_version},
cpu::{Avx2, BASELINE, Cpu, DynCpu, cpu_multiversion, cpu_version, to_dyn},
simd::Vec256,
};
@@ -25,11 +25,10 @@ pub(super) unsafe fn jsx_glue_hyphens(
b: usize,
cpu: impl Cpu,
) {
cpu_select!(cpu, {
Avx2(cpu) => jsx_glue_hyphens_avx2(src, n, st, opch, word, a, b, cpu),
Neon(_) => jsx_glue_hyphens_generic(src, n, st, opch, word, a, b),
Generic(_) => jsx_glue_hyphens_generic(src, n, st, opch, word, a, b),
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => jsx_glue_hyphens_avx2(src, n, st, opch, word, a, b, cpu),
_ => jsx_glue_hyphens_generic(src, n, st, opch, word, a, b),
}
}
/// [`jsx_glue_hyphens`] for AVX2.
+12 -6
View File
@@ -1,4 +1,4 @@
use crate::cpu::{Cpu, cpu_multiversion, cpu_select};
use crate::cpu::{Cpu, DynCpu, cpu_multiversion, to_dyn};
mod avx2;
use avx2::classify_avx2;
@@ -31,11 +31,17 @@ pub(super) unsafe fn classify(
kind: *mut u8,
cpu: impl Cpu,
) {
cpu_select!(cpu, {
Avx2(cpu) => classify_avx2(ts, src, n, word, st, kwinit, opch, digit, dot, misc, kind, cpu),
Neon(cpu) => classify_neon(ts, src, n, word, st, kwinit, opch, digit, dot, misc, kind, cpu),
Generic(_) => classify_generic(ts, src, n, word, st, kwinit, opch, digit, dot, misc, kind),
});
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => {
classify_avx2(ts, src, n, word, st, kwinit, opch, digit, dot, misc, kind, cpu)
}
DynCpu::Neon(cpu) => {
classify_neon(ts, src, n, word, st, kwinit, opch, digit, dot, misc, kind, cpu)
}
DynCpu::Generic(_) => {
classify_generic(ts, src, n, word, st, kwinit, opch, digit, dot, misc, kind)
}
};
*word.add(nb) = 0;
*st.add(nb) = 0;
+16 -21
View File
@@ -1,7 +1,7 @@
use oxc_span::Span;
use crate::{
cpu::{Cpu, DynCpu, cpu_multiversion, cpu_select},
cpu::{Cpu, DynCpu, cpu_multiversion, to_dyn},
token::{SPAN_SENTINELS, tk},
};
@@ -71,11 +71,9 @@ pub unsafe fn write_sentinels(n: u32, spans: *mut Span, sig_kinds: *mut u8, _cpu
///
/// `PairLuts` is only used by AVX2 implementation, so this function is a no-op on other CPUs.
pub(super) fn init_pair_luts(cpu: DynCpu) {
cpu_select!(cpu, {
Avx2(cpu) => init_pair_luts_avx2(cpu),
Neon(_) => {}
Generic(_) => {}
});
if let DynCpu::Avx2(cpu) = cpu {
init_pair_luts_avx2(cpu);
}
}
#[cpu_multiversion]
@@ -89,11 +87,10 @@ unsafe fn compress_blocks(
kinds: *mut u8,
cpu: impl Cpu,
) -> usize {
cpu_select!(cpu, {
Avx2(cpu) => compress_blocks_avx2(st, kind, b0, b1, starts, kinds, cpu),
Neon(_) => compress_blocks_generic(st, kind, b0, b1, starts, kinds),
Generic(_) => compress_blocks_generic(st, kind, b0, b1, starts, kinds),
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => compress_blocks_avx2(st, kind, b0, b1, starts, kinds, cpu),
_ => compress_blocks_generic(st, kind, b0, b1, starts, kinds),
}
}
#[cpu_multiversion]
@@ -106,11 +103,10 @@ unsafe fn build_spans(
sig_kinds: *mut u8,
cpu: impl Cpu,
) -> usize {
cpu_select!(cpu, {
Avx2(cpu) => build_spans_avx2(stage_kind, stage_pos, m, spans, sig_kinds, cpu),
Neon(_) => build_spans_generic(stage_kind, stage_pos, m, spans, sig_kinds),
Generic(_) => build_spans_generic(stage_kind, stage_pos, m, spans, sig_kinds),
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => build_spans_avx2(stage_kind, stage_pos, m, spans, sig_kinds, cpu),
_ => build_spans_generic(stage_kind, stage_pos, m, spans, sig_kinds),
}
}
#[cpu_multiversion]
@@ -124,9 +120,8 @@ unsafe fn lanes_post(
lanes: &mut Lanes,
cpu: impl Cpu,
) {
cpu_select!(cpu, {
Avx2(cpu) => lanes_post_avx2(src, out_kinds, out_spans, m, nn, lanes, cpu),
Neon(_) => lanes_post_generic(src, out_kinds, out_spans, m, nn, lanes),
Generic(_) => lanes_post_generic(src, out_kinds, out_spans, m, nn, lanes),
});
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => lanes_post_avx2(src, out_kinds, out_spans, m, nn, lanes, cpu),
_ => lanes_post_generic(src, out_kinds, out_spans, m, nn, lanes),
};
}
+9 -11
View File
@@ -1,4 +1,4 @@
use crate::cpu::{Cpu, cpu_multiversion, cpu_select};
use crate::cpu::{Cpu, DynCpu, cpu_multiversion, to_dyn};
mod avx2;
use avx2::{bracket_bits_avx2, find_avx2};
@@ -18,20 +18,18 @@ pub(super) unsafe fn find<const K: usize>(
needles: [u8; K],
cpu: impl Cpu,
) -> usize {
cpu_select!(cpu, {
Avx2(cpu) => find_avx2(src, n, i, needles, cpu),
Neon(_) => find_generic(src, n, i, needles),
Generic(_) => find_generic(src, n, i, needles),
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => find_avx2(src, n, i, needles, cpu),
_ => find_generic(src, n, i, needles),
}
}
/// Bits of the 64 bytes at `base` that are brackets (`(){}[]`). Bit `i` is for byte `base + i`.
#[cpu_multiversion]
#[inline(always)]
pub(super) fn bracket_bits(src: &[u8], base: usize, cpu: impl Cpu) -> u64 {
cpu_select!(cpu, {
Avx2(cpu) => bracket_bits_avx2(src, base, cpu),
Neon(_) => bracket_bits_generic(src, base),
Generic(_) => bracket_bits_generic(src, base),
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => bracket_bits_avx2(src, base, cpu),
_ => bracket_bits_generic(src, base),
}
}
+6 -6
View File
@@ -37,7 +37,7 @@ use constcat::concat_slices;
use oxc_data_structures::{assert_unchecked, branch_hints::unlikely, str::const_str_eq};
use crate::{
cpu::{Avx2, BASELINE, Cpu, Neon, cpu_multiversion, cpu_select, cpu_version},
cpu::{Avx2, BASELINE, Cpu, DynCpu, Neon, cpu_multiversion, cpu_version, to_dyn},
simd::Vec128,
token::{KW_KIND_BASE, TokenKind},
};
@@ -507,11 +507,11 @@ pub(super) unsafe fn kw_match<const IS_TS: bool>(
#[cpu_multiversion]
#[inline(always)]
unsafe fn text_matches(ptr: *const u8, len: usize, text: &[u64; 2], cpu: impl Cpu) -> bool {
cpu_select!(cpu, {
Avx2(cpu) => text_matches_avx2(ptr, len, text, cpu),
Neon(cpu) => text_matches_neon(ptr, len, text, cpu),
Generic(_) => text_matches_generic(ptr, len, text),
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => text_matches_avx2(ptr, len, text, cpu),
DynCpu::Neon(cpu) => text_matches_neon(ptr, len, text, cpu),
DynCpu::Generic(_) => text_matches_generic(ptr, len, text),
}
}
/// [`text_matches`] for AVX2.
+13 -16
View File
@@ -9,7 +9,7 @@ use oxc_ast::ast::{Comment, RegExpFlags};
use oxc_syntax::identifier::{is_identifier_part, is_identifier_start};
use crate::{
cpu::{Avx2, BASELINE, Cpu, cpu_multiversion, cpu_select, cpu_version},
cpu::{Avx2, BASELINE, Cpu, DynCpu, cpu_multiversion, cpu_version, to_dyn},
error::{DiagCode, DiagSeverity, Diagnostic},
simd::Vec256,
token::StringSpan,
@@ -481,13 +481,12 @@ fn cook_short<const EMIT: bool, const CRLF: bool>(
diags: &mut Vec<Diagnostic>,
cpu: impl Cpu,
) -> (u32, bool, bool) {
cpu_select!(cpu, {
Avx2(cpu) => {
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => {
cook_short_avx2::<EMIT, CRLF>(src, bs, be, base, ss, body_len, diags, cpu)
}
Neon(_) => cook_short_generic::<EMIT, CRLF>(src, bs, be, base, ss, body_len, diags),
Generic(_) => cook_short_generic::<EMIT, CRLF>(src, bs, be, base, ss, body_len, diags),
})
_ => cook_short_generic::<EMIT, CRLF>(src, bs, be, base, ss, body_len, diags),
}
}
/// [`cook_short`] for AVX2.
@@ -555,11 +554,10 @@ fn cook_short_generic<const EMIT: bool, const CRLF: bool>(
#[cpu_multiversion]
#[inline(always)]
fn span_has_bs(src: &[u8], bs: usize, be: usize, cpu: impl Cpu) -> bool {
cpu_select!(cpu, {
Avx2(cpu) => span_has_bs_avx2(src, bs, be, cpu),
Neon(_) => span_has_bs_generic(src, bs, be),
Generic(_) => span_has_bs_generic(src, bs, be),
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => span_has_bs_avx2(src, bs, be, cpu),
_ => span_has_bs_generic(src, bs, be),
}
}
/// [`span_has_bs`] for AVX2.
@@ -595,11 +593,10 @@ fn span_has_bs_generic(src: &[u8], bs: usize, be: usize) -> bool {
#[cpu_multiversion]
#[inline(always)]
fn span_has_bs_or_cr(src: &[u8], bs: usize, be: usize, cpu: impl Cpu) -> bool {
cpu_select!(cpu, {
Avx2(cpu) => span_has_bs_or_cr_avx2(src, bs, be, cpu),
Neon(_) => span_has_bs_or_cr_generic(src, bs, be),
Generic(_) => span_has_bs_or_cr_generic(src, bs, be),
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => span_has_bs_or_cr_avx2(src, bs, be, cpu),
_ => span_has_bs_or_cr_generic(src, bs, be),
}
}
/// [`span_has_bs_or_cr`] for AVX2.
+9 -11
View File
@@ -1,4 +1,4 @@
use crate::cpu::{Cpu, cpu_multiversion, cpu_select};
use crate::cpu::{Cpu, DynCpu, cpu_multiversion, to_dyn};
mod avx2;
use avx2::{scan_block_comment_avx2, scan_line_comment_avx2};
@@ -17,11 +17,10 @@ pub(super) unsafe fn scan_block_comment(
i: usize,
cpu: impl Cpu,
) -> (usize, bool, i64) {
cpu_select!(cpu, {
Avx2(cpu) => scan_block_comment_avx2(src, n, i, cpu),
Neon(_) => scan_block_comment_generic(src, n, i),
Generic(_) => scan_block_comment_generic(src, n, i),
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => scan_block_comment_avx2(src, n, i, cpu),
_ => scan_block_comment_generic(src, n, i),
}
}
/// Scan a `//` line comment to its LineTerminator (LF, CR, or LS/PS),
@@ -34,9 +33,8 @@ pub(super) unsafe fn scan_line_comment(
i: usize,
cpu: impl Cpu,
) -> (usize, i64) {
cpu_select!(cpu, {
Avx2(cpu) => scan_line_comment_avx2(src, n, i, cpu),
Neon(_) => scan_line_comment_generic(src, n, i),
Generic(_) => scan_line_comment_generic(src, n, i),
})
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => scan_line_comment_avx2(src, n, i, cpu),
_ => scan_line_comment_generic(src, n, i),
}
}
+7 -13
View File
@@ -3,7 +3,7 @@
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::{_MM_HINT_T0, _mm_prefetch};
use crate::cpu::{Cpu, cpu_multiversion, cpu_select};
use crate::cpu::{Cpu, DynCpu, cpu_multiversion, to_dyn};
/// Ask the CPU to fetch the cache line containing `p` into all levels of the cache.
///
@@ -14,17 +14,11 @@ use crate::cpu::{Cpu, cpu_multiversion, cpu_select};
pub fn prefetch(p: *const u8, cpu: impl Cpu) {
let _ = p; // Avoid clippy unused vars warning
cpu_select!(cpu, {
Avx2(_) => {
cfg_select! {
target_arch = "x86_64" => {
// SAFETY: `Avx2` type guarantees CPU supports AVX2
unsafe { _mm_prefetch(p.cast::<i8>(), _MM_HINT_T0) }
}
_ => {}
}
if matches!(to_dyn(cpu), DynCpu::Avx2(_)) {
// SAFETY: `Avx2` type guarantees CPU supports AVX2
#[cfg(target_arch = "x86_64")]
unsafe {
_mm_prefetch(p.cast::<i8>(), _MM_HINT_T0);
}
Neon(_) => { /* no-op */ }
Generic(_) => { /* no-op */ }
});
}
}
+2 -2
View File
@@ -11,7 +11,7 @@
//! - [`cpu_impl`]: Methods of one feature set's types, which exist on every platform,
//! but only work on the platform which has the feature set.
//!
//! `oxc_lexer`'s `cpu_select!` macro chooses between versions.
//! `oxc_lexer` code chooses between versions with a `match` on `to_dyn(cpu)`.
//!
//! The macros are only for `oxc_lexer`.
//! Their output refers to its `cpu` and `simd` modules by path.
@@ -228,7 +228,7 @@ pub fn cpu_multiversion(args: TokenStream, item: TokenStream) -> TokenStream {
/// Marks a function as the version of some code for one CPU feature set.
///
/// `cpu_select!` picks between the versions.
/// A `match` on `to_dyn(cpu)` picks between the versions.
///
/// Applied to a function with one of:
///