rusticl: add proc macro module for generating API stubs

Most of the API stubs are very very trivial to generate as the sole
purpose of those are to deconstruct the returned `Result` object.

Sadly we can't use external crates yet, so "syn" and "qoute" can't be used
for this :'(

The code is kinda hacky, but we also don't expose this to other people, so
we can keep this as a big hack until we can use external crates.

I wish there was a better solution here.

Signed-off-by: Karol Herbst <git@karolherbst.de>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/23413>
This commit is contained in:
Karol Herbst 2023-06-03 14:59:41 +02:00 committed by Marge Bot
parent b1cd5780d6
commit 5875f2e803
2 changed files with 232 additions and 0 deletions

View file

@ -44,6 +44,10 @@ libmesa_rust_files = files(
'mesa/pipe/transfer.rs',
)
rusticl_proc_macros_files = files(
'proc/lib.rs',
)
rusticl_files = files(
'lib.rs',
'api.rs',
@ -316,6 +320,15 @@ libmesa_rust = static_library(
]
)
rusticl_proc_macros = shared_library(
'rusticl_proc_macros',
[rusticl_proc_macros_files],
rust_crate_type : 'proc-macro',
rust_args : [
rusticl_args,
],
)
librusticl = static_library(
'rusticl',
[rusticl_files],
@ -329,6 +342,7 @@ librusticl = static_library(
libmesa_rust_gen,
libmesa_rust_util,
rusticl_opencl_gen,
rusticl_proc_macros,
],
dependencies : [
idep_rusticl_gen,

View file

@ -0,0 +1,218 @@
extern crate proc_macro;
use proc_macro::Delimiter;
use proc_macro::TokenStream;
use proc_macro::TokenTree::Group;
use proc_macro::TokenTree::Ident;
use proc_macro::TokenTree::Punct;
/// Macro for generating the C API stubs for normal functions
#[proc_macro_attribute]
pub fn cl_entrypoint(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut name = None;
let mut args = None;
let mut ret_type = None;
let mut iter = item.clone().into_iter();
while let Some(item) = iter.next() {
match item {
Ident(ident) => match ident.to_string().as_str() {
// extract the function name
"fn" => name = Some(iter.next().unwrap().to_string()),
// extract inner type
"CLResult" => {
// skip the `<`
iter.next();
let mut ret_type_tmp = String::new();
for ident in iter.by_ref() {
if ident.to_string() == ">" {
break;
}
if ret_type_tmp.ends_with("mut") || ret_type_tmp.ends_with("const") {
ret_type_tmp.push(' ');
}
ret_type_tmp.push_str(ident.to_string().as_str());
}
ret_type = Some(ret_type_tmp);
}
_ => {}
},
Group(group) => {
if args.is_some() {
continue;
}
if group.delimiter() != Delimiter::Parenthesis {
continue;
}
// the first group are our function args :)
args = Some(group.stream());
}
_ => {}
}
}
let name = name.as_ref().expect("no name found!");
let args = args.as_ref().expect("no args found!");
let ret_type = ret_type.as_ref().expect("no ret_type found!");
let mut arg_names = Vec::new();
let mut collect = true;
// extract the variable names of our function arguments
for item in args.clone() {
match item {
Ident(ident) => {
if collect {
arg_names.push(ident);
}
}
// we ignore everything between a `:` and a `,` as those are the argument types
Punct(punct) => match punct.as_char() {
':' => collect = false,
',' => collect = true,
_ => {}
},
_ => {}
}
}
// convert to string and strip `mut` specifiers
let arg_names: Vec<_> = arg_names
.clone()
.into_iter()
.map(|ident| ident.to_string())
.filter(|ident| ident != "mut")
.collect();
let arg_names_str = arg_names.join(",");
let mut args = args.to_string();
if !args.ends_with(',') {
args.push(',');
}
// depending on the return type we have to generate a different match case
let mut res: TokenStream = if ret_type == "()" {
// trivial case: return the `Err(err)` as is
format!(
"pub extern \"C\" fn cl_{name}(
{args}
) -> cl_int {{
match {name}({arg_names_str}) {{
Ok(_) => CL_SUCCESS as cl_int,
Err(e) => e,
}}
}}"
)
} else {
// here we write the error code into the last argument, which we also add. All OpenCL APIs
// which return an object do have the `errcode_ret: *mut cl_int` argument last, so we can
// just make use of this here.
format!(
"pub extern \"C\" fn cl_{name}(
{args}
errcode_ret: *mut cl_int,
) -> {ret_type} {{
let (ptr, err) = match {name}({arg_names_str}) {{
Ok(o) => (o, CL_SUCCESS as cl_int),
Err(e) => (std::ptr::null_mut(), e),
}};
if !errcode_ret.is_null() {{
unsafe {{
*errcode_ret = err;
}}
}}
ptr
}}"
)
}
.parse()
.unwrap();
res.extend(item);
res
}
/// Special macro for generating C function stubs to call into our `CLInfo` trait
#[proc_macro_attribute]
pub fn cl_info_entrypoint(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut name = None;
let mut args = Vec::new();
let mut iter = item.clone().into_iter();
let mut collect = false;
// we have to extract the type name we implement the trait for and the type of the input
// parameters. The input Parameters are defined as `T` inside `CLInfo<T>` or `CLInfoObj<T, ..>`
while let Some(item) = iter.next() {
match item {
Ident(ident) => {
if collect {
args.push(ident);
} else if ident.to_string() == "for" {
name = Some(iter.next().unwrap().to_string());
}
}
Punct(punct) => match punct.as_char() {
'<' => collect = true,
'>' => collect = false,
_ => {}
},
_ => {}
}
}
let name = name.as_ref().expect("no name found!");
assert!(!args.is_empty());
// the 1st argument is special as it's the actual property being queried. The remaining
// arguments are additional input data being passed before the property.
let arg = &args[0];
let (args_values, args) = args[1..]
.iter()
.enumerate()
.map(|(idx, arg)| (format!("arg{idx},"), format!("arg{idx}: {arg},")))
.reduce(|(a1, b1), (a2, b2)| (a1 + &a2, b1 + &b2))
.unwrap_or_default();
// depending on the amount of arguments we have a different trait implementation
let method = if args.len() > 1 {
"get_info_obj"
} else {
"get_info"
};
let mut res: TokenStream = format!(
"pub extern \"C\" fn {attr}(
input: {name},
{args}
param_name: {arg},
param_value_size: usize,
param_value: *mut ::std::ffi::c_void,
param_value_size_ret: *mut usize,
) -> cl_int {{
match input.{method}(
{args_values}
param_name,
param_value_size,
param_value,
param_value_size_ret,
) {{
Ok(_) => CL_SUCCESS as cl_int,
Err(e) => e,
}}
}}"
)
.parse()
.unwrap();
res.extend(item);
res
}