fix(lexer): cpu_version macro always validate proof param (#28690)

`cpu_version` macro passes through the function unchanged where the function is unsafe or has an `#[inline(always)]` attribute. It checks that the function has an appropriate param which proves that the system supports the required CPU features (e.g. `cpu: Avx2` param for a function marked `#[cpu_version(Avx2)]`.

However, that check is based on the source text only. The macro can only see the name of the type in the param, not it's actual type. Add a line to the body of the function that checks the proof - making sure the type passed in is what it appears to be e.g.:

```rust
let _: crate::cpu::Avx2 = cpu;
```
This commit is contained in:
overlookmotel committed 2026-11-02 01:43:30 +00:00
1 parent 60a1860667
commit 8808fd7211
2 files changed
+39 -6

No files matched your search

+2 -2
View File
@@ -251,8 +251,8 @@ pub fn cpu_multiversion(args: TokenStream, item: TokenStream) -> TokenStream {
/// A generic version only runs where the CPU feature set is `BASELINE`'s,
/// so it uses `BASELINE` where it needs a CPU type. The function gets no attributes.
///
/// A function which isn't wrapped is otherwise output unchanged, from its original tokens.
/// So it compiles, and is linted, exactly as written.
/// A function which isn't wrapped is otherwise output from its original tokens, with the `let _`
/// check described below at the start of its body. So it compiles, and is linted, as written.
///
/// The parameters must be `name: Type` or `mut name: Type` (no other patterns).
///
+37 -4
View File
@@ -14,7 +14,7 @@
//!
//! [`cpu_version`]: crate::cpu_version
use proc_macro2::{Span, TokenStream};
use proc_macro2::{Delimiter, Group, Span, TokenStream};
use quote::quote;
use syn::{Error, GenericArgument, Ident, PathArguments, Result, Type, parse2, spanned::Spanned};
@@ -49,23 +49,56 @@ pub fn expand(args: TokenStream, item: TokenStream) -> Result<TokenStream> {
let proof = find_proof(&func, cpu)?;
let cfg_attr = cpu.cfg_attr();
let original = &func.original;
// An `#[inline(always)]` function can't have `#[target_feature]`.
// It's compiled as part of its callers, with their features.
if func.is_inline_always() {
return Ok(quote!(#cfg_attr #original));
let checked_fn = with_proof(&func, &proof);
return Ok(quote!(#cfg_attr #checked_fn));
}
// An `unsafe` function's callers need an `unsafe` block anyway, so it isn't wrapped
if func.unsafety.is_some() {
let target_feature_attr = cpu.target_feature_attr();
return Ok(quote!(#cfg_attr #target_feature_attr #original));
let checked_fn = with_proof(&func, &proof);
return Ok(quote!(#cfg_attr #target_feature_attr #checked_fn));
}
Ok(wrap(&func, cpu, &proof))
}
/// The original function, with the proof check at the start of its body.
/// For a function which isn't wrapped, so there's no wrapper to hold the check.
///
/// For the example function as an `unsafe fn`:
///
/// ```ignore
/// /// Do some stuff (AVX2 version).
/// #[inline(never)]
/// unsafe fn foo<'a, const N: usize, T>(mut arr: &'a [T; N], cpu: Avx2) -> bool {
/// let _: crate::cpu::Avx2 = cpu;
/// // ... original function body ...
/// }
/// ```
fn with_proof(func: &Func, proof: &Proof) -> TokenStream {
// Everything but the body
let mut head = func.original.clone().into_iter().collect::<Vec<_>>();
head.pop();
let Proof { param_name, ty } = proof;
let statements = func.body.stream();
let body = quote! {
// `find_proof` only matched the type's name.
// This fails to compile if it's not the real type.
let _: #ty = #param_name;
#statements
};
let mut body = Group::new(Delimiter::Brace, body);
body.set_span(func.body.span());
quote!(#(#head)* #body)
}
/// Wrap the function in one which is safe to call. See the docs on `cpu_version`.
///
/// ```ignore