[llvm] [SelectionDAG] Fix dangling SUnit node after unfold during scheduling (PR #202295)

Paweł Bylica via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 8 02:23:18 PDT 2026


https://github.com/chfast updated https://github.com/llvm/llvm-project/pull/202295

>From 3fa62cd19ab11585e67cada4b56b40aadef75614 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Bylica?= <pawel at hepcolgum.band>
Date: Mon, 8 Jun 2026 10:39:43 +0200
Subject: [PATCH] [SelectionDAG] Fix dangling SUnit node after unfold during
 scheduling

When a scheduler unfolds a load (TryUnfoldSU), it replaces all uses of the
folded node. A replacement can recursively CSE-merge a user into an existing
equivalent node and delete it; if that deleted node was owned by an SUnit, the
SUnit was left with a dangling SDNode pointer, later causing a use-after-free
and "Node emitted out of order - early" in InstrEmitter.

Register a DAGNodeDeletedListener around the replacements that redirects the
affected SUnit to the surviving node, shared via a RedirectMergedNode helper
on the common base. Apply the same fix to the fast scheduler.

Fixes #63309.
Fixes #63312.
Fixes #120631.
---
 .../CodeGen/SelectionDAG/ScheduleDAGFast.cpp  |  5 ++
 .../SelectionDAG/ScheduleDAGRRList.cpp        |  7 +-
 .../SelectionDAG/ScheduleDAGSDNodes.cpp       | 10 +++
 .../CodeGen/SelectionDAG/ScheduleDAGSDNodes.h |  5 ++
 llvm/test/CodeGen/X86/pr63309.ll              | 85 +++++++++++++++++++
 5 files changed, 111 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/CodeGen/X86/pr63309.ll

diff --git a/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGFast.cpp b/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGFast.cpp
index 0f67cee757407..817814b5b1b86 100644
--- a/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGFast.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGFast.cpp
@@ -232,6 +232,11 @@ SUnit *ScheduleDAGFast::CopyAndMoveSuccessors(SUnit *SU) {
     SDNode *LoadNode = NewNodes[0];
     unsigned NumVals = N->getNumValues();
     unsigned OldNumVals = SU->getNode()->getNumValues();
+    // Fix up SUnits whose nodes get deleted by the replacements below.
+    SelectionDAG::DAGNodeDeletedListener Listener(
+        *DAG, [&](SDNode *DeadNode, SDNode *NewNode) {
+          RedirectMergedNode(DeadNode, NewNode);
+        });
     for (unsigned i = 0; i != NumVals; ++i)
       DAG->ReplaceAllUsesOfValueWith(SDValue(SU->getNode(), i), SDValue(N, i));
     DAG->ReplaceAllUsesOfValueWith(SDValue(SU->getNode(), OldNumVals-1),
diff --git a/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGRRList.cpp b/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGRRList.cpp
index fffe5b8a83501..b6b74f623e98d 100644
--- a/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGRRList.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGRRList.cpp
@@ -1045,7 +1045,12 @@ SUnit *ScheduleDAGRRList::TryUnfoldSU(SUnit *SU) {
 
   LLVM_DEBUG(dbgs() << "Unfolding SU #" << SU->NodeNum << "\n");
 
-  // Now that we are committed to unfolding replace DAG Uses.
+  // Now that we are committed to unfolding replace DAG Uses, fixing up SUnits
+  // whose nodes get deleted by the replacements.
+  SelectionDAG::DAGNodeDeletedListener Listener(
+      *DAG, [&](SDNode *DeadNode, SDNode *NewNode) {
+        RedirectMergedNode(DeadNode, NewNode);
+      });
   for (unsigned i = 0; i != NumVals; ++i)
     DAG->ReplaceAllUsesOfValueWith(SDValue(SU->getNode(), i), SDValue(N, i));
   DAG->ReplaceAllUsesOfValueWith(SDValue(SU->getNode(), OldNumVals - 1),
diff --git a/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGSDNodes.cpp b/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGSDNodes.cpp
index 58101f415ce51..39429e30edf1b 100644
--- a/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGSDNodes.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGSDNodes.cpp
@@ -105,6 +105,16 @@ SUnit *ScheduleDAGSDNodes::Clone(SUnit *Old) {
   return SU;
 }
 
+void ScheduleDAGSDNodes::RedirectMergedNode(SDNode *DeadNode, SDNode *NewNode) {
+  int Id = DeadNode->getNodeId();
+  if (Id == -1 || unsigned(Id) >= SUnits.size() ||
+      SUnits[Id].getNode() != DeadNode)
+    return;
+  SUnits[Id].setNode(NewNode);
+  if (NewNode->getNodeId() == -1)
+    NewNode->setNodeId(Id);
+}
+
 /// CheckForPhysRegDependency - Check if the dependency between def and use of
 /// a specified operand is a physical register dependency. If so, returns the
 /// register and the cost of copying the register.
diff --git a/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGSDNodes.h b/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGSDNodes.h
index ff5615b7658f3..e6551f6ab186d 100644
--- a/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGSDNodes.h
+++ b/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGSDNodes.h
@@ -90,6 +90,11 @@ class InstrItineraryData;
     ///
     SUnit *Clone(SUnit *Old);
 
+    /// If an SUnit owns \p DeadNode, which is being deleted and replaced by
+    /// \p NewNode (e.g. when a node is CSE'd into an existing one), point that
+    /// SUnit at \p NewNode so it is not left with a dangling node pointer.
+    void RedirectMergedNode(SDNode *DeadNode, SDNode *NewNode);
+
     /// BuildSchedGraph - Build the SUnit graph from the selection dag that we
     /// are input.  This SUnit graph is similar to the SelectionDAG, but
     /// excludes nodes that aren't interesting to scheduling, and represents
diff --git a/llvm/test/CodeGen/X86/pr63309.ll b/llvm/test/CodeGen/X86/pr63309.ll
new file mode 100644
index 0000000000000..4a89cb090fdc5
--- /dev/null
+++ b/llvm/test/CodeGen/X86/pr63309.ll
@@ -0,0 +1,85 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu | FileCheck %s --check-prefix=RRLIST
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -pre-RA-sched=fast | FileCheck %s --check-prefix=FAST
+
+; Make sure this does not crash with "Node emitted out of order - early".
+; Unfolding a load during scheduling replaces all uses of the folded node,
+; which can CSE-merge and delete a node still referenced by another SUnit.
+; Both the list (ScheduleDAGRRList) and fast (ScheduleDAGFast) schedulers
+; perform this unfold, so exercise both.
+
+ at G.1 = external global i1
+ at G.5 = external global ptr
+
+define void @pr63309(ptr %G.5, i64 %L3) {
+; RRLIST-LABEL: pr63309:
+; RRLIST:       # %bb.0: # %BB
+; RRLIST-NEXT:    movzbl -{{[0-9]+}}(%rsp), %eax
+; RRLIST-NEXT:    andl $1, %eax
+; RRLIST-NEXT:    negq %rax
+; RRLIST-NEXT:    movb $0, -{{[0-9]+}}(%rsp)
+; RRLIST-NEXT:    movzbl (%rax), %eax
+; RRLIST-NEXT:    movq G.1 at GOTPCREL(%rip), %rcx
+; RRLIST-NEXT:    movq 0, %rdx
+; RRLIST-NEXT:    movb $0, (%rcx)
+; RRLIST-NEXT:    movq $0, -{{[0-9]+}}(%rsp)
+; RRLIST-NEXT:    movq G.5 at GOTPCREL(%rip), %rcx
+; RRLIST-NEXT:    movq $0, (%rcx)
+; RRLIST-NEXT:    testq %rsi, %rsi
+; RRLIST-NEXT:    setg 0
+; RRLIST-NEXT:    testq %rdx, %rdx
+; RRLIST-NEXT:    movq $0, (%rdi)
+; RRLIST-NEXT:    movb %al, 0
+; RRLIST-NEXT:    setne (%rdi)
+; RRLIST-NEXT:    testb %al, %al
+; RRLIST-NEXT:    sets 0
+; RRLIST-NEXT:    retq
+;
+; FAST-LABEL: pr63309:
+; FAST:       # %bb.0: # %BB
+; FAST-NEXT:    movq G.1 at GOTPCREL(%rip), %rax
+; FAST-NEXT:    movzbl -{{[0-9]+}}(%rsp), %ecx
+; FAST-NEXT:    andl $1, %ecx
+; FAST-NEXT:    negq %rcx
+; FAST-NEXT:    movb $0, -{{[0-9]+}}(%rsp)
+; FAST-NEXT:    movzbl (%rcx), %ecx
+; FAST-NEXT:    movq 0, %rdx
+; FAST-NEXT:    movb $0, (%rax)
+; FAST-NEXT:    movq G.5 at GOTPCREL(%rip), %rax
+; FAST-NEXT:    movq $0, (%rax)
+; FAST-NEXT:    movq $0, -{{[0-9]+}}(%rsp)
+; FAST-NEXT:    testq %rsi, %rsi
+; FAST-NEXT:    setg 0
+; FAST-NEXT:    testq %rdx, %rdx
+; FAST-NEXT:    movq $0, (%rdi)
+; FAST-NEXT:    movb %cl, 0
+; FAST-NEXT:    setne (%rdi)
+; FAST-NEXT:    testb %cl, %cl
+; FAST-NEXT:    sets 0
+; FAST-NEXT:    retq
+BB:
+  %A31 = alloca i1, i32 0, align 1
+  %A = alloca i1, i32 0, align 1
+  %L2 = load i1, ptr %A, align 1
+  %L = load i1, ptr %A, align 1
+  store i1 poison, ptr null, align 1
+  store i1 false, ptr %A31, align 1
+  %LGV3 = load i64, ptr null, align 4
+  %G3 = getelementptr i1, ptr null, i1 %L2
+  %L4 = load i1, ptr %G3, align 1
+  %G1 = getelementptr i1, ptr null, i1 %L
+  %L9 = load i8, ptr %G1, align 1
+  %C10 = icmp sgt i8 0, %L9
+  %L31 = load i64, ptr %A31, align 4
+  store i1 false, ptr @G.1, align 1
+  %C4 = icmp ne i64 0, %LGV3
+  store ptr null, ptr %A31, align 8
+  store ptr null, ptr @G.5, align 8
+  %C5 = icmp sgt i64 %L3, 0
+  store i1 %C5, ptr null, align 1
+  store ptr null, ptr %G.5, align 8
+  store i1 %L4, ptr null, align 1
+  store i1 %C4, ptr %G.5, align 1
+  store i1 %C10, ptr null, align 1
+  ret void
+}



More information about the llvm-commits mailing list