Fix missing modification of jump table in licm

This commit is contained in:
Y-Nak
2020-05-14 22:20:24 +09:00
committed by Yoshitomo Nakanishi
parent 2cec20aa57
commit 855a6374dd
5 changed files with 109 additions and 21 deletions

View File

@@ -7,8 +7,9 @@ use crate::binemit::CodeOffset;
use crate::entity::{PrimaryMap, SecondaryMap}; use crate::entity::{PrimaryMap, SecondaryMap};
use crate::ir; use crate::ir;
use crate::ir::{ use crate::ir::{
Block, ExtFuncData, FuncRef, GlobalValue, GlobalValueData, Heap, HeapData, Inst, JumpTable, instructions::BranchInfo, Block, ExtFuncData, FuncRef, GlobalValue, GlobalValueData, Heap,
JumpTableData, Opcode, SigRef, StackSlot, StackSlotData, Table, TableData, HeapData, Inst, InstructionData, JumpTable, JumpTableData, Opcode, SigRef, StackSlot,
StackSlotData, Table, TableData,
}; };
use crate::ir::{BlockOffsets, InstEncodings, SourceLocs, StackSlots, ValueLocations}; use crate::ir::{BlockOffsets, InstEncodings, SourceLocs, StackSlots, ValueLocations};
use crate::ir::{DataFlowGraph, ExternalName, Layout, Signature}; use crate::ir::{DataFlowGraph, ExternalName, Layout, Signature};
@@ -270,6 +271,8 @@ impl Function {
/// Changes the destination of a jump or branch instruction. /// Changes the destination of a jump or branch instruction.
/// Does nothing if called with a non-jump or non-branch instruction. /// Does nothing if called with a non-jump or non-branch instruction.
///
/// Note that this method ignores multi-destination branches like `br_table`.
pub fn change_branch_destination(&mut self, inst: Inst, new_dest: Block) { pub fn change_branch_destination(&mut self, inst: Inst, new_dest: Block) {
match self.dfg[inst].branch_destination_mut() { match self.dfg[inst].branch_destination_mut() {
None => (), None => (),
@@ -277,6 +280,43 @@ impl Function {
} }
} }
/// Rewrite the branch destination to `new_dest` if the destination matches `old_dest`.
/// Does nothing if called with a non-jump or non-branch instruction.
///
/// Unlike [change_branch_destination](Function::change_branch_destination), this method rewrite the destinations of
/// multi-destination branches like `br_table`.
pub fn rewrite_branch_destination(&mut self, inst: Inst, old_dest: Block, new_dest: Block) {
match self.dfg.analyze_branch(inst) {
BranchInfo::SingleDest(dest, ..) => {
if dest == old_dest {
self.change_branch_destination(inst, new_dest);
}
}
BranchInfo::Table(table, default_dest) => {
self.jump_tables[table].iter_mut().for_each(|entry| {
if *entry == old_dest {
*entry = new_dest;
}
});
if default_dest == Some(old_dest) {
match &mut self.dfg[inst] {
InstructionData::BranchTable { destination, .. } => {
*destination = new_dest;
}
_ => panic!(
"Unexpected instruction {} having default destination",
self.dfg.display_inst(inst, None)
),
}
}
}
BranchInfo::NotABranch => {}
}
}
/// Checks that the specified block can be encoded as a basic block. /// Checks that the specified block can be encoded as a basic block.
/// ///
/// On error, returns the first invalid instruction and an error message. /// On error, returns the first invalid instruction and an error message.

View File

@@ -277,7 +277,7 @@ impl InstructionData {
ref mut destination, ref mut destination,
.. ..
} => Some(destination), } => Some(destination),
Self::BranchTable { .. } => None, Self::BranchTable { .. } | Self::IndirectJump { .. } => None,
_ => { _ => {
debug_assert!(!self.opcode().is_branch()); debug_assert!(!self.opcode().is_branch());
None None

View File

@@ -61,8 +61,8 @@ pub fn do_licm(
domtree.compute(func, cfg); domtree.compute(func, cfg);
} }
// Insert a pre-header before the header, modifying the function layout and CFG to reflect it. /// Insert a pre-header before the header, modifying the function layout and CFG to reflect it.
// A jump instruction to the header is placed at the end of the pre-header. /// A jump instruction to the header is placed at the end of the pre-header.
fn create_pre_header( fn create_pre_header(
isa: &dyn TargetIsa, isa: &dyn TargetIsa,
header: Block, header: Block,
@@ -81,30 +81,31 @@ fn create_pre_header(
for typ in header_args_types { for typ in header_args_types {
pre_header_args_value.push(func.dfg.append_block_param(pre_header, typ), pool); pre_header_args_value.push(func.dfg.append_block_param(pre_header, typ), pool);
} }
for BlockPredecessor { for BlockPredecessor {
inst: last_inst, .. inst: last_inst, ..
} in cfg.pred_iter(header) } in cfg.pred_iter(header)
{ {
// We only follow normal edges (not the back edges) // We only follow normal edges (not the back edges)
if !domtree.dominates(header, last_inst, &func.layout) { if !domtree.dominates(header, last_inst, &func.layout) {
func.change_branch_destination(last_inst, pre_header); func.rewrite_branch_destination(last_inst, header, pre_header);
} }
} }
{
let mut pos = EncCursor::new(func, isa).at_top(header);
// Inserts the pre-header at the right place in the layout. // Inserts the pre-header at the right place in the layout.
let mut pos = EncCursor::new(func, isa).at_top(header);
pos.insert_block(pre_header); pos.insert_block(pre_header);
pos.next_inst(); pos.next_inst();
pos.ins().jump(header, pre_header_args_value.as_slice(pool)); pos.ins().jump(header, pre_header_args_value.as_slice(pool));
}
pre_header pre_header
} }
// Detects if a loop header has a natural pre-header. /// Detects if a loop header has a natural pre-header.
// ///
// A loop header has a pre-header if there is only one predecessor that the header doesn't /// A loop header has a pre-header if there is only one predecessor that the header doesn't
// dominate. /// dominate.
// Returns the pre-header Block and the instruction jumping to the header. /// Returns the pre-header Block and the instruction jumping to the header.
fn has_pre_header( fn has_pre_header(
layout: &Layout, layout: &Layout,
cfg: &ControlFlowGraph, cfg: &ControlFlowGraph,
@@ -176,9 +177,9 @@ fn is_loop_invariant(inst: Inst, dfg: &DataFlowGraph, loop_values: &FxHashSet<Va
true true
} }
// Traverses a loop in reverse post-order from a header block and identify loop-invariant /// Traverses a loop in reverse post-order from a header block and identify loop-invariant
// instructions. These loop-invariant instructions are then removed from the code and returned /// instructions. These loop-invariant instructions are then removed from the code and returned
// (in reverse post-order) for later use. /// (in reverse post-order) for later use.
fn remove_loop_invariant_instructions( fn remove_loop_invariant_instructions(
lp: Loop, lp: Loop,
func: &mut Function, func: &mut Function,

View File

@@ -0,0 +1,19 @@
test compile
set opt_level=speed_and_size
target x86_64
function %br_table_opt() {
jt0 = jump_table [block1, block2]
block0:
v0 = iconst.i32 1
br_table v0, block2, jt0
block1:
return
block2:
v1 = iconst.i32 1
jump block2
}

View File

@@ -0,0 +1,28 @@
test licm
target aarch64
function %rewrite_jump_table() {
jt0 = jump_table [block1, block2]
block0:
v0 = iconst.i64 1
v1 = jump_table_base.i64 jt0
v2 = jump_table_entry.i64 v0, v1, 4, jt0
v3 = iadd v1, v2
indirect_jump_table_br v3, jt0
block1:
return
block2:
v4 = bconst.b1 false
jump block2
}
; sameln: function
; nextln: jt0 = jump_table [block1, block3]
; check: block3:
; nextln: v4 = bconst.b1 false
; nextln: jump block2
; check: block2:
; nextln: jump block2