Check in the wasmstandalone code.
This is based on the code in https://github.com/denismerigoux/cretonne/commits/wasm2cretonne before wasmstandalone was removed, with minor updates for the new library structure. It is not yet updated for the latest cretonne API changes.
This commit is contained in:
68
src/cat.rs
68
src/cat.rs
@@ -1,68 +0,0 @@
|
||||
//! The `cat` sub-command.
|
||||
//!
|
||||
//! Read a sequence of Cretonne IL files and print them again to stdout. This has the effect of
|
||||
//! normalizing formatting and removing comments.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use cretonne::ir::Function;
|
||||
use cton_reader::{parse_functions, TestCommand};
|
||||
use CommandResult;
|
||||
use utils::read_to_string;
|
||||
use filetest::subtest::{self, SubTest, Context, Result as STResult};
|
||||
|
||||
pub fn run(files: Vec<String>) -> CommandResult {
|
||||
for (i, f) in files.into_iter().enumerate() {
|
||||
if i != 0 {
|
||||
println!("");
|
||||
}
|
||||
cat_one(f)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cat_one(filename: String) -> CommandResult {
|
||||
let buffer = read_to_string(&filename)
|
||||
.map_err(|e| format!("{}: {}", filename, e))?;
|
||||
let items = parse_functions(&buffer)
|
||||
.map_err(|e| format!("{}: {}", filename, e))?;
|
||||
|
||||
for (idx, func) in items.into_iter().enumerate() {
|
||||
if idx != 0 {
|
||||
println!("");
|
||||
}
|
||||
print!("{}", func);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Object implementing the `test cat` sub-test.
|
||||
///
|
||||
/// This command is used for testing the parser and function printer. It simply parses a function
|
||||
/// and prints it out again.
|
||||
///
|
||||
/// The result is verified by filecheck.
|
||||
struct TestCat;
|
||||
|
||||
pub fn subtest(parsed: &TestCommand) -> STResult<Box<SubTest>> {
|
||||
assert_eq!(parsed.command, "cat");
|
||||
if !parsed.options.is_empty() {
|
||||
Err(format!("No options allowed on {}", parsed))
|
||||
} else {
|
||||
Ok(Box::new(TestCat))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubTest for TestCat {
|
||||
fn name(&self) -> Cow<str> {
|
||||
Cow::from("cat")
|
||||
}
|
||||
|
||||
fn needs_verifier(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn run(&self, func: Cow<Function>, context: &Context) -> STResult<()> {
|
||||
subtest::run_filecheck(&func.display(context.isa).to_string(), context)
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
#[macro_use(dbg)]
|
||||
extern crate cretonne;
|
||||
extern crate cton_reader;
|
||||
extern crate docopt;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate filecheck;
|
||||
extern crate num_cpus;
|
||||
|
||||
use cretonne::VERSION;
|
||||
use docopt::Docopt;
|
||||
use std::io::{self, Write};
|
||||
use std::process;
|
||||
|
||||
mod utils;
|
||||
mod filetest;
|
||||
mod cat;
|
||||
mod print_cfg;
|
||||
mod rsfilecheck;
|
||||
|
||||
const USAGE: &str = "
|
||||
Cretonne code generator utility
|
||||
|
||||
Usage:
|
||||
cton-util test [-v] <file>...
|
||||
cton-util cat <file>...
|
||||
cton-util filecheck [-v] <file>
|
||||
cton-util print-cfg <file>...
|
||||
cton-util --help | --version
|
||||
|
||||
Options:
|
||||
-v, --verbose be more verbose
|
||||
-h, --help print this help message
|
||||
--version print the Cretonne version
|
||||
|
||||
";
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Args {
|
||||
cmd_test: bool,
|
||||
cmd_cat: bool,
|
||||
cmd_filecheck: bool,
|
||||
cmd_print_cfg: bool,
|
||||
arg_file: Vec<String>,
|
||||
flag_verbose: bool,
|
||||
}
|
||||
|
||||
/// A command either succeeds or fails with an error message.
|
||||
pub type CommandResult = Result<(), String>;
|
||||
|
||||
/// Parse the command line arguments and run the requested command.
|
||||
fn cton_util() -> CommandResult {
|
||||
// Parse command line arguments.
|
||||
let args: Args = Docopt::new(USAGE)
|
||||
.and_then(|d| {
|
||||
d.help(true)
|
||||
.version(Some(format!("Cretonne {}", VERSION)))
|
||||
.deserialize()
|
||||
})
|
||||
.unwrap_or_else(|e| e.exit());
|
||||
|
||||
// Find the sub-command to execute.
|
||||
if args.cmd_test {
|
||||
filetest::run(args.flag_verbose, args.arg_file)
|
||||
} else if args.cmd_cat {
|
||||
cat::run(args.arg_file)
|
||||
} else if args.cmd_filecheck {
|
||||
rsfilecheck::run(args.arg_file, args.flag_verbose)
|
||||
} else if args.cmd_print_cfg {
|
||||
print_cfg::run(args.arg_file)
|
||||
} else {
|
||||
// Debugging / shouldn't happen with proper command line handling above.
|
||||
Err(format!("Unhandled args: {:?}", args))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(mut msg) = cton_util() {
|
||||
if !msg.ends_with('\n') {
|
||||
msg.push('\n');
|
||||
}
|
||||
io::stderr().write(msg.as_bytes()).unwrap();
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
//! Test command for testing the binary machine code emission.
|
||||
//!
|
||||
//! The `binemit` test command generates binary machine code for every instruction in the input
|
||||
//! functions and compares the results to the expected output.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use cretonne::binemit;
|
||||
use cretonne::ir;
|
||||
use cretonne::ir::entities::AnyEntity;
|
||||
use cretonne::isa::TargetIsa;
|
||||
use cretonne::regalloc::RegDiversions;
|
||||
use cton_reader::TestCommand;
|
||||
use filetest::subtest::{SubTest, Context, Result};
|
||||
use utils::{match_directive, pretty_error};
|
||||
|
||||
struct TestBinEmit;
|
||||
|
||||
pub fn subtest(parsed: &TestCommand) -> Result<Box<SubTest>> {
|
||||
assert_eq!(parsed.command, "binemit");
|
||||
if !parsed.options.is_empty() {
|
||||
Err(format!("No options allowed on {}", parsed))
|
||||
} else {
|
||||
Ok(Box::new(TestBinEmit))
|
||||
}
|
||||
}
|
||||
|
||||
// Code sink that generates text.
|
||||
struct TextSink {
|
||||
rnames: &'static [&'static str],
|
||||
offset: binemit::CodeOffset,
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl TextSink {
|
||||
/// Create a new empty TextSink.
|
||||
pub fn new(isa: &TargetIsa) -> TextSink {
|
||||
TextSink {
|
||||
rnames: isa.reloc_names(),
|
||||
offset: 0,
|
||||
text: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl binemit::CodeSink for TextSink {
|
||||
fn offset(&self) -> binemit::CodeOffset {
|
||||
self.offset
|
||||
}
|
||||
|
||||
fn put1(&mut self, x: u8) {
|
||||
write!(self.text, "{:02x} ", x).unwrap();
|
||||
self.offset += 1;
|
||||
}
|
||||
|
||||
fn put2(&mut self, x: u16) {
|
||||
write!(self.text, "{:04x} ", x).unwrap();
|
||||
self.offset += 2;
|
||||
}
|
||||
|
||||
fn put4(&mut self, x: u32) {
|
||||
write!(self.text, "{:08x} ", x).unwrap();
|
||||
self.offset += 4;
|
||||
}
|
||||
|
||||
fn put8(&mut self, x: u64) {
|
||||
write!(self.text, "{:016x} ", x).unwrap();
|
||||
self.offset += 8;
|
||||
}
|
||||
|
||||
fn reloc_ebb(&mut self, reloc: binemit::Reloc, ebb: ir::Ebb) {
|
||||
write!(self.text, "{}({}) ", self.rnames[reloc.0 as usize], ebb).unwrap();
|
||||
}
|
||||
|
||||
fn reloc_func(&mut self, reloc: binemit::Reloc, fref: ir::FuncRef) {
|
||||
write!(self.text, "{}({}) ", self.rnames[reloc.0 as usize], fref).unwrap();
|
||||
}
|
||||
|
||||
fn reloc_jt(&mut self, reloc: binemit::Reloc, jt: ir::JumpTable) {
|
||||
write!(self.text, "{}({}) ", self.rnames[reloc.0 as usize], jt).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl SubTest for TestBinEmit {
|
||||
fn name(&self) -> Cow<str> {
|
||||
Cow::from("binemit")
|
||||
}
|
||||
|
||||
fn is_mutating(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn needs_isa(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(&self, func: Cow<ir::Function>, context: &Context) -> Result<()> {
|
||||
let isa = context.isa.expect("binemit needs an ISA");
|
||||
let encinfo = isa.encoding_info();
|
||||
// TODO: Run a verifier pass over the code first to detect any bad encodings or missing/bad
|
||||
// value locations. The current error reporting is just crashing...
|
||||
let mut func = func.into_owned();
|
||||
|
||||
// Give an encoding to any instruction that doesn't already have one.
|
||||
for ebb in func.layout.ebbs() {
|
||||
for inst in func.layout.ebb_insts(ebb) {
|
||||
if !func.encodings.get_or_default(inst).is_legal() {
|
||||
if let Ok(enc) = isa.encode(&func.dfg,
|
||||
&func.dfg[inst],
|
||||
func.dfg.ctrl_typevar(inst)) {
|
||||
*func.encodings.ensure(inst) = enc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Relax branches and compute EBB offsets based on the encodings.
|
||||
let code_size = binemit::relax_branches(&mut func, isa)
|
||||
.map_err(|e| pretty_error(&func, context.isa, e))?;
|
||||
|
||||
// Collect all of the 'bin:' directives on instructions.
|
||||
let mut bins = HashMap::new();
|
||||
for comment in &context.details.comments {
|
||||
if let Some(want) = match_directive(comment.text, "bin:") {
|
||||
match comment.entity {
|
||||
AnyEntity::Inst(inst) => {
|
||||
if let Some(prev) = bins.insert(inst, want) {
|
||||
return Err(format!("multiple 'bin:' directives on {}: '{}' and '{}'",
|
||||
func.dfg.display_inst(inst, isa),
|
||||
prev,
|
||||
want));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(format!("'bin:' directive on non-inst {}: {}",
|
||||
comment.entity,
|
||||
comment.text))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if bins.is_empty() {
|
||||
return Err("No 'bin:' directives found".to_string());
|
||||
}
|
||||
|
||||
// Now emit all instructions.
|
||||
let mut sink = TextSink::new(isa);
|
||||
let mut divert = RegDiversions::new();
|
||||
for ebb in func.layout.ebbs() {
|
||||
divert.clear();
|
||||
// Correct header offsets should have been computed by `relax_branches()`.
|
||||
assert_eq!(sink.offset,
|
||||
func.offsets[ebb],
|
||||
"Inconsistent {} header offset",
|
||||
ebb);
|
||||
for inst in func.layout.ebb_insts(ebb) {
|
||||
sink.text.clear();
|
||||
let enc = func.encodings.get_or_default(inst);
|
||||
|
||||
// Send legal encodings into the emitter.
|
||||
if enc.is_legal() {
|
||||
let before = sink.offset;
|
||||
isa.emit_inst(&func, inst, &mut divert, &mut sink);
|
||||
let emitted = sink.offset - before;
|
||||
// Verify the encoding recipe sizes against the ISAs emit_inst implementation.
|
||||
assert_eq!(emitted,
|
||||
encinfo.bytes(enc),
|
||||
"Inconsistent size for [{}] {}",
|
||||
encinfo.display(enc),
|
||||
func.dfg.display_inst(inst, isa));
|
||||
}
|
||||
|
||||
// Check against bin: directives.
|
||||
if let Some(want) = bins.remove(&inst) {
|
||||
if !enc.is_legal() {
|
||||
return Err(format!("{} can't be encoded: {}",
|
||||
inst,
|
||||
func.dfg.display_inst(inst, isa)));
|
||||
}
|
||||
let have = sink.text.trim();
|
||||
if have != want {
|
||||
return Err(format!("Bad machine code for {}: {}\nWant: {}\nGot: {}",
|
||||
inst,
|
||||
func.dfg.display_inst(inst, isa),
|
||||
want,
|
||||
have));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sink.offset != code_size {
|
||||
return Err(format!("Expected code size {}, got {}", code_size, sink.offset));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
//! Test command for testing the code generator pipeline
|
||||
//!
|
||||
//! The `compile` test command runs each function through the full code generator pipeline
|
||||
|
||||
use cretonne::binemit;
|
||||
use cretonne::ir;
|
||||
use cretonne;
|
||||
use cton_reader::TestCommand;
|
||||
use filetest::subtest::{SubTest, Context, Result};
|
||||
use std::borrow::Cow;
|
||||
use utils::pretty_error;
|
||||
|
||||
struct TestCompile;
|
||||
|
||||
pub fn subtest(parsed: &TestCommand) -> Result<Box<SubTest>> {
|
||||
assert_eq!(parsed.command, "compile");
|
||||
if !parsed.options.is_empty() {
|
||||
Err(format!("No options allowed on {}", parsed))
|
||||
} else {
|
||||
Ok(Box::new(TestCompile))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubTest for TestCompile {
|
||||
fn name(&self) -> Cow<str> {
|
||||
Cow::from("compile")
|
||||
}
|
||||
|
||||
fn is_mutating(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn needs_isa(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(&self, func: Cow<ir::Function>, context: &Context) -> Result<()> {
|
||||
let isa = context.isa.expect("compile needs an ISA");
|
||||
|
||||
// Create a compilation context, and drop in the function.
|
||||
let mut comp_ctx = cretonne::Context::new();
|
||||
comp_ctx.func = func.into_owned();
|
||||
|
||||
let code_size = comp_ctx
|
||||
.compile(isa)
|
||||
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?;
|
||||
|
||||
dbg!("Generated {} bytes of code:\n{}",
|
||||
code_size,
|
||||
comp_ctx.func.display(isa));
|
||||
|
||||
// Finally verify that the returned code size matches the emitted bytes.
|
||||
let mut sink = SizeSink { offset: 0 };
|
||||
binemit::emit_function(&comp_ctx.func,
|
||||
|func, inst, div, sink| isa.emit_inst(func, inst, div, sink),
|
||||
&mut sink);
|
||||
|
||||
if sink.offset != code_size {
|
||||
return Err(format!("Expected code size {}, got {}", code_size, sink.offset));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Code sink that simply counts bytes.
|
||||
struct SizeSink {
|
||||
offset: binemit::CodeOffset,
|
||||
}
|
||||
|
||||
impl binemit::CodeSink for SizeSink {
|
||||
fn offset(&self) -> binemit::CodeOffset {
|
||||
self.offset
|
||||
}
|
||||
|
||||
fn put1(&mut self, _: u8) {
|
||||
self.offset += 1;
|
||||
}
|
||||
|
||||
fn put2(&mut self, _: u16) {
|
||||
self.offset += 2;
|
||||
}
|
||||
|
||||
fn put4(&mut self, _: u32) {
|
||||
self.offset += 4;
|
||||
}
|
||||
|
||||
fn put8(&mut self, _: u64) {
|
||||
self.offset += 8;
|
||||
}
|
||||
|
||||
fn reloc_ebb(&mut self, _reloc: binemit::Reloc, _ebb: ir::Ebb) {}
|
||||
fn reloc_func(&mut self, _reloc: binemit::Reloc, _fref: ir::FuncRef) {}
|
||||
fn reloc_jt(&mut self, _reloc: binemit::Reloc, _jt: ir::JumpTable) {}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
//! Run tests concurrently.
|
||||
//!
|
||||
//! This module provides the `ConcurrentRunner` struct which uses a pool of threads to run tests
|
||||
//! concurrently.
|
||||
|
||||
use std::panic::catch_unwind;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc::{channel, Sender, Receiver};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use num_cpus;
|
||||
use filetest::{TestResult, runone};
|
||||
|
||||
// Request sent to worker threads contains jobid and path.
|
||||
struct Request(usize, PathBuf);
|
||||
|
||||
/// Reply from worker thread,
|
||||
pub enum Reply {
|
||||
Starting { jobid: usize, thread_num: usize },
|
||||
Done { jobid: usize, result: TestResult },
|
||||
Tick,
|
||||
}
|
||||
|
||||
/// Manage threads that run test jobs concurrently.
|
||||
pub struct ConcurrentRunner {
|
||||
// Channel for sending requests to the worker threads.
|
||||
// The workers are sharing the receiver with an `Arc<Mutex<Receiver>>`.
|
||||
// This is `None` when shutting down.
|
||||
request_tx: Option<Sender<Request>>,
|
||||
|
||||
// Channel for receiving replies from the workers.
|
||||
// Workers have their own `Sender`.
|
||||
reply_rx: Receiver<Reply>,
|
||||
|
||||
handles: Vec<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl ConcurrentRunner {
|
||||
/// Create a new `ConcurrentRunner` with threads spun up.
|
||||
pub fn new() -> ConcurrentRunner {
|
||||
let (request_tx, request_rx) = channel();
|
||||
let request_mutex = Arc::new(Mutex::new(request_rx));
|
||||
let (reply_tx, reply_rx) = channel();
|
||||
|
||||
heartbeat_thread(reply_tx.clone());
|
||||
|
||||
let handles = (0..num_cpus::get())
|
||||
.map(|num| worker_thread(num, request_mutex.clone(), reply_tx.clone()))
|
||||
.collect();
|
||||
|
||||
ConcurrentRunner {
|
||||
request_tx: Some(request_tx),
|
||||
reply_rx,
|
||||
handles,
|
||||
}
|
||||
}
|
||||
|
||||
/// Shut down worker threads orderly. They will finish any queued jobs first.
|
||||
pub fn shutdown(&mut self) {
|
||||
self.request_tx = None;
|
||||
}
|
||||
|
||||
/// Join all the worker threads.
|
||||
pub fn join(&mut self) {
|
||||
assert!(self.request_tx.is_none(), "must shutdown before join");
|
||||
for h in self.handles.drain(..) {
|
||||
if let Err(e) = h.join() {
|
||||
println!("worker panicked: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new job to the queues.
|
||||
pub fn put(&mut self, jobid: usize, path: &Path) {
|
||||
self.request_tx
|
||||
.as_ref()
|
||||
.expect("cannot push after shutdown")
|
||||
.send(Request(jobid, path.to_owned()))
|
||||
.expect("all the worker threads are gone");
|
||||
}
|
||||
|
||||
/// Get a job reply without blocking.
|
||||
pub fn try_get(&mut self) -> Option<Reply> {
|
||||
self.reply_rx.try_recv().ok()
|
||||
}
|
||||
|
||||
/// Get a job reply, blocking until one is available.
|
||||
pub fn get(&mut self) -> Option<Reply> {
|
||||
self.reply_rx.recv().ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a heartbeat thread which sends ticks down the reply channel every second.
|
||||
/// This lets us implement timeouts without the not yet stable `recv_timeout`.
|
||||
fn heartbeat_thread(replies: Sender<Reply>) -> thread::JoinHandle<()> {
|
||||
thread::Builder::new()
|
||||
.name("heartbeat".to_string())
|
||||
.spawn(move || while replies.send(Reply::Tick).is_ok() {
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Spawn a worker thread running tests.
|
||||
fn worker_thread(thread_num: usize,
|
||||
requests: Arc<Mutex<Receiver<Request>>>,
|
||||
replies: Sender<Reply>)
|
||||
-> thread::JoinHandle<()> {
|
||||
thread::Builder::new()
|
||||
.name(format!("worker #{}", thread_num))
|
||||
.spawn(move || {
|
||||
loop {
|
||||
// Lock the mutex only long enough to extract a request.
|
||||
let Request(jobid, path) = match requests.lock().unwrap().recv() {
|
||||
Err(..) => break, // TX end shuit down. exit thread.
|
||||
Ok(req) => req,
|
||||
};
|
||||
|
||||
// Tell them we're starting this job.
|
||||
// The receiver should always be present for this as long as we have jobs.
|
||||
replies.send(Reply::Starting { jobid, thread_num }).unwrap();
|
||||
|
||||
let result = catch_unwind(|| runone::run(path.as_path())).unwrap_or_else(|e| {
|
||||
// The test panicked, leaving us a `Box<Any>`.
|
||||
// Panics are usually strings.
|
||||
if let Some(msg) = e.downcast_ref::<String>() {
|
||||
Err(format!("panicked in worker #{}: {}", thread_num, msg))
|
||||
} else if let Some(msg) = e.downcast_ref::<&'static str>() {
|
||||
Err(format!("panicked in worker #{}: {}", thread_num, msg))
|
||||
} else {
|
||||
Err(format!("panicked in worker #{}", thread_num))
|
||||
}
|
||||
});
|
||||
|
||||
if let &Err(ref msg) = &result {
|
||||
dbg!("FAIL: {}", msg);
|
||||
}
|
||||
|
||||
replies.send(Reply::Done { jobid, result }).unwrap();
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
|
||||
//! Test command for verifying dominator trees.
|
||||
//!
|
||||
//! The `test domtree` test command looks for annotations on instructions like this:
|
||||
//!
|
||||
//! jump ebb3 ; dominates: ebb3
|
||||
//!
|
||||
//! This annotation means that the jump instruction is expected to be the immediate dominator of
|
||||
//! `ebb3`.
|
||||
//!
|
||||
//! We verify that the dominator tree annotations are complete and correct.
|
||||
//!
|
||||
|
||||
use cretonne::dominator_tree::DominatorTree;
|
||||
use cretonne::flowgraph::ControlFlowGraph;
|
||||
use cretonne::ir::Function;
|
||||
use cretonne::ir::entities::AnyEntity;
|
||||
use cton_reader::TestCommand;
|
||||
use filetest::subtest::{SubTest, Context, Result};
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::collections::HashMap;
|
||||
use utils::match_directive;
|
||||
|
||||
struct TestDomtree;
|
||||
|
||||
pub fn subtest(parsed: &TestCommand) -> Result<Box<SubTest>> {
|
||||
assert_eq!(parsed.command, "domtree");
|
||||
if !parsed.options.is_empty() {
|
||||
Err(format!("No options allowed on {}", parsed))
|
||||
} else {
|
||||
Ok(Box::new(TestDomtree))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubTest for TestDomtree {
|
||||
fn name(&self) -> Cow<str> {
|
||||
Cow::from("domtree")
|
||||
}
|
||||
|
||||
// Extract our own dominator tree from
|
||||
fn run(&self, func: Cow<Function>, context: &Context) -> Result<()> {
|
||||
let func = func.borrow();
|
||||
let cfg = ControlFlowGraph::with_function(func);
|
||||
let domtree = DominatorTree::with_function(func, &cfg);
|
||||
|
||||
// Build an expected domtree from the source annotations.
|
||||
let mut expected = HashMap::new();
|
||||
for comment in &context.details.comments {
|
||||
if let Some(tail) = match_directive(comment.text, "dominates:") {
|
||||
let inst = match comment.entity {
|
||||
AnyEntity::Inst(inst) => inst,
|
||||
_ => {
|
||||
return Err(format!("annotation on non-inst {}: {}",
|
||||
comment.entity,
|
||||
comment.text))
|
||||
}
|
||||
};
|
||||
for src_ebb in tail.split_whitespace() {
|
||||
let ebb = match context.details.map.lookup_str(src_ebb) {
|
||||
Some(AnyEntity::Ebb(ebb)) => ebb,
|
||||
_ => return Err(format!("expected EBB: {}", src_ebb)),
|
||||
};
|
||||
|
||||
// Annotations say that `inst` is the idom of `ebb`.
|
||||
if expected.insert(ebb, inst).is_some() {
|
||||
return Err(format!("multiple dominators for {}", src_ebb));
|
||||
}
|
||||
|
||||
// Compare to computed domtree.
|
||||
match domtree.idom(ebb) {
|
||||
Some(got_inst) if got_inst != inst => {
|
||||
return Err(format!("mismatching idoms for {}:\n\
|
||||
want: {}, got: {}",
|
||||
src_ebb,
|
||||
inst,
|
||||
got_inst));
|
||||
}
|
||||
None => {
|
||||
return Err(format!("mismatching idoms for {}:\n\
|
||||
want: {}, got: unreachable",
|
||||
src_ebb,
|
||||
inst));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we know that everything in `expected` is consistent with `domtree`.
|
||||
// All other EBB's should be either unreachable or the entry block.
|
||||
for ebb in func.layout
|
||||
.ebbs()
|
||||
.skip(1)
|
||||
.filter(|ebb| !expected.contains_key(&ebb)) {
|
||||
if let Some(got_inst) = domtree.idom(ebb) {
|
||||
return Err(format!("mismatching idoms for renumbered {}:\n\
|
||||
want: unrechable, got: {}",
|
||||
ebb,
|
||||
got_inst));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
//! Test command for checking the IL legalizer.
|
||||
//!
|
||||
//! The `test legalizer` test command runs each function through `legalize_function()` and sends
|
||||
//! the result to filecheck.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use cretonne;
|
||||
use cretonne::ir::Function;
|
||||
use cton_reader::TestCommand;
|
||||
use filetest::subtest::{SubTest, Context, Result, run_filecheck};
|
||||
use std::fmt::Write;
|
||||
use utils::pretty_error;
|
||||
|
||||
struct TestLegalizer;
|
||||
|
||||
pub fn subtest(parsed: &TestCommand) -> Result<Box<SubTest>> {
|
||||
assert_eq!(parsed.command, "legalizer");
|
||||
if !parsed.options.is_empty() {
|
||||
Err(format!("No options allowed on {}", parsed))
|
||||
} else {
|
||||
Ok(Box::new(TestLegalizer))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubTest for TestLegalizer {
|
||||
fn name(&self) -> Cow<str> {
|
||||
Cow::from("legalizer")
|
||||
}
|
||||
|
||||
fn is_mutating(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn needs_isa(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(&self, func: Cow<Function>, context: &Context) -> Result<()> {
|
||||
let mut comp_ctx = cretonne::Context::new();
|
||||
comp_ctx.func = func.into_owned();
|
||||
let isa = context.isa.expect("legalizer needs an ISA");
|
||||
|
||||
comp_ctx.flowgraph();
|
||||
comp_ctx
|
||||
.legalize(isa)
|
||||
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?;
|
||||
|
||||
let mut text = String::new();
|
||||
write!(&mut text, "{}", &comp_ctx.func.display(Some(isa)))
|
||||
.map_err(|e| e.to_string())?;
|
||||
run_filecheck(&text, context)
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
//! Test command for testing the LICM pass.
|
||||
//!
|
||||
//! The `licm` test command runs each function through the LICM pass after ensuring
|
||||
//! that all instructions are legal for the target.
|
||||
//!
|
||||
//! The resulting function is sent to `filecheck`.
|
||||
|
||||
use cretonne::ir::Function;
|
||||
use cretonne;
|
||||
use cton_reader::TestCommand;
|
||||
use filetest::subtest::{SubTest, Context, Result, run_filecheck};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
use utils::pretty_error;
|
||||
|
||||
struct TestLICM;
|
||||
|
||||
pub fn subtest(parsed: &TestCommand) -> Result<Box<SubTest>> {
|
||||
assert_eq!(parsed.command, "licm");
|
||||
if !parsed.options.is_empty() {
|
||||
Err(format!("No options allowed on {}", parsed))
|
||||
} else {
|
||||
Ok(Box::new(TestLICM))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubTest for TestLICM {
|
||||
fn name(&self) -> Cow<str> {
|
||||
Cow::from("licm")
|
||||
}
|
||||
|
||||
fn is_mutating(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(&self, func: Cow<Function>, context: &Context) -> Result<()> {
|
||||
// Create a compilation context, and drop in the function.
|
||||
let mut comp_ctx = cretonne::Context::new();
|
||||
comp_ctx.func = func.into_owned();
|
||||
|
||||
comp_ctx.flowgraph();
|
||||
comp_ctx
|
||||
.licm()
|
||||
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?;
|
||||
|
||||
let mut text = String::new();
|
||||
write!(&mut text, "{}", &comp_ctx.func)
|
||||
.map_err(|e| e.to_string())?;
|
||||
run_filecheck(&text, context)
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
//! File tests.
|
||||
//!
|
||||
//! This module contains the main driver for `cton-util test` as well as implementations of the
|
||||
//! available test commands.
|
||||
|
||||
use std::path::Path;
|
||||
use std::time;
|
||||
use cton_reader::TestCommand;
|
||||
use CommandResult;
|
||||
use cat;
|
||||
use print_cfg;
|
||||
use filetest::runner::TestRunner;
|
||||
|
||||
pub mod subtest;
|
||||
|
||||
mod binemit;
|
||||
mod compile;
|
||||
mod concurrent;
|
||||
mod domtree;
|
||||
mod legalizer;
|
||||
mod licm;
|
||||
mod regalloc;
|
||||
mod runner;
|
||||
mod runone;
|
||||
mod simple_gvn;
|
||||
mod verifier;
|
||||
|
||||
/// The result of running the test in a file.
|
||||
pub type TestResult = Result<time::Duration, String>;
|
||||
|
||||
/// Main entry point for `cton-util test`.
|
||||
///
|
||||
/// Take a list of filenames which can be either `.cton` files or directories.
|
||||
///
|
||||
/// Files are interpreted as test cases and executed immediately.
|
||||
///
|
||||
/// Directories are scanned recursively for test cases ending in `.cton`. These test cases are
|
||||
/// executed on background threads.
|
||||
///
|
||||
pub fn run(verbose: bool, files: Vec<String>) -> CommandResult {
|
||||
let mut runner = TestRunner::new(verbose);
|
||||
|
||||
for path in files.iter().map(Path::new) {
|
||||
if path.is_file() {
|
||||
runner.push_test(path);
|
||||
} else {
|
||||
runner.push_dir(path);
|
||||
}
|
||||
}
|
||||
|
||||
runner.start_threads();
|
||||
runner.run()
|
||||
}
|
||||
|
||||
/// Create a new subcommand trait object to match `parsed.command`.
|
||||
///
|
||||
/// This function knows how to create all of the possible `test <foo>` commands that can appear in
|
||||
/// a `.cton` test file.
|
||||
fn new_subtest(parsed: &TestCommand) -> subtest::Result<Box<subtest::SubTest>> {
|
||||
match parsed.command {
|
||||
"binemit" => binemit::subtest(parsed),
|
||||
"cat" => cat::subtest(parsed),
|
||||
"compile" => compile::subtest(parsed),
|
||||
"domtree" => domtree::subtest(parsed),
|
||||
"legalizer" => legalizer::subtest(parsed),
|
||||
"licm" => licm::subtest(parsed),
|
||||
"print-cfg" => print_cfg::subtest(parsed),
|
||||
"regalloc" => regalloc::subtest(parsed),
|
||||
"simple-gvn" => simple_gvn::subtest(parsed),
|
||||
"verifier" => verifier::subtest(parsed),
|
||||
_ => Err(format!("unknown test command '{}'", parsed.command)),
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
//! Test command for testing the register allocator.
|
||||
//!
|
||||
//! The `regalloc` test command runs each function through the register allocator after ensuring
|
||||
//! that all instructions are legal for the target.
|
||||
//!
|
||||
//! The resulting function is sent to `filecheck`.
|
||||
|
||||
use cretonne::ir::Function;
|
||||
use cretonne;
|
||||
use cton_reader::TestCommand;
|
||||
use filetest::subtest::{SubTest, Context, Result, run_filecheck};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
use utils::pretty_error;
|
||||
|
||||
struct TestRegalloc;
|
||||
|
||||
pub fn subtest(parsed: &TestCommand) -> Result<Box<SubTest>> {
|
||||
assert_eq!(parsed.command, "regalloc");
|
||||
if !parsed.options.is_empty() {
|
||||
Err(format!("No options allowed on {}", parsed))
|
||||
} else {
|
||||
Ok(Box::new(TestRegalloc))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubTest for TestRegalloc {
|
||||
fn name(&self) -> Cow<str> {
|
||||
Cow::from("regalloc")
|
||||
}
|
||||
|
||||
fn is_mutating(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn needs_isa(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(&self, func: Cow<Function>, context: &Context) -> Result<()> {
|
||||
let isa = context.isa.expect("register allocator needs an ISA");
|
||||
|
||||
// Create a compilation context, and drop in the function.
|
||||
let mut comp_ctx = cretonne::Context::new();
|
||||
comp_ctx.func = func.into_owned();
|
||||
|
||||
comp_ctx.flowgraph();
|
||||
// TODO: Should we have an option to skip legalization?
|
||||
comp_ctx
|
||||
.legalize(isa)
|
||||
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?;
|
||||
comp_ctx
|
||||
.regalloc(isa)
|
||||
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?;
|
||||
|
||||
let mut text = String::new();
|
||||
write!(&mut text, "{}", &comp_ctx.func.display(Some(isa)))
|
||||
.map_err(|e| e.to_string())?;
|
||||
run_filecheck(&text, context)
|
||||
}
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
//! Test runner.
|
||||
//!
|
||||
//! This module implements the `TestRunner` struct which manages executing tests as well as
|
||||
//! scanning directories for tests.
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::{self, Display};
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use filetest::{TestResult, runone};
|
||||
use filetest::concurrent::{ConcurrentRunner, Reply};
|
||||
use CommandResult;
|
||||
|
||||
// Timeout in seconds when we're not making progress.
|
||||
const TIMEOUT_PANIC: usize = 10;
|
||||
|
||||
// Timeout for reporting slow tests without panicking.
|
||||
const TIMEOUT_SLOW: usize = 3;
|
||||
|
||||
struct QueueEntry {
|
||||
path: PathBuf,
|
||||
state: State,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
enum State {
|
||||
New,
|
||||
Queued,
|
||||
Running,
|
||||
Done(TestResult),
|
||||
}
|
||||
|
||||
impl QueueEntry {
|
||||
pub fn path(&self) -> &Path {
|
||||
self.path.as_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for QueueEntry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let p = self.path.to_string_lossy();
|
||||
match self.state {
|
||||
State::Done(Ok(dur)) => {
|
||||
write!(f,
|
||||
"{}.{:03} {}",
|
||||
dur.as_secs(),
|
||||
dur.subsec_nanos() / 1000000,
|
||||
p)
|
||||
}
|
||||
State::Done(Err(ref e)) => write!(f, "FAIL {}: {}", p, e),
|
||||
_ => write!(f, "{}", p),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestRunner {
|
||||
verbose: bool,
|
||||
|
||||
// Directories that have not yet been scanned.
|
||||
dir_stack: Vec<PathBuf>,
|
||||
|
||||
// Filenames of tests to run.
|
||||
tests: Vec<QueueEntry>,
|
||||
|
||||
// Pointer into `tests` where the `New` entries begin.
|
||||
new_tests: usize,
|
||||
|
||||
// Number of contiguous reported tests at the front of `tests`.
|
||||
reported_tests: usize,
|
||||
|
||||
// Number of errors seen so far.
|
||||
errors: usize,
|
||||
|
||||
// Number of ticks received since we saw any progress.
|
||||
ticks_since_progress: usize,
|
||||
|
||||
threads: Option<ConcurrentRunner>,
|
||||
}
|
||||
|
||||
impl TestRunner {
|
||||
/// Create a new blank TrstRunner.
|
||||
pub fn new(verbose: bool) -> TestRunner {
|
||||
TestRunner {
|
||||
verbose,
|
||||
dir_stack: Vec::new(),
|
||||
tests: Vec::new(),
|
||||
new_tests: 0,
|
||||
reported_tests: 0,
|
||||
errors: 0,
|
||||
ticks_since_progress: 0,
|
||||
threads: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a directory path to be scanned later.
|
||||
///
|
||||
/// If `dir` turns out to be a regular file, it is silently ignored.
|
||||
/// Otherwise, any problems reading the directory are reported.
|
||||
pub fn push_dir<P: Into<PathBuf>>(&mut self, dir: P) {
|
||||
self.dir_stack.push(dir.into());
|
||||
}
|
||||
|
||||
/// Add a test to be executed later.
|
||||
///
|
||||
/// Any problems reading `file` as a test case file will be reported as a test failure.
|
||||
pub fn push_test<P: Into<PathBuf>>(&mut self, file: P) {
|
||||
self.tests
|
||||
.push(QueueEntry {
|
||||
path: file.into(),
|
||||
state: State::New,
|
||||
});
|
||||
}
|
||||
|
||||
/// Begin running tests concurrently.
|
||||
pub fn start_threads(&mut self) {
|
||||
assert!(self.threads.is_none());
|
||||
self.threads = Some(ConcurrentRunner::new());
|
||||
}
|
||||
|
||||
/// Scan any directories pushed so far.
|
||||
/// Push any potential test cases found.
|
||||
pub fn scan_dirs(&mut self) {
|
||||
// This recursive search tries to minimize statting in a directory hierarchy containing
|
||||
// mostly test cases.
|
||||
//
|
||||
// - Directory entries with a "cton" extension are presumed to be test case files.
|
||||
// - Directory entries with no extension are presumed to be subdirectories.
|
||||
// - Anything else is ignored.
|
||||
//
|
||||
while let Some(dir) = self.dir_stack.pop() {
|
||||
match dir.read_dir() {
|
||||
Err(err) => {
|
||||
// Fail silently if `dir` was actually a regular file.
|
||||
// This lets us skip spurious extensionless files without statting everything
|
||||
// needlessly.
|
||||
if !dir.is_file() {
|
||||
self.path_error(dir, err);
|
||||
}
|
||||
}
|
||||
Ok(entries) => {
|
||||
// Read all directory entries. Avoid statting.
|
||||
for entry_result in entries {
|
||||
match entry_result {
|
||||
Err(err) => {
|
||||
// Not sure why this would happen. `read_dir` succeeds, but there's
|
||||
// a problem with an entry. I/O error during a getdirentries
|
||||
// syscall seems to be the reason. The implementation in
|
||||
// libstd/sys/unix/fs.rs seems to suggest that breaking now would
|
||||
// be a good idea, or the iterator could keep returning the same
|
||||
// error forever.
|
||||
self.path_error(dir, err);
|
||||
break;
|
||||
}
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
// Recognize directories and tests by extension.
|
||||
// Yes, this means we ignore directories with '.' in their name.
|
||||
match path.extension().and_then(OsStr::to_str) {
|
||||
Some("cton") => self.push_test(path),
|
||||
Some(_) => {}
|
||||
None => self.push_dir(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get the new jobs running before moving on to the next directory.
|
||||
self.schedule_jobs();
|
||||
}
|
||||
}
|
||||
|
||||
/// Report an error related to a path.
|
||||
fn path_error<E: Error>(&mut self, path: PathBuf, err: E) {
|
||||
self.errors += 1;
|
||||
println!("{}: {}", path.to_string_lossy(), err);
|
||||
}
|
||||
|
||||
/// Report on the next in-order job, if it's done.
|
||||
fn report_job(&self) -> bool {
|
||||
let jobid = self.reported_tests;
|
||||
if let Some(&QueueEntry { state: State::Done(ref result), .. }) = self.tests.get(jobid) {
|
||||
if self.verbose || result.is_err() {
|
||||
println!("{}", self.tests[jobid]);
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedule any new jobs to run.
|
||||
fn schedule_jobs(&mut self) {
|
||||
for jobid in self.new_tests..self.tests.len() {
|
||||
assert_eq!(self.tests[jobid].state, State::New);
|
||||
if let Some(ref mut conc) = self.threads {
|
||||
// Queue test for concurrent execution.
|
||||
self.tests[jobid].state = State::Queued;
|
||||
conc.put(jobid, self.tests[jobid].path());
|
||||
} else {
|
||||
// Run test synchronously.
|
||||
self.tests[jobid].state = State::Running;
|
||||
let result = runone::run(self.tests[jobid].path());
|
||||
self.finish_job(jobid, result);
|
||||
}
|
||||
self.new_tests = jobid + 1;
|
||||
}
|
||||
|
||||
// Check for any asynchronous replies without blocking.
|
||||
while let Some(reply) = self.threads.as_mut().and_then(ConcurrentRunner::try_get) {
|
||||
self.handle_reply(reply);
|
||||
}
|
||||
}
|
||||
|
||||
/// Report the end of a job.
|
||||
fn finish_job(&mut self, jobid: usize, result: TestResult) {
|
||||
assert_eq!(self.tests[jobid].state, State::Running);
|
||||
if result.is_err() {
|
||||
self.errors += 1;
|
||||
}
|
||||
self.tests[jobid].state = State::Done(result);
|
||||
|
||||
// Rports jobs in order.
|
||||
while self.report_job() {
|
||||
self.reported_tests += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a reply from the async threads.
|
||||
fn handle_reply(&mut self, reply: Reply) {
|
||||
match reply {
|
||||
Reply::Starting { jobid, .. } => {
|
||||
assert_eq!(self.tests[jobid].state, State::Queued);
|
||||
self.tests[jobid].state = State::Running;
|
||||
}
|
||||
Reply::Done { jobid, result } => {
|
||||
self.ticks_since_progress = 0;
|
||||
self.finish_job(jobid, result)
|
||||
}
|
||||
Reply::Tick => {
|
||||
self.ticks_since_progress += 1;
|
||||
if self.ticks_since_progress == TIMEOUT_SLOW {
|
||||
println!("STALLED for {} seconds with {}/{} tests finished",
|
||||
self.ticks_since_progress,
|
||||
self.reported_tests,
|
||||
self.tests.len());
|
||||
for jobid in self.reported_tests..self.tests.len() {
|
||||
if self.tests[jobid].state == State::Running {
|
||||
println!("slow: {}", self.tests[jobid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.ticks_since_progress >= TIMEOUT_PANIC {
|
||||
panic!("worker threads stalled for {} seconds.",
|
||||
self.ticks_since_progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Drain the async jobs and shut down the threads.
|
||||
fn drain_threads(&mut self) {
|
||||
if let Some(mut conc) = self.threads.take() {
|
||||
conc.shutdown();
|
||||
while self.reported_tests < self.tests.len() {
|
||||
match conc.get() {
|
||||
Some(reply) => self.handle_reply(reply),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
conc.join();
|
||||
}
|
||||
}
|
||||
|
||||
/// Print out a report of slow tests.
|
||||
fn report_slow_tests(&self) {
|
||||
// Collect runtimes of succeeded tests.
|
||||
let mut times = self.tests
|
||||
.iter()
|
||||
.filter_map(|entry| match *entry {
|
||||
QueueEntry { state: State::Done(Ok(dur)), .. } => Some(dur),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Get me some real data, kid.
|
||||
let len = times.len();
|
||||
if len < 4 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute quartiles.
|
||||
times.sort();
|
||||
let qlen = len / 4;
|
||||
let q1 = times[qlen];
|
||||
let q3 = times[len - 1 - qlen];
|
||||
// Inter-quartile range.
|
||||
let iqr = q3 - q1;
|
||||
|
||||
// Cut-off for what we consider a 'slow' test: 1.5 IQR from the 75% quartile.
|
||||
// These are the data points that would be plotted as outliers outside a box plot.
|
||||
let cut = q3 + iqr * 2 / 3;
|
||||
if cut > *times.last().unwrap() {
|
||||
return;
|
||||
}
|
||||
|
||||
for t in self.tests
|
||||
.iter()
|
||||
.filter(|entry| match **entry {
|
||||
QueueEntry { state: State::Done(Ok(dur)), .. } => dur > cut,
|
||||
_ => false,
|
||||
}) {
|
||||
println!("slow: {}", t)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Scan pushed directories for tests and run them.
|
||||
pub fn run(&mut self) -> CommandResult {
|
||||
self.scan_dirs();
|
||||
self.schedule_jobs();
|
||||
self.drain_threads();
|
||||
self.report_slow_tests();
|
||||
println!("{} tests", self.tests.len());
|
||||
match self.errors {
|
||||
0 => Ok(()),
|
||||
1 => Err("1 failure".to_string()),
|
||||
n => Err(format!("{} failures", n)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
//! Run the tests in a single test file.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
use std::time;
|
||||
use cretonne::ir::Function;
|
||||
use cretonne::isa::TargetIsa;
|
||||
use cretonne::settings::Flags;
|
||||
use cretonne::verify_function;
|
||||
use cton_reader::parse_test;
|
||||
use cton_reader::IsaSpec;
|
||||
use utils::{read_to_string, pretty_verifier_error};
|
||||
use filetest::{TestResult, new_subtest};
|
||||
use filetest::subtest::{SubTest, Context, Result};
|
||||
|
||||
/// Load `path` and run the test in it.
|
||||
///
|
||||
/// If running this test causes a panic, it will propagate as normal.
|
||||
pub fn run(path: &Path) -> TestResult {
|
||||
dbg!("---\nFile: {}", path.to_string_lossy());
|
||||
let started = time::Instant::now();
|
||||
let buffer = read_to_string(path).map_err(|e| e.to_string())?;
|
||||
let testfile = parse_test(&buffer).map_err(|e| e.to_string())?;
|
||||
if testfile.functions.is_empty() {
|
||||
return Err("no functions found".to_string());
|
||||
}
|
||||
|
||||
// Parse the test commands.
|
||||
let mut tests = testfile
|
||||
.commands
|
||||
.iter()
|
||||
.map(new_subtest)
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
// 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.
|
||||
let flags = match testfile.isa_spec {
|
||||
IsaSpec::None(ref f) => f,
|
||||
IsaSpec::Some(ref v) => v.last().expect("Empty ISA list").flags(),
|
||||
};
|
||||
|
||||
// Sort the tests so the mutators are at the end, and those that don't need the verifier are at
|
||||
// the front.
|
||||
tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier()));
|
||||
|
||||
// Expand the tests into (test, flags, isa) tuples.
|
||||
let mut tuples = test_tuples(&tests, &testfile.isa_spec, flags)?;
|
||||
|
||||
// Isolate the last test in the hope that this is the only mutating test.
|
||||
// If so, we can completely avoid cloning functions.
|
||||
let last_tuple = match tuples.pop() {
|
||||
None => return Err("no test commands found".to_string()),
|
||||
Some(t) => t,
|
||||
};
|
||||
|
||||
for (func, details) in testfile.functions {
|
||||
let mut context = Context {
|
||||
preamble_comments: &testfile.preamble_comments,
|
||||
details,
|
||||
verified: false,
|
||||
flags,
|
||||
isa: None,
|
||||
};
|
||||
|
||||
for tuple in &tuples {
|
||||
run_one_test(*tuple, Cow::Borrowed(&func), &mut context)?;
|
||||
}
|
||||
// Run the last test with an owned function which means it won't need to clone it before
|
||||
// mutating.
|
||||
run_one_test(last_tuple, Cow::Owned(func), &mut context)?;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Actually run the tests.
|
||||
Ok(started.elapsed())
|
||||
}
|
||||
|
||||
// Given a slice of tests, generate a vector of (test, flags, isa) tuples.
|
||||
fn test_tuples<'a>(tests: &'a [Box<SubTest>],
|
||||
isa_spec: &'a IsaSpec,
|
||||
no_isa_flags: &'a Flags)
|
||||
-> Result<Vec<(&'a SubTest, &'a Flags, Option<&'a TargetIsa>)>> {
|
||||
let mut out = Vec::new();
|
||||
for test in tests {
|
||||
if test.needs_isa() {
|
||||
match *isa_spec {
|
||||
IsaSpec::None(_) => {
|
||||
// TODO: Generate a list of default ISAs.
|
||||
return Err(format!("test {} requires an ISA", test.name()));
|
||||
}
|
||||
IsaSpec::Some(ref isas) => {
|
||||
for isa in isas {
|
||||
out.push((&**test, isa.flags(), Some(&**isa)));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This test doesn't require an ISA, and we only want to run one instance of it.
|
||||
// Still, give it an ISA ref if we happen to have a unique one.
|
||||
// For example, `test cat` can use this to print encodings and register names.
|
||||
out.push((&**test, no_isa_flags, isa_spec.unique_isa()));
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn run_one_test<'a>(tuple: (&'a SubTest, &'a Flags, Option<&'a TargetIsa>),
|
||||
func: Cow<Function>,
|
||||
context: &mut Context<'a>)
|
||||
-> Result<()> {
|
||||
let (test, flags, isa) = tuple;
|
||||
let name = format!("{}({})", test.name(), func.name);
|
||||
dbg!("Test: {} {}", name, isa.map(TargetIsa::name).unwrap_or("-"));
|
||||
|
||||
context.flags = flags;
|
||||
context.isa = isa;
|
||||
|
||||
// Should we run the verifier before this test?
|
||||
if !context.verified && test.needs_verifier() {
|
||||
verify_function(&func, isa)
|
||||
.map_err(|e| pretty_verifier_error(&func, isa, e))?;
|
||||
context.verified = true;
|
||||
}
|
||||
|
||||
test.run(func, context)
|
||||
.map_err(|e| format!("{}: {}", name, e))
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
//! Test command for testing the simple GVN pass.
|
||||
//!
|
||||
//! The `simple-gvn` test command runs each function through the simple GVN pass after ensuring
|
||||
//! that all instructions are legal for the target.
|
||||
//!
|
||||
//! The resulting function is sent to `filecheck`.
|
||||
|
||||
use cretonne::ir::Function;
|
||||
use cretonne;
|
||||
use cton_reader::TestCommand;
|
||||
use filetest::subtest::{SubTest, Context, Result, run_filecheck};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
use utils::pretty_error;
|
||||
|
||||
struct TestSimpleGVN;
|
||||
|
||||
pub fn subtest(parsed: &TestCommand) -> Result<Box<SubTest>> {
|
||||
assert_eq!(parsed.command, "simple-gvn");
|
||||
if !parsed.options.is_empty() {
|
||||
Err(format!("No options allowed on {}", parsed))
|
||||
} else {
|
||||
Ok(Box::new(TestSimpleGVN))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubTest for TestSimpleGVN {
|
||||
fn name(&self) -> Cow<str> {
|
||||
Cow::from("simple-gvn")
|
||||
}
|
||||
|
||||
fn is_mutating(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(&self, func: Cow<Function>, context: &Context) -> Result<()> {
|
||||
// Create a compilation context, and drop in the function.
|
||||
let mut comp_ctx = cretonne::Context::new();
|
||||
comp_ctx.func = func.into_owned();
|
||||
|
||||
comp_ctx.flowgraph();
|
||||
comp_ctx
|
||||
.simple_gvn()
|
||||
.map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?;
|
||||
|
||||
let mut text = String::new();
|
||||
write!(&mut text, "{}", &comp_ctx.func)
|
||||
.map_err(|e| e.to_string())?;
|
||||
run_filecheck(&text, context)
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
//! SubTest trait.
|
||||
|
||||
use std::result;
|
||||
use std::borrow::Cow;
|
||||
use cretonne::ir::Function;
|
||||
use cretonne::isa::TargetIsa;
|
||||
use cretonne::settings::Flags;
|
||||
use cton_reader::{Details, Comment};
|
||||
use filecheck::{self, CheckerBuilder, Checker, Value as FCValue};
|
||||
|
||||
pub type Result<T> = result::Result<T, String>;
|
||||
|
||||
/// Context for running a test on a single function.
|
||||
pub struct Context<'a> {
|
||||
/// Comments from the preamble f the test file. These apply to all functions.
|
||||
pub preamble_comments: &'a [Comment<'a>],
|
||||
|
||||
/// Additional details about the function from the parser.
|
||||
pub details: Details<'a>,
|
||||
|
||||
/// Was the function verified before running this test?
|
||||
pub verified: bool,
|
||||
|
||||
/// ISA-independent flags for this test.
|
||||
pub flags: &'a Flags,
|
||||
|
||||
/// Target ISA to test against. Only guaranteed to be present for sub-tests whose `needs_isa`
|
||||
/// method returned `true`. For other sub-tests, this is set if the test file has a unique ISA.
|
||||
pub isa: Option<&'a TargetIsa>,
|
||||
}
|
||||
|
||||
/// Common interface for implementations of test commands.
|
||||
///
|
||||
/// Each `.cton` test file may contain multiple test commands, each represented by a `SubTest`
|
||||
/// trait object.
|
||||
pub trait SubTest {
|
||||
/// Name identifying this subtest. Typically the same as the test command.
|
||||
fn name(&self) -> Cow<str>;
|
||||
|
||||
/// Should the verifier be run on the function before running the test?
|
||||
fn needs_verifier(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Does this test mutate the function when it runs?
|
||||
/// This is used as a hint to avoid cloning the function needlessly.
|
||||
fn is_mutating(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Does this test need a `TargetIsa` trait object?
|
||||
fn needs_isa(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Run this test on `func`.
|
||||
fn run(&self, func: Cow<Function>, context: &Context) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Make the parser's source map available as filecheck variables.
|
||||
///
|
||||
/// This means that the filecheck directives can refer to entities like `jump $ebb3`, where `$ebb3`
|
||||
/// will expand to the EBB number that was assigned to `ebb3` in the input source.
|
||||
///
|
||||
/// The expanded entity names are wrapped in word boundary regex guards so that 'inst1' doesn't
|
||||
/// match 'inst10'.
|
||||
impl<'a> filecheck::VariableMap for Context<'a> {
|
||||
fn lookup(&self, varname: &str) -> Option<FCValue> {
|
||||
self.details
|
||||
.map
|
||||
.lookup_str(varname)
|
||||
.map(|e| FCValue::Regex(format!(r"\b{}\b", e).into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Run filecheck on `text`, using directives extracted from `context`.
|
||||
pub fn run_filecheck(text: &str, context: &Context) -> Result<()> {
|
||||
let checker = build_filechecker(context)?;
|
||||
if checker
|
||||
.check(&text, context)
|
||||
.map_err(|e| format!("filecheck: {}", e))? {
|
||||
Ok(())
|
||||
} else {
|
||||
// Filecheck mismatch. Emit an explanation as output.
|
||||
let (_, explain) = checker
|
||||
.explain(&text, context)
|
||||
.map_err(|e| format!("explain: {}", e))?;
|
||||
Err(format!("filecheck failed:\n{}{}", checker, explain))
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a filechecker using the directives in the file preamble and the function's comments.
|
||||
pub fn build_filechecker(context: &Context) -> Result<Checker> {
|
||||
let mut builder = CheckerBuilder::new();
|
||||
// Preamble comments apply to all functions.
|
||||
for comment in context.preamble_comments {
|
||||
builder
|
||||
.directive(comment.text)
|
||||
.map_err(|e| format!("filecheck: {}", e))?;
|
||||
}
|
||||
for comment in &context.details.comments {
|
||||
builder
|
||||
.directive(comment.text)
|
||||
.map_err(|e| format!("filecheck: {}", e))?;
|
||||
}
|
||||
let checker = builder.finish();
|
||||
if checker.is_empty() {
|
||||
Err("no filecheck directives in function".to_string())
|
||||
} else {
|
||||
Ok(checker)
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
//! Test command for checking the IL verifier.
|
||||
//!
|
||||
//! The `test verifier` test command looks for annotations on instructions like this:
|
||||
//!
|
||||
//! jump ebb3 ; error: jump to non-existent EBB
|
||||
//!
|
||||
//! This annotation means that the verifier is expected to given an error for the jump instruction
|
||||
//! containing the substring "jump to non-existent EBB".
|
||||
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use cretonne::verify_function;
|
||||
use cretonne::ir::Function;
|
||||
use cton_reader::TestCommand;
|
||||
use filetest::subtest::{SubTest, Context, Result};
|
||||
use utils::match_directive;
|
||||
|
||||
struct TestVerifier;
|
||||
|
||||
pub fn subtest(parsed: &TestCommand) -> Result<Box<SubTest>> {
|
||||
assert_eq!(parsed.command, "verifier");
|
||||
if !parsed.options.is_empty() {
|
||||
Err(format!("No options allowed on {}", parsed))
|
||||
} else {
|
||||
Ok(Box::new(TestVerifier))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubTest for TestVerifier {
|
||||
fn name(&self) -> Cow<str> {
|
||||
Cow::from("verifier")
|
||||
}
|
||||
|
||||
fn needs_verifier(&self) -> bool {
|
||||
// Running the verifier before this test would defeat its purpose.
|
||||
false
|
||||
}
|
||||
|
||||
fn run(&self, func: Cow<Function>, context: &Context) -> Result<()> {
|
||||
let func = func.borrow();
|
||||
|
||||
// Scan source annotations for "error:" directives.
|
||||
let mut expected = None;
|
||||
for comment in &context.details.comments {
|
||||
if let Some(tail) = match_directive(comment.text, "error:") {
|
||||
// Currently, the verifier can only report one problem at a time.
|
||||
// Reject more than one `error:` directives.
|
||||
if expected.is_some() {
|
||||
return Err("cannot handle multiple error: directives".to_string());
|
||||
}
|
||||
expected = Some((comment.entity, tail));
|
||||
}
|
||||
}
|
||||
|
||||
match verify_function(func, context.isa) {
|
||||
Ok(_) => {
|
||||
match expected {
|
||||
None => Ok(()),
|
||||
Some((_, msg)) => Err(format!("passed, expected error: {}", msg)),
|
||||
}
|
||||
}
|
||||
Err(got) => {
|
||||
match expected {
|
||||
None => Err(format!("verifier pass, got {}", got)),
|
||||
Some((want_loc, want_msg)) if got.message.contains(want_msg) => {
|
||||
if want_loc == got.location {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("correct error reported on {}, but wanted {}",
|
||||
got.location,
|
||||
want_loc))
|
||||
}
|
||||
}
|
||||
Some(_) => Err(format!("mismatching error: {}", got)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
440
src/main.rs
Normal file
440
src/main.rs
Normal file
@@ -0,0 +1,440 @@
|
||||
//! CLI tool to use the functions provided by crates [wasm2cretonne](../wasm2cretonne/index.html)
|
||||
//! and [wasmstandalone](../wasmstandalone/index.html).
|
||||
//!
|
||||
//! Reads Wasm binary files (one Wasm module per file), translates the functions' code to Cretonne
|
||||
//! IL. Can also executes the `start` function of the module by laying out the memories, globals
|
||||
//! and tables, then emitting the translated code with hardcoded addresses to memory.
|
||||
|
||||
extern crate cton_wasm;
|
||||
extern crate wasmstandalone;
|
||||
extern crate wasmparser;
|
||||
extern crate cretonne;
|
||||
extern crate wasmtext;
|
||||
extern crate docopt;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate term;
|
||||
extern crate tempdir;
|
||||
|
||||
use cton_wasm::{translate_module, TranslationResult, FunctionTranslation, DummyRuntime,
|
||||
WasmRuntime};
|
||||
use wasmstandalone::{StandaloneRuntime, compile_module, execute};
|
||||
use std::path::PathBuf;
|
||||
use wasmparser::{Parser, ParserState, WasmDecoder, SectionCode};
|
||||
use wasmtext::Writer;
|
||||
use cretonne::loop_analysis::LoopAnalysis;
|
||||
use cretonne::flowgraph::ControlFlowGraph;
|
||||
use cretonne::dominator_tree::DominatorTree;
|
||||
use cretonne::Context;
|
||||
use cretonne::result::CtonError;
|
||||
use cretonne::ir;
|
||||
use cretonne::ir::entities::AnyEntity;
|
||||
use cretonne::isa::TargetIsa;
|
||||
use cretonne::verifier;
|
||||
use std::fs::File;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::io::{BufReader, stdout};
|
||||
use std::io::prelude::*;
|
||||
use docopt::Docopt;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use tempdir::TempDir;
|
||||
|
||||
macro_rules! vprintln {
|
||||
($x: expr, $($tts:tt)*) => {
|
||||
if $x {
|
||||
println!($($tts)*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! vprint {
|
||||
($x: expr, $($tts:tt)*) => {
|
||||
if $x {
|
||||
print!($($tts)*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const USAGE: &str = "
|
||||
Wasm to Cretonne IL translation utility.
|
||||
Takes a binary WebAssembly module and returns its functions in Cretonne IL format.
|
||||
The translation is dependent on the runtime chosen.
|
||||
The default is a dummy runtime that produces placeholder values.
|
||||
|
||||
Usage:
|
||||
wasmstandalone-util [-vcop] <file>...
|
||||
wasmstandalone-util -e [-mvcop] <file>...
|
||||
wasmstandalone-util --help | --version
|
||||
|
||||
Options:
|
||||
-v, --verbose displays info on the different steps
|
||||
-p, --print displays the module and translated functions
|
||||
-c, --check checks the corectness of the translated functions
|
||||
-o, --optimize runs optimization passes on the translated functions
|
||||
-e, --execute enable the standalone runtime and executes the start function of the module
|
||||
-m, --memory interactive memory inspector after execution
|
||||
-h, --help print this help message
|
||||
--version print the Cretonne version
|
||||
";
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct Args {
|
||||
arg_file: Vec<String>,
|
||||
flag_verbose: bool,
|
||||
flag_execute: bool,
|
||||
flag_memory: bool,
|
||||
flag_check: bool,
|
||||
flag_optimize: bool,
|
||||
flag_print: bool,
|
||||
}
|
||||
|
||||
fn read_wasm_file(path: PathBuf) -> Result<Vec<u8>, io::Error> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
let file = File::open(path)?;
|
||||
let mut buf_reader = BufReader::new(file);
|
||||
buf_reader.read_to_end(&mut buf)?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
let args: Args = Docopt::new(USAGE)
|
||||
.and_then(|d| d.help(true).version(Some(format!("0.0.0"))).deserialize())
|
||||
.unwrap_or_else(|e| e.exit());
|
||||
let mut terminal = term::stdout().unwrap();
|
||||
for filename in args.arg_file.iter() {
|
||||
let path = Path::new(&filename);
|
||||
let name = String::from(path.as_os_str().to_string_lossy());
|
||||
match handle_module(&args, path.to_path_buf(), name) {
|
||||
Ok(()) => {}
|
||||
Err(message) => {
|
||||
terminal.fg(term::color::RED).unwrap();
|
||||
vprintln!(args.flag_verbose, "error");
|
||||
terminal.reset().unwrap();
|
||||
vprintln!(args.flag_verbose, "{}", message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_module(args: &Args, path: PathBuf, name: String) -> Result<(), String> {
|
||||
let mut terminal = term::stdout().unwrap();
|
||||
terminal.fg(term::color::YELLOW).unwrap();
|
||||
vprint!(args.flag_verbose, "Handling: ");
|
||||
terminal.reset().unwrap();
|
||||
vprintln!(args.flag_verbose, "\"{}\"", name);
|
||||
terminal.fg(term::color::MAGENTA).unwrap();
|
||||
vprint!(args.flag_verbose, "Translating...");
|
||||
terminal.reset().unwrap();
|
||||
let data = match path.extension() {
|
||||
None => {
|
||||
return Err(String::from("the file extension is not wasm or wast"));
|
||||
}
|
||||
Some(ext) => {
|
||||
match ext.to_str() {
|
||||
Some("wasm") => {
|
||||
match read_wasm_file(path.clone()) {
|
||||
Ok(data) => data,
|
||||
Err(err) => {
|
||||
return Err(String::from(err.description()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some("wast") => {
|
||||
let tmp_dir = TempDir::new("wasmstandalone").unwrap();
|
||||
let file_path = tmp_dir.path().join("module.wasm");
|
||||
File::create(file_path.clone()).unwrap();
|
||||
Command::new("wast2wasm")
|
||||
.arg(path.clone())
|
||||
.arg("-o")
|
||||
.arg(file_path.to_str().unwrap())
|
||||
.output()
|
||||
.or_else(|e| if let io::ErrorKind::NotFound = e.kind() {
|
||||
return Err(String::from("wast2wasm not found"));
|
||||
} else {
|
||||
return Err(String::from(e.description()));
|
||||
})
|
||||
.unwrap();
|
||||
match read_wasm_file(file_path) {
|
||||
Ok(data) => data,
|
||||
Err(err) => {
|
||||
return Err(String::from(err.description()));
|
||||
}
|
||||
}
|
||||
}
|
||||
None | Some(&_) => {
|
||||
return Err(String::from("the file extension is not wasm or wast"));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut dummy_runtime = DummyRuntime::new();
|
||||
let mut standalone_runtime = StandaloneRuntime::new();
|
||||
let translation = {
|
||||
let runtime: &mut WasmRuntime = if args.flag_execute {
|
||||
&mut standalone_runtime
|
||||
} else {
|
||||
&mut dummy_runtime
|
||||
};
|
||||
match translate_module(&data, runtime) {
|
||||
Ok(x) => x,
|
||||
Err(string) => {
|
||||
return Err(string);
|
||||
}
|
||||
}
|
||||
};
|
||||
terminal.fg(term::color::GREEN).unwrap();
|
||||
vprintln!(args.flag_verbose, " ok");
|
||||
terminal.reset().unwrap();
|
||||
if args.flag_check {
|
||||
terminal.fg(term::color::MAGENTA).unwrap();
|
||||
vprint!(args.flag_verbose, "Checking... ");
|
||||
terminal.reset().unwrap();
|
||||
for func in translation.functions.iter() {
|
||||
let il = match func {
|
||||
&FunctionTranslation::Import() => continue,
|
||||
&FunctionTranslation::Code { ref il, .. } => il.clone(),
|
||||
};
|
||||
match verifier::verify_function(&il, None) {
|
||||
Ok(()) => (),
|
||||
Err(err) => return Err(pretty_verifier_error(&il, None, err)),
|
||||
}
|
||||
}
|
||||
terminal.fg(term::color::GREEN).unwrap();
|
||||
vprintln!(args.flag_verbose, " ok");
|
||||
terminal.reset().unwrap();
|
||||
}
|
||||
if args.flag_print {
|
||||
let mut writer1 = stdout();
|
||||
let mut writer2 = stdout();
|
||||
match pretty_print_translation(&name, &data, &translation, &mut writer1, &mut writer2) {
|
||||
Err(error) => return Err(String::from(error.description())),
|
||||
Ok(()) => (),
|
||||
}
|
||||
}
|
||||
if args.flag_optimize {
|
||||
terminal.fg(term::color::MAGENTA).unwrap();
|
||||
vprint!(args.flag_verbose, "Optimizing... ");
|
||||
terminal.reset().unwrap();
|
||||
for func in translation.functions.iter() {
|
||||
let mut il = match func {
|
||||
&FunctionTranslation::Import() => continue,
|
||||
&FunctionTranslation::Code { ref il, .. } => il.clone(),
|
||||
};
|
||||
let mut loop_analysis = LoopAnalysis::new();
|
||||
let mut cfg = ControlFlowGraph::new();
|
||||
cfg.compute(&il);
|
||||
let mut domtree = DominatorTree::new();
|
||||
domtree.compute(&mut il, &cfg);
|
||||
loop_analysis.compute(&mut il, &mut cfg, &mut domtree);
|
||||
let mut context = Context::new();
|
||||
context.func = il;
|
||||
context.cfg = cfg;
|
||||
context.domtree = domtree;
|
||||
context.loop_analysis = loop_analysis;
|
||||
match verifier::verify_context(&context.func, &context.cfg, &context.domtree, None) {
|
||||
Ok(()) => (),
|
||||
Err(err) => {
|
||||
return Err(pretty_verifier_error(&context.func, None, err));
|
||||
}
|
||||
};
|
||||
match context.licm() {
|
||||
Ok(())=> (),
|
||||
Err(error) => {
|
||||
match error {
|
||||
CtonError::Verifier(err) => {
|
||||
return Err(pretty_verifier_error(&context.func, None, err));
|
||||
}
|
||||
CtonError::InvalidInput |
|
||||
CtonError::ImplLimitExceeded |
|
||||
CtonError::CodeTooLarge => return Err(String::from(error.description())),
|
||||
}
|
||||
}
|
||||
};
|
||||
match verifier::verify_context(&context.func, &context.cfg, &context.domtree, None) {
|
||||
Ok(()) => (),
|
||||
Err(err) => return Err(pretty_verifier_error(&context.func, None, err)),
|
||||
}
|
||||
}
|
||||
terminal.fg(term::color::GREEN).unwrap();
|
||||
vprintln!(args.flag_verbose, " ok");
|
||||
terminal.reset().unwrap();
|
||||
}
|
||||
if args.flag_execute {
|
||||
terminal.fg(term::color::MAGENTA).unwrap();
|
||||
vprint!(args.flag_verbose, "Compiling... ");
|
||||
terminal.reset().unwrap();
|
||||
match compile_module(&translation) {
|
||||
Ok(exec) => {
|
||||
terminal.fg(term::color::GREEN).unwrap();
|
||||
vprintln!(args.flag_verbose, "ok");
|
||||
terminal.reset().unwrap();
|
||||
terminal.fg(term::color::MAGENTA).unwrap();
|
||||
vprint!(args.flag_verbose, "Executing... ");
|
||||
terminal.reset().unwrap();
|
||||
match execute(exec) {
|
||||
Ok(()) => {
|
||||
terminal.fg(term::color::GREEN).unwrap();
|
||||
vprintln!(args.flag_verbose, "ok");
|
||||
terminal.reset().unwrap();
|
||||
}
|
||||
Err(s) => {
|
||||
return Err(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(s) => {
|
||||
return Err(s);
|
||||
}
|
||||
};
|
||||
if args.flag_memory {
|
||||
let mut input = String::new();
|
||||
terminal.fg(term::color::YELLOW).unwrap();
|
||||
println!("Inspecting memory");
|
||||
terminal.fg(term::color::MAGENTA).unwrap();
|
||||
println!("Type 'quit' to exit.");
|
||||
terminal.reset().unwrap();
|
||||
loop {
|
||||
input.clear();
|
||||
terminal.fg(term::color::YELLOW).unwrap();
|
||||
print!("Memory index, offset, length (e.g. 0,0,4): ");
|
||||
terminal.reset().unwrap();
|
||||
let _ = stdout().flush();
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
input.pop();
|
||||
if input == "quit" {
|
||||
break;
|
||||
}
|
||||
let split: Vec<&str> = input.split(",").collect();
|
||||
if split.len() != 3 {
|
||||
break;
|
||||
}
|
||||
let memory = standalone_runtime
|
||||
.inspect_memory(str::parse(split[0]).unwrap(),
|
||||
str::parse(split[1]).unwrap(),
|
||||
str::parse(split[2]).unwrap());
|
||||
let mut s = memory
|
||||
.iter()
|
||||
.fold(String::from("#"), |mut acc, byte| {
|
||||
acc.push_str(format!("{:02x}_", byte).as_str());
|
||||
acc
|
||||
});
|
||||
s.pop();
|
||||
println!("{}", s);
|
||||
}
|
||||
Err(error) => return Err(String::from(error.description())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Prints out a Wasm module, and for each function the corresponding translation in Cretonne IL.
|
||||
fn pretty_print_translation(filename: &String,
|
||||
data: &Vec<u8>,
|
||||
translation: &TranslationResult,
|
||||
writer_wast: &mut Write,
|
||||
writer_cretonne: &mut Write)
|
||||
-> Result<(), io::Error> {
|
||||
let mut terminal = term::stdout().unwrap();
|
||||
let mut parser = Parser::new(data.as_slice());
|
||||
let mut parser_writer = Writer::new(writer_wast);
|
||||
let imports_count = translation
|
||||
.functions
|
||||
.iter()
|
||||
.fold(0, |acc, &ref f| match f {
|
||||
&FunctionTranslation::Import() => acc + 1,
|
||||
&FunctionTranslation::Code { .. } => acc,
|
||||
});
|
||||
match parser.read() {
|
||||
s @ &ParserState::BeginWasm { .. } => parser_writer.write(&s)?,
|
||||
_ => panic!("modules should begin properly"),
|
||||
}
|
||||
loop {
|
||||
match parser.read() {
|
||||
s @ &ParserState::BeginSection { code: SectionCode::Code, .. } => {
|
||||
// The code section begins
|
||||
parser_writer.write(&s)?;
|
||||
break;
|
||||
}
|
||||
&ParserState::EndWasm => return Ok(()),
|
||||
s @ _ => parser_writer.write(&s)?,
|
||||
}
|
||||
}
|
||||
let mut function_index = 0;
|
||||
loop {
|
||||
match parser.read() {
|
||||
s @ &ParserState::BeginFunctionBody { .. } => {
|
||||
terminal.fg(term::color::BLUE).unwrap();
|
||||
write!(writer_cretonne,
|
||||
"====== Function No. {} of module \"{}\" ======\n",
|
||||
function_index,
|
||||
filename)?;
|
||||
terminal.fg(term::color::CYAN).unwrap();
|
||||
write!(writer_cretonne, "Wast ---------->\n")?;
|
||||
terminal.reset().unwrap();
|
||||
parser_writer.write(&s)?;
|
||||
}
|
||||
s @ &ParserState::EndSection => {
|
||||
parser_writer.write(&s)?;
|
||||
break;
|
||||
}
|
||||
_ => panic!("wrong content in code section"),
|
||||
}
|
||||
{
|
||||
loop {
|
||||
match parser.read() {
|
||||
s @ &ParserState::EndFunctionBody => {
|
||||
parser_writer.write(&s)?;
|
||||
break;
|
||||
}
|
||||
s @ _ => {
|
||||
parser_writer.write(&s)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
let mut function_string =
|
||||
format!(" {}",
|
||||
match translation.functions[function_index + imports_count] {
|
||||
FunctionTranslation::Code { ref il, .. } => il,
|
||||
FunctionTranslation::Import() => panic!("should not happen"),
|
||||
}
|
||||
.display(None));
|
||||
function_string.pop();
|
||||
let function_str = str::replace(function_string.as_str(), "\n", "\n ");
|
||||
terminal.fg(term::color::CYAN).unwrap();
|
||||
write!(writer_cretonne, "Cretonne IL --->\n")?;
|
||||
terminal.reset().unwrap();
|
||||
write!(writer_cretonne, "{}\n", function_str)?;
|
||||
function_index += 1;
|
||||
}
|
||||
loop {
|
||||
match parser.read() {
|
||||
&ParserState::EndWasm => return Ok(()),
|
||||
s @ _ => parser_writer.write(&s)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pretty-print a verifier error.
|
||||
pub fn pretty_verifier_error(func: &ir::Function,
|
||||
isa: Option<&TargetIsa>,
|
||||
err: verifier::Error)
|
||||
-> String {
|
||||
let msg = err.to_string();
|
||||
let str1 = match err.location {
|
||||
AnyEntity::Inst(inst) => {
|
||||
format!("{}\n{}: {}\n\n",
|
||||
msg,
|
||||
inst,
|
||||
func.dfg.display_inst(inst, isa))
|
||||
}
|
||||
_ => String::from(format!("{}\n", msg)),
|
||||
};
|
||||
format!("{}{}", str1, func.display(isa))
|
||||
}
|
||||
133
src/print_cfg.rs
133
src/print_cfg.rs
@@ -1,133 +0,0 @@
|
||||
//! The `print-cfg` sub-command.
|
||||
//!
|
||||
//! Read a series of Cretonne IL files and print their control flow graphs
|
||||
//! in graphviz format.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{Result, Write, Display, Formatter};
|
||||
|
||||
use CommandResult;
|
||||
use cretonne::flowgraph::ControlFlowGraph;
|
||||
use cretonne::ir::Function;
|
||||
use cretonne::ir::instructions::BranchInfo;
|
||||
use cton_reader::{parse_functions, TestCommand};
|
||||
use filetest::subtest::{self, SubTest, Context, Result as STResult};
|
||||
use utils::read_to_string;
|
||||
|
||||
pub fn run(files: Vec<String>) -> CommandResult {
|
||||
for (i, f) in files.into_iter().enumerate() {
|
||||
if i != 0 {
|
||||
println!("");
|
||||
}
|
||||
print_cfg(f)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct CFGPrinter<'a> {
|
||||
func: &'a Function,
|
||||
cfg: ControlFlowGraph,
|
||||
}
|
||||
|
||||
impl<'a> CFGPrinter<'a> {
|
||||
pub fn new(func: &'a Function) -> CFGPrinter<'a> {
|
||||
CFGPrinter {
|
||||
func,
|
||||
cfg: ControlFlowGraph::with_function(func),
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the CFG for this function to `w`.
|
||||
pub fn write(&self, w: &mut Write) -> Result {
|
||||
self.header(w)?;
|
||||
self.ebb_nodes(w)?;
|
||||
self.cfg_connections(w)?;
|
||||
writeln!(w, "}}")
|
||||
}
|
||||
|
||||
fn header(&self, w: &mut Write) -> Result {
|
||||
writeln!(w, "digraph {} {{", self.func.name)?;
|
||||
if let Some(entry) = self.func.layout.entry_block() {
|
||||
writeln!(w, " {{rank=min; {}}}", entry)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ebb_nodes(&self, w: &mut Write) -> Result {
|
||||
for ebb in &self.func.layout {
|
||||
write!(w, " {} [shape=record, label=\"{{{}", ebb, ebb)?;
|
||||
// Add all outgoing branch instructions to the label.
|
||||
for inst in self.func.layout.ebb_insts(ebb) {
|
||||
let idata = &self.func.dfg[inst];
|
||||
match idata.analyze_branch(&self.func.dfg.value_lists) {
|
||||
BranchInfo::SingleDest(dest, _) => {
|
||||
write!(w, " | <{}>{} {}", inst, idata.opcode(), dest)?
|
||||
}
|
||||
BranchInfo::Table(table) => {
|
||||
write!(w, " | <{}>{} {}", inst, idata.opcode(), table)?
|
||||
}
|
||||
BranchInfo::NotABranch => {}
|
||||
}
|
||||
}
|
||||
writeln!(w, "}}\"]")?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cfg_connections(&self, w: &mut Write) -> Result {
|
||||
for ebb in &self.func.layout {
|
||||
for &(parent, inst) in self.cfg.get_predecessors(ebb) {
|
||||
writeln!(w, " {}:{} -> {}", parent, inst, ebb)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for CFGPrinter<'a> {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
self.write(f)
|
||||
}
|
||||
}
|
||||
|
||||
fn print_cfg(filename: String) -> CommandResult {
|
||||
let buffer = read_to_string(&filename)
|
||||
.map_err(|e| format!("{}: {}", filename, e))?;
|
||||
let items = parse_functions(&buffer)
|
||||
.map_err(|e| format!("{}: {}", filename, e))?;
|
||||
|
||||
for (idx, func) in items.into_iter().enumerate() {
|
||||
if idx != 0 {
|
||||
println!("");
|
||||
}
|
||||
print!("{}", CFGPrinter::new(&func));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Object implementing the `test print-cfg` sub-test.
|
||||
struct TestPrintCfg;
|
||||
|
||||
pub fn subtest(parsed: &TestCommand) -> STResult<Box<SubTest>> {
|
||||
assert_eq!(parsed.command, "print-cfg");
|
||||
if !parsed.options.is_empty() {
|
||||
Err(format!("No options allowed on {}", parsed))
|
||||
} else {
|
||||
Ok(Box::new(TestPrintCfg))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubTest for TestPrintCfg {
|
||||
fn name(&self) -> Cow<str> {
|
||||
Cow::from("print-cfg")
|
||||
}
|
||||
|
||||
fn needs_verifier(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn run(&self, func: Cow<Function>, context: &Context) -> STResult<()> {
|
||||
subtest::run_filecheck(&CFGPrinter::new(&func).to_string(), context)
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use CommandResult;
|
||||
use utils::read_to_string;
|
||||
use filecheck::{CheckerBuilder, Checker, NO_VARIABLES};
|
||||
use std::io::{self, Read};
|
||||
|
||||
pub fn run(files: Vec<String>, verbose: bool) -> CommandResult {
|
||||
if files.is_empty() {
|
||||
return Err("No check files".to_string());
|
||||
}
|
||||
let checker = read_checkfile(&files[0])?;
|
||||
if checker.is_empty() {
|
||||
return Err(format!("{}: no filecheck directives found", files[0]));
|
||||
}
|
||||
|
||||
// Print out the directives under --verbose.
|
||||
if verbose {
|
||||
println!("{}", checker);
|
||||
}
|
||||
|
||||
let mut buffer = String::new();
|
||||
io::stdin()
|
||||
.read_to_string(&mut buffer)
|
||||
.map_err(|e| format!("stdin: {}", e))?;
|
||||
|
||||
if verbose {
|
||||
let (success, explain) = checker
|
||||
.explain(&buffer, NO_VARIABLES)
|
||||
.map_err(|e| e.to_string())?;
|
||||
print!("{}", explain);
|
||||
if success {
|
||||
println!("OK");
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Check failed".to_string())
|
||||
}
|
||||
} else if checker
|
||||
.check(&buffer, NO_VARIABLES)
|
||||
.map_err(|e| e.to_string())? {
|
||||
Ok(())
|
||||
} else {
|
||||
let (_, explain) = checker
|
||||
.explain(&buffer, NO_VARIABLES)
|
||||
.map_err(|e| e.to_string())?;
|
||||
print!("{}", explain);
|
||||
Err("Check failed".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn read_checkfile(filename: &str) -> Result<Checker, String> {
|
||||
let buffer = read_to_string(&filename)
|
||||
.map_err(|e| format!("{}: {}", filename, e))?;
|
||||
let mut builder = CheckerBuilder::new();
|
||||
builder
|
||||
.text(&buffer)
|
||||
.map_err(|e| format!("{}: {}", filename, e))?;
|
||||
Ok(builder.finish())
|
||||
}
|
||||
69
src/utils.rs
69
src/utils.rs
@@ -1,69 +0,0 @@
|
||||
//! Utility functions.
|
||||
|
||||
use cretonne::ir::entities::AnyEntity;
|
||||
use cretonne::{ir, verifier};
|
||||
use cretonne::result::CtonError;
|
||||
use cretonne::isa::TargetIsa;
|
||||
use std::fmt::Write;
|
||||
use std::fs::File;
|
||||
use std::io::{Result, Read};
|
||||
use std::path::Path;
|
||||
|
||||
/// Read an entire file into a string.
|
||||
pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
|
||||
let mut file = File::open(path)?;
|
||||
let mut buffer = String::new();
|
||||
file.read_to_string(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Look for a directive in a comment string.
|
||||
/// The directive is of the form "foo:" and should follow the leading `;` in the comment:
|
||||
///
|
||||
/// ; dominates: ebb3 ebb4
|
||||
///
|
||||
/// Return the comment text following the directive.
|
||||
pub fn match_directive<'a>(comment: &'a str, directive: &str) -> Option<&'a str> {
|
||||
assert!(directive.ends_with(':'),
|
||||
"Directive must include trailing colon");
|
||||
let text = comment.trim_left_matches(';').trim_left();
|
||||
if text.starts_with(directive) {
|
||||
Some(text[directive.len()..].trim())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Pretty-print a verifier error.
|
||||
pub fn pretty_verifier_error(func: &ir::Function,
|
||||
isa: Option<&TargetIsa>,
|
||||
err: verifier::Error)
|
||||
-> String {
|
||||
let mut msg = err.to_string();
|
||||
match err.location {
|
||||
AnyEntity::Inst(inst) => {
|
||||
write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst, isa)).unwrap()
|
||||
}
|
||||
_ => msg.push('\n'),
|
||||
}
|
||||
write!(msg, "{}", func.display(isa)).unwrap();
|
||||
msg
|
||||
}
|
||||
|
||||
/// Pretty-print a Cretonne error.
|
||||
pub fn pretty_error(func: &ir::Function, isa: Option<&TargetIsa>, err: CtonError) -> String {
|
||||
if let CtonError::Verifier(e) = err {
|
||||
pretty_verifier_error(func, isa, e)
|
||||
} else {
|
||||
err.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match_directive() {
|
||||
assert_eq!(match_directive("; foo: bar ", "foo:"), Some("bar"));
|
||||
assert_eq!(match_directive(" foo:bar", "foo:"), Some("bar"));
|
||||
assert_eq!(match_directive("foo:bar", "foo:"), Some("bar"));
|
||||
assert_eq!(match_directive(";x foo: bar", "foo:"), None);
|
||||
assert_eq!(match_directive(";;; foo: bar", "foo:"), Some("bar"));
|
||||
}
|
||||
Reference in New Issue
Block a user