Skip to content

Commit b67187c

Browse files
authored
Merge pull request #63614 from eeckstein/fix-escape-utils
EscapeUtils: fix some bugs related to `followLoads`
2 parents 7e0caae + 7597641 commit b67187c

File tree

6 files changed

+147
-59
lines changed

6 files changed

+147
-59
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ComputeEscapeEffects.swift

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,9 @@ func addArgEffects(_ arg: FunctionArgument, argPath ap: SmallProjectionPath,
158158
effect = EscapeEffects.ArgumentEffect(.escapingToReturn(toPath, exclusive),
159159
argumentIndex: arg.index, pathPattern: argPath)
160160
case .toArgument(let toArgIdx, let toPath):
161-
let exclusive = isExclusiveEscapeToArgument(fromArgument: arg, fromPath: argPath,
162-
toArgumentIndex: toArgIdx, toPath: toPath, context)
163-
effect = EscapeEffects.ArgumentEffect(.escapingToArgument(toArgIdx, toPath, exclusive),
161+
// Exclusive argument -> argument effects cannot appear because such an effect would
162+
// involve a store which is not permitted for exclusive escapes.
163+
effect = EscapeEffects.ArgumentEffect(.escapingToArgument(toArgIdx, toPath, /*exclusive*/ false),
164164
argumentIndex: arg.index, pathPattern: argPath)
165165
}
166166
newEffects.append(effect)
@@ -197,25 +197,39 @@ private func isOperandOfRecursiveCall(_ op: Operand) -> Bool {
197197
return false
198198
}
199199

200-
/// Returns true if when walking from the `toSelection` to the `fromArgument`,
201-
/// there are no other arguments or escape points than `fromArgument`. Also, the
202-
/// path at the `fromArgument` must match with `fromPath`.
200+
/// Returns true if when walking up from the `returnInst`, the `fromArgument` is the one
201+
/// and only argument which is reached - with a matching `fromPath`.
203202
private
204203
func isExclusiveEscapeToReturn(fromArgument: Argument, fromPath: SmallProjectionPath,
205204
toPath: SmallProjectionPath,
206205
returnInst: ReturnInst, _ context: FunctionPassContext) -> Bool {
207-
struct IsExclusiveReturnEscapeVisitor : EscapeVisitor {
206+
struct IsExclusiveReturnEscapeVisitor : EscapeVisitorWithResult {
208207
let fromArgument: Argument
209208
let fromPath: SmallProjectionPath
210209
let toPath: SmallProjectionPath
210+
var result = false
211211

212212
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
213-
if operand.instruction is ReturnInst {
213+
switch operand.instruction {
214+
case is ReturnInst:
214215
if path.followStores { return .abort }
215216
if path.projectionPath.matches(pattern: toPath) {
216217
return .ignore
217218
}
218219
return .abort
220+
case let si as StoringInstruction:
221+
// Don't allow store instructions because this allows the EscapeUtils to walk up
222+
// an apply result with `followStores`.
223+
if operand == si.destinationOperand {
224+
return .abort
225+
}
226+
case let ca as CopyAddrInst:
227+
// `copy_addr` is like a store.
228+
if operand == ca.destinationOperand {
229+
return .abort
230+
}
231+
default:
232+
break
219233
}
220234
return .continueWalk
221235
}
@@ -226,37 +240,12 @@ func isExclusiveEscapeToReturn(fromArgument: Argument, fromPath: SmallProjection
226240
}
227241
if path.followStores { return .abort }
228242
if arg == fromArgument && path.projectionPath.matches(pattern: fromPath) {
243+
result = true
229244
return .walkDown
230245
}
231246
return .abort
232247
}
233248
}
234249
let visitor = IsExclusiveReturnEscapeVisitor(fromArgument: fromArgument, fromPath: fromPath, toPath: toPath)
235-
return !returnInst.operand.at(toPath).isEscaping(using: visitor, context)
250+
return returnInst.operand.at(toPath).visit(using: visitor, context) ?? false
236251
}
237-
238-
private
239-
func isExclusiveEscapeToArgument(fromArgument: Argument, fromPath: SmallProjectionPath,
240-
toArgumentIndex: Int, toPath: SmallProjectionPath, _ context: FunctionPassContext) -> Bool {
241-
struct IsExclusiveArgumentEscapeVisitor : EscapeVisitor {
242-
let fromArgument: Argument
243-
let fromPath: SmallProjectionPath
244-
let toArgumentIndex: Int
245-
let toPath: SmallProjectionPath
246-
247-
mutating func visitDef(def: Value, path: EscapePath) -> DefResult {
248-
guard let arg = def as? FunctionArgument else {
249-
return .continueWalkUp
250-
}
251-
if path.followStores { return .abort }
252-
if arg == fromArgument && path.projectionPath.matches(pattern: fromPath) { return .walkDown }
253-
if arg.index == toArgumentIndex && path.projectionPath.matches(pattern: toPath) { return .walkDown }
254-
return .abort
255-
}
256-
}
257-
let visitor = IsExclusiveArgumentEscapeVisitor(fromArgument: fromArgument, fromPath: fromPath,
258-
toArgumentIndex: toArgumentIndex, toPath: toPath)
259-
let toArg = fromArgument.parentFunction.arguments[toArgumentIndex]
260-
return !toArg.at(toPath).isEscapingWhenWalkingDown(using: visitor, context)
261-
}
262-

SwiftCompilerSources/Sources/Optimizer/Utilities/EscapeUtils.swift

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,9 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
547547
}
548548

