263 lines
8.4 KiB
Rust
263 lines
8.4 KiB
Rust
// Build script.
|
|
//
|
|
// This program is run by Cargo when building cranelift-codegen. It is used to generate Rust code from
|
|
// the language definitions in the cranelift-codegen/meta directory.
|
|
//
|
|
// Environment:
|
|
//
|
|
// OUT_DIR
|
|
// Directory where generated files should be placed.
|
|
//
|
|
// TARGET
|
|
// Target triple provided by Cargo.
|
|
//
|
|
// The build script expects to be run from the directory where this build.rs file lives. The
|
|
// current directory is used to find the sources.
|
|
|
|
use cranelift_codegen_meta as meta;
|
|
|
|
use std::env;
|
|
use std::io::Read;
|
|
use std::process;
|
|
use std::time::Instant;
|
|
|
|
fn main() {
|
|
let start_time = Instant::now();
|
|
|
|
let out_dir = env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set");
|
|
let target_triple = env::var("TARGET").expect("The TARGET environment variable must be set");
|
|
|
|
let isa_targets = meta::isa::Isa::all()
|
|
.iter()
|
|
.cloned()
|
|
.filter(|isa| {
|
|
let env_key = format!("CARGO_FEATURE_{}", isa.to_string().to_uppercase());
|
|
env::var(env_key).is_ok()
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let isas = if isa_targets.is_empty() {
|
|
// Try to match native target.
|
|
let target_name = target_triple.split('-').next().unwrap();
|
|
let isa = meta::isa_from_arch(&target_name).expect("error when identifying target");
|
|
println!("cargo:rustc-cfg=feature=\"{}\"", isa);
|
|
vec![isa]
|
|
} else {
|
|
isa_targets
|
|
};
|
|
|
|
let cur_dir = env::current_dir().expect("Can't access current working directory");
|
|
let crate_dir = cur_dir.as_path();
|
|
|
|
println!("cargo:rerun-if-changed=build.rs");
|
|
|
|
if let Err(err) = meta::generate(&isas, &out_dir, crate_dir) {
|
|
eprintln!("Error: {}", err);
|
|
process::exit(1);
|
|
}
|
|
|
|
if env::var("CRANELIFT_VERBOSE").is_ok() {
|
|
for isa in &isas {
|
|
println!("cargo:warning=Includes support for {} ISA", isa.to_string());
|
|
}
|
|
println!(
|
|
"cargo:warning=Build step took {:?}.",
|
|
Instant::now() - start_time
|
|
);
|
|
println!("cargo:warning=Generated files are in {}", out_dir);
|
|
}
|
|
|
|
#[cfg(feature = "rebuild-peephole-optimizers")]
|
|
{
|
|
let cur_dir = env::current_dir().expect("Can't access current working directory");
|
|
std::fs::write(
|
|
std::path::Path::new(&out_dir).join("CRANELIFT_CODEGEN_PATH"),
|
|
cur_dir.to_str().unwrap(),
|
|
)
|
|
.unwrap()
|
|
}
|
|
|
|
#[cfg(feature = "rebuild-isle")]
|
|
{
|
|
if let Err(e) = rebuild_isle(crate_dir) {
|
|
eprintln!("Error building ISLE files: {:?}", e);
|
|
let mut source = e.source();
|
|
while let Some(e) = source {
|
|
eprintln!("{:?}", e);
|
|
source = e.source();
|
|
}
|
|
std::process::abort();
|
|
}
|
|
}
|
|
|
|
let pkg_version = env::var("CARGO_PKG_VERSION").unwrap();
|
|
let mut cmd = std::process::Command::new("git");
|
|
cmd.arg("rev-parse")
|
|
.arg("HEAD")
|
|
.stdout(std::process::Stdio::piped())
|
|
.current_dir(env::var("CARGO_MANIFEST_DIR").unwrap());
|
|
let version = if let Ok(mut child) = cmd.spawn() {
|
|
let mut git_rev = String::new();
|
|
child
|
|
.stdout
|
|
.as_mut()
|
|
.unwrap()
|
|
.read_to_string(&mut git_rev)
|
|
.unwrap();
|
|
let status = child.wait().unwrap();
|
|
if status.success() {
|
|
let git_rev = git_rev.trim().chars().take(9).collect::<String>();
|
|
format!("{}-{}", pkg_version, git_rev)
|
|
} else {
|
|
// not a git repo
|
|
pkg_version
|
|
}
|
|
} else {
|
|
// git not available
|
|
pkg_version
|
|
};
|
|
std::fs::write(
|
|
std::path::Path::new(&out_dir).join("version.rs"),
|
|
format!(
|
|
"/// Version number of this crate. \n\
|
|
pub const VERSION: &str = \"{}\";",
|
|
version
|
|
),
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
/// Rebuild ISLE DSL source text into generated Rust code.
|
|
///
|
|
/// NB: This must happen *after* the `cranelift-codegen-meta` functions, since
|
|
/// it consumes files generated by them.
|
|
#[cfg(feature = "rebuild-isle")]
|
|
fn rebuild_isle(crate_dir: &std::path::Path) -> Result<(), Box<dyn std::error::Error + 'static>> {
|
|
use std::sync::Once;
|
|
static SET_MIETTE_HOOK: Once = Once::new();
|
|
SET_MIETTE_HOOK.call_once(|| {
|
|
let _ = miette::set_hook(Box::new(|_| {
|
|
Box::new(
|
|
miette::MietteHandlerOpts::new()
|
|
// Ensure `miette` emits source snippets, even when the
|
|
// output is not a tty (NB: there are no terminal control
|
|
// codes in the "graphical" output).
|
|
.force_graphical(true)
|
|
.build(),
|
|
)
|
|
}));
|
|
});
|
|
|
|
let clif_isle = crate_dir.join("src").join("clif.isle");
|
|
let prelude_isle = crate_dir.join("src").join("prelude.isle");
|
|
let src_isa_x64 = crate_dir.join("src").join("isa").join("x64");
|
|
|
|
// This is a set of ISLE compilation units.
|
|
//
|
|
// The format of each entry is:
|
|
//
|
|
// (output Rust code file, input ISLE source files)
|
|
//
|
|
// There should be one entry for each backend that uses ISLE for lowering,
|
|
// and if/when we replace our peephole optimization passes with ISLE, there
|
|
// should be an entry for each of those as well.
|
|
let isle_compilations = vec![
|
|
// The x86-64 instruction selector.
|
|
(
|
|
src_isa_x64
|
|
.join("lower")
|
|
.join("isle")
|
|
.join("generated_code.rs"),
|
|
vec![
|
|
clif_isle,
|
|
prelude_isle,
|
|
src_isa_x64.join("inst.isle"),
|
|
src_isa_x64.join("lower.isle"),
|
|
],
|
|
),
|
|
];
|
|
|
|
let cur_dir = std::env::current_dir()?;
|
|
for (out_file, mut files) in isle_compilations {
|
|
for file in files.iter_mut() {
|
|
println!("cargo:rerun-if-changed={}", file.display());
|
|
|
|
// Strip the current directory from the file paths, because `islec`
|
|
// includes them in the generated source, and this helps us maintain
|
|
// deterministic builds that don't include those local file paths.
|
|
if let Ok(suffix) = file.strip_prefix(&cur_dir) {
|
|
*file = suffix.to_path_buf();
|
|
}
|
|
}
|
|
|
|
let code = (|| {
|
|
let lexer = isle::lexer::Lexer::from_files(files)?;
|
|
let defs = isle::parser::parse(lexer)?;
|
|
isle::compile::compile(&defs)
|
|
})()
|
|
.map_err(|e| {
|
|
// Make sure to include the source snippets location info along with
|
|
// the error messages.
|
|
|
|
let report = miette::Report::new(e);
|
|
return DebugReport(report);
|
|
|
|
struct DebugReport(miette::Report);
|
|
|
|
impl std::fmt::Display for DebugReport {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
self.0.handler().debug(&*self.0, f)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for DebugReport {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
std::fmt::Display::fmt(self, f)
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for DebugReport {}
|
|
})?;
|
|
|
|
let code = rustfmt(&code).unwrap_or_else(|e| {
|
|
println!(
|
|
"cargo:warning=Failed to run `rustfmt` on ISLE-generated code: {:?}",
|
|
e
|
|
);
|
|
code
|
|
});
|
|
|
|
println!("Writing ISLE-generated Rust code to {}", out_file.display());
|
|
std::fs::write(out_file, code)?;
|
|
}
|
|
|
|
return Ok(());
|
|
|
|
fn rustfmt(code: &str) -> std::io::Result<String> {
|
|
use std::io::Write;
|
|
|
|
let mut rustfmt = std::process::Command::new("rustfmt")
|
|
.stdin(std::process::Stdio::piped())
|
|
.stdout(std::process::Stdio::piped())
|
|
.spawn()?;
|
|
|
|
let mut stdin = rustfmt.stdin.take().unwrap();
|
|
stdin.write_all(code.as_bytes())?;
|
|
drop(stdin);
|
|
|
|
let mut stdout = rustfmt.stdout.take().unwrap();
|
|
let mut data = vec![];
|
|
stdout.read_to_end(&mut data)?;
|
|
|
|
let status = rustfmt.wait()?;
|
|
if !status.success() {
|
|
return Err(std::io::Error::new(
|
|
std::io::ErrorKind::Other,
|
|
format!("`rustfmt` exited with status {}", status),
|
|
));
|
|
}
|
|
|
|
Ok(String::from_utf8(data).expect("rustfmt always writs utf-8 to stdout"))
|
|
}
|
|
}
|