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 19 use std::env; 20 use std::io::Read; 21 use std::process; 22 use std::time::Instant; 23 24 fn main() { 25 let start_time = Instant::now(); 26 27 let out_dir = env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"); 28 let target_triple = env::var("TARGET").expect("The TARGET environment variable must be set"); 29 30 let isa_targets = meta::isa::Isa::all() 31 .iter() 32 .cloned() 33 .filter(|isa| { 34 let env_key = format!("CARGO_FEATURE_{}", isa.to_string().to_uppercase()); 35 env::var(env_key).is_ok() 36 }) 37 .collect::<Vec<_>>(); 38 39 let isas = if isa_targets.is_empty() { 40 // Try to match native target. 41 let target_name = target_triple.split('-').next().unwrap(); 42 let isa = meta::isa_from_arch(&target_name).expect("error when identifying target"); 43 println!("cargo:rustc-cfg=feature=\"{}\"", isa); 44 vec![isa] 45 } else { 46 isa_targets 47 }; 48 49 let cur_dir = env::current_dir().expect("Can't access current working directory"); 50 let crate_dir = cur_dir.as_path(); 51 52 println!("cargo:rerun-if-changed=build.rs"); 53 54 let explicit_isle_dir = &crate_dir.join("isle_generated_code"); 55 #[cfg(feature = "isle-in-source-tree")] 56 let isle_dir = explicit_isle_dir; 57 #[cfg(not(feature = "isle-in-source-tree"))] 58 let isle_dir = std::path::Path::new(&out_dir); 59 60 #[cfg(feature = "isle-in-source-tree")] 61 { 62 std::fs::create_dir_all(isle_dir).expect("Could not create ISLE source directory"); 63 } 64 #[cfg(not(feature = "isle-in-source-tree"))] 65 { 66 if explicit_isle_dir.is_dir() { 67 eprintln!(concat!( 68 "Error: directory isle_generated_code/ exists but is only used when\n", 69 "`--feature isle-in-source-tree` is specified. To prevent confusion,\n", 70 "this build script requires the directory to be removed when reverting\n", 71 "to the usual generated code in target/. Please delete the directory and\n", 72 "re-run this build.\n", 73 )); 74 std::process::exit(1); 75 } 76 } 77 78 if let Err(err) = meta::generate(&isas, &out_dir, isle_dir.to_str().unwrap()) { 79 eprintln!("Error: {}", err); 80 process::exit(1); 81 } 82 83 if &std::env::var("SKIP_ISLE").unwrap_or("0".to_string()) != "1" { 84 if let Err(err) = build_isle(crate_dir, isle_dir) { 85 eprintln!("Error: {}", err); 86 process::exit(1); 87 } 88 } 89 90 if env::var("CRANELIFT_VERBOSE").is_ok() { 91 for isa in &isas { 92 println!("cargo:warning=Includes support for {} ISA", isa.to_string()); 93 } 94 println!( 95 "cargo:warning=Build step took {:?}.", 96 Instant::now() - start_time 97 ); 98 println!("cargo:warning=Generated files are in {}", out_dir); 99 } 100 101 let pkg_version = env::var("CARGO_PKG_VERSION").unwrap(); 102 let mut cmd = std::process::Command::new("git"); 103 cmd.arg("rev-parse") 104 .arg("HEAD") 105 .stdout(std::process::Stdio::piped()) 106 .current_dir(env::var("CARGO_MANIFEST_DIR").unwrap()); 107 let version = if let Ok(mut child) = cmd.spawn() { 108 let mut git_rev = String::new(); 109 child 110 .stdout 111 .as_mut() 112 .unwrap() 113 .read_to_string(&mut git_rev) 114 .unwrap(); 115 let status = child.wait().unwrap(); 116 if status.success() { 117 let git_rev = git_rev.trim().chars().take(9).collect::<String>(); 118 format!("{}-{}", pkg_version, git_rev) 119 } else { 120 // not a git repo 121 pkg_version 122 } 123 } else { 124 // git not available 125 pkg_version 126 }; 127 std::fs::write( 128 std::path::Path::new(&out_dir).join("version.rs"), 129 format!( 130 "/// Version number of this crate. \n\ 131 pub const VERSION: &str = \"{}\";", 132 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::PathBuf, 143 filename: std::path::PathBuf, 144 ) -> std::path::PathBuf { 145 if let Ok(suffix) = filename.strip_prefix(&cur_dir) { 146 suffix.to_path_buf() 147 } else { 148 filename 149 } 150 } 151 152 /// A list of compilations (transformations from ISLE source to 153 /// generated Rust source) that exist in the repository. 154 /// 155 /// This list is used either to regenerate the Rust source in-tree (if 156 /// the `rebuild-isle` feature is enabled), or to verify that the ISLE 157 /// source in-tree corresponds to the ISLE source that was last used 158 /// to rebuild the Rust source (if the `rebuild-isle` feature is not 159 /// enabled). 160 #[derive(Clone, Debug)] 161 struct IsleCompilations { 162 items: Vec<IsleCompilation>, 163 } 164 165 #[derive(Clone, Debug)] 166 struct IsleCompilation { 167 output: std::path::PathBuf, 168 inputs: Vec<std::path::PathBuf>, 169 untracked_inputs: Vec<std::path::PathBuf>, 170 } 171 172 /// Construct the list of compilations (transformations from ISLE 173 /// source to generated Rust source) that exist in the repository. 174 fn get_isle_compilations( 175 crate_dir: &std::path::Path, 176 out_dir: &std::path::Path, 177 ) -> Result<IsleCompilations, std::io::Error> { 178 let cur_dir = std::env::current_dir()?; 179 180 let clif_isle = out_dir.join("clif.isle"); 181 let prelude_isle = 182 make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("prelude.isle")); 183 let src_isa_x64 = 184 make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("isa").join("x64")); 185 let src_isa_aarch64 = 186 make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("isa").join("aarch64")); 187 let src_isa_s390x = 188 make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("isa").join("s390x")); 189 190 // This is a set of ISLE compilation units. 191 // 192 // The format of each entry is: 193 // 194 // (output Rust code file, input ISLE source files) 195 // 196 // There should be one entry for each backend that uses ISLE for lowering, 197 // and if/when we replace our peephole optimization passes with ISLE, there 198 // should be an entry for each of those as well. 199 // 200 // N.B.: add any new compilation outputs to 201 // `scripts/force-rebuild-isle.sh` if they do not fit the pattern 202 // `cranelift/codegen/src/isa/*/lower/isle/generated_code.rs`! 203 Ok(IsleCompilations { 204 items: vec![ 205 // The x86-64 instruction selector. 206 IsleCompilation { 207 output: out_dir.join("isle_x64.rs"), 208 inputs: vec![ 209 prelude_isle.clone(), 210 src_isa_x64.join("inst.isle"), 211 src_isa_x64.join("lower.isle"), 212 ], 213 untracked_inputs: vec![clif_isle.clone()], 214 }, 215 // The aarch64 instruction selector. 216 IsleCompilation { 217 output: out_dir.join("isle_aarch64.rs"), 218 inputs: vec![ 219 prelude_isle.clone(), 220 src_isa_aarch64.join("inst.isle"), 221 src_isa_aarch64.join("inst_neon.isle"), 222 src_isa_aarch64.join("lower.isle"), 223 src_isa_aarch64.join("lower_dynamic_neon.isle"), 224 ], 225 untracked_inputs: vec![clif_isle.clone()], 226 }, 227 // The s390x instruction selector. 228 IsleCompilation { 229 output: out_dir.join("isle_s390x.rs"), 230 inputs: vec![ 231 prelude_isle.clone(), 232 src_isa_s390x.join("inst.isle"), 233 src_isa_s390x.join("lower.isle"), 234 ], 235 untracked_inputs: vec![clif_isle.clone()], 236 }, 237 ], 238 }) 239 } 240 241 fn build_isle( 242 crate_dir: &std::path::Path, 243 isle_dir: &std::path::Path, 244 ) -> Result<(), Box<dyn std::error::Error + 'static>> { 245 let isle_compilations = get_isle_compilations(crate_dir, isle_dir)?; 246 247 let mut had_error = false; 248 for compilation in &isle_compilations.items { 249 for file in &compilation.inputs { 250 println!("cargo:rerun-if-changed={}", file.display()); 251 } 252 253 if let Err(e) = run_compilation(compilation) { 254 eprintln!("Error building ISLE files: {:?}", e); 255 let mut source = e.source(); 256 while let Some(e) = source { 257 eprintln!("{:?}", e); 258 source = e.source(); 259 } 260 had_error = true; 261 } 262 } 263 264 if had_error { 265 std::process::exit(1); 266 } 267 268 println!("cargo:rustc-env=ISLE_DIR={}", isle_dir.to_str().unwrap()); 269 270 Ok(()) 271 } 272 273 /// Build ISLE DSL source text into generated Rust code. 274 /// 275 /// NB: This must happen *after* the `cranelift-codegen-meta` functions, since 276 /// it consumes files generated by them. 277 fn run_compilation( 278 compilation: &IsleCompilation, 279 ) -> Result<(), Box<dyn std::error::Error + 'static>> { 280 use cranelift_isle as isle; 281 282 eprintln!("Rebuilding {}", compilation.output.display()); 283 284 let code = (|| { 285 let lexer = isle::lexer::Lexer::from_files( 286 compilation 287 .inputs 288 .iter() 289 .chain(compilation.untracked_inputs.iter()), 290 )?; 291 let defs = isle::parser::parse(lexer)?; 292 293 let mut options = isle::codegen::CodegenOptions::default(); 294 // Because we include!() the generated ISLE source, we cannot 295 // put the global pragmas (`#![allow(...)]`) in the ISLE 296 // source itself; we have to put them in the source that 297 // include!()s it. (See 298 // https://github.com/rust-lang/rust/issues/47995.) 299 options.exclude_global_allow_pragmas = true; 300 301 isle::compile::compile(&defs, &options) 302 })() 303 .map_err(|e| { 304 // Make sure to include the source snippets location info along with 305 // the error messages. 306 307 #[cfg(feature = "isle-errors")] 308 { 309 let report = miette::Report::new(e); 310 return DebugReport(report); 311 312 struct DebugReport(miette::Report); 313 314 impl std::fmt::Display for DebugReport { 315 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 316 self.0.handler().debug(&*self.0, f) 317 } 318 } 319 320 impl std::fmt::Debug for DebugReport { 321 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 322 std::fmt::Display::fmt(self, f) 323 } 324 } 325 326 impl std::error::Error for DebugReport {} 327 } 328 #[cfg(not(feature = "isle-errors"))] 329 { 330 return DebugReport(format!("{}", e)); 331 332 struct DebugReport(String); 333 334 impl std::fmt::Display for DebugReport { 335 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 336 writeln!(f, "ISLE errors:\n\n{}\n", self.0)?; 337 writeln!(f, "To see a more detailed error report, run: ")?; 338 writeln!(f, "")?; 339 writeln!( 340 f, 341 " $ cargo check -p cranelift-codegen --features isle-errors" 342 )?; 343 writeln!(f, "")?; 344 Ok(()) 345 } 346 } 347 348 impl std::fmt::Debug for DebugReport { 349 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 350 std::fmt::Display::fmt(self, f) 351 } 352 } 353 354 impl std::error::Error for DebugReport {} 355 } 356 })?; 357 358 let code = rustfmt(&code).unwrap_or_else(|e| { 359 println!( 360 "cargo:warning=Failed to run `rustfmt` on ISLE-generated code: {:?}", 361 e 362 ); 363 code 364 }); 365 366 eprintln!( 367 "Writing ISLE-generated Rust code to {}", 368 compilation.output.display() 369 ); 370 std::fs::write(&compilation.output, code)?; 371 372 Ok(()) 373 } 374 375 fn rustfmt(code: &str) -> std::io::Result<String> { 376 use std::io::Write; 377 378 let mut rustfmt = std::process::Command::new("rustfmt") 379 .stdin(std::process::Stdio::piped()) 380 .stdout(std::process::Stdio::piped()) 381 .spawn()?; 382 383 let mut stdin = rustfmt.stdin.take().unwrap(); 384 stdin.write_all(code.as_bytes())?; 385 drop(stdin); 386 387 let mut stdout = rustfmt.stdout.take().unwrap(); 388 let mut data = vec![]; 389 stdout.read_to_end(&mut data)?; 390 391 let status = rustfmt.wait()?; 392 if !status.success() { 393 return Err(std::io::Error::new( 394 std::io::ErrorKind::Other, 395 format!("`rustfmt` exited with status {}", status), 396 )); 397 } 398 399 Ok(String::from_utf8(data).expect("rustfmt always writs utf-8 to stdout")) 400 } 401