Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ jobs:
else
pr=${{ github.event.number }}
gh pr view $pr --json commits | tee commits.log
gh pr diff $pr --name-only | tee names.log
gh pr diff $pr --name-only | tee names.log || echo "failed to get files"
if [ "${{ github.base_ref }}" != "main" ]; then
run_full=true
elif grep -q 'prtest:full' commits.log; then
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ cranelift-reader = { workspace = true }
toml = { workspace = true }
similar = { workspace = true }
libtest-mimic = "0.7.0"
capstone = { workspace = true }
object = { workspace = true }

[target.'cfg(windows)'.dev-dependencies]
windows-sys = { workspace = true, features = ["Win32_System_Memory"] }
Expand Down
209 changes: 131 additions & 78 deletions tests/disas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,33 +98,10 @@ fn find_tests(path: &Path, dst: &mut Vec<PathBuf>) -> Result<()> {

fn run_test(path: &Path) -> Result<()> {
let mut test = Test::new(path)?;
let clifs = test.generate_clif()?;
let output = test.compile()?;
let isa = test.build_target_isa()?;

// Parse the text format CLIF which is emitted by Wasmtime back into
// in-memory data structures.
let mut functions = clifs
.iter()
.map(|clif| {
let mut funcs = cranelift_reader::parse_functions(clif)?;
if funcs.len() != 1 {
bail!("expected one function per clif");
}
Ok(funcs.remove(0))
})
.collect::<Result<Vec<_>>>()?;
functions.sort_by_key(|f| match f.name {
UserFuncName::User(UserExternalName { namespace, index }) => (namespace, index),
UserFuncName::Testcase(_) => unreachable!(),
});

run_functions(
&test.path,
&test.contents,
&*isa,
test.config.test,
&functions,
)?;
assert_output(&test.path, &test.contents, &*isa, test.config.test, output)?;

Ok(())
}
Expand All @@ -151,6 +128,19 @@ struct Test {
config: TestConfig,
}

/// Which kind of test is being performed.
#[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum TestKind {
/// Test the CLIF output, raw from translation.
#[default]
Clif,
/// Compile output to machine code.
Compile,
/// Test the CLIF output, optimized.
Optimize,
}

