[llvm] [DAGCombiner] Replace the node before the old load in reduceLoadWidth (PR #230947)
Aochang Liu via llvm-commits
llvm-commits at lists.llvm.org
Sun Oct 11 03:53:20 PDT 2026
https://github.com/HelloWorldU created https://github.com/llvm/llvm-project/pull/230947
When `reduceLoadWidth` looks through a multi-use freeze, it replaces the old load under the freeze with the narrowed load. If the narrowed load already exists, CSE merges the freeze, and then the AND being combined, into existing nodes. The AND is deleted, but `reduceLoadWidth` still returns a new value for it, which hits the "Node was deleted but visit returned new node!" assertion. This was introduced in #189317.
Replace the AND through `CombineTo` before replacing the old load, as `visitAND` does for its EXTLOAD to ZEXTLOAD fold. Its users then get the new value with the `AssertZext`, even if the AND is merged away later. This patch does not return early when the AND is found deleted, because that cannot guarantee that the users get the `AssertZext`.
Fixes #230899
Assisted-by: Claude Opus 5.5
>From a0caea470a1922c177aa7cbeab01d344bf0b8081 Mon Sep 17 00:00:00 2001
From: HelloWorldU <asd001liu at gmail.com>
Date: Sun, 11 Oct 2026 18:36:20 +0800
Subject: [PATCH] [DAGCombiner] Replace the node before the old load in
reduceLoadWidth
When reduceLoadWidth looks through a multi-use freeze, it replaces the
old load with the narrowed load under the freeze. If the narrowed load
already exists, CSE can merge the freeze into an existing freeze of it,
and then merge the AND being combined into an existing AND. The AND is
deleted, but reduceLoadWidth still returns a new value for it, which
hits the "Node was deleted but visit returned new node!" assertion.
Replace the node being combined with the new value first, and then
replace the old load. The users of the node get the new value, including
its AssertZext, whether or not the node is merged away later. visitAND
orders its EXTLOAD to ZEXTLOAD fold in the same way.
This was introduced in #189317.
Fixes #230899
Assisted-by: Claude Opus 5.5
---
llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 12 ++--
.../AArch64/reduce-load-width-freeze.ll | 70 +++++++++++++++++++
2 files changed, 78 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index af114462ed141..82d396e6a3581 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -17353,10 +17353,6 @@ SDValue DAGCombiner::reduceLoadWidth(SDNode *N) {
WorklistRemover DeadNodes(*this);
DAG.ReplaceAllUsesOfValueWith(N0.getValue(1), Load.getValue(1));
- // Replace old load value for multi-use freeze so all users benefit.
- if (FreezeNode && !FreezeNode.hasOneUse())
- DAG.ReplaceAllUsesOfValueWith(N0.getValue(0), Load.getValue(0));
-
// If we looked through a freeze, rewrap the narrowed result and add an
// Assert node so downstream analyses can see the range.
SDValue Result = Load;
@@ -17393,6 +17389,14 @@ SDValue DAGCombiner::reduceLoadWidth(SDNode *N) {
DAG.ReplaceAllUsesOfValueWith(SDValue(N, 0), Result);
}
+ // Replace old load value for multi-use freeze so all users benefit. Replace
+ // N first, since replacing the load can CSE N into an existing node.
+ if (FreezeNode && !FreezeNode.hasOneUse()) {
+ CombineTo(N, Result);
+ DAG.ReplaceAllUsesOfValueWith(N0.getValue(0), Load.getValue(0));
+ return SDValue(N, 0);
+ }
+
// Return the new loaded value.
return Result;
}
diff --git a/llvm/test/CodeGen/AArch64/reduce-load-width-freeze.ll b/llvm/test/CodeGen/AArch64/reduce-load-width-freeze.ll
index 4dff6a1603fb5..e82b910050cf6 100644
--- a/llvm/test/CodeGen/AArch64/reduce-load-width-freeze.ll
+++ b/llvm/test/CodeGen/AArch64/reduce-load-width-freeze.ll
@@ -341,3 +341,73 @@ define i16 @srl_freeze_load_i64_to_i16(ptr %p) {
%trunc = trunc i64 %srl to i16
ret i16 %trunc
}
+
+; The narrowed load already exists. Replacing the load under the multi-use
+; freeze makes CSE merge the freeze, and then the AND being combined, into
+; existing nodes.
+define i8 @and_freeze_multi_use_load_cse(i8 %a, i1 %cond, ptr %p, ptr %q) {
+; CHECK-LABEL: and_freeze_multi_use_load_cse:
+; CHECK: ; %bb.0: ; %entry
+; CHECK-NEXT: tbz w1, #0, LBB28_2
+; CHECK-NEXT: ; %bb.1:
+; CHECK-NEXT: mov w0, wzr
+; CHECK-NEXT: mov w8, wzr
+; CHECK-NEXT: tbnz w8, #0, LBB28_3
+; CHECK-NEXT: b LBB28_4
+; CHECK-NEXT: LBB28_2: ; %body
+; CHECK-NEXT: sub sp, sp, #16
+; CHECK-NEXT: .cfi_def_cfa_offset 16
+; CHECK-NEXT: ldrb w8, [x2]
+; CHECK-NEXT: and w10, w0, #0xff
+; CHECK-NEXT: cmp w8, #0
+; CHECK-NEXT: strb w8, [sp, #15]
+; CHECK-NEXT: cset w9, ne
+; CHECK-NEXT: strb w8, [x3]
+; CHECK-NEXT: cmp w8, w9
+; CHECK-NEXT: and w8, w8, #0x1
+; CHECK-NEXT: csel x9, x2, x3, eq
+; CHECK-NEXT: ldrh w9, [x9]
+; CHECK-NEXT: cmp w9, #0
+; CHECK-NEXT: cset w0, ne
+; CHECK-NEXT: cmp w8, w10
+; CHECK-NEXT: cset w8, ne
+; CHECK-NEXT: add sp, sp, #16
+; CHECK-NEXT: tbz w8, #0, LBB28_4
+; CHECK-NEXT: LBB28_3: ; %exit1
+; CHECK-NEXT: mov w0, wzr
+; CHECK-NEXT: LBB28_4: ; %common.ret
+; CHECK-NEXT: ret
+entry:
+ %s = alloca i8, align 1
+ br i1 %cond, label %join, label %body
+
+body:
+ %l1 = load i8, ptr %p, align 1
+ store i8 %l1, ptr %s, align 1
+ %l2 = load i8, ptr %p, align 1
+ store i8 %l2, ptr %q, align 1
+ %c1 = icmp ne i8 %l1, 0
+ %z1 = zext i1 %c1 to i8
+ %c2 = icmp eq i8 %l2, %z1
+ %x = load i16, ptr %p, align 1
+ %y = load i16, ptr %q, align 1
+ %sel = select i1 %c2, i16 %x, i16 %y
+ %c3 = icmp ne i16 %sel, 0
+ %l3 = load i8, ptr %s, align 1
+ %m = and i8 %l3, 1
+ %c4 = icmp ne i8 %m, %a
+ br label %join
+
+join:
+ %p1 = phi i1 [ false, %entry ], [ %c3, %body ]
+ %p2 = phi i1 [ false, %entry ], [ %c4, %body ]
+ br i1 %p2, label %exit1, label %exit2
+
+exit1:
+ %d = zext i8 %a to i32
+ ret i8 0
+
+exit2:
+ %r = zext i1 %p1 to i8
+ ret i8 %r
+}
More information about the llvm-commits
mailing list