From a864b75f0aef9a4d7ab3b9ae85681a54f7521142 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Fri, 26 Jul 2019 11:08:53 -0700 Subject: [PATCH 1/3] Add ability to run CLIF IR using `clif-util run [-v] {file}*` --- Cargo.toml | 3 + src/clif-util.rs | 16 ++++ src/run.rs | 188 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 src/run.rs diff --git a/Cargo.toml b/Cargo.toml index eb25cb2a6..ef14bd4b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,9 @@ target-lexicon = "0.4.0" pretty_env_logger = "0.3.0" file-per-thread-logger = "0.1.2" indicatif = "0.11.0" +region = "2.1.2" +mmap = "0.1.1" +walkdir = "2.2" [features] default = ["disas", "wasm", "cranelift-codegen/all-arch"] diff --git a/src/clif-util.rs b/src/clif-util.rs index a18288905..9b7573e73 100755 --- a/src/clif-util.rs +++ b/src/clif-util.rs @@ -33,6 +33,7 @@ mod cat; mod compile; mod disasm; mod print_cfg; +mod run; mod utils; /// A command either succeeds or fails with an error message. @@ -162,6 +163,13 @@ fn main() { .arg(add_input_file_arg()) .arg(add_debug_flag()), ) + .subcommand( + SubCommand::with_name("run") + .about("Execute either WASM or CLIF code and verify with test expressions") + .arg(add_verbose_flag()) + .arg(add_input_file_arg()) + .arg(add_debug_flag()), + ) .subcommand( SubCommand::with_name("cat") .about("Outputs .clif file") @@ -224,6 +232,14 @@ fn main() { ) .map(|_time| ()) } + ("run", Some(rest_cmd)) => { + handle_debug_flag(rest_cmd.is_present("debug")); + run::run( + get_vec(rest_cmd.values_of("file")), + rest_cmd.is_present("verbose"), + ) + .map(|_time| ()) + } ("pass", Some(rest_cmd)) => { handle_debug_flag(rest_cmd.is_present("debug")); diff --git a/src/run.rs b/src/run.rs new file mode 100644 index 000000000..65d22010c --- /dev/null +++ b/src/run.rs @@ -0,0 +1,188 @@ +//! CLI tool to compile Cranelift IR files to native code in memory and execute them. + +use crate::utils::read_to_string; +use core::mem; +use cranelift_codegen::binemit::{Reloc, Stackmap}; +use cranelift_codegen::ir::{ExternalName, Function, JumpTable}; +use cranelift_codegen::isa::TargetIsa; +use cranelift_codegen::Context; +use cranelift_codegen::{binemit, ir}; +use cranelift_native::builder as host_isa_builder; +use cranelift_reader::{parse_test, Details, IsaSpec}; +use mmap::{MapOption, MemoryMap}; +use region; +use region::Protection; +use std::path::PathBuf; +use walkdir::{DirEntry, WalkDir}; + +pub fn run(files: Vec, flag_print: bool) -> Result<(), String> { + let mut total = 0; + let mut errors = 0; + for file in iterate_files(files) { + total += 1; + match run_single_file(&file) { + Ok(_) => { + if flag_print { + println!("{}", file.to_string_lossy()); + } + } + Err(e) => { + if flag_print { + println!("{}: {}", file.to_string_lossy(), e); + } + errors += 1; + } + } + } + + if flag_print { + match total { + 0 => println!("0 files"), + 1 => println!("1 file"), + n => println!("{} files", n), + } + } + + match errors { + 0 => Ok(()), + 1 => Err(String::from("1 failure")), + n => Err(format!("{} failures", n)), + } +} + +/// Iterate over all of the files passed as arguments, recursively iterating through directories +fn iterate_files(files: Vec) -> impl Iterator { + files + .into_iter() + .flat_map(WalkDir::new) + .filter(|f| match f { + Ok(d) => !is_hidden(d) && !is_directory(d), + Err(e) => { + println!("Unable to read file: {}", e); + false + } + }) + .map(|f| { + f.expect("This should not happen: we have already filtered out the errors") + .into_path() + }) +} +fn is_hidden(d: &DirEntry) -> bool { + d.file_name().to_str().map_or(false, |s| s.starts_with(".")) +} +fn is_directory(d: &DirEntry) -> bool { + d.file_type().is_dir() +} + +/// Run all functions in a file that are succeeded by "run:" comments +fn run_single_file(path: &PathBuf) -> Result<(), String> { + let file_contents = read_to_string(&path).map_err(|e| e.to_string())?; + run_file_contents(file_contents) +} + +/// Main body of `run_single_file` separated for testing +fn run_file_contents(file_contents: String) -> Result<(), String> { + let test_file = parse_test(&file_contents, None, None).map_err(|e| e.to_string())?; + let isa = create_target_isa(test_file.isa_spec)?; + for (func, Details { comments, .. }) in test_file.functions { + if comments.iter().filter(|c| c.text.contains("run")).count() > 0 { + run_single_function(func, isa.as_ref())? + } + } + Ok(()) +} + +/// Build an ISA based on the current machine running this code (the host) +fn create_target_isa(isa_spec: IsaSpec) -> Result, String> { + if let IsaSpec::None(flags) = isa_spec { + // build an ISA for the current machine + let builder = host_isa_builder()?; + Ok(builder.finish(flags)) + } 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"))? + } +} + +/// Compile and execute a single function, expecting a boolean to be returned; a 'true' value is +/// interpreted as a successful test execution and mapped to Ok whereas a 'false' value is +/// interpreted as a failed test and mapped to Err. +fn run_single_function(func: Function, isa: &dyn TargetIsa) -> Result<(), String> { + if !(func.signature.params.is_empty() + && func.signature.returns.len() == 1 + && func.signature.returns.first().unwrap().value_type.is_bool()) + { + return Err(String::from( + "Functions must have a signature like: () -> boolean", + )); + } + // TODO observed segfaults with return types of b64 + + // set up the context + let mut context = Context::new(); + context.func = func; + + // compile and encode the result to machine code + let relocs = &mut NullRelocSink {}; + let traps = &mut NullTrapSink {}; + let stackmaps = &mut NullStackmapSink {}; + let code_info = context.compile(isa).map_err(|e| e.to_string())?; + let code_page = MemoryMap::new(code_info.total_size as usize, &[MapOption::MapWritable]) + .map_err(|e| e.to_string())?; + let callable_fn: fn() -> bool = unsafe { + context.emit_to_memory(isa, code_page.data(), relocs, traps, stackmaps); + region::protect(code_page.data(), code_page.len(), Protection::ReadExecute) + .map_err(|e| e.to_string())?; + mem::transmute(code_page.data()) + }; + + // execute + match callable_fn() { + true => Ok(()), + false => Err(format!("Failed: {}", context.func.name.to_string())), + } +} + +/// Relocation helper +struct NullRelocSink {} + +impl binemit::RelocSink for NullRelocSink { + fn reloc_ebb(&mut self, _: u32, _: Reloc, _: u32) {} + fn reloc_external(&mut self, _: u32, _: Reloc, _: &ExternalName, _: i64) {} + fn reloc_jt(&mut self, _: u32, _: Reloc, _: JumpTable) {} +} + +/// Trap helper +struct NullTrapSink {} + +impl binemit::TrapSink for NullTrapSink { + fn trap(&mut self, _: binemit::CodeOffset, _: ir::SourceLoc, _: ir::TrapCode) {} +} + +/// Stack helper +struct NullStackmapSink {} + +impl binemit::StackmapSink for NullStackmapSink { + fn add_stackmap(&mut self, _: u32, _: Stackmap) {} +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn nop() { + let code = String::from( + " + function %test() -> b8 system_v { + ebb0: + nop + v1 = bconst.b8 true + return v1 + } + + ; run + ", + ); + run_file_contents(code).unwrap() + } +} From 992fb31b099a7c1ba4f1781b4e6420930a9978e9 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Fri, 2 Aug 2019 11:23:06 -0700 Subject: [PATCH 2/3] Add `test run` to cranelift-filetests to allow executing CLIF This re-factors the compile/execute parts to a FunctionRunner that is shared between cranelift-filetests and clif-util. CLIF can be now be run using `clif-util run` as well as during `clif-util test` for files with a `test run` header. As before, only functions suffixed with a `run` comment are executed. The `run: fn(...) == ...` expression syntax is left for a subsequent change. --- Cargo.toml | 2 - cranelift-codegen/src/binemit/memorysink.rs | 10 ++ cranelift-codegen/src/binemit/mod.rs | 3 +- cranelift-filetests/Cargo.toml | 5 +- cranelift-filetests/src/function_runner.rs | 118 ++++++++++++++++++++ cranelift-filetests/src/lib.rs | 4 + cranelift-filetests/src/test_run.rs | 46 ++++++++ filetests/isa/x86/run-const.clif | 11 ++ src/run.rs | 96 +++------------- 9 files changed, 208 insertions(+), 87 deletions(-) create mode 100644 cranelift-filetests/src/function_runner.rs create mode 100644 cranelift-filetests/src/test_run.rs create mode 100644 filetests/isa/x86/run-const.clif diff --git a/Cargo.toml b/Cargo.toml index ef14bd4b9..a8f85c30b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,8 +42,6 @@ target-lexicon = "0.4.0" pretty_env_logger = "0.3.0" file-per-thread-logger = "0.1.2" indicatif = "0.11.0" -region = "2.1.2" -mmap = "0.1.1" walkdir = "2.2" [features] diff --git a/cranelift-codegen/src/binemit/memorysink.rs b/cranelift-codegen/src/binemit/memorysink.rs index b543d3e89..49c519f6b 100644 --- a/cranelift-codegen/src/binemit/memorysink.rs +++ b/cranelift-codegen/src/binemit/memorysink.rs @@ -162,6 +162,16 @@ impl<'a> CodeSink for MemoryCodeSink<'a> { } } +/// A `RelocSink` implementation that does nothing, which is convenient when +/// compiling code that does not relocate anything. +pub struct NullRelocSink {} + +impl RelocSink for NullRelocSink { + fn reloc_ebb(&mut self, _: u32, _: Reloc, _: u32) {} + fn reloc_external(&mut self, _: u32, _: Reloc, _: &ExternalName, _: i64) {} + fn reloc_jt(&mut self, _: u32, _: Reloc, _: JumpTable) {} +} + /// A `TrapSink` implementation that does nothing, which is convenient when /// compiling code that does not rely on trapping semantics. pub struct NullTrapSink {} diff --git a/cranelift-codegen/src/binemit/mod.rs b/cranelift-codegen/src/binemit/mod.rs index d6b72553b..996f2e449 100644 --- a/cranelift-codegen/src/binemit/mod.rs +++ b/cranelift-codegen/src/binemit/mod.rs @@ -9,7 +9,8 @@ mod shrink; mod stackmap; pub use self::memorysink::{ - MemoryCodeSink, NullStackmapSink, NullTrapSink, RelocSink, StackmapSink, TrapSink, + MemoryCodeSink, NullRelocSink, NullStackmapSink, NullTrapSink, RelocSink, StackmapSink, + TrapSink, }; pub use self::relaxation::relax_branches; pub use self::shrink::shrink_instructions; diff --git a/cranelift-filetests/Cargo.toml b/cranelift-filetests/Cargo.toml index fd8b309bb..f5d3220ed 100644 --- a/cranelift-filetests/Cargo.toml +++ b/cranelift-filetests/Cargo.toml @@ -11,9 +11,12 @@ edition = "2018" [dependencies] cranelift-codegen = { path = "../cranelift-codegen", version = "0.40.0", features = ["testing_hooks"] } +cranelift-native = { path = "../cranelift-native", version = "0.40.0" } cranelift-reader = { path = "../cranelift-reader", version = "0.40.0" } cranelift-preopt = { path = "../cranelift-preopt", version = "0.40.0" } file-per-thread-logger = "0.1.2" filecheck = "0.4.0" -num_cpus = "1.8.0" log = "0.4.6" +mmap = "0.1.1" +num_cpus = "1.8.0" +region = "2.1.2" diff --git a/cranelift-filetests/src/function_runner.rs b/cranelift-filetests/src/function_runner.rs new file mode 100644 index 000000000..dee732038 --- /dev/null +++ b/cranelift-filetests/src/function_runner.rs @@ -0,0 +1,118 @@ +use core::mem; +use cranelift_codegen::binemit::{NullRelocSink, NullStackmapSink, NullTrapSink}; +use cranelift_codegen::ir::Function; +use cranelift_codegen::isa::{CallConv, TargetIsa}; +use cranelift_codegen::{settings, Context}; +use cranelift_native::builder as host_isa_builder; +use mmap::{MapOption, MemoryMap}; +use region; +use region::Protection; + +/// Run a function on a host +pub struct FunctionRunner { + function: Function, + isa: Box, +} + +impl FunctionRunner { + /// Build a function runner from a function and the ISA to run on (must be the host machine's ISA) + pub fn new(function: Function, isa: Box) -> Self { + FunctionRunner { function, isa } + } + + /// Build a function runner using the host machine's ISA and the passed flags + pub fn with_host_isa(function: Function, flags: settings::Flags) -> Self { + let builder = host_isa_builder().expect("Unable to build a TargetIsa for the current host"); + let isa = builder.finish(flags); + FunctionRunner::new(function, isa) + } + + /// Build a function runner using the host machine's ISA and the default flags for this ISA + pub fn with_default_host_isa(function: Function) -> Self { + let flags = settings::Flags::new(settings::builder()); + FunctionRunner::with_host_isa(function, flags) + } + + /// Compile and execute a single function, expecting a boolean to be returned; a 'true' value is + /// interpreted as a successful test execution and mapped to Ok whereas a 'false' value is + /// interpreted as a failed test and mapped to Err. + pub fn run(&self) -> Result<(), String> { + let func = self.function.clone(); + if !(func.signature.params.is_empty() + && func.signature.returns.len() == 1 + && func.signature.returns.first().unwrap().value_type.is_bool()) + { + return Err(String::from( + "Functions must have a signature like: () -> boolean", + )); + } + + if func.signature.call_conv != self.isa.default_call_conv() + && func.signature.call_conv != CallConv::Fast + { + // ideally we wouldn't have to also check for Fast here but currently there is no way to inform the filetest parser that we would like to use a default other than Fast + return Err(String::from( + "Functions only run on the host's default calling convention; remove the specified calling convention in the function signature to use the host's default.", + )); + } + + // set up the context + let mut context = Context::new(); + context.func = func; + + // compile and encode the result to machine code + let relocs = &mut NullRelocSink {}; + let traps = &mut NullTrapSink {}; + let stackmaps = &mut NullStackmapSink {}; + let code_info = context + .compile(self.isa.as_ref()) + .map_err(|e| e.to_string())?; + let code_page = MemoryMap::new(code_info.total_size as usize, &[MapOption::MapWritable]) + .map_err(|e| e.to_string())?; + let callable_fn: fn() -> bool = unsafe { + context.emit_to_memory( + self.isa.as_ref(), + code_page.data(), + relocs, + traps, + stackmaps, + ); + region::protect(code_page.data(), code_page.len(), Protection::ReadExecute) + .map_err(|e| e.to_string())?; + mem::transmute(code_page.data()) + }; + + // execute + match callable_fn() { + true => Ok(()), + false => Err(format!("Failed: {}", context.func.name.to_string())), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use cranelift_reader::parse_test; + + #[test] + fn nop() { + let code = String::from( + "function %test() -> b8 system_v { + ebb0: + nop + v1 = bconst.b8 true + return v1 + }", + ); + + // extract function + let test_file = parse_test(code.as_str(), None, None).unwrap(); + assert_eq!(1, test_file.functions.len()); + let function = test_file.functions[0].0.clone(); + + // execute function + let runner = FunctionRunner::with_default_host_isa(function); + runner.run().unwrap() // will panic if execution fails + } +} diff --git a/cranelift-filetests/src/lib.rs b/cranelift-filetests/src/lib.rs index 6099ce372..08f02afbf 100644 --- a/cranelift-filetests/src/lib.rs +++ b/cranelift-filetests/src/lib.rs @@ -23,6 +23,7 @@ ) )] +pub use crate::function_runner::FunctionRunner; use crate::runner::TestRunner; use cranelift_codegen::timing; use cranelift_reader::TestCommand; @@ -30,6 +31,7 @@ use std::path::Path; use std::time; mod concurrent; +mod function_runner; mod match_directive; mod runner; mod runone; @@ -46,6 +48,7 @@ mod test_postopt; mod test_preopt; mod test_print_cfg; mod test_regalloc; +mod test_run; mod test_safepoint; mod test_shrink; mod test_simple_gvn; @@ -124,6 +127,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::SubtestResult test_simple_preopt::subtest(parsed), "print-cfg" => test_print_cfg::subtest(parsed), "regalloc" => test_regalloc::subtest(parsed), + "run" => test_run::subtest(parsed), "shrink" => test_shrink::subtest(parsed), "simple-gvn" => test_simple_gvn::subtest(parsed), "verifier" => test_verifier::subtest(parsed), diff --git a/cranelift-filetests/src/test_run.rs b/cranelift-filetests/src/test_run.rs new file mode 100644 index 000000000..6e34bfebf --- /dev/null +++ b/cranelift-filetests/src/test_run.rs @@ -0,0 +1,46 @@ +//! Test command for running CLIF files and verifying their results +//! +//! The `run` test command compiles each function on the host machine and executes it + +use crate::function_runner::FunctionRunner; +use crate::subtest::{Context, SubTest, SubtestResult}; +use cranelift_codegen; +use cranelift_codegen::ir; +use cranelift_reader::TestCommand; +use std::borrow::Cow; + +struct TestRun; + +pub fn subtest(parsed: &TestCommand) -> SubtestResult> { + assert_eq!(parsed.command, "run"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestRun)) + } +} + +impl SubTest for TestRun { + fn name(&self) -> &'static str { + "run" + } + + fn is_mutating(&self) -> bool { + false + } + + fn needs_isa(&self) -> bool { + false + } + + fn run(&self, func: Cow, context: &Context) -> SubtestResult<()> { + for comment in context.details.comments.iter() { + if comment.text.contains("run") { + let runner = + FunctionRunner::with_host_isa(func.clone().into_owned(), context.flags.clone()); + runner.run()? + } + } + Ok(()) + } +} diff --git a/filetests/isa/x86/run-const.clif b/filetests/isa/x86/run-const.clif new file mode 100644 index 000000000..1ac5062e4 --- /dev/null +++ b/filetests/isa/x86/run-const.clif @@ -0,0 +1,11 @@ +test run + +function %test_compare_i32() -> b1 { +ebb0: + v0 = iconst.i32 42 + v1 = iconst.i32 42 + v2 = icmp eq v0, v1 + return v2 +} + +; run diff --git a/src/run.rs b/src/run.rs index 65d22010c..a6c460083 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,19 +1,12 @@ //! CLI tool to compile Cranelift IR files to native code in memory and execute them. use crate::utils::read_to_string; -use core::mem; -use cranelift_codegen::binemit::{Reloc, Stackmap}; -use cranelift_codegen::ir::{ExternalName, Function, JumpTable}; use cranelift_codegen::isa::TargetIsa; -use cranelift_codegen::Context; -use cranelift_codegen::{binemit, ir}; +use cranelift_filetests::FunctionRunner; use cranelift_native::builder as host_isa_builder; use cranelift_reader::{parse_test, Details, IsaSpec}; -use mmap::{MapOption, MemoryMap}; -use region; -use region::Protection; use std::path::PathBuf; -use walkdir::{DirEntry, WalkDir}; +use walkdir::WalkDir; pub fn run(files: Vec, flag_print: bool) -> Result<(), String> { let mut total = 0; @@ -56,7 +49,12 @@ fn iterate_files(files: Vec) -> impl Iterator { .into_iter() .flat_map(WalkDir::new) .filter(|f| match f { - Ok(d) => !is_hidden(d) && !is_directory(d), + Ok(d) => { + // filter out hidden files (starting with .) + !d.file_name().to_str().map_or(false, |s| s.starts_with(".")) + // filter out directories + && !d.file_type().is_dir() + } Err(e) => { println!("Unable to read file: {}", e); false @@ -67,12 +65,6 @@ fn iterate_files(files: Vec) -> impl Iterator { .into_path() }) } -fn is_hidden(d: &DirEntry) -> bool { - d.file_name().to_str().map_or(false, |s| s.starts_with(".")) -} -fn is_directory(d: &DirEntry) -> bool { - d.file_type().is_dir() -} /// Run all functions in a file that are succeeded by "run:" comments fn run_single_file(path: &PathBuf) -> Result<(), String> { @@ -83,88 +75,26 @@ fn run_single_file(path: &PathBuf) -> Result<(), String> { /// Main body of `run_single_file` separated for testing fn run_file_contents(file_contents: String) -> Result<(), String> { let test_file = parse_test(&file_contents, None, None).map_err(|e| e.to_string())?; - let isa = create_target_isa(test_file.isa_spec)?; for (func, Details { comments, .. }) in test_file.functions { - if comments.iter().filter(|c| c.text.contains("run")).count() > 0 { - run_single_function(func, isa.as_ref())? + if comments.iter().any(|c| c.text.contains("run")) { + let isa = create_target_isa(&test_file.isa_spec)?; + FunctionRunner::new(func, isa).run()? } } Ok(()) } /// Build an ISA based on the current machine running this code (the host) -fn create_target_isa(isa_spec: IsaSpec) -> Result, String> { +fn create_target_isa(isa_spec: &IsaSpec) -> Result, String> { if let IsaSpec::None(flags) = isa_spec { // build an ISA for the current machine let builder = host_isa_builder()?; - Ok(builder.finish(flags)) + Ok(builder.finish(flags.clone())) } 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"))? } } -/// Compile and execute a single function, expecting a boolean to be returned; a 'true' value is -/// interpreted as a successful test execution and mapped to Ok whereas a 'false' value is -/// interpreted as a failed test and mapped to Err. -fn run_single_function(func: Function, isa: &dyn TargetIsa) -> Result<(), String> { - if !(func.signature.params.is_empty() - && func.signature.returns.len() == 1 - && func.signature.returns.first().unwrap().value_type.is_bool()) - { - return Err(String::from( - "Functions must have a signature like: () -> boolean", - )); - } - // TODO observed segfaults with return types of b64 - - // set up the context - let mut context = Context::new(); - context.func = func; - - // compile and encode the result to machine code - let relocs = &mut NullRelocSink {}; - let traps = &mut NullTrapSink {}; - let stackmaps = &mut NullStackmapSink {}; - let code_info = context.compile(isa).map_err(|e| e.to_string())?; - let code_page = MemoryMap::new(code_info.total_size as usize, &[MapOption::MapWritable]) - .map_err(|e| e.to_string())?; - let callable_fn: fn() -> bool = unsafe { - context.emit_to_memory(isa, code_page.data(), relocs, traps, stackmaps); - region::protect(code_page.data(), code_page.len(), Protection::ReadExecute) - .map_err(|e| e.to_string())?; - mem::transmute(code_page.data()) - }; - - // execute - match callable_fn() { - true => Ok(()), - false => Err(format!("Failed: {}", context.func.name.to_string())), - } -} - -/// Relocation helper -struct NullRelocSink {} - -impl binemit::RelocSink for NullRelocSink { - fn reloc_ebb(&mut self, _: u32, _: Reloc, _: u32) {} - fn reloc_external(&mut self, _: u32, _: Reloc, _: &ExternalName, _: i64) {} - fn reloc_jt(&mut self, _: u32, _: Reloc, _: JumpTable) {} -} - -/// Trap helper -struct NullTrapSink {} - -impl binemit::TrapSink for NullTrapSink { - fn trap(&mut self, _: binemit::CodeOffset, _: ir::SourceLoc, _: ir::TrapCode) {} -} - -/// Stack helper -struct NullStackmapSink {} - -impl binemit::StackmapSink for NullStackmapSink { - fn add_stackmap(&mut self, _: u32, _: Stackmap) {} -} - #[cfg(test)] mod test { use super::*; From ca2f645ac1b0b1536bca656e7471aa4915ecae3b Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 21 Aug 2019 09:00:12 -0700 Subject: [PATCH 3/3] fixup! Add ability to run CLIF IR using `clif-util run [-v] {file}*` --- src/clif-util.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clif-util.rs b/src/clif-util.rs index 9b7573e73..51bac2a53 100755 --- a/src/clif-util.rs +++ b/src/clif-util.rs @@ -165,7 +165,7 @@ fn main() { ) .subcommand( SubCommand::with_name("run") - .about("Execute either WASM or CLIF code and verify with test expressions") + .about("Execute CLIF code and verify with test expressions") .arg(add_verbose_flag()) .arg(add_input_file_arg()) .arg(add_debug_flag()),