Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit 5570b2f

Browse files
committed
libm-analyze: a proc macro to analyze libm APIs
This proc macro takes a macro of the form: ``` macro_rules! nop { ( id: $id:ident; arg_tys: $($arg_tys:ty),*; arg_ids: $($arg_ids:ident),*; ret: $ty:ty; ) => {}; } ``` as input and expands it on every public libm API. This facilitates generating tests, benchmarks, etc. for all APIs.
1 parent 0d32984 commit 5570b2f

File tree

4 files changed

+219
-26
lines changed

4 files changed

+219
-26
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
members = [
33
"crates/libm",
44
"crates/libm-analyze",
5-
"crates/libm-verify",
5+
"crates/libm-test",
66
"crates/compiler-builtins-smoke-test",
77
"crates/libm-bench",
88
]

crates/libm-analyze/Cargo.toml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
[package]
22
name = "libm-analyze"
33
version = "0.1.0"
4-
authors = ["gnzlbg <[email protected]>"]
4+
authors = ["Gonzalo Brito Gadeschi <[email protected]>"]
55
edition = "2018"
66

77
[lib]
88
proc-macro = true
99
test = false
1010

11-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12-
1311
[dependencies]
1412
syn = { version = "0.15", features = ["full", "extra-traits"] }
1513
quote = "0.6"
16-
walkdir = "2.2.8"
14+
walkdir = "2.2.8"
15+
proc-macro2 = "0.4"
16+
17+
[features]
18+
default = ["analyze"]
19+
analyze = []

crates/libm-analyze/src/lib.rs

Lines changed: 198 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,41 @@
22

33
extern crate proc_macro;
44
use self::proc_macro::TokenStream;
5+
use quote::quote;
6+
use syn::parse_macro_input;
57

68
#[proc_macro]
79
pub fn for_each_api(input: TokenStream) -> TokenStream {
810
let files = get_libm_files();
911
let functions = get_functions(files);
10-
input
12+
let input = parse_macro_input!(input as syn::Ident);
13+
let mut tokens = proc_macro2::TokenStream::new();
14+
for function in functions {
15+
let id = function.ident;
16+
let ret_ty = function.ret_ty;
17+
let arg_tys = function.arg_tys;
18+
let arg_ids = get_arg_ids(arg_tys.len());
19+
let t = quote! {
20+
#input! {
21+
id: #id;
22+
arg_tys: #(#arg_tys),*;
23+
arg_ids: #(#arg_ids),*;
24+
ret: #ret_ty;
25+
}
26+
};
27+
tokens.extend(t);
28+
}
29+
tokens.into()
1130
}
1231

