Skip to content

Conversation

aokblast
Copy link
Contributor

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.

@llvmbot
Copy link
Member

llvmbot commented Oct 16, 2025

@llvm/pr-subscribers-function-specialization

@llvm/pr-subscribers-llvm-transforms

Author: None (aokblast)

Changes

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.


Full diff: https://github.com/llvm/llvm-project/pull/163713.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/Utils/SCCPSolver.cpp (+53-29)
  • (modified) llvm/test/Transforms/SCCP/constant-range-struct.ll (+51-9)
diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
index 9693ae6b8ceb5..f030e2e1391f4 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"
@@ -1383,49 +1384,72 @@ 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;
-
   // 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);
+  if (StructType *STy = dyn_cast<StructType>(PN.getType())) {
+    for (unsigned i = 0, e = STy->getNumElements(); i < e; ++i) {
+      ValueLatticeElement PNState = getStructValueState(&PN, i);
+      if (PNState.isOverdefined())
+        return;
+    }
+  } else {
+    ValueLatticeElement PhiState = getValueState(&PN);
+    if (PhiState.isOverdefined())
+      return;
+  }
+  std::vector<unsigned> FeasibleIncomingIndices;
   for (unsigned i = 0, e = PN.getNumIncomingValues(); i != e; ++i) {
     if (!isEdgeFeasible(PN.getIncomingBlock(i), PN.getParent()))
       continue;
+    FeasibleIncomingIndices.push_back(i);
+  }
 
-    ValueLatticeElement IV = getValueState(PN.getIncomingValue(i));
-    PhiState.mergeIn(IV);
-    NumActiveIncoming++;
-    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.
-  mergeInValue(&PN, PhiState,
-               ValueLatticeElement::MergeOptions().setMaxWidenSteps(
-                   NumActiveIncoming + 1));
-  ValueLatticeElement &PhiStateRef = getValueState(&PN);
-  PhiStateRef.setNumRangeExtensions(
-      std::max(NumActiveIncoming, PhiStateRef.getNumRangeExtensions()));
+  if (StructType *STy = dyn_cast<StructType>(PN.getType())) {
+    for (unsigned i = 0, e = STy->getNumElements(); i != e; ++i) {
+      ValueLatticeElement PhiState = getStructValueState(&PN, i);
+      for (unsigned j : FeasibleIncomingIndices) {
+        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) {
+      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.
+    mergeInValue(&PN, PhiState,
+                 ValueLatticeElement::MergeOptions().setMaxWidenSteps(
+                     FeasibleIncomingIndices.size() + 1));
+    ValueLatticeElement &PhiStateRef = getValueState(&PN);
+    PhiStateRef.setNumRangeExtensions(
+        std::max((unsigned)FeasibleIncomingIndices.size(),
+                 PhiStateRef.getNumRangeExtensions()));
+  }
 }
 
 void SCCPInstVisitor::visitReturnInst(ReturnInst &I) {
diff --git a/llvm/test/Transforms/SCCP/constant-range-struct.ll b/llvm/test/Transforms/SCCP/constant-range-struct.ll
index 7a399df11fb13..ac7abfe6d3a56 100644
--- a/llvm/test/Transforms/SCCP/constant-range-struct.ll
+++ b/llvm/test/Transforms/SCCP/constant-range-struct.ll
@@ -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
 }
@@ -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" undef, 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" undef, 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
+}

Copy link

github-actions bot commented Oct 16, 2025

✅ With the latest revision this PR passed the undef deprecator.

@XChy XChy requested review from dtcxzyw and nikic October 16, 2025 06:59
@XChy XChy changed the title [llvm, Pass, SCCP] Support constant structure in PhiNode [SCCP] Support constant structure in PhiNode Oct 16, 2025
@aokblast aokblast force-pushed the sccp/phi_for_const_struct branch 3 times, most recently from 6c7c539 to 13446e8 Compare October 16, 2025 07:58
@aokblast
Copy link
Contributor Author

Should I make a seperate patch for changing the original test from undef -> poison?

@dtcxzyw dtcxzyw requested a review from fhahn October 16, 2025 17:27
@aokblast aokblast force-pushed the sccp/phi_for_const_struct branch from 814124c to 26d0925 Compare October 17, 2025 04:18
@aokblast
Copy link
Contributor Author

Fixed. Thanks for your review!

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks reasonable

@aokblast
Copy link
Contributor Author

Fix other problem. Thanks for your review!

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@aokblast
Copy link
Contributor Author

aokblast commented Oct 17, 2025

This is really strange. Why changing some function name, comment, and remove the namespace qualifier breaks the unrelated CI🫠. Can I just ignore the CI fail?

Update: I can confirm that it works on my local machine.

@aokblast
Copy link
Contributor Author

I also see the failure in CI pipeline from other people.
https://github.com/llvm/llvm-project/actions/runs/18596005860

@dtcxzyw
Copy link
Member

dtcxzyw commented Oct 17, 2025

This is really strange. Why changing some function name, comment, and remove the namespace qualifier breaks the unrelated CI🫠. Can I just ignore the CI fail?

Update: I can confirm that it works on my local machine.

You can rebase your branch to make the CI green :)

Copy link
Member

@dtcxzyw dtcxzyw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG

@dtcxzyw dtcxzyw enabled auto-merge (squash) October 17, 2025 16:19
@aokblast
Copy link
Contributor Author

aokblast commented Oct 17, 2025

LG

Oh, thanks! I am just thinking that weather I did something wrong:).
Also, I just got commit bit yesterday from LLVM but haven't received the invitation link.
Can I merge this patch myself once I get the link?

@dtcxzyw dtcxzyw disabled auto-merge October 17, 2025 17:01
@dtcxzyw
Copy link
Member

dtcxzyw commented Oct 17, 2025

Also, I just got commit bit yesterday from LLVM but haven't received the invitation link.
Can I merge this patch myself once I get the link?

Sure :)

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.
@aokblast aokblast force-pushed the sccp/phi_for_const_struct branch from de72e1e to 0d22afd Compare October 19, 2025 03:27
@aokblast aokblast enabled auto-merge (squash) October 19, 2025 03:28
@aokblast aokblast merged commit a3082c9 into llvm:main Oct 19, 2025
9 of 10 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Oct 19, 2025

LLVM Buildbot has detected a new failure on builder lldb-aarch64-windows running on linaro-armv8-windows-msvc-05 while building llvm at step 6 "test".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/141/builds/12364

Here is the relevant piece of the build log for the reference
Step 6 (test) failure: build (failure)
...
UNSUPPORTED: lldb-api :: macosx/unregistered-macho/TestUnregisteredMacho.py (1088 of 2314)
PASS: lldb-api :: macosx/version_zero/TestGetVersionZeroVersion.py (1089 of 2314)
UNSUPPORTED: lldb-api :: pointer-nonaddressable-bits/TestArmPointerMetadataStripping.py (1090 of 2314)
PASS: lldb-api :: python_api/absolute_symbol/TestAbsoluteSymbol.py (1091 of 2314)
PASS: lldb-api :: python_api/address_range/TestAddressRange.py (1092 of 2314)
XFAIL: lldb-api :: python_api/basename/TestGetBaseName.py (1093 of 2314)
PASS: lldb-api :: python_api/breakpoint/TestBreakpointAPI.py (1094 of 2314)
UNSUPPORTED: lldb-api :: python_api/class_members/TestSBTypeClassMembers.py (1095 of 2314)
PASS: lldb-api :: python_api/compile_unit/TestCompileUnitAPI.py (1096 of 2314)
TIMEOUT: lldb-api :: functionalities/step-vrs-interrupt/TestStepVrsInterruptTimeout.py (1097 of 2314)
******************** TEST 'lldb-api :: functionalities/step-vrs-interrupt/TestStepVrsInterruptTimeout.py' FAILED ********************
Script:
--
C:/Users/tcwg/scoop/apps/python/current/python.exe C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/llvm-project/lldb\test\API\dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./lib --env LLVM_INCLUDE_DIR=C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/include --env LLVM_TOOLS_DIR=C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./bin --arch aarch64 --build-dir C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/lldb-test-build.noindex --lldb-module-cache-dir C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/lldb-test-build.noindex/module-cache-lldb\lldb-api --clang-module-cache-dir C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/lldb-test-build.noindex/module-cache-clang\lldb-api --executable C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./bin/lldb.exe --compiler C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./bin/clang.exe --dsymutil C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./bin/dsymutil.exe --make C:/Users/tcwg/scoop/shims/make.exe --llvm-tools-dir C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./bin --lldb-obj-root C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/tools/lldb --lldb-libs-dir C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./lib --cmake-build-type Release --skip-category=watchpoint C:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\test\API\functionalities\step-vrs-interrupt -p TestStepVrsInterruptTimeout.py
--
Exit Code: 15
Timeout: Reached timeout of 600 seconds

Command Output (stdout):
--
lldb version 22.0.0git (https://github.com/llvm/llvm-project.git revision a3082c9d9004e2017c86a76cdf91ba02d2f84bce)
  clang revision a3082c9d9004e2017c86a76cdf91ba02d2f84bce
  llvm revision a3082c9d9004e2017c86a76cdf91ba02d2f84bce

--

********************
PASS: lldb-api :: python_api/default-constructor/TestDefaultConstructorForAPIObjects.py (1098 of 2314)
PASS: lldb-api :: python_api/disassemble-raw-data/TestDisassembleRawData.py (1099 of 2314)
PASS: lldb-api :: python_api/disassemble-raw-data/TestDisassemble_VST1_64.py (1100 of 2314)
PASS: lldb-api :: python_api/debugger/TestDebuggerAPI.py (1101 of 2314)
UNSUPPORTED: lldb-api :: python_api/event/TestEvents.py (1102 of 2314)
UNSUPPORTED: lldb-api :: python_api/exprpath_synthetic/TestExprPathSynthetic.py (1103 of 2314)
PASS: lldb-api :: python_api/file_handle/TestFileHandle.py (1104 of 2314)
PASS: lldb-api :: python_api/find_in_memory/TestFindInMemory.py (1105 of 2314)
PASS: lldb-api :: python_api/findvalue_duplist/TestSBFrameFindValue.py (1106 of 2314)
PASS: lldb-api :: python_api/format/TestFormat.py (1107 of 2314)
PASS: lldb-api :: python_api/find_in_memory/TestFindRangesInMemory.py (1108 of 2314)
PASS: lldb-api :: python_api/formatters/TestFormattersSBAPI.py (1109 of 2314)
PASS: lldb-api :: python_api/frame/get-variables/TestGetVariables.py (1110 of 2314)
PASS: lldb-api :: python_api/frame/TestFrames.py (1111 of 2314)
PASS: lldb-api :: python_api/frame/inlines/TestInlinedFrame.py (1112 of 2314)
UNSUPPORTED: lldb-api :: python_api/function_symbol/TestDisasmAPI.py (1113 of 2314)
UNSUPPORTED: lldb-api :: python_api/function_symbol/TestSymbolAPI.py (1114 of 2314)
PASS: lldb-api :: python_api/get-value-32bit-int/TestGetValue32BitInt.py (1115 of 2314)
UNSUPPORTED: lldb-api :: python_api/global_module_cache/TestGlobalModuleCache.py (1116 of 2314)
PASS: lldb-api :: python_api/interpreter/TestCommandInterpreterAPI.py (1117 of 2314)
PASS: lldb-api :: python_api/interpreter/TestCommandOverrideCallback.py (1118 of 2314)
PASS: lldb-api :: python_api/interpreter/TestRunCommandInterpreterAPI.py (1119 of 2314)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants