Skip to main content

bingen/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3use std::env;
4use std::fs;
5use std::fs::File;
6use std::io::Write;
7use std::process::{Command, Stdio};
8use syn::parse::Parse;
9use syn::parse::ParseStream;
10use syn::{parse_macro_input, LitStr};
11use tempfile::tempdir;
12
13struct Args {
14    arch: LitStr,
15    asm: LitStr,
16}
17
18impl Parse for Args {
19    fn parse(input: ParseStream) -> syn::Result<Self> {
20        let arch = input.parse()?;
21        input.parse::<syn::Token![,]>()?;
22        let asm = input.parse()?;
23        Ok(Args { arch, asm })
24    }
25}
26
27#[derive(Debug)]
28struct LLVMPath {
29    clang: String,
30    llvm_objcopy: String,
31}
32
33#[cfg(target_os = "macos")]
34fn get_llvm_path() -> LLVMPath {
35    let brew = Command::new("brew")
36        .args(["--prefix", "llvm"])
37        .output()
38        .expect("Failed to run brew command");
39    assert!(
40        brew.status.success(),
41        "Failed to get llvm path from brew: {}",
42        String::from_utf8_lossy(&brew.stderr)
43    );
44    let mut base_path = String::from_utf8_lossy(&brew.stdout).to_string();
45    if base_path.ends_with('\n') {
46        base_path.pop();
47        if base_path.ends_with('\r') {
48            base_path.pop();
49        }
50    }
51    LLVMPath {
52        clang: base_path.clone() + &"/bin/clang".to_string(),
53        llvm_objcopy: base_path + &"/bin/llvm-objcopy".to_string(),
54    }
55}
56
57#[cfg(not(target_os = "macos"))]
58fn get_llvm_path() -> LLVMPath {
59    let supported_versions = (6..=13).rev();
60    let mut clang_versions: Vec<String> = vec!["clang".to_string()];
61    let mut objcopy_versions: Vec<String> = vec!["llvm-objcopy".to_string()];
62    for v in supported_versions {
63        clang_versions.extend_from_slice(&[
64            format!("clang-{}", v),
65            format!("clang-{}.0", v),
66            format!("clang-{}0", v),
67        ]);
68        objcopy_versions.extend_from_slice(&[
69            format!("llvm-objcopy-{}", v),
70            format!("llvm-objcopy-{}.0", v),
71            format!("llvm-objcopy-{}0", v),
72        ]);
73    }
74
75    let clang_results = clang_versions.iter().map(which::which);
76    let objcopy_results = objcopy_versions.iter().map(which::which);
77    let results = clang_results.zip(objcopy_results);
78    let mut results = results.filter(|x| x.0.is_ok() && x.1.is_ok());
79
80    let path = results
81        .next()
82        .unwrap_or_else(|| panic!("clang or llvm-objcopy not found"));
83
84    LLVMPath {
85        clang: path.0.unwrap().to_string_lossy().to_string(),
86        llvm_objcopy: path.1.unwrap().to_string_lossy().to_string(),
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    #[ignore]
96    fn print_llvm_path() {
97        let llvm_path = get_llvm_path();
98        println!("{:?}", llvm_path);
99    }
100}
101
102#[proc_macro]
103pub fn bingen(input: TokenStream) -> TokenStream {
104    let Args { arch, asm } = parse_macro_input!(input as Args);
105
106    let env_clang_path = env::var("BINGEN_CLANG_PATH");
107    let env_objcopy_path = env::var("BINGEN_OBJCOPY_PATH");
108    let LLVMPath {
109        clang,
110        llvm_objcopy,
111    } = if env_clang_path.is_err() && env_objcopy_path.is_err() {
112        get_llvm_path()
113    } else {
114        LLVMPath {
115            clang: env_clang_path
116                .as_ref()
117                .expect("BINGEN_CLANG_PATH is not set")
118                .to_string(),
119            llvm_objcopy: env_objcopy_path
120                .as_ref()
121                .expect("BINGEN_OBJCOPY_PATH is not set")
122                .to_string(),
123        }
124    };
125
126    let dir = tempdir().expect("Failed to create a temp dir");
127
128    let mut input = File::create(dir.path().join("bingen.S")).unwrap();
129    input.write_all(asm.value().as_bytes()).unwrap();
130
131    let result = Command::new(clang.clone())
132        .args([
133            "-target",
134            &arch.value(),
135            "-xassembler-with-cpp",
136            "-o",
137            dir.path()
138                .join("bingen.o")
139                .to_str()
140                .expect("Failed to create a str from path"),
141            "-c",
142            dir.path()
143                .join("bingen.S")
144                .to_str()
145                .expect("Failed to create a str from path"),
146        ])
147        .output()
148        .expect("Failed to run clang");
149    assert!(
150        result.status.success(),
151        "{} returned {:?}. stderr:\n{}",
152        clang,
153        result.status.code().expect("exit code not set"),
154        String::from_utf8_lossy(&result.stderr)
155    );
156
157    let result = Command::new(llvm_objcopy.clone())
158        .args([
159            "-O",
160            "binary",
161            dir.path()
162                .join("bingen.o")
163                .to_str()
164                .expect("Failed to create a str from path"),
165            dir.path()
166                .join("bingen.bin")
167                .to_str()
168                .expect("Failed to create a str from path"),
169        ])
170        .stdout(Stdio::piped())
171        .output()
172        .expect("Failed to run objcopy");
173
174    assert!(
175        result.status.success(),
176        "{} returned {:?}. stderr:\n{}",
177        llvm_objcopy,
178        result.status.code().expect("exit code not set"),
179        String::from_utf8_lossy(&result.stderr)
180    );
181
182    format!(
183        "{:?}",
184        fs::read(dir.path().join("bingen.bin")).expect("Failed to open /tmp/bingen.bin")
185    )
186    .parse()
187    .unwrap()
188}