use crate::binemit::{CodeInfo, CodeOffset};
use crate::cursor::{Cursor, FuncCursor};
use crate::dominator_tree::DominatorTree;
use crate::flowgraph::ControlFlowGraph;
use crate::ir::{Block, Function, Inst, InstructionData, Opcode, Value, ValueList};
use crate::isa::{EncInfo, TargetIsa};
use crate::iterators::IteratorExtras;
use crate::regalloc::RegDiversions;
use crate::timing;
use crate::CodegenResult;
use core::convert::TryFrom;
use log::debug;
pub fn relax_branches(
func: &mut Function,
_cfg: &mut ControlFlowGraph,
_domtree: &mut DominatorTree,
isa: &dyn TargetIsa,
) -> CodegenResult<CodeInfo> {
let _tt = timing::relax_branches();
let encinfo = isa.encoding_info();
func.offsets.clear();
func.offsets.resize(func.dfg.num_blocks());
fold_redundant_jumps(func, _cfg, _domtree);
fallthroughs(func);
let mut offset = 0;
let mut divert = RegDiversions::new();
{
let mut cur = FuncCursor::new(func);
while let Some(block) = cur.next_block() {
divert.at_block(&cur.func.entry_diversions, block);
cur.func.offsets[block] = offset;
while let Some(inst) = cur.next_inst() {
divert.apply(&cur.func.dfg[inst]);
let enc = cur.func.encodings[inst];
offset += encinfo.byte_size(enc, inst, &divert, &cur.func);
}
}
}
let mut go_again = true;
while go_again {
go_again = false;
offset = 0;
let mut cur = FuncCursor::new(func);
while let Some(block) = cur.next_block() {
divert.at_block(&cur.func.entry_diversions, block);
if cur.func.offsets[block] != offset {
cur.func.offsets[block] = offset;
go_again = true;
}
while let Some(inst) = cur.next_inst() {
divert.apply(&cur.func.dfg[inst]);
let enc = cur.func.encodings[inst];
if let Some(range) = encinfo.branch_range(enc) {
if let Some(dest) = cur.func.dfg[inst].branch_destination() {
let dest_offset = cur.func.offsets[dest];
if !range.contains(offset, dest_offset) {
offset +=
relax_branch(&mut cur, &divert, offset, dest_offset, &encinfo, isa);
continue;
}
}
}
offset += encinfo.byte_size(enc, inst, &divert, &cur.func);
}
}
}
let code_size = offset;
let jumptables = offset;
for (jt, jt_data) in func.jump_tables.iter() {
func.jt_offsets[jt] = offset;
offset += jt_data.len() as u32 * 4;
}
let jumptables_size = offset - jumptables;
let rodata = offset;
for constant in func.dfg.constants.entries_mut() {
constant.set_offset(offset);
offset +=
u32::try_from(constant.len()).expect("Constants must have a length that fits in a u32")
}
let rodata_size = offset - rodata;
Ok(CodeInfo {
code_size,
jumptables_size,
rodata_size,
total_size: offset,
})
}
fn try_fold_redundant_jump(
func: &mut Function,
cfg: &mut ControlFlowGraph,
block: Block,
first_inst: Inst,
) -> bool {
let first_dest = match func.dfg[first_inst].branch_destination() {
Some(block) => block, None => {
return false; }
};
if func.dfg.num_block_params(first_dest) != 0 {
return false;
}
let second_inst = func.layout.first_inst(first_dest).expect("Instructions");
if func.dfg[second_inst].opcode() != Opcode::Jump {
return false;
}
let num_fixed = func.dfg[first_inst]
.opcode()
.constraints()
.num_fixed_value_arguments();
let (first_args, first_params) = func.dfg[first_inst]
.arguments(&func.dfg.value_lists)
.split_at(num_fixed);
let num_fixed = func.dfg[second_inst]
.opcode()
.constraints()
.num_fixed_value_arguments();
let (_, second_params) = func.dfg[second_inst]
.arguments(&func.dfg.value_lists)
.split_at(num_fixed);
let mut second_params = second_params.to_vec();
let block_params: &[Value] = func.dfg.block_params(first_dest);
debug_assert!(block_params.len() == first_params.len());
for value in second_params.iter_mut() {
if let Some((n, _)) = block_params.iter().enumerate().find(|(_, &p)| p == *value) {
*value = first_params[n];
}
}
let arguments_vec: alloc::vec::Vec<_> = first_args
.iter()
.chain(second_params.iter())
.copied()
.collect();
let value_list = ValueList::from_slice(&arguments_vec, &mut func.dfg.value_lists);
func.dfg[first_inst].take_value_list(); func.dfg[first_inst].put_value_list(value_list);
let second_dest = func.dfg[second_inst].branch_destination().expect("Dest");
func.change_branch_destination(first_inst, second_dest);
cfg.recompute_block(func, block);
if cfg.pred_iter(first_dest).count() == 0 {
while let Some(inst) = func.layout.first_inst(first_dest) {
func.layout.remove_inst(inst);
}
cfg.recompute_block(func, first_dest); func.layout.remove_block(first_dest); }
true
}
fn fold_redundant_jumps(
func: &mut Function,
cfg: &mut ControlFlowGraph,
domtree: &mut DominatorTree,
) {
let mut folded = false;
for &block in domtree.cfg_postorder() {
let first_inst = func
.layout
.last_inst(block)
.expect("Block has no terminator");
folded |= try_fold_redundant_jump(func, cfg, block, first_inst);
if let Some(prev_inst) = func.layout.prev_inst(first_inst) {
folded |= try_fold_redundant_jump(func, cfg, block, prev_inst);
}
}
if folded {
domtree.compute(func, cfg);
}
}
fn fallthroughs(func: &mut Function) {
for (block, succ) in func.layout.blocks().adjacent_pairs() {
let term = func
.layout
.last_inst(block)
.expect("block has no terminator.");
if let InstructionData::Jump {
ref mut opcode,
destination,
..
} = func.dfg[term]
{
match *opcode {
Opcode::Fallthrough => {
debug_assert_eq!(destination, succ, "Illegal fall-through in {}", block)
}
Opcode::Jump => {
if destination == succ {
*opcode = Opcode::Fallthrough;
func.encodings[term] = Default::default();
}
}
_ => {}
}
}
}
}
fn relax_branch(
cur: &mut FuncCursor,
divert: &RegDiversions,
offset: CodeOffset,
dest_offset: CodeOffset,
encinfo: &EncInfo,
isa: &dyn TargetIsa,
) -> CodeOffset {
let inst = cur.current_inst().unwrap();
debug!(
"Relaxing [{}] {} for {:#x}-{:#x} range",
encinfo.display(cur.func.encodings[inst]),
cur.func.dfg.display_inst(inst, isa),
offset,
dest_offset
);
let dfg = &cur.func.dfg;
let ctrl_type = dfg.ctrl_typevar(inst);
if let Some(enc) = isa
.legal_encodings(cur.func, &dfg[inst], ctrl_type)
.filter(|&enc| {
let range = encinfo.branch_range(enc).expect("Branch with no range");
if !range.contains(offset, dest_offset) {
debug!(" trying [{}]: out of range", encinfo.display(enc));
false
} else if encinfo.operand_constraints(enc)
!= encinfo.operand_constraints(cur.func.encodings[inst])
{
debug!(" trying [{}]: constraints differ", encinfo.display(enc));
false
} else {
debug!(" trying [{}]: OK", encinfo.display(enc));
true
}
})
.min_by_key(|&enc| encinfo.byte_size(enc, inst, &divert, &cur.func))
{
debug_assert!(enc != cur.func.encodings[inst]);
cur.func.encodings[inst] = enc;
return encinfo.byte_size(enc, inst, &divert, &cur.func);
}
panic!("No branch in range for {:#x}-{:#x}", offset, dest_offset);
}