Skip to content

Conversation

@quic-asaravan
Copy link
Contributor

@quic-asaravan quic-asaravan commented Nov 8, 2025

  • This patch detects cycles by phis and bails out if one is found.
  • It prevents to violate DAG restrictions.

Abort pipelining in the below case

%1 = phi i32 [ %a, %entry ], [ %3, %loop ]
%2 = phi i32 [ %a, %entry ], [ %1, %loop ]
%3 = phi i32 [ %b, %entry ], [ %2, %loop ]

Abort pipelining in the below case

%1 = phi i32 [ %a, %entry ], [ %3, %loop ]
%2 = phi i32 [ %a, %entry ], [ %1, %loop ]
%3 = phi i32 [ %b, %entry ], [ %2, %loop ]
@llvmbot
Copy link
Member

llvmbot commented Nov 8, 2025

@llvm/pr-subscribers-backend-hexagon

Author: Abinaya Saravanan (quic-asaravan)

Changes

Abort pipelining in the below case

%1 = phi i32 [ %a, %entry ], [ %3, %loop ]
%2 = phi i32 [ %a, %entry ], [ %1, %loop ]
%3 = phi i32 [ %b, %entry ], [ %2, %loop ]


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

2 Files Affected:

  • (modified) llvm/lib/CodeGen/MachinePipeliner.cpp (+54)
  • (added) llvm/test/CodeGen/Hexagon/swp-phi-cycle.ll (+22)
diff --git a/llvm/lib/CodeGen/MachinePipeliner.cpp b/llvm/lib/CodeGen/MachinePipeliner.cpp
index a717d9e4a618d..b327ba8900f03 100644
--- a/llvm/lib/CodeGen/MachinePipeliner.cpp
+++ b/llvm/lib/CodeGen/MachinePipeliner.cpp
@@ -485,6 +485,55 @@ void MachinePipeliner::setPragmaPipelineOptions(MachineLoop &L) {
   }
 }
 
+bool hasPHICycle(const MachineBasicBlock *LoopHeader, const MachineRegisterInfo &MRI) {
+  DenseMap<unsigned, SmallVector<unsigned, 2>> PhiDeps;
+  SmallSet<unsigned, 8> PhiRegs;
+
+  // Collect PHI nodes and their dependencies
+  for (const MachineInstr &MI : *LoopHeader) {
+    if (!MI.isPHI())
+      continue;
+
+    unsigned DefReg = MI.getOperand(0).getReg();
+    PhiRegs.insert(DefReg);
+
+    for (unsigned i = 1; i < MI.getNumOperands(); i += 2) {
+      unsigned SrcReg = MI.getOperand(i).getReg();
+      PhiDeps[DefReg].push_back(SrcReg);
+    }
+  }
+
+  // DFS to detect cycles
+  SmallSet<unsigned, 8> Visited, RecStack;
+
+  std::function<bool(unsigned)> DFS = [&](unsigned Reg) -> bool {
+  if (!PhiRegs.count(Reg))
+      return false;
+    if (RecStack.count(Reg))
+      return true;
+    if (Visited.count(Reg))
+      return false;
+
+    Visited.insert(Reg);
+    RecStack.insert(Reg);
+
+    for (unsigned Dep : PhiDeps[Reg]) {
+      if (DFS(Dep))
+        return true;
+    }
+
+    RecStack.erase(Reg);
+    return false;
+  };
+
+  for (unsigned Reg : PhiRegs) {
+    if (DFS(Reg))
+      return true;
+  }
+
+  return false;
+}
+
 /// Return true if the loop can be software pipelined.  The algorithm is
 /// restricted to loops with a single basic block.  Make sure that the
 /// branch in the loop can be analyzed.
@@ -499,6 +548,11 @@ bool MachinePipeliner::canPipelineLoop(MachineLoop &L) {
     return false;
   }
 
