[llvm] [CodeGen] Delete mov from imul sequence by allowing rm form (PR #190304)

Takashi Idobe via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 6 06:18:05 PDT 2026


https://github.com/Takashiidobe updated https://github.com/llvm/llvm-project/pull/190304

>From b630149d32f6e8b5c75b790f1990619636b0f8c0 Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Thu, 2 Apr 2026 23:37:49 -0400
Subject: [PATCH 1/5] test only commit for unnecessary mov in two
 multiplications

---
 .../CodeGen/X86/mul-lohi-no-implicit-copy.ll  | 44 +++++++++++++++++++
 1 file changed, 44 insertions(+)

diff --git a/llvm/test/CodeGen/X86/mul-lohi-no-implicit-copy.ll b/llvm/test/CodeGen/X86/mul-lohi-no-implicit-copy.ll
index 0135ec230cf28..0141dff091936 100644
--- a/llvm/test/CodeGen/X86/mul-lohi-no-implicit-copy.ll
+++ b/llvm/test/CodeGen/X86/mul-lohi-no-implicit-copy.ll
@@ -37,3 +37,47 @@ define i64 @mul64_no_implicit_copy(i64 %a0) nounwind {
   %a3 = extractvalue { i64, i1 } %a2, 0
   ret i64 %a3
 }
+
+define i64 @mul64_add_hi_order_a(ptr %x, i64 %y) {
+; CHECK-LABEL: mul64_add_hi_order_a:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movq %rsi, %rax
+; CHECK-NEXT:    movq 8(%rdi), %rcx
+; CHECK-NEXT:    imulq %rsi, %rcx
+; CHECK-NEXT:    mulq (%rdi)
+; CHECK-NEXT:    addq %rdx, %rcx
+; CHECK-NEXT:    movq %rcx, %rax
+; CHECK-NEXT:    retq
+  %p1 = getelementptr inbounds i64, ptr %x, i64 1
+  %qv = load i64, ptr %p1, align 8
+  %q = mul i64 %qv, %y
+  %pv = load i64, ptr %x, align 8
+  %pv.zext = zext i64 %pv to i128
+  %y.zext = zext i64 %y to i128
+  %prod = mul nuw i128 %pv.zext, %y.zext
+  %prod.hi = lshr i128 %prod, 64
+  %p = trunc i128 %prod.hi to i64
+  %sum = add i64 %q, %p
+  ret i64 %sum
+}
+
+define i64 @mul64_add_hi_order_b(ptr %x, i64 %y) {
+; CHECK-LABEL: mul64_add_hi_order_b:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movq %rsi, %rax
+; CHECK-NEXT:    mulq (%rdi)
+; CHECK-NEXT:    imulq 8(%rdi), %rsi
+; CHECK-NEXT:    leaq (%rsi,%rdx), %rax
+; CHECK-NEXT:    retq
+  %pv = load i64, ptr %x, align 8
+  %pv.zext = zext i64 %pv to i128
+  %y.zext = zext i64 %y to i128
+  %prod = mul nuw i128 %pv.zext, %y.zext
+  %prod.hi = lshr i128 %prod, 64
+  %p = trunc i128 %prod.hi to i64
+  %p1 = getelementptr inbounds i64, ptr %x, i64 1
+  %qv = load i64, ptr %p1, align 8
+  %q = mul i64 %qv, %y
+  %sum = add i64 %q, %p
+  ret i64 %sum
+}

>From 266ceb32267cabd74524bc17cb1e355eb97fe019 Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Thu, 2 Apr 2026 23:40:06 -0400
Subject: [PATCH 2/5] fix unnecessary mov for mul -> imul chain by hoisting
 copy kill before imul to allow for rm form instead of rr form to be used for
 imul

---
 .../lib/CodeGen/TwoAddressInstructionPass.cpp | 22 +++++++++++++++++--
 .../CodeGen/X86/mul-lohi-no-implicit-copy.ll  |  6 ++---
 llvm/test/CodeGen/X86/pr78897.ll              | 10 ++++-----
 3 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/CodeGen/TwoAddressInstructionPass.cpp b/llvm/lib/CodeGen/TwoAddressInstructionPass.cpp
index ace5c6e49596e..475b63b88bf84 100644
--- a/llvm/lib/CodeGen/TwoAddressInstructionPass.cpp
+++ b/llvm/lib/CodeGen/TwoAddressInstructionPass.cpp
@@ -1148,10 +1148,28 @@ bool TwoAddressInstructionImpl::rescheduleKillAboveMI(
   } else {
     KillMI = LV->getVarInfo(Reg).findKill(MBB);
   }
-  if (!KillMI || MI == KillMI || KillMI->isCopy() || KillMI->isCopyLike())
-    // Don't mess with copies, they may be coalesced later.
+  if (!KillMI || MI == KillMI)
     return false;
 
+  bool IsCopyKill = KillMI->isCopy() || KillMI->isCopyLike();
+  if (IsCopyKill) {
+    if (!MI->mayLoad())
+      return false;
+
+    Register CopySrcReg, CopyDstReg;
+    bool IsCopySrcPhys, IsCopyDstPhys;
+    // Most copies are better left for coalescing. Allow moving only the
+    // case of a kill-copy from a source virtual register into a
+    // physical register when the current two-address instruction has a folded
+    // load; that preserves the memory form and avoids introducing a load+copy.
+    if (!isCopyToReg(*KillMI, CopySrcReg, CopyDstReg, IsCopySrcPhys,
+                     IsCopyDstPhys))
+      return false;
+
+    if (CopySrcReg != Reg || IsCopySrcPhys || !IsCopyDstPhys)
+      return false;
+  }
+
   Register DstReg;
   if (isTwoAddrUse(*KillMI, Reg, DstReg))
     return false;
diff --git a/llvm/test/CodeGen/X86/mul-lohi-no-implicit-copy.ll b/llvm/test/CodeGen/X86/mul-lohi-no-implicit-copy.ll
index 0141dff091936..6815a4d178f22 100644
--- a/llvm/test/CodeGen/X86/mul-lohi-no-implicit-copy.ll
+++ b/llvm/test/CodeGen/X86/mul-lohi-no-implicit-copy.ll
@@ -42,11 +42,9 @@ define i64 @mul64_add_hi_order_a(ptr %x, i64 %y) {
 ; CHECK-LABEL: mul64_add_hi_order_a:
 ; CHECK:       # %bb.0:
 ; CHECK-NEXT:    movq %rsi, %rax
-; CHECK-NEXT:    movq 8(%rdi), %rcx
-; CHECK-NEXT:    imulq %rsi, %rcx
+; CHECK-NEXT:    imulq 8(%rdi), %rsi
 ; CHECK-NEXT:    mulq (%rdi)
-; CHECK-NEXT:    addq %rdx, %rcx
-; CHECK-NEXT:    movq %rcx, %rax
+; CHECK-NEXT:    leaq (%rdx,%rsi), %rax
 ; CHECK-NEXT:    retq
   %p1 = getelementptr inbounds i64, ptr %x, i64 1
   %qv = load i64, ptr %p1, align 8
diff --git a/llvm/test/CodeGen/X86/pr78897.ll b/llvm/test/CodeGen/X86/pr78897.ll
index db77baa7ff8a3..f5e8d77a87a3f 100644
--- a/llvm/test/CodeGen/X86/pr78897.ll
+++ b/llvm/test/CodeGen/X86/pr78897.ll
@@ -136,12 +136,12 @@ define <16 x i8> @produceShuffleVectorForByte(i8 zeroext %0) nounwind {
 ; X64-SSE42:       # %bb.0: # %entry
 ; X64-SSE42-NEXT:    movd %edi, %xmm0
 ; X64-SSE42-NEXT:    punpcklbw {{.*#+}} xmm0 = xmm0[0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7]
-; X64-SSE42-NEXT:    pshuflw {{.*#+}} xmm1 = xmm0[0,0,0,0,4,5,6,7]
+; X64-SSE42-NEXT:    pshuflw {{.*#+}} xmm0 = xmm0[0,0,0,0,4,5,6,7]
+; X64-SSE42-NEXT:    pand {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %xmm0
+; X64-SSE42-NEXT:    pxor %xmm1, %xmm1
+; X64-SSE42-NEXT:    pcmpeqb %xmm0, %xmm1
+; X64-SSE42-NEXT:    movdqa %xmm1, %xmm0
 ; X64-SSE42-NEXT:    pand {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %xmm1
-; X64-SSE42-NEXT:    pxor %xmm0, %xmm0
-; X64-SSE42-NEXT:    pcmpeqb %xmm1, %xmm0
-; X64-SSE42-NEXT:    movdqa {{.*#+}} xmm1 = [17,17,17,17,17,17,17,17,u,u,u,u,u,u,u,u]
-; X64-SSE42-NEXT:    pand %xmm0, %xmm1
 ; X64-SSE42-NEXT:    movq %xmm1, %rax
 ; X64-SSE42-NEXT:    movabsq $1229782938247303440, %rcx # imm = 0x1111111111111110
 ; X64-SSE42-NEXT:    movabsq $76861433640456465, %rdx # imm = 0x111111111111111

>From 35f81d5f766046e6ebb8960c12788b273d1bd042 Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Fri, 3 Apr 2026 08:17:12 -0400
Subject: [PATCH 3/5] Update failing tests from change

---
 llvm/test/CodeGen/X86/tailcallstack64.ll | 10 +++++-----
 llvm/test/CodeGen/X86/tailccstack64.ll   | 10 +++++-----
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/llvm/test/CodeGen/X86/tailcallstack64.ll b/llvm/test/CodeGen/X86/tailcallstack64.ll
index 77295ee0ff871..ef57ede471886 100644
--- a/llvm/test/CodeGen/X86/tailcallstack64.ll
+++ b/llvm/test/CodeGen/X86/tailcallstack64.ll
@@ -5,12 +5,12 @@
 ; CHECK: subq  ${{24|72|80}}, %rsp
 
 ; Check that lowered arguments on the stack do not overwrite each other.
-; Add %in1 %p1 to a different temporary register (%eax).
-; CHECK: movl  [[A1:32|144]](%rsp), [[R1:%e..|%r.*d]]
-; Move param %in1 to temp register (%r10d).
+; Move %p1 to a temporary register (%eax).
+; CHECK: movl  {{%edi|%ecx}}, [[R1:%e..|%r.*d]]
+; Fold the load of %in1 into the add.
+; CHECK: addl  [[A1:32|144]](%rsp), [[R1]]
+; Move param %in2 to temp register (%r10d).
 ; CHECK: movl  [[A2:40|152]](%rsp), [[R2:%[a-z0-9]+]]
-; Add %in1 %p1 to a different temporary register (%eax).
-; CHECK: addl {{%edi|%ecx}}, [[R1]]
 ; Move param %in2 to stack.
 ; CHECK-DAG: movl  [[R2]], [[A1]](%rsp)
 ; Move result of addition to stack.
diff --git a/llvm/test/CodeGen/X86/tailccstack64.ll b/llvm/test/CodeGen/X86/tailccstack64.ll
index bcedea5e16384..9b3754a9ee27d 100644
--- a/llvm/test/CodeGen/X86/tailccstack64.ll
+++ b/llvm/test/CodeGen/X86/tailccstack64.ll
@@ -6,12 +6,12 @@
 ; CHECK: subq  ${{24|72|80}}, %rsp
 
 ; Check that lowered arguments on the stack do not overwrite each other.
-; Add %in1 %p1 to a different temporary register (%eax).
-; CHECK: movl  [[A1:32|144]](%rsp), [[R1:%e..|%r.*d]]
-; Move param %in1 to temp register (%r10d).
+; Move %p1 to a temporary register (%eax).
+; CHECK: movl  {{%edi|%ecx}}, [[R1:%e..|%r.*d]]
+; Fold the load of %in1 into the add.
+; CHECK: addl  [[A1:32|144]](%rsp), [[R1]]
+; Move param %in2 to temp register (%r10d).
 ; CHECK: movl  [[A2:40|152]](%rsp), [[R2:%[a-z0-9]+]]
-; Add %in1 %p1 to a different temporary register (%eax).
-; CHECK: addl {{%edi|%ecx}}, [[R1]]
 ; Move param %in2 to stack.
 ; CHECK-DAG: movl  [[R2]], [[A1]](%rsp)
 ; Move result of addition to stack.

>From e5dfd3e89912f3aaf498b09e4a7fbc7d209a9558 Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Mon, 6 Apr 2026 09:03:09 -0400
Subject: [PATCH 4/5] code review feedback: add MIR test and use isCopyLike
 only since it includes isCopy + isSubregToReg

---
 .../lib/CodeGen/TwoAddressInstructionPass.cpp |  2 +-
 .../X86/two-address-subreg-to-reg-kill.mir    | 57 +++++++++++++++++++
 2 files changed, 58 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/CodeGen/X86/two-address-subreg-to-reg-kill.mir

diff --git a/llvm/lib/CodeGen/TwoAddressInstructionPass.cpp b/llvm/lib/CodeGen/TwoAddressInstructionPass.cpp
index 475b63b88bf84..2b0f2afaa1383 100644
--- a/llvm/lib/CodeGen/TwoAddressInstructionPass.cpp
+++ b/llvm/lib/CodeGen/TwoAddressInstructionPass.cpp
@@ -1151,7 +1151,7 @@ bool TwoAddressInstructionImpl::rescheduleKillAboveMI(
   if (!KillMI || MI == KillMI)
     return false;
 
-  bool IsCopyKill = KillMI->isCopy() || KillMI->isCopyLike();
+  bool IsCopyKill = KillMI->isCopyLike();
   if (IsCopyKill) {
     if (!MI->mayLoad())
       return false;
diff --git a/llvm/test/CodeGen/X86/two-address-subreg-to-reg-kill.mir b/llvm/test/CodeGen/X86/two-address-subreg-to-reg-kill.mir
new file mode 100644
index 0000000000000..57383292fc262
--- /dev/null
+++ b/llvm/test/CodeGen/X86/two-address-subreg-to-reg-kill.mir
@@ -0,0 +1,57 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 6
+# RUN: llc -mtriple=x86_64-unknown-linux-gnu --run-pass=livevars,twoaddressinstruction %s -o - | FileCheck %s
+
+# Positive case: IMUL32rm with %1 as a tied source should be hoisted 
+# before the pass, subreg_to_reg kills a 32-bit vreg (%1) and writes to
+# physical $eax, which allows IMUL32rm can use %eax.
+---
+name: subreg_to_reg_kill_hoist
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: gr64 }
+  - { id: 1, class: gr32 }
+  - { id: 2, class: gr32 }
+liveins:
+  - { reg: '$rdi', virtual-reg: '%0' }
+  - { reg: '$esi', virtual-reg: '%1' }
+body: |
+  bb.0:
+    liveins: $rdi, $esi
+
+    ; CHECK-LABEL: name: subreg_to_reg_kill_hoist
+    ; CHECK: [[COPY1:%[0-9]+]]:gr32 = COPY killed $esi
+    ; CHECK-NEXT: dead $rax = SUBREG_TO_REG [[COPY1]], %subreg.sub_32bit
+    ; CHECK: IMUL32rm
+    %0:gr64 = COPY killed $rdi
+    %1:gr32 = COPY killed $esi
+    dead %2:gr32 = IMUL32rm %1, %0, 1, $noreg, 4, $noreg, implicit-def dead $eflags :: (load (s32) from `ptr undef`, align 4)
+    dead $rax = SUBREG_TO_REG killed %1, %subreg.sub_32bit
+    RET 0
+
+---
+# Negative case: subreg_to_reg with a vreg is not hoisted.
+# The pass keeps the IMUL32rm as is and MOV32rm + IMUL32rr is emitted instead.
+name: subreg_to_reg_virtual_dst_no_hoist
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: gr64 }
+  - { id: 1, class: gr32 }
+  - { id: 2, class: gr32 }
+  - { id: 3, class: gr64 }
+liveins:
+  - { reg: '$rdi', virtual-reg: '%0' }
+  - { reg: '$esi', virtual-reg: '%1' }
+body: |
+  bb.0:
+    liveins: $rdi, $esi
+
+    ; CHECK-LABEL: name: subreg_to_reg_virtual_dst_no_hoist
+    ; CHECK: MOV32rm
+    ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gr32 = COPY
+    ; CHECK-NEXT: dead [[COPY2]]:gr32 = IMUL32rr [[COPY2]], %1, implicit-def dead $eflags
+    %0:gr64 = COPY killed $rdi
+    %1:gr32 = COPY killed $esi
+    dead %2:gr32 = IMUL32rm %1, %0, 1, $noreg, 4, $noreg, implicit-def dead $eflags :: (load (s32) from `ptr undef`, align 4)
+    dead %3:gr64 = SUBREG_TO_REG killed %1, %subreg.sub_32bit
+    RET 0
+...

>From 0562075a56a31185001d2024d3baa4551724c196 Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Mon, 6 Apr 2026 09:17:54 -0400
Subject: [PATCH 5/5] add mir tests for copy only case

---
 .../X86/two-address-subreg-to-reg-kill.mir    | 51 +++++++++++++++++++
 1 file changed, 51 insertions(+)

diff --git a/llvm/test/CodeGen/X86/two-address-subreg-to-reg-kill.mir b/llvm/test/CodeGen/X86/two-address-subreg-to-reg-kill.mir
index 57383292fc262..dbbcff0498ded 100644
--- a/llvm/test/CodeGen/X86/two-address-subreg-to-reg-kill.mir
+++ b/llvm/test/CodeGen/X86/two-address-subreg-to-reg-kill.mir
@@ -54,4 +54,55 @@ body: |
     dead %2:gr32 = IMUL32rm %1, %0, 1, $noreg, 4, $noreg, implicit-def dead $eflags :: (load (s32) from `ptr undef`, align 4)
     dead %3:gr64 = SUBREG_TO_REG killed %1, %subreg.sub_32bit
     RET 0
+
+---
+# Positive case: COPY to a physical register is hoisted above IMUL32rm.
+name: copy_kill_hoist
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: gr64 }
+  - { id: 1, class: gr32 }
+  - { id: 2, class: gr32 }
+liveins:
+  - { reg: '$rdi', virtual-reg: '%0' }
+  - { reg: '$esi', virtual-reg: '%1' }
+body: |
+  bb.0:
+    liveins: $rdi, $esi
+
+    ; CHECK-LABEL: name: copy_kill_hoist
+    ; CHECK: [[COPY1:%[0-9]+]]:gr32 = COPY killed $esi
+    ; CHECK-NEXT: dead $eax = COPY [[COPY1]]
+    ; CHECK: IMUL32rm
+    %0:gr64 = COPY killed $rdi
+    %1:gr32 = COPY killed $esi
+    dead %2:gr32 = IMUL32rm %1, %0, 1, $noreg, 4, $noreg, implicit-def dead $eflags :: (load (s32) from `ptr undef`, align 4)
+    dead $eax = COPY killed %1
+    RET 0
+
+---
+# Negative case: COPY with a vreg destination is not hoisted.
+name: copy_virtual_dst_no_hoist
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: gr64 }
+  - { id: 1, class: gr32 }
+  - { id: 2, class: gr32 }
+  - { id: 3, class: gr32 }
+liveins:
+  - { reg: '$rdi', virtual-reg: '%0' }
+  - { reg: '$esi', virtual-reg: '%1' }
+body: |
+  bb.0:
+    liveins: $rdi, $esi
+
+    ; CHECK-LABEL: name: copy_virtual_dst_no_hoist
+    ; CHECK: MOV32rm
+    ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gr32 = COPY
+    ; CHECK-NEXT: dead [[COPY2]]:gr32 = IMUL32rr [[COPY2]], %1, implicit-def dead $eflags
+    %0:gr64 = COPY killed $rdi
+    %1:gr32 = COPY killed $esi
+    dead %2:gr32 = IMUL32rm %1, %0, 1, $noreg, 4, $noreg, implicit-def dead $eflags :: (load (s32) from `ptr undef`, align 4)
+    dead %3:gr32 = COPY killed %1
+    RET 0
 ...



More information about the llvm-commits mailing list