Skip to content

[LoopVectorize] Fix crash with GC statepoint in epilogue vectorization#179459

Closed
Luohaothu wants to merge 3 commits intollvm:mainfrom
Luohaothu:fix-issue-179407
Closed

[LoopVectorize] Fix crash with GC statepoint in epilogue vectorization#179459
Luohaothu wants to merge 3 commits intollvm:mainfrom
Luohaothu:fix-issue-179407

Conversation

@Luohaothu
Copy link
Copy Markdown
Contributor

Fixes #179407.

When vectorizing loops in functions with GC statepoint, fixScalarResumeValuesFromBypass assumed that IVPhi->getIncomingValueForBlock(PH) always returns a PHINode. However, in functions with GC statepoint, the preheader may be simplified and the incoming value could be a constant or other non-PHINode value, causing an assertion failure:
"isa<> used on a null pointer"

The fix uses dyn_cast instead of cast to safely check the type, and when the incoming value is not a PHINode, creates a new PHI node in the preheader to merge the original value and the bypass value.

@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Feb 3, 2026

@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-vectorizers

Author: Luohao Wang (Luohaothu)

Changes

Fixes #179407.

When vectorizing loops in functions with GC statepoint, fixScalarResumeValuesFromBypass assumed that IVPhi-&gt;getIncomingValueForBlock(PH) always returns a PHINode. However, in functions with GC statepoint, the preheader may be simplified and the incoming value could be a constant or other non-PHINode value, causing an assertion failure:
"isa<> used on a null pointer"

The fix uses dyn_cast instead of cast to safely check the type, and when the incoming value is not a PHINode, creates a new PHI node in the preheader to merge the original value and the bypass value.


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

2 Files Affected:

  • (modified) llvm/lib/Transforms/Vectorize/LoopVectorize.cpp (+21-3)
  • (added) llvm/test/Transforms/LoopVectorize/pr179407-gc-statepoint.ll (+40)
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index abac45b265d10..2581353f900bf 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -9361,12 +9361,30 @@ static void fixScalarResumeValuesFromBypass(BasicBlock *BypassBlock, Loop *L,
   // Fix induction resume values from the additional bypass block.
   IRBuilder<> BypassBuilder(BypassBlock, BypassBlock->getFirstInsertionPt());
   for (const auto &[IVPhi, II] : LVL.getInductionVars()) {
-    auto *Inc = cast<PHINode>(IVPhi->getIncomingValueForBlock(PH));
+    Value *IncomingFromPH = IVPhi->getIncomingValueForBlock(PH);
     Value *V = createInductionAdditionalBypassValues(
         IVPhi, II, BypassBuilder, ExpandedSCEVs, MainVectorTripCount,
         LVL.getPrimaryInduction());
-    // TODO: Directly add as extra operand to the VPResumePHI recipe.
-    Inc->setIncomingValueForBlock(BypassBlock, V);
+
+    if (auto *Inc = dyn_cast<PHINode>(IncomingFromPH)) {
+      // TODO: Directly add as extra operand to the VPResumePHI recipe.
+      Inc->setIncomingValueForBlock(BypassBlock, V);
+    } else {
+      // If the incoming value from preheader is not a PHI node (e.g., a constant
+      // in functions with GC statepoint), we need to create a PHI node in the
+      // preheader that merges the original value and the bypass value, then update
+      // IVPhi to use this new PHI node.
+      PHINode *NewPhi = PHINode::Create(IncomingFromPH->getType(), 2,
+                                        IVPhi->getName() + ".phi.merge", PH->getFirstNonPHIIt());
+      for (BasicBlock *Pred : predecessors(PH)) {
+        if (Pred == BypassBlock)
+          NewPhi->addIncoming(V, BypassBlock);
+        else
+          NewPhi->addIncoming(IncomingFromPH, Pred);
+      }
+      // Update IVPhi to use the new PHI node for the incoming value from PH.
+      IVPhi->setIncomingValueForBlock(PH, NewPhi);
+    }
   }
 }
 
diff --git a/llvm/test/Transforms/LoopVectorize/pr179407-gc-statepoint.ll b/llvm/test/Transforms/LoopVectorize/pr179407-gc-statepoint.ll
new file mode 100644
index 0000000000000..a2c33b3f1a702
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/pr179407-gc-statepoint.ll
@@ -0,0 +1,40 @@
+; RUN: opt -passes=loop-vectorize -enable-epilogue-vectorization -force-vector-width=2 -epilogue-vectorization-force-VF=2 -S < %s | FileCheck %s
+;
+; Test case for issue #179407: LoopVectorize should not crash when
+; vectorizing loops in functions with GC statepoint.
+;
+; The issue was that fixScalarResumeValuesFromBypass assumed
+; IVPhi->getIncomingValueForBlock(PH) always returns a PHINode, but in
+; functions with GC statepoint, it could return a constant or other value.
+;
+; This test verifies that epilogue vectorization works correctly with
+; GC statepoint by checking that the loop is vectorized and the
+; bypass blocks are properly created.
+
+; CHECK: define void @wombat
+; CHECK-SAME: gc "statepoint-example"
+; CHECK: vector.body:
+; CHECK: vec.epilog.vector.body:
+; CHECK: vec.epilog.scalar.ph:
+; CHECK: bb1:
+
+define void @wombat(i64 %arg) gc "statepoint-example" {
+bb:
+  br label %bb1
+
+bb1:                                              ; preds = %bb1, %bb
+  %phi = phi i64 [ 0, %bb ], [ %add, %bb1 ]
+  %phi2 = phi i32 [ 0, %bb ], [ %or, %bb1 ]
+  %phi3 = phi i32 [ 0, %bb ], [ %select, %bb1 ]
+  %icmp = icmp eq i32 0, 0
+  %select = select i1 %icmp, i32 0, i32 %phi3
+  %or = or i32 %phi2, 0
+  %add = add i64 %phi, 1
+  %icmp4 = icmp ult i64 %phi, %arg
+  br i1 %icmp4, label %bb1, label %bb5
+
+bb5:                                              ; preds = %bb1
+  %phi6 = phi i32 [ %select, %bb1 ]
+  %phi7 = phi i32 [ %or, %bb1 ]
+  ret void
+}

@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 3, 2026

✅ With the latest revision this PR passed the C/C++ code formatter.

Fixes llvm#179407.

When vectorizing loops in functions with GC statepoint,
fixScalarResumeValuesFromBypass assumed that IVPhi->getIncomingValueForBlock(PH)
always returns a PHINode. However, in functions with GC statepoint, the
preheader may be simplified and the incoming value could be a constant
or other non-PHINode value, causing an assertion failure:
  "isa<> used on a null pointer"

The fix uses dyn_cast instead of cast to safely check the type, and
when the incoming value is not a PHINode, creates a new PHI node in the
preheader to merge the original value and the bypass value.
@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 3, 2026

🐧 Linux x64 Test Results

  • 189586 tests passed
  • 5046 tests skipped

✅ The build succeeded and all tests passed.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 3, 2026

🪟 Windows x64 Test Results

  • 130445 tests passed
  • 2909 tests skipped

✅ The build succeeded and all tests passed.

Luohaothu and others added 2 commits February 4, 2026 07:57
The original fix attempted to create a new PHI node in the preheader when
IncomingFromPH was not a PHINode (e.g., a constant in functions with GC
statepoint). However, this approach had issues because:
1. It assumed BypassBlock would be in PH's predecessor list, but control
   flow modifications in connectEpilogueVectorLoop change this.
2. The complex logic of creating NewPhi and iterating over PH's predecessors
   was error-prone.

Simplified the fix: when IncomingFromPH is not a PHINode, directly add an
incoming value from BypassBlock to IVPhi. This ensures that when control
flows from BypassBlock, IVPhi gets the correct bypass value V, while the
incoming value from PH remains unchanged (IncomingFromPH).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…gue vectorization

In fixReductionScalarResumeWhenVectorizingEpilog, line 7359 used cast<>
on the result of vputils::findRecipe, which could return nullptr in
functions with GC statepoint. This caused the same "isa<> used on a null
pointer" assertion failure that was fixed for induction variables.

This commit changes cast<> to cast_if_present<> and adds an early return
when EpiRedHeaderPhi is null, consistent with the pattern used elsewhere
in the function (line 7352).

Fixes the test failure in pr179407-gc-statepoint.ll on Windows and Linux
CI builds.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Comment on lines +10 to +13
; This test verifies that epilogue vectorization works correctly with
; GC statepoint by checking that the loop is vectorized and the
; bypass blocks are properly created.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think this is related to statepoints?

@@ -0,0 +1,40 @@
; RUN: opt -passes=loop-vectorize -enable-epilogue-vectorization -force-vector-width=2 -epilogue-vectorization-force-VF=2 -S < %s | FileCheck %s
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you generate the full IR for the test, using update_test_checks.py?

Is it possible that we end up with a trivial reduction? I think we expect them to be folded away

@annamthomas
Copy link
Copy Markdown
Contributor

reverse-ping @Luohaothu

We have couple of crashes with this downstream. Thanks

@annamthomas
Copy link
Copy Markdown
Contributor

reverse ping x2 @Luohaothu.

@fhahn is the issue only about the testcase and the fix in LV looks correct? I'll try reducing the original testcase from the bug. I'll go ahead and fix this so we can land it and unblock the issue. I'm not sure what's the usual procedure though. Do I just create a new PR?

@Luohaothu
Copy link
Copy Markdown
Contributor Author

reverse ping x2 @Luohaothu.

@fhahn is the issue only about the testcase and the fix in LV looks correct? I'll try reducing the original testcase from the bug. I'll go ahead and fix this so we can land it and unblock the issue. I'm not sure what's the usual procedure though. Do I just create a new PR?

Hi @annamthomas , sorry for the delay. I don’t have time to work on this PR further at the moment, so please feel free to take over. Much appreciated.

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.

Assertion `Val && "isa<> used on a null pointer"' failed.

4 participants