use std::ffi::OsStr; use std::{ env, fs::File, io::Write, path::Path, process::{Command, Stdio}, }; use itertools::Itertools; use regex::Regex; /// Extracts the extension-less filename from a [`Path`]. fn path_to_extless_filename(path: &Path) -> &str { path.file_name().map(Path::new).and_then(Path::file_stem).and_then(OsStr::to_str).unwrap() } /// Compiles a source C file into LLVM bitcode. fn compile_file_to_ir(path: &Path, filename_without_ext: &str) { /* * HACK: Sadly, clang doesn't let us emit generic LLVM bitcode. * Compiling for WASM32 and filtering the output with regex is the closest we can get. */ let flags: &[&str] = &[ "--target=wasm32", path.to_str().unwrap(), "-x", "c++", "-fno-discard-value-names", "-fno-exceptions", "-fno-rtti", match env::var("PROFILE").as_deref() { Ok("debug") => "-O0", Ok("release") => "-O3", flavor => panic!("Unknown or missing build flavor {flavor:?}"), }, "-emit-llvm", "-S", "-Wall", "-Wextra", "-o", "-", ]; println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); let out_dir = env::var("OUT_DIR").unwrap(); let out_path = Path::new(&out_dir); let output = Command::new("clang-irrt") .args(flags) .output() .map(|o| { assert!(o.status.success(), "{}", std::str::from_utf8(&o.stderr).unwrap()); o }) .unwrap(); let output = std::str::from_utf8(&output.stdout).unwrap(); let mut filtered_output = String::with_capacity(output.len()); let regex_filter = Regex::new(r"(?ms:^define.*?\}$)|(?m:^declare.*?$)").unwrap(); for f in regex_filter.captures_iter(output) { assert_eq!(f.len(), 1); filtered_output.push_str(&f[0]); filtered_output.push('\n'); } let filtered_output = Regex::new("(#\\d+)|(, *![0-9A-Za-z.]+)|(![0-9A-Za-z.]+)|(!\".*?\")") .unwrap() .replace_all(&filtered_output, ""); println!("cargo:rerun-if-env-changed=DEBUG_DUMP_IRRT"); if env::var("DEBUG_DUMP_IRRT").is_ok() { let mut file = File::create(out_path.join(format!("{filename_without_ext}.ll"))).unwrap(); file.write_all(output.as_bytes()).unwrap(); let mut file = File::create(out_path.join(format!("{filename_without_ext}-filtered.ll"))).unwrap(); file.write_all(filtered_output.as_bytes()).unwrap(); } let mut llvm_as = Command::new("llvm-as-irrt") .stdin(Stdio::piped()) .arg("-o") .arg(out_path.join(format!("{filename_without_ext}.bc"))) .spawn() .unwrap(); llvm_as.stdin.as_mut().unwrap().write_all(filtered_output.as_bytes()).unwrap(); assert!(llvm_as.wait().unwrap().success()); } fn main() { const IRRT_SOURCE_PATHS: &[&str] = &["src/codegen/irrt/irrt.cpp", "src/codegen/tracert/tracert.cpp"]; assert!(IRRT_SOURCE_PATHS.iter().map(Path::new).map(path_to_extless_filename).all_unique()); for path in IRRT_SOURCE_PATHS { let path = Path::new(path); compile_file_to_ir(path, path_to_extless_filename(path)) } }