549549
// Indirect arguments cannot escape the function, but loaded values from such can.
550-
if !followLoads(at: path.projectionPath) {
550+
if !followLoads(at: path.projectionPath) &&
551+
// Except for begin_apply: it can yield an address value.
552+
!apply.isBeginApplyWithIndirectResults {
551553
return .continueWalk
552554
}
553555

@@ -592,7 +594,8 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
592594
var matched = false
593595
for effect in effects.escapeEffects.arguments {
594596
switch effect.kind {
595-
case .escapingToArgument(let toArgIdx, let toPath, let exclusive):
597+
case .escapingToArgument(let toArgIdx, let toPath, _):
598+
// Note: exclusive argument -> argument effects cannot appear, so we don't need to handle them here.
596599
if effect.matches(calleeArgIdx, argPath.projectionPath) {
597600
guard let callerToIdx = apply.callerArgIndex(calleeArgIndex: toArgIdx) else {
598601
return isEscaping
@@ -602,20 +605,6 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
602605
let arg = apply.arguments[callerToIdx]
603606

604607
let p = Path(projectionPath: toPath, followStores: false, knownType: nil)
605-
if walkUp(addressOrValue: arg, path: p) == .abortWalk {
606-
return .abortWalk
607-
}
608-
matched = true
609-
} else if toArgIdx == calleeArgIdx && argPath.projectionPath.matches(pattern: toPath) {
610-
// Handle the reverse direction of an arg-to-arg escape.
611-
guard let callerArgIdx = apply.callerArgIndex(calleeArgIndex: effect.argumentIndex) else {
612-
return isEscaping
613-
}
614-
if !exclusive { return isEscaping }
615-
616-
let arg = apply.arguments[callerArgIdx]
617-
let p = Path(projectionPath: effect.pathPattern, followStores: false, knownType: nil)
618-
619608
if walkUp(addressOrValue: arg, path: p) == .abortWalk {
620609
return .abortWalk
621610
}
@@ -690,6 +679,14 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
690679
case let ap as ApplyInst:
691680
return walkUpApplyResult(apply: ap, path: path.with(knownType: nil))
692681
case is LoadInst, is LoadWeakInst, is LoadUnownedInst:
682+
if !followLoads(at: path.projectionPath) {
683+
// When walking up we shouldn't end up at a load where followLoads is false,
684+
// because going from a (non-followLoads) address to a load always involves a class indirection.
685+
// There is one exception: loading a raw pointer, e.g.
686+
// %l = load %a : $Builtin.RawPointer
687+
// %a = pointer_to_address %l // the up-walk starts at %a
688+
return isEscaping
689+
}
693690
return walkUp(address: (def as! UnaryInstruction).operand,
694691
path: path.with(followStores: true).with(knownType: nil))
695692
case let atp as AddressToPointerInst:
@@ -837,3 +834,13 @@ private extension SmallProjectionPath {
837834
EscapeUtilityTypes.EscapePath(projectionPath: self, followStores: false, knownType: nil)
838835
}
839836
}
837+
838+
private extension ApplySite {
839+
var isBeginApplyWithIndirectResults: Bool {
840+
guard let ba = self as? BeginApplyInst else {
841+
return false
842+
}
843+
// Note that the token result is always a non-address type.
844+
return ba.results.contains { $0.type.isAddress }
845+
}
846+
}

test/SILOptimizer/addr_escape_info.sil

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ sil @initX : $@convention(method) (@owned X) -> @owned X {
5151
[%0: escape => %r, escape c*.v** => %r.c*.v**]
5252
}
5353
sil @modifyStr : $@convention(method) (@inout Str) -> ()
54+
sil @guaranteed_yield_coroutine : $@yield_once @convention(thin) (@inout X) -> @yields @inout X
5455

5556
// CHECK-LABEL: Address escape information for test_simple:
5657
// CHECK: value: %1 = struct_element_addr %0 : $*Str, #Str.a
@@ -268,6 +269,28 @@ bb0:
268269
return %12 : $()
269270
}
270271

272+
// CHECK-LABEL: Address escape information for indirect_argument_escape_to_yield:
273+
// CHECK: value: %1 = alloc_stack $X
274+
// CHECK-NEXT: ==> (%5, %6) = begin_apply %4(%1) : $@yield_once @convention(thin) (@inout X) -> @yields @inout X
275+
// CHECK-NEXT: ==> %8 = apply %7(%5) : $@convention(thin) (@inout X) -> ()
276+
// CHECK: End function indirect_argument_escape_to_yield
277+
sil @indirect_argument_escape_to_yield : $@convention(thin) (@guaranteed X) -> () {
278+
bb0(%0 : $X):
279+
%1 = alloc_stack $X
280+
store %0 to %1 : $*X
281+
fix_lifetime %1 : $*X
282+
%3 = function_ref @guaranteed_yield_coroutine : $@yield_once @convention(thin) (@inout X) -> @yields @inout X
283+
(%4, %5) = begin_apply %3(%1) : $@yield_once @convention(thin) (@inout X) -> @yields @inout X
284+
285+
%6 = function_ref @inout_class_argument : $@convention(thin) (@inout X) -> ()
286+
%7 = apply %6(%4) : $@convention(thin) (@inout X) -> ()
287+
288+
end_apply %5
289+
dealloc_stack %1 : $*X
290+
%r = tuple ()
291+
return %r : $()
292+
}
293+
271294
// CHECK-LABEL: Address escape information for ignore_copy_addr_and_release:
272295
// CHECK: value: %1 = ref_element_addr %0 : $X, #X.s
273296
// CHECK-NEXT: - %6 = apply %5(%3) : $@convention(thin) (@in Str) -> ()
@@ -477,6 +500,28 @@ bb0:
477500
return %7 : $()
478501
}
479502

503+
// CHECK-LABEL: Address escape information for load_rawpointer:
504+
// CHECK: value: %5 = pointer_to_address %4 : $Builtin.RawPointer to [strict] $*Int
505+
// CHECK-NEXT: ==> %8 = apply %7(%5) : $@convention(thin) (@in Int) -> ()
506+
// CHECK: End function load_rawpointer
507+
sil @load_rawpointer : $@convention(thin) () -> () {
508+
bb0:
509+
%0 = alloc_stack $Int
510+
%1 = address_to_pointer %0 : $*Int to $Builtin.RawPointer
511+
%2 = alloc_stack $Builtin.RawPointer
512+
store %1 to %2 : $*Builtin.RawPointer
513+
%4 = load %2 : $*Builtin.RawPointer
514+
%5 = pointer_to_address %4 : $Builtin.RawPointer to [strict] $*Int
515+
fix_lifetime %5 : $*Int
516+
%7 = function_ref @indirect_argument : $@convention(thin) (@in Int) -> ()
517+
%8 = apply %7(%5) : $@convention(thin) (@in Int) -> ()
518+
dealloc_stack %2 : $*Builtin.RawPointer
519+
dealloc_stack %0 : $*Int
520+
%r = tuple ()
521+
return %r : $()
522+
}
523+
524+
480525
// CHECK-LABEL: Address escape information for test_addr_alias1:
481526
// CHECK: pair 0 - 1
482527
// CHECK-NEXT: %2 = ref_element_addr %0 : $XandIntClass, #XandIntClass.x

test/SILOptimizer/escape_effects.sil

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ sil [ossa] @unknown_function : $@convention(thin) (@owned X) -> ()
5757
sil [ossa] @unknown_function_y : $@convention(thin) (@owned Y) -> ()
5858

5959
// CHECK-LABEL: sil [ossa] @test_basic
60-
// CHECK-NEXT: [%1: escape v** => %3.v**]
60+
// CHECK-NEXT: [%1: escape v** -> %3.v**]
6161
// CHECK-NEXT: [%2: noescape, escape c*.v** -> %r.v**]
6262
// CHECK-NEXT: [%3: noescape]
6363
// CHECK-NEXT: {{^[^[]}}
@@ -201,7 +201,7 @@ sil [ossa] @self_arg_callee : $@convention(thin) (@inout Str) -> () {
201201
}
202202

203203
// CHECK-LABEL: sil [ossa] @test_self_arg_escape
204-
// CHECK-NEXT: [%0: escape v** => %0.v**]
204+
// CHECK-NEXT: [%0: escape v** -> %0.v**]
205205
// CHECK-NEXT: {{^[^[]}}
206206
sil [ossa] @test_self_arg_escape : $@convention(thin) (@inout Str) -> () {
207207
bb0(%0 : $*Str):
@@ -212,7 +212,7 @@ bb0(%0 : $*Str):
212212
}
213213

214214
// CHECK-LABEL: sil [ossa] @test_self_arg_escape_with_copy
215-
// CHECK-NEXT: [%0: escape v** => %0.s1.0.v**]
215+
// CHECK-NEXT: [%0: escape v** -> %0.s1.0.v**]
216216
// CHECK-NEXT: {{^[^[]}}
217217
sil [ossa] @test_self_arg_escape_with_copy : $@convention(thin) (@inout Str) -> () {
218218
bb0(%0 : $*Str):
@@ -225,7 +225,7 @@ bb0(%0 : $*Str):
225225
}
226226

227227
// CHECK-LABEL: sil [ossa] @test_self_arg_escape_with_bidirectional_copy
228-
// CHECK-NEXT: [%0: escape v** => %0.v**]
228+
// CHECK-NEXT: [%0: escape v** -> %0.v**]
229229
// CHECK-NEXT: {{^[^[]}}
230230
sil [ossa] @test_self_arg_escape_with_bidirectional_copy : $@convention(thin) (@inout Str) -> () {
231231
bb0(%0 : $*Str):
@@ -240,7 +240,7 @@ bb0(%0 : $*Str):
240240

241241
// CHECK-LABEL: sil [ossa] @test_content_to_arg_addr
242242
// CHECK-NEXT: [%0: noescape **]
243-
// CHECK-NEXT: [%1: noescape, escape c*.v** => %0.v**]
243+
// CHECK-NEXT: [%1: noescape, escape c*.v** -> %0.v**]
244244
// CHECK-NEXT: {{^[^[]}}
245245
sil [ossa] @test_content_to_arg_addr : $@convention(thin) (@guaranteed Y) -> @out Str {
246246
bb0(%0 : $*Str, %1 : @guaranteed $Y):
@@ -331,7 +331,7 @@ bb0(%0 : $*Z, %1 : $Y):
331331
}
332332

333333
// CHECK-LABEL: sil @store_to_content
334-
// CHECK-NEXT: [%0: escape => %r, escape c*.v** => %r.c*.v**]
334+
// CHECK-NEXT: [%0: escape => %r, escape c*.v** -> %r.c*.v**]
335335
// CHECK-NEXT: {{^[^[]}}
336336
sil @store_to_content : $@convention(thin) (@owned Z, @guaranteed Y) -> @owned Z {
337337
bb0(%0 : $Z, %1 : $Y):
@@ -372,3 +372,15 @@ bb0(%0 : $*Z, %1 : $Y):
372372
%5 = tuple ()
373373
return %5 : $()
374374
}
375+
376+
// CHECK-LABEL: sil @store_to_class_field
377+
// CHECK-NEXT: [%0: escape v** -> %r.c0.v**, escape v**.c*.v** -> %r.c0.v**.c*.v**]
378+
// CHECK-NEXT: {{^[^[]}}
379+
sil @store_to_class_field : $@convention(thin) (@in_guaranteed Str) -> @owned Y {
380+
bb0(%0 : $*Str):
381+
%1 = alloc_ref $Y
382+
%2 = ref_element_addr %1 : $Y, #Y.s
383+
copy_addr %0 to [init] %2 : $*Str
384+
return %1 : $Y
385+
}
386+

test/SILOptimizer/escape_info.sil

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ sil [ossa] @arg_to_return : $@convention(thin) (@owned X) -> @owned Str {
9393
[%0: escape => %r.s0]
9494
}
9595
sil [ossa] @arg_to_arg : $@convention(thin) (@owned X) -> @out Str {
96-
[%0: noescape]
97-
[%1: escape => %0.s0]
96+
[%0: noescape v**]
97+
[%1: escape -> %0.s0]
9898
}
9999
sil [ossa] @content_of_arg_to_return : $@convention(thin) (@owned Z) -> @owned Y {
100100
[%0: noescape, escape c* => %r]
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %target-swift-frontend -O -module-name=test -emit-sil %s | %FileCheck %s
2+
3+
protocol P {}
4+
5+
public struct S {
6+
let p: P // force S to be an address-only type
7+
let i: Int
8+
}
9+
10+
public final class X {
11+
let s: S
12+
13+
init(s: S) {
14+
self.s = s
15+
}
16+
}
17+
18+
@inline(never)
19+
func createX(s: S) -> X {
20+
return X(s: s)
21+
}
22+
23+
// Test that the load is a barrier for release hoisting and that the strong_release is not hoisted above the load.
24+
25+
// CHECK-LABEL: sil @$s4test6testit1sSiAA1SV_tF
26+
// CHECK: [[X:%[0-9]+]] = apply
27+
// CHECK: [[R:%[0-9]+]] = load
28+
// CHECK: strong_release [[X]]
29+
// CHECK: return [[R]]
30+
// CHECK: } // end sil function '$s4test6testit1sSiAA1SV_tF'
31+
public func testit(s: S) -> Int {
32+
let x = createX(s: s)
33+
return x.s.i
34+
}
35+

0 commit comments

Comments
 (0)