+  if (hasPHICycle(L.getHeader(), MF->getRegInfo())) {
+    LLVM_DEBUG(dbgs() << "Cannot pipeline loop due to PHI cycle\n");
+    return false;
+  }
+
   if (disabledByPragma) {
     ORE->emit([&]() {
       return MachineOptimizationRemarkAnalysis(DEBUG_TYPE, "canPipelineLoop",
diff --git a/llvm/test/CodeGen/Hexagon/swp-phi-cycle.ll b/llvm/test/CodeGen/Hexagon/swp-phi-cycle.ll
new file mode 100644
index 0000000000000..a92d113f01c88
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/swp-phi-cycle.ll
@@ -0,0 +1,22 @@
+; RUN: llc -mtriple=hexagon-unknown-linux-gnu -enable-pipeliner -debug-only=pipeliner < %s 2>&1 | FileCheck %s
+
+; CHECK: Cannot pipeline loop due to PHI cycle
+
+define void @phi_cycle_loop(i32 %a, i32 %b) {
+entry:
+  br label %loop
+
+loop:
+  %1 = phi i32 [ %a, %entry ], [ %3, %loop ]
+  %2 = phi i32 [ %a, %entry ], [ %1, %loop ]
+  %3 = phi i32 [ %b, %entry ], [ %2, %loop ]
+
+  ; Prevent PHI elimination by using all values
+  %add1 = add i32 %1, %2
+  %add2 = add i32 %add1, %3
+  %cmp = icmp slt i32 %add2, 100
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret void
+}

@github-actions
Copy link

github-actions bot commented Nov 8, 2025

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

Abort pipelining in the below case

%1 = phi i32 [ %a, %entry ], [ %3, %loop ]
%2 = phi i32 [ %a, %entry ], [ %1, %loop ]
%3 = phi i32 [ %b, %entry ], [ %2, %loop ]
@quic-asaravan
Copy link
Contributor Author

@iajbar @sgundapa Could you please review ?

@iajbar iajbar self-requested a review November 10, 2025 15:10
@quic-asaravan
Copy link
Contributor Author

@kasuga-fj Could you please review this patch?

Copy link
Contributor

@kasuga-fj kasuga-fj left a comment

Choose a reason for hiding this comment

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

Generally looks reasonable to me.

// DFS to detect cycles
SmallSet<unsigned, 8> Visited, RecStack;

std::function<bool(unsigned)> DFS = [&](unsigned Reg) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer to define a function like static bool foo(unsigned Reg, ...) rather than using std::function for recursive function.

Comment on lines 499 to 504
PhiRegs.insert(DefReg);

for (unsigned i = 1; i < MI.getNumOperands(); i += 2) {
unsigned SrcReg = MI.getOperand(i).getReg();
PhiDeps[DefReg].push_back(SrcReg);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
PhiRegs.insert(DefReg);
for (unsigned i = 1; i < MI.getNumOperands(); i += 2) {
unsigned SrcReg = MI.getOperand(i).getReg();
PhiDeps[DefReg].push_back(SrcReg);
}
auto Ite = PhiDeps.try_emplace(DefReg).first;
for (unsigned i = 1; i < MI.getNumOperands(); i += 2)
Ite->second.push_back(MI.getOperand(i).getReg());

To avoid repeated hash lookup.

Comment on lines 511 to 512
if (!PhiRegs.count(Reg))
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider calling PhiDeps.find(Reg) instead and remove PhiRegs.

@quic-asaravan
Copy link
Contributor Author

@kasuga-fj Could you please review? I have made the suggested changes.

Copy link
Contributor

@kasuga-fj kasuga-fj left a comment

Choose a reason for hiding this comment

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

The code looks good to me, but I think the PR description should be updated. At least the following should be included.

  • This patch detects cycles by phis and bails out if one is found.
  • It prevents to violate DAG restrictions.

@quic-asaravan quic-asaravan merged commit c946418 into llvm:main Nov 17, 2025
10 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Nov 17, 2025

LLVM Buildbot has detected a new failure on builder llvm-nvptx-nvidia-win running on as-builder-8 while building llvm at step 7 "test-build-unified-tree-check-llvm".

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

Here is the relevant piece of the build log for the reference
Step 7 (test-build-unified-tree-check-llvm) failure: test (failure)
******************** TEST 'LLVM-Unit :: Support/./SupportTests.exe/79/105' FAILED ********************
Script(shard):
--
GTEST_OUTPUT=json:C:\buildbot\as-builder-8\llvm-nvptx-nvidia-win\build\unittests\Support\.\SupportTests.exe-LLVM-Unit-19284-79-105.json GTEST_SHUFFLE=0 GTEST_TOTAL_SHARDS=105 GTEST_SHARD_INDEX=79 C:\buildbot\as-builder-8\llvm-nvptx-nvidia-win\build\unittests\Support\.\SupportTests.exe
--


Note: This is test shard 80 of 105.

[==========] Running 16 tests from 16 test suites.

[----------] Global test environment set-up.

[----------] 1 test from BinaryStreamTest

[ RUN      ] BinaryStreamTest.DropOperations

[       OK ] BinaryStreamTest.DropOperations (0 ms)

[----------] 1 test from BinaryStreamTest (0 ms total)



[----------] 1 test from CommandLineTest

[ RUN      ] CommandLineTest.TokenizeAndMarkEOLs

[       OK ] CommandLineTest.TokenizeAndMarkEOLs (0 ms)

[----------] 1 test from CommandLineTest (0 ms total)



[----------] 1 test from DataExtractorTest

[ RUN      ] DataExtractorTest.LEB128_error

[       OK ] DataExtractorTest.LEB128_error (0 ms)

[----------] 1 test from DataExtractorTest (0 ms total)



[----------] 1 test from Error

[ RUN      ] Error.ForwardToExpected

[       OK ] Error.ForwardToExpected (0 ms)

[----------] 1 test from Error (0 ms total)
...

quic-asaravan added a commit to quic-asaravan/llvm-project that referenced this pull request Nov 17, 2025
aadeshps-mcw pushed a commit to aadeshps-mcw/llvm-project that referenced this pull request Nov 26, 2025
…167095)

- This patch detects cycles by phis and bails out if one is found.
- It prevents to violate DAG restrictions.

Abort pipelining in the below case

%1 = phi i32 [ %a, %entry ], [ %3, %loop ]
%2 = phi i32 [ %a, %entry ], [ %1, %loop ]
%3 = phi i32 [ %b, %entry ], [ %2, %loop ]

---------

Co-authored-by: Ryotaro Kasuga <[email protected]>
@iajbar iajbar added this to the LLVM 21.x Release milestone Dec 9, 2025
@iajbar
Copy link
Contributor

iajbar commented Dec 9, 2025

/cherry-pick c946418

@llvmbot
Copy link
Member

llvmbot commented Dec 9, 2025

/pull-request #171472

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

Projects

Development

Successfully merging this pull request may close these issues.

5 participants