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
main()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.
make_isle_source_path_relative( cur_dir: &std::path::Path, filename: &std::path::Path, ) -> std::path::PathBuf141 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
build_isle( crate_dir: &std::path::Path, isle_dir: &std::path::Path, ) -> Result<(), Box<dyn std::error::Error + 'static>>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.
run_compilation(compilation: &IsleCompilation) -> Result<(), Errors>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 if cfg!(debug_assertions) {
227 options.split_match_arms = true;
228 options.match_arm_split_threshold = Some(4);
229 } else {
230 options.split_match_arms = std::env::var("CARGO_FEATURE_ISLE_SPLIT_MATCH").is_ok();
231 if let Ok(value) = std::env::var("ISLE_SPLIT_MATCH_THRESHOLD") {
232 options.match_arm_split_threshold = Some(value.parse().unwrap_or_else(|err| {
233 panic!("invalid ISLE_SPLIT_MATCH_THRESHOLD value '{value}': {err}");
234 }));
235 }
236 }
237
238 if let Ok(out_dir) = std::env::var("OUT_DIR") {
239 options.prefixes.push(isle::codegen::Prefix {
240 prefix: out_dir,
241 name: "<OUT_DIR>".to_string(),
242 })
243 };
244
245 isle::compile::from_files(file_paths, &options)?
246 };
247
248 eprintln!(
249 "Writing ISLE-generated Rust code to {}",
250 compilation.output.display()
251 );
252 std::fs::write(&compilation.output, code)
253 .map_err(|e| Errors::from_io(e, "failed writing output"))?;
254
255 Ok(())
256 }
257