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:
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
13
filetests/isa/riscv/binary32.cton
Normal file
13
filetests/isa/riscv/binary32.cton
Normal 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
107
src/filetest/binemit.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user