[llvm] 7b0f637 - [SimplifyCFG] Allow SimplifyCFG hoisting to skip over non-matching instructions
Momchil Velikov via llvm-commits
llvm-commits at lists.llvm.org
Sun Jul 31 23:56:51 PDT 2022
Author: Momchil Velikov
Date: 2022-08-01T07:55:14+01:00
New Revision: 7b0f6378e211e881c574748090a86beeab264ab3
URL: https://github.com/llvm/llvm-project/commit/7b0f6378e211e881c574748090a86beeab264ab3
DIFF: https://github.com/llvm/llvm-project/commit/7b0f6378e211e881c574748090a86beeab264ab3.diff
LOG: [SimplifyCFG] Allow SimplifyCFG hoisting to skip over non-matching instructions
SimplifyCFG does some common code hoisting, which is limited to hoisting a
sequence of identical instruction in identical order and stops at the first
non-identical instruction.
This patch allows hoisting instruction pairs over same-length sequences of
non-matching instructions. The linear asymptotic complexity of the algorithm
stays the same, there's an extra parameter `simplifycfg-hoist-common-skip-limit`
serving to limit compilation time and/or the size of the hoisted live ranges.
The patch improves SPECv6/525.x264_r by about 10%.
Reviewed By: dmgreen
Differential Revision: https://reviews.llvm.org/D129370
Added:
llvm/test/Transforms/SimplifyCFG/hoist-common-skip-limit.ll
llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll
Modified:
llvm/lib/Transforms/Utils/SimplifyCFG.cpp
Removed:
################################################################################
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 1806081678a86..1b148aeb794d3 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -115,6 +115,12 @@ static cl::opt<bool>
HoistCommon("simplifycfg-hoist-common", cl::Hidden, cl::init(true),
cl::desc("Hoist common instructions up to the parent block"));
+static cl::opt<unsigned>
+ HoistCommonSkipLimit("simplifycfg-hoist-common-skip-limit", cl::Hidden,
+ cl::init(20),
+ cl::desc("Allow reordering across at most this many "
+ "instructions when hoisting"));
+
static cl::opt<bool>
SinkCommon("simplifycfg-sink-common", cl::Hidden, cl::init(true),
cl::desc("Sink common instructions down to the end block"));
@@ -1430,6 +1436,32 @@ static bool isSafeToHoistInvoke(BasicBlock *BB1, BasicBlock *BB2,
return true;
}
+// Returns true if it is safe to reorder an instruction across preceding
+// instructions in a basic block.
+static bool isSafeToHoistInstr(Instruction *I, bool ForceNoSideEffects,
+ bool ForceNoSpeculation) {
+ // If we have seen an instruction with side effects, it's unsafe to reorder an
+ // instruction which reads memory or itself has side effects.
+ if (ForceNoSideEffects && (I->mayReadFromMemory() || I->mayHaveSideEffects()))
+ return false;
+
+ // Reordering across an instruction which does not necessarily transfer
+ // control to the next instruction is speculation.
+ if (ForceNoSpeculation && !isSafeToSpeculativelyExecute(I))
+ return false;
+
+ // It's also unsafe/illegal to hoist an instruction above its instruction
+ // operands
+ BasicBlock *BB = I->getParent();
+ for (Value *Op : I->operands()) {
+ if (auto *J = dyn_cast<Instruction>(Op))
+ if (J->getParent() == BB)
+ return false;
+ }
+
+ return true;
+}
+
static bool passingValueIsAlwaysUndefined(Value *V, Instruction *I, bool PtrValueMayBeModified = false);
/// Given a conditional branch that goes to BB1 and BB2, hoist any common code
@@ -1444,7 +1476,8 @@ bool SimplifyCFGOpt::HoistThenElseCodeToIf(BranchInst *BI,
// instructions in the two blocks. In particular, we don't want to get into
// O(M*N) situations here where M and N are the sizes of BB1 and BB2. As
// such, we currently just scan for obviously identical instructions in an
- // identical order.
+ // identical order, possibly separated by the same number of non-identical
+ // instructions.
BasicBlock *BB1 = BI->getSuccessor(0); // The true destination.
BasicBlock *BB2 = BI->getSuccessor(1); // The false destination
@@ -1467,7 +1500,7 @@ bool SimplifyCFGOpt::HoistThenElseCodeToIf(BranchInst *BI,
while (isa<DbgInfoIntrinsic>(I2))
I2 = &*BB2_Itr++;
}
- if (isa<PHINode>(I1) || !I1->isIdenticalToWhenDefined(I2))
+ if (isa<PHINode>(I1))
return false;
BasicBlock *BIParent = BI->getParent();
@@ -1493,75 +1526,122 @@ bool SimplifyCFGOpt::HoistThenElseCodeToIf(BranchInst *BI,
// terminator. Let the loop below handle those 2 cases.
}
- do {
+ // Count how many instructions were not hoisted so far. There's a limit on how
+ // many instructions we skip, serving as a compilation time control as well as
+ // preventing excessive increase of life ranges.
+ unsigned NumSkipped = 0;
+
+ // Record if any non-hoisted instruction contains side-effects, as it could
+ // make it illegal to reorder some instructions across.
+ bool ForceNoReadMemOrSideEffectsBB1 = false;
+ bool ForceNoReadMemOrSideEffectsBB2 = false;
+
+ // Record if any non-hoisted instructions does not necessarily transfer
+ // control to the next instruction. Then we can hoist only instrucions which
+ // are safe to speculate.
+ bool ForceNoSpeculationBB1 = false;
+ bool ForceNoSpeculationBB2 = false;
+
+ for (;;) {
// If we are hoisting the terminator instruction, don't move one (making a
// broken BB), instead clone it, and remove BI.
- if (I1->isTerminator())
+ if (I1->isTerminator() || I2->isTerminator()) {
+ // If any instructions remain in the block, we cannot hoist terminators.
+ if (NumSkipped || !I1->isIdenticalToWhenDefined(I2))
+ return Changed;
goto HoistTerminator;
+ }
- // If we're going to hoist a call, make sure that the two instructions we're
- // commoning/hoisting are both marked with musttail, or neither of them is
- // marked as such. Otherwise, we might end up in a situation where we hoist
- // from a block where the terminator is a `ret` to a block where the terminator
- // is a `br`, and `musttail` calls expect to be followed by a return.
- auto *C1 = dyn_cast<CallInst>(I1);
- auto *C2 = dyn_cast<CallInst>(I2);
- if (C1 && C2)
- if (C1->isMustTailCall() != C2->isMustTailCall())
+ if (I1->isIdenticalToWhenDefined(I2)) {
+ // Hoisting token-returning instructions would obscure the origin.
+ if (I1->getType()->isTokenTy())
return Changed;
- if (!TTI.isProfitableToHoist(I1) || !TTI.isProfitableToHoist(I2))
- return Changed;
-
- // If any of the two call sites has nomerge attribute, stop hoisting.
- if (const auto *CB1 = dyn_cast<CallBase>(I1))
- if (CB1->cannotMerge())
+ // Even if the instructions are identical, it may not be safe to hoist
+ // them if we have skipped over instructions with side effects or their
+ // operands weren't hoisted.
+ if (!isSafeToHoistInstr(I1, ForceNoReadMemOrSideEffectsBB1, ForceNoSpeculationBB1) ||
+ !isSafeToHoistInstr(I2, ForceNoReadMemOrSideEffectsBB2, ForceNoSpeculationBB2))
return Changed;
- if (const auto *CB2 = dyn_cast<CallBase>(I2))
- if (CB2->cannotMerge())
+
+ // If we're going to hoist a call, make sure that the two instructions
+ // we're commoning/hoisting are both marked with musttail, or neither of
+ // them is marked as such. Otherwise, we might end up in a situation where
+ // we hoist from a block where the terminator is a `ret` to a block where
+ // the terminator is a `br`, and `musttail` calls expect to be followed by
+ // a return.
+ auto *C1 = dyn_cast<CallInst>(I1);
+ auto *C2 = dyn_cast<CallInst>(I2);
+ if (C1 && C2)
+ if (C1->isMustTailCall() != C2->isMustTailCall())
+ return Changed;
+
+ if (!TTI.isProfitableToHoist(I1) || !TTI.isProfitableToHoist(I2))
return Changed;
- if (isa<DbgInfoIntrinsic>(I1) || isa<DbgInfoIntrinsic>(I2)) {
- assert (isa<DbgInfoIntrinsic>(I1) && isa<DbgInfoIntrinsic>(I2));
- // The debug location is an integral part of a debug info intrinsic
- // and can't be separated from it or replaced. Instead of attempting
- // to merge locations, simply hoist both copies of the intrinsic.
- BIParent->getInstList().splice(BI->getIterator(),
- BB1->getInstList(), I1);
- BIParent->getInstList().splice(BI->getIterator(),
- BB2->getInstList(), I2);
+ // If any of the two call sites has nomerge attribute, stop hoisting.
+ if (const auto *CB1 = dyn_cast<CallBase>(I1))
+ if (CB1->cannotMerge())
+ return Changed;
+ if (const auto *CB2 = dyn_cast<CallBase>(I2))
+ if (CB2->cannotMerge())
+ return Changed;
+
+ if (isa<DbgInfoIntrinsic>(I1) || isa<DbgInfoIntrinsic>(I2)) {
+ assert(isa<DbgInfoIntrinsic>(I1) && isa<DbgInfoIntrinsic>(I2));
+ // The debug location is an integral part of a debug info intrinsic
+ // and can't be separated from it or replaced. Instead of attempting
+ // to merge locations, simply hoist both copies of the intrinsic.
+ BIParent->getInstList().splice(BI->getIterator(), BB1->getInstList(),
+ I1);
+ BIParent->getInstList().splice(BI->getIterator(), BB2->getInstList(),
+ I2);
+ } else {
+ // For a normal instruction, we just move one to right before the
+ // branch, then replace all uses of the other with the first. Finally,
+ // we remove the now redundant second instruction.
+ BIParent->getInstList().splice(BI->getIterator(), BB1->getInstList(),
+ I1);
+ if (!I2->use_empty())
+ I2->replaceAllUsesWith(I1);
+ I1->andIRFlags(I2);
+ unsigned KnownIDs[] = {LLVMContext::MD_tbaa,
+ LLVMContext::MD_range,
+ LLVMContext::MD_fpmath,
+ LLVMContext::MD_invariant_load,
+ LLVMContext::MD_nonnull,
+ LLVMContext::MD_invariant_group,
+ LLVMContext::MD_align,
+ LLVMContext::MD_dereferenceable,
+ LLVMContext::MD_dereferenceable_or_null,
+ LLVMContext::MD_mem_parallel_loop_access,
+ LLVMContext::MD_access_group,
+ LLVMContext::MD_preserve_access_index};
+ combineMetadata(I1, I2, KnownIDs, true);
+
+ // I1 and I2 are being combined into a single instruction. Its debug
+ // location is the merged locations of the original instructions.
+ I1->applyMergedLocation(I1->getDebugLoc(), I2->getDebugLoc());
+
+ I2->eraseFromParent();
+ }
Changed = true;
+ ++NumHoistCommonInstrs;
} else {
- // For a normal instruction, we just move one to right before the branch,
- // then replace all uses of the other with the first. Finally, we remove
- // the now redundant second instruction.
- BIParent->getInstList().splice(BI->getIterator(),
- BB1->getInstList(), I1);
- if (!I2->use_empty())
- I2->replaceAllUsesWith(I1);
- I1->andIRFlags(I2);
- unsigned KnownIDs[] = {LLVMContext::MD_tbaa,
- LLVMContext::MD_range,
- LLVMContext::MD_fpmath,
- LLVMContext::MD_invariant_load,
- LLVMContext::MD_nonnull,
- LLVMContext::MD_invariant_group,
- LLVMContext::MD_align,
- LLVMContext::MD_dereferenceable,
- LLVMContext::MD_dereferenceable_or_null,
- LLVMContext::MD_mem_parallel_loop_access,
- LLVMContext::MD_access_group,
- LLVMContext::MD_preserve_access_index};
- combineMetadata(I1, I2, KnownIDs, true);
-
- // I1 and I2 are being combined into a single instruction. Its debug
- // location is the merged locations of the original instructions.
- I1->applyMergedLocation(I1->getDebugLoc(), I2->getDebugLoc());
-
- I2->eraseFromParent();
- Changed = true;
+ if (NumSkipped >= HoistCommonSkipLimit)
+ return Changed;
+ // We are about to skip over a pair of non-identical instructions. Record
+ // if any of them has side effects.
+ if (I1->mayHaveSideEffects())
+ ForceNoReadMemOrSideEffectsBB1 = true;
+ if (I2->mayHaveSideEffects())
+ ForceNoReadMemOrSideEffectsBB2 = true;
+ if (!isGuaranteedToTransferExecutionToSuccessor(I1))
+ ForceNoSpeculationBB1 = true;
+ if (!isGuaranteedToTransferExecutionToSuccessor(I2))
+ ForceNoSpeculationBB2 = true;
+ ++NumSkipped;
}
- ++NumHoistCommonInstrs;
I1 = &*BB1_Itr++;
I2 = &*BB2_Itr++;
@@ -1574,9 +1654,9 @@ bool SimplifyCFGOpt::HoistThenElseCodeToIf(BranchInst *BI,
while (isa<DbgInfoIntrinsic>(I2))
I2 = &*BB2_Itr++;
}
- } while (I1->isIdenticalToWhenDefined(I2));
+ }
- return true;
+ return Changed;
HoistTerminator:
// It may not be possible to hoist an invoke.
diff --git a/llvm/test/Transforms/SimplifyCFG/hoist-common-skip-limit.ll b/llvm/test/Transforms/SimplifyCFG/hoist-common-skip-limit.ll
new file mode 100644
index 0000000000000..0e1a171dd9ebf
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/hoist-common-skip-limit.ll
@@ -0,0 +1,97 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -S --passes='simplifycfg<hoist-common-insts>' -simplifycfg-hoist-common-skip-limit=0 %s | FileCheck %s --check-prefix=LIMIT0
+; RUN: opt -S --passes='simplifycfg<hoist-common-insts>' -simplifycfg-hoist-common-skip-limit=1 %s | FileCheck %s --check-prefix=LIMIT1
+; RUN: opt -S --passes='simplifycfg<hoist-common-insts>' -simplifycfg-hoist-common-skip-limit=2 %s | FileCheck %s --check-prefix=LIMIT2
+
+define void @f(i1 %c, ptr nocapture noundef %d, ptr nocapture noundef readonly %m, ptr nocapture noundef readonly %b) {
+; LIMIT0-LABEL: @f(
+; LIMIT0-NEXT: entry:
+; LIMIT0-NEXT: [[TMP0:%.*]] = load i16, ptr [[B:%.*]], align 2
+; LIMIT0-NEXT: br i1 [[C:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; LIMIT0: if.then:
+; LIMIT0-NEXT: call void @no_side_effects0()
+; LIMIT0-NEXT: [[ADD:%.*]] = add nsw i16 [[TMP0]], 1
+; LIMIT0-NEXT: [[TMP1:%.*]] = load i16, ptr [[M:%.*]], align 2
+; LIMIT0-NEXT: [[U:%.*]] = add i16 [[ADD]], [[TMP1]]
+; LIMIT0-NEXT: br label [[IF_END:%.*]]
+; LIMIT0: if.else:
+; LIMIT0-NEXT: call void @no_side_effects1()
+; LIMIT0-NEXT: [[SUB:%.*]] = sub nsw i16 [[TMP0]], 1
+; LIMIT0-NEXT: [[TMP2:%.*]] = load i16, ptr [[M]], align 2
+; LIMIT0-NEXT: [[V:%.*]] = add i16 [[SUB]], [[TMP2]]
+; LIMIT0-NEXT: br label [[IF_END]]
+; LIMIT0: if.end:
+; LIMIT0-NEXT: [[UV:%.*]] = phi i16 [ [[V]], [[IF_ELSE]] ], [ [[U]], [[IF_THEN]] ]
+; LIMIT0-NEXT: store i16 [[UV]], ptr [[D:%.*]], align 2
+; LIMIT0-NEXT: ret void
+;
+; LIMIT1-LABEL: @f(
+; LIMIT1-NEXT: entry:
+; LIMIT1-NEXT: [[TMP0:%.*]] = load i16, ptr [[B:%.*]], align 2
+; LIMIT1-NEXT: br i1 [[C:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; LIMIT1: if.then:
+; LIMIT1-NEXT: call void @no_side_effects0()
+; LIMIT1-NEXT: [[ADD:%.*]] = add nsw i16 [[TMP0]], 1
+; LIMIT1-NEXT: [[TMP1:%.*]] = load i16, ptr [[M:%.*]], align 2
+; LIMIT1-NEXT: [[U:%.*]] = add i16 [[ADD]], [[TMP1]]
+; LIMIT1-NEXT: br label [[IF_END:%.*]]
+; LIMIT1: if.else:
+; LIMIT1-NEXT: call void @no_side_effects1()
+; LIMIT1-NEXT: [[SUB:%.*]] = sub nsw i16 [[TMP0]], 1
+; LIMIT1-NEXT: [[TMP2:%.*]] = load i16, ptr [[M]], align 2
+; LIMIT1-NEXT: [[V:%.*]] = add i16 [[SUB]], [[TMP2]]
+; LIMIT1-NEXT: br label [[IF_END]]
+; LIMIT1: if.end:
+; LIMIT1-NEXT: [[UV:%.*]] = phi i16 [ [[V]], [[IF_ELSE]] ], [ [[U]], [[IF_THEN]] ]
+; LIMIT1-NEXT: store i16 [[UV]], ptr [[D:%.*]], align 2
+; LIMIT1-NEXT: ret void
+;
+; LIMIT2-LABEL: @f(
+; LIMIT2-NEXT: entry:
+; LIMIT2-NEXT: [[TMP0:%.*]] = load i16, ptr [[B:%.*]], align 2
+; LIMIT2-NEXT: [[TMP1:%.*]] = load i16, ptr [[M:%.*]], align 2
+; LIMIT2-NEXT: br i1 [[C:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; LIMIT2: if.then:
+; LIMIT2-NEXT: call void @no_side_effects0()
+; LIMIT2-NEXT: [[ADD:%.*]] = add nsw i16 [[TMP0]], 1
+; LIMIT2-NEXT: [[U:%.*]] = add i16 [[ADD]], [[TMP1]]
+; LIMIT2-NEXT: br label [[IF_END:%.*]]
+; LIMIT2: if.else:
+; LIMIT2-NEXT: call void @no_side_effects1()
+; LIMIT2-NEXT: [[SUB:%.*]] = sub nsw i16 [[TMP0]], 1
+; LIMIT2-NEXT: [[V:%.*]] = add i16 [[SUB]], [[TMP1]]
+; LIMIT2-NEXT: br label [[IF_END]]
+; LIMIT2: if.end:
+; LIMIT2-NEXT: [[UV:%.*]] = phi i16 [ [[V]], [[IF_ELSE]] ], [ [[U]], [[IF_THEN]] ]
+; LIMIT2-NEXT: store i16 [[UV]], ptr [[D:%.*]], align 2
+; LIMIT2-NEXT: ret void
+;
+entry:
+ br i1 %c, label %if.then, label %if.else
+
+if.then:
+ %0 = load i16, ptr %b, align 2
+ call void @no_side_effects0()
+ %add = add nsw i16 %0, 1
+ %1 = load i16, ptr %m, align 2
+ %u = add i16 %add, %1
+ br label %if.end
+
+if.else:
+ %2 = load i16, ptr %b, align 2
+ call void @no_side_effects1()
+ %sub = sub nsw i16 %2, 1
+ %3 = load i16, ptr %m, align 2
+ %v = add i16 %sub, %3
+ br label %if.end
+
+if.end:
+ %uv = phi i16 [ %v, %if.else ], [ %u, %if.then ]
+ store i16 %uv, ptr %d, align 2
+ ret void
+}
+
+declare void @side_effects0()
+declare void @side_effects1()
+declare void @no_side_effects0() readonly nounwind willreturn
+declare void @no_side_effects1() readonly nounwind willreturn
diff --git a/llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll b/llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll
new file mode 100644
index 0000000000000..400f981cf552e
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll
@@ -0,0 +1,277 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -S --passes='simplifycfg<hoist-common-insts>' %s | FileCheck %s
+
+;; Check that the two loads are hoisted to the common predecessor, skipping
+;; over the add/sub instructions.
+
+define void @f0(i1 %c, ptr nocapture noundef %d, ptr nocapture noundef readonly %m, ptr nocapture noundef readonly %b) {
+; CHECK-LABEL: @f0(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[TMP0:%.*]] = load i16, ptr [[B:%.*]], align 2
+; CHECK-NEXT: [[TMP1:%.*]] = load i16, ptr [[M:%.*]], align 2
+; CHECK-NEXT: br i1 [[C:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; CHECK: if.then:
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i16 [[TMP0]], 1
+; CHECK-NEXT: [[U:%.*]] = add i16 [[ADD]], [[TMP1]]
+; CHECK-NEXT: br label [[IF_END:%.*]]
+; CHECK: if.else:
+; CHECK-NEXT: [[SUB:%.*]] = sub nsw i16 [[TMP0]], 1
+; CHECK-NEXT: [[TMP2:%.*]] = add i16 [[SUB]], 3
+; CHECK-NEXT: [[V:%.*]] = add i16 [[SUB]], [[TMP2]]
+; CHECK-NEXT: br label [[IF_END]]
+; CHECK: if.end:
+; CHECK-NEXT: [[UV:%.*]] = phi i16 [ [[V]], [[IF_ELSE]] ], [ [[U]], [[IF_THEN]] ]
+; CHECK-NEXT: store i16 [[UV]], ptr [[D:%.*]], align 2
+; CHECK-NEXT: ret void
+;
+entry:
+ br i1 %c, label %if.then, label %if.else
+
+if.then:
+ %0 = load i16, ptr %b, align 2
+ %add = add nsw i16 %0, 1
+ %1 = load i16, ptr %m, align 2
+ %u = add i16 %add, %1
+ br label %if.end
+
+if.else:
+ %2 = load i16, ptr %b, align 2
+ %sub = sub nsw i16 %2, 1
+ %3 = load i16, ptr %m, align 2
+ %4 = add i16 %sub, 3
+ %v = add i16 %sub, %4
+ br label %if.end
+
+if.end:
+ %uv = phi i16 [ %v, %if.else ], [ %u, %if.then ]
+ store i16 %uv, ptr %d, align 2
+ ret void
+}
+
+
+;; Check some instructions (e.g. add) can be reordered across instructions with side
+;; effects, while others (e.g. load) can't.
+define void @f2(i1 %c, ptr nocapture noundef %d, ptr nocapture noundef readonly %m, ptr nocapture noundef readonly %b) {
+; CHECK-LABEL: @f2(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[TMP0:%.*]] = load i16, ptr [[B:%.*]], align 2
+; CHECK-NEXT: [[ADD_0:%.*]] = add nsw i16 [[TMP0]], 1
+; CHECK-NEXT: br i1 [[C:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; CHECK: if.then:
+; CHECK-NEXT: call void @side_effects0()
+; CHECK-NEXT: [[TMP1:%.*]] = load i16, ptr [[M:%.*]], align 2
+; CHECK-NEXT: [[U:%.*]] = add i16 [[ADD_0]], [[TMP1]]
+; CHECK-NEXT: br label [[IF_END:%.*]]
+; CHECK: if.else:
+; CHECK-NEXT: call void @no_side_effects0()
+; CHECK-NEXT: [[TMP2:%.*]] = load i16, ptr [[M]], align 2
+; CHECK-NEXT: [[V:%.*]] = add i16 [[ADD_0]], [[TMP2]]
+; CHECK-NEXT: br label [[IF_END]]
+; CHECK: if.end:
+; CHECK-NEXT: [[UV:%.*]] = phi i16 [ [[V]], [[IF_ELSE]] ], [ [[U]], [[IF_THEN]] ]
+; CHECK-NEXT: store i16 [[UV]], ptr [[D:%.*]], align 2
+; CHECK-NEXT: ret void
+;
+entry:
+ br i1 %c, label %if.then, label %if.else
+
+if.then:
+ %0 = load i16, ptr %b, align 2
+ call void @side_effects0()
+ %add.0 = add nsw i16 %0, 1
+ %1 = load i16, ptr %m, align 2
+ %u = add i16 %add.0, %1
+ br label %if.end
+
+if.else:
+ %2 = load i16, ptr %b, align 2
+ call void @no_side_effects0()
+ %add.1 = add nsw i16 %2, 1
+ %3 = load i16, ptr %m, align 2
+ %v = add i16 %add.1, %3
+ br label %if.end
+
+if.end:
+ %uv = phi i16 [ %v, %if.else ], [ %u, %if.then ]
+ store i16 %uv, ptr %d, align 2
+ ret void
+}
+
+
+;; Check indeed it was the side effects that prevented hoisting the load
+;; in the previous test.
+define void @f3(i1 %c, ptr nocapture noundef %d, ptr nocapture noundef readonly %m, ptr nocapture noundef readonly %b) {
+; CHECK-LABEL: @f3(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[TMP0:%.*]] = load i16, ptr [[B:%.*]], align 2
+; CHECK-NEXT: [[ADD_0:%.*]] = add nsw i16 [[TMP0]], 1
+; CHECK-NEXT: [[TMP1:%.*]] = load i16, ptr [[M:%.*]], align 2
+; CHECK-NEXT: [[U:%.*]] = add i16 [[ADD_0]], [[TMP1]]
+; CHECK-NEXT: br i1 [[C:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; CHECK: if.then:
+; CHECK-NEXT: call void @no_side_effects0()
+; CHECK-NEXT: br label [[IF_END:%.*]]
+; CHECK: if.else:
+; CHECK-NEXT: call void @no_side_effects1()
+; CHECK-NEXT: br label [[IF_END]]
+; CHECK: if.end:
+; CHECK-NEXT: store i16 [[U]], ptr [[D:%.*]], align 2
+; CHECK-NEXT: ret void
+;
+entry:
+ br i1 %c, label %if.then, label %if.else
+
+if.then:
+ %0 = load i16, ptr %b, align 2
+ call void @no_side_effects0()
+ %add.0 = add nsw i16 %0, 1
+ %1 = load i16, ptr %m, align 2
+ %u = add i16 %add.0, %1
+ br label %if.end
+
+if.else:
+ %2 = load i16, ptr %b, align 2
+ call void @no_side_effects1()
+ %add.1 = add nsw i16 %2, 1
+ %3 = load i16, ptr %m, align 2
+ %v = add i16 %add.1, %3
+ br label %if.end
+
+if.end:
+ %uv = phi i16 [ %v, %if.else ], [ %u, %if.then ]
+ store i16 %uv, ptr %d, align 2
+ ret void
+}
+
+;; Check some instructions (e.g. sdiv) are not speculatively executed.
+
+;; Division by non-zero constant OK to speculate ...
+define void @f4(i1 %c, ptr nocapture noundef %d, ptr nocapture noundef readonly %m, ptr nocapture noundef readonly %b) {
+; CHECK-LABEL: @f4(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[TMP0:%.*]] = load i16, ptr [[B:%.*]], align 2
+; CHECK-NEXT: [[DIV_0:%.*]] = sdiv i16 [[TMP0]], 2
+; CHECK-NEXT: [[U:%.*]] = add i16 [[DIV_0]], [[TMP0]]
+; CHECK-NEXT: br i1 [[C:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; CHECK: if.then:
+; CHECK-NEXT: call void @side_effects0()
+; CHECK-NEXT: br label [[IF_END:%.*]]
+; CHECK: if.else:
+; CHECK-NEXT: call void @side_effects1()
+; CHECK-NEXT: br label [[IF_END]]
+; CHECK: if.end:
+; CHECK-NEXT: store i16 [[U]], ptr [[D:%.*]], align 2
+; CHECK-NEXT: ret void
+;
+entry:
+ br i1 %c, label %if.then, label %if.else
+
+if.then:
+ %0 = load i16, ptr %b, align 2
+ call void @side_effects0()
+ %div.0 = sdiv i16 %0, 2
+ %u = add i16 %div.0, %0
+ br label %if.end
+
+if.else:
+ %1 = load i16, ptr %b, align 2
+ call void @side_effects1()
+ %div.1 = sdiv i16 %1, 2
+ %v = add i16 %div.1, %1
+ br label %if.end
+
+if.end:
+ %uv = phi i16 [ %v, %if.else ], [ %u, %if.then ]
+ store i16 %uv, ptr %d, align 2
+ ret void
+}
+
+;; ... but not a general division ...
+define void @f5(i1 %c, ptr nocapture noundef %d, ptr nocapture noundef readonly %m, ptr nocapture noundef readonly %b) {
+; CHECK-LABEL: @f5(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[TMP0:%.*]] = load i16, ptr [[B:%.*]], align 2
+; CHECK-NEXT: br i1 [[C:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; CHECK: if.then:
+; CHECK-NEXT: call void @side_effects0()
+; CHECK-NEXT: [[DIV_0:%.*]] = sdiv i16 211, [[TMP0]]
+; CHECK-NEXT: [[U:%.*]] = add i16 [[DIV_0]], [[TMP0]]
+; CHECK-NEXT: br label [[IF_END:%.*]]
+; CHECK: if.else:
+; CHECK-NEXT: call void @side_effects1()
+; CHECK-NEXT: [[DIV_1:%.*]] = sdiv i16 211, [[TMP0]]
+; CHECK-NEXT: [[V:%.*]] = add i16 [[DIV_1]], [[TMP0]]
+; CHECK-NEXT: br label [[IF_END]]
+; CHECK: if.end:
+; CHECK-NEXT: [[UV:%.*]] = phi i16 [ [[V]], [[IF_ELSE]] ], [ [[U]], [[IF_THEN]] ]
+; CHECK-NEXT: store i16 [[UV]], ptr [[D:%.*]], align 2
+; CHECK-NEXT: ret void
+;
+entry:
+ br i1 %c, label %if.then, label %if.else
+
+if.then:
+ %0 = load i16, ptr %b, align 2
+ call void @side_effects0()
+ %div.0 = sdiv i16 211, %0
+ %u = add i16 %div.0, %0
+ br label %if.end
+
+if.else:
+ %1 = load i16, ptr %b, align 2
+ call void @side_effects1()
+ %div.1 = sdiv i16 211, %1
+ %v = add i16 %div.1, %1
+ br label %if.end
+
+if.end:
+ %uv = phi i16 [ %v, %if.else ], [ %u, %if.then ]
+ store i16 %uv, ptr %d, align 2
+ ret void
+}
+
+;; ... and it's also OK to hoist the division when there's no speculation happening.
+define void @f6(i1 %c, ptr nocapture noundef %d, ptr nocapture noundef readonly %m, ptr nocapture noundef readonly %b) {
+; CHECK-LABEL: @f6(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[TMP0:%.*]] = load i16, ptr [[B:%.*]], align 2
+; CHECK-NEXT: [[DIV_0:%.*]] = sdiv i16 211, [[TMP0]]
+; CHECK-NEXT: [[U:%.*]] = add i16 [[DIV_0]], [[TMP0]]
+; CHECK-NEXT: br i1 [[C:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; CHECK: if.then:
+; CHECK-NEXT: call void @no_side_effects0()
+; CHECK-NEXT: br label [[IF_END:%.*]]
+; CHECK: if.else:
+; CHECK-NEXT: call void @no_side_effects1()
+; CHECK-NEXT: br label [[IF_END]]
+; CHECK: if.end:
+; CHECK-NEXT: store i16 [[U]], ptr [[D:%.*]], align 2
+; CHECK-NEXT: ret void
+;
+entry:
+ br i1 %c, label %if.then, label %if.else
+
+if.then:
+ %0 = load i16, ptr %b, align 2
+ call void @no_side_effects0()
+ %div.0 = sdiv i16 211, %0
+ %u = add i16 %div.0, %0
+ br label %if.end
+
+if.else:
+ %1 = load i16, ptr %b, align 2
+ call void @no_side_effects1()
+ %div.1 = sdiv i16 211, %1
+ %v = add i16 %div.1, %1
+ br label %if.end
+
+if.end:
+ %uv = phi i16 [ %v, %if.else ], [ %u, %if.then ]
+ store i16 %uv, ptr %d, align 2
+ ret void
+}
+
+declare void @side_effects0()
+declare void @side_effects1()
+declare void @no_side_effects0() readonly nounwind willreturn
+declare void @no_side_effects1() readonly nounwind willreturn
More information about the llvm-commits
mailing list