Skip to content

Commit 22687aa

Browse files
authored
[CodeGen] Correctly handle non-standard cases in RemoveLoadsIntoFakeUses (#111551)
In the RemoveLoadsIntoFakeUses pass, we try to remove loads that are only used by fake uses, as well as the fake use in question. There are two existing errors with the pass however: it incorrectly examines every operand of each FAKE_USE, when only the first is relevant (extra operands will just be "killed" regs assigned by a previous pass), and it ignores cases where the FAKE_USE register is not an exact match for the loaded register, which is incorrect as regalloc may choose to load a wider value than the FAKE_USE required pre-regalloc. This patch fixes both of these cases.
1 parent 37b595c commit 22687aa

File tree

2 files changed

+213
-28
lines changed

2 files changed

+213
-28
lines changed

llvm/lib/CodeGen/RemoveLoadsIntoFakeUses.cpp

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "llvm/IR/Function.h"
3333
#include "llvm/InitializePasses.h"
3434
#include "llvm/Support/Debug.h"
35+
#include "llvm/Target/TargetMachine.h"
3536

3637
using namespace llvm;
3738

@@ -74,6 +75,10 @@ INITIALIZE_PASS_END(RemoveLoadsIntoFakeUses, DEBUG_TYPE,
7475
"Remove Loads Into Fake Uses", false, false)
7576

7677
bool RemoveLoadsIntoFakeUses::runOnMachineFunction(MachineFunction &MF) {
78+
// Skip this pass if we would use VarLoc-based LDV, as there may be DBG_VALUE
79+
// instructions of the restored values that would become invalid.
80+
if (!MF.useDebugInstrRef())
81+
return false;
7782
// Only run this for functions that have fake uses.
7883
if (!MF.hasFakeUses() || skipFunction(MF.getFunction()))
7984
return false;
@@ -86,20 +91,20 @@ bool RemoveLoadsIntoFakeUses::runOnMachineFunction(MachineFunction &MF) {
8691
const TargetInstrInfo *TII = ST.getInstrInfo();
8792
const TargetRegisterInfo *TRI = ST.getRegisterInfo();
8893

89-
SmallDenseMap<Register, SmallVector<MachineInstr *>> RegFakeUses;
94+
SmallVector<MachineInstr *> RegFakeUses;
9095
LivePhysRegs.init(*TRI);
9196
SmallVector<MachineInstr *, 16> Statepoints;
9297
for (MachineBasicBlock *MBB : post_order(&MF)) {
98+
RegFakeUses.clear();
9399
LivePhysRegs.addLiveOuts(*MBB);
94100

95101
for (MachineInstr &MI : make_early_inc_range(reverse(*MBB))) {
96102
if (MI.isFakeUse()) {
97-
for (const MachineOperand &MO : MI.operands()) {
98-
// Track the Fake Uses that use this register so that we can delete
99-
// them if we delete the corresponding load.
100-
if (MO.isReg())
101-
RegFakeUses[MO.getReg()].push_back(&MI);
102-
}
103+
if (MI.getNumOperands() == 0 || !MI.getOperand(0).isReg())
104+
continue;
105+
// Track the Fake Uses that use these register units so that we can
106+
// delete them if we delete the corresponding load.
107+
RegFakeUses.push_back(&MI);
103108
// Do not record FAKE_USE uses in LivePhysRegs so that we can recognize
104109
// otherwise-unused loads.
105110
continue;
@@ -109,31 +114,38 @@ bool RemoveLoadsIntoFakeUses::runOnMachineFunction(MachineFunction &MF) {
109114
// reload of a spilled register.
110115
if (MI.getRestoreSize(TII)) {
111116
Register Reg = MI.getOperand(0).getReg();
112-
assert(Reg.isPhysical() && "VReg seen in function with NoVRegs set?");
113117
// Don't delete live physreg defs, or any reserved register defs.
114118
if (!LivePhysRegs.available(Reg) || MRI->isReserved(Reg))
115119
continue;
116-
// There should be an exact match between the loaded register and the
117-
// FAKE_USE use. If not, this is a load that is unused by anything? It
118-
// should probably be deleted, but that's outside of this pass' scope.
119-
if (RegFakeUses.contains(Reg)) {
120+
// There should typically be an exact match between the loaded register
121+
// and the FAKE_USE, but sometimes regalloc will choose to load a larger
122+
// value than is needed. Therefore, as long as the load isn't used by
123+
// anything except at least one FAKE_USE, we will delete it. If it isn't
124+
// used by any fake uses, it should still be safe to delete but we
125+
// choose to ignore it so that this pass has no side effects unrelated
126+
// to fake uses.
127+
SmallDenseSet<MachineInstr *> FakeUsesToDelete;
128+
SmallVector<MachineInstr *> RemainingFakeUses;
129+
for (MachineInstr *&FakeUse : reverse(RegFakeUses)) {
130+
if (FakeUse->readsRegister(Reg, TRI)) {
131+
FakeUsesToDelete.insert(FakeUse);
132+
RegFakeUses.erase(&FakeUse);
133+
}
134+
}
135+
if (!FakeUsesToDelete.empty()) {
120136
LLVM_DEBUG(dbgs() << "RemoveLoadsIntoFakeUses: DELETING: " << MI);
121-
// It is possible that some DBG_VALUE instructions refer to this
122-
// instruction. They will be deleted in the live debug variable
123-
// analysis.
137+
// Since this load only exists to restore a spilled register and we
138+
// haven't, run LiveDebugValues yet, there shouldn't be any DBG_VALUEs
139+
// for this load; otherwise, deleting this would be incorrect.
124140
MI.eraseFromParent();
125141
AnyChanges = true;
126142
++NumLoadsDeleted;
127-
// Each FAKE_USE now appears to be a fake use of the previous value
128-
// of the loaded register; delete them to avoid incorrectly
129-
// interpreting them as such.
130-
for (MachineInstr *FakeUse : RegFakeUses[Reg]) {
143+
for (MachineInstr *FakeUse : FakeUsesToDelete) {
131144
LLVM_DEBUG(dbgs()
132145
<< "RemoveLoadsIntoFakeUses: DELETING: " << *FakeUse);
133146
FakeUse->eraseFromParent();
134147
}
135-
NumFakeUsesDeleted += RegFakeUses[Reg].size();
136-
RegFakeUses[Reg].clear();
148+
NumFakeUsesDeleted += FakeUsesToDelete.size();
137149
}
138150
continue;
139151
}
@@ -143,13 +155,15 @@ bool RemoveLoadsIntoFakeUses::runOnMachineFunction(MachineFunction &MF) {
143155
// that register.
144156
if (!RegFakeUses.empty()) {
145157
for (const MachineOperand &MO : MI.operands()) {
146-
if (MO.isReg() && MO.isDef()) {
147-
Register Reg = MO.getReg();
148-
assert(Reg.isPhysical() &&
149-
"VReg seen in function with NoVRegs set?");
150-
for (MCRegUnit Unit : TRI->regunits(Reg))
151-
RegFakeUses.erase(Unit);
152-
}
158+
if (!MO.isReg())
159+
continue;
160+
Register Reg = MO.getReg();
161+
// We clear RegFakeUses for this register and all subregisters,
162+
// because any such FAKE_USE encountered prior is no longer relevant
163+
// for later encountered loads.
164+
for (MachineInstr *&FakeUse : reverse(RegFakeUses))
165+
if (FakeUse->readsRegister(Reg, TRI))
166+
RegFakeUses.erase(&FakeUse);
153167
}
154168
}
155169
LivePhysRegs.stepBackward(MI);
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 5
2+
# Ensure that loads into FAKE_USEs are correctly removed by the
3+
# remove-loads-into-fake-uses pass, and that if the function does not use
4+
# instruction referencing then no changes are made.
5+
# RUN: llc %s -run-pass remove-loads-into-fake-uses -mtriple=x86_64-unknown-linux -debug-only=remove-loads-into-fake-uses 2>&1 -o - | FileCheck %s
6+
# REQUIRES: asserts
7+
#
8+
## We verify that:
9+
## - The load into the FAKE_USE is removed, along with the FAKE_USE itself,
10+
## even when the FAKE_USE is for a subregister of the move.
11+
## - We correctly handle situations where FAKE_USE has additional `killed`
12+
## operands added by other passes.
13+
## - The store to the stack slot still exists.
14+
## - When the register has a use between the restore and the FAKE_USE, we do
15+
## not delete the load or fake use.
16+
17+
18+
---
19+
name: enabled
20+
alignment: 16
21+
tracksRegLiveness: true
22+
noPhis: true
23+
noVRegs: true
24+
hasFakeUses: true
25+
tracksDebugUserValues: true
26+
debugInstrRef: true
27+
liveins:
28+
- { reg: '$rdi', virtual-reg: '' }
29+
- { reg: '$esi', virtual-reg: '' }
30+
- { reg: '$rdx', virtual-reg: '' }
31+
frameInfo:
32+
isCalleeSavedInfoValid: true
33+
stack:
34+
- { id: 0, name: '', type: spill-slot, offset: -8, size: 8, alignment: 8,
35+
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
36+
debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
37+
- { id: 1, name: '', type: spill-slot, offset: -16, size: 8, alignment: 8,
38+
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
39+
debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
40+
body: |
41+
bb.0:
42+
liveins: $esi, $rdi, $rdx, $r15, $r14, $r13, $r12, $r11, $rbx
43+
44+
; CHECK-LABEL: name: enabled
45+
; CHECK: liveins: $esi, $rdi, $rdx, $r15, $r14, $r13, $r12, $r11, $rbx
46+
; CHECK-NEXT: {{ $}}
47+
; CHECK-NEXT: $rbx = MOV64rr $rdx
48+
; CHECK-NEXT: $r14d = MOV32rr $esi
49+
; CHECK-NEXT: $r15 = MOV64rr $rdi
50+
; CHECK-NEXT: renamable $r12d = XOR32rr undef $r12d, undef $r12d, implicit-def dead $eflags, implicit-def $r12
51+
; CHECK-NEXT: renamable $eax = XOR32rr undef $eax, undef $eax, implicit-def dead $eflags, implicit-def $rax
52+
53+
;; The store to the stack slot is still present.
54+
; CHECK-NEXT: MOV64mr $rbp, 1, $noreg, -48, $noreg, killed renamable $rax :: (store (s64) into %stack.0)
55+
56+
; CHECK-NEXT: MOV64mr $rbp, 1, $noreg, -40, $noreg, killed renamable $r11 :: (store (s64) into %stack.1)
57+
; CHECK-NEXT: renamable $eax = XOR32rr undef $eax, undef $eax, implicit-def dead $eflags
58+
; CHECK-NEXT: $r13d = MOV32rr killed $eax
59+
; CHECK-NEXT: $rdi = MOV64rr $r15
60+
; CHECK-NEXT: CALL64r renamable $r12, csr_64, implicit $rsp, implicit $ssp, implicit $rdi, implicit-def $rsp, implicit-def $ssp
61+
; CHECK-NEXT: dead renamable $eax = MOV32rm renamable $rbx, 1, $noreg, 0, $noreg
62+
; CHECK-NEXT: renamable $eax = MOV32ri 1
63+
; CHECK-NEXT: TEST8ri renamable $r14b, 1, implicit-def $eflags
64+
65+
;; First FAKE_USE and its corresponding load are removed; second FAKE_USE of
66+
;; a restored value that is also used is preserved.
67+
; CHECK-NEXT: renamable $r11 = MOV64rm $rbp, 1, $noreg, -40, $noreg :: (load (s64) from %stack.1)
68+
; CHECK-NEXT: renamable $r12d = XOR32rr $r12d, $r11d, implicit-def dead $eflags
69+
; CHECK-NEXT: FAKE_USE killed renamable $r11d
70+
71+
; CHECK-NEXT: TEST32rr killed renamable $r13d, renamable $r13d, implicit-def $eflags
72+
; CHECK-NEXT: RET64
73+
74+
$rbx = MOV64rr $rdx
75+
$r14d = MOV32rr $esi
76+
$r15 = MOV64rr $rdi
77+
renamable $r12d = XOR32rr undef $r12d, undef $r12d, implicit-def dead $eflags, implicit-def $r12
78+
renamable $eax = XOR32rr undef $eax, undef $eax, implicit-def dead $eflags, implicit-def $rax
79+
MOV64mr $rbp, 1, $noreg, -48, $noreg, killed renamable $rax :: (store (s64) into %stack.0)
80+
MOV64mr $rbp, 1, $noreg, -40, $noreg, killed renamable $r11 :: (store (s64) into %stack.1)
81+
renamable $eax = XOR32rr undef $eax, undef $eax, implicit-def dead $eflags
82+
$r13d = MOV32rr killed $eax
83+
$rdi = MOV64rr $r15
84+
CALL64r renamable $r12, csr_64, implicit $rsp, implicit $ssp, implicit $rdi, implicit-def $rsp, implicit-def $ssp
85+
dead renamable $eax = MOV32rm renamable $rbx, 1, $noreg, 0, $noreg
86+
renamable $eax = MOV32ri 1
87+
TEST8ri renamable $r14b, 1, implicit-def $eflags
88+
renamable $rax = MOV64rm $rbp, 1, $noreg, -48, $noreg :: (load (s64) from %stack.0)
89+
FAKE_USE renamable $eax, implicit killed $rax
90+
renamable $r11 = MOV64rm $rbp, 1, $noreg, -40, $noreg :: (load (s64) from %stack.1)
91+
renamable $r12d = XOR32rr $r12d, $r11d, implicit-def dead $eflags
92+
FAKE_USE killed renamable $r11d
93+
TEST32rr killed renamable $r13d, renamable $r13d, implicit-def $eflags
94+
RET64
95+
96+
...
97+
---
98+
name: disabled
99+
alignment: 16
100+
tracksRegLiveness: true
101+
noPhis: true
102+
noVRegs: true
103+
hasFakeUses: true
104+
tracksDebugUserValues: true
105+
debugInstrRef: false
106+
liveins:
107+
- { reg: '$rdi', virtual-reg: '' }
108+
- { reg: '$esi', virtual-reg: '' }
109+
- { reg: '$rdx', virtual-reg: '' }
110+
frameInfo:
111+
isCalleeSavedInfoValid: true
112+
stack:
113+
- { id: 0, name: '', type: spill-slot, offset: -8, size: 8, alignment: 8,
114+
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
115+
debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
116+
- { id: 1, name: '', type: spill-slot, offset: -16, size: 8, alignment: 8,
117+
stack-id: default, callee-saved-register: '', callee-saved-restored: true,
118+
debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
119+
body: |
120+
bb.0:
121+
liveins: $esi, $rdi, $rdx, $r15, $r14, $r13, $r12, $r11, $rbx
122+
123+
; CHECK-LABEL: name: disabled
124+
; CHECK: liveins: $esi, $rdi, $rdx, $r15, $r14, $r13, $r12, $r11, $rbx
125+
; CHECK-NEXT: {{ $}}
126+
; CHECK-NEXT: $rbx = MOV64rr $rdx
127+
; CHECK-NEXT: $r14d = MOV32rr $esi
128+
; CHECK-NEXT: $r15 = MOV64rr $rdi
129+
; CHECK-NEXT: renamable $r12d = XOR32rr undef $r12d, undef $r12d, implicit-def dead $eflags, implicit-def $r12
130+
; CHECK-NEXT: renamable $eax = XOR32rr undef $eax, undef $eax, implicit-def dead $eflags, implicit-def $rax
131+
; CHECK-NEXT: MOV64mr $rbp, 1, $noreg, -48, $noreg, killed renamable $rax :: (store (s64) into %stack.0)
132+
; CHECK-NEXT: MOV64mr $rbp, 1, $noreg, -40, $noreg, killed renamable $r11 :: (store (s64) into %stack.1)
133+
; CHECK-NEXT: renamable $eax = XOR32rr undef $eax, undef $eax, implicit-def dead $eflags
134+
; CHECK-NEXT: $r13d = MOV32rr killed $eax
135+
; CHECK-NEXT: $rdi = MOV64rr $r15
136+
; CHECK-NEXT: CALL64r renamable $r12, csr_64, implicit $rsp, implicit $ssp, implicit $rdi, implicit-def $rsp, implicit-def $ssp
137+
; CHECK-NEXT: dead renamable $eax = MOV32rm renamable $rbx, 1, $noreg, 0, $noreg
138+
; CHECK-NEXT: renamable $eax = MOV32ri 1
139+
; CHECK-NEXT: TEST8ri renamable $r14b, 1, implicit-def $eflags
140+
141+
;; Verify that when instr-ref is disabled, we do not remove fake uses.
142+
; CHECK-NEXT: renamable $rax = MOV64rm $rbp, 1, $noreg, -48, $noreg :: (load (s64) from %stack.0)
143+
; CHECK-NEXT: FAKE_USE renamable $eax, implicit killed $rax
144+
; CHECK-NEXT: renamable $r11 = MOV64rm $rbp, 1, $noreg, -40, $noreg :: (load (s64) from %stack.1)
145+
; CHECK-NEXT: renamable $r12d = XOR32rr $r12d, $r11d, implicit-def dead $eflags
146+
; CHECK-NEXT: FAKE_USE killed renamable $r11d
147+
; CHECK-NEXT: TEST32rr killed renamable $r13d, renamable $r13d, implicit-def $eflags
148+
; CHECK-NEXT: RET64
149+
$rbx = MOV64rr $rdx
150+
$r14d = MOV32rr $esi
151+
$r15 = MOV64rr $rdi
152+
renamable $r12d = XOR32rr undef $r12d, undef $r12d, implicit-def dead $eflags, implicit-def $r12
153+
renamable $eax = XOR32rr undef $eax, undef $eax, implicit-def dead $eflags, implicit-def $rax
154+
MOV64mr $rbp, 1, $noreg, -48, $noreg, killed renamable $rax :: (store (s64) into %stack.0)
155+
MOV64mr $rbp, 1, $noreg, -40, $noreg, killed renamable $r11 :: (store (s64) into %stack.1)
156+
renamable $eax = XOR32rr undef $eax, undef $eax, implicit-def dead $eflags
157+
$r13d = MOV32rr killed $eax
158+
$rdi = MOV64rr $r15
159+
CALL64r renamable $r12, csr_64, implicit $rsp, implicit $ssp, implicit $rdi, implicit-def $rsp, implicit-def $ssp
160+
dead renamable $eax = MOV32rm renamable $rbx, 1, $noreg, 0, $noreg
161+
renamable $eax = MOV32ri 1
162+
TEST8ri renamable $r14b, 1, implicit-def $eflags
163+
renamable $rax = MOV64rm $rbp, 1, $noreg, -48, $noreg :: (load (s64) from %stack.0)
164+
FAKE_USE renamable $eax, implicit killed $rax
165+
renamable $r11 = MOV64rm $rbp, 1, $noreg, -40, $noreg :: (load (s64) from %stack.1)
166+
renamable $r12d = XOR32rr $r12d, $r11d, implicit-def dead $eflags
167+
FAKE_USE killed renamable $r11d
168+
TEST32rr killed renamable $r13d, renamable $r13d, implicit-def $eflags
169+
RET64
170+
171+
...

0 commit comments

Comments
 (0)