clif-util: Use anyhow::Error for errors instead of String

Also does the same for `cranelift-filetests`.
This commit is contained in:
Nick Fitzgerald
2020-09-14 16:23:30 -07:00
parent 9fea412333
commit 31cbbd1d20
43 changed files with 415 additions and 443 deletions

6
Cargo.lock generated
View File

@@ -50,9 +50,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.31" version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
@@ -419,6 +419,7 @@ dependencies = [
name = "cranelift-filetests" name = "cranelift-filetests"
version = "0.66.0" version = "0.66.0"
dependencies = [ dependencies = [
"anyhow",
"byteorder", "byteorder",
"cranelift-codegen", "cranelift-codegen",
"cranelift-frontend", "cranelift-frontend",
@@ -544,6 +545,7 @@ dependencies = [
name = "cranelift-tools" name = "cranelift-tools"
version = "0.66.0" version = "0.66.0"
dependencies = [ dependencies = [
"anyhow",
"capstone", "capstone",
"cfg-if", "cfg-if",
"clap", "clap",

View File

@@ -35,7 +35,7 @@ log = "0.4.8"
term = "0.6.1" term = "0.6.1"
capstone = { version = "0.6.0", optional = true } capstone = { version = "0.6.0", optional = true }
wat = { version = "1.0.18", optional = true } wat = { version = "1.0.18", optional = true }
target-lexicon = "0.10" target-lexicon = { version = "0.10", features = ["std"] }
peepmatic-souper = { path = "./peepmatic/crates/souper", version = "0.66.0", optional = true } peepmatic-souper = { path = "./peepmatic/crates/souper", version = "0.66.0", optional = true }
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
rayon = { version = "1", optional = true } rayon = { version = "1", optional = true }
@@ -43,6 +43,7 @@ file-per-thread-logger = "0.1.2"
indicatif = "0.13.0" indicatif = "0.13.0"
thiserror = "1.0.15" thiserror = "1.0.15"
walkdir = "2.2" walkdir = "2.2"
anyhow = "1.0.32"
[features] [features]
default = ["disas", "wasm", "cranelift-codegen/all-arch", "peepmatic-souper", "souper-harvest"] default = ["disas", "wasm", "cranelift-codegen/all-arch", "peepmatic-souper", "souper-harvest"]

View File

@@ -25,6 +25,7 @@ memmap = "0.7.0"
num_cpus = "1.8.0" num_cpus = "1.8.0"
target-lexicon = "0.10" target-lexicon = "0.10"
thiserror = "1.0.15" thiserror = "1.0.15"
anyhow = "1.0.32"
[features] [features]
enable-peepmatic = [] enable-peepmatic = []

View File

@@ -4,7 +4,6 @@
//! concurrently. //! concurrently.
use crate::runone; use crate::runone;
use crate::TestResult;
use cranelift_codegen::dbg::LOG_FILENAME_PREFIX; use cranelift_codegen::dbg::LOG_FILENAME_PREFIX;
use cranelift_codegen::timing; use cranelift_codegen::timing;
use file_per_thread_logger; use file_per_thread_logger;
@@ -22,8 +21,14 @@ struct Request(usize, PathBuf);
/// Reply from worker thread, /// Reply from worker thread,
pub enum Reply { pub enum Reply {
Starting { jobid: usize, thread_num: usize }, Starting {
Done { jobid: usize, result: TestResult }, jobid: usize,
thread_num: usize,
},
Done {
jobid: usize,
result: anyhow::Result<Duration>,
},
Tick, Tick,
} }
@@ -147,11 +152,11 @@ fn worker_thread(
// The test panicked, leaving us a `Box<Any>`. // The test panicked, leaving us a `Box<Any>`.
// Panics are usually strings. // Panics are usually strings.
if let Some(msg) = e.downcast_ref::<String>() { if let Some(msg) = e.downcast_ref::<String>() {
Err(format!("panicked in worker #{}: {}", thread_num, msg)) anyhow::bail!("panicked in worker #{}: {}", thread_num, msg)
} else if let Some(msg) = e.downcast_ref::<&'static str>() { } else if let Some(msg) = e.downcast_ref::<&'static str>() {
Err(format!("panicked in worker #{}: {}", thread_num, msg)) anyhow::bail!("panicked in worker #{}: {}", thread_num, msg)
} else { } else {
Err(format!("panicked in worker #{}", thread_num)) anyhow::bail!("panicked in worker #{}", thread_num)
} }
}); });

View File

@@ -60,9 +60,6 @@ mod test_stack_maps;
mod test_unwind; mod test_unwind;
mod test_verifier; mod test_verifier;
/// The result of running the test in a file.
type TestResult = Result<time::Duration, String>;
/// Main entry point for `clif-util test`. /// Main entry point for `clif-util test`.
/// ///
/// Take a list of filenames which can be either `.clif` files or directories. /// Take a list of filenames which can be either `.clif` files or directories.
@@ -72,7 +69,7 @@ type TestResult = Result<time::Duration, String>;
/// Directories are scanned recursively for test cases ending in `.clif`. These test cases are /// Directories are scanned recursively for test cases ending in `.clif`. These test cases are
/// executed on background threads. /// executed on background threads.
/// ///
pub fn run(verbose: bool, report_times: bool, files: &[String]) -> TestResult { pub fn run(verbose: bool, report_times: bool, files: &[String]) -> anyhow::Result<time::Duration> {
let mut runner = TestRunner::new(verbose, report_times); let mut runner = TestRunner::new(verbose, report_times);
for path in files.iter().map(Path::new) { for path in files.iter().map(Path::new) {
@@ -98,7 +95,7 @@ pub fn run_passes(
passes: &[String], passes: &[String],
target: &str, target: &str,
file: &str, file: &str,
) -> TestResult { ) -> anyhow::Result<time::Duration> {
let mut runner = TestRunner::new(verbose, /* report_times */ false); let mut runner = TestRunner::new(verbose, /* report_times */ false);
let path = Path::new(file); let path = Path::new(file);
@@ -119,7 +116,7 @@ pub fn run_passes(
/// ///
/// This function knows how to create all of the possible `test <foo>` commands that can appear in /// This function knows how to create all of the possible `test <foo>` commands that can appear in
/// a `.clif` test file. /// a `.clif` test file.
fn new_subtest(parsed: &TestCommand) -> subtest::SubtestResult<Box<dyn subtest::SubTest>> { fn new_subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn subtest::SubTest>> {
match parsed.command { match parsed.command {
"binemit" => test_binemit::subtest(parsed), "binemit" => test_binemit::subtest(parsed),
"cat" => test_cat::subtest(parsed), "cat" => test_cat::subtest(parsed),
@@ -143,6 +140,15 @@ fn new_subtest(parsed: &TestCommand) -> subtest::SubtestResult<Box<dyn subtest::
"stack_maps" => test_stack_maps::subtest(parsed), "stack_maps" => test_stack_maps::subtest(parsed),
"unwind" => test_unwind::subtest(parsed), "unwind" => test_unwind::subtest(parsed),
"verifier" => test_verifier::subtest(parsed), "verifier" => test_verifier::subtest(parsed),
_ => Err(format!("unknown test command '{}'", parsed.command)), _ => anyhow::bail!("unknown test command '{}'", parsed.command),
} }
} }
fn pretty_anyhow_error(
func: &cranelift_codegen::ir::Function,
isa: Option<&dyn cranelift_codegen::isa::TargetIsa>,
err: cranelift_codegen::CodegenError,
) -> anyhow::Error {
let s = cranelift_codegen::print_errors::pretty_error(func, isa, err);
anyhow::anyhow!("{}", s)
}

View File

@@ -5,7 +5,6 @@
use crate::concurrent::{ConcurrentRunner, Reply}; use crate::concurrent::{ConcurrentRunner, Reply};
use crate::runone; use crate::runone;
use crate::TestResult;
use cranelift_codegen::timing; use cranelift_codegen::timing;
use std::error::Error; use std::error::Error;
use std::ffi::OsStr; use std::ffi::OsStr;
@@ -24,12 +23,12 @@ struct QueueEntry {
state: State, state: State,
} }
#[derive(PartialEq, Eq, Debug)] #[derive(Debug)]
enum State { enum State {
New, New,
Queued, Queued,
Running, Running,
Done(TestResult), Done(anyhow::Result<time::Duration>),
} }
#[derive(PartialEq, Eq, Debug, Clone, Copy)] #[derive(PartialEq, Eq, Debug, Clone, Copy)]
@@ -205,7 +204,7 @@ impl TestRunner {
/// Schedule any new jobs to run. /// Schedule any new jobs to run.
fn schedule_jobs(&mut self) { fn schedule_jobs(&mut self) {
for jobid in self.new_tests..self.tests.len() { for jobid in self.new_tests..self.tests.len() {
assert_eq!(self.tests[jobid].state, State::New); assert!(matches!(self.tests[jobid].state, State::New));
if let Some(ref mut conc) = self.threads { if let Some(ref mut conc) = self.threads {
// Queue test for concurrent execution. // Queue test for concurrent execution.
self.tests[jobid].state = State::Queued; self.tests[jobid].state = State::Queued;
@@ -228,7 +227,7 @@ impl TestRunner {
/// Schedule any new job to run for the pass command. /// Schedule any new job to run for the pass command.
fn schedule_pass_job(&mut self, passes: &[String], target: &str) { fn schedule_pass_job(&mut self, passes: &[String], target: &str) {
self.tests[0].state = State::Running; self.tests[0].state = State::Running;
let result: Result<time::Duration, String>; let result: anyhow::Result<time::Duration>;
let specified_target = match target { let specified_target = match target {
"" => None, "" => None,
@@ -240,8 +239,8 @@ impl TestRunner {
} }
/// Report the end of a job. /// Report the end of a job.
fn finish_job(&mut self, jobid: usize, result: TestResult) { fn finish_job(&mut self, jobid: usize, result: anyhow::Result<time::Duration>) {
assert_eq!(self.tests[jobid].state, State::Running); assert!(matches!(self.tests[jobid].state, State::Running));
if result.is_err() { if result.is_err() {
self.errors += 1; self.errors += 1;
} }
@@ -257,7 +256,7 @@ impl TestRunner {
fn handle_reply(&mut self, reply: Reply) { fn handle_reply(&mut self, reply: Reply) {
match reply { match reply {
Reply::Starting { jobid, .. } => { Reply::Starting { jobid, .. } => {
assert_eq!(self.tests[jobid].state, State::Queued); assert!(matches!(self.tests[jobid].state, State::Queued));
self.tests[jobid].state = State::Running; self.tests[jobid].state = State::Running;
} }
Reply::Done { jobid, result } => { Reply::Done { jobid, result } => {
@@ -274,7 +273,7 @@ impl TestRunner {
self.tests.len() self.tests.len()
); );
for jobid in self.reported_tests..self.tests.len() { for jobid in self.reported_tests..self.tests.len() {
if self.tests[jobid].state == State::Running { if let State::Running = self.tests[jobid].state {
println!("slow: {}", self.tests[jobid]); println!("slow: {}", self.tests[jobid]);
} }
} }
@@ -356,7 +355,7 @@ impl TestRunner {
} }
/// Scan pushed directories for tests and run them. /// Scan pushed directories for tests and run them.
pub fn run(&mut self) -> TestResult { pub fn run(&mut self) -> anyhow::Result<time::Duration> {
let started = time::Instant::now(); let started = time::Instant::now();
self.scan_dirs(IsPass::NotPass); self.scan_dirs(IsPass::NotPass);
self.schedule_jobs(); self.schedule_jobs();
@@ -366,13 +365,17 @@ impl TestRunner {
println!("{} tests", self.tests.len()); println!("{} tests", self.tests.len());
match self.errors { match self.errors {
0 => Ok(started.elapsed()), 0 => Ok(started.elapsed()),
1 => Err("1 failure".to_string()), 1 => anyhow::bail!("1 failure"),
n => Err(format!("{} failures", n)), n => anyhow::bail!("{} failures", n),
} }
} }
/// Scan pushed directories for tests and run specified passes from commandline on them. /// Scan pushed directories for tests and run specified passes from commandline on them.
pub fn run_passes(&mut self, passes: &[String], target: &str) -> TestResult { pub fn run_passes(
&mut self,
passes: &[String],
target: &str,
) -> anyhow::Result<time::Duration> {
let started = time::Instant::now(); let started = time::Instant::now();
self.scan_dirs(IsPass::Pass); self.scan_dirs(IsPass::Pass);
self.schedule_pass_job(passes, target); self.schedule_pass_job(passes, target);
@@ -381,8 +384,8 @@ impl TestRunner {
println!("{} tests", self.tests.len()); println!("{} tests", self.tests.len());
match self.errors { match self.errors {
0 => Ok(started.elapsed()), 0 => Ok(started.elapsed()),
1 => Err("1 failure".to_string()), 1 => anyhow::bail!("1 failure"),
n => Err(format!("{} failures", n)), n => anyhow::bail!("{} failures", n),
} }
} }
} }

View File

@@ -1,7 +1,8 @@
//! Run the tests in a single test file. //! Run the tests in a single test file.
use crate::subtest::{Context, SubTest, SubtestResult}; use crate::new_subtest;
use crate::{new_subtest, TestResult}; use crate::subtest::{Context, SubTest};
use anyhow::Context as _;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::print_errors::pretty_verifier_error; use cranelift_codegen::print_errors::pretty_verifier_error;
@@ -18,11 +19,16 @@ use std::time;
/// Load `path` and run the test in it. /// Load `path` and run the test in it.
/// ///
/// If running this test causes a panic, it will propagate as normal. /// If running this test causes a panic, it will propagate as normal.
pub fn run(path: &Path, passes: Option<&[String]>, target: Option<&str>) -> TestResult { pub fn run(
path: &Path,
passes: Option<&[String]>,
target: Option<&str>,
) -> anyhow::Result<time::Duration> {
let _tt = timing::process_file(); let _tt = timing::process_file();
info!("---\nFile: {}", path.to_string_lossy()); info!("---\nFile: {}", path.to_string_lossy());
let started = time::Instant::now(); let started = time::Instant::now();
let buffer = fs::read_to_string(path).map_err(|e| e.to_string())?; let buffer =
fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?;
let options = ParseOptions { let options = ParseOptions {
target, target,
passes, passes,
@@ -39,12 +45,14 @@ pub fn run(path: &Path, passes: Option<&[String]>, target: Option<&str>) -> Test
); );
return Ok(started.elapsed()); return Ok(started.elapsed());
} }
return Err(e.to_string()); return Err(e)
.context(format!("failed to parse {}", path.display()))
.into();
} }
}; };
if testfile.functions.is_empty() { if testfile.functions.is_empty() {
return Err("no functions found".to_string()); anyhow::bail!("no functions found");
} }
// Parse the test commands. // Parse the test commands.
@@ -52,7 +60,7 @@ pub fn run(path: &Path, passes: Option<&[String]>, target: Option<&str>) -> Test
.commands .commands
.iter() .iter()
.map(new_subtest) .map(new_subtest)
.collect::<SubtestResult<Vec<_>>>()?; .collect::<anyhow::Result<Vec<_>>>()?;
// Flags to use for those tests that don't need an ISA. // Flags to use for those tests that don't need an ISA.
// This is the cumulative effect of all the `set` commands in the file. // This is the cumulative effect of all the `set` commands in the file.
@@ -71,7 +79,7 @@ pub fn run(path: &Path, passes: Option<&[String]>, target: Option<&str>) -> Test
// Isolate the last test in the hope that this is the only mutating test. // Isolate the last test in the hope that this is the only mutating test.
// If so, we can completely avoid cloning functions. // If so, we can completely avoid cloning functions.
let last_tuple = match tuples.pop() { let last_tuple = match tuples.pop() {
None => return Err("no test commands found".to_string()), None => anyhow::bail!("no test commands found"),
Some(t) => t, Some(t) => t,
}; };
@@ -102,14 +110,14 @@ fn test_tuples<'a>(
tests: &'a [Box<dyn SubTest>], tests: &'a [Box<dyn SubTest>],
isa_spec: &'a IsaSpec, isa_spec: &'a IsaSpec,
no_isa_flags: &'a Flags, no_isa_flags: &'a Flags,
) -> SubtestResult<Vec<(&'a dyn SubTest, &'a Flags, Option<&'a dyn TargetIsa>)>> { ) -> anyhow::Result<Vec<(&'a dyn SubTest, &'a Flags, Option<&'a dyn TargetIsa>)>> {
let mut out = Vec::new(); let mut out = Vec::new();
for test in tests { for test in tests {
if test.needs_isa() { if test.needs_isa() {
match *isa_spec { match *isa_spec {
IsaSpec::None(_) => { IsaSpec::None(_) => {
// TODO: Generate a list of default ISAs. // TODO: Generate a list of default ISAs.
return Err(format!("test {} requires an ISA", test.name())); anyhow::bail!("test {} requires an ISA", test.name());
} }
IsaSpec::Some(ref isas) => { IsaSpec::Some(ref isas) => {
for isa in isas { for isa in isas {
@@ -131,7 +139,7 @@ fn run_one_test<'a>(
tuple: (&'a dyn SubTest, &'a Flags, Option<&'a dyn TargetIsa>), tuple: (&'a dyn SubTest, &'a Flags, Option<&'a dyn TargetIsa>),
func: Cow<Function>, func: Cow<Function>,
context: &mut Context<'a>, context: &mut Context<'a>,
) -> SubtestResult<()> { ) -> anyhow::Result<()> {
let (test, flags, isa) = tuple; let (test, flags, isa) = tuple;
let name = format!("{}({})", test.name(), func.name); let name = format!("{}({})", test.name(), func.name);
info!("Test: {} {}", name, isa.map_or("-", TargetIsa::name)); info!("Test: {} {}", name, isa.map_or("-", TargetIsa::name));
@@ -141,11 +149,12 @@ fn run_one_test<'a>(
// Should we run the verifier before this test? // Should we run the verifier before this test?
if !context.verified && test.needs_verifier() { if !context.verified && test.needs_verifier() {
verify_function(&func, context.flags_or_isa()) verify_function(&func, context.flags_or_isa()).map_err(|errors| {
.map_err(|errors| pretty_verifier_error(&func, isa, None, errors))?; anyhow::anyhow!("{}", pretty_verifier_error(&func, isa, None, errors))
})?;
context.verified = true; context.verified = true;
} }
test.run(func, context) test.run(func, context).context(test.name())?;
.map_err(|e| format!("{}:\n{}", name, e)) Ok(())
} }

View File

@@ -1,5 +1,6 @@
//! `SubTest` trait. //! `SubTest` trait.
use anyhow::Context as _;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::settings::{Flags, FlagsOrIsa}; use cranelift_codegen::settings::{Flags, FlagsOrIsa};
@@ -7,8 +8,6 @@ use cranelift_reader::{Comment, Details};
use filecheck::{Checker, CheckerBuilder, NO_VARIABLES}; use filecheck::{Checker, CheckerBuilder, NO_VARIABLES};
use std::borrow::Cow; use std::borrow::Cow;
pub type SubtestResult<T> = Result<T, String>;
/// Context for running a test on a single function. /// Context for running a test on a single function.
pub struct Context<'a> { pub struct Context<'a> {
/// Comments from the preamble f the test file. These apply to all functions. /// Comments from the preamble f the test file. These apply to all functions.
@@ -66,39 +65,39 @@ pub trait SubTest {
} }
/// Run this test on `func`. /// Run this test on `func`.
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()>; fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()>;
} }
/// Run filecheck on `text`, using directives extracted from `context`. /// Run filecheck on `text`, using directives extracted from `context`.
pub fn run_filecheck(text: &str, context: &Context) -> SubtestResult<()> { pub fn run_filecheck(text: &str, context: &Context) -> anyhow::Result<()> {
let checker = build_filechecker(context)?; let checker = build_filechecker(context)?;
if checker if checker
.check(text, NO_VARIABLES) .check(text, NO_VARIABLES)
.map_err(|e| format!("filecheck: {}", e))? .context("filecheck failed")?
{ {
Ok(()) Ok(())
} else { } else {
// Filecheck mismatch. Emit an explanation as output. // Filecheck mismatch. Emit an explanation as output.
let (_, explain) = checker let (_, explain) = checker
.explain(text, NO_VARIABLES) .explain(text, NO_VARIABLES)
.map_err(|e| format!("explain: {}", e))?; .context("filecheck explain failed")?;
Err(format!("filecheck failed:\n{}{}", checker, explain)) anyhow::bail!("filecheck failed:\n{}{}", checker, explain);
} }
} }
/// Build a filechecker using the directives in the file preamble and the function's comments. /// Build a filechecker using the directives in the file preamble and the function's comments.
pub fn build_filechecker(context: &Context) -> SubtestResult<Checker> { pub fn build_filechecker(context: &Context) -> anyhow::Result<Checker> {
let mut builder = CheckerBuilder::new(); let mut builder = CheckerBuilder::new();
// Preamble comments apply to all functions. // Preamble comments apply to all functions.
for comment in context.preamble_comments { for comment in context.preamble_comments {
builder builder
.directive(comment.text) .directive(comment.text)
.map_err(|e| format!("filecheck: {}", e))?; .context("filecheck directive failed")?;
} }
for comment in &context.details.comments { for comment in &context.details.comments {
builder builder
.directive(comment.text) .directive(comment.text)
.map_err(|e| format!("filecheck: {}", e))?; .context("filecheck directive failed")?;
} }
Ok(builder.finish()) Ok(builder.finish())
} }

View File

@@ -4,7 +4,7 @@
//! functions and compares the results to the expected output. //! functions and compares the results to the expected output.
use crate::match_directive::match_directive; use crate::match_directive::match_directive;
use crate::subtest::{Context, SubTest, SubtestResult}; use crate::subtest::{Context, SubTest};
use cranelift_codegen::binemit::{self, CodeInfo, CodeSink, RegDiversions}; use cranelift_codegen::binemit::{self, CodeInfo, CodeSink, RegDiversions};
use cranelift_codegen::dbg::DisplayList; use cranelift_codegen::dbg::DisplayList;
use cranelift_codegen::dominator_tree::DominatorTree; use cranelift_codegen::dominator_tree::DominatorTree;
@@ -12,7 +12,6 @@ use cranelift_codegen::flowgraph::ControlFlowGraph;
use cranelift_codegen::ir; use cranelift_codegen::ir;
use cranelift_codegen::ir::entities::AnyEntity; use cranelift_codegen::ir::entities::AnyEntity;
use cranelift_codegen::isa; use cranelift_codegen::isa;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_codegen::settings::OptLevel; use cranelift_codegen::settings::OptLevel;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
@@ -21,10 +20,10 @@ use std::fmt::Write;
struct TestBinEmit; struct TestBinEmit;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "binemit"); assert_eq!(parsed.command, "binemit");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed)
} else { } else {
Ok(Box::new(TestBinEmit)) Ok(Box::new(TestBinEmit))
} }
@@ -130,7 +129,7 @@ impl SubTest for TestBinEmit {
true true
} }
fn run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("binemit needs an ISA"); let isa = context.isa.expect("binemit needs an ISA");
let encinfo = isa.encoding_info(); let encinfo = isa.encoding_info();
// TODO: Run a verifier pass over the code first to detect any bad encodings or missing/bad // TODO: Run a verifier pass over the code first to detect any bad encodings or missing/bad
@@ -187,7 +186,7 @@ impl SubTest for TestBinEmit {
let mut domtree = DominatorTree::with_function(&func, &cfg); let mut domtree = DominatorTree::with_function(&func, &cfg);
let CodeInfo { total_size, .. } = let CodeInfo { total_size, .. } =
binemit::relax_branches(&mut func, &mut cfg, &mut domtree, isa) binemit::relax_branches(&mut func, &mut cfg, &mut domtree, isa)
.map_err(|e| pretty_error(&func, context.isa, e))?; .map_err(|e| crate::pretty_anyhow_error(&func, context.isa, e))?;
// Collect all of the 'bin:' directives on instructions. // Collect all of the 'bin:' directives on instructions.
let mut bins = HashMap::new(); let mut bins = HashMap::new();
@@ -196,25 +195,26 @@ impl SubTest for TestBinEmit {
match comment.entity { match comment.entity {
AnyEntity::Inst(inst) => { AnyEntity::Inst(inst) => {
if let Some(prev) = bins.insert(inst, want) { if let Some(prev) = bins.insert(inst, want) {
return Err(format!( anyhow::bail!(
"multiple 'bin:' directives on {}: '{}' and '{}'", "multiple 'bin:' directives on {}: '{}' and '{}'",
func.dfg.display_inst(inst, isa), func.dfg.display_inst(inst, isa),
prev, prev,
want want
)); );
} }
} }
_ => { _ => {
return Err(format!( anyhow::bail!(
"'bin:' directive on non-inst {}: {}", "'bin:' directive on non-inst {}: {}",
comment.entity, comment.text comment.entity,
)); comment.text
);
} }
} }
} }
} }
if bins.is_empty() { if bins.is_empty() {
return Err("No 'bin:' directives found".to_string()); anyhow::bail!("No 'bin:' directives found");
} }
// Now emit all instructions. // Now emit all instructions.
@@ -265,26 +265,26 @@ impl SubTest for TestBinEmit {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if encodings.is_empty() { if encodings.is_empty() {
return Err(format!( anyhow::bail!(
"No encodings found for: {}", "No encodings found for: {}",
func.dfg.display_inst(inst, isa) func.dfg.display_inst(inst, isa)
)); );
} }
return Err(format!( anyhow::bail!(
"No matching encodings for {} in {}", "No matching encodings for {} in {}",
func.dfg.display_inst(inst, isa), func.dfg.display_inst(inst, isa),
DisplayList(&encodings), DisplayList(&encodings),
)); );
} }
let have = sink.text.trim(); let have = sink.text.trim();
if have != want { if have != want {
return Err(format!( anyhow::bail!(
"Bad machine code for {}: {}\nWant: {}\nGot: {}", "Bad machine code for {}: {}\nWant: {}\nGot: {}",
inst, inst,
func.dfg.display_inst(inst, isa), func.dfg.display_inst(inst, isa),
want, want,
have have
)); );
} }
} }
} }
@@ -312,10 +312,7 @@ impl SubTest for TestBinEmit {
sink.end_codegen(); sink.end_codegen();
if sink.offset != total_size { if sink.offset != total_size {
return Err(format!( anyhow::bail!("Expected code size {}, got {}", total_size, sink.offset);
"Expected code size {}, got {}",
total_size, sink.offset
));
} }
Ok(()) Ok(())
@@ -328,7 +325,7 @@ fn validate_location_annotations(
inst: ir::Inst, inst: ir::Inst,
isa: &dyn isa::TargetIsa, isa: &dyn isa::TargetIsa,
validate_inputs: bool, validate_inputs: bool,
) -> SubtestResult<()> { ) -> anyhow::Result<()> {
let values = if validate_inputs { let values = if validate_inputs {
func.dfg.inst_args(inst) func.dfg.inst_args(inst)
} else { } else {
@@ -336,12 +333,11 @@ fn validate_location_annotations(
}; };
if let Some(&v) = values.iter().find(|&&v| !func.locations[v].is_assigned()) { if let Some(&v) = values.iter().find(|&&v| !func.locations[v].is_assigned()) {
Err(format!( anyhow::bail!(
"Need register/stack slot annotation for {} in {}", "Need register/stack slot annotation for {} in {}",
v, v,
func.dfg.display_inst(inst, isa) func.dfg.display_inst(inst, isa)
)) );
} else { }
Ok(()) Ok(())
} }
}

View File

@@ -1,6 +1,6 @@
//! The `cat` subtest. //! The `cat` subtest.
use crate::subtest::{self, Context, SubTest, SubtestResult}; use crate::subtest::{self, Context, SubTest};
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
@@ -13,13 +13,12 @@ use std::borrow::Cow;
/// The result is verified by filecheck. /// The result is verified by filecheck.
struct TestCat; struct TestCat;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "cat"); assert_eq!(parsed.command, "cat");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestCat))
} }
Ok(Box::new(TestCat))
} }
impl SubTest for TestCat { impl SubTest for TestCat {
@@ -31,7 +30,7 @@ impl SubTest for TestCat {
false false
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
subtest::run_filecheck(&func.display(context.isa).to_string(), context) subtest::run_filecheck(&func.display(context.isa).to_string(), context)
} }
} }

View File

@@ -2,25 +2,23 @@
//! //!
//! The `compile` test command runs each function through the full code generator pipeline //! The `compile` test command runs each function through the full code generator pipeline
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen; use cranelift_codegen;
use cranelift_codegen::binemit::{self, CodeInfo}; use cranelift_codegen::binemit::{self, CodeInfo};
use cranelift_codegen::ir; use cranelift_codegen::ir;
use cranelift_codegen::isa; use cranelift_codegen::isa;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use log::info; use log::info;
use std::borrow::Cow; use std::borrow::Cow;
struct TestCompile; struct TestCompile;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "compile"); assert_eq!(parsed.command, "compile");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestCompile))
} }
Ok(Box::new(TestCompile))
} }
impl SubTest for TestCompile { impl SubTest for TestCompile {
@@ -36,7 +34,7 @@ impl SubTest for TestCompile {
true true
} }
fn run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("compile needs an ISA"); let isa = context.isa.expect("compile needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
@@ -47,7 +45,7 @@ impl SubTest for TestCompile {
let CodeInfo { total_size, .. } = comp_ctx let CodeInfo { total_size, .. } = comp_ctx
.compile(isa) .compile(isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
info!( info!(
"Generated {} bytes of code:\n{}", "Generated {} bytes of code:\n{}",
@@ -66,10 +64,7 @@ impl SubTest for TestCompile {
); );
if sink.offset != total_size { if sink.offset != total_size {
return Err(format!( anyhow::bail!("Expected code size {}, got {}", total_size, sink.offset);
"Expected code size {}, got {}",
total_size, sink.offset
));
} }
// Run final code through filecheck. // Run final code through filecheck.

View File

@@ -5,22 +5,20 @@
//! //!
//! The resulting function is sent to `filecheck`. //! The resulting function is sent to `filecheck`.
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen; use cranelift_codegen;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
struct TestDCE; struct TestDCE;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "dce"); assert_eq!(parsed.command, "dce");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestDCE))
} }
Ok(Box::new(TestDCE))
} }
impl SubTest for TestDCE { impl SubTest for TestDCE {
@@ -32,14 +30,14 @@ impl SubTest for TestDCE {
true true
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
comp_ctx.flowgraph(); comp_ctx.flowgraph();
comp_ctx.compute_loop_analysis(); comp_ctx.compute_loop_analysis();
comp_ctx comp_ctx
.dce(context.flags_or_isa()) .dce(context.flags_or_isa())
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, Into::into(e)))?;
let text = comp_ctx.func.display(context.isa).to_string(); let text = comp_ctx.func.display(context.isa).to_string();
run_filecheck(&text, context) run_filecheck(&text, context)

View File

@@ -13,7 +13,7 @@
//! //!
use crate::match_directive::match_directive; use crate::match_directive::match_directive;
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen::dominator_tree::{DominatorTree, DominatorTreePreorder}; use cranelift_codegen::dominator_tree::{DominatorTree, DominatorTreePreorder};
use cranelift_codegen::flowgraph::ControlFlowGraph; use cranelift_codegen::flowgraph::ControlFlowGraph;
use cranelift_codegen::ir::entities::AnyEntity; use cranelift_codegen::ir::entities::AnyEntity;
@@ -25,13 +25,12 @@ use std::fmt::{self, Write};
struct TestDomtree; struct TestDomtree;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "domtree"); assert_eq!(parsed.command, "domtree");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed)
} else {
Ok(Box::new(TestDomtree))
} }
Ok(Box::new(TestDomtree))
} }
impl SubTest for TestDomtree { impl SubTest for TestDomtree {
@@ -40,7 +39,7 @@ impl SubTest for TestDomtree {
} }
// Extract our own dominator tree from // Extract our own dominator tree from
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let func = func.borrow(); let func = func.borrow();
let cfg = ControlFlowGraph::with_function(func); let cfg = ControlFlowGraph::with_function(func);
let domtree = DominatorTree::with_function(func, &cfg); let domtree = DominatorTree::with_function(func, &cfg);
@@ -52,38 +51,42 @@ impl SubTest for TestDomtree {
let inst = match comment.entity { let inst = match comment.entity {
AnyEntity::Inst(inst) => inst, AnyEntity::Inst(inst) => inst,
_ => { _ => {
return Err(format!( anyhow::bail!(
"annotation on non-inst {}: {}", "annotation on non-inst {}: {}",
comment.entity, comment.text comment.entity,
)); comment.text
);
} }
}; };
for src_block in tail.split_whitespace() { for src_block in tail.split_whitespace() {
let block = match context.details.map.lookup_str(src_block) { let block = match context.details.map.lookup_str(src_block) {
Some(AnyEntity::Block(block)) => block, Some(AnyEntity::Block(block)) => block,
_ => return Err(format!("expected defined block, got {}", src_block)), _ => anyhow::bail!("expected defined block, got {}", src_block),
}; };
// Annotations say that `inst` is the idom of `block`. // Annotations say that `inst` is the idom of `block`.
if expected.insert(block, inst).is_some() { if expected.insert(block, inst).is_some() {
return Err(format!("multiple dominators for {}", src_block)); anyhow::bail!("multiple dominators for {}", src_block);
} }
// Compare to computed domtree. // Compare to computed domtree.
match domtree.idom(block) { match domtree.idom(block) {
Some(got_inst) if got_inst != inst => { Some(got_inst) if got_inst != inst => {
return Err(format!( anyhow::bail!(
"mismatching idoms for {}:\n\ "mismatching idoms for {}:\n\
want: {}, got: {}", want: {}, got: {}",
src_block, inst, got_inst src_block,
)); inst,
got_inst
);
} }
None => { None => {
return Err(format!( anyhow::bail!(
"mismatching idoms for {}:\n\ "mismatching idoms for {}:\n\
want: {}, got: unreachable", want: {}, got: unreachable",
src_block, inst src_block,
)); inst
);
} }
_ => {} _ => {}
} }
@@ -100,11 +103,12 @@ impl SubTest for TestDomtree {
.filter(|block| !expected.contains_key(block)) .filter(|block| !expected.contains_key(block))
{ {
if let Some(got_inst) = domtree.idom(block) { if let Some(got_inst) = domtree.idom(block) {
return Err(format!( anyhow::bail!(
"mismatching idoms for renumbered {}:\n\ "mismatching idoms for renumbered {}:\n\
want: unrechable, got: {}", want: unrechable, got: {}",
block, got_inst block,
)); got_inst
);
} }
} }

View File

@@ -3,7 +3,7 @@
//! The `interpret` test command interprets each function on the host machine //! The `interpret` test command interprets each function on the host machine
//! using [RunCommand](cranelift_reader::RunCommand)s. //! using [RunCommand](cranelift_reader::RunCommand)s.
use crate::subtest::{Context, SubTest, SubtestResult}; use crate::subtest::{Context, SubTest};
use cranelift_codegen::{self, ir}; use cranelift_codegen::{self, ir};
use cranelift_interpreter::environment::Environment; use cranelift_interpreter::environment::Environment;
use cranelift_interpreter::interpreter::{ControlFlow, Interpreter}; use cranelift_interpreter::interpreter::{ControlFlow, Interpreter};
@@ -13,13 +13,12 @@ use std::borrow::Cow;
struct TestInterpret; struct TestInterpret;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "interpret"); assert_eq!(parsed.command, "interpret");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestInterpret))
} }
Ok(Box::new(TestInterpret))
} }
impl SubTest for TestInterpret { impl SubTest for TestInterpret {
@@ -35,26 +34,28 @@ impl SubTest for TestInterpret {
false false
} }
fn run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {
for comment in context.details.comments.iter() { for comment in context.details.comments.iter() {
if let Some(command) = if let Some(command) = parse_run_command(comment.text, &func.signature)? {
parse_run_command(comment.text, &func.signature).map_err(|e| e.to_string())?
{
trace!("Parsed run command: {}", command); trace!("Parsed run command: {}", command);
let mut env = Environment::default(); let mut env = Environment::default();
env.add(func.name.to_string(), func.clone().into_owned()); env.add(func.name.to_string(), func.clone().into_owned());
let interpreter = Interpreter::new(env); let interpreter = Interpreter::new(env);
command.run(|func_name, args| { command
.run(|func_name, args| {
// Because we have stored function names with a leading %, we need to re-add it. // Because we have stored function names with a leading %, we need to re-add it.
let func_name = &format!("%{}", func_name); let func_name = &format!("%{}", func_name);
match interpreter.call_by_name(func_name, args) { match interpreter.call_by_name(func_name, args) {
Ok(ControlFlow::Return(results)) => Ok(results), Ok(ControlFlow::Return(results)) => Ok(results),
Ok(_) => panic!("Unexpected returned control flow--this is likely a bug."), Ok(_) => {
Err(t) => Err(t.to_string()), panic!("Unexpected returned control flow--this is likely a bug.")
} }
})?; Err(t) => Err(format!("unexpected trap: {:?}", t)),
}
})
.map_err(|e| anyhow::anyhow!("{}", e))?;
} }
} }
Ok(()) Ok(())

View File

@@ -3,22 +3,20 @@
//! The `test legalizer` test command runs each function through `legalize_function()` and sends //! The `test legalizer` test command runs each function through `legalize_function()` and sends
//! the result to filecheck. //! the result to filecheck.
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen; use cranelift_codegen;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
struct TestLegalizer; struct TestLegalizer;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "legalizer"); assert_eq!(parsed.command, "legalizer");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestLegalizer))
} }
Ok(Box::new(TestLegalizer))
} }
impl SubTest for TestLegalizer { impl SubTest for TestLegalizer {
@@ -34,14 +32,14 @@ impl SubTest for TestLegalizer {
true true
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
let isa = context.isa.expect("legalizer needs an ISA"); let isa = context.isa.expect("legalizer needs an ISA");
comp_ctx.compute_cfg(); comp_ctx.compute_cfg();
comp_ctx comp_ctx
.legalize(isa) .legalize(isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
let text = comp_ctx.func.display(Some(isa)).to_string(); let text = comp_ctx.func.display(Some(isa)).to_string();
run_filecheck(&text, context) run_filecheck(&text, context)

View File

@@ -5,22 +5,20 @@
//! //!
//! The resulting function is sent to `filecheck`. //! The resulting function is sent to `filecheck`.
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen; use cranelift_codegen;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
struct TestLICM; struct TestLICM;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "licm"); assert_eq!(parsed.command, "licm");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestLICM))
} }
Ok(Box::new(TestLICM))
} }
impl SubTest for TestLICM { impl SubTest for TestLICM {
@@ -32,7 +30,7 @@ impl SubTest for TestLICM {
true true
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("LICM needs an ISA"); let isa = context.isa.expect("LICM needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
@@ -40,7 +38,7 @@ impl SubTest for TestLICM {
comp_ctx.compute_loop_analysis(); comp_ctx.compute_loop_analysis();
comp_ctx comp_ctx
.licm(isa) .licm(isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, Into::into(e)))?;
let text = comp_ctx.func.display(context.isa).to_string(); let text = comp_ctx.func.display(context.isa).to_string();
run_filecheck(&text, context) run_filecheck(&text, context)

View File

@@ -1,20 +1,19 @@
//! Test command for `peepmatic`-generated peephole optimizers. //! Test command for `peepmatic`-generated peephole optimizers.
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen; use cranelift_codegen;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
struct TestPreopt; struct TestPreopt;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "peepmatic"); assert_eq!(parsed.command, "peepmatic");
if parsed.options.is_empty() { if parsed.options.is_empty() {
Ok(Box::new(TestPreopt)) Ok(Box::new(TestPreopt))
} else { } else {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} }
} }
@@ -31,14 +30,14 @@ impl SubTest for TestPreopt {
true true
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
let isa = context.isa.expect("preopt needs an ISA"); let isa = context.isa.expect("preopt needs an ISA");
comp_ctx.compute_cfg(); comp_ctx.compute_cfg();
comp_ctx comp_ctx
.preopt(isa) .preopt(isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, Into::into(e)))?;
let text = &comp_ctx.func.display(isa).to_string(); let text = &comp_ctx.func.display(isa).to_string();
log::debug!("After peepmatic-based simple_preopt:\n{}", text); log::debug!("After peepmatic-based simple_preopt:\n{}", text);

View File

@@ -2,22 +2,20 @@
//! //!
//! The resulting function is sent to `filecheck`. //! The resulting function is sent to `filecheck`.
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen; use cranelift_codegen;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
struct TestPostopt; struct TestPostopt;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "postopt"); assert_eq!(parsed.command, "postopt");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestPostopt))
} }
Ok(Box::new(TestPostopt))
} }
impl SubTest for TestPostopt { impl SubTest for TestPostopt {
@@ -29,14 +27,14 @@ impl SubTest for TestPostopt {
true true
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
let isa = context.isa.expect("postopt needs an ISA"); let isa = context.isa.expect("postopt needs an ISA");
comp_ctx.flowgraph(); comp_ctx.flowgraph();
comp_ctx comp_ctx
.postopt(isa) .postopt(isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, Into::into(e)))?;
let text = comp_ctx.func.display(isa).to_string(); let text = comp_ctx.func.display(isa).to_string();
run_filecheck(&text, context) run_filecheck(&text, context)

View File

@@ -5,23 +5,21 @@
//! //!
//! The resulting function is sent to `filecheck`. //! The resulting function is sent to `filecheck`.
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen; use cranelift_codegen;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_preopt::optimize; use cranelift_preopt::optimize;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
struct TestPreopt; struct TestPreopt;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "preopt"); assert_eq!(parsed.command, "preopt");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestPreopt))
} }
Ok(Box::new(TestPreopt))
} }
impl SubTest for TestPreopt { impl SubTest for TestPreopt {
@@ -37,12 +35,12 @@ impl SubTest for TestPreopt {
true true
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("compile needs an ISA"); let isa = context.isa.expect("compile needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
optimize(&mut comp_ctx, isa) optimize(&mut comp_ctx, isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, Into::into(e)))?;
let text = comp_ctx.func.display(context.isa).to_string(); let text = comp_ctx.func.display(context.isa).to_string();
run_filecheck(&text, context) run_filecheck(&text, context)

View File

@@ -5,7 +5,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use crate::subtest::{self, Context, SubTest, SubtestResult}; use crate::subtest::{self, Context, SubTest};
use cranelift_codegen::cfg_printer::CFGPrinter; use cranelift_codegen::cfg_printer::CFGPrinter;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
@@ -13,13 +13,12 @@ use cranelift_reader::TestCommand;
/// Object implementing the `test print-cfg` sub-test. /// Object implementing the `test print-cfg` sub-test.
struct TestPrintCfg; struct TestPrintCfg;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "print-cfg"); assert_eq!(parsed.command, "print-cfg");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestPrintCfg))
} }
Ok(Box::new(TestPrintCfg))
} }
impl SubTest for TestPrintCfg { impl SubTest for TestPrintCfg {
@@ -31,7 +30,7 @@ impl SubTest for TestPrintCfg {
false false
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
subtest::run_filecheck(&CFGPrinter::new(&func).to_string(), context) subtest::run_filecheck(&CFGPrinter::new(&func).to_string(), context)
} }
} }

