Add a binemit test command.

This makes it possible to write file tests that verify the binary
encoding of machine code.
This commit is contained in:
Jakob Stoklund Olesen
2017-03-28 15:43:41 -07:00
parent ca2b1c79d7
commit 36eb39a1f8
4 changed files with 144 additions and 0 deletions

View File

@@ -314,3 +314,25 @@ Second, the register allocator is run on the function, inserting spill code and
assigning registers and stack slots to all values. assigning registers and stack slots to all values.
The resulting function is then run through filecheck. The resulting function is then run through filecheck.
`test binemit`
--------------
Test the emission of binary machine code.
The functions must contains instructions that are annotated with both encodings
and value locations (registers or stack slots). For instructions that are
annotated with a `bin:` directive, the emitted hexadecimal machine code for
that instruction is compared to the directive::
test binemit
isa riscv
function int32() {
ebb0:
[-,%x5] v1 = iconst.i32 1
[-,%x6] v2 = iconst.i32 2
[R#0c,%x7] v10 = iadd v1, v2 ; bin: 006283b3
[R#200c,%x8] v11 = isub v1, v2 ; bin: 40628433
return
}

View File

@@ -0,0 +1,13 @@
; Binary emission of 32-bit code.
test binemit
isa riscv
function int32() {
ebb0:
[-,%x5] v1 = iconst.i32 1
[-,%x6] v2 = iconst.i32 2
[R#0c,%x7] v10 = iadd v1, v2 ; bin: 006283b3
[R#200c,%x8] v11 = isub v1, v2 ; bin: 40628433
[R#10c] v12 = imul v1, v2
return
}

107
src/filetest/binemit.rs Normal file
View File

@@ -0,0 +1,107 @@
//! 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::{Borrow, Cow};
use std::fmt::Write;
use cretonne::binemit;
use cretonne::ir;
use cretonne::ir::entities::AnyEntity;
use cton_reader::TestCommand;
use filetest::subtest::{SubTest, Context, Result};
use utils::match_directive;
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 {
text: String,
}
impl binemit::CodeSink for TextSink {
fn put1(&mut self, x: u8) {
write!(self.text, "{:02x} ", x).unwrap();
}
fn put2(&mut self, x: u16) {
write!(self.text, "{:04x} ", x).unwrap();
}
fn put4(&mut self, x: u32) {
write!(self.text, "{:08x} ", x).unwrap();
}
fn put8(&mut self, x: u64) {
write!(self.text, "{:016x} ", x).unwrap();
}
fn reloc_func(&mut self, _: binemit::Reloc, _: ir::FuncRef) {
unimplemented!()
}
fn reloc_jt(&mut self, _: binemit::Reloc, _: ir::JumpTable) {
unimplemented!()
}
}
impl SubTest for TestBinEmit {
fn name(&self) -> Cow<str> {
Cow::from("binemit")
}
fn is_mutating(&self) -> bool {
false
}
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");
// 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 func = func.borrow();
let mut sink = TextSink { text: String::new() };
for comment in &context.details.comments {
if let Some(want) = match_directive(comment.text, "bin:") {
let inst = match comment.entity {
AnyEntity::Inst(inst) => inst,
_ => {
return Err(format!("annotation on non-inst {}: {}",
comment.entity,
comment.text))
}
};
sink.text.clear();
isa.emit_inst(&func, inst, &mut sink);
let have = sink.text.trim();
if have != want {
return Err(format!("Bad machine code for {}: {}\nWant: {}\nGot: {}",
inst,
func.dfg.display_inst(inst),
want,
have));
}
}
}
if sink.text.is_empty() {
Err("No bin: directives found".to_string())
} else {
Ok(())
}
}
}

View File

@@ -13,6 +13,7 @@ use filetest::runner::TestRunner;
pub mod subtest; pub mod subtest;
mod binemit;
mod concurrent; mod concurrent;
mod domtree; mod domtree;
mod legalizer; mod legalizer;
@@ -60,6 +61,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::Result<Box<subtest::SubTest>> {
"verifier" => verifier::subtest(parsed), "verifier" => verifier::subtest(parsed),
"legalizer" => legalizer::subtest(parsed), "legalizer" => legalizer::subtest(parsed),
"regalloc" => regalloc::subtest(parsed), "regalloc" => regalloc::subtest(parsed),
"binemit" => binemit::subtest(parsed),
_ => Err(format!("unknown test command '{}'", parsed.command)), _ => Err(format!("unknown test command '{}'", parsed.command)),
} }
} }