Cranelift: do not check in generated ISLE code; regenerate on every compile. (#4143)
This PR fixes #4066: it modifies the Cranelift `build.rs` workflow to invoke the ISLE DSL compiler on every compilation, rather than only when the user specifies a special "rebuild ISLE" feature. The main benefit of this change is that it vastly simplifies the mental model required of developers, and removes a bunch of failure modes we have tried to work around in other ways. There is now just one "source of truth", the ISLE source itself, in the repository, and so there is no need to understand a special "rebuild" step and how to handle merge errors. There is no special process needed to develop the compiler when modifying the DSL. And there is no "noise" in the git history produced by constantly-regenerated files. The two main downsides we discussed in #4066 are: - Compile time could increase, by adding more to the "meta" step before the main build; - It becomes less obvious where the source definitions are (everything becomes more "magic"), which makes exploration and debugging harder. This PR addresses each of these concerns: 1. To maintain reasonable compile time, it includes work to cut down the dependencies of the `cranelift-isle` crate to *nothing* (only the Rust stdlib), in the default build. It does this by putting the error-reporting bits (`miette` crate) under an optional feature, and the logging (`log` crate) under a feature-controlled macro, and manually writing an `Error` impl rather than using `thiserror`. This completely avoids proc macros and the `syn` build slowness. The user can still get nice errors out of `miette`: this is enabled by specifying a Cargo feature `--features isle-errors`. 2. To allow the user to optionally inspect the generated source, which nominally lives in a hard-to-find path inside `target/` now, this PR adds a feature `isle-in-source-tree` that, as implied by the name, moves the target for ISLE generated source into the source tree, at `cranelift/codegen/isle_generated_source/`. It seems reasonable to do this when an explicit feature (opt-in) is specified because this is how ISLE regeneration currently works as well. To prevent surprises, if the feature is *not* specified, the build fails if this directory exists.
This commit is contained in:
17
.github/workflows/main.yml
vendored
17
.github/workflows/main.yml
vendored
@@ -188,21 +188,6 @@ jobs:
|
||||
# Check that the ISLE fuzz targets build too.
|
||||
- run: cargo fuzz build --dev --fuzz-dir ./cranelift/isle/fuzz
|
||||
|
||||
rebuild_isle:
|
||||
name: Rebuild ISLE
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- uses: ./.github/actions/install-rust
|
||||
- name: Force-rebuild ISLE DSL files
|
||||
run: ./scripts/force-rebuild-isle.sh
|
||||
- name: Reformat
|
||||
run: cargo fmt -p cranelift-codegen
|
||||
- name: Check that the ISLE DSL files are up-to-date
|
||||
run: git diff --exit-code
|
||||
|
||||
# Perform all tests (debug mode) for `wasmtime`. This runs stable/beta/nightly
|
||||
# channels of Rust as well as macOS/Linux/Windows.
|
||||
test:
|
||||
@@ -364,7 +349,7 @@ jobs:
|
||||
submodules: true
|
||||
- name: Install Rust
|
||||
run: rustup update stable && rustup default stable
|
||||
- run: cd cranelift/codegen && cargo build --features "all-arch completely-skip-isle-for-ci-deterministic-check"
|
||||
- run: cd cranelift/codegen && cargo build --features all-arch
|
||||
- run: ci/ensure_deterministic_build.sh
|
||||
|
||||
# Perform release builds of `wasmtime` and `libwasmtime.so`. Builds on
|
||||
|
||||
22
Cargo.lock
generated
22
Cargo.lock
generated
@@ -605,7 +605,6 @@ dependencies = [
|
||||
"log",
|
||||
"miette",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1664,9 +1663,9 @@ checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882"
|
||||
|
||||
[[package]]
|
||||
name = "miette"
|
||||
version = "3.3.0"
|
||||
version = "4.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd2adcfcced5d625bf90a958a82ae5b93231f57f3df1383fee28c9b5096d35ed"
|
||||
checksum = "a565d01cf2dcab463159347d98a18f509fda8e4824e2965c3b430dd045ac1f23"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"backtrace",
|
||||
@@ -1677,15 +1676,16 @@ dependencies = [
|
||||
"supports-hyperlinks",
|
||||
"supports-unicode",
|
||||
"terminal_size",
|
||||
"textwrap 0.14.2",
|
||||
"textwrap 0.15.0",
|
||||
"thiserror",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miette-derive"
|
||||
version = "3.3.0"
|
||||
version = "4.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c01a8b61312d367ce87956bb686731f87e4c6dd5dbc550e8f06e3c24fb1f67f"
|
||||
checksum = "3240cd84b71bf89e5994030c21221f399a882251ed4b89849d3b8610fe8fe8ab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2817,21 +2817,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.14.2"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
dependencies = [
|
||||
"smawk",
|
||||
"unicode-linebreak",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.31"
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
# This script makes sure that the meta crate deterministically generate files
|
||||
# with a high probability.
|
||||
# The current directory must be set to the repository's root.
|
||||
#
|
||||
# We set SKIP_ISLE=1 to skip ISLE generation, because it depends on files
|
||||
# in-tree (cranelift/codegen/.../*.isle) but these are not available when we
|
||||
# switch to different working directories during this test.
|
||||
|
||||
set -e
|
||||
|
||||
@@ -11,7 +15,7 @@ BUILD_SCRIPT=$(find -wholename "./target/debug/build/cranelift-codegen-*/build-s
|
||||
# First, run the script to generate a reference comparison.
|
||||
rm -rf /tmp/reference
|
||||
mkdir /tmp/reference
|
||||
OUT_DIR=/tmp/reference TARGET=x86_64 CARGO_PKG_VERSION=0.1.0 CARGO_MANIFEST_DIR=/tmp $BUILD_SCRIPT
|
||||
SKIP_ISLE=1 OUT_DIR=/tmp/reference TARGET=x86_64 CARGO_PKG_VERSION=0.1.0 CARGO_MANIFEST_DIR=/tmp $BUILD_SCRIPT
|
||||
|
||||
# To make sure the build script doesn't depend on the current directory, we'll
|
||||
# change the current working directory on every iteration. Make this easy to
|
||||
@@ -36,6 +40,6 @@ do
|
||||
|
||||
rm -rf /tmp/try
|
||||
mkdir /tmp/try
|
||||
OUT_DIR=/tmp/try TARGET=x86_64 CARGO_PKG_VERSION=0.1.0 CARGO_MANIFEST_DIR=/tmp/src$i $BUILD_SCRIPT
|
||||
SKIP_ISLE=1 OUT_DIR=/tmp/try TARGET=x86_64 CARGO_PKG_VERSION=0.1.0 CARGO_MANIFEST_DIR=/tmp/src$i $BUILD_SCRIPT
|
||||
diff -qr /tmp/reference /tmp/try
|
||||
done
|
||||
|
||||
@@ -35,8 +35,8 @@ criterion = "0.3"
|
||||
|
||||
[build-dependencies]
|
||||
cranelift-codegen-meta = { path = "meta", version = "0.85.0" }
|
||||
cranelift-isle = { path = "../isle/isle", version = "=0.85.0", optional = true }
|
||||
miette = { version = "3", features = ["fancy"], optional = true }
|
||||
cranelift-isle = { path = "../isle/isle", version = "=0.85.0" }
|
||||
miette = { version = "4.7.0", features = ["fancy"], optional = true }
|
||||
|
||||
[features]
|
||||
default = ["std", "unwind"]
|
||||
@@ -84,12 +84,12 @@ enable-serde = [
|
||||
# Enable support for the Souper harvester.
|
||||
souper-harvest = ["souper-ir", "souper-ir/stringify"]
|
||||
|
||||
# Recompile ISLE DSL source files into their generated Rust code.
|
||||
rebuild-isle = ["cranelift-isle", "miette", "cranelift-codegen-meta/rebuild-isle"]
|
||||
# Provide fancy Miette-produced errors for ISLE.
|
||||
isle-errors = ["miette", "cranelift-isle/miette-errors"]
|
||||
|
||||
# A hack to skip the ISLE-rebuild logic when testing for determinism
|
||||
# with the "Meta deterministic check" CI job.
|
||||
completely-skip-isle-for-ci-deterministic-check = []
|
||||
# Put ISLE generated files in isle_generated_code/, for easier
|
||||
# inspection, rather than inside of target/.
|
||||
isle-in-source-tree = []
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "experimental" }
|
||||
|
||||
@@ -51,11 +51,42 @@ fn main() {
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
if let Err(err) = meta::generate(&isas, &out_dir, crate_dir) {
|
||||
let explicit_isle_dir = &crate_dir.join("isle_generated_code");
|
||||
#[cfg(feature = "isle-in-source-tree")]
|
||||
let isle_dir = explicit_isle_dir;
|
||||
#[cfg(not(feature = "isle-in-source-tree"))]
|
||||
let isle_dir = std::path::Path::new(&out_dir);
|
||||
|
||||
#[cfg(feature = "isle-in-source-tree")]
|
||||
{
|
||||
std::fs::create_dir_all(isle_dir).expect("Could not create ISLE source directory");
|
||||
}
|
||||
#[cfg(not(feature = "isle-in-source-tree"))]
|
||||
{
|
||||
if explicit_isle_dir.is_dir() {
|
||||
eprintln!(concat!(
|
||||
"Error: directory isle_generated_code/ exists but is only used when\n",
|
||||
"`--feature isle-in-source-tree` is specified. To prevent confusion,\n",
|
||||
"this build script requires the directory to be removed when reverting\n",
|
||||
"to the usual generated code in target/. Please delete the directory and\n",
|
||||
"re-run this build.\n",
|
||||
));
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = meta::generate(&isas, &out_dir, isle_dir.to_str().unwrap()) {
|
||||
eprintln!("Error: {}", err);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
if &std::env::var("SKIP_ISLE").unwrap_or("0".to_string()) != "1" {
|
||||
if let Err(err) = build_isle(crate_dir, isle_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());
|
||||
@@ -67,18 +98,6 @@ fn main() {
|
||||
println!("cargo:warning=Generated files are in {}", out_dir);
|
||||
}
|
||||
|
||||
// The "Meta deterministic check" CI job runs this build script N
|
||||
// times to ensure it produces the same output
|
||||
// consistently. However, it runs the script in a fresh directory,
|
||||
// without any of the source tree present; this breaks our
|
||||
// manifest check (we need the ISLE source to be present). To keep
|
||||
// things simple, we just disable all ISLE-related logic for this
|
||||
// specific CI job.
|
||||
#[cfg(not(feature = "completely-skip-isle-for-ci-deterministic-check"))]
|
||||
{
|
||||
maybe_rebuild_isle(crate_dir).expect("Unhandled failure in ISLE rebuild");
|
||||
}
|
||||
|
||||
let pkg_version = env::var("CARGO_PKG_VERSION").unwrap();
|
||||
let mut cmd = std::process::Command::new("git");
|
||||
cmd.arg("rev-parse")
|
||||
@@ -149,71 +168,15 @@ struct IsleCompilation {
|
||||
inputs: Vec<std::path::PathBuf>,
|
||||
}
|
||||
|
||||
impl IsleCompilation {
|
||||
/// Compute the manifest filename for the given generated Rust file.
|
||||
fn manifest_filename(&self) -> std::path::PathBuf {
|
||||
self.output.with_extension("manifest")
|
||||
}
|
||||
|
||||
/// Compute the content of the source manifest for all ISLE source
|
||||
/// files that go into the compilation of one Rust file.
|
||||
///
|
||||
/// We store this alongside the `<generated_filename>.rs` file as
|
||||
/// `<generated_filename>.manifest` and use it to verify that a
|
||||
/// rebuild was done if necessary.
|
||||
fn compute_manifest(&self) -> Result<String, Box<dyn std::error::Error + 'static>> {
|
||||
// We use the deprecated SipHasher from std::hash in order to verify
|
||||
// that ISLE sources haven't changed since the generated source was
|
||||
// last regenerated.
|
||||
//
|
||||
// We use this instead of a stronger and more usual content hash, like
|
||||
// SHA-{160,256,512}, because it's built into the standard library and
|
||||
// we don't want to pull in a separate crate. We try to keep Cranelift
|
||||
// crate dependencies as intentionally small as possible. In fact, we
|
||||
// used to use the `sha2` crate for SHA-512 and this turns out to be
|
||||
// undesirable for downstream consumers (see #3609).
|
||||
//
|
||||
// Why not the recommended replacement
|
||||
// `std::collections::hash_map::DefaultHasher`? Because we need the
|
||||
// hash to be deterministic, both between runs (i.e., not seeded with
|
||||
// random state) and across Rust versions.
|
||||
//
|
||||
// If `SipHasher` is ever actually removed from `std`, we'll need to
|
||||
// find a new option, either a very small crate or something else
|
||||
// that's built-in.
|
||||
#![allow(deprecated)]
|
||||
use std::fmt::Write;
|
||||
use std::hash::{Hasher, SipHasher};
|
||||
|
||||
let mut manifest = String::new();
|
||||
|
||||
for filename in &self.inputs {
|
||||
// Our source must be valid UTF-8 for this to work, else user
|
||||
// will get an error on build. This is not expected to be an
|
||||
// issue.
|
||||
let content = std::fs::read_to_string(filename)?;
|
||||
// On Windows, source is checked out with line-endings changed
|
||||
// to `\r\n`; canonicalize the source that we hash to
|
||||
// Unix-style (`\n`) so hashes will match.
|
||||
let content = content.replace("\r\n", "\n");
|
||||
// One line in the manifest: <filename> <siphash>.
|
||||
let mut hasher = SipHasher::new_with_keys(0, 0); // fixed keys for determinism
|
||||
hasher.write(content.as_bytes());
|
||||
let filename = format!("{}", filename.display()).replace("\\", "/");
|
||||
writeln!(&mut manifest, "{} {:x}", filename, hasher.finish())?;
|
||||
}
|
||||
|
||||
Ok(manifest)
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct the list of compilations (transformations from ISLE
|
||||
/// source to generated Rust source) that exist in the repository.
|
||||
fn get_isle_compilations(crate_dir: &std::path::Path) -> Result<IsleCompilations, std::io::Error> {
|
||||
fn get_isle_compilations(
|
||||
crate_dir: &std::path::Path,
|
||||
out_dir: &std::path::Path,
|
||||
) -> Result<IsleCompilations, std::io::Error> {
|
||||
let cur_dir = std::env::current_dir()?;
|
||||
|
||||
let clif_isle =
|
||||
make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("clif.isle"));
|
||||
let clif_isle = out_dir.join("clif.isle");
|
||||
let prelude_isle =
|
||||
make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("prelude.isle"));
|
||||
let src_isa_x64 =
|
||||
@@ -240,10 +203,7 @@ fn get_isle_compilations(crate_dir: &std::path::Path) -> Result<IsleCompilations
|
||||
items: vec![
|
||||
// The x86-64 instruction selector.
|
||||
IsleCompilation {
|
||||
output: src_isa_x64
|
||||
.join("lower")
|
||||
.join("isle")
|
||||
.join("generated_code.rs"),
|
||||
output: out_dir.join("isle_x64.rs"),
|
||||
inputs: vec![
|
||||
clif_isle.clone(),
|
||||
prelude_isle.clone(),
|
||||
@@ -253,10 +213,7 @@ fn get_isle_compilations(crate_dir: &std::path::Path) -> Result<IsleCompilations
|
||||
},
|
||||
// The aarch64 instruction selector.
|
||||
IsleCompilation {
|
||||
output: src_isa_aarch64
|
||||
.join("lower")
|
||||
.join("isle")
|
||||
.join("generated_code.rs"),
|
||||
output: out_dir.join("isle_aarch64.rs"),
|
||||
inputs: vec![
|
||||
clif_isle.clone(),
|
||||
prelude_isle.clone(),
|
||||
@@ -266,10 +223,7 @@ fn get_isle_compilations(crate_dir: &std::path::Path) -> Result<IsleCompilations
|
||||
},
|
||||
// The s390x instruction selector.
|
||||
IsleCompilation {
|
||||
output: src_isa_s390x
|
||||
.join("lower")
|
||||
.join("isle")
|
||||
.join("generated_code.rs"),
|
||||
output: out_dir.join("isle_s390x.rs"),
|
||||
inputs: vec![
|
||||
clif_isle.clone(),
|
||||
prelude_isle.clone(),
|
||||
@@ -281,170 +235,116 @@ fn get_isle_compilations(crate_dir: &std::path::Path) -> Result<IsleCompilations
|
||||
})
|
||||
}
|
||||
|
||||
/// Check the manifest for the ISLE generated code, which documents
|
||||
/// what ISLE source went into generating the Rust, and if there is a
|
||||
/// mismatch, either invoke the ISLE compiler (if we have the
|
||||
/// `rebuild-isle` feature) or exit with an error (if not).
|
||||
///
|
||||
/// We do this by computing a hash of the ISLE source and checking it
|
||||
/// against a "manifest" that is also checked into git, alongside the
|
||||
/// generated Rust.
|
||||
///
|
||||
/// (Why not include the `rebuild-isle` feature by default? Because
|
||||
/// the build process must not modify the checked-in source by
|
||||
/// default; any checked-in source is a human-managed bit of data, and
|
||||
/// we can only act as an agent of the human developer when explicitly
|
||||
/// requested to do so. This manifest check is a middle ground that
|
||||
/// ensures this explicit control while also avoiding the easy footgun
|
||||
/// of "I changed the ISLE, why isn't the compiler updated?!".)
|
||||
fn maybe_rebuild_isle(
|
||||
fn build_isle(
|
||||
crate_dir: &std::path::Path,
|
||||
isle_dir: &std::path::Path,
|
||||
) -> Result<(), Box<dyn std::error::Error + 'static>> {
|
||||
let isle_compilations = get_isle_compilations(crate_dir)?;
|
||||
let mut rebuild_compilations = vec![];
|
||||
let isle_compilations = get_isle_compilations(crate_dir, isle_dir)?;
|
||||
|
||||
let mut had_error = false;
|
||||
for compilation in &isle_compilations.items {
|
||||
for file in &compilation.inputs {
|
||||
println!("cargo:rerun-if-changed={}", file.display());
|
||||
}
|
||||
|
||||
let manifest =
|
||||
std::fs::read_to_string(compilation.manifest_filename()).unwrap_or(String::new());
|
||||
// Canonicalize Windows line-endings into Unix line-endings in
|
||||
// the manifest text itself.
|
||||
let manifest = manifest.replace("\r\n", "\n");
|
||||
let expected_manifest = compilation.compute_manifest()?.replace("\r\n", "\n");
|
||||
if manifest != expected_manifest {
|
||||
rebuild_compilations.push((compilation, expected_manifest));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rebuild-isle")]
|
||||
{
|
||||
if !rebuild_compilations.is_empty() {
|
||||
set_miette_hook();
|
||||
}
|
||||
let mut had_error = false;
|
||||
for (compilation, expected_manifest) in rebuild_compilations {
|
||||
if let Err(e) = rebuild_isle(compilation, &expected_manifest) {
|
||||
eprintln!("Error building ISLE files: {:?}", e);
|
||||
let mut source = e.source();
|
||||
while let Some(e) = source {
|
||||
eprintln!("{:?}", e);
|
||||
source = e.source();
|
||||
}
|
||||
had_error = true;
|
||||
if let Err(e) = run_compilation(compilation) {
|
||||
eprintln!("Error building ISLE files: {:?}", e);
|
||||
let mut source = e.source();
|
||||
while let Some(e) = source {
|
||||
eprintln!("{:?}", e);
|
||||
source = e.source();
|
||||
}
|
||||
}
|
||||
|
||||
if had_error {
|
||||
std::process::exit(1);
|
||||
had_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "rebuild-isle"))]
|
||||
{
|
||||
if !rebuild_compilations.is_empty() {
|
||||
for (compilation, _) in rebuild_compilations {
|
||||
eprintln!("");
|
||||
eprintln!(
|
||||
"Error: the ISLE source files that resulted in the generated Rust source"
|
||||
);
|
||||
eprintln!("");
|
||||
eprintln!(" * {}", compilation.output.display());
|
||||
eprintln!("");
|
||||
eprintln!(
|
||||
"have changed but the generated source was not rebuilt! These ISLE source"
|
||||
);
|
||||
eprintln!("files are:");
|
||||
eprintln!("");
|
||||
for file in &compilation.inputs {
|
||||
eprintln!(" * {}", file.display());
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("");
|
||||
eprintln!("Please add `--features rebuild-isle` to your `cargo build` command");
|
||||
eprintln!("if you wish to rebuild the generated source, then include these changes");
|
||||
eprintln!("in any git commits you make that include the changes to the ISLE.");
|
||||
eprintln!("");
|
||||
eprintln!("For example:");
|
||||
eprintln!("");
|
||||
eprintln!(" $ cargo build -p cranelift-codegen --features rebuild-isle");
|
||||
eprintln!("");
|
||||
eprintln!("(This build script cannot do this for you by default because we cannot");
|
||||
eprintln!("modify checked-into-git source without your explicit opt-in.)");
|
||||
eprintln!("");
|
||||
std::process::exit(1);
|
||||
}
|
||||
if had_error {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
println!("cargo:rustc-env=ISLE_DIR={}", isle_dir.to_str().unwrap());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "rebuild-isle")]
|
||||
fn set_miette_hook() {
|
||||
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()
|
||||
// This is necessary for `miette` to properly display errors
|
||||
// until https://github.com/zkat/miette/issues/93 is fixed.
|
||||
.force_graphical(true)
|
||||
.build(),
|
||||
)
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
/// Rebuild ISLE DSL source text into generated Rust code.
|
||||
/// Build 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(
|
||||
fn run_compilation(
|
||||
compilation: &IsleCompilation,
|
||||
manifest: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error + 'static>> {
|
||||
use cranelift_isle as isle;
|
||||
|
||||
println!("Rebuilding {}", compilation.output.display());
|
||||
|
||||
// First, remove the manifest, if any; we will recreate it
|
||||
// below if the compilation is successful. Ignore error if no
|
||||
// manifest was present.
|
||||
let manifest_filename = compilation.manifest_filename();
|
||||
let _ = std::fs::remove_file(&manifest_filename);
|
||||
|
||||
let code = (|| {
|
||||
let lexer = isle::lexer::Lexer::from_files(&compilation.inputs[..])?;
|
||||
let defs = isle::parser::parse(lexer)?;
|
||||
isle::compile::compile(&defs)
|
||||
|
||||
let mut options = isle::codegen::CodegenOptions::default();
|
||||
// Because we include!() the generated ISLE source, we cannot
|
||||
// put the global pragmas (`#![allow(...)]`) in the ISLE
|
||||
// source itself; we have to put them in the source that
|
||||
// include!()s it. (See
|
||||
// https://github.com/rust-lang/rust/issues/47995.)
|
||||
options.exclude_global_allow_pragmas = true;
|
||||
|
||||
isle::compile::compile(&defs, &options)
|
||||
})()
|
||||
.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);
|
||||
#[cfg(feature = "isle-errors")]
|
||||
{
|
||||
let report = miette::Report::new(e);
|
||||
return DebugReport(report);
|
||||
|
||||
struct DebugReport(miette::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::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::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 {}
|
||||
impl std::error::Error for DebugReport {}
|
||||
}
|
||||
#[cfg(not(feature = "isle-errors"))]
|
||||
{
|
||||
return DebugReport(format!("{}", e));
|
||||
|
||||
struct DebugReport(String);
|
||||
|
||||
impl std::fmt::Display for DebugReport {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
writeln!(f, "ISLE errors:\n\n{}\n", self.0)?;
|
||||
writeln!(f, "To see a more detailed error report, run: ")?;
|
||||
writeln!(f, "")?;
|
||||
writeln!(
|
||||
f,
|
||||
" $ cargo check -p cranelift-codegen --features isle-errors"
|
||||
)?;
|
||||
writeln!(f, "")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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| {
|
||||
@@ -461,39 +361,32 @@ fn rebuild_isle(
|
||||
);
|
||||
std::fs::write(&compilation.output, code)?;
|
||||
|
||||
// Write the manifest so that, in the default build configuration
|
||||
// without the `rebuild-isle` feature, we can at least verify that
|
||||
// no changes were made that will not be picked up. Note that we
|
||||
// only write this *after* we write the source above, so no
|
||||
// manifest is produced if there was an error.
|
||||
std::fs::write(&manifest_filename, manifest)?;
|
||||
|
||||
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"))
|
||||
}
|
||||
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"))
|
||||
}
|
||||
|
||||
@@ -17,6 +17,3 @@ cranelift-codegen-shared = { path = "../shared", version = "0.85.0" }
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "experimental" }
|
||||
|
||||
[features]
|
||||
rebuild-isle = []
|
||||
|
||||
@@ -113,7 +113,6 @@ pub(crate) enum OperandKindFields {
|
||||
|
||||
impl OperandKindFields {
|
||||
/// Return the [EnumValues] for this field if it is an `enum`.
|
||||
#[cfg(feature = "rebuild-isle")]
|
||||
pub(crate) fn enum_values(&self) -> Option<&EnumValues> {
|
||||
match self {
|
||||
OperandKindFields::ImmEnum(map) => Some(map),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! Generate instruction data (including opcodes, formats, builders, etc.).
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
||||
use cranelift_codegen_shared::constant_hash;
|
||||
|
||||
@@ -1085,7 +1084,6 @@ fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Fo
|
||||
fmtln!(fmt, "}")
|
||||
}
|
||||
|
||||
#[cfg(feature = "rebuild-isle")]
|
||||
fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt: &mut Formatter) {
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fmt::Write;
|
||||
@@ -1341,7 +1339,6 @@ fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt:
|
||||
}
|
||||
|
||||
/// Generate an `enum` immediate in ISLE.
|
||||
#[cfg(feature = "rebuild-isle")]
|
||||
fn gen_isle_enum(name: &str, mut variants: Vec<&str>, fmt: &mut Formatter) {
|
||||
variants.sort();
|
||||
let prefix = format!(";;;; Enumerated Immediate: {} ", name);
|
||||
@@ -1407,7 +1404,7 @@ pub(crate) fn generate(
|
||||
inst_builder_filename: &str,
|
||||
isle_filename: &str,
|
||||
out_dir: &str,
|
||||
crate_dir: &Path,
|
||||
isle_dir: &str,
|
||||
) -> Result<(), error::Error> {
|
||||
// Opcodes.
|
||||
let mut fmt = Formatter::new();
|
||||
@@ -1424,18 +1421,9 @@ pub(crate) fn generate(
|
||||
fmt.update_file(opcode_filename, out_dir)?;
|
||||
|
||||
// ISLE DSL.
|
||||
#[cfg(feature = "rebuild-isle")]
|
||||
{
|
||||
let mut fmt = Formatter::new();
|
||||
gen_isle(&formats, all_inst, &mut fmt);
|
||||
let crate_src_dir = crate_dir.join("src");
|
||||
fmt.update_file(isle_filename, &crate_src_dir.display().to_string())?;
|
||||
}
|
||||
#[cfg(not(feature = "rebuild-isle"))]
|
||||
{
|
||||
// Silence unused variable warnings.
|
||||
let _ = (isle_filename, crate_dir);
|
||||
}
|
||||
let mut fmt = Formatter::new();
|
||||
gen_isle(&formats, all_inst, &mut fmt);
|
||||
fmt.update_file(isle_filename, isle_dir)?;
|
||||
|
||||
// Instruction builder.
|
||||
let mut fmt = Formatter::new();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! This crate generates Rust sources for use by
|
||||
//! [`cranelift_codegen`](../cranelift_codegen/index.html).
|
||||
|
||||
use std::path::Path;
|
||||
#[macro_use]
|
||||
mod cdsl;
|
||||
mod srcgen;
|
||||
@@ -23,7 +22,7 @@ pub fn isa_from_arch(arch: &str) -> Result<isa::Isa, String> {
|
||||
}
|
||||
|
||||
/// Generates all the Rust source files used in Cranelift from the meta-language.
|
||||
pub fn generate(isas: &[isa::Isa], out_dir: &str, crate_dir: &Path) -> Result<(), error::Error> {
|
||||
pub fn generate(isas: &[isa::Isa], out_dir: &str, isle_dir: &str) -> Result<(), error::Error> {
|
||||
// Create all the definitions:
|
||||
// - common definitions.
|
||||
let mut shared_defs = shared::define();
|
||||
@@ -50,7 +49,7 @@ pub fn generate(isas: &[isa::Isa], out_dir: &str, crate_dir: &Path) -> Result<()
|
||||
"inst_builder.rs",
|
||||
"clif.isle",
|
||||
&out_dir,
|
||||
crate_dir,
|
||||
isle_dir,
|
||||
)?;
|
||||
|
||||
for isa in target_isas {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
src/clif.isle 443b34b797fc8ace
|
||||
src/prelude.isle e6c91b0115343ab9
|
||||
src/isa/aarch64/inst.isle 21a43af20be377d2
|
||||
src/isa/aarch64/lower.isle 75ad8450963e3829
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
src/clif.isle 443b34b797fc8ace
|
||||
src/prelude.isle e6c91b0115343ab9
|
||||
src/isa/s390x/inst.isle 36c2500563cdd4e6
|
||||
src/isa/s390x/lower.isle e5c946ab8a265b77
|
||||
13835
cranelift/codegen/src/isa/s390x/lower/isle/generated_code.rs
generated
13835
cranelift/codegen/src/isa/s390x/lower/isle/generated_code.rs
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
src/clif.isle 443b34b797fc8ace
|
||||
src/prelude.isle e6c91b0115343ab9
|
||||
src/isa/x64/inst.isle 833710d359126637
|
||||
src/isa/x64/lower.isle 4c567e9157f84afb
|
||||
12337
cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs
generated
12337
cranelift/codegen/src/isa/x64/lower/isle/generated_code.rs
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::default::Default;
|
||||
|
||||
fuzz_target!(|s: &str| {
|
||||
let _ = env_logger::try_init();
|
||||
@@ -19,7 +20,7 @@ fuzz_target!(|s: &str| {
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let code = cranelift_isle::compile::compile(&defs);
|
||||
let code = cranelift_isle::compile::compile(&defs, &Default::default());
|
||||
log::debug!("code = {:?}", code);
|
||||
let code = match code {
|
||||
Ok(c) => c,
|
||||
|
||||
@@ -9,9 +9,14 @@ repository = "https://github.com/bytecodealliance/wasmtime/tree/main/cranelift/i
|
||||
version = "0.85.0"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
miette = "3.0.0"
|
||||
thiserror = "1.0.29"
|
||||
log = { version = "0.4", optional = true }
|
||||
miette = { version = "4.7.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
logging = ["log"]
|
||||
miette-errors = ["miette"]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use crate::lexer::Pos;
|
||||
use crate::log;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The parsed form of an ISLE file.
|
||||
@@ -172,7 +173,7 @@ impl Pattern {
|
||||
}
|
||||
|
||||
pub fn make_macro_template(&self, macro_args: &[Ident]) -> Pattern {
|
||||
log::trace!("make_macro_template: {:?} with {:?}", self, macro_args);
|
||||
log!("make_macro_template: {:?} with {:?}", self, macro_args);
|
||||
match self {
|
||||
&Pattern::BindPattern {
|
||||
ref var,
|
||||
@@ -233,7 +234,7 @@ impl Pattern {
|
||||
}
|
||||
|
||||
pub fn subst_macro_args(&self, macro_args: &[Pattern]) -> Option<Pattern> {
|
||||
log::trace!("subst_macro_args: {:?} with {:?}", self, macro_args);
|
||||
log!("subst_macro_args: {:?} with {:?}", self, macro_args);
|
||||
match self {
|
||||
&Pattern::BindPattern {
|
||||
ref var,
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
//! Generate Rust code from a series of Sequences.
|
||||
|
||||
use crate::ir::{ExprInst, InstId, PatternInst, Value};
|
||||
use crate::log;
|
||||
use crate::sema::ExternalSig;
|
||||
use crate::sema::{TermEnv, TermId, Type, TypeEnv, TypeId, Variant};
|
||||
use crate::trie::{TrieEdge, TrieNode, TrieSymbol};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fmt::Write;
|
||||
|
||||
/// Options for code generation.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct CodegenOptions {
|
||||
/// Do not include the `#![allow(...)]` pragmas in the generated
|
||||
/// source. Useful if it must be include!()'d elsewhere.
|
||||
pub exclude_global_allow_pragmas: bool,
|
||||
}
|
||||
|
||||
/// Emit Rust source code for the given type and term environments.
|
||||
pub fn codegen(typeenv: &TypeEnv, termenv: &TermEnv, tries: &BTreeMap<TermId, TrieNode>) -> String {
|
||||
Codegen::compile(typeenv, termenv, tries).generate_rust()
|
||||
pub fn codegen(
|
||||
typeenv: &TypeEnv,
|
||||
termenv: &TermEnv,
|
||||
tries: &BTreeMap<TermId, TrieNode>,
|
||||
options: &CodegenOptions,
|
||||
) -> String {
|
||||
Codegen::compile(typeenv, termenv, tries).generate_rust(options)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -38,10 +52,10 @@ impl<'a> Codegen<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_rust(&self) -> String {
|
||||
fn generate_rust(&self, options: &CodegenOptions) -> String {
|
||||
let mut code = String::new();
|
||||
|
||||
self.generate_header(&mut code);
|
||||
self.generate_header(&mut code, options);
|
||||
self.generate_ctx_trait(&mut code);
|
||||
self.generate_internal_types(&mut code);
|
||||
self.generate_internal_term_constructors(&mut code);
|
||||
@@ -49,7 +63,7 @@ impl<'a> Codegen<'a> {
|
||||
code
|
||||
}
|
||||
|
||||
fn generate_header(&self, code: &mut String) {
|
||||
fn generate_header(&self, code: &mut String, options: &CodegenOptions) {
|
||||
writeln!(code, "// GENERATED BY ISLE. DO NOT EDIT!").unwrap();
|
||||
writeln!(code, "//").unwrap();
|
||||
writeln!(
|
||||
@@ -61,17 +75,19 @@ impl<'a> Codegen<'a> {
|
||||
writeln!(code, "// - {}", file).unwrap();
|
||||
}
|
||||
|
||||
writeln!(
|
||||
code,
|
||||
"\n#![allow(dead_code, unreachable_code, unreachable_patterns)]"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
code,
|
||||
"#![allow(unused_imports, unused_variables, non_snake_case, unused_mut)]"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(code, "#![allow(irrefutable_let_patterns)]").unwrap();
|
||||
if !options.exclude_global_allow_pragmas {
|
||||
writeln!(
|
||||
code,
|
||||
"\n#![allow(dead_code, unreachable_code, unreachable_patterns)]"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
code,
|
||||
"#![allow(unused_imports, unused_variables, non_snake_case, unused_mut)]"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(code, "#![allow(irrefutable_let_patterns)]").unwrap();
|
||||
}
|
||||
|
||||
writeln!(code, "\nuse super::*; // Pulls in all external types.").unwrap();
|
||||
}
|
||||
@@ -311,7 +327,7 @@ impl<'a> Codegen<'a> {
|
||||
ctx: &mut BodyContext,
|
||||
returns: &mut Vec<(usize, String)>,
|
||||
) {
|
||||
log::trace!("generate_expr_inst: {:?}", inst);
|
||||
log!("generate_expr_inst: {:?}", inst);
|
||||
match inst {
|
||||
&ExprInst::ConstInt { ty, val } => {
|
||||
let value = Value::Expr {
|
||||
@@ -665,7 +681,7 @@ impl<'a> Codegen<'a> {
|
||||
indent: &str,
|
||||
ctx: &mut BodyContext,
|
||||
) -> bool {
|
||||
log::trace!("generate_body:\n{}", trie.pretty());
|
||||
log!("generate_body:\n{}", trie.pretty());
|
||||
let mut returned = false;
|
||||
match trie {
|
||||
&TrieNode::Empty => {}
|
||||
@@ -706,7 +722,7 @@ impl<'a> Codegen<'a> {
|
||||
let mut last = i;
|
||||
let mut adjacent_variants = BTreeSet::new();
|
||||
let mut adjacent_variant_input = None;
|
||||
log::trace!(
|
||||
log!(
|
||||
"edge: prio = {:?}, symbol = {:?}",
|
||||
edges[i].prio,
|
||||
edges[i].symbol
|
||||
|
||||
@@ -4,9 +4,9 @@ use crate::error::Result;
|
||||
use crate::{ast, codegen, sema, trie};
|
||||
|
||||
/// Compile the given AST definitions into Rust source code.
|
||||
pub fn compile(defs: &ast::Defs) -> Result<String> {
|
||||
pub fn compile(defs: &ast::Defs, options: &codegen::CodegenOptions) -> Result<String> {
|
||||
let mut typeenv = sema::TypeEnv::from_ast(defs)?;
|
||||
let termenv = sema::TermEnv::from_ast(&mut typeenv, defs)?;
|
||||
let tries = trie::build_tries(&typeenv, &termenv);
|
||||
Ok(codegen::codegen(&typeenv, &termenv, &tries))
|
||||
Ok(codegen::codegen(&typeenv, &termenv, &tries, options))
|
||||
}
|
||||
|
||||
@@ -1,62 +1,47 @@
|
||||
//! Error types.
|
||||
|
||||
use miette::{Diagnostic, SourceCode, SourceSpan};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Either `Ok(T)` or `Err(isle::Error)`.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Errors produced by ISLE.
|
||||
#[derive(thiserror::Error, Diagnostic, Clone, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Error {
|
||||
/// An I/O error.
|
||||
#[error("{context}")]
|
||||
IoError {
|
||||
/// The underlying I/O error.
|
||||
#[source]
|
||||
error: Arc<std::io::Error>,
|
||||
/// The context explaining what caused the I/O error.
|
||||
context: String,
|
||||
},
|
||||
|
||||
/// The input ISLE source has a parse error.
|
||||
#[error("parse error: {msg}")]
|
||||
#[diagnostic()]
|
||||
ParseError {
|
||||
/// The error message.
|
||||
msg: String,
|
||||
|
||||
/// The input ISLE source.
|
||||
#[source_code]
|
||||
src: Source,
|
||||
|
||||
/// The location of the parse error.
|
||||
#[label("{msg}")]
|
||||
span: SourceSpan,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// The input ISLE source has a type error.
|
||||
#[error("type error: {msg}")]
|
||||
#[diagnostic()]
|
||||
TypeError {
|
||||
/// The error message.
|
||||
msg: String,
|
||||
|
||||
/// The input ISLE source.
|
||||
#[source_code]
|
||||
src: Source,
|
||||
|
||||
/// The location of the type error.
|
||||
#[label("{msg}")]
|
||||
span: SourceSpan,
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// Multiple errors.
|
||||
#[error("Found {} errors:\n\n{}",
|
||||
self.unwrap_errors().len(),
|
||||
DisplayErrors(self.unwrap_errors()))]
|
||||
#[diagnostic()]
|
||||
Errors(#[related] Vec<Error>),
|
||||
Errors(Vec<Error>),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
@@ -84,6 +69,31 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Error::IoError { error, .. } => Some(&*error as &dyn std::error::Error),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::IoError { context, .. } => write!(f, "{}", context),
|
||||
Error::ParseError { msg, .. } => write!(f, "parse error: {}", msg),
|
||||
Error::TypeError { msg, .. } => write!(f, "type error: {}", msg),
|
||||
Error::Errors(_) => write!(
|
||||
f,
|
||||
"found {} errors:\n\n{}",
|
||||
self.unwrap_errors().len(),
|
||||
DisplayErrors(self.unwrap_errors())
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayErrors<'a>(&'a [Error]);
|
||||
impl std::fmt::Display for DisplayErrors<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
@@ -97,8 +107,11 @@ impl std::fmt::Display for DisplayErrors<'_> {
|
||||
/// A source file and its contents.
|
||||
#[derive(Clone)]
|
||||
pub struct Source {
|
||||
name: Arc<str>,
|
||||
text: Arc<str>,
|
||||
/// The name of this source file.
|
||||
pub name: Arc<str>,
|
||||
/// The text of this source file.
|
||||
#[allow(unused)] // Used only when miette is enabled.
|
||||
pub text: Arc<str>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Source {
|
||||
@@ -126,23 +139,21 @@ impl Source {
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceCode for Source {
|
||||
fn read_span<'a>(
|
||||
&'a self,
|
||||
span: &SourceSpan,
|
||||
context_lines_before: usize,
|
||||
context_lines_after: usize,
|
||||
) -> std::result::Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
|
||||
let contents = self
|
||||
.text
|
||||
.read_span(span, context_lines_before, context_lines_after)?;
|
||||
Ok(Box::new(miette::MietteSpanContents::new_named(
|
||||
self.name.to_string(),
|
||||
contents.data(),
|
||||
contents.span().clone(),
|
||||
contents.line(),
|
||||
contents.column(),
|
||||
contents.line_count(),
|
||||
)))
|
||||
/// A span in a given source.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Span {
|
||||
/// The byte offset of the start of the span.
|
||||
pub from: usize,
|
||||
/// The byte offset of the end of the span.
|
||||
pub to: usize,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
/// Create a new span that covers one character at the given offset.
|
||||
pub fn new_single(offset: usize) -> Span {
|
||||
Span {
|
||||
from: offset,
|
||||
to: offset + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
cranelift/isle/isle/src/error_miette.rs
Normal file
65
cranelift/isle/isle/src/error_miette.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
//! miette-specific trait implementations. This is kept separate so
|
||||
//! that we can have a very lightweight build of the ISLE compiler as
|
||||
//! part of the Cranelift build process without pulling in any
|
||||
//! dependencies.
|
||||
|
||||
use crate::error::{Error, Source, Span};
|
||||
use miette::{SourceCode, SourceSpan};
|
||||
|
||||
impl From<Span> for SourceSpan {
|
||||
fn from(span: Span) -> Self {
|
||||
SourceSpan::new(span.from.into(), span.to.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceCode for Source {
|
||||
fn read_span<'a>(
|
||||
&'a self,
|
||||
span: &SourceSpan,
|
||||
context_lines_before: usize,
|
||||
context_lines_after: usize,
|
||||
) -> std::result::Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
|
||||
let contents = self
|
||||
.text
|
||||
.read_span(span, context_lines_before, context_lines_after)?;
|
||||
Ok(Box::new(miette::MietteSpanContents::new_named(
|
||||
self.name.to_string(),
|
||||
contents.data(),
|
||||
contents.span().clone(),
|
||||
contents.line(),
|
||||
contents.column(),
|
||||
contents.line_count(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl miette::Diagnostic for Error {
|
||||
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
|
||||
match self {
|
||||
Self::ParseError { msg, span, .. } | Self::TypeError { msg, span, .. } => {
|
||||
Some(Box::new(
|
||||
vec![miette::LabeledSpan::new_with_span(
|
||||
Some(msg.clone()),
|
||||
span.clone(),
|
||||
)]
|
||||
.into_iter(),
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode> {
|
||||
match self {
|
||||
Self::ParseError { src, .. } | Self::TypeError { src, .. } => Some(src),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
fn related(&self) -> Option<Box<dyn Iterator<Item = &dyn miette::Diagnostic> + '_>> {
|
||||
match self {
|
||||
Self::Errors(errors) => Some(Box::new(
|
||||
errors.iter().map(|x| x as &dyn miette::Diagnostic),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Lowered matching IR.
|
||||
|
||||
use crate::lexer::Pos;
|
||||
use crate::log;
|
||||
use crate::sema::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -549,7 +550,7 @@ impl ExprSequence {
|
||||
expr: &Expr,
|
||||
vars: &BTreeMap<VarId, Value>,
|
||||
) -> Value {
|
||||
log::trace!("gen_expr: expr {:?}", expr);
|
||||
log!("gen_expr: expr {:?}", expr);
|
||||
match expr {
|
||||
&Expr::ConstInt(ty, val) => self.add_const_int(ty, val),
|
||||
&Expr::ConstPrim(ty, val) => self.add_const_prim(ty, val),
|
||||
@@ -627,7 +628,7 @@ pub fn lower_rule(
|
||||
.root_term()
|
||||
.expect("Pattern must have a term at the root");
|
||||
|
||||
log::trace!("lower_rule: ruledata {:?}", ruledata,);
|
||||
log!("lower_rule: ruledata {:?}", ruledata,);
|
||||
|
||||
// Lower the pattern, starting from the root input value.
|
||||
pattern_seq.gen_pattern(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Lexer for the ISLE language.
|
||||
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::error::{Error, Result, Source, Span};
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -167,7 +167,7 @@ impl<'a> Lexer<'a> {
|
||||
self.filenames[pos.file].clone(),
|
||||
self.file_texts[pos.file].clone(),
|
||||
),
|
||||
span: miette::SourceSpan::from((self.pos().offset, 1)),
|
||||
span: Span::new_single(self.pos().offset),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ pub mod compile;
|
||||
pub mod error;
|
||||
pub mod ir;
|
||||
pub mod lexer;
|
||||
mod log;
|
||||
pub mod parser;
|
||||
pub mod sema;
|
||||
pub mod trie;
|
||||
|
||||
#[cfg(feature = "miette-errors")]
|
||||
mod error_miette;
|
||||
|
||||
17
cranelift/isle/isle/src/log.rs
Normal file
17
cranelift/isle/isle/src/log.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
//! Debug logging from the ISLE compiler itself.
|
||||
|
||||
/// Log a compiler-internal message for debugging purposes.
|
||||
#[cfg(feature = "logging")]
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($($msg:tt)*) => {
|
||||
::log::trace!($($msg)*)
|
||||
};
|
||||
}
|
||||
|
||||
/// Log a compiler-internal message for debugging purposes.
|
||||
#[cfg(not(feature = "logging"))]
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($($msg:tt)*) => {};
|
||||
}
|
||||
@@ -39,7 +39,7 @@ impl<'a> Parser<'a> {
|
||||
self.lexer.filenames[pos.file].clone(),
|
||||
self.lexer.file_texts[pos.file].clone(),
|
||||
),
|
||||
span: miette::SourceSpan::from((pos.offset, 1)),
|
||||
span: Span::new_single(pos.offset),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::ast;
|
||||
use crate::ast::Ident;
|
||||
use crate::error::*;
|
||||
use crate::lexer::Pos;
|
||||
use crate::log;
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
@@ -725,9 +726,9 @@ impl TypeEnv {
|
||||
self.filenames[pos.file].clone(),
|
||||
self.file_texts[pos.file].clone(),
|
||||
),
|
||||
span: miette::SourceSpan::from((pos.offset, 1)),
|
||||
span: Span::new_single(pos.offset),
|
||||
};
|
||||
log::trace!("{}", e);
|
||||
log!("{}", e);
|
||||
e
|
||||
}
|
||||
|
||||
@@ -902,7 +903,7 @@ impl TermEnv {
|
||||
|
||||
fn collect_constructors(&mut self, tyenv: &mut TypeEnv, defs: &ast::Defs) {
|
||||
for def in &defs.defs {
|
||||
log::debug!("collect_constructors from def: {:?}", def);
|
||||
log!("collect_constructors from def: {:?}", def);
|
||||
match def {
|
||||
&ast::Def::Rule(ref rule) => {
|
||||
let pos = rule.pos;
|
||||
@@ -982,7 +983,7 @@ impl TermEnv {
|
||||
};
|
||||
|
||||
let template = ext.template.make_macro_template(&ext.args[..]);
|
||||
log::trace!("extractor def: {:?} becomes template {:?}", def, template);
|
||||
log!("extractor def: {:?} becomes template {:?}", def, template);
|
||||
|
||||
let mut callees = BTreeSet::new();
|
||||
template.terms(&mut |pos, t| {
|
||||
@@ -1451,8 +1452,8 @@ impl TermEnv {
|
||||
bindings: &mut Bindings,
|
||||
is_root: bool,
|
||||
) -> Option<(Pattern, TypeId)> {
|
||||
log::trace!("translate_pattern: {:?}", pat);
|
||||
log::trace!("translate_pattern: bindings = {:?}", bindings);
|
||||
log!("translate_pattern: {:?}", pat);
|
||||
log!("translate_pattern: bindings = {:?}", bindings);
|
||||
match pat {
|
||||
// TODO: flag on primitive type decl indicating it's an integer type?
|
||||
&ast::Pattern::ConstInt { val, pos } => {
|
||||
@@ -1546,7 +1547,7 @@ impl TermEnv {
|
||||
}
|
||||
let id = VarId(bindings.next_var);
|
||||
bindings.next_var += 1;
|
||||
log::trace!("binding var {:?}", var.0);
|
||||
log!("binding var {:?}", var.0);
|
||||
bindings.vars.push(BoundVar { name, id, ty });
|
||||
|
||||
Some((Pattern::BindPattern(ty, id, Box::new(subpat)), ty))
|
||||
@@ -1572,7 +1573,7 @@ impl TermEnv {
|
||||
};
|
||||
let id = VarId(bindings.next_var);
|
||||
bindings.next_var += 1;
|
||||
log::trace!("binding var {:?}", var.0);
|
||||
log!("binding var {:?}", var.0);
|
||||
bindings.vars.push(BoundVar { name, id, ty });
|
||||
Some((
|
||||
Pattern::BindPattern(ty, id, Box::new(Pattern::Wildcard(ty))),
|
||||
@@ -1687,7 +1688,7 @@ impl TermEnv {
|
||||
for template_arg in args {
|
||||
macro_args.push(template_arg.clone());
|
||||
}
|
||||
log::trace!("internal extractor macro args = {:?}", args);
|
||||
log!("internal extractor macro args = {:?}", args);
|
||||
let pat = template.subst_macro_args(¯o_args[..])?;
|
||||
return self.translate_pattern(
|
||||
tyenv,
|
||||
@@ -1767,7 +1768,7 @@ impl TermEnv {
|
||||
bindings: &mut Bindings,
|
||||
pure: bool,
|
||||
) -> Option<Expr> {
|
||||
log::trace!("translate_expr: {:?}", expr);
|
||||
log!("translate_expr: {:?}", expr);
|
||||
match expr {
|
||||
&ast::Expr::Term {
|
||||
ref sym,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Trie construction.
|
||||
|
||||
use crate::ir::{lower_rule, ExprSequence, PatternInst, PatternSequence};
|
||||
use crate::log;
|
||||
use crate::sema::{RuleId, TermEnv, TermId, TypeEnv};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -8,7 +9,7 @@ use std::collections::BTreeMap;
|
||||
pub fn build_tries(typeenv: &TypeEnv, termenv: &TermEnv) -> BTreeMap<TermId, TrieNode> {
|
||||
let mut builder = TermFunctionsBuilder::new(typeenv, termenv);
|
||||
builder.build();
|
||||
log::trace!("builder: {:?}", builder);
|
||||
log!("builder: {:?}", builder);
|
||||
builder.finalize()
|
||||
}
|
||||
|
||||
@@ -324,8 +325,8 @@ struct TermFunctionsBuilder<'a> {
|
||||
|
||||
impl<'a> TermFunctionsBuilder<'a> {
|
||||
fn new(typeenv: &'a TypeEnv, termenv: &'a TermEnv) -> Self {
|
||||
log::trace!("typeenv: {:?}", typeenv);
|
||||
log::trace!("termenv: {:?}", termenv);
|
||||
log!("typeenv: {:?}", typeenv);
|
||||
log!("termenv: {:?}", termenv);
|
||||
Self {
|
||||
builders_by_term: BTreeMap::new(),
|
||||
typeenv,
|
||||
@@ -341,7 +342,7 @@ impl<'a> TermFunctionsBuilder<'a> {
|
||||
let (pattern, expr) = lower_rule(self.typeenv, self.termenv, rule);
|
||||
let root_term = self.termenv.rules[rule.index()].lhs.root_term().unwrap();
|
||||
|
||||
log::trace!(
|
||||
log!(
|
||||
"build:\n- rule {:?}\n- pattern {:?}\n- expr {:?}",
|
||||
self.termenv.rules[rule.index()],
|
||||
pattern,
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
use cranelift_isle::error::Result;
|
||||
use cranelift_isle::{compile, lexer, parser};
|
||||
use std::default::Default;
|
||||
|
||||
fn build(filename: &str) -> Result<String> {
|
||||
let lexer = lexer::Lexer::from_files(vec![filename])?;
|
||||
let defs = parser::parse(lexer)?;
|
||||
compile::compile(&defs)
|
||||
compile::compile(&defs, &Default::default())
|
||||
}
|
||||
|
||||
pub fn run_pass(filename: &str) {
|
||||
|
||||
@@ -7,7 +7,7 @@ license = "Apache-2.0 WITH LLVM-exception"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
cranelift-isle = { version = "*", path = "../isle/" }
|
||||
cranelift-isle = { version = "*", path = "../isle/", features = ["miette-errors", "logging"] }
|
||||
env_logger = { version = "0.9", default-features = false }
|
||||
miette = { version = "3.0.0", features = ["fancy"] }
|
||||
miette = { version = "4.7.0", features = ["fancy"] }
|
||||
clap = { version = "3.1.12", features = ["derive"] }
|
||||
|
||||
@@ -2,6 +2,7 @@ use clap::Parser;
|
||||
use cranelift_isle::{compile, lexer, parser};
|
||||
use miette::{Context, IntoDiagnostic, Result};
|
||||
use std::{
|
||||
default::Default,
|
||||
fs,
|
||||
io::{self, Write},
|
||||
path::PathBuf,
|
||||
@@ -36,7 +37,7 @@ fn main() -> Result<()> {
|
||||
|
||||
let lexer = lexer::Lexer::from_files(opts.inputs)?;
|
||||
let defs = parser::parse(lexer)?;
|
||||
let code = compile::compile(&defs)?;
|
||||
let code = compile::compile(&defs, &Default::default())?;
|
||||
|
||||
let stdout = io::stdout();
|
||||
let (mut output, output_name): (Box<dyn Write>, _) = match &opts.output {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# This script rebuilds all ISLE generated source that is checked in, even if
|
||||
# the source has not changed relative to the manifests.
|
||||
#
|
||||
# This is useful when one is developing the ISLE compiler itself; otherwise,
|
||||
# changing the compiler does not automatically change the generated code, even
|
||||
# if the `rebuild-isle` feature is specified.
|
||||
|
||||
set -e
|
||||
|
||||
# Remove the manifests (which contain hashes of ISLE source) to force the build
|
||||
# script to regenerate all backends.
|
||||
rm -f cranelift/codegen/src/isa/*/lower/isle/generated_code.manifest
|
||||
|
||||
# `cargo check` will both invoke the build script to rebuild the backends, and
|
||||
# check that the output is valid Rust. We specify `all-arch` here to include
|
||||
# all backends.
|
||||
cargo check -p cranelift-codegen --features rebuild-isle,all-arch
|
||||
Reference in New Issue
Block a user