Skip to content

Commit

Permalink
[IRSim][IROutliner] Add support for outlining PHINodes with the rest …
Browse files Browse the repository at this point in the history
…of the region.

We use the same similarity scheme we used for branch instructions for phi nodes, and allow them to be outlined. There is not a lot of special handling needed for these phi nodes when outlining, as they simply act as outputs. The code extractor does not currently allow for non entry blocks within the extracted region to have predecessors, so there are not conflicts to handle with respect to predecessors no longer contained in the function.

Recommit of 515eec3

Reviewers: paquette

Differential Revision: https://reviews.llvm.org/D106997
  • Loading branch information
AndrewLitteken committed Jan 26, 2022
1 parent e50b217 commit e8f4e41
Show file tree
Hide file tree
Showing 14 changed files with 833 additions and 29 deletions.
17 changes: 15 additions & 2 deletions llvm/include/llvm/Analysis/IRSimilarityIdentifier.h
Expand Up @@ -214,6 +214,16 @@ struct IRInstructionData
/// function name as a differentiating parameter.
void setCalleeName(bool MatchByName = true);

/// For an IRInstructionData containing a PHINode, finds the
/// relative distances from the incoming basic block to the current block by
/// taking the difference of the number assigned to the current basic block
/// and the incoming basic block of the branch.
///
/// \param BasicBlockToInteger - The mapping of basic blocks to their location
/// in the module.
void
setPHIPredecessors(DenseMap<BasicBlock *, unsigned> &BasicBlockToInteger);

/// Hashes \p Value based on its opcode, types, and operand types.
/// Two IRInstructionData instances produce the same hash when they perform
/// the same operation.
Expand Down Expand Up @@ -497,8 +507,11 @@ struct IRInstructionMapper {
return Legal;
return Illegal;
}
// TODO: Determine a scheme to resolve when the labels are similar enough.
InstrType visitPHINode(PHINode &PN) { return Illegal; }
InstrType visitPHINode(PHINode &PN) {
if (EnableBranches)
return Legal;
return Illegal;
}
// TODO: Handle allocas.
InstrType visitAllocaInst(AllocaInst &AI) { return Illegal; }
// We exclude variable argument instructions since variable arguments
Expand Down
3 changes: 1 addition & 2 deletions llvm/include/llvm/Transforms/IPO/IROutliner.h
Expand Up @@ -342,8 +342,7 @@ class IROutliner {
bool visitBranchInst(BranchInst &BI) {
return EnableBranches;
}
// TODO: Determine a scheme to resolve when the labels are similar enough.
bool visitPHINode(PHINode &PN) { return false; }
bool visitPHINode(PHINode &PN) { return EnableBranches; }
// TODO: Handle allocas.
bool visitAllocaInst(AllocaInst &AI) { return false; }
// VAArg instructions are not allowed since this could cause difficulty when
Expand Down
37 changes: 37 additions & 0 deletions llvm/lib/Analysis/IRSimilarityIdentifier.cpp
Expand Up @@ -70,6 +70,12 @@ void IRInstructionData::initializeInstruction() {

OperVals.push_back(OI.get());
}

// We capture the incoming BasicBlocks as values as well as the incoming
// Values in order to check for structural similarity.
if (PHINode *PN = dyn_cast<PHINode>(Inst))
for (BasicBlock *BB : PN->blocks())
OperVals.push_back(BB);
}

IRInstructionData::IRInstructionData(IRInstructionDataList &IDList)
Expand Down Expand Up @@ -108,6 +114,34 @@ void IRInstructionData::setCalleeName(bool MatchByName) {
CalleeName = CI->getCalledFunction()->getName().str();
}

void IRInstructionData::setPHIPredecessors(
DenseMap<BasicBlock *, unsigned> &BasicBlockToInteger) {
assert(isa<PHINode>(Inst) && "Instruction must be phi node");

PHINode *PN = cast<PHINode>(Inst);
DenseMap<BasicBlock *, unsigned>::iterator BBNumIt;

BBNumIt = BasicBlockToInteger.find(PN->getParent());
assert(BBNumIt != BasicBlockToInteger.end() &&
"Could not find location for BasicBlock!");

int CurrentBlockNumber = static_cast<int>(BBNumIt->second);

// Convert the incoming blocks of the PHINode to an integer value, based on
// the relative distances between the current block and the incoming block.
for (unsigned Idx = 0; Idx < PN->getNumIncomingValues(); Idx++) {
BasicBlock *Incoming = PN->getIncomingBlock(Idx);
BBNumIt = BasicBlockToInteger.find(Incoming);
assert(BBNumIt != BasicBlockToInteger.end() &&
"Could not find number for BasicBlock!");
int OtherBlockNumber = static_cast<int>(BBNumIt->second);

int Relative = OtherBlockNumber - CurrentBlockNumber;
RelativeBlockLocations.push_back(Relative);
RelativeBlockLocations.push_back(Relative);
}
}

