From 36eb39a1f8a5f6ceb756e285b70ba0c01b36c371 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 28 Mar 2017 15:43:41 -0700 Subject: [PATCH] Add a binemit test command. This makes it possible to write file tests that verify the binary encoding of machine code. --- docs/testing.rst | 22 ++++++ filetests/isa/riscv/binary32.cton | 13 ++++ src/filetest/binemit.rs | 107 ++++++++++++++++++++++++++++++ src/filetest/mod.rs | 2 + 4 files changed, 144 insertions(+) create mode 100644 filetests/isa/riscv/binary32.cton create mode 100644 src/filetest/binemit.rs diff --git a/docs/testing.rst b/docs/testing.rst index db027f8fbe..9a59f2c7be 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -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. 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 + } diff --git a/filetests/isa/riscv/binary32.cton b/filetests/isa/riscv/binary32.cton new file mode 100644 index 0000000000..0d2805681a --- /dev/null +++ b/filetests/isa/riscv/binary32.cton @@ -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 +} diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs new file mode 100644 index 0000000000..2c6a725431 --- /dev/null +++ b/src/filetest/binemit.rs @@ -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> { + 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 { + Cow::from("binemit") + } + + fn is_mutating(&self) -> bool { + false + } + + fn needs_isa(&self) -> bool { + true + } + + fn run(&self, func: Cow, 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(()) + } + } +} diff --git a/src/filetest/mod.rs b/src/filetest/mod.rs index 41b89948a1..7a379ec0fe 100644 --- a/src/filetest/mod.rs +++ b/src/filetest/mod.rs @@ -13,6 +13,7 @@ use filetest::runner::TestRunner; pub mod subtest; +mod binemit; mod concurrent; mod domtree; mod legalizer; @@ -60,6 +61,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::Result> { "verifier" => verifier::subtest(parsed), "legalizer" => legalizer::subtest(parsed), "regalloc" => regalloc::subtest(parsed), + "binemit" => binemit::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } }