[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