[llvm] 078899c - [SimplifyCFG] Allow SimplifyCFG hoisting to skip over non-matching instructions
Momchil Velikov via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 5 07:15:02 PDT 2022
Author: Momchil Velikov
Date: 2022-09-05T15:13:46+01:00
New Revision: 078899cd64cd2fb787c2c5356e16dd818ee3ad23
URL: https://github.com/llvm/llvm-project/commit/078899cd64cd2fb787c2c5356e16dd818ee3ad23
DIFF: https://github.com/llvm/llvm-project/commit/078899cd64cd2fb787c2c5356e16dd818ee3ad23.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: nikic, 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 d61cb0b2469c0..49ecd988dba72 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -116,6 +116,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"));
@@ -1423,6 +1429,56 @@ static bool isSafeToHoistInvoke(BasicBlock *BB1, BasicBlock *BB2,
return true;
}
+// Get interesting characteristics of instructions that `HoistThenElseCodeToIf`
+// didn't hoist. They restrict what kind of instructions can be reordered
+// across.
+enum SkipFlags {
+ SkipReadMem = 1,
+ SkipSideEffect = 2,
+ SkipImplicitControlFlow = 4
+};
+
+static unsigned skippedInstrFlags(Instruction *I) {
+ unsigned Flags = 0;
+ if (I->mayReadFromMemory())
+ Flags |= SkipReadMem;
+ if (I->mayHaveSideEffects())
+ Flags |= SkipSideEffect;
+ if (!isGuaranteedToTransferExecutionToSuccessor(I))
+ Flags |= SkipImplicitControlFlow;
+ return Flags;
+}
+
+// Returns true if it is safe to reorder an instruction across preceding
+// instructions in a basic block.
+static bool isSafeToHoistInstr(Instruction *I, unsigned Flags) {
+ // Don't reorder a store over a load.
+ if ((Flags & SkipReadMem) && I->mayWriteToMemory())
+ return false;
+
+ // 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 ((Flags & SkipSideEffect) &&
+ (I->mayReadFromMemory() || I->mayHaveSideEffects()))
+ return false;
+
+ // Reordering across an instruction which does not necessarily transfer
+ // control to the next instruction is speculation.
+ if ((Flags & SkipImplicitControlFlow) && !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
@@ -1437,7 +1493,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
@@ -1460,7 +1517,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();
@@ -1486,75 +1543,107 @@ 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 any skipped instuctions that may read memory, write memory or have
+ // side effects, or have implicit control flow.
+ unsigned SkipFlagsBB1 = 0;
+ unsigned SkipFlagsBB2 = 0;
+
+ 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)) {
+ // 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, SkipFlagsBB1) ||
+ !isSafeToHoistInstr(I2, SkipFlagsBB2))
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())
- 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 have characteristics that would prevent reordering instructions
+ // across them.
+ SkipFlagsBB1 |= skippedInstrFlags(I1);
+ SkipFlagsBB2 |= skippedInstrFlags(I2);
+ ++NumSkipped;
}
- ++NumHoistCommonInstrs;
I1 = &*BB1_Itr++;
I2 = &*BB2_Itr++;
@@ -1567,9 +1656,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..fcb5d5203a9cb
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/hoist-common-skip.ll
@@ -0,0 +1,393 @@
+; 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
+}
+
+;; No reorder of store over a load.
+define i16 @f7(i1 %c, ptr %a, ptr %b) {
+; CHECK-LABEL: @f7(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br i1 [[C:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; CHECK: if.then:
+; CHECK-NEXT: [[VA:%.*]] = load i16, ptr [[A:%.*]], align 2
+; CHECK-NEXT: store i16 0, ptr [[B:%.*]], align 2
+; CHECK-NEXT: br label [[IF_END:%.*]]
+; CHECK: if.else:
+; CHECK-NEXT: [[VB:%.*]] = load i16, ptr [[B]], align 2
+; CHECK-NEXT: store i16 0, ptr [[B]], align 2
+; CHECK-NEXT: br label [[IF_END]]
+; CHECK: if.end:
+; CHECK-NEXT: [[V:%.*]] = phi i16 [ [[VA]], [[IF_THEN]] ], [ [[VB]], [[IF_ELSE]] ]
+; CHECK-NEXT: ret i16 [[V]]
+;
+entry:
+ br i1 %c, label %if.then, label %if.else
+
+if.then:
+ %va = load i16, ptr %a, align 2
+ store i16 0, ptr %b, align 2
+ br label %if.end
+
+if.else:
+ %vb = load i16, ptr %b, align 2
+ store i16 0, ptr %b, align 2
+ br label %if.end
+
+if.end:
+ %v = phi i16 [ %va, %if.then ], [ %vb, %if.else ]
+ ret i16 %v
+}
+
+;; Can reorder load over another load
+define i16 @f8(i1 %cond, ptr %a, ptr %b, ptr %c) {
+; CHECK-LABEL: @f8(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[C_0:%.*]] = load i16, ptr [[C:%.*]], align 2
+; CHECK-NEXT: br i1 [[COND:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; CHECK: if.then:
+; CHECK-NEXT: [[VA:%.*]] = load i16, ptr [[A:%.*]], align 2
+; CHECK-NEXT: br label [[IF_END:%.*]]
+; CHECK: if.else:
+; CHECK-NEXT: [[VB:%.*]] = load i16, ptr [[B:%.*]], align 2
+; CHECK-NEXT: br label [[IF_END]]
+; CHECK: if.end:
+; CHECK-NEXT: [[V:%.*]] = phi i16 [ [[VA]], [[IF_THEN]] ], [ [[VB]], [[IF_ELSE]] ]
+; CHECK-NEXT: [[U:%.*]] = phi i16 [ [[C_0]], [[IF_THEN]] ], [ [[C_0]], [[IF_ELSE]] ]
+; CHECK-NEXT: [[W:%.*]] = add i16 [[V]], [[U]]
+; CHECK-NEXT: ret i16 [[W]]
+;
+entry:
+ br i1 %cond, label %if.then, label %if.else
+
+if.then:
+ %va = load i16, ptr %a, align 2
+ %c.0 = load i16, ptr %c
+ br label %if.end
+
+if.else:
+ %vb = load i16, ptr %b, align 2
+ %c.1 = load i16, ptr %c
+ br label %if.end
+
+if.end:
+ %v = phi i16 [ %va, %if.then ], [ %vb, %if.else ]
+ %u = phi i16 [ %c.0, %if.then ], [ %c.1, %if.else ]
+
+ %w = add i16 %v, %u
+
+ ret i16 %w
+}
+
+;; Currently won't reorder volatile and non-volatile loads.
+define i16 @f9(i1 %cond, ptr %a, ptr %b, ptr %c) {
+; CHECK-LABEL: @f9(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br i1 [[COND:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
+; CHECK: if.then:
+; CHECK-NEXT: [[VA:%.*]] = load volatile i16, ptr [[A:%.*]], align 2
+; CHECK-NEXT: [[C_0:%.*]] = load i16, ptr [[C:%.*]], align 2
+; CHECK-NEXT: br label [[IF_END:%.*]]
+; CHECK: if.else:
+; CHECK-NEXT: [[VB:%.*]] = load i16, ptr [[B:%.*]], align 2
+; CHECK-NEXT: [[C_1:%.*]] = load i16, ptr [[C]], align 2
+; CHECK-NEXT: br label [[IF_END]]
+; CHECK: if.end:
+; CHECK-NEXT: [[V:%.*]] = phi i16 [ [[VA]], [[IF_THEN]] ], [ [[VB]], [[IF_ELSE]] ]
+; CHECK-NEXT: [[U:%.*]] = phi i16 [ [[C_0]], [[IF_THEN]] ], [ [[C_1]], [[IF_ELSE]] ]
+; CHECK-NEXT: [[W:%.*]] = add i16 [[V]], [[U]]
+; CHECK-NEXT: ret i16 [[W]]
+;
+entry:
+ br i1 %cond, label %if.then, label %if.else
+
+if.then:
+ %va = load volatile i16, ptr %a, align 2
+ %c.0 = load i16, ptr %c
+ br label %if.end
+
+if.else:
+ %vb = load i16, ptr %b, align 2
+ %c.1 = load i16, ptr %c
+ br label %if.end
+
+if.end:
+ %v = phi i16 [ %va, %if.then ], [ %vb, %if.else ]
+ %u = phi i16 [ %c.0, %if.then ], [ %c.1, %if.else ]
+
+ %w = add i16 %v, %u
+
+ ret i16 %w
+}
+
+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