Skip to content

Commit 8be0ca0

Browse files
committed
SIL optimizer: remove unbalanced retains/releases from immortal objects
ARC operations don't have an effect on immortal objects, like the empty array singleton or statically allocated arrays. Therefore we can freely remove and retain/release instructions on such objects, even if there is no paired balanced ARC operation. This optimization can only be done with a minimum deployment target of Swift 5.1, because in that version we added immortal ref count bits. The optimization is implemented in libswift. Additionally, the remaining logic of simplifying strong_retain and strong_release is also ported to libswift. rdar://81482156
1 parent 90c71ad commit 8be0ca0

File tree

9 files changed

+241
-5
lines changed

9 files changed

+241
-5
lines changed

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,8 @@ PASS(PruneVTables, "prune-vtables",
422422
PASS_RANGE(AllPasses, AADumper, PruneVTables)
423423

424424
SWIFT_INSTRUCTION_PASS_WITH_LEGACY(GlobalValueInst, "simplify-global_value")
425+
SWIFT_INSTRUCTION_PASS_WITH_LEGACY(StrongRetainInst, "simplify-strong_retain")
426+
SWIFT_INSTRUCTION_PASS_WITH_LEGACY(StrongReleaseInst, "simplify-strong_release")
425427

426428
#undef IRGEN_PASS
427429
#undef SWIFT_FUNCTION_PASS

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ class SILCombiner :
279279
SILInstruction *optimizeStringObject(BuiltinInst *BI);
280280
SILInstruction *visitBuiltinInst(BuiltinInst *BI);
281281
SILInstruction *visitCondFailInst(CondFailInst *CFI);
282-
SILInstruction *visitStrongRetainInst(StrongRetainInst *SRI);
282+
SILInstruction *legacyVisitStrongRetainInst(StrongRetainInst *SRI);
283283
SILInstruction *visitCopyValueInst(CopyValueInst *cvi);
284284
SILInstruction *visitDestroyValueInst(DestroyValueInst *dvi);
285285
SILInstruction *visitRefToRawPointerInst(RefToRawPointerInst *RRPI);
@@ -309,7 +309,7 @@ class SILCombiner :
309309
SILInstruction *visitRawPointerToRefInst(RawPointerToRefInst *RPTR);
310310
SILInstruction *
311311
visitUncheckedTakeEnumDataAddrInst(UncheckedTakeEnumDataAddrInst *TEDAI);
312-
SILInstruction *visitStrongReleaseInst(StrongReleaseInst *SRI);
312+
SILInstruction *legacyVisitStrongReleaseInst(StrongReleaseInst *SRI);
313313
SILInstruction *visitCondBranchInst(CondBranchInst *CBI);
314314
SILInstruction *
315315
visitUncheckedTrivialBitCastInst(UncheckedTrivialBitCastInst *UTBCI);

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,7 +1213,7 @@ SILInstruction *SILCombiner::visitDestroyValueInst(DestroyValueInst *dvi) {
12131213
return nullptr;
12141214
}
12151215

1216-
SILInstruction *SILCombiner::visitStrongRetainInst(StrongRetainInst *SRI) {
1216+
SILInstruction *SILCombiner::legacyVisitStrongRetainInst(StrongRetainInst *SRI) {
12171217
assert(!SRI->getFunction()->hasOwnership());
12181218

12191219
// Retain of ThinToThickFunction is a no-op.
@@ -1821,7 +1821,7 @@ SILInstruction *SILCombiner::visitUncheckedTakeEnumDataAddrInst(
18211821
return eraseInstFromFunction(*tedai);
18221822
}
18231823

1824-
SILInstruction *SILCombiner::visitStrongReleaseInst(StrongReleaseInst *SRI) {
1824+
SILInstruction *SILCombiner::legacyVisitStrongReleaseInst(StrongReleaseInst *SRI) {
18251825
assert(!SRI->getFunction()->hasOwnership());
18261826

18271827
// Release of ThinToThickFunction is a no-op.

libswift/Sources/Optimizer/InstructionPasses/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
libswift_sources(Optimizer
10-
SimplifyGlobalValue.swift)
10+
SimplifyGlobalValue.swift
11+
SimplifyStrongRetainRelease.swift)

libswift/Sources/Optimizer/InstructionPasses/SimplifyGlobalValue.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212

1313
import SIL
1414

15+
// Removes all reference counting instructions of a `global_value` instruction
16+
// if it does not escape.
17+
//
18+
// Note that `simplifyStrongRetainPass` and `simplifyStrongReleasePass` can
19+
// even remove "unbalanced" retains/releases of a `global_value`, but this
20+
// requires a minimum deployment target.
1521
let simplifyGlobalValuePass = InstructionPass<GlobalValueInst>(
1622
name: "simplify-global_value", {
1723
(globalValue: GlobalValueInst, context: PassContext) in
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//===--- SimplifyStrongRetainRelease.swift - strong_retain/release opt ----===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SIL
14+
15+
let simplifyStrongRetainPass = InstructionPass<StrongRetainInst>(
16+
name: "simplify-strong_retain", {
17+
(retain: StrongRetainInst, context: PassContext) in
18+
19+
if isNotReferenceCounted(value: retain.operand, context: context) {
20+
context.erase(instruction: retain)
21+
return
22+
}
23+
24+
// Sometimes in the stdlib due to hand offs, we will see code like:
25+
//
26+
// strong_release %0
27+
// strong_retain %0
28+
//
29+
// with the matching strong_retain to the strong_release in a predecessor
30+
// basic block and the matching strong_release for the strong_retain in a
31+
// successor basic block.
32+
//
33+
// Due to the matching pairs being in different basic blocks, the ARC
34+
// Optimizer (which is currently local to one basic block does not handle
35+
// it). But that does not mean that we cannot eliminate this pair with a
36+
// peephole.
37+
if let prev = retain.previous {
38+
if let release = prev as? StrongReleaseInst {
39+
if release.operand == retain.operand {
40+
context.erase(instruction: retain)
41+
context.erase(instruction: release)
42+
return
43+
}
44+
}
45+
}
46+
})
47+
48+
let simplifyStrongReleasePass = InstructionPass<StrongReleaseInst>(
49+
name: "simplify-strong_release", {
50+
(release: StrongReleaseInst, context: PassContext) in
51+
52+
let op = release.operand
53+
if isNotReferenceCounted(value: op, context: context) {
54+
context.erase(instruction: release)
55+
return
56+
}
57+
58+
// Release of a classbound existential converted from a class is just a
59+
// release of the class, squish the conversion.
60+
if let ier = op as? InitExistentialRefInst {
61+
if ier.uses.isSingleUse {
62+
context.setOperand(of: release, at: 0, to: ier.operand)
63+
context.erase(instruction: ier)
64+
return
65+
}
66+
}
67+
})
68+
69+
/// Returns true if \p value is something where reference counting instructions
70+
/// don't have any effect.
71+
private func isNotReferenceCounted(value: Value, context: PassContext) -> Bool {
72+
switch value {
73+
case let cfi as ConvertFunctionInst:
74+
return isNotReferenceCounted(value: cfi.operand, context: context)
75+
case let uci as UpcastInst:
76+
return isNotReferenceCounted(value: uci.operand, context: context)
77+
case let urc as UncheckedRefCastInst:
78+
return isNotReferenceCounted(value: urc.operand, context: context)
79+
case is GlobalValueInst:
80+
// Since Swift 5.1, statically allocated objects have "immortal" reference
81+
// counts. Therefore we can safely eliminate unbalaced retains and
82+
// releases, because they are no-ops on immortal objects.
83+
// Note that the `simplifyGlobalValuePass` pass is deleting balanced
84+
// retains/releases, which doesn't require a Swift 5.1 minimum deployment
85+
// targert.
86+
return context.isSwift51RuntimeAvailable
87+
case let rptr as RawPointerToRefInst:
88+
// Like `global_value` but for the empty collection singletons from the
89+
// stdlib, e.g. the empty Array singleton.
90+
if context.isSwift51RuntimeAvailable {
91+
// The pattern generated for empty collection singletons is:
92+
// %0 = global_addr @_swiftEmptyArrayStorage
93+
// %1 = address_to_pointer %0
94+
// %2 = raw_pointer_to_ref %1
95+
if let atp = rptr.operand as? AddressToPointerInst {
96+
return atp.operand is GlobalAddrInst
97+
}
98+
}
99+
return false
100+
case // Thin functions are not reference counted.
101+
is ThinToThickFunctionInst,
102+
// The same for meta types.
103+
is ObjCExistentialMetatypeToObjectInst,
104+
is ObjCMetatypeToObjectInst,
105+
// Retain and Release of tagged strings is a no-op.
106+
// The builtin code pattern to find tagged strings is:
107+
// builtin "stringObjectOr_Int64" (or to tag the string)
108+
// value_to_bridge_object (cast the UInt to bridge object)
109+
is ValueToBridgeObjectInst:
110+
return true
111+
default:
112+
return false
113+
}
114+
}

libswift/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,6 @@ private func registerSwiftPasses() {
3939
registerPass(silPrinterPass, { silPrinterPass.run($0) })
4040
registerPass(mergeCondFailsPass, { mergeCondFailsPass.run($0) })
4141
registerPass(simplifyGlobalValuePass, { simplifyGlobalValuePass.run($0) })
42+
registerPass(simplifyStrongRetainPass, { simplifyStrongRetainPass.run($0) })
43+
registerPass(simplifyStrongReleasePass, { simplifyStrongReleasePass.run($0) })
4244
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// RUN: %sil-opt -enable-sil-verify-all %s -sil-combine | %FileCheck %s
2+
// Note: Intentionally not using %target-sil-opt, because we need at least a
3+
// swift 5.1 deployment target.
4+
5+
// REQUIRES: libswift
6+
7+
sil_stage canonical
8+
9+
import Builtin
10+
import Swift
11+
import SwiftShims
12+
13+
sil_global public_external @_swiftEmptyArrayStorage : $_SwiftEmptyArrayStorage
14+
15+
16+
// CHECK-LABEL: sil @testEmptyArraySingleton
17+
// CHECK: global_addr
18+
// CHECK-NOT: retain
19+
// CHECK: } // end sil function 'testEmptyArraySingleton'
20+
sil @testEmptyArraySingleton : $@convention(thin) () -> @owned Builtin.BridgeObject {
21+
bb0:
22+
%0 = global_addr @_swiftEmptyArrayStorage : $*_SwiftEmptyArrayStorage
23+
%1 = address_to_pointer %0 : $*_SwiftEmptyArrayStorage to $Builtin.RawPointer
24+
%2 = raw_pointer_to_ref %1 : $Builtin.RawPointer to $__EmptyArrayStorage
25+
%3 = unchecked_ref_cast %2 : $__EmptyArrayStorage to $Builtin.BridgeObject
26+
strong_retain %3 : $Builtin.BridgeObject
27+
return %3 : $Builtin.BridgeObject
28+
}
29+
30+
31+
sil_global private @staticArray : $_ContiguousArrayStorage<Int> = {
32+
%0 = integer_literal $Builtin.Int64, 0
33+
%1 = struct $UInt (%0 : $Builtin.Int64)
34+
%2 = struct $Int (%0 : $Builtin.Int64)
35+
%3 = struct $_SwiftArrayBodyStorage (%2 : $Int, %1 : $UInt)
36+
%4 = struct $_ArrayBody (%3 : $_SwiftArrayBodyStorage)
37+
%initval = object $_ContiguousArrayStorage<Int> (%4 : $_ArrayBody)
38+
}
39+
40+
// CHECK-LABEL: sil @testGlobalValue
41+
// CHECK: global_value
42+
// CHECK-NOT: retain
43+
// CHECK: } // end sil function 'testGlobalValue'
44+
sil @testGlobalValue : $@convention(thin) () -> @owned Builtin.BridgeObject {
45+
bb0:
46+
%0 = global_value @staticArray : $_ContiguousArrayStorage<Int>
47+
%1 = unchecked_ref_cast %0 : $_ContiguousArrayStorage<Int> to $Builtin.BridgeObject
48+
strong_retain %1 : $Builtin.BridgeObject
49+
return %1 : $Builtin.BridgeObject
50+
}
51+
52+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// RUN: %target-swift-frontend -primary-file %s -O -sil-verify-all -Xllvm -sil-disable-pass=function-signature-opts -module-name=test -O -target %target-cpu-apple-macos10.14 -emit-sil | %FileCheck --check-prefix=CHECK-SWIFT4x %s
2+
// RUN: %target-swift-frontend -primary-file %s -O -sil-verify-all -Xllvm -sil-disable-pass=function-signature-opts -module-name=test -O -target %target-cpu-apple-macos10.15 -emit-sil | %FileCheck --check-prefix=CHECK-SWIFT50 %s
3+
4+
// RUN: %empty-directory(%t)
5+
// RUN: %target-build-swift -O -Xllvm -sil-disable-pass=function-signature-opts -module-name=test %s -o %t/a.out
6+
// RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT
7+
8+
// REQUIRES: OS=macosx
9+
// REQUIRES: executable_test,swift_stdlib_no_asserts,optimized_stdlib
10+
// REQUIRES: libswift
11+
12+
// Check that the optimizer can remove "unbalanced" retains for immortal objects.
13+
// But only with a Swift 5.1 runtime (which supports immortal objects).
14+
15+
// CHECK-SWIFT4x-LABEL: sil hidden [noinline] @$s4test10emptyArraySaySiGyF
16+
// CHECK-SWIFT4x: global_addr
17+
// CHECK-SWIFT4x: retain
18+
// CHECK-SWIFT4x: } // end sil function '$s4test10emptyArraySaySiGyF'
19+
20+
// CHECK-SWIFT50-LABEL: sil hidden [noinline] @$s4test10emptyArraySaySiGyF
21+
// CHECK-SWIFT50: global_addr
22+
// CHECK-SWIFT50-NOT: retain
23+
// CHECK-SWIFT50: } // end sil function '$s4test10emptyArraySaySiGyF'
24+
@inline(never)
25+
func emptyArray() -> [Int] {
26+
let x = [Int]()
27+
return x
28+
}
29+
30+
// CHECK-SWIFT4x-LABEL: sil hidden [noinline] @$s4test13constantArraySaySiGyF
31+
// CHECK-SWIFT4x: global_value
32+
// CHECK-SWIFT4x: retain
33+
// CHECK-SWIFT4x: } // end sil function '$s4test13constantArraySaySiGyF'
34+
35+
// CHECK-SWIFT50-LABEL: sil hidden [noinline] @$s4test13constantArraySaySiGyF
36+
// CHECK-SWIFT50: global_value
37+
// CHECK-SWIFT50-NOT: retain
38+
// CHECK-SWIFT50: } // end sil function '$s4test13constantArraySaySiGyF'
39+
@inline(never)
40+
func constantArray() -> [Int] {
41+
return [1, 2, 3]
42+
}
43+
44+
func testit() {
45+
// CHECK-OUTPUT: []
46+
// CHECK-OUTPUT: [1, 2, 3]
47+
// CHECK-OUTPUT: []
48+
// CHECK-OUTPUT: [1, 2, 3]
49+
// CHECK-OUTPUT: []
50+
// CHECK-OUTPUT: [1, 2, 3]
51+
for _ in 0..<3 {
52+
print(emptyArray())
53+
print(constantArray())
54+
}
55+
}
56+
57+
testit()
58+
59+

0 commit comments

Comments
 (0)