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 20 use std::env; 21 use std::io::Read; 22 use std::process; 23 use std::time::Instant; 24 25 fn main() { 26 let start_time = Instant::now(); 27 28 let out_dir = env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set"); 29 let target_triple = env::var("TARGET").expect("The TARGET environment variable must be set"); 30 31 let mut isas = meta::isa::Isa::all() 32 .iter() 33 .cloned() 34 .filter(|isa| { 35 let env_key = format!("CARGO_FEATURE_{}", isa.to_string().to_uppercase()); 36 env::var(env_key).is_ok() 37 }) 38 .collect::<Vec<_>>(); 39 40 let host_isa = env::var("CARGO_FEATURE_HOST_ARCH").is_ok(); 41 42 if isas.is_empty() || host_isa { 43 // Try to match native target. 44 let target_name = target_triple.split('-').next().unwrap(); 45 let isa = meta::isa_from_arch(&target_name).expect("error when identifying target"); 46 println!("cargo:rustc-cfg=feature=\"{}\"", isa); 47 isas.push(isa); 48 } 49 50 let cur_dir = env::current_dir().expect("Can't access current working directory"); 51 let crate_dir = cur_dir.as_path(); 52 53 println!("cargo:rerun-if-changed=build.rs"); 54 55 let explicit_isle_dir = &crate_dir.join("isle_generated_code"); 56 #[cfg(feature = "isle-in-source-tree")] 57 let isle_dir = explicit_isle_dir; 58 #[cfg(not(feature = "isle-in-source-tree"))] 59 let isle_dir = std::path::Path::new(&out_dir); 60 61 #[cfg(feature = "isle-in-source-tree")] 62 { 63 std::fs::create_dir_all(isle_dir).expect("Could not create ISLE source directory"); 64 } 65 #[cfg(not(feature = "isle-in-source-tree"))] 66 { 67 if explicit_isle_dir.is_dir() { 68 eprintln!(concat!( 69 "Error: directory isle_generated_code/ exists but is only used when\n", 70 "`--feature isle-in-source-tree` is specified. To prevent confusion,\n", 71 "this build script requires the directory to be removed when reverting\n", 72 "to the usual generated code in target/. Please delete the directory and\n", 73 "re-run this build.\n", 74 )); 75 std::process::exit(1); 76 } 77 } 78 79 if let Err(err) = meta::generate(&isas, &out_dir, isle_dir.to_str().unwrap()) { 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); 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 = \"{}\";", 133 version 134 ), 135 ) 136 .unwrap(); 137 } 138 139 /// Strip the current directory from the file paths, because `islec` 140 /// includes them in the generated source, and this helps us maintain 141 /// deterministic builds that don't include those local file paths. 142 fn make_isle_source_path_relative( 143 cur_dir: &std::path::PathBuf, 144 filename: std::path::PathBuf, 145 ) -> std::path::PathBuf { 146 if let Ok(suffix) = filename.strip_prefix(&cur_dir) { 147 suffix.to_path_buf() 148 } else { 149 filename 150 } 151 } 152 153 /// A list of compilations (transformations from ISLE source to 154 /// generated Rust source) that exist in the repository. 155 /// 156 /// This list is used either to regenerate the Rust source in-tree (if 157 /// the `rebuild-isle` feature is enabled), or to verify that the ISLE 158 /// source in-tree corresponds to the ISLE source that was last used 159 /// to rebuild the Rust source (if the `rebuild-isle` feature is not 160 /// enabled). 161 #[derive(Clone, Debug)] 162 struct IsleCompilations { 163 items: Vec<IsleCompilation>, 164 } 165 166 #[derive(Clone, Debug)] 167 struct IsleCompilation { 168 output: std::path::PathBuf, 169 inputs: Vec<std::path::PathBuf>, 170 untracked_inputs: Vec<std::path::PathBuf>, 171 } 172 173 /// Construct the list of compilations (transformations from ISLE 174 /// source to generated Rust source) that exist in the repository. 175 fn get_isle_compilations( 176 crate_dir: &std::path::Path, 177 out_dir: &std::path::Path, 178 ) -> Result<IsleCompilations, std::io::Error> { 179 let cur_dir = std::env::current_dir()?; 180 181 // Preludes. 182 let clif_lower_isle = out_dir.join("clif_lower.isle"); 183 let clif_opt_isle = out_dir.join("clif_opt.isle"); 184 let prelude_isle = 185 make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("prelude.isle")); 186 let prelude_opt_isle = 187 make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("prelude_opt.isle")); 188 let prelude_lower_isle = 189 make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("prelude_lower.isle")); 190 191 // Directory for mid-end optimizations. 192 let src_opts = make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("opts")); 193 // Directories for lowering backends. 194 let src_isa_x64 = 195 make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("isa").join("x64")); 196 let src_isa_aarch64 = 197 make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("isa").join("aarch64")); 198 let src_isa_s390x = 199 make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("isa").join("s390x")); 200 201 let src_isa_risc_v = 202 make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("isa").join("riscv64")); 203 // This is a set of ISLE compilation units. 204 // 205 // The format of each entry is: 206 // 207 // (output Rust code file, input ISLE source files) 208 // 209 // There should be one entry for each backend that uses ISLE for lowering, 210 // and if/when we replace our peephole optimization passes with ISLE, there 211 // should be an entry for each of those as well. 212 // 213 // N.B.: add any new compilation outputs to 214 // `scripts/force-rebuild-isle.sh` if they do not fit the pattern 215 // `cranelift/codegen/src/isa/*/lower/isle/generated_code.rs`! 216 Ok(IsleCompilations { 217 items: vec![ 218 // The mid-end optimization rules. 219 IsleCompilation { 220 output: out_dir.join("isle_opt.rs"), 221 inputs: vec![ 222 prelude_isle.clone(), 223 prelude_opt_isle, 224 src_opts.join("arithmetic.isle"), 225 src_opts.join("bitops.isle"), 226 src_opts.join("cprop.isle"), 227 src_opts.join("extends.isle"), 228 src_opts.join("icmp.isle"), 229 src_opts.join("remat.isle"), 230 src_opts.join("selects.isle"), 231 src_opts.join("spaceship.isle"), 232 src_opts.join("shifts.isle"), 233 src_opts.join("vector.isle"), 234 ], 235 untracked_inputs: vec![clif_opt_isle], 236 }, 237 // The x86-64 instruction selector. 238 IsleCompilation { 239 output: out_dir.join("isle_x64.rs"), 240 inputs: vec![ 241 prelude_isle.clone(), 242 prelude_lower_isle.clone(), 243 src_isa_x64.join("inst.isle"), 244 src_isa_x64.join("lower.isle"), 245 ], 246 untracked_inputs: vec![clif_lower_isle.clone()], 247 }, 248 // The aarch64 instruction selector. 249 IsleCompilation { 250 output: out_dir.join("isle_aarch64.rs"), 251 inputs: vec![ 252 prelude_isle.clone(), 253 prelude_lower_isle.clone(), 254 src_isa_aarch64.join("inst.isle"), 255 src_isa_aarch64.join("inst_neon.isle"), 256 src_isa_aarch64.join("lower.isle"), 257 src_isa_aarch64.join("lower_dynamic_neon.isle"), 258 ], 259 untracked_inputs: vec![clif_lower_isle.clone()], 260 }, 261 // The s390x instruction selector. 262 IsleCompilation { 263 output: out_dir.join("isle_s390x.rs"), 264 inputs: vec![ 265 prelude_isle.clone(), 266 prelude_lower_isle.clone(), 267 src_isa_s390x.join("inst.isle"), 268 src_isa_s390x.join("lower.isle"), 269 ], 270 untracked_inputs: vec![clif_lower_isle.clone()], 271 }, 272 // The risc-v instruction selector. 273 IsleCompilation { 274 output: out_dir.join("isle_riscv64.rs"), 275 inputs: vec![ 276 prelude_isle.clone(), 277 prelude_lower_isle.clone(), 278 src_isa_risc_v.join("inst.isle"), 279 src_isa_risc_v.join("inst_vector.isle"), 280 src_isa_risc_v.join("lower.isle"), 281 ], 282 untracked_inputs: vec![clif_lower_isle.clone()], 283 }, 284 ], 285 }) 286 } 287 288 fn build_isle( 289 crate_dir: &std::path::Path, 290 isle_dir: &std::path::Path, 291 ) -> Result<(), Box<dyn std::error::Error + 'static>> { 292 let isle_compilations = get_isle_compilations(crate_dir, isle_dir)?; 293 294 let mut had_error = false; 295 for compilation in &isle_compilations.items { 296 for file in &compilation.inputs { 297 println!("cargo:rerun-if-changed={}", file.display()); 298 } 299 300 if let Err(e) = run_compilation(compilation) { 301 had_error = true; 302 eprintln!("Error building ISLE files:"); 303 eprintln!("{:?}", e); 304 #[cfg(not(feature = "isle-errors"))] 305 { 306 eprintln!("To see a more detailed error report, run: "); 307 eprintln!(); 308 eprintln!(" $ cargo check -p cranelift-codegen --features isle-errors"); 309 eprintln!(); 310 } 311 } 312 } 313 314 if had_error { 315 std::process::exit(1); 316 } 317 318 println!("cargo:rustc-env=ISLE_DIR={}", isle_dir.to_str().unwrap()); 319 320 Ok(()) 321 } 322 323 /// Build ISLE DSL source text into generated Rust code. 324 /// 325 /// NB: This must happen *after* the `cranelift-codegen-meta` functions, since 326 /// it consumes files generated by them. 327 fn run_compilation(compilation: &IsleCompilation) -> Result<(), Errors> { 328 use cranelift_isle as isle; 329 330 eprintln!("Rebuilding {}", compilation.output.display()); 331 332 let code = { 333 let file_paths = compilation 334 .inputs 335 .iter() 336 .chain(compilation.untracked_inputs.iter()); 337 338 let mut options = isle::codegen::CodegenOptions::default(); 339 // Because we include!() the generated ISLE source, we cannot 340 // put the global pragmas (`#![allow(...)]`) in the ISLE 341 // source itself; we have to put them in the source that 342 // include!()s it. (See 343 // https://github.com/rust-lang/rust/issues/47995.) 344 options.exclude_global_allow_pragmas = true; 345 346 isle::compile::from_files(file_paths, &options)? 347 }; 348 349 let code = rustfmt(&code).unwrap_or_else(|e| { 350 println!( 351 "cargo:warning=Failed to run `rustfmt` on ISLE-generated code: {:?}", 352 e 353 ); 354 code 355 }); 356 357 eprintln!( 358 "Writing ISLE-generated Rust code to {}", 359 compilation.output.display() 360 ); 361 std::fs::write(&compilation.output, code) 362 .map_err(|e| Errors::from_io(e, "failed writing output"))?; 363 364 Ok(()) 365 } 366 367 fn rustfmt(code: &str) -> std::io::Result<String> { 368 use std::io::Write; 369 370 let mut rustfmt = std::process::Command::new("rustfmt") 371 .stdin(std::process::Stdio::piped()) 372 .stdout(std::process::Stdio::piped()) 373 .spawn()?; 374 375 let mut stdin = rustfmt.stdin.take().unwrap(); 376 stdin.write_all(code.as_bytes())?; 377 drop(stdin); 378 379 let mut stdout = rustfmt.stdout.take().unwrap(); 380 let mut data = vec![]; 381 stdout.read_to_end(&mut data)?; 382 383 let status = rustfmt.wait()?; 384 if !status.success() { 385 return Err(std::io::Error::new( 386 std::io::ErrorKind::Other, 387 format!("`rustfmt` exited with status {}", status), 388 )); 389 } 390 391 Ok(String::from_utf8(data).expect("rustfmt always writs utf-8 to stdout")) 392 } 393