Skip to content

Commit 01107f1

Browse files
authored
Merge pull request #81469 from eeckstein/temp-rvalue-elimination
TempRValueElimination: handle `store` instructions to the temporary stack location
2 parents 9ace644 + 5d24a26 commit 01107f1

File tree

14 files changed

+274
-62
lines changed

14 files changed

+274
-62
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,51 @@ let tempRValueElimination = FunctionPass(name: "temp-rvalue-elimination") {
3838
(function: Function, context: FunctionPassContext) in
3939

4040
for inst in function.instructions {
41-
if let copyAddr = inst as? CopyAddrInst {
42-
tryEliminate(copy: copyAddr, context)
41+
switch inst {
42+
case let copy as CopyAddrInst:
43+
if copy.source == copy.destination {
44+
// Remove identity copies which may have been created by an earlier iteration, where another `copy_addr`
45+
// copied the `alloc_stack` back to the source location.
46+
context.erase(instruction: copy)
47+
} else {
48+
tryEliminate(copy: copy, context)
49+
}
50+
case let store as StoreInst:
51+
// Also handle `load`-`store` pairs which are basically the same thing as a `copy_addr`.
52+
if let load = store.source as? LoadInst, load.uses.isSingleUse, load.parentBlock == store.parentBlock {
53+
tryEliminate(copy: store, context)
54+
}
55+
default:
56+
break
4357
}
4458
}
4559
}
4660

47-
private func tryEliminate(copy: CopyAddrInst, _ context: FunctionPassContext) {
48-
// Remove identity copies which may have been created by an earlier iteration, where another `copy_addr`
49-
// copied the `alloc_stack` back to the source location.
50-
if copy.source == copy.destination {
51-
context.erase(instruction: copy)
52-
}
61+
private protocol CopyLikeInstruction: Instruction {
62+
var sourceAddress: Value { get }
63+
var destinationAddress: Value { get }
64+
var isTakeOfSource: Bool { get }
65+
var isInitializationOfDestination: Bool { get }
66+
var loadingInstruction: Instruction { get }
67+
}
68+
69+
extension CopyAddrInst: CopyLikeInstruction {
70+
var sourceAddress: Value { source }
71+
var destinationAddress: Value { destination }
72+
var loadingInstruction: Instruction { self }
73+
}
74+
75+
// A `store` which has a `load` as source operand. This is basically the same as a `copy_addr`.
76+
extension StoreInst: CopyLikeInstruction {
77+
var sourceAddress: Value { load.address }
78+
var destinationAddress: Value { destination }
79+
var isTakeOfSource: Bool { load.loadOwnership == .take }
80+
var isInitializationOfDestination: Bool { storeOwnership != .assign }
81+
var loadingInstruction: Instruction { load }
82+
private var load: LoadInst { source as! LoadInst }
83+
}
84+
85+
private func tryEliminate(copy: CopyLikeInstruction, _ context: FunctionPassContext) {
5386

5487
guard let (allocStack, lastUseOfAllocStack) = getRemovableAllocStackDestination(of: copy, context) else {
5588
return
@@ -61,7 +94,7 @@ private func tryEliminate(copy: CopyAddrInst, _ context: FunctionPassContext) {
6194

6295
if needToInsertDestroy(copy: copy, lastUseOfAllocStack: lastUseOfAllocStack) {
6396
Builder.insert(after: lastUseOfAllocStack, context) { builder in
64-
builder.createDestroyAddr(address: copy.source)
97+
builder.createDestroyAddr(address: copy.sourceAddress)
6598
}
6699
}
67100

@@ -76,25 +109,25 @@ private func tryEliminate(copy: CopyAddrInst, _ context: FunctionPassContext) {
76109
assert(cai == lastUseOfAllocStack && cai.source == allocStack)
77110
cai.set(isTakeOfSource: false, context)
78111
}
79-
use.set(to: copy.source, context)
112+
use.set(to: copy.sourceAddress, context)
80113
case let load as LoadInst:
81114
if load.loadOwnership == .take, !copy.isTakeOfSource {
82115
// If the original copy is not taking its source, a `load` from the `allocStack` must
83116
// not take its source either.
84117
assert(load == lastUseOfAllocStack)
85118
load.set(ownership: .copy, context)
86119
}
87-
use.set(to: copy.source, context);
120+
use.set(to: copy.sourceAddress, context);
88121

89122
default:
90123
// Note that no operations that may be handled by this default clause can destroy the `allocStack`.
91124
// This includes operations that load the value from memory and release it or cast the address
92125
// before destroying it.
93-
use.set(to: copy.source, context);
126+
use.set(to: copy.sourceAddress, context);
94127
}
95128
}
96129
context.erase(instruction: allocStack)
97-
context.erase(instruction: copy)
130+
context.erase(instructionIncludingAllUsers: copy.loadingInstruction)
98131
}
99132

100133
/// Checks if the `copy` is copying into an `alloc_stack` which is removable:
@@ -105,17 +138,17 @@ private func tryEliminate(copy: CopyAddrInst, _ context: FunctionPassContext) {
105138
/// %lastUseOfAllocStack = load %allocStack
106139
/// ```
107140
private func getRemovableAllocStackDestination(
108-
of copy: CopyAddrInst, _ context: FunctionPassContext
141+
of copy: CopyLikeInstruction, _ context: FunctionPassContext
109142
) -> (allocStack: AllocStackInst, lastUseOfAllocStack: Instruction)? {
110143
guard copy.isInitializationOfDestination,
111-
let allocStack = copy.destination as? AllocStackInst
144+
let allocStack = copy.destinationAddress as? AllocStackInst
112145
else {
113146
return nil
114147
}
115148

116149
// If the `allocStack` is lexical we can eliminate it if the source of the copy is lexical and
117150
// it is live for longer than the `allocStack`.
118-
if allocStack.isLexical && !copy.source.accessBase.storageIsLexical {
151+
if allocStack.isLexical && !copy.sourceAddress.accessBase.storageIsLexical {
119152
return nil
120153
}
121154

@@ -149,7 +182,7 @@ private func getRemovableAllocStackDestination(
149182
if copy.isTakeOfSource,
150183
lastUseOfAllocStack != copy,
151184
!(lastUseOfAllocStack is DestroyAddrInst),
152-
lastUseOfAllocStack.mayWrite(toAddress: copy.source, context.aliasAnalysis)
185+
lastUseOfAllocStack.mayWrite(toAddress: copy.sourceAddress, context.aliasAnalysis)
153186
{
154187
return nil
155188
}
@@ -168,7 +201,7 @@ private func getRemovableAllocStackDestination(
168201

169202
/// We need to insert a final destroy if the original `copy` is taking the source but the
170203
/// `lastUseOfAllocStack` is not taking the `alloc_stack`.
171-
private func needToInsertDestroy(copy: CopyAddrInst, lastUseOfAllocStack: Instruction) -> Bool {
204+
private func needToInsertDestroy(copy: CopyLikeInstruction, lastUseOfAllocStack: Instruction) -> Bool {
172205
if !copy.isTakeOfSource {
173206
return false
174207
}
@@ -177,13 +210,13 @@ private func needToInsertDestroy(copy: CopyAddrInst, lastUseOfAllocStack: Instru
177210
return true
178211
case let cai as CopyAddrInst:
179212
if cai.isTakeOfSource {
180-
assert(cai.source == copy.destination, "copy_addr must be not take a projected address")
213+
assert(cai.source == copy.destinationAddress, "copy_addr must be not take a projected address")
181214
return false
182215
}
183216
return true
184217
case let li as LoadInst:
185218
if li.loadOwnership == .take {
186-
assert(li.address == copy.destination, "load must be not take a projected address")
219+
assert(li.address == copy.destinationAddress, "load must be not take a projected address")
187220
return false
188221
}
189222
return true
@@ -247,7 +280,9 @@ private extension AllocStackInst {
247280
/// ```
248281
/// We must not replace %temp with %a after the `end_access`. Instead we try to move the `end_access`
249282
/// after the last use.
250-
private func extendAccessScopes(beyond lastUse: Instruction, copy: CopyAddrInst, _ context: FunctionPassContext) -> Bool {
283+
private func extendAccessScopes(beyond lastUse: Instruction, copy: CopyLikeInstruction,
284+
_ context: FunctionPassContext) -> Bool
285+
{
251286
var endAccessToMove: EndAccessInst? = nil
252287

253288
for inst in InstructionList(first: copy.next) {
@@ -257,7 +292,7 @@ private func extendAccessScopes(beyond lastUse: Instruction, copy: CopyAddrInst,
257292
if endAccessToMove != nil {
258293
return false
259294
}
260-
if context.aliasAnalysis.mayAlias(copy.source, endAccess.beginAccess.address),
295+
if context.aliasAnalysis.mayAlias(copy.sourceAddress, endAccess.beginAccess.address),
261296
// There cannot be any aliasing modifying accesses within the liverange of the `alloc_stack`,
262297
// because we would have cought this in `getLastUseWhileSourceIsNotModified`.
263298
// But there are cases where `aliasAnalysis.mayAlias` is less precise than `Instruction.mayWrite`.
@@ -301,7 +336,7 @@ private func extendAccessScopes(beyond lastUse: Instruction, copy: CopyAddrInst,
301336
///
302337
/// Unfortunately, we cannot simply use the destroy points as the lifetime end, because they can be in a
303338
/// different basic block. Instead we look for the last non-destroy, non-dealloc use.
304-
private func getLastUseWhileSourceIsNotModified(of copy: CopyAddrInst,
339+
private func getLastUseWhileSourceIsNotModified(of copy: CopyLikeInstruction,
305340
uses: InstructionSetWithCount,
306341
_ context: FunctionPassContext) -> Instruction?
307342
{
@@ -313,7 +348,7 @@ private func getLastUseWhileSourceIsNotModified(of copy: CopyAddrInst,
313348

314349
// We already checked that the useful lifetime of the `alloc_stack` ends in the same block as the `copy`.
315350
// Therefore we can limit our search to the instructions of this block.
316-
for inst in InstructionList(first: copy.next) {
351+
for inst in InstructionList(first: copy.loadingInstruction.next) {
317352
if uses.contains(inst) {
318353
numUsesFound += 1
319354
}
@@ -327,7 +362,7 @@ private func getLastUseWhileSourceIsNotModified(of copy: CopyAddrInst,
327362
// could occur _before_ the read of the `alloc_stack`.
328363
switch inst {
329364
case is FullApplySite, is YieldInst:
330-
if inst.mayWrite(toAddress: copy.source, aliasAnalysis) {
365+
if inst.mayWrite(toAddress: copy.sourceAddress, aliasAnalysis) {
331366
return nil
332367
}
333368
return inst
@@ -336,7 +371,7 @@ private func getLastUseWhileSourceIsNotModified(of copy: CopyAddrInst,
336371
}
337372
}
338373

339-
if inst.mayWrite(toAddress: copy.source, aliasAnalysis) {
374+
if inst.mayWrite(toAddress: copy.sourceAddress, aliasAnalysis) {
340375
return nil
341376
}
342377
}
@@ -348,9 +383,9 @@ private func getLastUseWhileSourceIsNotModified(of copy: CopyAddrInst,
348383
/// Collects all uses of the `alloc_stack`.
349384
private struct UseCollector : AddressDefUseWalker {
350385
private(set) var uses: InstructionSetWithCount
351-
private let copy: CopyAddrInst
386+
private let copy: CopyLikeInstruction
352387

353-
init(copy: CopyAddrInst, _ context: FunctionPassContext) {
388+
init(copy: CopyLikeInstruction, _ context: FunctionPassContext) {
354389
self.uses = InstructionSetWithCount(context)
355390
self.copy = copy
356391
}
@@ -467,7 +502,7 @@ private struct UseCollector : AddressDefUseWalker {
467502
if load.loadOwnership == .take,
468503
// Only accept `load [take]` if it takes the whole `alloc_stack`. A `load [take]` from
469504
// a projection would destroy only a part of the `alloc_stack` and we don't handle this.
470-
load.address != copy.destination
505+
load.address != copy.destinationAddress
471506
{
472507
return .abortWalk
473508
}
@@ -494,7 +529,7 @@ private struct UseCollector : AddressDefUseWalker {
494529
return .abortWalk
495530
}
496531
// As with `load [take]`, only accept `copy_addr [take]` if it takes the whole `alloc_stack`.
497-
if copyFromStack.isTakeOfSource && copyFromStack.source != copy.destination {
532+
if copyFromStack.isTakeOfSource && copyFromStack.source != copy.destinationAddress {
498533
return .abortWalk
499534
}
500535
uses.insert(copyFromStack)

SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,10 @@ extension MutatingContext {
140140
return _bridged.createBlockAfter(block.bridged).block
141141
}
142142

143-
func erase(instruction: Instruction) {
143+
/// Removes and deletes `instruction`.
144+
/// If `salvageDebugInfo` is true, compensating `debug_value` instructions are inserted for certain
145+
/// kind of instructions.
146+
func erase(instruction: Instruction, salvageDebugInfo: Bool = true) {
144147
if !instruction.isInStaticInitializer {
145148
verifyIsTransforming(function: instruction.parentFunction)
146149
}
@@ -152,7 +155,7 @@ extension MutatingContext {
152155
}
153156
notifyInstructionsChanged()
154157

155-
_bridged.eraseInstruction(instruction.bridged)
158+
_bridged.eraseInstruction(instruction.bridged, salvageDebugInfo)
156159
}
157160

158161
func erase(instructionIncludingAllUsers inst: Instruction) {
@@ -164,7 +167,9 @@ extension MutatingContext {
164167
erase(instructionIncludingAllUsers: use.instruction)
165168
}
166169
}
167-
erase(instruction: inst)
170+
// We rely that after deleting the instruction its operands have no users.
171+
// Therefore `salvageDebugInfo` must be turned off because we cannot insert debug_value instructions.
172+
erase(instruction: inst, salvageDebugInfo: false)
168173
}
169174

170175
func erase<S: Sequence>(instructions: S) where S.Element: Instruction {

include/swift/SIL/SILInstructionWorklist.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,12 @@ class SILInstructionWorklist : SILInstructionWorklistBase {
302302
// method to delete the given instruction.
303303
void eraseInstFromFunction(SILInstruction &instruction,
304304
SILBasicBlock::iterator &iterator,
305-
bool addOperandsToWorklist = true) {
306-
// Try to salvage debug info first.
307-
swift::salvageDebugInfo(&instruction);
305+
bool addOperandsToWorklist = true,
306+
bool salvageDebugInfo = true) {
307+
if (salvageDebugInfo) {
308+
// Try to salvage debug info first.
309+
swift::salvageDebugInfo(&instruction);
310+
}
308311
// Then delete old debug users.
309312
for (auto result : instruction.getResults()) {
310313
while (!result->use_empty()) {
@@ -323,9 +326,11 @@ class SILInstructionWorklist : SILInstructionWorklistBase {
323326
}
324327

325328
void eraseInstFromFunction(SILInstruction &instruction,
326-
bool addOperandsToWorklist = true) {
329+
bool addOperandsToWorklist = true,
330+
bool salvageDebugInfo = true) {
327331
SILBasicBlock::iterator nullIter;
328-
return eraseInstFromFunction(instruction, nullIter, addOperandsToWorklist);
332+
return eraseInstFromFunction(instruction, nullIter, addOperandsToWorklist,
333+
salvageDebugInfo);
329334
}
330335

331336
void eraseSingleInstFromFunction(SILInstruction &instruction,

include/swift/SILOptimizer/OptimizerBridging.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ struct BridgedPassContext {
234234
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedBasicBlock splitBlockAfter(BridgedInstruction bridgedInst) const;
235235
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedBasicBlock createBlockAfter(BridgedBasicBlock bridgedBlock) const;
236236
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedBasicBlock appendBlock(BridgedFunction bridgedFunction) const;
237-
BRIDGED_INLINE void eraseInstruction(BridgedInstruction inst) const;
237+
BRIDGED_INLINE void eraseInstruction(BridgedInstruction inst, bool salvageDebugInfo) const;
238238
BRIDGED_INLINE void eraseBlock(BridgedBasicBlock block) const;
239239
static BRIDGED_INLINE void moveInstructionBefore(BridgedInstruction inst, BridgedInstruction beforeInst);
240240
bool tryOptimizeApplyOfPartialApply(BridgedInstruction closure) const;

include/swift/SILOptimizer/OptimizerBridgingImpl.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "swift/SILOptimizer/OptimizerBridging.h"
2828
#include "swift/SILOptimizer/PassManager/PassManager.h"
2929
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
30+
#include "swift/SILOptimizer/Utils/DebugOptUtils.h"
3031

3132
SWIFT_BEGIN_NULLABILITY_ANNOTATIONS
3233

@@ -253,8 +254,8 @@ BridgedBasicBlock BridgedPassContext::appendBlock(BridgedFunction bridgedFunctio
253254
return {bridgedFunction.getFunction()->createBasicBlock()};
254255
}
255256

256-
void BridgedPassContext::eraseInstruction(BridgedInstruction inst) const {
257-
invocation->eraseInstruction(inst.unbridged());
257+
void BridgedPassContext::eraseInstruction(BridgedInstruction inst, bool salvageDebugInfo) const {
258+
invocation->eraseInstruction(inst.unbridged(), salvageDebugInfo);
258259
}
259260

260261
void BridgedPassContext::eraseBlock(BridgedBasicBlock block) const {

include/swift/SILOptimizer/PassManager/PassManager.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ class SwiftPassInvocation {
138138
void freeOperandSet(OperandSet *set);
139139

140140
/// The top-level API to erase an instruction, called from the Swift pass.
141-
void eraseInstruction(SILInstruction *inst);
141+
void eraseInstruction(SILInstruction *inst, bool salvageDebugInfo);
142142

143143
/// Called by the pass when changes are made to the SIL.
144144
void notifyChanges(SILAnalysis::InvalidationKind invalidationKind);

lib/SILOptimizer/SILCombiner/SILCombine.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -694,11 +694,13 @@ SILTransform *swift::createSILCombine() {
694694
// SwiftFunctionPassContext
695695
//===----------------------------------------------------------------------===//
696696

697-
void SwiftPassInvocation::eraseInstruction(SILInstruction *inst) {
697+
void SwiftPassInvocation::eraseInstruction(SILInstruction *inst, bool salvageDebugInfo) {
698698
if (silCombiner) {
699-
silCombiner->eraseInstFromFunction(*inst);
699+
silCombiner->eraseInstFromFunction(*inst, /*addOperandsToWorklist=*/ true, salvageDebugInfo);
700700
} else {
701-
swift::salvageDebugInfo(inst);
701+
if (salvageDebugInfo) {
702+
swift::salvageDebugInfo(inst);
703+
}
702704
if (inst->isStaticInitializerInst()) {
703705
inst->getParent()->erase(inst, *getPassManager()->getModule());
704706
} else {

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,10 @@ class SILCombiner :
220220
// by this method.
221221
SILInstruction *eraseInstFromFunction(SILInstruction &I,
222222
SILBasicBlock::iterator &InstIter,
223-
bool AddOperandsToWorklist = true) {
224-
Worklist.eraseInstFromFunction(I, InstIter, AddOperandsToWorklist);
223+
bool AddOperandsToWorklist = true,
224+
bool salvageDebugInfo = true) {
225+
Worklist.eraseInstFromFunction(I, InstIter, AddOperandsToWorklist,
226+
salvageDebugInfo);
225227
MadeChange = true;
226228
// Dummy return, so the caller doesn't need to explicitly return nullptr.
227229
return nullptr;
@@ -232,9 +234,10 @@ class SILCombiner :
232234
void eraseInstIncludingUsers(SILInstruction *inst);
233235

234236
SILInstruction *eraseInstFromFunction(SILInstruction &I,
235-
bool AddOperandsToWorklist = true) {
237+
bool AddOperandsToWorklist = true,
238+
bool salvageDebugInfo = true) {
236239
SILBasicBlock::iterator nullIter;
237-
return eraseInstFromFunction(I, nullIter, AddOperandsToWorklist);
240+
return eraseInstFromFunction(I, nullIter, AddOperandsToWorklist, salvageDebugInfo);
238241
}
239242

240243
void addInitialGroup(ArrayRef<SILInstruction *> List) {

test/SILOptimizer/globalopt_resilience.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,9 @@ public func cannotConvertToValueUse() {
3737
}
3838

3939
// CHECK-LABEL: sil @$s4test23cannotConvertToValueUseyyF : $@convention(thin) () -> ()
40-
// CHECK: [[INT:%.*]] = integer_literal $Builtin.Int{{32|64}}, 27
41-
// CHECK: [[S1:%.*]] = struct $Int ([[INT]] : $Builtin.Int{{32|64}})
42-
// CHECK: [[S2:%.*]] = struct $ResilientStruct ([[S1]] : $Int)
43-
// CHECK: [[TMP:%.*]] = alloc_stack $ResilientStruct
44-
// CHECK: store [[S2]] to [[TMP]] : $*ResilientStruct
40+
// CHECK: [[GA:%.*]] = global_addr @$s4test15ResilientStructV9staticValACvpZ
4541
// CHECK: [[METHOD:%.*]] = function_ref @$s4test15ResilientStructV6methodyyF : $@convention(method) (@in_guaranteed ResilientStruct) -> ()
46-
// CHECK: apply [[METHOD]]([[TMP]]) : $@convention(method) (@in_guaranteed ResilientStruct) -> ()
42+
// CHECK: apply [[METHOD]]([[GA]]) : $@convention(method) (@in_guaranteed ResilientStruct) -> ()
4743
// CHECK: [[RESULT:%.*]] = tuple ()
4844
// CHECK: return [[RESULT]] : $()
4945

0 commit comments

Comments
 (0)