rusticl: add proc macro module for generating API stubs
authorKarol Herbst <git@karolherbst.de>
Sat, 3 Jun 2023 12:59:41 +0000 (14:59 +0200)
committerMarge Bot <emma+marge@anholt.net>
Fri, 9 Jun 2023 10:35:23 +0000 (10:35 +0000)
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>

src/gallium/frontends/rusticl/meson.build
src/gallium/frontends/rusticl/proc/lib.rs [new file with mode: 0644]

index 3489aac..e844dc1 100644 (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,
diff --git a/src/gallium/frontends/rusticl/proc/lib.rs b/src/gallium/frontends/rusticl/proc/lib.rs
new file mode 100644 (file)
index 0000000..daff724
--- /dev/null
@@ -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
+}