View File

@@ -5,22 +5,20 @@
//! //!
//! The resulting function is sent to `filecheck`. //! The resulting function is sent to `filecheck`.
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen; use cranelift_codegen;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
struct TestRegalloc; struct TestRegalloc;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "regalloc"); assert_eq!(parsed.command, "regalloc");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestRegalloc))
} }
Ok(Box::new(TestRegalloc))
} }
impl SubTest for TestRegalloc { impl SubTest for TestRegalloc {
@@ -36,7 +34,7 @@ impl SubTest for TestRegalloc {
true true
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("register allocator needs an ISA"); let isa = context.isa.expect("register allocator needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
@@ -44,11 +42,11 @@ impl SubTest for TestRegalloc {
// TODO: Should we have an option to skip legalization? // TODO: Should we have an option to skip legalization?
comp_ctx comp_ctx
.legalize(isa) .legalize(isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
comp_ctx.compute_domtree(); comp_ctx.compute_domtree();
comp_ctx comp_ctx
.regalloc(isa) .regalloc(isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
let text = comp_ctx.func.display(Some(isa)).to_string(); let text = comp_ctx.func.display(Some(isa)).to_string();
run_filecheck(&text, context) run_filecheck(&text, context)

View File

@@ -2,26 +2,24 @@
//! //!
//! The `rodata` test command runs each function through the full code generator pipeline //! The `rodata` test command runs each function through the full code generator pipeline
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen; use cranelift_codegen;
use cranelift_codegen::binemit::{self, CodeInfo}; use cranelift_codegen::binemit::{self, CodeInfo};
use cranelift_codegen::ir; use cranelift_codegen::ir;
use cranelift_codegen::ir::{Function, Value}; use cranelift_codegen::ir::{Function, Value};
use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use log::info; use log::info;
use std::borrow::Cow; use std::borrow::Cow;
struct TestRodata; struct TestRodata;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "rodata"); assert_eq!(parsed.command, "rodata");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestRodata))
} }
Ok(Box::new(TestRodata))
} }
impl SubTest for TestRodata { impl SubTest for TestRodata {
@@ -37,13 +35,13 @@ impl SubTest for TestRodata {
true true
} }
fn run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("rodata needs an ISA"); let isa = context.isa.expect("rodata needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
let CodeInfo { total_size, .. } = comp_ctx let CodeInfo { total_size, .. } = comp_ctx
.compile(isa) .compile(isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
info!( info!(
"Generated {} bytes of code:\n{}", "Generated {} bytes of code:\n{}",

View File

@@ -3,7 +3,7 @@
//! The `run` test command compiles each function on the host machine and executes it //! The `run` test command compiles each function on the host machine and executes it
use crate::function_runner::SingleFunctionCompiler; use crate::function_runner::SingleFunctionCompiler;
use crate::subtest::{Context, SubTest, SubtestResult}; use crate::subtest::{Context, SubTest};
use cranelift_codegen::ir; use cranelift_codegen::ir;
use cranelift_reader::parse_run_command; use cranelift_reader::parse_run_command;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
@@ -13,13 +13,12 @@ use target_lexicon::Architecture;
struct TestRun; struct TestRun;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "run"); assert_eq!(parsed.command, "run");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestRun))
} }
Ok(Box::new(TestRun))
} }
impl SubTest for TestRun { impl SubTest for TestRun {
@@ -35,7 +34,7 @@ impl SubTest for TestRun {
true true
} }
fn run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {
// If this test requests to run on a completely different // If this test requests to run on a completely different
// architecture than the host platform then we skip it entirely, // architecture than the host platform then we skip it entirely,
// since we won't be able to natively execute machine code. // since we won't be able to natively execute machine code.
@@ -50,9 +49,7 @@ impl SubTest for TestRun {
let mut compiler = SingleFunctionCompiler::with_host_isa(context.flags.clone()); let mut compiler = SingleFunctionCompiler::with_host_isa(context.flags.clone());
for comment in context.details.comments.iter() { for comment in context.details.comments.iter() {
if let Some(command) = if let Some(command) = parse_run_command(comment.text, &func.signature)? {
parse_run_command(comment.text, &func.signature).map_err(|e| e.to_string())?
{
trace!("Parsed run command: {}", command); trace!("Parsed run command: {}", command);
// Note that here we're also explicitly ignoring `context.isa`, // Note that here we're also explicitly ignoring `context.isa`,
@@ -60,10 +57,10 @@ impl SubTest for TestRun {
// host ISA no matter what here, so the ISA listed in the file // host ISA no matter what here, so the ISA listed in the file
// is only used as a filter to not run into situations like // is only used as a filter to not run into situations like
// running x86_64 code on aarch64 platforms. // running x86_64 code on aarch64 platforms.
let compiled_fn = compiler let compiled_fn = compiler.compile(func.clone().into_owned())?;
.compile(func.clone().into_owned()) command
.map_err(|e| format!("{:?}", e))?; .run(|_, args| Ok(compiled_fn.call(args)))
command.run(|_, args| Ok(compiled_fn.call(args)))?; .map_err(|s| anyhow::anyhow!("{}", s))?;
} }
} }
Ok(()) Ok(())

View File

@@ -1,18 +1,16 @@
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
struct TestSafepoint; struct TestSafepoint;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "safepoint"); assert_eq!(parsed.command, "safepoint");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestSafepoint))
} }
Ok(Box::new(TestSafepoint))
} }
impl SubTest for TestSafepoint { impl SubTest for TestSafepoint {
@@ -20,18 +18,18 @@ impl SubTest for TestSafepoint {
"safepoint" "safepoint"
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
let isa = context.isa.expect("register allocator needs an ISA"); let isa = context.isa.expect("register allocator needs an ISA");
comp_ctx.compute_cfg(); comp_ctx.compute_cfg();
comp_ctx comp_ctx
.legalize(isa) .legalize(isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
comp_ctx.compute_domtree(); comp_ctx.compute_domtree();
comp_ctx comp_ctx
.regalloc(isa) .regalloc(isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
let text = comp_ctx.func.display(context.isa).to_string(); let text = comp_ctx.func.display(context.isa).to_string();
run_filecheck(&text, context) run_filecheck(&text, context)

View File

@@ -5,22 +5,20 @@
//! //!
//! The resulting function is sent to `filecheck`. //! The resulting function is sent to `filecheck`.
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen; use cranelift_codegen;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
struct TestShrink; struct TestShrink;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "shrink"); assert_eq!(parsed.command, "shrink");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestShrink))
} }
Ok(Box::new(TestShrink))
} }
impl SubTest for TestShrink { impl SubTest for TestShrink {
@@ -32,13 +30,13 @@ impl SubTest for TestShrink {
true true
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("shrink needs an ISA"); let isa = context.isa.expect("shrink needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
comp_ctx comp_ctx
.shrink_instructions(isa) .shrink_instructions(isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, Into::into(e)))?;
let text = comp_ctx.func.display(isa).to_string(); let text = comp_ctx.func.display(isa).to_string();
run_filecheck(&text, context) run_filecheck(&text, context)

View File

@@ -5,22 +5,20 @@
//! //!
//! The resulting function is sent to `filecheck`. //! The resulting function is sent to `filecheck`.
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen; use cranelift_codegen;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
struct TestSimpleGVN; struct TestSimpleGVN;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "simple-gvn"); assert_eq!(parsed.command, "simple-gvn");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestSimpleGVN))
} }
Ok(Box::new(TestSimpleGVN))
} }
impl SubTest for TestSimpleGVN { impl SubTest for TestSimpleGVN {
@@ -32,13 +30,13 @@ impl SubTest for TestSimpleGVN {
true true
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
comp_ctx.flowgraph(); comp_ctx.flowgraph();
comp_ctx comp_ctx
.simple_gvn(context.flags_or_isa()) .simple_gvn(context.flags_or_isa())
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, Into::into(e)))?;
let text = comp_ctx.func.display(context.isa).to_string(); let text = comp_ctx.func.display(context.isa).to_string();
run_filecheck(&text, context) run_filecheck(&text, context)

View File

@@ -2,22 +2,20 @@
//! //!
//! The resulting function is sent to `filecheck`. //! The resulting function is sent to `filecheck`.
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen; use cranelift_codegen;
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
struct TestSimplePreopt; struct TestSimplePreopt;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "simple_preopt"); assert_eq!(parsed.command, "simple_preopt");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestSimplePreopt))
} }
Ok(Box::new(TestSimplePreopt))
} }
impl SubTest for TestSimplePreopt { impl SubTest for TestSimplePreopt {
@@ -29,14 +27,14 @@ impl SubTest for TestSimplePreopt {
true true
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
let isa = context.isa.expect("preopt needs an ISA"); let isa = context.isa.expect("preopt needs an ISA");
comp_ctx.compute_cfg(); comp_ctx.compute_cfg();
comp_ctx comp_ctx
.preopt(isa) .preopt(isa)
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, Into::into(e)))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
let text = &comp_ctx.func.display(isa).to_string(); let text = &comp_ctx.func.display(isa).to_string();
log::debug!("After simple_preopt:\n{}", text); log::debug!("After simple_preopt:\n{}", text);

View File

@@ -1,21 +1,19 @@
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen::binemit::{self, Addend, CodeOffset, CodeSink, Reloc, StackMap}; use cranelift_codegen::binemit::{self, Addend, CodeOffset, CodeSink, Reloc, StackMap};
use cranelift_codegen::ir::*; use cranelift_codegen::ir::*;
use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::print_errors::pretty_error;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::Write; use std::fmt::Write;
struct TestStackMaps; struct TestStackMaps;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "stack_maps"); assert_eq!(parsed.command, "stack_maps");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestStackMaps))
} }
Ok(Box::new(TestStackMaps))
} }
impl SubTest for TestStackMaps { impl SubTest for TestStackMaps {
@@ -23,12 +21,12 @@ impl SubTest for TestStackMaps {
"stack_maps" "stack_maps"
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
comp_ctx comp_ctx
.compile(context.isa.expect("`test stack_maps` requires an isa")) .compile(context.isa.expect("`test stack_maps` requires an isa"))
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, context.isa, e))?;
let mut sink = TestStackMapsSink::default(); let mut sink = TestStackMapsSink::default();
binemit::emit_function( binemit::emit_function(

View File

@@ -3,7 +3,7 @@
//! The `unwind` test command runs each function through the full code generator pipeline. //! The `unwind` test command runs each function through the full code generator pipeline.
#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))] #![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen::{self, ir, isa::unwind::UnwindInfo}; use cranelift_codegen::{self, ir, isa::unwind::UnwindInfo};
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
use gimli::{ use gimli::{
@@ -14,13 +14,12 @@ use std::borrow::Cow;
struct TestUnwind; struct TestUnwind;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "unwind"); assert_eq!(parsed.command, "unwind");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestUnwind))
} }
Ok(Box::new(TestUnwind))
} }
impl SubTest for TestUnwind { impl SubTest for TestUnwind {
@@ -36,7 +35,7 @@ impl SubTest for TestUnwind {
true true
} }
fn run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("unwind needs an ISA"); let isa = context.isa.expect("unwind needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());

View File

@@ -10,7 +10,7 @@
//! containing the substring "jump to non-existent block". //! containing the substring "jump to non-existent block".
use crate::match_directive::match_directive; use crate::match_directive::match_directive;
use crate::subtest::{Context, SubTest, SubtestResult}; use crate::subtest::{Context, SubTest};
use cranelift_codegen::ir::Function; use cranelift_codegen::ir::Function;
use cranelift_codegen::verify_function; use cranelift_codegen::verify_function;
use cranelift_reader::TestCommand; use cranelift_reader::TestCommand;
@@ -19,13 +19,12 @@ use std::fmt::Write;
struct TestVerifier; struct TestVerifier;
pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> { pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "verifier"); assert_eq!(parsed.command, "verifier");
if !parsed.options.is_empty() { if !parsed.options.is_empty() {
Err(format!("No options allowed on {}", parsed)) anyhow::bail!("No options allowed on {}", parsed);
} else {
Ok(Box::new(TestVerifier))
} }
Ok(Box::new(TestVerifier))
} }
impl SubTest for TestVerifier { impl SubTest for TestVerifier {
@@ -38,7 +37,7 @@ impl SubTest for TestVerifier {
false false
} }
fn run(&self, func: Cow<Function>, context: &Context) -> SubtestResult<()> { fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
let func = func.borrow(); let func = func.borrow();
// Scan source annotations for "error:" directives. // Scan source annotations for "error:" directives.
@@ -52,10 +51,10 @@ impl SubTest for TestVerifier {
match verify_function(func, context.flags_or_isa()) { match verify_function(func, context.flags_or_isa()) {
Ok(()) if expected.is_empty() => Ok(()), Ok(()) if expected.is_empty() => Ok(()),
Ok(()) => Err(format!("passed, but expected errors: {:?}", expected)), Ok(()) => anyhow::bail!("passed, but expected errors: {:?}", expected),
Err(ref errors) if expected.is_empty() => { Err(ref errors) if expected.is_empty() => {
Err(format!("expected no error, but got:\n{}", errors)) anyhow::bail!("expected no error, but got:\n{}", errors);
} }
Err(errors) => { Err(errors) => {
@@ -86,7 +85,7 @@ impl SubTest for TestVerifier {
if msg.is_empty() { if msg.is_empty() {
Ok(()) Ok(())
} else { } else {
Err(msg) anyhow::bail!("{}", msg);
} }
} }
} }

View File

@@ -33,6 +33,8 @@ impl fmt::Display for ParseError {
} }
} }
impl std::error::Error for ParseError {}
/// Result of a parser operation. The `ParseError` variant includes a location. /// Result of a parser operation. The `ParseError` variant includes a location.
pub type ParseResult<T> = Result<T, ParseError>; pub type ParseResult<T> = Result<T, ParseError>;

View File

@@ -2,6 +2,7 @@
use crate::disasm::{PrintRelocs, PrintStackMaps, PrintTraps}; use crate::disasm::{PrintRelocs, PrintStackMaps, PrintTraps};
use crate::utils::{parse_sets_and_triple, read_to_string}; use crate::utils::{parse_sets_and_triple, read_to_string};
use anyhow::{Context as _, Result};
use cranelift_codegen::cursor::{Cursor, FuncCursor}; use cranelift_codegen::cursor::{Cursor, FuncCursor};
use cranelift_codegen::flowgraph::ControlFlowGraph; use cranelift_codegen::flowgraph::ControlFlowGraph;
use cranelift_codegen::ir::types::{F32, F64}; use cranelift_codegen::ir::types::{F32, F64};
@@ -13,25 +14,19 @@ use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::Context; use cranelift_codegen::Context;
use cranelift_entity::PrimaryMap; use cranelift_entity::PrimaryMap;
use cranelift_reader::{parse_test, ParseOptions}; use cranelift_reader::{parse_test, ParseOptions};
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}; pub fn run(filename: &str, flag_set: &[String], flag_isa: &str, verbose: bool) -> Result<()> {
pub fn run(
filename: &str,
flag_set: &[String],
flag_isa: &str,
verbose: bool,
) -> Result<(), String> {
let parsed = parse_sets_and_triple(flag_set, flag_isa)?; let parsed = parse_sets_and_triple(flag_set, flag_isa)?;
let fisa = parsed.as_fisa(); let fisa = parsed.as_fisa();
let path = Path::new(&filename).to_path_buf(); let path = Path::new(&filename).to_path_buf();
let buffer = read_to_string(&path).map_err(|e| format!("{}: {}", filename, e))?; let buffer = read_to_string(&path)?;
let test_file = let test_file = parse_test(&buffer, ParseOptions::default())
parse_test(&buffer, ParseOptions::default()).map_err(|e| format!("{}: {}", filename, e))?; .with_context(|| format!("failed to parse {}", filename))?;
// If we have an isa from the command-line, use that. Otherwise if the // If we have an isa from the command-line, use that. Otherwise if the
// file contains a unique isa, use that. // file contains a unique isa, use that.
@@ -40,7 +35,7 @@ pub fn run(
} else if let Some(isa) = test_file.isa_spec.unique_isa() { } else if let Some(isa) = test_file.isa_spec.unique_isa() {
isa isa
} else { } else {
return Err(String::from("compilation requires a target isa")); anyhow::bail!("compilation requires a target isa");
}; };
std::env::set_var("RUST_BACKTRACE", "0"); // Disable backtraces to reduce verbosity std::env::set_var("RUST_BACKTRACE", "0"); // Disable backtraces to reduce verbosity
@@ -833,20 +828,11 @@ fn try_resolve_aliases(context: &mut CrashCheckContext, func: &mut Function) {
} }
} }
fn reduce( fn reduce(isa: &dyn TargetIsa, mut func: Function, verbose: bool) -> Result<(Function, String)> {
isa: &dyn TargetIsa,
mut func: Function,
verbose: bool,
) -> Result<(Function, String), String> {
let mut context = CrashCheckContext::new(isa); let mut context = CrashCheckContext::new(isa);
match context.check_for_crash(&func) { if let CheckResult::Succeed = context.check_for_crash(&func) {
CheckResult::Succeed => { anyhow::bail!("Given function compiled successfully or gave a verifier error.");
return Err(
"Given function compiled successfully or gave a verifier error.".to_string(),
);
}
CheckResult::Crash(_) => {}
} }
try_resolve_aliases(&mut context, &mut func); try_resolve_aliases(&mut context, &mut func);

View File

@@ -4,10 +4,10 @@
//! normalizing formatting and removing comments. //! normalizing formatting and removing comments.
use crate::utils::read_to_string; use crate::utils::read_to_string;
use crate::CommandResult; use anyhow::{Context, Result};
use cranelift_reader::parse_functions; use cranelift_reader::parse_functions;
pub fn run(files: &[String]) -> CommandResult { pub fn run(files: &[String]) -> Result<()> {
for (i, f) in files.iter().enumerate() { for (i, f) in files.iter().enumerate() {
if i != 0 { if i != 0 {
println!(); println!();
@@ -17,9 +17,10 @@ pub fn run(files: &[String]) -> CommandResult {
Ok(()) Ok(())
} }
fn cat_one(filename: &str) -> CommandResult { fn cat_one(filename: &str) -> Result<()> {
let buffer = read_to_string(&filename).map_err(|e| format!("{}: {}", filename, e))?; let buffer = read_to_string(&filename)?;
let items = parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))?; let items =
parse_functions(&buffer).with_context(|| format!("failed to parse {}", filename))?;
for (idx, func) in items.into_iter().enumerate() { for (idx, func) in items.into_iter().enumerate() {
if idx != 0 { if idx != 0 {

View File

@@ -16,9 +16,7 @@
use clap::{arg_enum, App, Arg, SubCommand}; use clap::{arg_enum, App, Arg, SubCommand};
use cranelift_codegen::dbg::LOG_FILENAME_PREFIX; use cranelift_codegen::dbg::LOG_FILENAME_PREFIX;
use cranelift_codegen::VERSION; use cranelift_codegen::VERSION;
use std::io::{self, Write};
use std::option::Option; use std::option::Option;
use std::process;
mod bugpoint; mod bugpoint;
mod cat; mod cat;
@@ -37,9 +35,6 @@ mod souper_to_peepmatic;
#[cfg(feature = "wasm")] #[cfg(feature = "wasm")]
mod wasm; mod wasm;
/// A command either succeeds or fails with an error message.
pub type CommandResult = Result<(), String>;
fn add_input_file_arg<'a>() -> clap::Arg<'a, 'a> { fn add_input_file_arg<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("file") Arg::with_name("file")
.default_value("-") .default_value("-")
@@ -199,7 +194,7 @@ fn handle_debug_flag(debug: bool) {
} }
} }
fn main() { fn main() -> anyhow::Result<()> {
let app_cmds = App::new("Cranelift code generator utility") let app_cmds = App::new("Cranelift code generator utility")
.version(VERSION) .version(VERSION)
.subcommand( .subcommand(
@@ -276,10 +271,10 @@ fn main() {
.arg(add_set_flag()), .arg(add_set_flag()),
); );
let res_util = match app_cmds.get_matches().subcommand() { match app_cmds.get_matches().subcommand() {
("cat", Some(rest_cmd)) => { ("cat", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug")); handle_debug_flag(rest_cmd.is_present("debug"));
cat::run(&get_vec(rest_cmd.values_of("file"))) cat::run(&get_vec(rest_cmd.values_of("file")))?
} }
("test", Some(rest_cmd)) => { ("test", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug")); handle_debug_flag(rest_cmd.is_present("debug"));
@@ -288,23 +283,21 @@ fn main() {
rest_cmd.is_present("time-passes"), rest_cmd.is_present("time-passes"),
&get_vec(rest_cmd.values_of("file")), &get_vec(rest_cmd.values_of("file")),
) )
.map(|_time| ()) .map_err(|s| anyhow::anyhow!("{}", s))?;
} }
("run", Some(rest_cmd)) => { ("run", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug")); handle_debug_flag(rest_cmd.is_present("debug"));
run::run( run::run(
get_vec(rest_cmd.values_of("file")), get_vec(rest_cmd.values_of("file")),
rest_cmd.is_present("verbose"), rest_cmd.is_present("verbose"),
) )?;
.map(|_time| ())
} }
("interpret", Some(rest_cmd)) => { ("interpret", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug")); handle_debug_flag(rest_cmd.is_present("debug"));
interpret::run( interpret::run(
get_vec(rest_cmd.values_of("file")), get_vec(rest_cmd.values_of("file")),
rest_cmd.is_present("verbose"), rest_cmd.is_present("verbose"),
) )?;
.map(|_time| ())
} }
("pass", Some(rest_cmd)) => { ("pass", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug")); handle_debug_flag(rest_cmd.is_present("debug"));
@@ -322,11 +315,11 @@ fn main() {
target_val, target_val,
rest_cmd.value_of("single-file").unwrap(), rest_cmd.value_of("single-file").unwrap(),
) )
.map(|_time| ()) .map_err(|s| anyhow::anyhow!("{}", s))?;
} }
("print-cfg", Some(rest_cmd)) => { ("print-cfg", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug")); handle_debug_flag(rest_cmd.is_present("debug"));
print_cfg::run(&get_vec(rest_cmd.values_of("file"))) print_cfg::run(&get_vec(rest_cmd.values_of("file")))?;
} }
("compile", Some(rest_cmd)) => { ("compile", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug")); handle_debug_flag(rest_cmd.is_present("debug"));
@@ -343,13 +336,13 @@ fn main() {
rest_cmd.is_present("time-passes"), rest_cmd.is_present("time-passes"),
&get_vec(rest_cmd.values_of("set")), &get_vec(rest_cmd.values_of("set")),
target_val, target_val,
) )?;
} }
("wasm", Some(rest_cmd)) => { ("wasm", Some(rest_cmd)) => {
handle_debug_flag(rest_cmd.is_present("debug")); handle_debug_flag(rest_cmd.is_present("debug"));
#[cfg(feature = "wasm")] #[cfg(feature = "wasm")]
let result = { {
let mut target_val: &str = ""; let mut target_val: &str = "";
if let Some(clap_target) = rest_cmd.value_of("target") { if let Some(clap_target) = rest_cmd.value_of("target") {
target_val = clap_target; target_val = clap_target;
@@ -368,13 +361,13 @@ fn main() {
rest_cmd.is_present("print-size"), rest_cmd.is_present("print-size"),
rest_cmd.is_present("time-passes"), rest_cmd.is_present("time-passes"),
rest_cmd.is_present("value-ranges"), rest_cmd.is_present("value-ranges"),
) )?;
}; }
#[cfg(not(feature = "wasm"))] #[cfg(not(feature = "wasm"))]
let result = Err("Error: clif-util was compiled without wasm support.".to_owned()); {
anyhow::bail!("Error: clif-util was compiled without wasm support.");
result }
} }
("bugpoint", Some(rest_cmd)) => { ("bugpoint", Some(rest_cmd)) => {
let mut target_val: &str = ""; let mut target_val: &str = "";
@@ -387,7 +380,7 @@ fn main() {
&get_vec(rest_cmd.values_of("set")), &get_vec(rest_cmd.values_of("set")),
target_val, target_val,
rest_cmd.is_present("verbose"), rest_cmd.is_present("verbose"),
) )?;
} }
("souper-to-peepmatic", Some(rest_cmd)) => { ("souper-to-peepmatic", Some(rest_cmd)) => {
#[cfg(feature = "peepmatic-souper")] #[cfg(feature = "peepmatic-souper")]
@@ -396,15 +389,15 @@ fn main() {
souper_to_peepmatic::run( souper_to_peepmatic::run(
Path::new(rest_cmd.value_of("single-file").unwrap()), Path::new(rest_cmd.value_of("single-file").unwrap()),
Path::new(rest_cmd.value_of("output").unwrap()), Path::new(rest_cmd.value_of("output").unwrap()),
) )?;
} }
#[cfg(not(feature = "peepmatic-souper"))] #[cfg(not(feature = "peepmatic-souper"))]
{ {
Err( anyhow::bail!(
"Error: clif-util was compiled without support for the `souper-to-peepmatic` \ "Error: clif-util was compiled without suport for the `souper-to-peepmatic` \
subcommand" subcommand"
.into(), .into(),
) );
} }
} }
("souper-harvest", Some(rest_cmd)) => { ("souper-harvest", Some(rest_cmd)) => {
@@ -415,23 +408,16 @@ fn main() {
rest_cmd.value_of("single-file").unwrap(), rest_cmd.value_of("single-file").unwrap(),
rest_cmd.value_of("output").unwrap(), rest_cmd.value_of("output").unwrap(),
&get_vec(rest_cmd.values_of("set")), &get_vec(rest_cmd.values_of("set")),
) )?;
} }
#[cfg(not(feature = "souper-harvest"))] #[cfg(not(feature = "souper-harvest"))]
{ {
Err("clif-util was compiled without `souper-harvest` support".into()) anyhow::bail!("clif-util was compiled without `souper-harvest` support");
} }
} }
_ => Err("Invalid subcommand.".to_owned()), _ => anyhow::bail!("Invalid subcommand.".to_owned()),
}; }
if let Err(mut msg) = res_util { Ok(())
if !msg.ends_with('\n') {
msg.push('\n');
}
io::stdout().flush().expect("flushing stdout");
io::stderr().write_all(msg.as_bytes()).unwrap();
process::exit(1);
}
} }

View File

@@ -2,6 +2,7 @@
use crate::disasm::{print_all, PrintRelocs, PrintStackMaps, PrintTraps}; use crate::disasm::{print_all, PrintRelocs, PrintStackMaps, PrintTraps};
use crate::utils::{parse_sets_and_triple, read_to_string}; use crate::utils::{parse_sets_and_triple, read_to_string};
use anyhow::{Context as _, Result};
use cranelift_codegen::print_errors::pretty_error; use cranelift_codegen::print_errors::pretty_error;
use cranelift_codegen::settings::FlagsOrIsa; use cranelift_codegen::settings::FlagsOrIsa;
use cranelift_codegen::timing; use cranelift_codegen::timing;
@@ -17,7 +18,7 @@ pub fn run(
flag_report_times: bool, flag_report_times: bool,
flag_set: &[String], flag_set: &[String],
flag_isa: &str, flag_isa: &str,
) -> Result<(), String> { ) -> Result<()> {
let parsed = parse_sets_and_triple(flag_set, flag_isa)?; let parsed = parse_sets_and_triple(flag_set, flag_isa)?;
for filename in files { for filename in files {
@@ -42,17 +43,17 @@ fn handle_module(
path: &PathBuf, path: &PathBuf,
name: &str, name: &str,
fisa: FlagsOrIsa, fisa: FlagsOrIsa,
) -> Result<(), String> { ) -> Result<()> {
let buffer = read_to_string(&path).map_err(|e| format!("{}: {}", name, e))?; let buffer = read_to_string(&path)?;
let test_file = let test_file = parse_test(&buffer, ParseOptions::default())
parse_test(&buffer, ParseOptions::default()).map_err(|e| format!("{}: {}", name, e))?; .with_context(|| format!("failed to parse {}", name))?;
// If we have an isa from the command-line, use that. Otherwise if the // If we have an isa from the command-line, use that. Otherwise if the
// file contains a unique isa, use that. // file contains a unique isa, use that.
let isa = fisa.isa.or(test_file.isa_spec.unique_isa()); let isa = fisa.isa.or(test_file.isa_spec.unique_isa());
if isa.is_none() { if isa.is_none() {
return Err(String::from("compilation requires a target isa")); anyhow::bail!("compilation requires a target isa");
}; };
for (func, _) in test_file.functions { for (func, _) in test_file.functions {
@@ -68,7 +69,9 @@ fn handle_module(
// Compile and encode the result to machine code. // Compile and encode the result to machine code.
let code_info = context let code_info = context
.compile_and_emit(isa, &mut mem, &mut relocs, &mut traps, &mut stack_maps) .compile_and_emit(isa, &mut mem, &mut relocs, &mut traps, &mut stack_maps)
.map_err(|err| pretty_error(&context.func, Some(isa), err))?; .map_err(|err| {
anyhow::anyhow!("{}", pretty_error(&context.func, Some(isa), err))
})?;
if flag_print { if flag_print {
println!("{}", context.func.display(isa)); println!("{}", context.func.display(isa));

View File

@@ -1,3 +1,4 @@
use anyhow::Result;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::{binemit, ir}; use cranelift_codegen::{binemit, ir};
@@ -124,53 +125,52 @@ cfg_if! {
use capstone::prelude::*; use capstone::prelude::*;
use target_lexicon::Architecture; use target_lexicon::Architecture;
fn get_disassembler(isa: &dyn TargetIsa) -> Result<Capstone, String> { fn get_disassembler(isa: &dyn TargetIsa) -> Result<Capstone> {
let cs = match isa.triple().architecture { let cs = match isa.triple().architecture {
Architecture::Riscv32 | Architecture::Riscv64 => { Architecture::Riscv32 | Architecture::Riscv64 => {
return Err(String::from("No disassembler for RiscV")) anyhow::bail!("No disassembler for RiscV");
} }
Architecture::I386 | Architecture::I586 | Architecture::I686 => Capstone::new() Architecture::I386 | Architecture::I586 | Architecture::I686 => Capstone::new()
.x86() .x86()
.mode(arch::x86::ArchMode::Mode32) .mode(arch::x86::ArchMode::Mode32)
.build(), .build()?,
Architecture::X86_64 => Capstone::new() Architecture::X86_64 => Capstone::new()
.x86() .x86()
.mode(arch::x86::ArchMode::Mode64) .mode(arch::x86::ArchMode::Mode64)
.build(), .build()?,
Architecture::Arm(arm) => { Architecture::Arm(arm) => {
if arm.is_thumb() { if arm.is_thumb() {
Capstone::new() Capstone::new()
.arm() .arm()
.mode(arch::arm::ArchMode::Thumb) .mode(arch::arm::ArchMode::Thumb)
.build() .build()?
} else { } else {
Capstone::new() Capstone::new()
.arm() .arm()
.mode(arch::arm::ArchMode::Arm) .mode(arch::arm::ArchMode::Arm)
.build() .build()?
} }
} }
Architecture::Aarch64 {..} => { Architecture::Aarch64 {..} => {
let mut cs = Capstone::new() let mut cs = Capstone::new()
.arm64() .arm64()
.mode(arch::arm64::ArchMode::Arm) .mode(arch::arm64::ArchMode::Arm)
.build() .build()?;
.map_err(|err| err.to_string())?;
// AArch64 uses inline constants rather than a separate constant pool right now. // AArch64 uses inline constants rather than a separate constant pool right now.
// Without this option, Capstone will stop disassembling as soon as it sees // Without this option, Capstone will stop disassembling as soon as it sees
// an inline constant that is not also a valid instruction. With this option, // an inline constant that is not also a valid instruction. With this option,
// Capstone will print a `.byte` directive with the bytes of the inline constant // Capstone will print a `.byte` directive with the bytes of the inline constant
// and continue to the next instruction. // and continue to the next instruction.
cs.set_skipdata(true).map_err(|err| err.to_string())?; cs.set_skipdata(true)?;
Ok(cs) cs
} }
_ => return Err(String::from("Unknown ISA")), _ => anyhow::bail!("Unknown ISA"),
}; };
cs.map_err(|err| err.to_string()) Ok(cs)
} }
pub fn print_disassembly(isa: &dyn TargetIsa, mem: &[u8]) -> Result<(), String> { pub fn print_disassembly(isa: &dyn TargetIsa, mem: &[u8]) -> Result<()> {
let cs = get_disassembler(isa)?; let cs = get_disassembler(isa)?;
println!("\nDisassembly of {} bytes:", mem.len()); println!("\nDisassembly of {} bytes:", mem.len());
@@ -209,7 +209,7 @@ cfg_if! {
Ok(()) Ok(())
} }
} else { } else {
pub fn print_disassembly(_: &dyn TargetIsa, _: &[u8]) -> Result<(), String> { pub fn print_disassembly(_: &dyn TargetIsa, _: &[u8]) -> Result<()> {
println!("\nNo disassembly available."); println!("\nNo disassembly available.");
Ok(()) Ok(())
} }
@@ -224,7 +224,7 @@ pub fn print_all(
relocs: &PrintRelocs, relocs: &PrintRelocs,
traps: &PrintTraps, traps: &PrintTraps,
stack_maps: &PrintStackMaps, stack_maps: &PrintStackMaps,
) -> Result<(), String> { ) -> Result<()> {
print_bytes(&mem); print_bytes(&mem);
print_disassembly(isa, &mem[0..code_size as usize])?; print_disassembly(isa, &mem[0..code_size as usize])?;
print_readonly_data(&mem[code_size as usize..(code_size + rodata_size) as usize]); print_readonly_data(&mem[code_size as usize..(code_size + rodata_size) as usize]);

View File

@@ -10,12 +10,12 @@ use std::{fs, io};
use thiserror::Error; use thiserror::Error;
/// Run files through the Cranelift interpreter, interpreting any functions with annotations. /// Run files through the Cranelift interpreter, interpreting any functions with annotations.
pub fn run(files: Vec<String>, flag_print: bool) -> Result<(), String> { pub fn run(files: Vec<String>, flag_print: bool) -> anyhow::Result<()> {
let mut total = 0; let mut total = 0;
let mut errors = 0; let mut errors = 0;
for file in iterate_files(files) { for file in iterate_files(files) {
total += 1; total += 1;
let runner = FileInterpreter::from_path(file).map_err(|e| e.to_string())?; let runner = FileInterpreter::from_path(file)?;
match runner.run() { match runner.run() {
Ok(_) => { Ok(_) => {
if flag_print { if flag_print {
@@ -41,8 +41,8 @@ pub fn run(files: Vec<String>, flag_print: bool) -> Result<(), String> {
match errors { match errors {
0 => Ok(()), 0 => Ok(()),
1 => Err(String::from("1 failure")), 1 => anyhow::bail!("1 failure"),
n => Err(format!("{} failures", n)), n => anyhow::bail!("{} failures", n),
} }
} }

View File

@@ -4,11 +4,11 @@
//! in graphviz format. //! in graphviz format.
use crate::utils::read_to_string; use crate::utils::read_to_string;
use crate::CommandResult; use anyhow::Result;
use cranelift_codegen::cfg_printer::CFGPrinter; use cranelift_codegen::cfg_printer::CFGPrinter;
use cranelift_reader::parse_functions; use cranelift_reader::parse_functions;
pub fn run(files: &[String]) -> CommandResult { pub fn run(files: &[String]) -> Result<()> {
for (i, f) in files.iter().enumerate() { for (i, f) in files.iter().enumerate() {
if i != 0 { if i != 0 {
println!(); println!();
@@ -18,9 +18,9 @@ pub fn run(files: &[String]) -> CommandResult {
Ok(()) Ok(())
} }
fn print_cfg(filename: &str) -> CommandResult { fn print_cfg(filename: &str) -> Result<()> {
let buffer = read_to_string(filename).map_err(|e| format!("{}: {}", filename, e))?; let buffer = read_to_string(filename)?;
let items = parse_functions(&buffer).map_err(|e| format!("{}: {}", filename, e))?; let items = parse_functions(&buffer)?;
for (idx, func) in items.into_iter().enumerate() { for (idx, func) in items.into_iter().enumerate() {
if idx != 0 { if idx != 0 {

View File

@@ -1,6 +1,7 @@
//! CLI tool to compile Cranelift IR files to native code in memory and execute them. //! CLI tool to compile Cranelift IR files to native code in memory and execute them.
use crate::utils::{iterate_files, read_to_string}; use crate::utils::{iterate_files, read_to_string};
use anyhow::Result;
use cranelift_codegen::isa::{CallConv, TargetIsa}; use cranelift_codegen::isa::{CallConv, TargetIsa};
use cranelift_filetests::SingleFunctionCompiler; use cranelift_filetests::SingleFunctionCompiler;
use cranelift_native::builder as host_isa_builder; use cranelift_native::builder as host_isa_builder;
@@ -8,7 +9,7 @@ use cranelift_reader::{parse_run_command, parse_test, Details, IsaSpec, ParseOpt
use std::path::PathBuf; use std::path::PathBuf;
use target_lexicon::Triple; use target_lexicon::Triple;
pub fn run(files: Vec<String>, flag_print: bool) -> Result<(), String> { pub fn run(files: Vec<String>, flag_print: bool) -> Result<()> {
let stdin_exist = files.iter().find(|file| *file == "-").is_some(); let stdin_exist = files.iter().find(|file| *file == "-").is_some();
let filtered_files = files let filtered_files = files
.iter() .iter()
@@ -48,33 +49,33 @@ pub fn run(files: Vec<String>, flag_print: bool) -> Result<(), String> {
match errors { match errors {
0 => Ok(()), 0 => Ok(()),
1 => Err(String::from("1 failure")), 1 => anyhow::bail!("1 failure"),
n => Err(format!("{} failures", n)), n => anyhow::bail!("{} failures", n),
} }
} }
/// Run all functions in a file that are succeeded by "run:" comments /// Run all functions in a file that are succeeded by "run:" comments
fn run_single_file(path: &PathBuf) -> Result<(), String> { fn run_single_file(path: &PathBuf) -> Result<()> {
let file_contents = read_to_string(&path).map_err(|e| e.to_string())?; let file_contents = read_to_string(&path)?;
run_file_contents(file_contents) run_file_contents(file_contents)
} }
/// Main body of `run_single_file` separated for testing /// Main body of `run_single_file` separated for testing
fn run_file_contents(file_contents: String) -> Result<(), String> { fn run_file_contents(file_contents: String) -> Result<()> {
let options = ParseOptions { let options = ParseOptions {
default_calling_convention: CallConv::triple_default(&Triple::host()), // use the host's default calling convention default_calling_convention: CallConv::triple_default(&Triple::host()), // use the host's default calling convention
..ParseOptions::default() ..ParseOptions::default()
}; };
let test_file = parse_test(&file_contents, options).map_err(|e| e.to_string())?; let test_file = parse_test(&file_contents, options)?;
let isa = create_target_isa(&test_file.isa_spec)?; let isa = create_target_isa(&test_file.isa_spec)?;
let mut compiler = SingleFunctionCompiler::new(isa); let mut compiler = SingleFunctionCompiler::new(isa);
for (func, Details { comments, .. }) in test_file.functions { for (func, Details { comments, .. }) in test_file.functions {
for comment in comments { for comment in comments {
if let Some(command) = if let Some(command) = parse_run_command(comment.text, &func.signature)? {
parse_run_command(comment.text, &func.signature).map_err(|e| e.to_string())? let compiled_fn = compiler.compile(func.clone())?;
{ command
let compiled_fn = compiler.compile(func.clone()).map_err(|e| e.to_string())?; .run(|_, args| Ok(compiled_fn.call(args)))
command.run(|_, args| Ok(compiled_fn.call(args)))?; .map_err(|s| anyhow::anyhow!("{}", s))?;
} }
} }
} }
@@ -82,13 +83,16 @@ fn run_file_contents(file_contents: String) -> Result<(), String> {
} }
/// Build an ISA based on the current machine running this code (the host) /// Build an ISA based on the current machine running this code (the host)
fn create_target_isa(isa_spec: &IsaSpec) -> Result<Box<dyn TargetIsa>, String> { fn create_target_isa(isa_spec: &IsaSpec) -> Result<Box<dyn TargetIsa>> {
if let IsaSpec::None(flags) = isa_spec { if let IsaSpec::None(flags) = isa_spec {
// build an ISA for the current machine // build an ISA for the current machine
let builder = host_isa_builder()?; let builder = host_isa_builder().map_err(|s| anyhow::anyhow!("{}", s))?;
Ok(builder.finish(flags.clone())) Ok(builder.finish(flags.clone()))
} else { } else {
Err(String::from("A target ISA was specified in the file but should not have been--only the host ISA can be used for running CLIF files")) anyhow::bail!(
"A target ISA was specified in the file but should not have been--only \
the host ISA can be used for running CLIF files"
)
} }
} }

View File

@@ -1,4 +1,5 @@
use crate::utils::parse_sets_and_triple; use crate::utils::parse_sets_and_triple;
use anyhow::{Context as _, Result};
use cranelift_codegen::Context; use cranelift_codegen::Context;
use cranelift_wasm::{DummyEnvironment, ReturnMode}; use cranelift_wasm::{DummyEnvironment, ReturnMode};
use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::iter::{IntoParallelIterator, ParallelIterator};
@@ -6,32 +7,32 @@ use std::{fs, io};
static WASM_MAGIC: &[u8] = &[0x00, 0x61, 0x73, 0x6D]; static WASM_MAGIC: &[u8] = &[0x00, 0x61, 0x73, 0x6D];
pub fn run(target: &str, input: &str, output: &str, flag_set: &[String]) -> Result<(), String> { pub fn run(target: &str, input: &str, output: &str, flag_set: &[String]) -> Result<()> {
let parsed = parse_sets_and_triple(flag_set, target)?; let parsed = parse_sets_and_triple(flag_set, target)?;
let fisa = parsed.as_fisa(); let fisa = parsed.as_fisa();
if fisa.isa.is_none() { if fisa.isa.is_none() {
return Err("`souper-harvest` requires a target isa".into()); anyhow::bail!("`souper-harvest` requires a target isa");
} }
let stdin = io::stdin(); let stdin = io::stdin();
let mut input: Box<dyn io::BufRead> = match input { let mut input: Box<dyn io::BufRead> = match input {
"-" => Box::new(stdin.lock()), "-" => Box::new(stdin.lock()),
_ => Box::new(io::BufReader::new( _ => Box::new(io::BufReader::new(
fs::File::open(input).map_err(|e| format!("failed to open input file: {}", e))?, fs::File::open(input).context("failed to open input file")?,
)), )),
}; };
let mut output: Box<dyn io::Write + Send> = match output { let mut output: Box<dyn io::Write + Send> = match output {
"-" => Box::new(io::stdout()), "-" => Box::new(io::stdout()),
_ => Box::new(io::BufWriter::new( _ => Box::new(io::BufWriter::new(
fs::File::create(output).map_err(|e| format!("failed to create output file: {}", e))?, fs::File::create(output).context("failed to create output file")?,
)), )),
}; };
let mut contents = vec![]; let mut contents = vec![];
input input
.read_to_end(&mut contents) .read_to_end(&mut contents)
.map_err(|e| format!("failed to read from input file: {}", e))?; .context("failed to read input file")?;
let funcs = if &contents[..WASM_MAGIC.len()] == WASM_MAGIC { let funcs = if &contents[..WASM_MAGIC.len()] == WASM_MAGIC {
let mut dummy_environ = DummyEnvironment::new( let mut dummy_environ = DummyEnvironment::new(
@@ -40,7 +41,7 @@ pub fn run(target: &str, input: &str, output: &str, flag_set: &[String]) -> Resu
false, false,
); );
cranelift_wasm::translate_module(&contents, &mut dummy_environ) cranelift_wasm::translate_module(&contents, &mut dummy_environ)
.map_err(|e| format!("failed to translate Wasm module to clif: {}", e))?; .context("failed to translate Wasm module to clif")?;
dummy_environ dummy_environ
.info .info
.function_bodies .function_bodies
@@ -48,19 +49,17 @@ pub fn run(target: &str, input: &str, output: &str, flag_set: &[String]) -> Resu
.map(|(_, f)| f.clone()) .map(|(_, f)| f.clone())
.collect() .collect()
} else { } else {
let contents = String::from_utf8(contents) let contents = String::from_utf8(contents)?;
.map_err(|e| format!("input is not a UTF-8 string: {}", e))?; cranelift_reader::parse_functions(&contents)?
cranelift_reader::parse_functions(&contents)
.map_err(|e| format!("failed to parse clif: {}", e))?
}; };
let (send, recv) = std::sync::mpsc::channel::<String>(); let (send, recv) = std::sync::mpsc::channel::<String>();
let writing_thread = std::thread::spawn(move || -> Result<(), String> { let writing_thread = std::thread::spawn(move || -> Result<()> {
for lhs in recv { for lhs in recv {
output output
.write_all(lhs.as_bytes()) .write_all(lhs.as_bytes())
.map_err(|e| format!("failed to write to output file: {}", e))?; .context("failed to write to output file")?;
} }
Ok(()) Ok(())
}); });
@@ -73,14 +72,14 @@ pub fn run(target: &str, input: &str, output: &str, flag_set: &[String]) -> Resu
ctx.compute_cfg(); ctx.compute_cfg();
ctx.preopt(fisa.isa.unwrap()) ctx.preopt(fisa.isa.unwrap())
.map_err(|e| format!("failed to run preopt: {}", e))?; .context("failed to run preopt")?;
ctx.souper_harvest(send) ctx.souper_harvest(send)
.map_err(|e| format!("failed to run souper harvester: {}", e))?; .context("failed to run souper harvester")?;
Ok(()) Ok(())
}) })
.collect::<Result<(), String>>()?; .collect::<Result<()>>()?;
match writing_thread.join() { match writing_thread.join() {
Ok(result) => result?, Ok(result) => result?,

View File

@@ -1,20 +1,19 @@
use anyhow::{Context, Result};
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::Path; use std::path::Path;
pub fn run(input: &Path, output: &Path) -> Result<(), String> { pub fn run(input: &Path, output: &Path) -> Result<()> {
let peepmatic_dsl = if input == Path::new("-") { let peepmatic_dsl = if input == Path::new("-") {
let stdin = std::io::stdin(); let stdin = std::io::stdin();
let mut stdin = stdin.lock(); let mut stdin = stdin.lock();
let mut souper_dsl = vec![]; let mut souper_dsl = vec![];
stdin stdin
.read_to_end(&mut souper_dsl) .read_to_end(&mut souper_dsl)
.map_err(|e| format!("failed to read from stdin: {}", e))?; .context("failed to read from stdin")?;
let souper_dsl = let souper_dsl = String::from_utf8(souper_dsl).context("stdin is not UTF-8: {}")?;
String::from_utf8(souper_dsl).map_err(|e| format!("stdin is not UTF-8: {}", e))?; peepmatic_souper::convert_str(&souper_dsl, Some(Path::new("stdin")))?
peepmatic_souper::convert_str(&souper_dsl, Some(Path::new("stdin")))
.map_err(|e| e.to_string())?
} else { } else {
peepmatic_souper::convert_file(input).map_err(|e| e.to_string())? peepmatic_souper::convert_file(input)?
}; };
if output == Path::new("-") { if output == Path::new("-") {
@@ -22,10 +21,10 @@ pub fn run(input: &Path, output: &Path) -> Result<(), String> {
let mut stdout = stdout.lock(); let mut stdout = stdout.lock();
stdout stdout
.write_all(peepmatic_dsl.as_bytes()) .write_all(peepmatic_dsl.as_bytes())
.map_err(|e| format!("error writing to stdout: {}", e))?; .context("error writing to stdout")?;
} else { } else {
std::fs::write(output, peepmatic_dsl.as_bytes()) std::fs::write(output, peepmatic_dsl.as_bytes())
.map_err(|e| format!("error writing to {}: {}", output.display(), e))?; .with_context(|| format!("error writing to {}", output.display()))?;
} }
Ok(()) Ok(())

View File

@@ -1,5 +1,6 @@
//! Utility functions. //! Utility functions.
use anyhow::Context;
use cranelift_codegen::isa; use cranelift_codegen::isa;
use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::settings::{self, FlagsOrIsa}; use cranelift_codegen::settings::{self, FlagsOrIsa};
@@ -12,15 +13,19 @@ use target_lexicon::Triple;
use walkdir::WalkDir; use walkdir::WalkDir;
/// Read an entire file into a string. /// Read an entire file into a string.
pub fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String> { pub fn read_to_string<P: AsRef<Path>>(path: P) -> anyhow::Result<String> {
let mut buffer = String::new(); let mut buffer = String::new();
if path.as_ref() == Path::new("-") { let path = path.as_ref();
if path == Path::new("-") {
let stdin = io::stdin(); let stdin = io::stdin();
let mut stdin = stdin.lock(); let mut stdin = stdin.lock();
stdin.read_to_string(&mut buffer)?; stdin
.read_to_string(&mut buffer)
.context("failed to read stdin to string")?;
} else { } else {
let mut file = File::open(path)?; let mut file = File::open(path)?;
file.read_to_string(&mut buffer)?; file.read_to_string(&mut buffer)
.with_context(|| format!("failed to read {} to string", path.display()))?;
} }
Ok(buffer) Ok(buffer)
} }
@@ -45,7 +50,7 @@ impl OwnedFlagsOrIsa {
pub fn parse_sets_and_triple( pub fn parse_sets_and_triple(
flag_set: &[String], flag_set: &[String],
flag_triple: &str, flag_triple: &str,
) -> Result<OwnedFlagsOrIsa, String> { ) -> anyhow::Result<OwnedFlagsOrIsa> {
let mut flag_builder = settings::builder(); let mut flag_builder = settings::builder();
// Collect unknown system-wide settings, so we can try to parse them as target specific // Collect unknown system-wide settings, so we can try to parse them as target specific
@@ -59,7 +64,7 @@ pub fn parse_sets_and_triple(
Err(ParseOptionError::UnknownFlag { name, .. }) => { Err(ParseOptionError::UnknownFlag { name, .. }) => {
unknown_settings.push(name); unknown_settings.push(name);
} }
Err(ParseOptionError::Generic(err)) => return Err(err.to_string()), Err(ParseOptionError::Generic(err)) => return Err(err.into()),
Ok(()) => {} Ok(()) => {}
} }
@@ -68,14 +73,14 @@ pub fn parse_sets_and_triple(
if let Some(triple_name) = words.next() { if let Some(triple_name) = words.next() {
let triple = match Triple::from_str(triple_name) { let triple = match Triple::from_str(triple_name) {
Ok(triple) => triple, Ok(triple) => triple,
Err(parse_error) => return Err(parse_error.to_string()), Err(parse_error) => return Err(parse_error.into()),
}; };
let mut isa_builder = isa::lookup(triple).map_err(|err| match err { let mut isa_builder = isa::lookup(triple).map_err(|err| match err {
isa::LookupError::SupportDisabled => { isa::LookupError::SupportDisabled => {
format!("support for triple '{}' is disabled", triple_name) anyhow::anyhow!("support for triple '{}' is disabled", triple_name)
} }
isa::LookupError::Unsupported => format!( isa::LookupError::Unsupported => anyhow::anyhow!(
"support for triple '{}' is not implemented yet", "support for triple '{}' is not implemented yet",
triple_name triple_name
), ),
@@ -87,21 +92,18 @@ pub fn parse_sets_and_triple(
&mut isa_builder, &mut isa_builder,
Location { line_number: 0 }, Location { line_number: 0 },
) )
.map_err(|err| ParseError::from(err).to_string())?; .map_err(ParseError::from)?;
// Apply the ISA-specific settings to `isa_builder`. // Apply the ISA-specific settings to `isa_builder`.
parse_options(words, &mut isa_builder, Location { line_number: 0 }) parse_options(words, &mut isa_builder, Location { line_number: 0 })
.map_err(|err| ParseError::from(err).to_string())?; .map_err(ParseError::from)?;
Ok(OwnedFlagsOrIsa::Isa( Ok(OwnedFlagsOrIsa::Isa(
isa_builder.finish(settings::Flags::new(flag_builder)), isa_builder.finish(settings::Flags::new(flag_builder)),
)) ))
} else { } else {
if !unknown_settings.is_empty() { if !unknown_settings.is_empty() {
return Err(format!( anyhow::bail!("unknown settings: '{}'", unknown_settings.join("', '"));
"unknown settings: '{}'",
unknown_settings.join("', '")
));
} }
Ok(OwnedFlagsOrIsa::Flags(settings::Flags::new(flag_builder))) Ok(OwnedFlagsOrIsa::Flags(settings::Flags::new(flag_builder)))
} }

View File

@@ -10,6 +10,7 @@
use crate::disasm::{print_all, PrintRelocs, PrintStackMaps, PrintTraps}; use crate::disasm::{print_all, PrintRelocs, PrintStackMaps, PrintTraps};
use crate::utils::parse_sets_and_triple; use crate::utils::parse_sets_and_triple;
use crate::UseTerminalColor; use crate::UseTerminalColor;
use anyhow::{Context as _, Result};
use cranelift_codegen::ir::DisplayFunctionAnnotations; use cranelift_codegen::ir::DisplayFunctionAnnotations;
use cranelift_codegen::print_errors::{pretty_error, pretty_verifier_error}; use cranelift_codegen::print_errors::{pretty_error, pretty_verifier_error};
use cranelift_codegen::settings::FlagsOrIsa; use cranelift_codegen::settings::FlagsOrIsa;
@@ -72,7 +73,7 @@ pub fn run(
flag_print_size: bool, flag_print_size: bool,
flag_report_times: bool, flag_report_times: bool,
flag_calc_value_ranges: bool, flag_calc_value_ranges: bool,
) -> Result<(), String> { ) -> Result<()> {
let parsed = parse_sets_and_triple(flag_set, flag_triple)?; let parsed = parse_sets_and_triple(flag_set, flag_triple)?;
for filename in files { for filename in files {
let path = Path::new(&filename); let path = Path::new(&filename);
@@ -108,7 +109,7 @@ fn handle_module(
path: &PathBuf, path: &PathBuf,
name: &str, name: &str,
fisa: FlagsOrIsa, fisa: FlagsOrIsa,
) -> Result<(), String> { ) -> Result<()> {
let mut terminal = term::stdout().unwrap(); let mut terminal = term::stdout().unwrap();
let use_color = terminal.supports_color() && use_terminal_color == UseTerminalColor::Auto let use_color = terminal.supports_color() && use_terminal_color == UseTerminalColor::Auto
|| use_terminal_color == UseTerminalColor::Always; || use_terminal_color == UseTerminalColor::Always;
@@ -134,27 +135,23 @@ fn handle_module(
stdin stdin
.lock() .lock()
.read_to_end(&mut buf) .read_to_end(&mut buf)
.map_err(|e| e.to_string())?; .context("failed to read stdin")?;
wat::parse_bytes(&buf) wat::parse_bytes(&buf)?.into()
.map_err(|err| format!("{:?}", err))?
.into()
} else { } else {
wat::parse_file(path).map_err(|err| format!("{:?}", err))? wat::parse_file(path)?
}; };
let isa = match fisa.isa { let isa = match fisa.isa {
Some(isa) => isa, Some(isa) => isa,
None => { None => {
return Err(String::from( anyhow::bail!("Error: the wasm command requires an explicit isa.");
"Error: the wasm command requires an explicit isa.",
));
} }
}; };
let debug_info = flag_calc_value_ranges; let debug_info = flag_calc_value_ranges;
let mut dummy_environ = let mut dummy_environ =
DummyEnvironment::new(isa.frontend_config(), ReturnMode::NormalReturns, debug_info); DummyEnvironment::new(isa.frontend_config(), ReturnMode::NormalReturns, debug_info);
translate_module(&module_binary, &mut dummy_environ).map_err(|e| e.to_string())?; translate_module(&module_binary, &mut dummy_environ)?;
vcprintln!(flag_verbose, use_color, terminal, term::color::GREEN, "ok"); vcprintln!(flag_verbose, use_color, terminal, term::color::GREEN, "ok");
@@ -222,12 +219,15 @@ fn handle_module(
let mut stack_maps = PrintStackMaps::new(flag_print); let mut stack_maps = PrintStackMaps::new(flag_print);
if flag_check_translation { if flag_check_translation {
if let Err(errors) = context.verify(fisa) { if let Err(errors) = context.verify(fisa) {
return Err(pretty_verifier_error(&context.func, fisa.isa, None, errors)); anyhow::bail!(
"{}",
pretty_verifier_error(&context.func, fisa.isa, None, errors)
);
} }
} else { } else {
let code_info = context let code_info = context
.compile_and_emit(isa, &mut mem, &mut relocs, &mut traps, &mut stack_maps) .compile_and_emit(isa, &mut mem, &mut relocs, &mut traps, &mut stack_maps)
.map_err(|err| pretty_error(&context.func, fisa.isa, err))?; .map_err(|err| anyhow::anyhow!("{}", pretty_error(&context.func, fisa.isa, err)))?;
if flag_print_size { if flag_print_size {
println!( println!(