1332
/// Traverses the libm crate directory, parsing all .rs files
1433
fn get_libm_files() -> Vec<(syn::File, String)> {
1534
let root_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
16-
dbg!(&root_dir);
1735
let libm_dir = root_dir
1836
.parent()
1937
.expect("couldn't access crates/ dir")
2038
.join("libm");
21-
dbg!(&libm_dir);
2239
let libm_src_dir = libm_dir.join("src");
23-
dbg!(&libm_src_dir);
2440

2541
let mut files = Vec::new();
2642
for entry in walkdir::WalkDir::new(libm_src_dir)
@@ -38,7 +54,6 @@ fn get_libm_files() -> Vec<(syn::File, String)> {
3854
continue;
3955
}
4056

41-
eprintln!("{}", file_path.display());
4257
let mut file_string = String::new();
4358
std::fs::File::open(&file_path)
4459
.unwrap_or_else(|_| panic!("can't open file at path: {}", file_path.display()))
@@ -53,17 +68,33 @@ fn get_libm_files() -> Vec<(syn::File, String)> {
5368
struct FnSig {
5469
ident: syn::Ident,
5570
unsafety: bool,
56-
constness: bool,
57-
asyncness: bool,
58-
attrs: Vec<syn::Attribute>,
59-
abi: &'static str,
71+
c_abi: bool,
72+
ret_ty: Option<syn::Type>,
73+
arg_tys: Vec<syn::Type>,
74+
}
75+
76+
impl FnSig {
77+
fn name(&self) -> String {
78+
self.ident.to_string()
79+
}
80+
}
6081

82+
macro_rules! syn_to_str {
83+
($e:expr) => {{
84+
let t = $e;
85+
let tokens = quote! {
86+
#t
87+
};
88+
format!("{}", tokens)
89+
}};
6190
}
6291

6392
/// Extracts all public functions from the libm files.
64-
fn get_functions(files: Vec<(syn::File, String)>) {
65-
//let mut functions = Vec::new();
93+
fn get_functions(files: Vec<(syn::File, String)>) -> Vec<FnSig> {
94+
let mut error = false;
95+
let mut functions = Vec::new();
6696
for item in files.iter().flat_map(|f| f.0.items.iter()) {
97+
let mut e = false;
6798
match item {
6899
syn::Item::Fn(syn::ItemFn {
69100
vis: syn::Visibility::Public(_),
@@ -76,21 +107,167 @@ fn get_functions(files: Vec<(syn::File, String)>) {
76107
decl,
77108
block: _,
78109
}) => {
79-
if let Some(syn::Abi { name: Some(l), extern_token: _ }) = abi {
80-
println!("{:#?}", l);
81-
if l.value() != "C" {
82-
l.span().unwrap().warning(
83-
"public libm function is not `extern \"C\""
84-
).emit();
110+
let mut fn_sig = FnSig {
111+
ident: ident.clone(),
112+
unsafety: true,
113+
c_abi: false,
114+
arg_tys: Vec::new(),
115+
ret_ty: None,
116+
};
117+
macro_rules! err {
118+
($msg:expr) => {{
119+
#[cfg(feature = "analyze")]
120+
{
121+
eprintln!("[error]: Function \"{}\" {}", fn_sig.name(), $msg);
122+
}
123+
#[allow(unused_assignments)]
124+
{
125+
e = true;
126+
}
127+
()
128+
}};
129+
}
130+
if let Some(syn::Abi {
131+
name: Some(l),
132+
extern_token: _,
133+
}) = abi
134+
{
135+
if l.value() == "C" {
136+
fn_sig.c_abi = true;
137+
}
138+
}
139+
if let Some(_) = constness {
140+
err!("is const");
141+
}
142+
if let Some(_) = asyncness {
143+
err!("is async");
144+
}
145+
if &None == unsafety {
146+
fn_sig.unsafety = false;
147+
}
148+
let syn::FnDecl {
149+
fn_token: _,
150+
generics,
151+
paren_token: _,
152+
inputs,
153+
variadic,
154+
output,
155+
} = (**decl).clone();
156+
157+
if variadic.is_some() {
158+
err!(format!(
159+
"contains variadic arguments \"{}\"",
160+
syn_to_str!(variadic.unwrap())
161+
));
162+
}
163+
if generics.type_params().into_iter().count() != 0 {
164+
err!(format!(
165+
"contains generic parameters \"{}\"",
166+
syn_to_str!(generics.clone())
167+
));
168+
}
169+
if generics.lifetimes().into_iter().count() != 0 {
170+
err!(format!(
171+
"contains lifetime parameters \"{}\"",
172+
syn_to_str!(generics.clone())
173+
));
174+
}
175+
if generics.const_params().into_iter().count() != 0 {
176+
err!(format!(
177+
"contains const parameters \"{}\"",
178+
syn_to_str!(generics.clone())
179+
));
180+
}
181+
if attrs.is_empty() {
182+
err!(format!(
183+
"missing `#[inline]` and `#[no_panic]` attributes {}",
184+
attrs
185+
.iter()
186+
.map(|a| syn_to_str!(a))
187+
.collect::<Vec<_>>()
188+
.join(",")
189+
));
190+
} // TODO: might want to check other attributes as well
191+
if !fn_sig.c_abi {
192+
// FIXME: do not disable test if this fails - otherwise no test passes
193+
let e2 = e;
194+
err!("not `extern \"C\"`");
195+
e = e2;
196+
}
197+
match output {
198+
syn::ReturnType::Default => (),
199+
syn::ReturnType::Type(_, ref b) if valid_ty(&b) => {
200+
fn_sig.ret_ty = Some(*b.clone())
201+
}
202+
other => err!(format!("returns unsupported type {}", syn_to_str!(other))),
203+
}
204+
for input in inputs {
205+
match input {
206+
syn::FnArg::Captured(ref c) if valid_ty(&c.ty) => {
207+
fn_sig.arg_tys.push(c.ty.clone())
208+
}
209+
other => err!(format!(
210+
"takes unsupported argument type {}",
211+
syn_to_str!(other)
212+
)),
85213
}
214+
}
215+
if !e {
216+
functions.push(fn_sig);
86217
} else {
87-
ident.span().unwrap().warning(
88-
"public libm function is not `extern \"C\""
89-
).emit();
218+
error = true;
90219
}
91-
println!("{:#?}", ident);
92220
}
93221
_ => (),
94222
}
95223
}
224+
if error {
225+
// too many errors:
226+
// panic!("errors found");
227+
}
228+
functions
229+
}
230+
231+
/// Parses a type into a String - arg is true if the type is an argument, and
232+
/// false if its a return value.
233+
fn valid_ty(t: &syn::Type) -> bool {
234+
match t {
235+
syn::Type::Ptr(p) => {
236+
let c = p.const_token.is_some();
237+
let m = p.mutability.is_some();
238+
assert!(!(c && m));
239+
match &*p.elem {
240+
syn::Type::Path(_) => valid_ty(&p.elem),
241+
// Only one layer of pointers allowed:
242+
_ => false,
243+
}
244+
}
245+
syn::Type::Path(p) => {
246+
assert!(p.qself.is_none());
247+
assert_eq!(p.path.segments.len(), 1);
248+
let s = p
249+
.path
250+
.segments
251+
.first()
252+
.unwrap()
253+
.into_value()
254+
.ident
255+
.to_string();
256+
match s.as_str() {
257+
"i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize"
258+
| "f32" | "f64" => true,
259+
_ => false,
260+
}
261+
}
262+
_ => false,
263+
}
264+
}
265+
266+
fn get_arg_ids(len: usize) -> Vec<syn::Ident> {
267+
let mut ids = Vec::new();
268+
for i in 0..len {
269+
let x = format!("x{}", i);
270+
ids.push(syn::Ident::new(&x, proc_macro2::Span::call_site()));
271+
}
272+
ids
96273
}

crates/libm-analyze/tests/analyze.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//! Tests that the proc-macro accepts macros with
2+
//! the following pattern:
3+
4+
macro_rules! nop {
5+
(
6+
id: $id:ident;
7+
arg_tys: $($arg_tys:ty),*;
8+
arg_ids: $($arg_ids:ident),*;
9+
ret: $ty:ty;
10+
) => {};
11+
}
12+
13+
libm_analyze::for_each_api!(nop);

0 commit comments

Comments
 (0)