CmpInst::Predicate IRInstructionData::predicateForConsistency(CmpInst *CI) {
switch (CI->getPredicate()) {
case CmpInst::FCMP_OGT:
Expand Down Expand Up @@ -270,6 +304,9 @@ unsigned IRInstructionMapper::mapToLegalUnsigned(
if (isa<CallInst>(*It))
ID->setCalleeName(EnableMatchCallsByName);

if (isa<PHINode>(*It))
ID->setPHIPredecessors(BasicBlockToInteger);

// Add to the instruction list
bool WasInserted;
DenseMap<IRInstructionData *, unsigned, IRInstructionDataTraits>::iterator
Expand Down
110 changes: 98 additions & 12 deletions llvm/lib/Transforms/IPO/IROutliner.cpp
Expand Up @@ -185,6 +185,44 @@ Value *OutlinableRegion::findCorrespondingValueIn(const OutlinableRegion &Other,
return FoundValueOpt.getValueOr(nullptr);
}

/// Rewrite the BranchInsts in the incoming blocks to \p PHIBlock that are found
/// in \p Included to branch to BasicBlock \p Replace if they currently branch
/// to the BasicBlock \p Find. This is used to fix up the incoming basic blocks
/// when PHINodes are included in outlined regions.
///
/// \param PHIBlock - The BasicBlock containing the PHINodes that need to be
/// checked.
/// \param Find - The successor block to be replaced.
/// \param Replace - The new succesor block to branch to.
/// \param Included - The set of blocks about to be outlined.
static void replaceTargetsFromPHINode(BasicBlock *PHIBlock, BasicBlock *Find,
BasicBlock *Replace,
DenseSet<BasicBlock *> &Included) {
for (PHINode &PN : PHIBlock->phis()) {
for (unsigned Idx = 0, PNEnd = PN.getNumIncomingValues(); Idx != PNEnd;
++Idx) {
// Check if the incoming block is included in the set of blocks being
// outlined.
BasicBlock *Incoming = PN.getIncomingBlock(Idx);
if (!Included.contains(Incoming))
continue;

BranchInst *BI = dyn_cast<BranchInst>(Incoming->getTerminator());
assert(BI && "Not a branch instruction?");
// Look over the branching instructions into this block to see if we
// used to branch to Find in this outlined block.
for (unsigned Succ = 0, End = BI->getNumSuccessors(); Succ != End;
Succ++) {
// If we have found the block to replace, we do so here.
if (BI->getSuccessor(Succ) != Find)
continue;
BI->setSuccessor(Succ, Replace);
}
}
}
}


void OutlinableRegion::splitCandidate() {
assert(!CandidateSplit && "Candidate already split!");

Expand Down Expand Up @@ -215,6 +253,39 @@ void OutlinableRegion::splitCandidate() {
StartBB = StartInst->getParent();
PrevBB = StartBB;

DenseSet<BasicBlock *> BBSet;
Candidate->getBasicBlocks(BBSet);

// We iterate over the instructions in the region, if we find a PHINode, we
// check if there are predecessors outside of the region, if there are,
// we ignore this region since we are unable to handle the severing of the
// phi node right now.
BasicBlock::iterator It = StartInst->getIterator();
while (PHINode *PN = dyn_cast<PHINode>(&*It)) {
unsigned NumPredsOutsideRegion = 0;
for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i)
if (!BBSet.contains(PN->getIncomingBlock(i)))
++NumPredsOutsideRegion;

if (NumPredsOutsideRegion > 1)
return;

It++;
}

// If the region starts with a PHINode, but is not the initial instruction of
// the BasicBlock, we ignore this region for now.
if (isa<PHINode>(StartInst) && StartInst != &*StartBB->begin())
return;

// If the region ends with a PHINode, but does not contain all of the phi node
// instructions of the region, we ignore it for now.
if (isa<PHINode>(BackInst)) {
EndBB = BackInst->getParent();
if (BackInst != &*std::prev(EndBB->getFirstInsertionPt()))
return;
}

// The basic block gets split like so:
// block: block:
// inst1 inst1
Expand All @@ -241,12 +312,20 @@ void OutlinableRegion::splitCandidate() {
FollowBB = EndBB->splitBasicBlock(EndInst, OriginalName + "_after_outline");
EndBB->replaceSuccessorsPhiUsesWith(EndBB, FollowBB);
FollowBB->replaceSuccessorsPhiUsesWith(PrevBB, FollowBB);
return;
} else {
EndBB = BackInst->getParent();
EndsInBranch = true;
FollowBB = nullptr;
}

EndBB = BackInst->getParent();
EndsInBranch = true;
FollowBB = nullptr;
// Refind the basic block set.
BBSet.clear();
Candidate->getBasicBlocks(BBSet);
// For the phi nodes in the new starting basic block of the region, we
// reassign the targets of the basic blocks branching instructions.
replaceTargetsFromPHINode(StartBB, PrevBB, StartBB, BBSet);
if (FollowBB)
replaceTargetsFromPHINode(FollowBB, EndBB, FollowBB, BBSet);
}

void OutlinableRegion::reattachCandidate() {
Expand All @@ -268,15 +347,21 @@ void OutlinableRegion::reattachCandidate() {
// inst4
assert(StartBB != nullptr && "StartBB for Candidate is not defined!");

// StartBB should only have one predecessor since we put an unconditional
// branch at the end of PrevBB when we split the BasicBlock.
PrevBB = StartBB->getSinglePredecessor();
assert(PrevBB != nullptr &&
"No Predecessor for the region start basic block!");

assert(PrevBB->getTerminator() && "Terminator removed from PrevBB!");
PrevBB->getTerminator()->eraseFromParent();

// If we reattaching after outlining, we iterate over the phi nodes to
// the initial block, and reassign the branch instructions of the incoming
// blocks to the block we are remerging into.
if (!ExtractedFunction) {
DenseSet<BasicBlock *> BBSet;
Candidate->getBasicBlocks(BBSet);

replaceTargetsFromPHINode(StartBB, StartBB, PrevBB, BBSet);
if (!EndsInBranch)
replaceTargetsFromPHINode(FollowBB, FollowBB, EndBB, BBSet);
}

moveBBContents(*StartBB, *PrevBB);

BasicBlock *PlacementBB = PrevBB;
Expand Down Expand Up @@ -1622,7 +1707,8 @@ replaceArgumentUses(OutlinableRegion &Region,

// If this is storing a PHINode, we must make sure it is included in the
// overall function.
if (!isa<PHINode>(ValueOperand)) {
if (!isa<PHINode>(ValueOperand) ||
Region.Candidate->getGVN(ValueOperand).hasValue()) {
if (FirstFunction)
continue;
Value *CorrVal =
Expand Down Expand Up @@ -2161,7 +2247,7 @@ void IROutliner::pruneIncompatibleRegions(
if (FirstCandidate.getLength() == 2) {
if (isa<CallInst>(FirstCandidate.front()->Inst) &&
isa<BranchInst>(FirstCandidate.back()->Inst))
return;
return;
}

unsigned CurrentEndIdx = 0;
Expand Down
93 changes: 93 additions & 0 deletions llvm/test/Transforms/IROutliner/included-phi-nodes-begin.ll
@@ -0,0 +1,93 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --include-generated-funcs
; RUN: opt -S -verify -iroutliner -ir-outlining-no-cost < %s | FileCheck %s

; Show that we are able to outline when all of the phi nodes in the starting
; block are included in the region and there is no more than one predecessor
; into those phi nodes from outside of the region.

define void @function1(i32* %a, i32* %b) {
entry:
%0 = alloca i32, align 4
%c = load i32, i32* %0, align 4
%y = add i32 %c, %c
br label %test1
dummy:
ret void
test1:
%1 = phi i32 [ %e, %test1 ], [ %y, %entry ]
%2 = phi i32 [ %e, %test1 ], [ %y, %entry ]
%e = load i32, i32* %0, align 4
%3 = add i32 %c, %c
br i1 true, label %test, label %test1
test:
%d = load i32, i32* %0, align 4
br label %first
first:
ret void
}

define void @function2(i32* %a, i32* %b) {
entry:
%0 = alloca i32, align 4
%c = load i32, i32* %0, align 4
%y = mul i32 %c, %c
br label %test1
dummy:
ret void
test1:
%1 = phi i32 [ %e, %test1 ], [ %y, %entry ]
%2 = phi i32 [ %e, %test1 ], [ %y, %entry ]
%e = load i32, i32* %0, align 4
%3 = add i32 %c, %c
br i1 true, label %test, label %test1
test:
%d = load i32, i32* %0, align 4
br label %first
first:
ret void
}
; CHECK-LABEL: @function1(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[TMP0:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[C:%.*]] = load i32, i32* [[TMP0]], align 4
; CHECK-NEXT: [[Y:%.*]] = add i32 [[C]], [[C]]
; CHECK-NEXT: br label [[TEST1:%.*]]
; CHECK: dummy:
; CHECK-NEXT: ret void
; CHECK: test1:
; CHECK-NEXT: call void @outlined_ir_func_0(i32 [[Y]], i32* [[TMP0]], i32 [[C]])
; CHECK-NEXT: br label [[FIRST:%.*]]
; CHECK: first:
; CHECK-NEXT: ret void
;
;
; CHECK-LABEL: @function2(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[TMP0:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[C:%.*]] = load i32, i32* [[TMP0]], align 4
; CHECK-NEXT: [[Y:%.*]] = mul i32 [[C]], [[C]]
; CHECK-NEXT: br label [[TEST1:%.*]]
; CHECK: dummy:
; CHECK-NEXT: ret void
; CHECK: test1:
; CHECK-NEXT: call void @outlined_ir_func_0(i32 [[Y]], i32* [[TMP0]], i32 [[C]])
; CHECK-NEXT: br label [[FIRST:%.*]]
; CHECK: first:
; CHECK-NEXT: ret void
;
;
; CHECK: define internal void @outlined_ir_func_0(
; CHECK-NEXT: newFuncRoot:
; CHECK-NEXT: br label [[TEST1_TO_OUTLINE:%.*]]
; CHECK: test1_to_outline:
; CHECK-NEXT: [[TMP3:%.*]] = phi i32 [ [[E:%.*]], [[TEST1_TO_OUTLINE]] ], [ [[TMP0:%.*]], [[NEWFUNCROOT:%.*]] ]
; CHECK-NEXT: [[TMP4:%.*]] = phi i32 [ [[E]], [[TEST1_TO_OUTLINE]] ], [ [[TMP0]], [[NEWFUNCROOT]] ]
; CHECK-NEXT: [[E]] = load i32, i32* [[TMP1:%.*]], align 4
; CHECK-NEXT: [[TMP5:%.*]] = add i32 [[TMP2:%.*]], [[TMP2]]
; CHECK-NEXT: br i1 true, label [[TEST:%.*]], label [[TEST1_TO_OUTLINE]]
; CHECK: test:
; CHECK-NEXT: [[D:%.*]] = load i32, i32* [[TMP1]], align 4
; CHECK-NEXT: br label [[FIRST_EXITSTUB:%.*]]
; CHECK: first.exitStub:
; CHECK-NEXT: ret void
;

0 comments on commit e8f4e41

Please sign in to comment.