impl Test {
/// Parse the contents of `path` looking for directive-based comments
/// starting with `;;!` near the top of the file.
Expand Down Expand Up @@ -194,33 +184,60 @@ impl Test {
}

/// Generates CLIF for all the wasm functions in this test.
fn generate_clif(&mut self) -> Result<Vec<String>> {
fn compile(&mut self) -> Result<CompileOutput> {
// Use wasmtime::Config with its `emit_clif` option to get Wasmtime's
// code generator to jettison CLIF out the back.
let tempdir = TempDir::new().context("failed to make a tempdir")?;
let mut config = self.opts.config(Some(&self.config.target))?;
config.emit_clif(tempdir.path());
match self.config.test {
TestKind::Clif | TestKind::Optimize => {
config.emit_clif(tempdir.path());
}
TestKind::Compile => {}
}
let engine = Engine::new(&config).context("failed to create engine")?;
let module = wat::parse_file(&self.path)?;
engine
let elf = engine
.precompile_module(&module)
.context("failed to compile module")?;

// Read all `*.clif` files from the clif directory that the compilation
// process just emitted.
let mut clifs = Vec::new();
for entry in tempdir
.path()
.read_dir()
.context("failed to read tempdir")?
{
let entry = entry.context("failed to iterate over tempdir")?;
let path = entry.path();
let clif = std::fs::read_to_string(&path)
.with_context(|| format!("failed to read clif file {path:?}"))?;
clifs.push(clif);
match self.config.test {
TestKind::Clif | TestKind::Optimize => {
// Read all `*.clif` files from the clif directory that the
// compilation process just emitted.
let mut clifs = Vec::new();
for entry in tempdir
.path()
.read_dir()
.context("failed to read tempdir")?
{
let entry = entry.context("failed to iterate over tempdir")?;
let path = entry.path();
let clif = std::fs::read_to_string(&path)
.with_context(|| format!("failed to read clif file {path:?}"))?;
clifs.push(clif);
}

// Parse the text format CLIF which is emitted by Wasmtime back
// into in-memory data structures.
let mut functions = clifs
.iter()
.map(|clif| {
let mut funcs = cranelift_reader::parse_functions(clif)?;
if funcs.len() != 1 {
bail!("expected one function per clif");
}
Ok(funcs.remove(0))
})
.collect::<Result<Vec<_>>>()?;
functions.sort_by_key(|f| match f.name {
UserFuncName::User(UserExternalName { namespace, index }) => (namespace, index),
UserFuncName::Testcase(_) => unreachable!(),
});
Ok(CompileOutput::Clif(functions))
}
TestKind::Compile => Ok(CompileOutput::Elf(elf)),
}
Ok(clifs)
}

/// Use the test configuration present with CLI flags to build a
Expand Down Expand Up @@ -256,56 +273,92 @@ impl Test {
}
}

/// Which kind of test is being performed.
#[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TestKind {
/// Test the CLIF output, raw from translation.
#[default]
Clif,
/// Compile output to machine code.
Compile,
/// Test the CLIF output, optimized.
Optimize,
enum CompileOutput {
Clif(Vec<Function>),
Elf(Vec<u8>),
}

/// Assert that `wat` contains the test expectations necessary for `funcs`.
fn run_functions(
fn assert_output(
path: &Path,
wat: &str,
isa: &dyn TargetIsa,
kind: TestKind,
funcs: &[Function],
output: CompileOutput,
) -> Result<()> {
let mut actual = String::new();
for func in funcs {
match kind {
TestKind::Compile => {
let mut ctx = cranelift_codegen::Context::for_function(func.clone());
ctx.set_disasm(true);
let code = ctx
.compile(isa, &mut Default::default())
.map_err(|e| codegen_error_to_anyhow_error(&e.func, e.inner))?;
writeln!(&mut actual, "function {}:", func.name).unwrap();
writeln!(&mut actual, "{}", code.vcode.as_ref().unwrap()).unwrap();
}
TestKind::Optimize => {
let mut ctx = cranelift_codegen::Context::for_function(func.clone());
ctx.optimize(isa, &mut Default::default())
.map_err(|e| codegen_error_to_anyhow_error(&ctx.func, e))?;
ctx.func.dfg.resolve_all_aliases();
writeln!(&mut actual, "{}", ctx.func.display()).unwrap();
}
TestKind::Clif => {
let mut func = func.clone();
func.dfg.resolve_all_aliases();
writeln!(&mut actual, "{}", func.display()).unwrap();
match output {
CompileOutput::Clif(funcs) => {
for mut func in funcs {
match kind {
TestKind::Compile => unreachable!(),
TestKind::Optimize => {
let mut ctx = cranelift_codegen::Context::for_function(func.clone());
ctx.optimize(isa, &mut Default::default())
.map_err(|e| codegen_error_to_anyhow_error(&ctx.func, e))?;
ctx.func.dfg.resolve_all_aliases();
writeln!(&mut actual, "{}", ctx.func.display()).unwrap();
}
TestKind::Clif => {
func.dfg.resolve_all_aliases();
writeln!(&mut actual, "{}", func.display()).unwrap();
}
}
}
}
CompileOutput::Elf(bytes) => {
let disas = isa.to_capstone()?;
disas_elf(&disas, &mut actual, &bytes)?;
}
}
let actual = actual.trim();
log::debug!("=== actual ===\n{actual}");
assert_or_bless_output(path, wat, actual)
}

fn disas_elf(disas: &capstone::Capstone, result: &mut String, elf: &[u8]) -> Result<()> {
use object::{Endianness, Object, ObjectSection, ObjectSymbol};
let elf = object::read::elf::ElfFile64::<Endianness>::parse(elf)?;
let text = elf.section_by_name(".text").unwrap();
let text = text.data()?;
let mut first = true;
for sym in elf.symbols() {
let name = match sym.name() {
Ok(name) => name,
Err(_) => continue,
};
if !name.contains("wasm") || !name.contains("function") {
continue;
}

let bytes = &text[sym.address() as usize..][..sym.size() as usize];

if first {
first = false;
} else {
result.push_str("\n");
}
writeln!(result, "{name}:")?;
for inst in disas.disasm_all(bytes, sym.address())?.iter() {
write!(result, "{:>4x}: ", inst.address())?;

match (inst.mnemonic(), inst.op_str()) {
(Some(i), Some(o)) => {
if o.is_empty() {
writeln!(result, "{i}")?;
} else {
writeln!(result, "{i:7} {o}")?;
}
}
(Some(i), None) => writeln!(result, "{i}")?,
_ => unreachable!(),
}
}
}
Ok(())
}

fn assert_or_bless_output(path: &Path, wat: &str, actual: &str) -> Result<()> {
log::debug!("=== actual ===\n{actual}");
// The test's expectation is the final comment.
let mut expected_lines: Vec<_> = wat
.lines()
Expand Down
88 changes: 44 additions & 44 deletions tests/disas/aarch64-relaxed-simd.wat
Original file line number Diff line number Diff line change
Expand Up @@ -36,52 +36,52 @@
)
)

;; function u0:0:
;; block0:
;; b label1
;; block1:
;; fcvtzs v0.4s, v0.4s
;; ret
;; wasm[0]::function[0]:
;; 0: stp x29, x30, [sp, #-0x10]!
;; 4: mov x29, sp
;; 8: fcvtzs v0.4s, v0.4s
;; c: ldp x29, x30, [sp], #0x10
;; 10: ret
;;
;; function u0:1:
;; block0:
;; b label1
;; block1:
;; fcvtzu v0.4s, v0.4s
;; ret
;; wasm[0]::function[1]:
;; 20: stp x29, x30, [sp, #-0x10]!
;; 24: mov x29, sp
;; 28: fcvtzu v0.4s, v0.4s
;; 2c: ldp x29, x30, [sp], #0x10
;; 30: ret
;;
;; function u0:2:
;; block0:
;; b label1
;; block1:
;; fcvtzs v6.2d, v0.2d
;; sqxtn v0.2s, v6.2d
;; ret
;; wasm[0]::function[2]:
;; 40: stp x29, x30, [sp, #-0x10]!
;; 44: mov x29, sp
;; 48: fcvtzs v6.2d, v0.2d
;; 4c: sqxtn v0.2s, v6.2d
;; 50: ldp x29, x30, [sp], #0x10
;; 54: ret
;;
;; function u0:3:
;; block0:
;; b label1
;; block1:
;; fcvtzu v6.2d, v0.2d
;; uqxtn v0.2s, v6.2d
;; ret
;; wasm[0]::function[3]:
;; 60: stp x29, x30, [sp, #-0x10]!
;; 64: mov x29, sp
;; 68: fcvtzu v6.2d, v0.2d
;; 6c: uqxtn v0.2s, v6.2d
;; 70: ldp x29, x30, [sp], #0x10
;; 74: ret
;;
;; function u0:4:
;; block0:
;; b label1
;; block1:
;; smull v16.8h, v0.8b, v1.8b
;; smull2 v17.8h, v0.16b, v1.16b
;; addp v0.8h, v16.8h, v17.8h
;; ret
;; wasm[0]::function[4]:
;; 80: stp x29, x30, [sp, #-0x10]!
;; 84: mov x29, sp
;; 88: smull v16.8h, v0.8b, v1.8b
;; 8c: smull2 v17.8h, v0.16b, v1.16b
;; 90: addp v0.8h, v16.8h, v17.8h
;; 94: ldp x29, x30, [sp], #0x10
;; 98: ret
;;
;; function u0:5:
;; block0:
;; b label1
;; block1:
;; smull v19.8h, v0.8b, v1.8b
;; smull2 v20.8h, v0.16b, v1.16b
;; addp v19.8h, v19.8h, v20.8h
;; saddlp v19.4s, v19.8h
;; add v0.4s, v19.4s, v2.4s
;; ret
;; wasm[0]::function[5]:
;; a0: stp x29, x30, [sp, #-0x10]!
;; a4: mov x29, sp
;; a8: smull v19.8h, v0.8b, v1.8b
;; ac: smull2 v20.8h, v0.16b, v1.16b
;; b0: addp v19.8h, v19.8h, v20.8h
;; b4: saddlp v19.4s, v19.8h
;; b8: add v0.4s, v19.4s, v2.4s
;; bc: ldp x29, x30, [sp], #0x10
;; c0: ret
Loading