mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
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:
1 parent
60a1860667
commit
8808fd7211
2 files changed
+39
-6
No files matched your search
@@ -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).
|
||||
///
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user