From b3cc74c41eb35aa6aaa21c501ada63318384f704 Mon Sep 17 00:00:00 2001 From: ShengYi Hung Date: Wed, 15 Oct 2025 17:57:13 +0800 Subject: [PATCH 1/4] [Pass, SCCP] Support constant structure in PhiNode This patch adds support for constant propagation of individual structure members through Phi nodes. Each member is handled independently, allowing optimization opportunities when specific members become constant. Also, update the testcase since we are able to optimize the call parameter now. --- llvm/lib/Transforms/Utils/SCCPSolver.cpp | 96 +++++++++++++------ .../Transforms/SCCP/constant-range-struct.ll | 66 ++++++++++--- 2 files changed, 119 insertions(+), 43 deletions(-) diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp index b80c3c9e59441..9caa4d96e79be 100644 --- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp +++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp @@ -20,6 +20,7 @@ #include "llvm/Analysis/ValueLatticeUtils.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/ConstantRange.h" +#include "llvm/IR/DerivedTypes.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/InstVisitor.h" #include "llvm/IR/Instructions.h" @@ -760,6 +761,7 @@ class SCCPInstVisitor : public InstVisitor { void handleCallArguments(CallBase &CB); void handleExtractOfWithOverflow(ExtractValueInst &EVI, const WithOverflowInst *WO, unsigned Idx); + bool isInstUnderDefined(Instruction &Inst); private: friend class InstVisitor; @@ -1374,49 +1376,66 @@ bool SCCPInstVisitor::isEdgeFeasible(BasicBlock *From, BasicBlock *To) const { // 7. If a conditional branch has a value that is overdefined, make all // successors executable. void SCCPInstVisitor::visitPHINode(PHINode &PN) { - // If this PN returns a struct, just mark the result overdefined. - // TODO: We could do a lot better than this if code actually uses this. - if (PN.getType()->isStructTy()) - return (void)markOverdefined(&PN); - - if (getValueState(&PN).isOverdefined()) - return; // Quick exit - // Super-extra-high-degree PHI nodes are unlikely to ever be marked constant, // and slow us down a lot. Just mark them overdefined. if (PN.getNumIncomingValues() > 64) return (void)markOverdefined(&PN); - unsigned NumActiveIncoming = 0; + if (isInstUnderDefined(PN)) + return; + llvm::SmallVector FeasibleIncomingIndices; + for (unsigned i = 0, e = PN.getNumIncomingValues(); i != e; ++i) { + if (!isEdgeFeasible(PN.getIncomingBlock(i), PN.getParent())) + continue; + FeasibleIncomingIndices.push_back(i); + } // Look at all of the executable operands of the PHI node. If any of them // are overdefined, the PHI becomes overdefined as well. If they are all // constant, and they agree with each other, the PHI becomes the identical // constant. If they are constant and don't agree, the PHI is a constant // range. If there are no executable operands, the PHI remains unknown. - ValueLatticeElement PhiState = getValueState(&PN); - for (unsigned i = 0, e = PN.getNumIncomingValues(); i != e; ++i) { - if (!isEdgeFeasible(PN.getIncomingBlock(i), PN.getParent())) - continue; - - const ValueLatticeElement &IV = getValueState(PN.getIncomingValue(i)); - PhiState.mergeIn(IV); - NumActiveIncoming++; - if (PhiState.isOverdefined()) - break; + if (StructType *STy = dyn_cast(PN.getType())) { + for (unsigned i = 0, e = STy->getNumElements(); i != e; ++i) { + ValueLatticeElement PhiState = getStructValueState(&PN, i); + if (PhiState.isOverdefined()) + continue; + for (unsigned j : FeasibleIncomingIndices) { + const ValueLatticeElement &IV = + getStructValueState(PN.getIncomingValue(j), i); + PhiState.mergeIn(IV); + if (PhiState.isOverdefined()) + break; + } + mergeInValue(getStructValueState(&PN, i), &PN, PhiState, + ValueLatticeElement::MergeOptions().setMaxWidenSteps( + FeasibleIncomingIndices.size() + 1)); + ValueLatticeElement &PhiStateRef = getStructValueState(&PN, i); + PhiStateRef.setNumRangeExtensions( + std::max((unsigned)FeasibleIncomingIndices.size(), + PhiStateRef.getNumRangeExtensions())); + } + } else { + ValueLatticeElement PhiState = getValueState(&PN); + for (unsigned i : FeasibleIncomingIndices) { + const ValueLatticeElement &IV = getValueState(PN.getIncomingValue(i)); + PhiState.mergeIn(IV); + if (PhiState.isOverdefined()) + break; + } + // We allow up to 1 range extension per active incoming value and one + // additional extension. Note that we manually adjust the number of range + // extensions to match the number of active incoming values. This helps to + // limit multiple extensions caused by the same incoming value, if other + // incoming values are equal. + ValueLatticeElement &PhiStateRef = getValueState(&PN); + mergeInValue(PhiStateRef, &PN, PhiState, + ValueLatticeElement::MergeOptions().setMaxWidenSteps( + FeasibleIncomingIndices.size() + 1)); + PhiStateRef.setNumRangeExtensions( + std::max((unsigned)FeasibleIncomingIndices.size(), + PhiStateRef.getNumRangeExtensions())); } - - // We allow up to 1 range extension per active incoming value and one - // additional extension. Note that we manually adjust the number of range - // extensions to match the number of active incoming values. This helps to - // limit multiple extensions caused by the same incoming value, if other - // incoming values are equal. - ValueLatticeElement &PhiStateRef = ValueState[&PN]; - mergeInValue(PhiStateRef, &PN, PhiState, - ValueLatticeElement::MergeOptions().setMaxWidenSteps( - NumActiveIncoming + 1)); - PhiStateRef.setNumRangeExtensions( - std::max(NumActiveIncoming, PhiStateRef.getNumRangeExtensions())); } void SCCPInstVisitor::visitReturnInst(ReturnInst &I) { @@ -2127,6 +2146,21 @@ void SCCPInstVisitor::handleCallResult(CallBase &CB) { } } +bool SCCPInstVisitor::isInstUnderDefined(Instruction &Inst) { + // For structure Type, we handle each member seperately. + // A structure object won't be considered as overDefined when + // there is at least one member can become constant. + if (StructType *STy = dyn_cast(Inst.getType())) { + for (unsigned i = 0, e = STy->getNumElements(); i < e; ++i) { + if (!getStructValueState(&Inst, i).isOverdefined()) + return false; + } + return true; + } + + return getValueState(&Inst).isOverdefined(); +} + void SCCPInstVisitor::solve() { // Process the work lists until they are empty! while (!BBWorkList.empty() || !InstWorkList.empty()) { diff --git a/llvm/test/Transforms/SCCP/constant-range-struct.ll b/llvm/test/Transforms/SCCP/constant-range-struct.ll index 7a399df11fb13..46ebdc1b09db8 100644 --- a/llvm/test/Transforms/SCCP/constant-range-struct.ll +++ b/llvm/test/Transforms/SCCP/constant-range-struct.ll @@ -25,7 +25,7 @@ true: br label %exit false: - %s.3 = insertvalue {i64, i64} undef, i64 30, 0 + %s.3 = insertvalue {i64, i64} poison, i64 30, 0 %s.4 = insertvalue {i64, i64} %s.3, i64 300, 1 br label %exit @@ -39,14 +39,14 @@ define void @struct1_caller() { ; CHECK-NEXT: [[S:%.*]] = call { i64, i64 } @struct1() ; CHECK-NEXT: [[V1:%.*]] = extractvalue { i64, i64 } [[S]], 0 ; CHECK-NEXT: [[V2:%.*]] = extractvalue { i64, i64 } [[S]], 1 -; CHECK-NEXT: [[T_1:%.*]] = icmp ne i64 [[V1]], 10 -; CHECK-NEXT: call void @use(i1 [[T_1]]) -; CHECK-NEXT: [[T_2:%.*]] = icmp ult i64 [[V1]], 100 -; CHECK-NEXT: call void @use(i1 [[T_2]]) -; CHECK-NEXT: [[T_3:%.*]] = icmp ne i64 [[V2]], 0 +; CHECK-NEXT: call void @use(i1 true) +; CHECK-NEXT: call void @use(i1 true) +; CHECK-NEXT: [[T_3:%.*]] = icmp eq i64 [[V1]], 20 ; CHECK-NEXT: call void @use(i1 [[T_3]]) -; CHECK-NEXT: [[T_4:%.*]] = icmp ult i64 [[V2]], 301 -; CHECK-NEXT: call void @use(i1 [[T_4]]) +; CHECK-NEXT: call void @use(i1 true) +; CHECK-NEXT: call void @use(i1 true) +; CHECK-NEXT: [[T_6:%.*]] = icmp eq i64 [[V2]], 300 +; CHECK-NEXT: call void @use(i1 [[T_6]]) ; CHECK-NEXT: ret void ; %s = call {i64, i64} @struct1() @@ -57,10 +57,14 @@ define void @struct1_caller() { call void @use(i1 %t.1) %t.2 = icmp ult i64 %v1, 100 call void @use(i1 %t.2) - %t.3 = icmp ne i64 %v2, 0 + %t.3 = icmp eq i64 %v1, 20 call void @use(i1 %t.3) - %t.4 = icmp ult i64 %v2, 301 + %t.4 = icmp ne i64 %v2, 0 call void @use(i1 %t.4) + %t.5 = icmp ult i64 %v2, 301 + call void @use(i1 %t.5) + %t.6 = icmp eq i64 %v2, 300 + call void @use(i1 %t.6) ret void } @@ -76,7 +80,7 @@ define internal {i64, i64} @struct2() { ; CHECK: exit: ; CHECK-NEXT: [[V1:%.*]] = phi i64 [ 20, [[TRUE]] ], [ 30, [[FALSE]] ] ; CHECK-NEXT: [[V2:%.*]] = phi i64 [ 200, [[TRUE]] ], [ 300, [[FALSE]] ] -; CHECK-NEXT: [[S_1:%.*]] = insertvalue { i64, i64 } undef, i64 [[V1]], 0 +; CHECK-NEXT: [[S_1:%.*]] = insertvalue { i64, i64 } poison, i64 [[V1]], 0 ; CHECK-NEXT: [[S_2:%.*]] = insertvalue { i64, i64 } [[S_1]], i64 [[V2]], 1 ; CHECK-NEXT: ret { i64, i64 } [[S_2]] ; @@ -92,7 +96,7 @@ false: exit: %v1 = phi i64 [ 20, %true ], [ 30, %false ] %v2 = phi i64 [ 200, %true ], [ 300, %false ] - %s.1 = insertvalue {i64, i64} undef, i64 %v1, 0 + %s.1 = insertvalue {i64, i64} poison, i64 %v1, 0 %s.2 = insertvalue {i64, i64} %s.1, i64 %v2, 1 ret {i64, i64} %s.2 } @@ -153,3 +157,41 @@ define void @struct2_caller() { ret void } + +%"phi_type" = type {i64, i64} + +; Function Attrs: mustprogress +define internal noundef %"phi_type" @test(i32 noundef %input) local_unnamed_addr readnone nounwind { +; CHECK-LABEL: @test( +; CHECK-NEXT: br label [[COND_TRUE_I:%.*]] +; CHECK: cond.true.i: +; CHECK-NEXT: br label [[COND_END_I:%.*]] +; CHECK: cond.end.i: +; CHECK-NEXT: ret [[PHI_TYPE:%.*]] poison +; + %cmp.cond = icmp eq i32 %input, 1 + br i1 %cmp.cond, label %cond.true.i, label %cond.false.i + +cond.true.i: + %r1.tmp = insertvalue %"phi_type" poison, i64 1, 0 + %r1.tmp.2 = insertvalue %"phi_type" %r1.tmp, i64 2, 1 + br label %cond.end.i + +cond.false.i: + %r2.tmp = insertvalue %"phi_type" poison, i64 3, 0 + %r2.tmp.2 = insertvalue %"phi_type" %r2.tmp, i64 4, 1 + br label %cond.end.i + +cond.end.i: + %retval = phi %"phi_type" [ %r1.tmp.2, %cond.true.i ], [ %r2.tmp.2, %cond.false.i ] + ret %"phi_type" %retval +} + +define dso_local noundef %"phi_type" @test2() { +; CHECK-LABEL: @test2( +; CHECK-NEXT: [[CALL_1:%.*]] = tail call fastcc [[PHI_TYPE:%.*]] @[[TEST:[a-zA-Z0-9_$\"\\.-]*[a-zA-Z_$\"\\.-][a-zA-Z0-9_$\"\\.-]*]](i32 noundef 1) +; CHECK-NEXT: ret [[PHI_TYPE]] { i64 1, i64 2 } +; + %call.1 = tail call fastcc noundef %"phi_type" @test(i32 noundef 1) + ret %"phi_type" %call.1 +} From e8ea8c021722ca566ae849e3749928d0c0b0f83a Mon Sep 17 00:00:00 2001 From: SHENG-YI HONG Date: Fri, 17 Oct 2025 21:51:08 +0800 Subject: [PATCH 2/4] fixup! [Pass, SCCP] Support constant structure in PhiNode --- llvm/lib/Transforms/Utils/SCCPSolver.cpp | 14 +++++++------- llvm/test/Transforms/SCCP/constant-range-struct.ll | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp index 9caa4d96e79be..62347a05f3dd1 100644 --- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp +++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp @@ -761,7 +761,7 @@ class SCCPInstVisitor : public InstVisitor { void handleCallArguments(CallBase &CB); void handleExtractOfWithOverflow(ExtractValueInst &EVI, const WithOverflowInst *WO, unsigned Idx); - bool isInstUnderDefined(Instruction &Inst); + bool isInstOverDefined(Instruction &Inst); private: friend class InstVisitor; @@ -1381,9 +1381,9 @@ void SCCPInstVisitor::visitPHINode(PHINode &PN) { if (PN.getNumIncomingValues() > 64) return (void)markOverdefined(&PN); - if (isInstUnderDefined(PN)) + if (isInstOverDefined(PN)) return; - llvm::SmallVector FeasibleIncomingIndices; + SmallVector FeasibleIncomingIndices; for (unsigned i = 0, e = PN.getNumIncomingValues(); i != e; ++i) { if (!isEdgeFeasible(PN.getIncomingBlock(i), PN.getParent())) continue; @@ -2146,10 +2146,10 @@ void SCCPInstVisitor::handleCallResult(CallBase &CB) { } } -bool SCCPInstVisitor::isInstUnderDefined(Instruction &Inst) { - // For structure Type, we handle each member seperately. - // A structure object won't be considered as overDefined when - // there is at least one member can become constant. +bool SCCPInstVisitor::isInstOverDefined(Instruction &Inst) { + // For structure Type, we handle each member separately. + // A structure object won't be considered as overdefined when + // there is at least one member that is not overdefined. if (StructType *STy = dyn_cast(Inst.getType())) { for (unsigned i = 0, e = STy->getNumElements(); i < e; ++i) { if (!getStructValueState(&Inst, i).isOverdefined()) diff --git a/llvm/test/Transforms/SCCP/constant-range-struct.ll b/llvm/test/Transforms/SCCP/constant-range-struct.ll index 46ebdc1b09db8..bd6c11c2e4113 100644 --- a/llvm/test/Transforms/SCCP/constant-range-struct.ll +++ b/llvm/test/Transforms/SCCP/constant-range-struct.ll @@ -161,7 +161,7 @@ define void @struct2_caller() { %"phi_type" = type {i64, i64} ; Function Attrs: mustprogress -define internal noundef %"phi_type" @test(i32 noundef %input) local_unnamed_addr readnone nounwind { +define internal %"phi_type" @test(i32 %input) { ; CHECK-LABEL: @test( ; CHECK-NEXT: br label [[COND_TRUE_I:%.*]] ; CHECK: cond.true.i: @@ -187,7 +187,7 @@ cond.end.i: ret %"phi_type" %retval } -define dso_local noundef %"phi_type" @test2() { +define %"phi_type" @test2() { ; CHECK-LABEL: @test2( ; CHECK-NEXT: [[CALL_1:%.*]] = tail call fastcc [[PHI_TYPE:%.*]] @[[TEST:[a-zA-Z0-9_$\"\\.-]*[a-zA-Z_$\"\\.-][a-zA-Z0-9_$\"\\.-]*]](i32 noundef 1) ; CHECK-NEXT: ret [[PHI_TYPE]] { i64 1, i64 2 } From 7defdffcbb68c31c3646903b7d66ea260de53782 Mon Sep 17 00:00:00 2001 From: SHENG-YI HONG Date: Fri, 17 Oct 2025 22:17:57 +0800 Subject: [PATCH 3/4] fixup! [Pass, SCCP] Support constant structure in PhiNode --- llvm/lib/Transforms/Utils/SCCPSolver.cpp | 6 +++--- llvm/test/Transforms/SCCP/constant-range-struct.ll | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp index 62347a05f3dd1..e31a66c1647e5 100644 --- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp +++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp @@ -761,7 +761,7 @@ class SCCPInstVisitor : public InstVisitor { void handleCallArguments(CallBase &CB); void handleExtractOfWithOverflow(ExtractValueInst &EVI, const WithOverflowInst *WO, unsigned Idx); - bool isInstOverDefined(Instruction &Inst); + bool isInstFullyOverDefined(Instruction &Inst); private: friend class InstVisitor; @@ -1381,7 +1381,7 @@ void SCCPInstVisitor::visitPHINode(PHINode &PN) { if (PN.getNumIncomingValues() > 64) return (void)markOverdefined(&PN); - if (isInstOverDefined(PN)) + if (isInstFullyOverDefined(PN)) return; SmallVector FeasibleIncomingIndices; for (unsigned i = 0, e = PN.getNumIncomingValues(); i != e; ++i) { @@ -2146,7 +2146,7 @@ void SCCPInstVisitor::handleCallResult(CallBase &CB) { } } -bool SCCPInstVisitor::isInstOverDefined(Instruction &Inst) { +bool SCCPInstVisitor::isInstFullyOverDefined(Instruction &Inst) { // For structure Type, we handle each member separately. // A structure object won't be considered as overdefined when // there is at least one member that is not overdefined. diff --git a/llvm/test/Transforms/SCCP/constant-range-struct.ll b/llvm/test/Transforms/SCCP/constant-range-struct.ll index bd6c11c2e4113..0f45b38cb39b5 100644 --- a/llvm/test/Transforms/SCCP/constant-range-struct.ll +++ b/llvm/test/Transforms/SCCP/constant-range-struct.ll @@ -160,7 +160,6 @@ define void @struct2_caller() { %"phi_type" = type {i64, i64} -; Function Attrs: mustprogress define internal %"phi_type" @test(i32 %input) { ; CHECK-LABEL: @test( ; CHECK-NEXT: br label [[COND_TRUE_I:%.*]] From 0d22afde7d4ad6fb2b58c3362066c941b1de855a Mon Sep 17 00:00:00 2001 From: ShengYi Hung Date: Sun, 19 Oct 2025 11:24:47 +0800 Subject: [PATCH 4/4] fixup! [Pass, SCCP] Support constant structure in PhiNode --- llvm/lib/Transforms/Utils/SCCPSolver.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp index e31a66c1647e5..4947d03a2dc66 100644 --- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp +++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp @@ -1407,10 +1407,10 @@ void SCCPInstVisitor::visitPHINode(PHINode &PN) { if (PhiState.isOverdefined()) break; } - mergeInValue(getStructValueState(&PN, i), &PN, PhiState, + ValueLatticeElement &PhiStateRef = getStructValueState(&PN, i); + mergeInValue(PhiStateRef, &PN, PhiState, ValueLatticeElement::MergeOptions().setMaxWidenSteps( FeasibleIncomingIndices.size() + 1)); - ValueLatticeElement &PhiStateRef = getStructValueState(&PN, i); PhiStateRef.setNumRangeExtensions( std::max((unsigned)FeasibleIncomingIndices.size(), PhiStateRef.getNumRangeExtensions())); @@ -1428,7 +1428,7 @@ void SCCPInstVisitor::visitPHINode(PHINode &PN) { // extensions to match the number of active incoming values. This helps to // limit multiple extensions caused by the same incoming value, if other // incoming values are equal. - ValueLatticeElement &PhiStateRef = getValueState(&PN); + ValueLatticeElement &PhiStateRef = ValueState[&PN]; mergeInValue(PhiStateRef, &PN, PhiState, ValueLatticeElement::MergeOptions().setMaxWidenSteps( FeasibleIncomingIndices.size() + 1));