[llvm] [SDAG][X86] Merge MMOs when reusing morphed node (PR #206700)

Nikita Popov via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 30 03:20:24 PDT 2026


https://github.com/nikic created https://github.com/llvm/llvm-project/pull/206700

Multiple isel patterns may morph to the same node. Currently, it will retain the MMOs from whichever one happens to run last.

This doesn't work with X86's EnablePromoteAnyextLoad feature, which can select the same MOV32rm from an i32 load and an appropriately aligned anyexted i16 load. If we end up retaining only the s16 MMO and drop the s32 MMO, this can result in miscompiles due to incorrect reordering down the road.

Fix this by merging MMOs instead of only retaining the last ones.

I'm not familiar with this area, and not sure whether this is the right fix. Maybe X86 needs to be doing something special for EnablePromoteAnyextLoad instead?

Fixes https://github.com/llvm/llvm-project/issues/194853.

>From 45db38f3e79663fd2e9ed77c45b2bd3efb1b5680 Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Tue, 30 Jun 2026 11:33:50 +0200
Subject: [PATCH 1/2] Add test for miscompile

---
 .../CodeGen/X86/promote-anyext-load-mmo.ll    | 23 +++++++++++++++++++
 1 file changed, 23 insertions(+)
 create mode 100644 llvm/test/CodeGen/X86/promote-anyext-load-mmo.ll

diff --git a/llvm/test/CodeGen/X86/promote-anyext-load-mmo.ll b/llvm/test/CodeGen/X86/promote-anyext-load-mmo.ll
new file mode 100644
index 0000000000000..1eb49970d9363
--- /dev/null
+++ b/llvm/test/CodeGen/X86/promote-anyext-load-mmo.ll
@@ -0,0 +1,23 @@
+; NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=x86_64-- -stop-after=x86-isel < %s | FileCheck %s
+
+declare void @use(i32, i16)
+
+define void @test(ptr %p) nounwind {
+  ; CHECK-LABEL: name: test
+  ; CHECK: bb.0 (%ir-block.0):
+  ; CHECK-NEXT:   liveins: $rdi
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gr64 = COPY $rdi
+  ; CHECK-NEXT:   [[MOV32rm:%[0-9]+]]:gr32 = MOV32rm [[COPY]], 1, $noreg, 0, $noreg :: (load (s16) from %ir.p, align 4)
+  ; CHECK-NEXT:   ADJCALLSTACKDOWN64 0, 0, 0, implicit-def dead $rsp, implicit-def dead $eflags, implicit-def dead $ssp, implicit $rsp, implicit $ssp
+  ; CHECK-NEXT:   $edi = COPY [[MOV32rm]]
+  ; CHECK-NEXT:   $esi = COPY [[MOV32rm]]
+  ; CHECK-NEXT:   CALL64pcrel32 target-flags(x86-plt) @use, csr_64, implicit $rsp, implicit $ssp, implicit $edi, implicit $esi, implicit-def $rsp, implicit-def $ssp
+  ; CHECK-NEXT:   ADJCALLSTACKUP64 0, 0, implicit-def dead $rsp, implicit-def dead $eflags, implicit-def dead $ssp, implicit $rsp, implicit $ssp
+  ; CHECK-NEXT:   RET 0
+  %x = load i32, ptr %p
+  %y = load i16, ptr %p, align 4
+  call void @use(i32 %x, i16 %y)
+  ret void
+}

>From b471e69a84bfa349b92b65b94e16bef4bdcf632c Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Tue, 30 Jun 2026 11:54:37 +0200
Subject: [PATCH 2/2] Fix miscompile

---
 .../CodeGen/SelectionDAG/SelectionDAGISel.cpp | 29 ++++++++++++++++++-
 llvm/test/CodeGen/X86/pr37916.ll              |  4 +--
 .../CodeGen/X86/promote-anyext-load-mmo.ll    |  3 +-
 3 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
index 5ae52cae771fb..5b4a581e0f0a7 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
@@ -4461,7 +4461,34 @@ void SelectionDAGISel::SelectCodeCommon(SDNode *NodeToMatch,
           }
         }
 
-        CurDAG->setNodeMemRefs(Res, FilteredMemRefs);
+        if (Res->memoperands_empty()) {
+          // If the node is new, just copy the MMOs.
+          CurDAG->setNodeMemRefs(Res, FilteredMemRefs);
+        } else {
+          // If the node is reused, most likely the existing MMOs are exactly
+          // the same as the new ones, in which case there is nothing to do.
+          bool AllEqual =
+              Res->memoperands().size() == FilteredMemRefs.size() &&
+              all_of(zip_equal(Res->memoperands(), FilteredMemRefs),
+                     [](const std::tuple<const MachineMemOperand *,
+                                         const MachineMemOperand *> &Pair) {
+                       return *std::get<0>(Pair) == *std::get<1>(Pair);
+                     });
+          if (!AllEqual) {
+            // If the MMOs differ, we have to retain both lists to be
+            // conservative. Deduplicate the MMOs.
+            SmallVector<MachineMemOperand *> NewMemRefs;
+            append_range(NewMemRefs, Res->memoperands());
+            for (MachineMemOperand *NewMMO : FilteredMemRefs) {
+              if (any_of(NewMemRefs, [NewMMO](MachineMemOperand *MMO) {
+                    return *MMO == *NewMMO;
+                  }))
+                continue;
+              NewMemRefs.push_back(NewMMO);
+            }
+            CurDAG->setNodeMemRefs(Res, NewMemRefs);
+          }
+        }
       }
 
       LLVM_DEBUG({
diff --git a/llvm/test/CodeGen/X86/pr37916.ll b/llvm/test/CodeGen/X86/pr37916.ll
index e6639a11ca5ea..96e7e866bf4b2 100644
--- a/llvm/test/CodeGen/X86/pr37916.ll
+++ b/llvm/test/CodeGen/X86/pr37916.ll
@@ -10,8 +10,8 @@ define void @fn1() local_unnamed_addr {
 ; CHECK-NEXT:    .p2align 4
 ; CHECK-NEXT:  .LBB0_1: # %if.end
 ; CHECK-NEXT:    # =>This Inner Loop Header: Depth=1
-; CHECK-NEXT:    movl a+4, %eax
-; CHECK-NEXT:    orl a, %eax
+; CHECK-NEXT:    movl a, %eax
+; CHECK-NEXT:    orl a+4, %eax
 ; CHECK-NEXT:    movl $a, f
 ; CHECK-NEXT:    je .LBB0_3
 ; CHECK-NEXT:  # %bb.2: # %if.end
diff --git a/llvm/test/CodeGen/X86/promote-anyext-load-mmo.ll b/llvm/test/CodeGen/X86/promote-anyext-load-mmo.ll
index 1eb49970d9363..03077751b6ed4 100644
--- a/llvm/test/CodeGen/X86/promote-anyext-load-mmo.ll
+++ b/llvm/test/CodeGen/X86/promote-anyext-load-mmo.ll
@@ -3,13 +3,14 @@
 
 declare void @use(i32, i16)
 
+; Should retain both the s32 and s16 MMOs here.
 define void @test(ptr %p) nounwind {
   ; CHECK-LABEL: name: test
   ; CHECK: bb.0 (%ir-block.0):
   ; CHECK-NEXT:   liveins: $rdi
   ; CHECK-NEXT: {{  $}}
   ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gr64 = COPY $rdi
-  ; CHECK-NEXT:   [[MOV32rm:%[0-9]+]]:gr32 = MOV32rm [[COPY]], 1, $noreg, 0, $noreg :: (load (s16) from %ir.p, align 4)
+  ; CHECK-NEXT:   [[MOV32rm:%[0-9]+]]:gr32 = MOV32rm [[COPY]], 1, $noreg, 0, $noreg :: (load (s32) from %ir.p), (load (s16) from %ir.p, align 4)
   ; CHECK-NEXT:   ADJCALLSTACKDOWN64 0, 0, 0, implicit-def dead $rsp, implicit-def dead $eflags, implicit-def dead $ssp, implicit $rsp, implicit $ssp
   ; CHECK-NEXT:   $edi = COPY [[MOV32rm]]
   ; CHECK-NEXT:   $esi = COPY [[MOV32rm]]



More information about the llvm-commits mailing list