[LICM] Hoist stores of invariant values to invariant addresses out of loops

Teach LICM to hoist stores out of loops when the store writes to a location otherwise unused in the loop, writes a value which is invariant, and is guaranteed to execute if the loop is entered.

Worth noting is that this transformation is partially overlapping with the existing promotion transformation. Reasons this is worthwhile anyway include:
 * For multi-exit loops, this doesn't require duplication of the store.
 * It kicks in for case where we can't prove we exit through a normal exit (i.e. we may throw), but can prove the store executes before that possible side exit.

Differential Revision: https://reviews.llvm.org/D50925

llvm-svn: 340974
This commit is contained in:
Philip Reames 2018-08-29 21:49:30 +00:00
parent fd48b7d558
commit f562fc8dbf
6 changed files with 136 additions and 17 deletions

View File

@ -256,9 +256,22 @@ Instruction* AliasSet::getUniqueInstruction() {
if (AliasAny)
// May have collapses alias set
return nullptr;
if (size() != 0)
// Can't track source of pointer, might be many instruction
return nullptr;
if (begin() != end()) {
if (!UnknownInsts.empty())
// Another instruction found
return nullptr;
if (std::next(begin()) != end())
// Another instruction found
return nullptr;
Value *Addr = begin()->getValue();
assert(!Addr->user_empty() &&
"where's the instruction which added this pointer?");
if (std::next(Addr->user_begin()) != Addr->user_end())
// Another instruction found -- this is really restrictive
// TODO: generalize!
return nullptr;
return cast<Instruction>(*(Addr->user_begin()));
}
if (1 != UnknownInsts.size())
return nullptr;
return cast<Instruction>(UnknownInsts[0]);

View File

@ -416,7 +416,8 @@ bool llvm::sinkRegion(DomTreeNode *N, AliasAnalysis *AA, LoopInfo *LI,
//
bool FreeInLoop = false;
if (isNotUsedOrFreeInLoop(I, CurLoop, SafetyInfo, TTI, FreeInLoop) &&
canSinkOrHoistInst(I, AA, DT, CurLoop, CurAST, true, ORE)) {
canSinkOrHoistInst(I, AA, DT, CurLoop, CurAST, true, ORE) &&
!I.mayHaveSideEffects()) {
if (sink(I, LI, DT, CurLoop, SafetyInfo, ORE, FreeInLoop)) {
if (!FreeInLoop) {
++II;
@ -604,8 +605,8 @@ namespace {
/// concerns such as aliasing or speculation safety.
bool isHoistableAndSinkableInst(Instruction &I) {
// Only these instructions are hoistable/sinkable.
return (isa<LoadInst>(I) || isa<CallInst>(I) ||
isa<FenceInst>(I) ||
return (isa<LoadInst>(I) || isa<StoreInst>(I) ||
isa<CallInst>(I) || isa<FenceInst>(I) ||
isa<BinaryOperator>(I) || isa<CastInst>(I) ||
isa<SelectInst>(I) || isa<GetElementPtrInst>(I) ||
isa<CmpInst>(I) || isa<InsertElementInst>(I) ||
@ -695,6 +696,7 @@ bool llvm::canSinkOrHoistInst(Instruction &I, AAResults *AA, DominatorTree *DT,
// it's arguments with arbitrary offsets. If we can prove there are no
// writes to this memory in the loop, we can hoist or sink.
if (AliasAnalysis::onlyAccessesArgPointees(Behavior)) {
// TODO: expand to writeable arguments
for (Value *Op : CI->arg_operands())
if (Op->getType()->isPointerTy() &&
pointerInvalidatedByLoop(
@ -729,6 +731,24 @@ bool llvm::canSinkOrHoistInst(Instruction &I, AAResults *AA, DominatorTree *DT,
(void)FI; //suppress unused variable warning
assert(UniqueI == FI && "AS must contain FI");
return true;
} else if (auto *SI = dyn_cast<StoreInst>(&I)) {
if (!SI->isUnordered())
return false; // Don't sink/hoist volatile or ordered atomic store!
// We can only hoist a store that we can prove writes a value which is not
// read or overwritten within the loop. For those cases, we fallback to
// load store promotion instead.
auto &AS = CurAST->getAliasSetFor(MemoryLocation::get(SI));
if (AS.isRef() || !AS.isMustAlias())
// Quick exit test, handled by the full path below as well.
return false;
auto *UniqueI = AS.getUniqueInstruction();
if (!UniqueI)
// other memory op, give up
return false;
assert(UniqueI == SI && "AS must contain SI");
return true;
}
assert(!I.mayReadOrWriteMemory() && "unhandled aliasing");

View File

@ -173,10 +173,11 @@ loop:
end:
ret i32 %vala
; CHECK-LABEL: define i32 @test7b(
; CHECK: load atomic i32, i32* %y monotonic
; CHECK-LABEL: end:
; CHECK-LABEL: entry:
; CHECK: store i32 5, i32* %x
; CHECK-LABEL: loop:
; CHECK: load atomic i32, i32* %y monotonic
; CHECK-LABEL: end:
; CHECK: store atomic i32 %{{.+}}, i32* %z unordered, align 4
}

View File

@ -97,9 +97,11 @@ else: ; preds = %postinvoke
}
; CHECK-LABEL: define void @test3(
; CHECK: catchswitch within none
; CHECK-LABEL: forbody.preheader:
; CHECK: store i32 1, i32* %bc, align 4
; CHECK: store i32 2, i32* %bc2, align 4
; CHECK: catchswitch within none
; CHECK-LABEL: forbody:
declare void @may_throw()

View File

@ -10,6 +10,11 @@ target triple = "x86_64-apple-macosx10.8.0"
@p = external global i8*
define i32* @_Z4doiti(i32 %n, float* %tmp1, i32* %tmp3) nounwind {
; CHECK-LABEL: for.body.lr.ph:
; CHECK: store float 1.000000e+00, float* %tmp1
; CHECK-LABEL: for.cond.for.end_crit_edge:
; CHECK: store i32 1, i32* %tmp3
entry:
%cmp1 = icmp slt i32 0, %n
br i1 %cmp1, label %for.body.lr.ph, label %for.end
@ -25,9 +30,6 @@ for.body: ; preds = %for.body, %for.body
%cmp = icmp slt i32 %inc, %n
br i1 %cmp, label %for.body, label %for.cond.for.end_crit_edge
; CHECK: for.cond.for.end_crit_edge:
; CHECK: store float 1.000000e+00, float* %tmp1
; CHECK: store i32 1, i32* %tmp3
for.cond.for.end_crit_edge: ; preds = %for.body
%split = phi i32* [ %tmp3, %for.body ]
br label %for.end

View File

@ -3,8 +3,9 @@
define void @test(i32* %loc) {
; CHECK-LABEL: @test
; CHECK-LABEL: exit:
; CHECK-LABEL: entry:
; CHECK: store i32 0, i32* %loc
; CHECK-LABEL: loop:
entry:
br label %loop
@ -21,10 +22,9 @@ exit:
define void @test_multiexit(i32* %loc, i1 %earlycnd) {
; CHECK-LABEL: @test_multiexit
; CHECK-LABEL: exit1:
; CHECK: store i32 0, i32* %loc
; CHECK-LABEL: exit2:
; CHECK-LABEL: entry:
; CHECK: store i32 0, i32* %loc
; CHECK-LABEL: loop:
entry:
br label %loop
@ -44,6 +44,24 @@ exit2:
ret void
}
define i32* @false_negative_2use(i32* %loc) {
; CHECK-LABEL: @false_negative_2use
; CHECK-LABEL: exit:
; CHECK: store i32 0, i32* %loc
entry:
br label %loop
loop:
%iv = phi i32 [0, %entry], [%iv.next, %loop]
store i32 0, i32* %loc
%iv.next = add i32 %iv, 1
%cmp = icmp slt i32 %iv, 200
br i1 %cmp, label %loop, label %exit
exit:
ret i32* %loc
}
define void @neg_lv_value(i32* %loc) {
; CHECK-LABEL: @neg_lv_value
; CHECK-LABEL: exit:
@ -227,4 +245,67 @@ exit:
ret void
}
declare void @maythrow() inaccessiblememonly
define void @neg_early_exit(i32* %loc) {
; CHECK-LABEL: @neg_early_exit
; CHECK-LABEL: body:
; CHECK: store i32 0, i32* %loc
; CHECK-LABEL: exit:
entry:
br label %loop
loop:
%iv = phi i32 [0, %entry], [%iv.next, %body]
%is_null = icmp eq i32* %loc, null
br i1 %is_null, label %exit, label %body
body:
call void @maythrow()
store i32 0, i32* %loc
%iv.next = add i32 %iv, 1
%cmp = icmp slt i32 %iv, 200
br i1 %cmp, label %loop, label %exit
exit:
ret void
}
define void @neg_early_throw(i32* %loc) {
; CHECK-LABEL: @neg_early_throw
; CHECK-LABEL: loop:
; CHECK: store i32 0, i32* %loc
; CHECK-LABEL: exit:
entry:
br label %loop
loop:
%iv = phi i32 [0, %entry], [%iv.next, %loop]
call void @maythrow()
store i32 0, i32* %loc
%iv.next = add i32 %iv, 1
%cmp = icmp slt i32 %iv, 200
br i1 %cmp, label %loop, label %exit
exit:
ret void
}
define void @test_late_throw(i32* %loc) {
; CHECK-LABEL: @test_late_throw
; CHECK-LABEL: entry:
; CHECK: store i32 0, i32* %loc
; CHECK-LABEL: loop:
entry:
br label %loop
loop:
%iv = phi i32 [0, %entry], [%iv.next, %loop]
store i32 0, i32* %loc
call void @maythrow()
%iv.next = add i32 %iv, 1
%cmp = icmp slt i32 %iv, 200
br i1 %cmp, label %loop, label %exit
exit:
ret void
}