[llvm] [SimplifyCFG] Optimize select over pointers to eliminate no-op load/store (PR #179277)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Feb 12 02:00:01 PST 2026
https://github.com/dnsampaio updated https://github.com/llvm/llvm-project/pull/179277
>From abb08aacd2ea08ca9f7ed171d614c7613273105c Mon Sep 17 00:00:00 2001
From: Diogo Sampaio <diogo.sampaio at openchip.com>
Date: Mon, 2 Feb 2026 17:01:31 +0100
Subject: [PATCH] [SimplifyCFG] Optimize select over pointers to eliminate
no-op load/store operations
Transform a pattern where one case of a select over pointers leads to a
store(load) of the same address, avoiding the load and store all together.
Pattern:
%gepA = getelementptr ... %a, ...
%gepB = getelementptr ... %b, ...
%sel = select i1 %cond, ptr %gepA, ptr %gepB
%val = load ... %sel
store ... %val, %gepA
When %sel chooses %gepA, the load and store
access the same address - this is a no-op.
This transforms it to:
br i1 %cond, label %cont, label %do_ldst
do_ldst:
%gepA = getelementptr ... %a, ...
%gepB = getelementptr ... %b, ...
%val = load ... %gepB
store ... %val, %gepA
br label %cont
cont:
...
This optimization is beneficial over loops, where the select
will avoid the vectorizer to hadle the select, but with the
conditional branch it will be converted to load and masked store.
---
llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 174 ++++++++
.../SimplifyCFG/select-pointer-noop-store.ll | 375 ++++++++++++++++++
2 files changed, 549 insertions(+)
create mode 100644 llvm/test/Transforms/SimplifyCFG/select-pointer-noop-store.ll
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 8b56109990b21..c79d01b696445 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -353,6 +353,174 @@ isSelectInRoleOfConjunctionOrDisjunction(const SelectInst *SI) {
} // end anonymous namespace
+/// Transform a pattern where one case of a select over pointers leads to a
+/// store(load) of the same address, avoiding the load and store all together.
+///
+/// Pattern:
+/// %gepA = getelementptr ... %a, ...
+/// %gepB = getelementptr ... %b, ...
+/// %sel = select i1 %cond, ptr %gepA, ptr %gepB
+/// %val = load ... %sel
+/// store ... %val, %gepA
+///
+/// When %sel chooses %gepA, the load and store
+/// access the same address - this is a no-op.
+///
+/// This transforms it to:
+/// br i1 %cond, label %cont, label %do_ldst
+/// do_ldst:
+/// %gepA = getelementptr ... %a, ...
+/// %gepB = getelementptr ... %b, ...
+/// %val = load ... %gepB
+/// store ... %val, %gepA
+/// br label %cont
+/// cont:
+/// ...
+///
+///
+/// This optimization is beneficial over loops, where the select
+/// will avoid the vectorizer to hadle the select, but with the
+/// conditional branch it will be converted to load and masked store.
+static bool foldSelectPointerLoadStoreToNoop(BasicBlock *BB,
+ DomTreeUpdater *DTU) {
+ for (Instruction &I : *BB) {
+ auto *Sel = dyn_cast<SelectInst>(&I);
+ if (!Sel || !Sel->getType()->isPointerTy() || !Sel->hasOneUse())
+ continue;
+
+ Value *Cond = Sel->getCondition();
+ Value *TruePtr = Sel->getTrueValue();
+ Value *FalsePtr = Sel->getFalseValue();
+
+ // Select must be used by a GEP
+ auto *LoadGEP = dyn_cast<GetElementPtrInst>(*Sel->user_begin());
+ if (!LoadGEP || LoadGEP->getParent() != BB ||
+ LoadGEP->getPointerOperand() != Sel || !LoadGEP->hasOneUse())
+ continue;
+
+ // LoadGEP must be used by a load
+ auto *LI = dyn_cast<LoadInst>(*LoadGEP->user_begin());
+ if (!LI || LI->getParent() != BB || !LI->hasOneUse() || LI->isVolatile())
+ continue;
+
+ // Load must be used by a store
+ auto *SI = dyn_cast<StoreInst>(*LI->user_begin());
+ if (!SI || SI->getParent() != BB || SI->getValueOperand() != LI ||
+ SI->isVolatile())
+ continue;
+
+ // Store destination must be a GEP with single use
+ auto *StoreGEP = dyn_cast<GetElementPtrInst>(SI->getPointerOperand());
+ if (!StoreGEP || StoreGEP->getParent() != BB || !StoreGEP->hasOneUse())
+ continue;
+
+ // Check if indices match between load GEP and store GEP
+ if (LoadGEP->getNumIndices() != StoreGEP->getNumIndices())
+ continue;
+
+ bool IndicesMatch = true;
+ for (auto [LoadIdx, StoreIdx] :
+ zip(LoadGEP->indices(), StoreGEP->indices())) {
+ if (LoadIdx != StoreIdx) {
+ IndicesMatch = false;
+ break;
+ }
+ }
+ if (!IndicesMatch)
+ continue;
+
+ // Check if one of the select operands matches the store GEP's base pointer
+ Value *StoreBase = StoreGEP->getPointerOperand();
+ Value *EffectivePtr = nullptr; // The pointer that does actual work
+ bool NoopOnTrue = false;
+
+ if (TruePtr == StoreBase) {
+ EffectivePtr = FalsePtr;
+ NoopOnTrue = true;
+ } else if (FalsePtr == StoreBase) {
+ EffectivePtr = TruePtr;
+ NoopOnTrue = false;
+ } else {
+ continue;
+ }
+
+ LLVM_DEBUG(dbgs() << "FOLDING SELECT LOAD STORE TO NOOP: " << *Sel << "\n");
+ LLVM_DEBUG(dbgs() << " LoadGEP: " << *LoadGEP << "\n");
+ LLVM_DEBUG(dbgs() << " Load: " << *LI << "\n");
+ LLVM_DEBUG(dbgs() << " StoreGEP: " << *StoreGEP << "\n");
+ LLVM_DEBUG(dbgs() << " Store: " << *SI << "\n");
+ LLVM_DEBUG(dbgs() << " Noop on " << (NoopOnTrue ? "true" : "false")
+ << "\n");
+
+ // Create a branch that skips the load/store
+ Function *F = BB->getParent();
+ BasicBlock *DoLdStBB =
+ BasicBlock::Create(F->getContext(), BB->getName() + ".do_ldst", F);
+ BasicBlock *ContBB =
+ BB->splitBasicBlock(SI->getIterator(), BB->getName() + ".cont");
+
+ // Remove the unconditional branch created by splitBasicBlock
+ BB->getTerminator()->eraseFromParent();
+
+ // Create conditional branch: skip load/store on noop case
+ IRBuilder<> Builder(BB);
+ if (NoopOnTrue) {
+ Builder.CreateCondBr(Cond, ContBB, DoLdStBB);
+ } else {
+ Builder.CreateCondBr(Cond, DoLdStBB, ContBB);
+ }
+
+ // Build the load/store in DoLdStBB
+ Builder.SetInsertPoint(DoLdStBB);
+
+ // Create new load GEP with the effective pointer
+ SmallVector<Value *, 4> Indices(LoadGEP->indices());
+ auto *NewLoadGEP =
+ Builder.CreateGEP(LoadGEP->getSourceElementType(), EffectivePtr,
+ Indices, LoadGEP->getName() + ".eff");
+ if (LoadGEP->isInBounds())
+ cast<GetElementPtrInst>(NewLoadGEP)->setIsInBounds(true);
+
+ // Create new load
+ auto *NewLoad = Builder.CreateAlignedLoad(
+ LI->getType(), NewLoadGEP, LI->getAlign(), LI->getName() + ".eff");
+
+ // Create new store GEP
+ SmallVector<Value *, 4> StoreIndices(StoreGEP->indices());
+ auto *NewStoreGEP =
+ Builder.CreateGEP(StoreGEP->getSourceElementType(), StoreBase,
+ StoreIndices, StoreGEP->getName() + ".eff");
+ if (StoreGEP->isInBounds())
+ cast<GetElementPtrInst>(NewStoreGEP)->setIsInBounds(true);
+
+ // Create new store
+ Builder.CreateAlignedStore(NewLoad, NewStoreGEP, SI->getAlign());
+
+ // Branch to continuation
+ Builder.CreateBr(ContBB);
+
+ // Remove the original instructions
+ SI->eraseFromParent();
+ LI->eraseFromParent();
+ StoreGEP->eraseFromParent();
+ LoadGEP->eraseFromParent();
+ Sel->eraseFromParent();
+
+ // Update dominator tree
+ if (DTU) {
+ SmallVector<DominatorTree::UpdateType, 4> Updates;
+ Updates.push_back({DominatorTree::Insert, BB, DoLdStBB});
+ Updates.push_back({DominatorTree::Insert, BB, ContBB});
+ Updates.push_back({DominatorTree::Insert, DoLdStBB, ContBB});
+ DTU->applyUpdates(Updates);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
/// Return true if all the PHI nodes in the basic block \p BB
/// receive compatible (identical) incoming values when coming from
/// all of the predecessor blocks that are specified in \p IncomingBlocks.
@@ -8950,6 +9118,12 @@ bool SimplifyCFGOpt::simplifyOnce(BasicBlock *BB) {
break;
}
+ // Check for select over pointers that leads to a load/store where one
+ // case is a no-op. Transform to conditional branch.
+ if (!RequireAndPreserveDomTree)
+ if (foldSelectPointerLoadStoreToNoop(BB, DTU))
+ Changed |= requestResimplify();
+
return Changed;
}
diff --git a/llvm/test/Transforms/SimplifyCFG/select-pointer-noop-store.ll b/llvm/test/Transforms/SimplifyCFG/select-pointer-noop-store.ll
new file mode 100644
index 0000000000000..08fa566790c41
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/select-pointer-noop-store.ll
@@ -0,0 +1,375 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -S -passes=simplifycfg < %s | FileCheck %s
+
+; Test the optimization that transforms select-over-pointers followed by
+; GEP, load/store where one case is a no-op (load and store to same address).
+;
+; Pattern: A[i] = P[i] ? B[i] : A[i]
+; When P[i] is false, we load from A[i] and store to A[i] - a no-op.
+; The optimization creates a branch to skip the load/store in that case.
+;
+; Requires GEPs for both load and store with matching indices, as this is
+; required for the vectorizer to convert conditional load/stores to masked
+; store.
+
+;----------------------------------------------------------------------------
+; POSITIVE CASES - should be transformed
+;----------------------------------------------------------------------------
+
+; Basic case: noop when condition is true (TrueValue matches store base)
+define void @noop_on_true(ptr %a, ptr %b, i1 %cond, i64 %idx) {
+; CHECK-LABEL: define void @noop_on_true(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br i1 [[COND]], label %[[ENTRY_CONT:.*]], label %[[ENTRY_DO_LDST:.*]]
+; CHECK: [[ENTRY_CONT]]:
+; CHECK-NEXT: ret void
+; CHECK: [[ENTRY_DO_LDST]]:
+; CHECK-NEXT: [[GEP_EFF:%.*]] = getelementptr float, ptr [[B]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL_EFF:%.*]] = load float, ptr [[GEP_EFF]], align 4
+; CHECK-NEXT: [[STORE_GEP_EFF:%.*]] = getelementptr float, ptr [[A]], i64 [[IDX]]
+; CHECK-NEXT: store float [[VAL_EFF]], ptr [[STORE_GEP_EFF]], align 4
+; CHECK-NEXT: br label %[[ENTRY_CONT]]
+;
+entry:
+ ; When cond is true: sel = %a, load from a[i], store to a[i] -> noop
+ ; When cond is false: sel = %b, load from b[i], store to a[i] -> effective
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr float, ptr %sel, i64 %idx
+ %val = load float, ptr %gep
+ %store_gep = getelementptr float, ptr %a, i64 %idx
+ store float %val, ptr %store_gep
+ ret void
+}
+
+; Basic case: noop when condition is false (FalseValue matches store base)
+define void @noop_on_false(ptr %a, ptr %b, i1 %cond, i64 %idx) {
+; CHECK-LABEL: define void @noop_on_false(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br i1 [[COND]], label %[[ENTRY_DO_LDST:.*]], label %[[ENTRY_CONT:.*]]
+; CHECK: [[ENTRY_CONT]]:
+; CHECK-NEXT: ret void
+; CHECK: [[ENTRY_DO_LDST]]:
+; CHECK-NEXT: [[GEP_EFF:%.*]] = getelementptr float, ptr [[A]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL_EFF:%.*]] = load float, ptr [[GEP_EFF]], align 4
+; CHECK-NEXT: [[STORE_GEP_EFF:%.*]] = getelementptr float, ptr [[B]], i64 [[IDX]]
+; CHECK-NEXT: store float [[VAL_EFF]], ptr [[STORE_GEP_EFF]], align 4
+; CHECK-NEXT: br label %[[ENTRY_CONT]]
+;
+entry:
+ ; When cond is true: sel = %a, load from a[i], store to b[i] -> effective
+ ; When cond is false: sel = %b, load from b[i], store to b[i] -> noop
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr float, ptr %sel, i64 %idx
+ %val = load float, ptr %gep
+ %store_gep = getelementptr float, ptr %b, i64 %idx
+ store float %val, ptr %store_gep
+ ret void
+}
+
+; With inbounds GEPs - should preserve inbounds attribute
+define void @with_inbounds(ptr %a, ptr %b, i1 %cond, i64 %idx) {
+; CHECK-LABEL: define void @with_inbounds(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br i1 [[COND]], label %[[ENTRY_CONT:.*]], label %[[ENTRY_DO_LDST:.*]]
+; CHECK: [[ENTRY_CONT]]:
+; CHECK-NEXT: ret void
+; CHECK: [[ENTRY_DO_LDST]]:
+; CHECK-NEXT: [[GEP_EFF:%.*]] = getelementptr inbounds float, ptr [[B]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL_EFF:%.*]] = load float, ptr [[GEP_EFF]], align 4
+; CHECK-NEXT: [[STORE_GEP_EFF:%.*]] = getelementptr inbounds float, ptr [[A]], i64 [[IDX]]
+; CHECK-NEXT: store float [[VAL_EFF]], ptr [[STORE_GEP_EFF]], align 4
+; CHECK-NEXT: br label %[[ENTRY_CONT]]
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr inbounds float, ptr %sel, i64 %idx
+ %val = load float, ptr %gep
+ %store_gep = getelementptr inbounds float, ptr %a, i64 %idx
+ store float %val, ptr %store_gep
+ ret void
+}
+
+; With multiple indices in GEP
+define void @multi_index_gep(ptr %a, ptr %b, i1 %cond, i64 %idx1, i64 %idx2) {
+; CHECK-LABEL: define void @multi_index_gep(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX1:%.*]], i64 [[IDX2:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br i1 [[COND]], label %[[ENTRY_CONT:.*]], label %[[ENTRY_DO_LDST:.*]]
+; CHECK: [[ENTRY_CONT]]:
+; CHECK-NEXT: ret void
+; CHECK: [[ENTRY_DO_LDST]]:
+; CHECK-NEXT: [[GEP_EFF:%.*]] = getelementptr [10 x float], ptr [[B]], i64 [[IDX1]], i64 [[IDX2]]
+; CHECK-NEXT: [[VAL_EFF:%.*]] = load float, ptr [[GEP_EFF]], align 4
+; CHECK-NEXT: [[STORE_GEP_EFF:%.*]] = getelementptr [10 x float], ptr [[A]], i64 [[IDX1]], i64 [[IDX2]]
+; CHECK-NEXT: store float [[VAL_EFF]], ptr [[STORE_GEP_EFF]], align 4
+; CHECK-NEXT: br label %[[ENTRY_CONT]]
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr [10 x float], ptr %sel, i64 %idx1, i64 %idx2
+ %val = load float, ptr %gep
+ %store_gep = getelementptr [10 x float], ptr %a, i64 %idx1, i64 %idx2
+ store float %val, ptr %store_gep
+ ret void
+}
+
+; With integer type instead of float
+define void @integer_type(ptr %a, ptr %b, i1 %cond, i64 %idx) {
+; CHECK-LABEL: define void @integer_type(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br i1 [[COND]], label %[[ENTRY_CONT:.*]], label %[[ENTRY_DO_LDST:.*]]
+; CHECK: [[ENTRY_CONT]]:
+; CHECK-NEXT: ret void
+; CHECK: [[ENTRY_DO_LDST]]:
+; CHECK-NEXT: [[GEP_EFF:%.*]] = getelementptr i32, ptr [[B]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL_EFF:%.*]] = load i32, ptr [[GEP_EFF]], align 4
+; CHECK-NEXT: [[STORE_GEP_EFF:%.*]] = getelementptr i32, ptr [[A]], i64 [[IDX]]
+; CHECK-NEXT: store i32 [[VAL_EFF]], ptr [[STORE_GEP_EFF]], align 4
+; CHECK-NEXT: br label %[[ENTRY_CONT]]
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr i32, ptr %sel, i64 %idx
+ %val = load i32, ptr %gep
+ %store_gep = getelementptr i32, ptr %a, i64 %idx
+ store i32 %val, ptr %store_gep
+ ret void
+}
+
+; With double type and alignment
+define void @double_type_aligned(ptr %a, ptr %b, i1 %cond, i64 %idx) {
+; CHECK-LABEL: define void @double_type_aligned(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br i1 [[COND]], label %[[ENTRY_CONT:.*]], label %[[ENTRY_DO_LDST:.*]]
+; CHECK: [[ENTRY_CONT]]:
+; CHECK-NEXT: ret void
+; CHECK: [[ENTRY_DO_LDST]]:
+; CHECK-NEXT: [[GEP_EFF:%.*]] = getelementptr double, ptr [[B]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL_EFF:%.*]] = load double, ptr [[GEP_EFF]], align 8
+; CHECK-NEXT: [[STORE_GEP_EFF:%.*]] = getelementptr double, ptr [[A]], i64 [[IDX]]
+; CHECK-NEXT: store double [[VAL_EFF]], ptr [[STORE_GEP_EFF]], align 8
+; CHECK-NEXT: br label %[[ENTRY_CONT]]
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr double, ptr %sel, i64 %idx
+ %val = load double, ptr %gep, align 8
+ %store_gep = getelementptr double, ptr %a, i64 %idx
+ store double %val, ptr %store_gep, align 8
+ ret void
+}
+
+;----------------------------------------------------------------------------
+; NEGATIVE CASES - should NOT be transformed
+;----------------------------------------------------------------------------
+
+; Store base doesn't match either select operand
+define void @no_match_store_base(ptr %a, ptr %b, ptr %c, i1 %cond, i64 %idx) {
+; CHECK-LABEL: define void @no_match_store_base(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], ptr [[C:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND]], ptr [[A]], ptr [[B]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr float, ptr [[SEL]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL:%.*]] = load float, ptr [[GEP]], align 4
+; CHECK-NEXT: [[STORE_GEP:%.*]] = getelementptr float, ptr [[C]], i64 [[IDX]]
+; CHECK-NEXT: store float [[VAL]], ptr [[STORE_GEP]], align 4
+; CHECK-NEXT: ret void
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr float, ptr %sel, i64 %idx
+ %val = load float, ptr %gep
+ %store_gep = getelementptr float, ptr %c, i64 %idx
+ store float %val, ptr %store_gep
+ ret void
+}
+
+; Different indices between load GEP and store GEP
+define void @different_indices(ptr %a, ptr %b, i1 %cond, i64 %idx1, i64 %idx2) {
+; CHECK-LABEL: define void @different_indices(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX1:%.*]], i64 [[IDX2:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND]], ptr [[A]], ptr [[B]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr float, ptr [[SEL]], i64 [[IDX1]]
+; CHECK-NEXT: [[VAL:%.*]] = load float, ptr [[GEP]], align 4
+; CHECK-NEXT: [[STORE_GEP:%.*]] = getelementptr float, ptr [[A]], i64 [[IDX2]]
+; CHECK-NEXT: store float [[VAL]], ptr [[STORE_GEP]], align 4
+; CHECK-NEXT: ret void
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr float, ptr %sel, i64 %idx1
+ %val = load float, ptr %gep
+ %store_gep = getelementptr float, ptr %a, i64 %idx2
+ store float %val, ptr %store_gep
+ ret void
+}
+
+; Volatile load - don't transform
+define void @volatile_load(ptr %a, ptr %b, i1 %cond, i64 %idx) {
+; CHECK-LABEL: define void @volatile_load(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND]], ptr [[A]], ptr [[B]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr float, ptr [[SEL]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL:%.*]] = load volatile float, ptr [[GEP]], align 4
+; CHECK-NEXT: [[STORE_GEP:%.*]] = getelementptr float, ptr [[A]], i64 [[IDX]]
+; CHECK-NEXT: store float [[VAL]], ptr [[STORE_GEP]], align 4
+; CHECK-NEXT: ret void
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr float, ptr %sel, i64 %idx
+ %val = load volatile float, ptr %gep
+ %store_gep = getelementptr float, ptr %a, i64 %idx
+ store float %val, ptr %store_gep
+ ret void
+}
+
+; Volatile store - don't transform
+define void @volatile_store(ptr %a, ptr %b, i1 %cond, i64 %idx) {
+; CHECK-LABEL: define void @volatile_store(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND]], ptr [[A]], ptr [[B]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr float, ptr [[SEL]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL:%.*]] = load float, ptr [[GEP]], align 4
+; CHECK-NEXT: [[STORE_GEP:%.*]] = getelementptr float, ptr [[A]], i64 [[IDX]]
+; CHECK-NEXT: store volatile float [[VAL]], ptr [[STORE_GEP]], align 4
+; CHECK-NEXT: ret void
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr float, ptr %sel, i64 %idx
+ %val = load float, ptr %gep
+ %store_gep = getelementptr float, ptr %a, i64 %idx
+ store volatile float %val, ptr %store_gep
+ ret void
+}
+
+; Select has multiple uses - don't transform
+define ptr @select_multi_use(ptr %a, ptr %b, i1 %cond, i64 %idx) {
+; CHECK-LABEL: define ptr @select_multi_use(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND]], ptr [[A]], ptr [[B]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr float, ptr [[SEL]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL:%.*]] = load float, ptr [[GEP]], align 4
+; CHECK-NEXT: [[STORE_GEP:%.*]] = getelementptr float, ptr [[A]], i64 [[IDX]]
+; CHECK-NEXT: store float [[VAL]], ptr [[STORE_GEP]], align 4
+; CHECK-NEXT: ret ptr [[SEL]]
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr float, ptr %sel, i64 %idx
+ %val = load float, ptr %gep
+ %store_gep = getelementptr float, ptr %a, i64 %idx
+ store float %val, ptr %store_gep
+ ret ptr %sel
+}
+
+; LoadGEP has multiple uses - don't transform
+define ptr @load_gep_multi_use(ptr %a, ptr %b, i1 %cond, i64 %idx) {
+; CHECK-LABEL: define ptr @load_gep_multi_use(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND]], ptr [[A]], ptr [[B]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr float, ptr [[SEL]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL:%.*]] = load float, ptr [[GEP]], align 4
+; CHECK-NEXT: [[STORE_GEP:%.*]] = getelementptr float, ptr [[A]], i64 [[IDX]]
+; CHECK-NEXT: store float [[VAL]], ptr [[STORE_GEP]], align 4
+; CHECK-NEXT: ret ptr [[GEP]]
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr float, ptr %sel, i64 %idx
+ %val = load float, ptr %gep
+ %store_gep = getelementptr float, ptr %a, i64 %idx
+ store float %val, ptr %store_gep
+ ret ptr %gep
+}
+
+; StoreGEP has multiple uses - don't transform
+define ptr @store_gep_multi_use(ptr %a, ptr %b, i1 %cond, i64 %idx) {
+; CHECK-LABEL: define ptr @store_gep_multi_use(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND]], ptr [[A]], ptr [[B]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr float, ptr [[SEL]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL:%.*]] = load float, ptr [[GEP]], align 4
+; CHECK-NEXT: [[STORE_GEP:%.*]] = getelementptr float, ptr [[A]], i64 [[IDX]]
+; CHECK-NEXT: store float [[VAL]], ptr [[STORE_GEP]], align 4
+; CHECK-NEXT: ret ptr [[STORE_GEP]]
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr float, ptr %sel, i64 %idx
+ %val = load float, ptr %gep
+ %store_gep = getelementptr float, ptr %a, i64 %idx
+ store float %val, ptr %store_gep
+ ret ptr %store_gep
+}
+
+; Load has multiple uses - don't transform
+define float @load_multi_use(ptr %a, ptr %b, i1 %cond, i64 %idx) {
+; CHECK-LABEL: define float @load_multi_use(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND]], ptr [[A]], ptr [[B]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr float, ptr [[SEL]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL:%.*]] = load float, ptr [[GEP]], align 4
+; CHECK-NEXT: [[STORE_GEP:%.*]] = getelementptr float, ptr [[A]], i64 [[IDX]]
+; CHECK-NEXT: store float [[VAL]], ptr [[STORE_GEP]], align 4
+; CHECK-NEXT: ret float [[VAL]]
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr float, ptr %sel, i64 %idx
+ %val = load float, ptr %gep
+ %store_gep = getelementptr float, ptr %a, i64 %idx
+ store float %val, ptr %store_gep
+ ret float %val
+}
+
+; Direct pointer (no GEP) - don't transform
+define void @direct_ptr_no_gep(ptr %a, ptr %b, i1 %cond) {
+; CHECK-LABEL: define void @direct_ptr_no_gep(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND]], ptr [[A]], ptr [[B]]
+; CHECK-NEXT: [[VAL:%.*]] = load float, ptr [[SEL]], align 4
+; CHECK-NEXT: store float [[VAL]], ptr [[A]], align 4
+; CHECK-NEXT: ret void
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %val = load float, ptr %sel
+ store float %val, ptr %a
+ ret void
+}
+
+; Store doesn't use loaded value - don't transform
+define void @store_different_value(ptr %a, ptr %b, i1 %cond, i64 %idx, float %other) {
+; CHECK-LABEL: define void @store_different_value(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i1 [[COND:%.*]], i64 [[IDX:%.*]], float [[OTHER:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND]], ptr [[A]], ptr [[B]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr float, ptr [[SEL]], i64 [[IDX]]
+; CHECK-NEXT: [[VAL:%.*]] = load float, ptr [[GEP]], align 4
+; CHECK-NEXT: [[STORE_GEP:%.*]] = getelementptr float, ptr [[A]], i64 [[IDX]]
+; CHECK-NEXT: store float [[OTHER]], ptr [[STORE_GEP]], align 4
+; CHECK-NEXT: ret void
+;
+entry:
+ %sel = select i1 %cond, ptr %a, ptr %b
+ %gep = getelementptr float, ptr %sel, i64 %idx
+ %val = load float, ptr %gep
+ %store_gep = getelementptr float, ptr %a, i64 %idx
+ store float %other, ptr %store_gep
+ ret void
+}
More information about the llvm-commits
mailing list