diff --git a/lib/cretonne/src/binemit/mod.rs b/lib/cretonne/src/binemit/mod.rs index 9721586033..33cce8d2b0 100644 --- a/lib/cretonne/src/binemit/mod.rs +++ b/lib/cretonne/src/binemit/mod.rs @@ -3,6 +3,10 @@ //! The `binemit` module contains code for translating Cretonne's intermediate representation into //! binary machine code. +mod relaxation; + +pub use self::relaxation::relax_branches; + use ir::{Ebb, FuncRef, JumpTable, Function, Inst}; /// Offset in bytes from the beginning of the function. diff --git a/lib/cretonne/src/binemit/relaxation.rs b/lib/cretonne/src/binemit/relaxation.rs new file mode 100644 index 0000000000..ef3bfdb257 --- /dev/null +++ b/lib/cretonne/src/binemit/relaxation.rs @@ -0,0 +1,110 @@ +//! Branch relaxation and offset computation. +//! +//! # EBB header offsets +//! +//! Before we can generate binary machine code for branch instructions, we need to know the final +//! offsets of all the EBB headers in the function. This information is encoded in the +//! `func.offsets` table. +//! +//! # Branch relaxation +//! +//! Branch relaxation is the process of ensuring that all branches in the function have enough +//! range to encode their destination. It is common to have multiple branch encodings in an ISA. +//! For example, Intel branches can have either an 8-bit or a 32-bit displacement. +//! +//! On RISC architectures, it can happen that conditional branches have a shorter range than +//! unconditional branches: +//! +//! ```cton +//! brz v1, ebb17 +//! ``` +//! +//! can be transformed into: +//! +//! ```cton +//! brnz v1, ebb23 +//! jump ebb17 +//! ebb23: +//! ``` + +use binemit::CodeOffset; +use entity_map::EntityMap; +use ir::{Function, DataFlowGraph, Cursor, Inst}; +use isa::{TargetIsa, EncInfo, Encoding}; + +/// 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) { + let encinfo = isa.encoding_info(); + + // Clear all offsets so we can recognize EBBs that haven't been visited yet. + func.offsets.clear(); + func.offsets.resize(func.dfg.num_ebbs()); + + // The relaxation algorithm iterates to convergence. + let mut go_again = true; + while go_again { + go_again = false; + + // 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. + if func.offsets[ebb] != offset { + assert!(func.offsets[ebb] < offset, + "Code shrinking during relaxation"); + func.offsets[ebb] = offset; + go_again = true; + } + + while let Some(inst) = pos.next_inst() { + let enc = func.encodings.get(inst).cloned().unwrap_or_default(); + let size = encinfo.bytes(enc); + + // See if this might be a branch that is out of range. + if let Some(range) = encinfo.branch_range(enc) { + if let Some(dest) = func.dfg[inst].branch_destination() { + let dest_offset = func.offsets[dest]; + if !range.contains(offset, dest_offset) { + // This is an out-of-range branch. + // Relax it unless the destination offset has not been computed yet. + if dest_offset != 0 || Some(dest) == pos.layout.entry_block() { + offset += relax_branch(&mut func.dfg, + &mut func.encodings, + &encinfo, + &mut pos, + offset, + dest_offset); + continue; + } + } + } + } + + offset += size; + } + } + } +} + +/// Relax the branch instruction at `pos` so it can cover the range `offset - dest_offset`. +/// +/// Return the size of the replacement instructions up to and including the location where `pos` is +/// left. +fn relax_branch(dfg: &mut DataFlowGraph, + encodings: &mut EntityMap, + encinfo: &EncInfo, + pos: &mut Cursor, + offset: CodeOffset, + dest_offset: CodeOffset) + -> CodeOffset { + let inst = pos.current_inst().unwrap(); + dbg!("Relaxing [{}] {} for {:#x}-{:#x} range", + encinfo.display(encodings[inst]), + dfg.display_inst(inst), + offset, + dest_offset); + unimplemented!(); +} diff --git a/lib/cretonne/src/ir/function.rs b/lib/cretonne/src/ir/function.rs index 1abf9c4b35..177ca0b5f1 100644 --- a/lib/cretonne/src/ir/function.rs +++ b/lib/cretonne/src/ir/function.rs @@ -3,11 +3,12 @@ //! The `Function` struct defined in this module owns all of its extended basic blocks and //! instructions. -use std::fmt::{self, Display, Debug, Formatter}; -use ir::{FunctionName, Signature, Value, Inst, StackSlot, StackSlotData, JumpTable, JumpTableData, - ValueLoc, DataFlowGraph, Layout}; -use isa::{TargetIsa, Encoding}; +use binemit::CodeOffset; use entity_map::{EntityMap, PrimaryEntityData}; +use ir::{FunctionName, Signature, Value, Inst, Ebb, StackSlot, StackSlotData, JumpTable, + JumpTableData, ValueLoc, DataFlowGraph, Layout}; +use isa::{TargetIsa, Encoding}; +use std::fmt::{self, Display, Debug, Formatter}; use write::write_function; /// A function. @@ -40,6 +41,13 @@ pub struct Function { /// Location assigned to every value. pub locations: EntityMap, + + /// Code offsets of the EBB headers. + /// + /// This information is only transiently available after the `binemit::relax_branches` function + /// computes it, and it can easily be recomputed by calling that function. It is not included + /// in the textual IL format. + pub offsets: EntityMap, } impl PrimaryEntityData for StackSlotData {} @@ -57,6 +65,7 @@ impl Function { layout: Layout::new(), encodings: EntityMap::new(), locations: EntityMap::new(), + offsets: EntityMap::new(), } } diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 1c292f473a..9816ccbc91 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -324,6 +324,19 @@ impl InstructionData { } } + /// Get the single destination of this branch instruction, if it is a single destination + /// branch or jump. + /// + /// Multi-destination branches like `br_table` return `None`. + pub fn branch_destination(&self) -> Option { + match self { + &InstructionData::Jump { destination, .. } => Some(destination), + &InstructionData::Branch { destination, .. } => Some(destination), + &InstructionData::BranchIcmp { destination, .. } => Some(destination), + _ => None, + } + } + /// Return information about a call instruction. /// /// Any instruction that can call another function reveals its call signature here. diff --git a/lib/cretonne/src/lib.rs b/lib/cretonne/src/lib.rs index b4c163b14d..1c983c74e6 100644 --- a/lib/cretonne/src/lib.rs +++ b/lib/cretonne/src/lib.rs @@ -10,6 +10,9 @@ pub use write::write_function; /// Version number of the cretonne crate. pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); +#[macro_use] +pub mod dbg; + pub mod binemit; pub mod flowgraph; pub mod dominator_tree; @@ -22,9 +25,6 @@ pub mod settings; pub mod sparse_map; pub mod verifier; -#[macro_use] -pub mod dbg; - mod abi; mod constant_hash; mod context; diff --git a/src/filetest/binemit.rs b/src/filetest/binemit.rs index 50949811ec..eee96e6ee5 100644 --- a/src/filetest/binemit.rs +++ b/src/filetest/binemit.rs @@ -4,6 +4,7 @@ //! functions and compares the results to the expected output. use std::borrow::Cow; +use std::collections::HashMap; use std::fmt::Write; use cretonne::binemit; use cretonne::ir; @@ -97,54 +98,99 @@ impl SubTest for TestBinEmit { fn run(&self, func: Cow, context: &Context) -> Result<()> { let isa = context.isa.expect("binemit needs an ISA"); + let encinfo = isa.encoding_info(); // 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 mut func = func.into_owned(); - let mut sink = TextSink::new(isa); - 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)) - } - }; - - // Compute an encoding for `inst` if one wasn't provided. + // Give an encoding to any instruction that doesn't already have one. + for ebb in func.layout.ebbs() { + for inst in func.layout.ebb_insts(ebb) { if !func.encodings .get(inst) .map(|e| e.is_legal()) .unwrap_or(false) { - match isa.encode(&func.dfg, &func.dfg[inst]) { - Ok(enc) => *func.encodings.ensure(inst) = enc, - Err(_) => { - return Err(format!("{} can't be encoded: {}", - inst, - func.dfg.display_inst(inst))) - } + if let Ok(enc) = isa.encode(&func.dfg, &func.dfg[inst]) { + *func.encodings.ensure(inst) = enc; } } - - 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(()) + // Relax branches and compute EBB offsets based on the encodings. + binemit::relax_branches(&mut func, isa); + + // Collect all of the 'bin:' directives on instructions. + let mut bins = HashMap::new(); + for comment in &context.details.comments { + if let Some(want) = match_directive(comment.text, "bin:") { + match comment.entity { + AnyEntity::Inst(inst) => { + if let Some(prev) = bins.insert(inst, want) { + return Err(format!("multiple 'bin:' directives on {}: '{}' and '{}'", + func.dfg.display_inst(inst), + prev, + want)); + } + } + _ => { + return Err(format!("'bin:' directive on non-inst {}: {}", + comment.entity, + comment.text)) + } + } + } } + if bins.is_empty() { + return Err("No 'bin:' directives found".to_string()); + } + + // Now emit all instructions. + let mut sink = TextSink::new(isa); + for ebb in func.layout.ebbs() { + // Correct header offsets should have been computed by `relax_branches()`. + assert_eq!(sink.offset, + func.offsets[ebb], + "Inconsistent {} header offset", + ebb); + for inst in func.layout.ebb_insts(ebb) { + sink.text.clear(); + let enc = func.encodings.get(inst).cloned().unwrap_or_default(); + + // Send legal encodings into the emitter. + if enc.is_legal() { + let before = sink.offset; + isa.emit_inst(&func, inst, &mut sink); + let emitted = sink.offset - before; + // Verify the encoding recipe sizes against the ISAs emit_inst implementation. + assert_eq!(emitted, + encinfo.bytes(enc), + "Inconsistent size for [{}] {}", + encinfo.display(enc), + func.dfg.display_inst(inst)); + } + + // Check against bin: directives. + if let Some(want) = bins.remove(&inst) { + if !enc.is_legal() { + return Err(format!("{} can't be encoded: {}", + inst, + func.dfg.display_inst(inst))); + } + 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)); + } + } + } + } + + Ok(()) } }