winch: Adding support for integration tests (#5588)
* Adding in the foundations for Winch `filetests` This commit adds two new crates into the Winch workspace: `filetests` and `test-macros`. The intent is to mimic the structure of Cranelift `filetests`, but in a simpler way. * Updates to documentation This commits adds a high level document to outline how to test Winch through the `winch-tools` utility. It also updates some inline documentation which gets propagated to the CLI. * Updating test-macro to use a glob instead of only a flat directory
This commit is contained in:
72
winch/src/compile.rs
Normal file
72
winch/src/compile.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use cranelift_codegen::settings;
|
||||
use std::{fs, path::PathBuf, str::FromStr};
|
||||
use target_lexicon::Triple;
|
||||
use wasmtime_environ::{
|
||||
wasmparser::{types::Types, Parser as WasmParser, Validator},
|
||||
DefinedFuncIndex, FunctionBodyData, Module, ModuleEnvironment, Tunables,
|
||||
};
|
||||
use winch_codegen::{lookup, TargetIsa};
|
||||
use winch_filetests::disasm::disasm;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Options {
|
||||
/// The input file.
|
||||
input: PathBuf,
|
||||
|
||||
/// The target architecture.
|
||||
#[clap(long = "target")]
|
||||
target: String,
|
||||
}
|
||||
|
||||
pub fn run(opt: &Options) -> Result<()> {
|
||||
let bytes = fs::read(&opt.input)
|
||||
.with_context(|| format!("Failed to read input file {}", opt.input.display()))?;
|
||||
let bytes = wat::parse_bytes(&bytes)?;
|
||||
let triple = Triple::from_str(&opt.target)?;
|
||||
let shared_flags = settings::Flags::new(settings::builder());
|
||||
let isa_builder = lookup(triple)?;
|
||||
let isa = isa_builder.build(shared_flags)?;
|
||||
let mut validator = Validator::new();
|
||||
let parser = WasmParser::new(0);
|
||||
let mut types = Default::default();
|
||||
let tunables = Tunables::default();
|
||||
let mut translation = ModuleEnvironment::new(&tunables, &mut validator, &mut types)
|
||||
.translate(parser, &bytes)
|
||||
.context("Failed to translate WebAssembly module")?;
|
||||
let _ = types.finish();
|
||||
|
||||
let body_inputs = std::mem::take(&mut translation.function_body_inputs);
|
||||
let module = &translation.module;
|
||||
let types = translation.get_types();
|
||||
|
||||
body_inputs
|
||||
.into_iter()
|
||||
.try_for_each(|func| compile(&*isa, module, types, func))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile(
|
||||
isa: &dyn TargetIsa,
|
||||
module: &Module,
|
||||
types: &Types,
|
||||
f: (DefinedFuncIndex, FunctionBodyData<'_>),
|
||||
) -> Result<()> {
|
||||
let index = module.func_index(f.0);
|
||||
let sig = types
|
||||
.func_type_at(index.as_u32())
|
||||
.expect(&format!("function type at index {:?}", index.as_u32()));
|
||||
let FunctionBodyData { body, validator } = f.1;
|
||||
let validator = validator.into_validator(Default::default());
|
||||
let buffer = isa
|
||||
.compile_function(&sig, &body, validator)
|
||||
.expect("Couldn't compile function");
|
||||
|
||||
disasm(buffer.data(), isa)?
|
||||
.iter()
|
||||
.for_each(|s| println!("{}", s));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
//! Disassembly utilities.
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use capstone::prelude::*;
|
||||
use std::fmt::Write;
|
||||
use target_lexicon::Architecture;
|
||||
use winch_codegen::TargetIsa;
|
||||
|
||||
/// Disassemble and print a machine code buffer.
|
||||
pub fn print(bytes: &[u8], isa: &dyn TargetIsa) -> Result<()> {
|
||||
let dis = disassembler_for(isa)?;
|
||||
let insts = dis.disasm_all(bytes, 0x0).unwrap();
|
||||
|
||||
for i in insts.iter() {
|
||||
let mut line = String::new();
|
||||
|
||||
write!(&mut line, "{:4x}:\t", i.address()).unwrap();
|
||||
|
||||
let mut bytes_str = String::new();
|
||||
let mut len = 0;
|
||||
let mut first = true;
|
||||
for b in i.bytes() {
|
||||
if !first {
|
||||
write!(&mut bytes_str, " ").unwrap();
|
||||
}
|
||||
write!(&mut bytes_str, "{:02x}", b).unwrap();
|
||||
len += 1;
|
||||
first = false;
|
||||
}
|
||||
write!(&mut line, "{:21}\t", bytes_str).unwrap();
|
||||
if len > 8 {
|
||||
write!(&mut line, "\n\t\t\t\t").unwrap();
|
||||
}
|
||||
|
||||
if let Some(s) = i.mnemonic() {
|
||||
write!(&mut line, "{}\t", s).unwrap();
|
||||
}
|
||||
|
||||
if let Some(s) = i.op_str() {
|
||||
write!(&mut line, "{}", s).unwrap();
|
||||
}
|
||||
|
||||
println!("{}", line);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn disassembler_for(isa: &dyn TargetIsa) -> Result<Capstone> {
|
||||
let disasm = match isa.triple().architecture {
|
||||
Architecture::X86_64 => Capstone::new()
|
||||
.x86()
|
||||
.mode(arch::x86::ArchMode::Mode64)
|
||||
.build()
|
||||
.map_err(|e| anyhow::format_err!("{}", e))?,
|
||||
|
||||
_ => bail!("Unsupported ISA"),
|
||||
};
|
||||
|
||||
Ok(disasm)
|
||||
}
|
||||
25
winch/src/filetests.rs
Normal file
25
winch/src/filetests.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use std::process::Command;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Options {
|
||||
/// Passes extra arguments to `cargo test --package winch-filetests`. For example, to run a single
|
||||
/// test, use `-- --test-threads 1 --test single_test_name`.
|
||||
#[clap(last = true, value_parser)]
|
||||
cargo_test_args: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn run(opts: &Options) -> Result<()> {
|
||||
Command::new("cargo")
|
||||
.arg("test")
|
||||
.arg("--package")
|
||||
.arg("winch-filetests")
|
||||
.arg("--")
|
||||
.args(&opts.cargo_test_args)
|
||||
.spawn()?
|
||||
.wait()
|
||||
.map(|_| ())
|
||||
.map_err(|e| anyhow::anyhow!("Failed to run cargo test: {}", e))
|
||||
}
|
||||
@@ -1,77 +1,21 @@
|
||||
//! Winch CLI tool, meant mostly for testing purposes.
|
||||
//!
|
||||
//! Reads Wasm in binary/text format and compiles them
|
||||
//! to any of the supported architectures using Winch.
|
||||
mod compile;
|
||||
mod filetests;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use cranelift_codegen::settings;
|
||||
use std::{fs, path::PathBuf, str::FromStr};
|
||||
use target_lexicon::Triple;
|
||||
use wasmtime_environ::{
|
||||
wasmparser::{types::Types, Parser as WasmParser, Validator},
|
||||
DefinedFuncIndex, FunctionBodyData, Module, ModuleEnvironment, Tunables,
|
||||
};
|
||||
use winch_codegen::{lookup, TargetIsa};
|
||||
|
||||
mod disasm;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Options {
|
||||
/// The input file.
|
||||
input: PathBuf,
|
||||
|
||||
/// The target architecture.
|
||||
#[clap(long = "target")]
|
||||
target: String,
|
||||
/// Winch compilation and testing tool.
|
||||
#[derive(Parser)]
|
||||
enum Commands {
|
||||
/// Compile a Wasm module to the specified target architecture.
|
||||
Compile(compile::Options),
|
||||
/// Run the filetests.
|
||||
Test(filetests::Options),
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let opt = Options::from_args();
|
||||
let bytes = fs::read(&opt.input)
|
||||
.with_context(|| format!("Failed to read input file {}", opt.input.display()))?;
|
||||
let bytes = wat::parse_bytes(&bytes)?;
|
||||
let triple = Triple::from_str(&opt.target)?;
|
||||
let shared_flags = settings::Flags::new(settings::builder());
|
||||
let isa_builder = lookup(triple)?;
|
||||
let isa = isa_builder.build(shared_flags)?;
|
||||
let mut validator = Validator::new();
|
||||
let parser = WasmParser::new(0);
|
||||
let mut types = Default::default();
|
||||
let tunables = Tunables::default();
|
||||
let mut translation = ModuleEnvironment::new(&tunables, &mut validator, &mut types)
|
||||
.translate(parser, &bytes)
|
||||
.context("Failed to translate WebAssembly module")?;
|
||||
let _ = types.finish();
|
||||
|
||||
let body_inputs = std::mem::take(&mut translation.function_body_inputs);
|
||||
let module = &translation.module;
|
||||
let types = translation.get_types();
|
||||
|
||||
body_inputs
|
||||
.into_iter()
|
||||
.try_for_each(|func| compile(&*isa, module, types, func))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile(
|
||||
isa: &dyn TargetIsa,
|
||||
module: &Module,
|
||||
types: &Types,
|
||||
f: (DefinedFuncIndex, FunctionBodyData<'_>),
|
||||
) -> Result<()> {
|
||||
let index = module.func_index(f.0);
|
||||
let sig = types
|
||||
.func_type_at(index.as_u32())
|
||||
.expect(&format!("function type at index {:?}", index.as_u32()));
|
||||
let FunctionBodyData { body, validator } = f.1;
|
||||
let validator = validator.into_validator(Default::default());
|
||||
let buffer = isa
|
||||
.compile_function(&sig, &body, validator)
|
||||
.expect("Couldn't compile function");
|
||||
|
||||
disasm::print(buffer.data(), isa)?;
|
||||
|
||||
Ok(())
|
||||
match Commands::parse() {
|
||||
Commands::Compile(c) => compile::run(&c),
|
||||
Commands::Test(t) => filetests::run(&t),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user