Add a branch relaxation pass for #72.
Compute exact EBB header offsets and check that branches are in range. Not implemented yet: Relax branches that are not in range. Invoke the relax_branches() pass from the 'test binemit' file tests so they can verify the proper encoding of branch instructions too.
This commit is contained in:
@@ -3,6 +3,10 @@
|
|||||||
//! The `binemit` module contains code for translating Cretonne's intermediate representation into
|
//! The `binemit` module contains code for translating Cretonne's intermediate representation into
|
||||||
//! binary machine code.
|
//! binary machine code.
|
||||||
|
|
||||||
|
mod relaxation;
|
||||||
|
|
||||||
|
pub use self::relaxation::relax_branches;
|
||||||
|
|
||||||
use ir::{Ebb, FuncRef, JumpTable, Function, Inst};
|
use ir::{Ebb, FuncRef, JumpTable, Function, Inst};
|
||||||
|
|
||||||
/// Offset in bytes from the beginning of the function.
|
/// Offset in bytes from the beginning of the function.
|
||||||
|
|||||||
110
lib/cretonne/src/binemit/relaxation.rs
Normal file
110
lib/cretonne/src/binemit/relaxation.rs
Normal file
@@ -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<Inst, Encoding>,
|
||||||
|
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!();
|
||||||
|
}
|
||||||
@@ -3,11 +3,12 @@
|
|||||||
//! The `Function` struct defined in this module owns all of its extended basic blocks and
|
//! The `Function` struct defined in this module owns all of its extended basic blocks and
|
||||||
//! instructions.
|
//! instructions.
|
||||||
|
|
||||||
use std::fmt::{self, Display, Debug, Formatter};
|
use binemit::CodeOffset;
|
||||||
use ir::{FunctionName, Signature, Value, Inst, StackSlot, StackSlotData, JumpTable, JumpTableData,
|
|
||||||
ValueLoc, DataFlowGraph, Layout};
|
|
||||||
use isa::{TargetIsa, Encoding};
|
|
||||||
use entity_map::{EntityMap, PrimaryEntityData};
|
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;
|
use write::write_function;
|
||||||
|
|
||||||
/// A function.
|
/// A function.
|
||||||
@@ -40,6 +41,13 @@ pub struct Function {
|
|||||||
|
|
||||||
/// Location assigned to every value.
|
/// Location assigned to every value.
|
||||||
pub locations: EntityMap<Value, ValueLoc>,
|
pub locations: EntityMap<Value, ValueLoc>,
|
||||||
|
|
||||||
|
/// 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<Ebb, CodeOffset>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrimaryEntityData for StackSlotData {}
|
impl PrimaryEntityData for StackSlotData {}
|
||||||
@@ -57,6 +65,7 @@ impl Function {
|
|||||||
layout: Layout::new(),
|
layout: Layout::new(),
|
||||||
encodings: EntityMap::new(),
|
encodings: EntityMap::new(),
|
||||||
locations: EntityMap::new(),
|
locations: EntityMap::new(),
|
||||||
|
offsets: EntityMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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<Ebb> {
|
||||||
|
match self {
|
||||||
|
&InstructionData::Jump { destination, .. } => Some(destination),
|
||||||
|
&InstructionData::Branch { destination, .. } => Some(destination),
|
||||||
|
&InstructionData::BranchIcmp { destination, .. } => Some(destination),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return information about a call instruction.
|
/// Return information about a call instruction.
|
||||||
///
|
///
|
||||||
/// Any instruction that can call another function reveals its call signature here.
|
/// Any instruction that can call another function reveals its call signature here.
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ pub use write::write_function;
|
|||||||
/// Version number of the cretonne crate.
|
/// Version number of the cretonne crate.
|
||||||
pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
pub mod dbg;
|
||||||
|
|
||||||
pub mod binemit;
|
pub mod binemit;
|
||||||
pub mod flowgraph;
|
pub mod flowgraph;
|
||||||
pub mod dominator_tree;
|
pub mod dominator_tree;
|
||||||
@@ -22,9 +25,6 @@ pub mod settings;
|
|||||||
pub mod sparse_map;
|
pub mod sparse_map;
|
||||||
pub mod verifier;
|
pub mod verifier;
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
pub mod dbg;
|
|
||||||
|
|
||||||
mod abi;
|
mod abi;
|
||||||
mod constant_hash;
|
mod constant_hash;
|
||||||
mod context;
|
mod context;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
//! functions and compares the results to the expected output.
|
//! functions and compares the results to the expected output.
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use cretonne::binemit;
|
use cretonne::binemit;
|
||||||
use cretonne::ir;
|
use cretonne::ir;
|
||||||
@@ -97,37 +98,85 @@ impl SubTest for TestBinEmit {
|
|||||||
|
|
||||||
fn run(&self, func: Cow<ir::Function>, context: &Context) -> Result<()> {
|
fn run(&self, func: Cow<ir::Function>, context: &Context) -> Result<()> {
|
||||||
let isa = context.isa.expect("binemit needs an ISA");
|
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
|
// 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...
|
// value locations. The current error reporting is just crashing...
|
||||||
let mut func = func.into_owned();
|
let mut func = func.into_owned();
|
||||||
let mut sink = TextSink::new(isa);
|
|
||||||
|
|
||||||
for comment in &context.details.comments {
|
// Give an encoding to any instruction that doesn't already have one.
|
||||||
if let Some(want) = match_directive(comment.text, "bin:") {
|
for ebb in func.layout.ebbs() {
|
||||||
let inst = match comment.entity {
|
for inst in func.layout.ebb_insts(ebb) {
|
||||||
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.
|
|
||||||
if !func.encodings
|
if !func.encodings
|
||||||
.get(inst)
|
.get(inst)
|
||||||
.map(|e| e.is_legal())
|
.map(|e| e.is_legal())
|
||||||
.unwrap_or(false) {
|
.unwrap_or(false) {
|
||||||
match isa.encode(&func.dfg, &func.dfg[inst]) {
|
if let Ok(enc) = isa.encode(&func.dfg, &func.dfg[inst]) {
|
||||||
Ok(enc) => *func.encodings.ensure(inst) = enc,
|
*func.encodings.ensure(inst) = enc;
|
||||||
Err(_) => {
|
}
|
||||||
return Err(format!("{} can't be encoded: {}",
|
|
||||||
inst,
|
|
||||||
func.dfg.display_inst(inst)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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();
|
sink.text.clear();
|
||||||
isa.emit_inst(&func, inst, &mut sink);
|
isa.emit_inst(&func, inst, &mut sink);
|
||||||
let have = sink.text.trim();
|
let have = sink.text.trim();
|
||||||
@@ -140,11 +189,8 @@ impl SubTest for TestBinEmit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if sink.text.is_empty() {
|
|
||||||
Err("No bin: directives found".to_string())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user