From 6cc729a69b06a887d8c37c6ea6f60f87baab4b5a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 11 Jul 2017 16:26:16 -0700 Subject: [PATCH] Add a Context::compile() function which runs all compiler passes. This is the main entry point to the code generator. It returns the computed size of the functions code. Also add a 'test compile' command which runs the whole code generation pipeline. --- lib/cretonne/src/binemit/relaxation.rs | 9 ++- lib/cretonne/src/context.rs | 27 ++++++- src/filetest/binemit.rs | 9 ++- src/filetest/compile.rs | 98 ++++++++++++++++++++++++++ src/filetest/mod.rs | 8 ++- 5 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 src/filetest/compile.rs diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs index d80d17d7bb..3420134d1b 100644 --- a/lib/cretonne/src/binemit/relaxation.rs +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -31,11 +31,12 @@ use binemit::CodeOffset; use ir::{Function, DataFlowGraph, Cursor, InstructionData, Opcode, InstEncodings}; use isa::{TargetIsa, EncInfo}; use iterators::IteratorExtras; +use result::CtonError; /// Relax branches and compute the final layout of EBB headers in `func`. /// /// Fill in the `func.offsets` table so the function is ready for binary emission. -pub fn relax_branches(func: &mut Function, isa: &TargetIsa) { +pub fn relax_branches(func: &mut Function, isa: &TargetIsa) -> Result { let encinfo = isa.encoding_info(); // Clear all offsets so we can recognize EBBs that haven't been visited yet. @@ -45,13 +46,15 @@ pub fn relax_branches(func: &mut Function, isa: &TargetIsa) { // Start by inserting fall through instructions. fallthroughs(func); + let mut offset = 0; + // The relaxation algorithm iterates to convergence. let mut go_again = true; while go_again { go_again = false; + offset = 0; // Visit all instructions in layout order - let mut offset = 0; let mut pos = Cursor::new(&mut func.layout); while let Some(ebb) = pos.next_ebb() { // Record the offset for `ebb` and make sure we iterate until offsets are stable. @@ -90,6 +93,8 @@ pub fn relax_branches(func: &mut Function, isa: &TargetIsa) { } } } + + Ok(offset) } /// Convert `jump` instructions to `fallthrough` instructions where possible and verify that any diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index 5e23ed3efc..a87e35cd32 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -9,6 +9,7 @@ //! contexts concurrently. Typically, you would have one context per compilation thread and only a //! single ISA instance. +use binemit::{CodeOffset, relax_branches}; use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; use ir::Function; @@ -16,7 +17,7 @@ use loop_analysis::LoopAnalysis; use isa::TargetIsa; use legalize_function; use regalloc; -use result::CtonResult; +use result::{CtonError, CtonResult}; use verifier; use simple_gvn::do_simple_gvn; use licm::do_licm; @@ -54,6 +55,22 @@ impl Context { } } + /// Compile the function. + /// + /// Run the function through all the passes necessary to generate code for the target ISA + /// represented by `isa`. This does not include the final step of emitting machine code into a + /// code sink. + /// + /// Returns the size of the function's code. + pub fn compile(&mut self, isa: &TargetIsa) -> Result { + self.flowgraph(); + self.verify_if(isa)?; + + self.legalize(isa)?; + self.regalloc(isa)?; + self.relax_branches(isa) + } + /// Run the verifier on the function. /// /// Also check that the dominator tree and control flow graph are consistent with the function. @@ -107,4 +124,12 @@ impl Context { self.regalloc .run(isa, &mut self.func, &self.cfg, &self.domtree) } + + /// Run the branch relaxation pass and return the final code size. + pub fn relax_branches(&mut self, isa: &TargetIsa) -> Result { + let code_size = relax_branches(&mut self.func, isa)?; + self.verify_if(isa)?; + + Ok(code_size) + } } diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs index 15f5b0a982..d359cea794 100644 --- a/src/filetest/binemit.rs +++ b/src/filetest/binemit.rs @@ -12,7 +12,7 @@ use cretonne::ir::entities::AnyEntity; use cretonne::isa::TargetIsa; use cton_reader::TestCommand; use filetest::subtest::{SubTest, Context, Result}; -use utils::match_directive; +use utils::{match_directive, pretty_error}; struct TestBinEmit; @@ -117,7 +117,8 @@ impl SubTest for TestBinEmit { } // Relax branches and compute EBB offsets based on the encodings. - binemit::relax_branches(&mut func, isa); + let code_size = binemit::relax_branches(&mut func, isa) + .map_err(|e| pretty_error(&func, context.isa, e))?; // Collect all of the 'bin:' directives on instructions. let mut bins = HashMap::new(); @@ -188,6 +189,10 @@ impl SubTest for TestBinEmit { } } + if sink.offset != code_size { + return Err(format!("Expected code size {}, got {}", code_size, sink.offset)); + } + Ok(()) } } diff --git a/src/filetest/compile.rs b/src/filetest/compile.rs new file mode 100644 index 0000000000..15db119efd --- /dev/null +++ b/src/filetest/compile.rs @@ -0,0 +1,98 @@ +//! Test command for testing the code generator pipeline +//! +//! The `compile` test command runs each function through the full code generator pipeline + +use cretonne::binemit; +use cretonne::ir; +use cretonne; +use cton_reader::TestCommand; +use filetest::subtest::{SubTest, Context, Result}; +use std::borrow::Cow; +use utils::pretty_error; + +struct TestCompile; + +pub fn subtest(parsed: &TestCommand) -> Result> { + assert_eq!(parsed.command, "compile"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestCompile)) + } +} + +impl SubTest for TestCompile { + fn name(&self) -> Cow { + Cow::from("compile") + } + + fn is_mutating(&self) -> bool { + true + } + + fn needs_isa(&self) -> bool { + true + } + + fn run(&self, func: Cow, context: &Context) -> Result<()> { + let isa = context.isa.expect("compile needs an ISA"); + + // Create a compilation context, and drop in the function. + let mut comp_ctx = cretonne::Context::new(); + comp_ctx.func = func.into_owned(); + + let code_size = comp_ctx + .compile(isa) + .map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; + + dbg!("Generated {} bytes of code:\n{}", + code_size, + comp_ctx.func.display(isa)); + + // Finally verify that the returned code size matches the emitted bytes. + let mut sink = SizeSink { offset: 0 }; + for ebb in comp_ctx.func.layout.ebbs() { + assert_eq!(sink.offset, comp_ctx.func.offsets[ebb]); + for inst in comp_ctx.func.layout.ebb_insts(ebb) { + isa.emit_inst(&comp_ctx.func, inst, &mut sink); + } + } + + if sink.offset != code_size { + return Err(format!("Expected code size {}, got {}", code_size, sink.offset)); + } + + Ok(()) + } +} + +// Code sink that simply counts bytes. +struct SizeSink { + offset: binemit::CodeOffset, +} + +impl binemit::CodeSink for SizeSink { + fn offset(&self) -> binemit::CodeOffset { + self.offset + } + + fn put1(&mut self, _: u8) { + self.offset += 1; + } + + fn put2(&mut self, _: u16) { + self.offset += 2; + } + + fn put4(&mut self, _: u32) { + self.offset += 4; + } + + fn put8(&mut self, _: u64) { + self.offset += 8; + } + + fn reloc_ebb(&mut self, _reloc: binemit::Reloc, _ebb: ir::Ebb) {} + fn reloc_func(&mut self, _reloc: binemit::Reloc, _fref: ir::FuncRef) {} + fn reloc_jt(&mut self, _reloc: binemit::Reloc, _jt: ir::JumpTable) {} +} diff --git a/src/filetest/mod.rs b/src/filetest/mod.rs index 9c03cc6d4a..2d2a9c6cac 100644 --- a/src/filetest/mod.rs +++ b/src/filetest/mod.rs @@ -14,6 +14,7 @@ use filetest::runner::TestRunner; pub mod subtest; mod binemit; +mod compile; mod concurrent; mod domtree; mod legalizer; @@ -57,15 +58,16 @@ pub fn run(verbose: bool, files: Vec) -> CommandResult { /// a `.cton` test file. fn new_subtest(parsed: &TestCommand) -> subtest::Result> { match parsed.command { + "binemit" => binemit::subtest(parsed), "cat" => cat::subtest(parsed), - "print-cfg" => print_cfg::subtest(parsed), + "compile" => compile::subtest(parsed), "domtree" => domtree::subtest(parsed), - "verifier" => verifier::subtest(parsed), "legalizer" => legalizer::subtest(parsed), "licm" => licm::subtest(parsed), + "print-cfg" => print_cfg::subtest(parsed), "regalloc" => regalloc::subtest(parsed), - "binemit" => binemit::subtest(parsed), "simple-gvn" => simple_gvn::subtest(parsed), + "verifier" => verifier::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } }