Skip to content

Commit e44e934

Browse files
eecksteinCatfish-Man
authored andcommitted
TempRValueElimination: handle store instructions to the temporary stack location.
Treat `load`-`store` pairs as if they were `copy_addr` instructions. This can catch more cases. For example it can eliminate unnecessary copies of InlineArray when subscripting it. rdar://149250346
1 parent c0482ce commit e44e934

File tree

7 files changed

+239
-43
lines changed

7 files changed

+239
-43
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)

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

test/SILOptimizer/inline_arrays.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %target-swift-frontend -primary-file %s -O -disable-availability-checking -module-name=test -emit-sil | %FileCheck %s
2+
3+
// REQUIRES: swift_stdlib_no_asserts, optimized_stdlib
4+
5+
// CHECK-LABEL: sil @$s4test0A9Subscriptys5UInt8Vs11InlineArrayVy$255_ADGz_SitF :
6+
// CHECK: [[S:%.*]] = struct_element_addr %0, #InlineArray._storage
7+
// CHECK: [[BA:%.*]] = vector_base_addr [[S]]
8+
// CHECK: [[EA:%.*]] = index_addr [stack_protection] [[BA]],
9+
// CHECK: [[E:%.*]] = load [[EA]]
10+
// CHECK: return [[E]]
11+
// CHECK: } // end sil function '$s4test0A9Subscriptys5UInt8Vs11InlineArrayVy$255_ADGz_SitF'
12+
public func testSubscript(_ a: inout InlineArray<256, UInt8>, _ i: Int) -> UInt8 {
13+
return a[i]
14+
}
15+
16+
public final class C {
17+
let a: InlineArray<256, UInt8>
18+
19+
init(_ a: InlineArray<256, UInt8>) {
20+
self.a = a
21+
}
22+
23+
// CHECK-LABEL: sil @$s4test1CC0A9Subscriptys5UInt8VSiF :
24+
// CHECK: [[CA:%.*]] = ref_element_addr [immutable] %1, #C.a
25+
// CHECK: [[S:%.*]] = struct_element_addr [[CA]], #InlineArray._storage
26+
// CHECK: [[BA:%.*]] = vector_base_addr [[S]]
27+
// CHECK: [[EA:%.*]] = index_addr [stack_protection] [[BA]],
28+
// CHECK: [[E:%.*]] = load [[EA]]
29+
// CHECK: return [[E]]
30+
// CHECK: } // end sil function '$s4test1CC0A9Subscriptys5UInt8VSiF'
31+
public func testSubscript(_ i: Int) -> UInt8 {
32+
return a[i]
33+
}
34+
}
35+

test/SILOptimizer/pre_specialize_layouts.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,6 @@ public func usePrespecializedEntryPoints(wrapperStruct: ReferenceWrapperStruct,
189189
// OPT: [[A1:%.*]] = unchecked_ref_cast [[P1]] : $SomeClass to $AnyObject
190190
// OPT: [[R3:%.*]] = apply [[F1]]([[A1]]) : $@convention(thin) (@guaranteed AnyObject) -> @owned AnyObject
191191
// OPT: store [[R3]] to [[R2]] : $*AnyObject
192-
// OPT: [[A2:%.*]] = load [[R1]] : $*SomeClass
193192
// OPT: [[F2:%.*]] = function_ref @$s22pre_specialize_layouts7consumeyyxlF0a20_specialized_module_C09SomeClassC_Ttg5 : $@convention(thin) () -> ()
194193
// OPT: apply [[F2]]() : $@convention(thin) () -> ()
195194
// OPT: dealloc_stack [[R1]] : $*SomeClass

test/SILOptimizer/specialize_opaque_type_archetypes.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -421,12 +421,12 @@ struct PAEM : P4EM {
421421
// CHECK: apply [[F]]([[V]])
422422

423423
// CHECK-64-LABEL: sil hidden @$s1A2PAV4testyyF : $@convention(method) (PA) -> ()
424-
// CHECK-64: [[V:%.*]] = integer_literal $Builtin.Int64, 5
425-
// CHECK-64: [[I:%.*]] = struct $Int64 ([[V]] : $Builtin.Int64)
426-
// CHECK-64: [[F:%.*]] = function_ref @$s1A4usePyyxAA1PRzlFs5Int64V_Tg5
427-
// CHECK-64: apply [[F]]([[I]]) : $@convention(thin) (Int64) -> ()
428-
// CHECK-64: apply [[F]]([[I]]) : $@convention(thin) (Int64) -> ()
429-
424+
// CHECK-64: [[V:%.*]] = integer_literal $Builtin.Int64, 5
425+
// CHECK-64-DAG: [[I:%.*]] = struct $Int64 ([[V]] : $Builtin.Int64)
426+
// CHECK-64-DAG: [[F:%.*]] = function_ref @$s1A4usePyyxAA1PRzlFs5Int64V_Tg5
427+
// CHECK-64: apply [[F]]([[I]]) : $@convention(thin) (Int64) -> ()
428+
// CHECK-64: apply [[F]]([[I]]) : $@convention(thin) (Int64) -> ()
429+
// CHECK-64: } // end sil function '$s1A2PAV4testyyF'
430430
@inline(never)
431431
func testIt<T>(cl: (Int64) throws -> T) {
432432
do {

test/SILOptimizer/temp_rvalue_opt.sil

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,3 +827,18 @@ bb0:
827827
return %12
828828
}
829829

830+
// CHECK-LABEL: sil @store_to_temp :
831+
// CHECK: %1 = load %0
832+
// CHECK-NEXT: return %1
833+
// CHECK-LABEL: } // end sil function 'store_to_temp'
834+
sil @store_to_temp : $@convention(thin) (@in_guaranteed Klass) -> @owned Klass {
835+
bb0(%0 : $*Klass):
836+
%1 = load %0
837+
%2 = alloc_stack $Klass
838+
store %1 to %2
839+
%4 = load %2
840+
destroy_addr %2
841+
dealloc_stack %2
842+
return %4
843+
}
844+

0 commit comments

Comments
 (0)