mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08:00

Rust has two different tools for generating function declarations to call across the FFI boundary: * bindgen. Generates Rust declarations from a C header. * cbindgen. Generates C headers from Rust declarations. However, we only use bindgen in the kernel. This means that when C code calls a Rust function by name, its signature must be duplicated in both Rust code and a C header, and the signature needs to be kept in sync manually. Introducing cbindgen as a mandatory dependency to build the kernel would be a rather complex and large change, so we do not consider that at this time. Instead, to eliminate this manual checking, introduce a new macro that verifies at compile time that the two function declarations use the same signature. The idea is to run the C declaration through bindgen, and then have rustc verify that the function pointers have the same type. The signature must still be written twice, but at least you can no longer get it wrong. If the signatures don't match, you will get errors that look like this: error[E0308]: `if` and `else` have incompatible types --> <linux>/rust/kernel/print.rs:22:22 | 21 | #[export] | --------- expected because of this 22 | unsafe extern "C" fn rust_fmt_argument( | ^^^^^^^^^^^^^^^^^ expected `u8`, found `i8` | = note: expected fn item `unsafe extern "C" fn(*mut u8, *mut u8, *mut c_void) -> *mut u8 {bindings::rust_fmt_argument}` found fn item `unsafe extern "C" fn(*mut i8, *mut i8, *const c_void) -> *mut i8 {print::rust_fmt_argument}` It is unfortunate that the error message starts out by saying "`if` and `else` have incompatible types", but I believe the rest of the error message is reasonably clear and not too confusing. Reviewed-by: Tamir Duberstein <tamird@gmail.com> Reviewed-by: Andreas Hindborg <a.hindborg@kernel.org> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Alice Ryhl <aliceryhl@google.com> Link: https://lore.kernel.org/r/20250303-export-macro-v3-3-41fbad85a27f@google.com Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
235 lines
8.3 KiB
Rust
235 lines
8.3 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
use proc_macro::{token_stream, Group, Ident, TokenStream, TokenTree};
|
|
|
|
pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
|
|
if let Some(TokenTree::Ident(ident)) = it.next() {
|
|
Some(ident.to_string())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub(crate) fn try_literal(it: &mut token_stream::IntoIter) -> Option<String> {
|
|
if let Some(TokenTree::Literal(literal)) = it.next() {
|
|
Some(literal.to_string())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub(crate) fn try_string(it: &mut token_stream::IntoIter) -> Option<String> {
|
|
try_literal(it).and_then(|string| {
|
|
if string.starts_with('\"') && string.ends_with('\"') {
|
|
let content = &string[1..string.len() - 1];
|
|
if content.contains('\\') {
|
|
panic!("Escape sequences in string literals not yet handled");
|
|
}
|
|
Some(content.to_string())
|
|
} else if string.starts_with("r\"") {
|
|
panic!("Raw string literals are not yet handled");
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
pub(crate) fn expect_ident(it: &mut token_stream::IntoIter) -> String {
|
|
try_ident(it).expect("Expected Ident")
|
|
}
|
|
|
|
pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
|
|
if let TokenTree::Punct(punct) = it.next().expect("Reached end of token stream for Punct") {
|
|
punct.as_char()
|
|
} else {
|
|
panic!("Expected Punct");
|
|
}
|
|
}
|
|
|
|
pub(crate) fn expect_string(it: &mut token_stream::IntoIter) -> String {
|
|
try_string(it).expect("Expected string")
|
|
}
|
|
|
|
pub(crate) fn expect_string_ascii(it: &mut token_stream::IntoIter) -> String {
|
|
let string = try_string(it).expect("Expected string");
|
|
assert!(string.is_ascii(), "Expected ASCII string");
|
|
string
|
|
}
|
|
|
|
pub(crate) fn expect_group(it: &mut token_stream::IntoIter) -> Group {
|
|
if let TokenTree::Group(group) = it.next().expect("Reached end of token stream for Group") {
|
|
group
|
|
} else {
|
|
panic!("Expected Group");
|
|
}
|
|
}
|
|
|
|
pub(crate) fn expect_end(it: &mut token_stream::IntoIter) {
|
|
if it.next().is_some() {
|
|
panic!("Expected end");
|
|
}
|
|
}
|
|
|
|
/// Parsed generics.
|
|
///
|
|
/// See the field documentation for an explanation what each of the fields represents.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust,ignore
|
|
/// # let input = todo!();
|
|
/// let (Generics { decl_generics, impl_generics, ty_generics }, rest) = parse_generics(input);
|
|
/// quote! {
|
|
/// struct Foo<$($decl_generics)*> {
|
|
/// // ...
|
|
/// }
|
|
///
|
|
/// impl<$impl_generics> Foo<$ty_generics> {
|
|
/// fn foo() {
|
|
/// // ...
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
pub(crate) struct Generics {
|
|
/// The generics with bounds and default values (e.g. `T: Clone, const N: usize = 0`).
|
|
///
|
|
/// Use this on type definitions e.g. `struct Foo<$decl_generics> ...` (or `union`/`enum`).
|
|
pub(crate) decl_generics: Vec<TokenTree>,
|
|
/// The generics with bounds (e.g. `T: Clone, const N: usize`).
|
|
///
|
|
/// Use this on `impl` blocks e.g. `impl<$impl_generics> Trait for ...`.
|
|
pub(crate) impl_generics: Vec<TokenTree>,
|
|
/// The generics without bounds and without default values (e.g. `T, N`).
|
|
///
|
|
/// Use this when you use the type that is declared with these generics e.g.
|
|
/// `Foo<$ty_generics>`.
|
|
pub(crate) ty_generics: Vec<TokenTree>,
|
|
}
|
|
|
|
/// Parses the given `TokenStream` into `Generics` and the rest.
|
|
///
|
|
/// The generics are not present in the rest, but a where clause might remain.
|
|
pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec<TokenTree>) {
|
|
// The generics with bounds and default values.
|
|
let mut decl_generics = vec![];
|
|
// `impl_generics`, the declared generics with their bounds.
|
|
let mut impl_generics = vec![];
|
|
// Only the names of the generics, without any bounds.
|
|
let mut ty_generics = vec![];
|
|
// Tokens not related to the generics e.g. the `where` token and definition.
|
|
let mut rest = vec![];
|
|
// The current level of `<`.
|
|
let mut nesting = 0;
|
|
let mut toks = input.into_iter();
|
|
// If we are at the beginning of a generic parameter.
|
|
let mut at_start = true;
|
|
let mut skip_until_comma = false;
|
|
while let Some(tt) = toks.next() {
|
|
if nesting == 1 && matches!(&tt, TokenTree::Punct(p) if p.as_char() == '>') {
|
|
// Found the end of the generics.
|
|
break;
|
|
} else if nesting >= 1 {
|
|
decl_generics.push(tt.clone());
|
|
}
|
|
match tt.clone() {
|
|
TokenTree::Punct(p) if p.as_char() == '<' => {
|
|
if nesting >= 1 && !skip_until_comma {
|
|
// This is inside of the generics and part of some bound.
|
|
impl_generics.push(tt);
|
|
}
|
|
nesting += 1;
|
|
}
|
|
TokenTree::Punct(p) if p.as_char() == '>' => {
|
|
// This is a parsing error, so we just end it here.
|
|
if nesting == 0 {
|
|
break;
|
|
} else {
|
|
nesting -= 1;
|
|
if nesting >= 1 && !skip_until_comma {
|
|
// We are still inside of the generics and part of some bound.
|
|
impl_generics.push(tt);
|
|
}
|
|
}
|
|
}
|
|
TokenTree::Punct(p) if skip_until_comma && p.as_char() == ',' => {
|
|
if nesting == 1 {
|
|
impl_generics.push(tt.clone());
|
|
impl_generics.push(tt);
|
|
skip_until_comma = false;
|
|
}
|
|
}
|
|
_ if !skip_until_comma => {
|
|
match nesting {
|
|
// If we haven't entered the generics yet, we still want to keep these tokens.
|
|
0 => rest.push(tt),
|
|
1 => {
|
|
// Here depending on the token, it might be a generic variable name.
|
|
match tt.clone() {
|
|
TokenTree::Ident(i) if at_start && i.to_string() == "const" => {
|
|
let Some(name) = toks.next() else {
|
|
// Parsing error.
|
|
break;
|
|
};
|
|
impl_generics.push(tt);
|
|
impl_generics.push(name.clone());
|
|
ty_generics.push(name.clone());
|
|
decl_generics.push(name);
|
|
at_start = false;
|
|
}
|
|
TokenTree::Ident(_) if at_start => {
|
|
impl_generics.push(tt.clone());
|
|
ty_generics.push(tt);
|
|
at_start = false;
|
|
}
|
|
TokenTree::Punct(p) if p.as_char() == ',' => {
|
|
impl_generics.push(tt.clone());
|
|
ty_generics.push(tt);
|
|
at_start = true;
|
|
}
|
|
// Lifetimes begin with `'`.
|
|
TokenTree::Punct(p) if p.as_char() == '\'' && at_start => {
|
|
impl_generics.push(tt.clone());
|
|
ty_generics.push(tt);
|
|
}
|
|
// Generics can have default values, we skip these.
|
|
TokenTree::Punct(p) if p.as_char() == '=' => {
|
|
skip_until_comma = true;
|
|
}
|
|
_ => impl_generics.push(tt),
|
|
}
|
|
}
|
|
_ => impl_generics.push(tt),
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
rest.extend(toks);
|
|
(
|
|
Generics {
|
|
impl_generics,
|
|
decl_generics,
|
|
ty_generics,
|
|
},
|
|
rest,
|
|
)
|
|
}
|
|
|
|
/// Given a function declaration, finds the name of the function.
|
|
pub(crate) fn function_name(input: TokenStream) -> Option<Ident> {
|
|
let mut input = input.into_iter();
|
|
while let Some(token) = input.next() {
|
|
match token {
|
|
TokenTree::Ident(i) if i.to_string() == "fn" => {
|
|
if let Some(TokenTree::Ident(i)) = input.next() {
|
|
return Some(i);
|
|
}
|
|
return None;
|
|
}
|
|
_ => continue,
|
|
}
|
|
}
|
|
None
|
|
}
|