[llvm] [CodeGen] Fix liveness tracking removing live sibling sub-registers (PR #181518)
Brian Cain via llvm-commits
llvm-commits at lists.llvm.org
Tue Feb 17 19:07:40 PST 2026
https://github.com/androm3da updated https://github.com/llvm/llvm-project/pull/181518
>From 078b6dc0aeb46baf2f82977839e0085c75075543 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Sat, 14 Feb 2026 16:56:56 -0800
Subject: [PATCH 1/7] [CodeGen] Fix fixupKills incorrectly killing
sub-registers via super-register implicit defs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
fixupKills() walks backward through instructions, removing defined
registers from a LiveRegUnits set, then checking which used registers
are "available" (dead) to set kill flags. When an instruction defines
a sub-register (e.g. $r1) and has an implicit-def of its
super-register ($d0), removeReg($d0) clears the register units of
all sub-registers — including siblings like $r0 that may still be
live. This causes available($r0) to incorrectly return true, setting
a wrong kill flag.
Skip removeReg for implicit defs of super-registers when a
sub-register is also defined by the same instruction. The implicit
def is an annotation of partial modification, not a full
redefinition of the super-register.
---
llvm/lib/CodeGen/ScheduleDAGInstrs.cpp | 20 +++++++++++
.../Hexagon/post-ra-kill-superreg-def.mir | 36 +++++++++++++++++++
2 files changed, 56 insertions(+)
create mode 100644 llvm/test/CodeGen/Hexagon/post-ra-kill-superreg-def.mir
diff --git a/llvm/lib/CodeGen/ScheduleDAGInstrs.cpp b/llvm/lib/CodeGen/ScheduleDAGInstrs.cpp
index 9662511e584c0..4dbb348ca5c22 100644
--- a/llvm/lib/CodeGen/ScheduleDAGInstrs.cpp
+++ b/llvm/lib/CodeGen/ScheduleDAGInstrs.cpp
@@ -1163,6 +1163,26 @@ void ScheduleDAGInstrs::fixupKills(MachineBasicBlock &MBB) {
Register Reg = MO.getReg();
if (!Reg)
continue;
+ // Skip implicit defs of super-registers when a sub-register is
+ // also defined by this instruction. The implicit def indicates a
+ // partial modification of the super-register, not a full
+ // redefinition. Removing the super-register from the live set
+ // would incorrectly clear the liveness of sibling sub-registers
+ // that may still be live, causing toggleKills to set wrong kill
+ // flags on their uses.
+ if (MO.isImplicit()) {
+ bool HasSubRegDef = false;
+ for (ConstMIBundleOperands O2(MI); O2.isValid(); ++O2) {
+ if (!O2->isReg() || !O2->isDef() || !O2->getReg())
+ continue;
+ if (O2->getReg() != Reg && TRI->isSubRegister(Reg, O2->getReg())) {
+ HasSubRegDef = true;
+ break;
+ }
+ }
+ if (HasSubRegDef)
+ continue;
+ }
LiveRegs.removeReg(Reg);
} else if (MO.isRegMask()) {
LiveRegs.removeRegsNotPreserved(MO.getRegMask());
diff --git a/llvm/test/CodeGen/Hexagon/post-ra-kill-superreg-def.mir b/llvm/test/CodeGen/Hexagon/post-ra-kill-superreg-def.mir
new file mode 100644
index 0000000000000..1d899fcdacf30
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/post-ra-kill-superreg-def.mir
@@ -0,0 +1,36 @@
+# RUN: llc -mtriple=hexagon -mcpu=hexagonv60 -run-pass post-RA-sched \
+# RUN: -verify-machineinstrs -o - %s | FileCheck %s
+
+# The fixupKills() function in ScheduleDAGInstrs walks backward through
+# instructions, maintaining a LiveRegUnits bitvector. When an instruction
+# defines a sub-register ($r1) and has an implicit-def of a super-register
+# ($d0), the def processing calls removeReg($d0), which clears the register
+# units of all sub-registers of $d0 — including $r0. If $r0 is live (used
+# by a subsequent instruction), its liveness is incorrectly cleared, causing
+# available($r0) to return true and a wrong kill flag to be set.
+#
+# In this test, A2_abs defines $r1 with an implicit-def of $d0. The
+# subsequent A2_add uses both $r0 and $r1, so $r0 must not be killed
+# on the A2_abs instruction.
+
+# CHECK-LABEL: name: test_kill_superreg_def
+# CHECK: $r1 = A2_abs $r0, implicit-def $d0
+# CHECK-NEXT: $r0 = A2_add killed $r0, killed $r1
+
+--- |
+ define void @test_kill_superreg_def() {
+ ret void
+ }
+...
+
+---
+name: test_kill_superreg_def
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $r0
+
+ $r1 = A2_abs $r0, implicit-def $d0
+ $r0 = A2_add $r0, $r1
+ PS_jmpret $r31, implicit-def dead $pc, implicit $r0
+...
>From 66e555c5d912945b2c2aad1ecea36a955303a54a Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Sun, 15 Feb 2026 10:39:11 -0800
Subject: [PATCH 2/7] [CodeGen] Fix LiveRegUnits::stepBackward() removing live
sibling sub-registers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When an instruction defines a sub-register (e.g. $r1) and has an
implicit-def of its super-register (e.g. $d0), stepBackward() calls
removeReg($d0) which clears the register units for ALL sub-registers
of $d0 — including siblings like $r0 that may still be live. This
causes DeadMachineInstructionElim to incorrectly consider definitions
of those siblings as dead and delete them.
Fix this by skipping removeReg for implicit defs of super-registers
when the instruction also defines one of their sub-registers. The
implicit def in this case is an annotation of partial modification,
not a full redefinition.
This is the same fix previously applied to ScheduleDAGInstrs::fixupKills()
in d7b0aab97b3e, now applied to LiveRegUnits::stepBackward().
---
llvm/lib/CodeGen/LiveRegUnits.cpp | 24 +++++++++++-
.../CodeGen/Hexagon/dead-mi-superreg-def.mir | 39 +++++++++++++++++++
2 files changed, 62 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/CodeGen/Hexagon/dead-mi-superreg-def.mir
diff --git a/llvm/lib/CodeGen/LiveRegUnits.cpp b/llvm/lib/CodeGen/LiveRegUnits.cpp
index 348ccd85f4c45..87b2c15f0395a 100644
--- a/llvm/lib/CodeGen/LiveRegUnits.cpp
+++ b/llvm/lib/CodeGen/LiveRegUnits.cpp
@@ -45,8 +45,30 @@ void LiveRegUnits::stepBackward(const MachineInstr &MI) {
// Remove defined registers and regmask kills from the set.
for (const MachineOperand &MOP : MI.operands()) {
if (MOP.isReg()) {
- if (MOP.isDef() && MOP.getReg().isPhysical())
+ if (MOP.isDef() && MOP.getReg().isPhysical()) {
+ // Skip implicit defs of super-registers when a sub-register is
+ // also defined by this instruction. The implicit def is an
+ // annotation of partial modification, not a full redefinition of
+ // the super-register. Removing the super-register from the live
+ // set would incorrectly clear the liveness of sibling
+ // sub-registers that may still be live.
+ if (MOP.isImplicit()) {
+ MCRegister Reg = MOP.getReg().asMCReg();
+ bool HasSubRegDef = false;
+ for (const MachineOperand &MOP2 : MI.operands()) {
+ if (!MOP2.isReg() || !MOP2.isDef() || !MOP2.getReg().isPhysical())
+ continue;
+ if (MOP2.getReg() != Reg &&
+ TRI->isSubRegister(Reg, MOP2.getReg().asMCReg())) {
+ HasSubRegDef = true;
+ break;
+ }
+ }
+ if (HasSubRegDef)
+ continue;
+ }
removeReg(MOP.getReg());
+ }
continue;
}
diff --git a/llvm/test/CodeGen/Hexagon/dead-mi-superreg-def.mir b/llvm/test/CodeGen/Hexagon/dead-mi-superreg-def.mir
new file mode 100644
index 0000000000000..4a210863ac89b
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/dead-mi-superreg-def.mir
@@ -0,0 +1,39 @@
+# RUN: llc -mtriple=hexagon -run-pass dead-mi-elimination \
+# RUN: -verify-machineinstrs -o - %s | FileCheck %s
+
+# LiveRegUnits::stepBackward() walks backward through instructions,
+# removing defined registers from a LiveRegUnits set. When an
+# instruction defines a sub-register ($r1) and has an implicit-def of
+# its super-register ($d0), removeReg($d0) clears the register units
+# of all sub-registers of $d0 — including siblings like $r0 that may
+# still be live. This causes available($r0) to incorrectly return
+# true, and dead MI elimination deletes the instruction defining $r0.
+#
+# In this test, A2_tfrsi defines $r1 with an implicit-def of $d0. The
+# subsequent J2_call uses both $r0 and $r1 as implicit operands, so
+# $r0 must remain live and "$r0 = A2_tfrsi @g" must not be deleted.
+
+# CHECK-LABEL: name: test_dead_mi_superreg
+# CHECK: $r0 = A2_tfrsi @g
+# CHECK: $r1 = A2_tfrsi 42, implicit-def $d0
+# CHECK: J2_call @foo
+
+--- |
+ @g = external global i32
+ define void @test_dead_mi_superreg() {
+ ret void
+ }
+ declare void @foo(ptr, i32)
+...
+
+---
+name: test_dead_mi_superreg
+tracksRegLiveness: true
+body: |
+ bb.0:
+ $r0 = A2_tfrsi @g
+ $r1 = A2_tfrsi 42, implicit-def $d0
+ J2_call @foo, implicit $r0, implicit $r1, implicit-def $r0, implicit-def $r1, implicit-def $d0
+ $r0 = A2_tfrsi 0
+ PS_jmpret $r31, implicit-def dead $pc, implicit $r0
+...
>From 15b516ba534b3aa1b7dfe5c3c41f56fe7a6a9014 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Sun, 15 Feb 2026 10:39:26 -0800
Subject: [PATCH 3/7] [CodeGen] Fix LivePhysRegs::removeDefs() removing live
sibling sub-registers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When an instruction defines a sub-register (e.g. $r1) and has an
implicit-def of its super-register (e.g. $d0), removeDefs() calls
removeReg($d0) which clears the liveness of ALL sub-registers of $d0
— including siblings like $r0 that may still be live. This causes
incorrect live-in computation in passes that use LivePhysRegs
(branch-folder, register scavenging, liveness recomputation, etc.),
which can lead to downstream miscompilation or crashes.
Fix this by skipping removeReg for implicit defs of super-registers
when the instruction also defines one of their sub-registers. The
implicit def in this case is an annotation of partial modification,
not a full redefinition.
This is the same fix previously applied to ScheduleDAGInstrs::fixupKills()
and LiveRegUnits::stepBackward(), now applied to LivePhysRegs::removeDefs().
---
llvm/lib/CodeGen/LivePhysRegs.cpp | 24 ++++++-
.../Hexagon/livephysregs-superreg-def.mir | 67 +++++++++++++++++++
2 files changed, 90 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/CodeGen/Hexagon/livephysregs-superreg-def.mir
diff --git a/llvm/lib/CodeGen/LivePhysRegs.cpp b/llvm/lib/CodeGen/LivePhysRegs.cpp
index 5c8f060c09e5f..7c45c2f62f8b1 100644
--- a/llvm/lib/CodeGen/LivePhysRegs.cpp
+++ b/llvm/lib/CodeGen/LivePhysRegs.cpp
@@ -49,8 +49,30 @@ void LivePhysRegs::removeDefs(const MachineInstr &MI) {
continue;
}
- if (MOP.isDef())
+ if (MOP.isDef()) {
+ // Skip implicit defs of super-registers when a sub-register is
+ // also defined by this instruction. The implicit def is an
+ // annotation of partial modification, not a full redefinition of
+ // the super-register. Removing the super-register from the live
+ // set would incorrectly clear the liveness of sibling
+ // sub-registers that may still be live.
+ if (MOP.isImplicit()) {
+ MCRegister Reg = MOP.getReg().asMCReg();
+ bool HasSubRegDef = false;
+ for (const MachineOperand &MOP2 : phys_regs_and_masks(MI)) {
+ if (!MOP2.isReg() || !MOP2.isDef())
+ continue;
+ MCRegister Reg2 = MOP2.getReg().asMCReg();
+ if (Reg2 != Reg && TRI->isSubRegister(Reg, Reg2)) {
+ HasSubRegDef = true;
+ break;
+ }
+ }
+ if (HasSubRegDef)
+ continue;
+ }
removeReg(MOP.getReg());
+ }
}
}
diff --git a/llvm/test/CodeGen/Hexagon/livephysregs-superreg-def.mir b/llvm/test/CodeGen/Hexagon/livephysregs-superreg-def.mir
new file mode 100644
index 0000000000000..beaa02ffa8a71
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/livephysregs-superreg-def.mir
@@ -0,0 +1,67 @@
+# RUN: llc -mtriple=hexagon -run-pass branch-folder \
+# RUN: -verify-machineinstrs -o - %s | FileCheck %s
+
+# LivePhysRegs::removeDefs() has the same super-register implicit def
+# issue as LiveRegUnits::stepBackward() and ScheduleDAGInstrs::fixupKills():
+# when an instruction defines a sub-register ($r1) and has an implicit-def
+# of its super-register ($d0), removeReg($d0) clears the register units of
+# all sub-registers — including siblings like $r0 that may still be live.
+#
+# This test has two blocks (bb.1 and bb.2) with a common tail. The common
+# tail includes "$r1 = A2_tfrsi 42, implicit-def $d0" followed by a call
+# that uses both $r0 and $r1. The branch folder merges the common tails.
+# After merging, the common tail block's live-in computation must include
+# $r0, because it is used by the call instruction.
+#
+# Without the fix to LivePhysRegs::removeDefs(), stepBackward over
+# "$r1 = A2_tfrsi 42, implicit-def $d0" incorrectly removes $r0 from the
+# live set, so $r0 would not appear in the merged block's live-ins.
+
+# CHECK-LABEL: name: test_livephysregs_superreg
+
+# The common tail block (merged from bb.1/bb.2 tails) must have
+# $r0 in its live-ins since J2_call uses it.
+# CHECK: liveins: $r0
+# CHECK: $r1 = A2_tfrsi 42, implicit-def $d0
+# CHECK-NEXT: J2_call @foo
+
+--- |
+ @g = external global i32
+ define void @test_livephysregs_superreg() {
+ ret void
+ }
+ declare void @foo(ptr, i32)
+...
+
+---
+name: test_livephysregs_superreg
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $r0, $r31
+ successors: %bb.1, %bb.2
+ J2_jumpt undef $p0, %bb.2, implicit-def $pc
+ J2_jump %bb.1, implicit-def $pc
+
+ bb.1:
+ liveins: $r0, $r31
+ successors: %bb.3
+ $r2 = A2_tfrsi 1
+ $r1 = A2_tfrsi 42, implicit-def $d0
+ J2_call @foo, implicit $r0, implicit $r1, implicit-def $r0, implicit-def $r1, implicit-def $d0
+ $r0 = A2_tfrsi 0
+ J2_jump %bb.3, implicit-def $pc
+
+ bb.2:
+ liveins: $r0, $r31
+ successors: %bb.3
+ $r2 = A2_tfrsi 2
+ $r1 = A2_tfrsi 42, implicit-def $d0
+ J2_call @foo, implicit $r0, implicit $r1, implicit-def $r0, implicit-def $r1, implicit-def $d0
+ $r0 = A2_tfrsi 0
+ J2_jump %bb.3, implicit-def $pc
+
+ bb.3:
+ liveins: $r0, $r31
+ PS_jmpret $r31, implicit-def dead $pc, implicit $r0
+...
>From 57f9be7e85776cb872aaeddcddb3c76ba9c36342 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Sun, 15 Feb 2026 20:20:56 -0800
Subject: [PATCH 4/7] fixup! [CodeGen] Fix LiveRegUnits::stepBackward()
removing live sibling sub-registers
Update X86 test expectations for corrected LiveRegUnits liveness. The
X86FixupBWInsts pass uses LiveRegUnits and now correctly identifies live
sibling sub-registers, so it no longer promotes narrow loads (movw/movb)
to zero-extending loads (movzwl/movzbl) when the upper bits are live.
---
llvm/test/CodeGen/X86/anyext.ll | 2 +-
llvm/test/CodeGen/X86/bittest-big-integer.ll | 4 ++--
llvm/test/CodeGen/X86/cmov.ll | 2 +-
llvm/test/CodeGen/X86/scmp.ll | 4 ++--
llvm/test/CodeGen/X86/sdiv_fix.ll | 4 ++--
llvm/test/CodeGen/X86/sdiv_fix_sat.ll | 4 ++--
6 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/llvm/test/CodeGen/X86/anyext.ll b/llvm/test/CodeGen/X86/anyext.ll
index ec23948d1a431..8238c97ad6726 100644
--- a/llvm/test/CodeGen/X86/anyext.ll
+++ b/llvm/test/CodeGen/X86/anyext.ll
@@ -30,7 +30,7 @@ define i32 @foo(i32 %p, i8 zeroext %x) nounwind {
define i32 @bar(i32 %p, i16 zeroext %x) nounwind {
; X86-LABEL: bar:
; X86: # %bb.0:
-; X86-NEXT: movzwl {{[0-9]+}}(%esp), %eax
+; X86-NEXT: movw {{[0-9]+}}(%esp), %ax
; X86-NEXT: xorl %edx, %edx
; X86-NEXT: divw {{[0-9]+}}(%esp)
; X86-NEXT: # kill: def $ax killed $ax def $eax
diff --git a/llvm/test/CodeGen/X86/bittest-big-integer.ll b/llvm/test/CodeGen/X86/bittest-big-integer.ll
index 023fb5065b892..4953c0b148d47 100644
--- a/llvm/test/CodeGen/X86/bittest-big-integer.ll
+++ b/llvm/test/CodeGen/X86/bittest-big-integer.ll
@@ -1413,7 +1413,7 @@ define i1 @sequence_i128(ptr %word, i32 %pos0, i32 %pos1, i32 %pos2) nounwind {
; AVX2-NEXT: cmovneq %r8, %rsi
; AVX2-NEXT: cmovneq %r9, %r8
; AVX2-NEXT: xorl %r11d, %r11d
-; AVX2-NEXT: movl %eax, %ecx
+; AVX2-NEXT: movb %al, %cl
; AVX2-NEXT: shldq %cl, %r10, %r11
; AVX2-NEXT: shlxq %rax, %r10, %r10
; AVX2-NEXT: testb $64, %al
@@ -1449,7 +1449,7 @@ define i1 @sequence_i128(ptr %word, i32 %pos0, i32 %pos1, i32 %pos2) nounwind {
; AVX512-NEXT: cmovneq %r8, %rsi
; AVX512-NEXT: cmovneq %r10, %r8
; AVX512-NEXT: xorl %r11d, %r11d
-; AVX512-NEXT: movl %eax, %ecx
+; AVX512-NEXT: movb %al, %cl
; AVX512-NEXT: shldq %cl, %r9, %r11
; AVX512-NEXT: shlxq %rax, %r9, %r9
; AVX512-NEXT: testb $64, %al
diff --git a/llvm/test/CodeGen/X86/cmov.ll b/llvm/test/CodeGen/X86/cmov.ll
index 374e75967d52f..a1bf7f8eeee7f 100644
--- a/llvm/test/CodeGen/X86/cmov.ll
+++ b/llvm/test/CodeGen/X86/cmov.ll
@@ -102,7 +102,7 @@ define i1 @test4() nounwind {
; CHECK-NEXT: testb %bl, %bl
; CHECK-NEXT: jne .LBB3_5
; CHECK-NEXT: # %bb.4: # %bb.i.i
-; CHECK-NEXT: movzbl g_100(%rip), %ecx
+; CHECK-NEXT: movb g_100(%rip), %cl
; CHECK-NEXT: xorl %ebx, %ebx
; CHECK-NEXT: movl %eax, %ecx
; CHECK-NEXT: .LBB3_5: # %func_1.exit
diff --git a/llvm/test/CodeGen/X86/scmp.ll b/llvm/test/CodeGen/X86/scmp.ll
index d03d0727b15df..9ba9aa969a330 100644
--- a/llvm/test/CodeGen/X86/scmp.ll
+++ b/llvm/test/CodeGen/X86/scmp.ll
@@ -2830,7 +2830,7 @@ define <7 x i117> @scmp_uncommon_vectors(<7 x i7> %x, <7 x i7> %y) nounwind {
; AVX-NEXT: movq %rdi, %rax
; AVX-NEXT: movzbl {{[0-9]+}}(%rsp), %edi
; AVX-NEXT: movzbl {{[0-9]+}}(%rsp), %r10d
-; AVX-NEXT: movzbl {{[0-9]+}}(%rsp), %r11d
+; AVX-NEXT: movb {{[0-9]+}}(%rsp), %r11b
; AVX-NEXT: movzbl {{[0-9]+}}(%rsp), %ebx
; AVX-NEXT: movzbl {{[0-9]+}}(%rsp), %ebp
; AVX-NEXT: movzbl {{[0-9]+}}(%rsp), %r15d
@@ -2904,7 +2904,7 @@ define <7 x i117> @scmp_uncommon_vectors(<7 x i7> %x, <7 x i7> %y) nounwind {
; AVX-NEXT: sarq $63, %r10
; AVX-NEXT: addb %dil, %dil
; AVX-NEXT: sarb %dil
-; AVX-NEXT: movzbl {{[0-9]+}}(%rsp), %r11d
+; AVX-NEXT: movb {{[0-9]+}}(%rsp), %r11b
; AVX-NEXT: addb %r11b, %r11b
; AVX-NEXT: sarb %r11b
; AVX-NEXT: cmpb %dil, %r11b
diff --git a/llvm/test/CodeGen/X86/sdiv_fix.ll b/llvm/test/CodeGen/X86/sdiv_fix.ll
index 392bc83d9d5d8..31636df4fc198 100644
--- a/llvm/test/CodeGen/X86/sdiv_fix.ll
+++ b/llvm/test/CodeGen/X86/sdiv_fix.ll
@@ -131,7 +131,7 @@ define i16 @func3(i15 %x, i8 %y) nounwind {
; X64-NEXT: movswl %si, %ecx
; X64-NEXT: addl %edi, %edi
; X64-NEXT: shrl $4, %ecx
-; X64-NEXT: movl %edi, %eax
+; X64-NEXT: movw %di, %ax
; X64-NEXT: cwtd
; X64-NEXT: idivw %cx
; X64-NEXT: # kill: def $ax killed $ax def $rax
@@ -161,7 +161,7 @@ define i16 @func3(i15 %x, i8 %y) nounwind {
; X86-NEXT: movswl %ax, %esi
; X86-NEXT: addl %ecx, %ecx
; X86-NEXT: shrl $4, %esi
-; X86-NEXT: movl %ecx, %eax
+; X86-NEXT: movw %cx, %ax
; X86-NEXT: cwtd
; X86-NEXT: idivw %si
; X86-NEXT: # kill: def $ax killed $ax def $eax
diff --git a/llvm/test/CodeGen/X86/sdiv_fix_sat.ll b/llvm/test/CodeGen/X86/sdiv_fix_sat.ll
index 7df490f984928..2cee39184e031 100644
--- a/llvm/test/CodeGen/X86/sdiv_fix_sat.ll
+++ b/llvm/test/CodeGen/X86/sdiv_fix_sat.ll
@@ -154,7 +154,7 @@ define i16 @func3(i15 %x, i8 %y) nounwind {
; X64-NEXT: movswl %si, %ecx
; X64-NEXT: addl %edi, %edi
; X64-NEXT: shrl $4, %ecx
-; X64-NEXT: movl %edi, %eax
+; X64-NEXT: movw %di, %ax
; X64-NEXT: cwtd
; X64-NEXT: idivw %cx
; X64-NEXT: # kill: def $ax killed $ax def $rax
@@ -189,7 +189,7 @@ define i16 @func3(i15 %x, i8 %y) nounwind {
; X86-NEXT: movswl %ax, %esi
; X86-NEXT: addl %ecx, %ecx
; X86-NEXT: shrl $4, %esi
-; X86-NEXT: movl %ecx, %eax
+; X86-NEXT: movw %cx, %ax
; X86-NEXT: cwtd
; X86-NEXT: idivw %si
; X86-NEXT: # kill: def $ax killed $ax def $eax
>From 941403d742b67e92c20184b6c529fda9bc273833 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Sun, 15 Feb 2026 20:21:03 -0800
Subject: [PATCH 5/7] fixup! [CodeGen] Fix LivePhysRegs::removeDefs() removing
live sibling sub-registers
Update ARM/Thumb2 test expectations for corrected LivePhysRegs liveness.
The ARM Constant Island and Low Overhead Loops passes use LivePhysRegs,
which now correctly preserves live sibling sub-registers. This adds
newly-visible live registers to liveins lists and removes incorrect
killed flags on super-registers when sub-register defs are present.
---
.../CodeGen/ARM/constant-island-movwt.mir | 13 ++++----
.../lstp-insertion-position.mir | 4 +--
.../CodeGen/Thumb2/LowOverheadLoops/vaddv.mir | 32 +++++++++----------
3 files changed, 25 insertions(+), 24 deletions(-)
diff --git a/llvm/test/CodeGen/ARM/constant-island-movwt.mir b/llvm/test/CodeGen/ARM/constant-island-movwt.mir
index 215d73f6d9774..f223a1181b78a 100644
--- a/llvm/test/CodeGen/ARM/constant-island-movwt.mir
+++ b/llvm/test/CodeGen/ARM/constant-island-movwt.mir
@@ -900,12 +900,13 @@ body: |
# CHECK-NEXT: bb.2.entry (align 2):
# CHECK-NEXT: liveins: $s26, $s27, $r10, $r9, $r8, $d13, $s24, $s25,
# CHECK-SAME: $d12, $d15, $s30, $s31, $d14, $s28, $s29, $lr,
-# CHECK-SAME: $d21, $q10, $r7, $r0, $d20, $d17, $r2, $q12,
-# CHECK-SAME: $q11, $d22, $d23, $r1, $q8, $d16, $d30, $q14,
-# CHECK-SAME: $d28, $d29, $d19, $s17, $r4, $d8, $r6, $r3,
-# CHECK-SAME: $s16, $d25, $q9, $d18, $s0, $d31, $s3, $q15,
-# CHECK-SAME: $r12, $d0, $s1, $d24, $d1, $s2, $q0, $s5, $d2,
-# CHECK-SAME: $q1, $s4, $s7, $d3, $s6, $d9, $s18, $s19, $q4
+# CHECK-SAME: $q1, $d22, $q10, $d16, $d4, $d20, $r3, $d21,
+# CHECK-SAME: $d19, $d29, $r0, $d17, $r2, $s9, $q11, $d23,
+# CHECK-SAME: $s1, $q8, $d31, $q14, $d28, $d18, $s16, $d24,
+# CHECK-SAME: $r6, $d8, $s4, $q9, $r1, $s0, $d25, $d2,
+# CHECK-SAME: $q15, $q4, $r12, $d0, $q12, $d30, $q0, $s6,
+# CHECK-SAME: $d3, $s5, $s3, $s7, $d1, $s2, $s17, $s8,
+# CHECK-SAME: $r7, $d9, $s18, $s19, $r4
# CHECK-NEXT: {{^ $}}
# CHECK-NEXT: $r5 = t2MOVi16 target-flags(arm-lo16) @.str.84, 14 /* CC::al */, $noreg
# CHECK-NEXT: $r5 = t2MOVTi16 $r5, target-flags(arm-hi16) @.str.84, 14 /* CC::al */, $noreg
diff --git a/llvm/test/CodeGen/Thumb2/LowOverheadLoops/lstp-insertion-position.mir b/llvm/test/CodeGen/Thumb2/LowOverheadLoops/lstp-insertion-position.mir
index 780831c8faeb7..370405c5b374c 100644
--- a/llvm/test/CodeGen/Thumb2/LowOverheadLoops/lstp-insertion-position.mir
+++ b/llvm/test/CodeGen/Thumb2/LowOverheadLoops/lstp-insertion-position.mir
@@ -162,7 +162,7 @@ body: |
; CHECK-NEXT: renamable $lr = nuw nsw t2ADDrs killed renamable $r3, killed renamable $r12, 19, 14 /* CC::al */, $noreg, $noreg
; CHECK-NEXT: renamable $r3 = tLDRpci %const.0, 14 /* CC::al */, $noreg :: (load (s32) from constant-pool)
; CHECK-NEXT: renamable $q1 = MVE_VDUP32 killed renamable $r3, 0, $noreg, $noreg, undef renamable $q1
- ; CHECK-NEXT: $s4 = VMOVS killed $s0, 14 /* CC::al */, $noreg, implicit killed $q1, implicit-def $q1
+ ; CHECK-NEXT: $s4 = VMOVS killed $s0, 14 /* CC::al */, $noreg, implicit $q1, implicit-def $q1
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: bb.2.vector.body:
; CHECK-NEXT: successors: %bb.2(0x7c000000), %bb.3(0x04000000)
@@ -303,7 +303,7 @@ body: |
; CHECK-NEXT: renamable $r3 = tLDRpci %const.0, 14 /* CC::al */, $noreg :: (load (s32) from constant-pool)
; CHECK-NEXT: renamable $q1 = MVE_VDUP32 killed renamable $r3, 0, $noreg, $noreg, undef renamable $q1
; CHECK-NEXT: renamable $r2, dead $cpsr = tLSRri killed renamable $r2, 2, 14 /* CC::al */, $noreg
- ; CHECK-NEXT: $s4 = VMOVS killed $s0, 14 /* CC::al */, $noreg, implicit killed $q1, implicit-def $q1
+ ; CHECK-NEXT: $s4 = VMOVS killed $s0, 14 /* CC::al */, $noreg, implicit $q1, implicit-def $q1
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: bb.2.vector.body:
; CHECK-NEXT: successors: %bb.2(0x7c000000), %bb.3(0x04000000)
diff --git a/llvm/test/CodeGen/Thumb2/LowOverheadLoops/vaddv.mir b/llvm/test/CodeGen/Thumb2/LowOverheadLoops/vaddv.mir
index 2019e6bf98a3e..2dd601d1ddfae 100644
--- a/llvm/test/CodeGen/Thumb2/LowOverheadLoops/vaddv.mir
+++ b/llvm/test/CodeGen/Thumb2/LowOverheadLoops/vaddv.mir
@@ -1902,7 +1902,7 @@ body: |
; CHECK-LABEL: name: illegal_vaddva_s16
; CHECK: bb.0.entry:
; CHECK-NEXT: successors: %bb.4(0x30000000), %bb.1(0x50000000)
- ; CHECK-NEXT: liveins: $lr, $r0, $r1, $r2, $r3, $r4
+ ; CHECK-NEXT: liveins: $lr, $d1, $r0, $r1, $r2, $r3, $r4
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: frame-setup tPUSH 14 /* CC::al */, $noreg, killed $r4, killed $lr, implicit-def $sp, implicit $sp
; CHECK-NEXT: frame-setup CFI_INSTRUCTION def_cfa_offset 8
@@ -1912,14 +1912,14 @@ body: |
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: bb.1.vector.ph:
; CHECK-NEXT: successors: %bb.2(0x80000000)
- ; CHECK-NEXT: liveins: $r0, $r1, $r2, $r3
+ ; CHECK-NEXT: liveins: $d1, $r0, $r1, $r2, $r3
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: renamable $d0 = VMOVDRR killed renamable $r2, killed renamable $r3, 14 /* CC::al */, $noreg, implicit-def $q0
; CHECK-NEXT: renamable $r2, dead $cpsr = tADDi3 renamable $r1, 3, 14 /* CC::al */, $noreg
; CHECK-NEXT: renamable $r2 = t2BICri killed renamable $r2, 3, 14 /* CC::al */, $noreg, $noreg
; CHECK-NEXT: renamable $r3, dead $cpsr = tMOVi8 1, 14 /* CC::al */, $noreg
; CHECK-NEXT: renamable $r2, dead $cpsr = tSUBi8 killed renamable $r2, 4, 14 /* CC::al */, $noreg
- ; CHECK-NEXT: renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit killed $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
+ ; CHECK-NEXT: renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
; CHECK-NEXT: renamable $r2 = nuw nsw t2ADDrs killed renamable $r3, killed renamable $r2, 19, 14 /* CC::al */, $noreg, $noreg
; CHECK-NEXT: renamable $r3, dead $cpsr = tMOVi8 0, 14 /* CC::al */, $noreg
; CHECK-NEXT: dead $lr = t2DLS renamable $r2
@@ -1969,7 +1969,7 @@ body: |
renamable $r2 = t2BICri killed renamable $r2, 3, 14 /* CC::al */, $noreg, $noreg
renamable $r3, dead $cpsr = tMOVi8 1, 14 /* CC::al */, $noreg
renamable $r2, dead $cpsr = tSUBi8 killed renamable $r2, 4, 14 /* CC::al */, $noreg
- renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit killed $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
+ renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
renamable $r2 = nuw nsw t2ADDrs killed renamable $r3, killed renamable $r2, 19, 14 /* CC::al */, $noreg, $noreg
renamable $r3, dead $cpsr = tMOVi8 0, 14 /* CC::al */, $noreg
$lr = t2DoLoopStart renamable $r2
@@ -2169,7 +2169,7 @@ body: |
; CHECK-LABEL: name: illegal_vaddva_u16
; CHECK: bb.0.entry:
; CHECK-NEXT: successors: %bb.4(0x30000000), %bb.1(0x50000000)
- ; CHECK-NEXT: liveins: $lr, $r0, $r1, $r2, $r3, $r4
+ ; CHECK-NEXT: liveins: $lr, $d1, $r0, $r1, $r2, $r3, $r4
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: frame-setup tPUSH 14 /* CC::al */, $noreg, killed $r4, killed $lr, implicit-def $sp, implicit $sp
; CHECK-NEXT: frame-setup CFI_INSTRUCTION def_cfa_offset 8
@@ -2179,14 +2179,14 @@ body: |
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: bb.1.vector.ph:
; CHECK-NEXT: successors: %bb.2(0x80000000)
- ; CHECK-NEXT: liveins: $r0, $r1, $r2, $r3
+ ; CHECK-NEXT: liveins: $d1, $r0, $r1, $r2, $r3
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: renamable $d0 = VMOVDRR killed renamable $r2, killed renamable $r3, 14 /* CC::al */, $noreg, implicit-def $q0
; CHECK-NEXT: renamable $r2, dead $cpsr = tADDi3 renamable $r1, 3, 14 /* CC::al */, $noreg
; CHECK-NEXT: renamable $r2 = t2BICri killed renamable $r2, 3, 14 /* CC::al */, $noreg, $noreg
; CHECK-NEXT: renamable $r3, dead $cpsr = tMOVi8 1, 14 /* CC::al */, $noreg
; CHECK-NEXT: renamable $r2, dead $cpsr = tSUBi8 killed renamable $r2, 4, 14 /* CC::al */, $noreg
- ; CHECK-NEXT: renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit killed $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
+ ; CHECK-NEXT: renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
; CHECK-NEXT: renamable $r2 = nuw nsw t2ADDrs killed renamable $r3, killed renamable $r2, 19, 14 /* CC::al */, $noreg, $noreg
; CHECK-NEXT: renamable $r3, dead $cpsr = tMOVi8 0, 14 /* CC::al */, $noreg
; CHECK-NEXT: dead $lr = t2DLS renamable $r2
@@ -2236,7 +2236,7 @@ body: |
renamable $r2 = t2BICri killed renamable $r2, 3, 14 /* CC::al */, $noreg, $noreg
renamable $r3, dead $cpsr = tMOVi8 1, 14 /* CC::al */, $noreg
renamable $r2, dead $cpsr = tSUBi8 killed renamable $r2, 4, 14 /* CC::al */, $noreg
- renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit killed $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
+ renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
renamable $r2 = nuw nsw t2ADDrs killed renamable $r3, killed renamable $r2, 19, 14 /* CC::al */, $noreg, $noreg
renamable $r3, dead $cpsr = tMOVi8 0, 14 /* CC::al */, $noreg
$lr = t2DoLoopStart renamable $r2
@@ -2436,7 +2436,7 @@ body: |
; CHECK-LABEL: name: illegal_vaddva_s8
; CHECK: bb.0.entry:
; CHECK-NEXT: successors: %bb.4(0x30000000), %bb.1(0x50000000)
- ; CHECK-NEXT: liveins: $lr, $r0, $r1, $r2, $r3, $r4
+ ; CHECK-NEXT: liveins: $lr, $d1, $r0, $r1, $r2, $r3, $r4
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: frame-setup tPUSH 14 /* CC::al */, $noreg, killed $r4, killed $lr, implicit-def $sp, implicit $sp
; CHECK-NEXT: frame-setup CFI_INSTRUCTION def_cfa_offset 8
@@ -2446,14 +2446,14 @@ body: |
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: bb.1.vector.ph:
; CHECK-NEXT: successors: %bb.2(0x80000000)
- ; CHECK-NEXT: liveins: $r0, $r1, $r2, $r3
+ ; CHECK-NEXT: liveins: $d1, $r0, $r1, $r2, $r3
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: renamable $d0 = VMOVDRR killed renamable $r2, killed renamable $r3, 14 /* CC::al */, $noreg, implicit-def $q0
; CHECK-NEXT: renamable $r2, dead $cpsr = tADDi3 renamable $r1, 7, 14 /* CC::al */, $noreg
; CHECK-NEXT: renamable $r2 = t2BICri killed renamable $r2, 7, 14 /* CC::al */, $noreg, $noreg
; CHECK-NEXT: renamable $r3, dead $cpsr = tMOVi8 1, 14 /* CC::al */, $noreg
; CHECK-NEXT: renamable $r2, dead $cpsr = tSUBi8 killed renamable $r2, 7, 14 /* CC::al */, $noreg
- ; CHECK-NEXT: renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit killed $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
+ ; CHECK-NEXT: renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
; CHECK-NEXT: renamable $r2 = nuw nsw t2ADDrs killed renamable $r3, killed renamable $r2, 27, 14 /* CC::al */, $noreg, $noreg
; CHECK-NEXT: renamable $r3, dead $cpsr = tMOVi8 0, 14 /* CC::al */, $noreg
; CHECK-NEXT: dead $lr = t2DLS renamable $r2
@@ -2503,7 +2503,7 @@ body: |
renamable $r2 = t2BICri killed renamable $r2, 7, 14 /* CC::al */, $noreg, $noreg
renamable $r3, dead $cpsr = tMOVi8 1, 14 /* CC::al */, $noreg
renamable $r2, dead $cpsr = tSUBi8 killed renamable $r2, 7, 14 /* CC::al */, $noreg
- renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit killed $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
+ renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
renamable $r2 = nuw nsw t2ADDrs killed renamable $r3, killed renamable $r2, 27, 14 /* CC::al */, $noreg, $noreg
renamable $r3, dead $cpsr = tMOVi8 0, 14 /* CC::al */, $noreg
$lr = t2DoLoopStart renamable $r2
@@ -2703,7 +2703,7 @@ body: |
; CHECK-LABEL: name: illegal_vaddva_u8
; CHECK: bb.0.entry:
; CHECK-NEXT: successors: %bb.4(0x30000000), %bb.1(0x50000000)
- ; CHECK-NEXT: liveins: $lr, $r0, $r1, $r2, $r3, $r4
+ ; CHECK-NEXT: liveins: $lr, $d1, $r0, $r1, $r2, $r3, $r4
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: frame-setup tPUSH 14 /* CC::al */, $noreg, killed $r4, killed $lr, implicit-def $sp, implicit $sp
; CHECK-NEXT: frame-setup CFI_INSTRUCTION def_cfa_offset 8
@@ -2713,14 +2713,14 @@ body: |
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: bb.1.vector.ph:
; CHECK-NEXT: successors: %bb.2(0x80000000)
- ; CHECK-NEXT: liveins: $r0, $r1, $r2, $r3
+ ; CHECK-NEXT: liveins: $d1, $r0, $r1, $r2, $r3
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: renamable $d0 = VMOVDRR killed renamable $r2, killed renamable $r3, 14 /* CC::al */, $noreg, implicit-def $q0
; CHECK-NEXT: renamable $r2, dead $cpsr = tADDi3 renamable $r1, 7, 14 /* CC::al */, $noreg
; CHECK-NEXT: renamable $r2 = t2BICri killed renamable $r2, 7, 14 /* CC::al */, $noreg, $noreg
; CHECK-NEXT: renamable $r3, dead $cpsr = tMOVi8 1, 14 /* CC::al */, $noreg
; CHECK-NEXT: renamable $r2, dead $cpsr = tSUBi8 killed renamable $r2, 7, 14 /* CC::al */, $noreg
- ; CHECK-NEXT: renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit killed $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
+ ; CHECK-NEXT: renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
; CHECK-NEXT: renamable $r2 = nuw nsw t2ADDrs killed renamable $r3, killed renamable $r2, 27, 14 /* CC::al */, $noreg, $noreg
; CHECK-NEXT: renamable $r3, dead $cpsr = tMOVi8 0, 14 /* CC::al */, $noreg
; CHECK-NEXT: dead $lr = t2DLS renamable $r2
@@ -2770,7 +2770,7 @@ body: |
renamable $r2 = t2BICri killed renamable $r2, 7, 14 /* CC::al */, $noreg, $noreg
renamable $r3, dead $cpsr = tMOVi8 1, 14 /* CC::al */, $noreg
renamable $r2, dead $cpsr = tSUBi8 killed renamable $r2, 7, 14 /* CC::al */, $noreg
- renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit killed $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
+ renamable $d1 = VLDRD $sp, 2, 14 /* CC::al */, $noreg, implicit $q0, implicit-def $q0 :: (load (s64) from %fixed-stack.0)
renamable $r2 = nuw nsw t2ADDrs killed renamable $r3, killed renamable $r2, 27, 14 /* CC::al */, $noreg, $noreg
renamable $r3, dead $cpsr = tMOVi8 0, 14 /* CC::al */, $noreg
$lr = t2DoLoopStart renamable $r2
>From 349e16a7375b502490cfd175c28810cb8d7ba805 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Mon, 16 Feb 2026 11:39:31 -0800
Subject: [PATCH 6/7] fixup! [CodeGen] Fix LiveRegUnits::stepBackward()
removing live sibling sub-registers
Update SystemZ test expectations for corrected LiveRegUnits liveness.
The SystemZShortenInst pass uses LiveRegUnits, which now correctly
preserves live sibling sub-registers. This prevents the instruction
shortening from llilh (which zeros the full 64-bit register) to iilf
(which only replaces the low 32-bit word, preserving the high word)
when the high word of the register is live.
---
llvm/test/CodeGen/SystemZ/int-ssub-06.ll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/test/CodeGen/SystemZ/int-ssub-06.ll b/llvm/test/CodeGen/SystemZ/int-ssub-06.ll
index aea317c7b9492..6de766c91ed12 100644
--- a/llvm/test/CodeGen/SystemZ/int-ssub-06.ll
+++ b/llvm/test/CodeGen/SystemZ/int-ssub-06.ll
@@ -73,7 +73,7 @@ define zeroext i1 @f4(i32 %dummy, i32 %a, ptr %res) {
; and must use a register.
define zeroext i1 @f5(i32 %dummy, i32 %a, ptr %res) {
; CHECK-LABEL: f5:
-; CHECK: llilh [[REG1:%r[0-5]]], 32768
+; CHECK: iilf [[REG1:%r[0-5]]], 2147483648
; CHECK: sr %r3, [[REG1]]
; CHECK-DAG: st %r3, 0(%r4)
; CHECK-DAG: ipm [[REG:%r[0-5]]]
@@ -171,7 +171,7 @@ define zeroext i1 @f10(i32 %dummy, i32 %a, ptr %res) {
; Check the next value down, which must use a register.
define zeroext i1 @f11(i32 %dummy, i32 %a, ptr %res) {
; CHECK-LABEL: f11:
-; CHECK: llilh [[REG1:%r[0-5]]], 32768
+; CHECK: iilf [[REG1:%r[0-5]]], 2147483648
; CHECK: sr %r3, [[REG1]]
; CHECK-DAG: st %r3, 0(%r4)
; CHECK-DAG: ipm [[REG:%r[0-5]]]
>From c1b1d15763a26ea6a0fd154e438800a46e2956cf Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Tue, 17 Feb 2026 10:20:31 -0800
Subject: [PATCH 7/7] [SystemZ] Refine sub-register liveness in
SystemZShortenInst for RISBG and pass-through instructions
The LiveRegUnits::stepBackward() correctness fix (66e555c5d912) correctly
preserves sibling sub-register liveness when processing implicit-defs of
super-registers. However, this exposes a pre-existing imprecision:
RISBG-family instructions model their R2 source as a full GR64 read,
even when the Start/End/Rotate immediates select bits from only one
32-bit half. Additionally, instructions with implicit use + implicit-def
of a GR64 alongside an explicit sub-register def (e.g. AFI) create false
pass-through liveness. Together, these block shortenIIF from converting
IILF to LLILH (6-byte to 4-byte encoding).
Add two helpers to SystemZShortenInst:
- getUnneededRISBGSubReg: analyzes RISBG-family bit-range to determine
which half of R2 is genuinely unneeded.
- getPreservedSubRegs: collects sub-registers whose liveness should not
be forced by stepBackward, combining RISBG analysis with general
pass-through detection for implicit super-register use/def pairs.
In the processBlock loop, before stepBackward, save the liveness state
of preserved sub-registers. After stepBackward, remove any that were
freshly forced live but shouldn't be.
---
.../lib/Target/SystemZ/SystemZShortenInst.cpp | 141 ++++++++++++++++++
llvm/test/CodeGen/SystemZ/int-ssub-06.ll | 4 +-
2 files changed, 143 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Target/SystemZ/SystemZShortenInst.cpp b/llvm/lib/Target/SystemZ/SystemZShortenInst.cpp
index 96a41487c87e3..003f400f29a81 100644
--- a/llvm/lib/Target/SystemZ/SystemZShortenInst.cpp
+++ b/llvm/lib/Target/SystemZ/SystemZShortenInst.cpp
@@ -42,6 +42,9 @@ class SystemZShortenInst : public MachineFunctionPass {
bool shortenOn001AddCC(MachineInstr &MI, unsigned Opcode);
bool shortenFPConv(MachineInstr &MI, unsigned Opcode);
bool shortenFusedFPOp(MachineInstr &MI, unsigned Opcode);
+ MCRegister getUnneededRISBGSubReg(const MachineInstr &MI) const;
+ void getPreservedSubRegs(const MachineInstr &MI,
+ SmallVectorImpl<MCRegister> &Result) const;
const SystemZInstrInfo *TII;
const TargetRegisterInfo *TRI;
@@ -200,6 +203,128 @@ bool SystemZShortenInst::shortenFusedFPOp(MachineInstr &MI, unsigned Opcode) {
return false;
}
+// For RISBG-family instructions, return the sub-register of R2 that is NOT
+// needed by the instruction's bit selection, or MCRegister() if both halves
+// may be needed. This allows refining the liveness of R2 when only one
+// 32-bit half is actually read.
+MCRegister
+SystemZShortenInst::getUnneededRISBGSubReg(const MachineInstr &MI) const {
+ bool IsOpt;
+ switch (MI.getOpcode()) {
+ case SystemZ::RISBG:
+ case SystemZ::RISBGZ:
+ case SystemZ::RISBGN:
+ case SystemZ::RISBGNZ:
+ case SystemZ::RNSBG:
+ case SystemZ::ROSBG:
+ case SystemZ::RXSBG:
+ IsOpt = false;
+ break;
+ case SystemZ::RISBGOpt:
+ case SystemZ::RISBGZOpt:
+ case SystemZ::RISBGNOpt:
+ case SystemZ::RISBGNZOpt:
+ case SystemZ::RNSBGOpt:
+ case SystemZ::ROSBGOpt:
+ case SystemZ::RXSBGOpt:
+ IsOpt = true;
+ break;
+ default:
+ return MCRegister();
+ }
+
+ // Operand layout:
+ // 0: R1 (def), 1: R1src (tied), 2: R2, 3: I3 (start), 4: I4 (end)
+ // 5: I5 (rotation) [regular only; Opt variants have I5 implicitly 0]
+ Register R1 = MI.getOperand(0).getReg();
+ const MachineOperand &R1Src = MI.getOperand(1);
+ Register R2 = MI.getOperand(2).getReg();
+ unsigned I3 = MI.getOperand(3).getImm();
+ unsigned I4 = MI.getOperand(4).getImm();
+ unsigned I5 = IsOpt ? 0 : MI.getOperand(5).getImm();
+
+ // Only refine GR64 registers.
+ if (!SystemZ::GR64BitRegClass.contains(R2))
+ return MCRegister();
+
+ // If R1 == R2 and R1src is actually read, the full GR64 is needed
+ // (the non-selected bits come from R1src which aliases R2).
+ if (R1 == R2 && R1Src.readsReg())
+ return MCRegister();
+
+ unsigned Start = I3 & 63;
+ unsigned End = I4 & 63;
+ unsigned Rotate = I5 & 63;
+
+ // Wrapping selection in the instruction-level bit range.
+ if (Start > End)
+ return MCRegister();
+
+ // De-rotate to find the original bit positions in R2.
+ unsigned OrigStart = (Start + Rotate) & 63;
+ unsigned OrigEnd = (End + Rotate) & 63;
+
+ // Wrapping after de-rotation means both halves may be touched.
+ if (OrigStart > OrigEnd)
+ return MCRegister();
+
+ // SystemZ bit numbering: 0-31 = subreg_h32, 32-63 = subreg_l32.
+ if (OrigEnd <= 31)
+ return TRI->getSubReg(R2, SystemZ::subreg_l32);
+ if (OrigStart >= 32)
+ return TRI->getSubReg(R2, SystemZ::subreg_h32);
+
+ return MCRegister();
+}
+
+// Collect sub-registers whose liveness should not be forced by stepBackward.
+// Two sources:
+//
+// 1. RISBG-family: bit-range analysis shows only one half of R2 is needed.
+//
+// 2. Pass-through: an instruction with implicit use + implicit-def of a GR64
+// alongside an explicit sub-register def merely preserves the other half.
+// stepBackward's implicit use would force it live, but it should only
+// remain live if it was already live before the instruction.
+void SystemZShortenInst::getPreservedSubRegs(
+ const MachineInstr &MI, SmallVectorImpl<MCRegister> &Result) const {
+ // RISBG refinement.
+ if (MCRegister R = getUnneededRISBGSubReg(MI))
+ Result.push_back(R);
+
+ // Detect pass-through sub-registers.
+ for (const MachineOperand &MO : MI.operands()) {
+ if (!MO.isReg() || !MO.readsReg() || !MO.isImplicit() ||
+ !MO.getReg().isPhysical())
+ continue;
+ MCRegister SuperReg = MO.getReg().asMCReg();
+ if (!SystemZ::GR64BitRegClass.contains(SuperReg))
+ continue;
+
+ // Look for a matching implicit-def and explicit sub-register def.
+ bool HasImplicitDef = false;
+ MCRegister ExplicitSubDef;
+ for (const MachineOperand &MO2 : MI.operands()) {
+ if (!MO2.isReg() || !MO2.isDef() || !MO2.getReg().isPhysical())
+ continue;
+ MCRegister DefReg = MO2.getReg().asMCReg();
+ if (DefReg == SuperReg && MO2.isImplicit())
+ HasImplicitDef = true;
+ else if (TRI->isSubRegister(SuperReg, DefReg) && !MO2.isImplicit())
+ ExplicitSubDef = DefReg;
+ }
+ if (!HasImplicitDef || !ExplicitSubDef)
+ continue;
+
+ // The sub-register NOT explicitly defined has pass-through semantics.
+ unsigned SubIdx = TRI->getSubRegIndex(SuperReg, ExplicitSubDef);
+ unsigned OtherIdx = (SubIdx == SystemZ::subreg_l32) ? SystemZ::subreg_h32
+ : SystemZ::subreg_l32;
+ if (MCRegister Other = TRI->getSubReg(SuperReg, OtherIdx))
+ Result.push_back(Other);
+ }
+}
+
// Process all instructions in MBB. Return true if something changed.
bool SystemZShortenInst::processBlock(MachineBasicBlock &MBB) {
bool Changed = false;
@@ -374,7 +499,23 @@ bool SystemZShortenInst::processBlock(MachineBasicBlock &MBB) {
}
}
+ // Collect sub-registers that should preserve their liveness through
+ // this instruction rather than being forced live by stepBackward.
+ SmallVector<MCRegister, 2> PreservedRegs;
+ getPreservedSubRegs(MI, PreservedRegs);
+
+ // Save whether each preserved sub-register was already live.
+ SmallVector<bool, 2> WasLive;
+ for (MCRegister R : PreservedRegs)
+ WasLive.push_back(!LiveRegs.available(R));
+
LiveRegs.stepBackward(MI);
+
+ // Remove sub-registers that were freshly forced live but shouldn't be.
+ for (unsigned I = 0, E = PreservedRegs.size(); I != E; ++I) {
+ if (!WasLive[I])
+ LiveRegs.removeReg(PreservedRegs[I]);
+ }
}
return Changed;
diff --git a/llvm/test/CodeGen/SystemZ/int-ssub-06.ll b/llvm/test/CodeGen/SystemZ/int-ssub-06.ll
index 6de766c91ed12..aea317c7b9492 100644
--- a/llvm/test/CodeGen/SystemZ/int-ssub-06.ll
+++ b/llvm/test/CodeGen/SystemZ/int-ssub-06.ll
@@ -73,7 +73,7 @@ define zeroext i1 @f4(i32 %dummy, i32 %a, ptr %res) {
; and must use a register.
define zeroext i1 @f5(i32 %dummy, i32 %a, ptr %res) {
; CHECK-LABEL: f5:
-; CHECK: iilf [[REG1:%r[0-5]]], 2147483648
+; CHECK: llilh [[REG1:%r[0-5]]], 32768
; CHECK: sr %r3, [[REG1]]
; CHECK-DAG: st %r3, 0(%r4)
; CHECK-DAG: ipm [[REG:%r[0-5]]]
@@ -171,7 +171,7 @@ define zeroext i1 @f10(i32 %dummy, i32 %a, ptr %res) {
; Check the next value down, which must use a register.
define zeroext i1 @f11(i32 %dummy, i32 %a, ptr %res) {
; CHECK-LABEL: f11:
-; CHECK: iilf [[REG1:%r[0-5]]], 2147483648
+; CHECK: llilh [[REG1:%r[0-5]]], 32768
; CHECK: sr %r3, [[REG1]]
; CHECK-DAG: st %r3, 0(%r4)
; CHECK-DAG: ipm [[REG:%r[0-5]]]
More information about the llvm-commits
mailing list