1 // Build script. 2 // 3 // This program is run by Cargo when building cranelift-codegen. It is used to generate Rust code from 4 // the language definitions in the cranelift-codegen/meta directory. 5 // 6 // Environment: 7 // 8 // OUT_DIR 9 // Directory where generated files should be placed. 10 // 11 // TARGET 12 // Target triple provided by Cargo. 13 // 14 // The build script expects to be run from the directory where this build.rs file lives. The 15 // current directory is used to find the sources. 16 17 use cranelift_codegen_meta as meta; 18 use cranelift_isle::error::Errors; 19 use meta::isle::IsleCompilation; 20 21 use std::env; 22 use std::io::Read; 23 use std::process; 24 use std::time::Instant; 25 26 fn main() { 27 let start_time = Instant::now(); 28 29 let out_dir = env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"); 30 let out_dir = std::path::Path::new(&out_dir); 31 let target_triple = env::var("TARGET").expect("The TARGET environment variable must be set"); 32 33 let all_arch = env::var("CARGO_FEATURE_ALL_ARCH").is_ok(); 34 let all_native_arch = env::var("CARGO_FEATURE_ALL_NATIVE_ARCH").is_ok(); 35 36 let mut isas = meta::isa::Isa::all() 37 .iter() 38 .cloned() 39 .filter(|isa| { 40 let env_key = match isa { 41 meta::isa::Isa::Pulley32 | meta::isa::Isa::Pulley64 => { 42 "CARGO_FEATURE_PULLEY".to_string() 43 } 44 _ => format!("CARGO_FEATURE_{}", isa.to_string().to_uppercase()), 45 }; 46 all_arch || env::var(env_key).is_ok() 47 }) 48 .collect::<Vec<_>>(); 49 50 // Don't require host isa if under 'all-arch' feature. 51 let host_isa = env::var("CARGO_FEATURE_HOST_ARCH").is_ok() && !all_native_arch; 52 53 if isas.is_empty() || host_isa { 54 // Try to match native target. 55 let target_name = target_triple.split('-').next().unwrap(); 56 if let Ok(isa) = meta::isa_from_arch(&target_name) { 57 println!("cargo:rustc-cfg=feature=\"{isa}\""); 58 isas.push(isa); 59 } 60 } 61 62 let cur_dir = env::current_dir().expect("Can't access current working directory"); 63 let crate_dir = cur_dir.as_path(); 64 65 println!("cargo:rerun-if-changed=build.rs"); 66 println!("cargo:rerun-if-env-changed=ISLE_SOURCE_DIR"); 67 68 let isle_dir = if let Ok(path) = std::env::var("ISLE_SOURCE_DIR") { 69 // This will canonicalize any relative path in terms of the 70 // crate root, and will take any absolute path as overriding the 71 // `crate_dir`. 72 crate_dir.join(&path) 73 } else { 74 out_dir.into() 75 }; 76 77 std::fs::create_dir_all(&isle_dir).expect("Could not create ISLE source directory"); 78 79 if let Err(err) = meta::generate(&isas, &out_dir, &isle_dir) { 80 eprintln!("Error: {err}"); 81 process::exit(1); 82 } 83 84 if &std::env::var("SKIP_ISLE").unwrap_or("0".to_string()) != "1" { 85 if let Err(err) = build_isle(crate_dir, &isle_dir) { 86 eprintln!("Error: {err}"); 87 process::exit(1); 88 } 89 } 90 91 if env::var("CRANELIFT_VERBOSE").is_ok() { 92 for isa in &isas { 93 println!("cargo:warning=Includes support for {} ISA", isa.to_string()); 94 } 95 println!( 96 "cargo:warning=Build step took {:?}.", 97 Instant::now() - start_time 98 ); 99 println!("cargo:warning=Generated files are in {}", out_dir.display()); 100 } 101 102 let pkg_version = env::var("CARGO_PKG_VERSION").unwrap(); 103 let mut cmd = std::process::Command::new("git"); 104 cmd.arg("rev-parse") 105 .arg("HEAD") 106 .stdout(std::process::Stdio::piped()) 107 .current_dir(env::var("CARGO_MANIFEST_DIR").unwrap()); 108 let version = if let Ok(mut child) = cmd.spawn() { 109 let mut git_rev = String::new(); 110 child 111 .stdout 112 .as_mut() 113 .unwrap() 114 .read_to_string(&mut git_rev) 115 .unwrap(); 116 let status = child.wait().unwrap(); 117 if status.success() { 118 let git_rev = git_rev.trim().chars().take(9).collect::<String>(); 119 format!("{pkg_version}-{git_rev}") 120 } else { 121 // not a git repo 122 pkg_version 123 } 124 } else { 125 // git not available 126 pkg_version 127 }; 128 std::fs::write( 129 std::path::Path::new(&out_dir).join("version.rs"), 130 format!( 131 "/// Version number of this crate. \n\ 132 pub const VERSION: &str = \"{version}\";" 133 ), 134 ) 135 .unwrap(); 136 } 137 138 /// Strip the current directory from the file paths, because `islec` 139 /// includes them in the generated source, and this helps us maintain 140 /// deterministic builds that don't include those local file paths. 141 fn make_isle_source_path_relative( 142 cur_dir: &std::path::Path, 143 filename: &std::path::Path, 144 ) -> std::path::PathBuf { 145 if let Ok(suffix) = filename.strip_prefix(&cur_dir) { 146 suffix.to_path_buf() 147 } else { 148 filename.to_path_buf() 149 } 150 } 151 152 fn build_isle( 153 crate_dir: &std::path::Path, 154 isle_dir: &std::path::Path, 155 ) -> Result<(), Box<dyn std::error::Error + 'static>> { 156 let cur_dir = std::env::current_dir()?; 157 let isle_compilations = meta::isle::get_isle_compilations( 158 &make_isle_source_path_relative(&cur_dir, &crate_dir), 159 &make_isle_source_path_relative(&cur_dir, &isle_dir), 160 ); 161 162 let mut had_error = false; 163 for compilation in &isle_compilations.items { 164 for file in &compilation.inputs { 165 println!("cargo:rerun-if-changed={}", file.display()); 166 } 167 168 if let Err(e) = run_compilation(compilation) { 169 had_error = true; 170 eprintln!("Error building ISLE files:"); 171 eprintln!("{e:?}"); 172 #[cfg(not(feature = "isle-errors"))] 173 { 174 eprintln!("To see a more detailed error report, run: "); 175 eprintln!(); 176 eprintln!(" $ cargo check -p cranelift-codegen --features isle-errors"); 177 eprintln!(); 178 } 179 } 180 } 181 182 if had_error { 183 std::process::exit(1); 184 } 185 186 println!("cargo:rustc-env=ISLE_DIR={}", isle_dir.to_str().unwrap()); 187 188 Ok(()) 189 } 190 191 /// Build ISLE DSL source text into generated Rust code. 192 /// 193 /// NB: This must happen *after* the `cranelift-codegen-meta` functions, since 194 /// it consumes files generated by them. 195 fn run_compilation(compilation: &IsleCompilation) -> Result<(), Errors> { 196 use cranelift_isle as isle; 197 198 eprintln!("Rebuilding {}", compilation.output.display()); 199 200 let code = { 201 let file_paths = compilation 202 .inputs 203 .iter() 204 .chain(compilation.untracked_inputs.iter()); 205 206 let mut options = isle::codegen::CodegenOptions::default(); 207 // Because we include!() the generated ISLE source, we cannot 208 // put the global pragmas (`#![allow(...)]`) in the ISLE 209 // source itself; we have to put them in the source that 210 // include!()s it. (See 211 // https://github.com/rust-lang/rust/issues/47995.) 212 options.exclude_global_allow_pragmas = true; 213 214 // When `cranelift-codegen` is built with detailed tracing enabled, also 215 // ask the ISLE compiler to emit `log::{debug,trace}!` invocations in 216 // the generated code to help debug rule matching. 217 options.emit_logging = std::env::var("CARGO_FEATURE_TRACE_LOG").is_ok(); 218 219 // Enable optional match-arm splitting in iterator terms for 220 // faster compile times in release builds. 221 // 222 // In debug builds, *always* split with an aggressive 223 // threshold, because we cannot rely on rustc doing regalloc 224 // on all of the local bindings to shrink the stack frame to a 225 // reasonable size. 226 #[cfg(not(debug_assertions))] 227 { 228 options.split_match_arms = std::env::var("CARGO_FEATURE_ISLE_SPLIT_MATCH").is_ok(); 229 if let Ok(value) = std::env::var("ISLE_SPLIT_MATCH_THRESHOLD") { 230 options.match_arm_split_threshold = Some(value.parse().unwrap_or_else(|err| { 231 panic!("invalid ISLE_SPLIT_MATCH_THRESHOLD value '{value}': {err}"); 232 })); 233 } 234 } 235 #[cfg(debug_assertions)] 236 { 237 options.split_match_arms = true; 238 options.match_arm_split_threshold = Some(4); 239 } 240 241 if let Ok(out_dir) = std::env::var("OUT_DIR") { 242 options.prefixes.push(isle::codegen::Prefix { 243 prefix: out_dir, 244 name: "<OUT_DIR>".to_string(), 245 }) 246 }; 247 248 isle::compile::from_files(file_paths, &options)? 249 }; 250 251 eprintln!( 252 "Writing ISLE-generated Rust code to {}", 253 compilation.output.display() 254 ); 255 std::fs::write(&compilation.output, code) 256 .map_err(|e| Errors::from_io(e, "failed writing output"))?; 257 258 Ok(()) 259 } 260