Skip to content

Commit b687c75

Browse files
Merge remote-tracking branch 'apple/main' into katei/merge-main-2022-05-04
2 parents 1bd3f56 + c58d4db commit b687c75

File tree

83 files changed

+2510
-375
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+2510
-375
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ swift_compiler_sources(Optimizer
1515
RangeDumper.swift
1616
ReleaseDevirtualizer.swift
1717
RunUnitTests.swift
18+
StackPromotion.swift
1819
)
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
//===--- StackPromotion.swift - Stack promotion optimization --------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 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+
/// Promotes heap allocated objects to the stack.
16+
///
17+
/// It handles `alloc_ref` and `alloc_ref_dynamic` instructions of native swift
18+
/// classes: if promoted, the `[stack]` attribute is set in the allocation
19+
/// instruction and a `dealloc_stack_ref` is inserted at the end of the object's
20+
/// lifetime.
21+
22+
/// The main criteria for stack promotion is that the allocated object must not
23+
/// escape its function.
24+
///
25+
/// Example:
26+
/// %k = alloc_ref $Klass
27+
/// // .. some uses of %k
28+
/// destroy_value %k // The end of %k's lifetime
29+
///
30+
/// is transformed to:
31+
///
32+
/// %k = alloc_ref [stack] $Klass
33+
/// // .. some uses of %k
34+
/// destroy_value %k
35+
/// dealloc_stack_ref %k
36+
///
37+
/// The destroy/release of the promoted object remains in the SIL, but is effectively
38+
/// a no-op, because a stack promoted object is initialized with an "immortal"
39+
/// reference count.
40+
/// Later optimizations can clean that up.
41+
let stackPromotion = FunctionPass(name: "stack-promotion", {
42+
(function: Function, context: PassContext) in
43+
44+
var escapeInfo = EscapeInfo(calleeAnalysis: context.calleeAnalysis)
45+
let deadEndBlocks = context.deadEndBlocks
46+
47+
var changed = false
48+
for block in function.blocks {
49+
for inst in block.instructions {
50+
if let ar = inst as? AllocRefInstBase {
51+
if deadEndBlocks.isDeadEnd(block) {
52+
// Don't stack promote any allocation inside a code region which ends up
53+
// in a no-return block. Such allocations may missing their final release.
54+
// We would insert the deallocation too early, which may result in a
55+
// use-after-free problem.
56+
continue
57+
}
58+
if !context.continueWithNextSubpassRun(for: ar) {
59+
break
60+
}
61+
62+
if tryPromoteAlloc(ar, &escapeInfo, deadEndBlocks, context) {
63+
changed = true
64+
}
65+
}
66+
}
67+
}
68+
if changed {
69+
// Make sure that all stack allocating instructions are nested correctly.
70+
context.fixStackNesting(function: function)
71+
}
72+
})
73+
74+
private
75+
func tryPromoteAlloc(_ allocRef: AllocRefInstBase,
76+
_ escapeInfo: inout EscapeInfo,
77+
_ deadEndBlocks: DeadEndBlocksAnalysis,
78+
_ context: PassContext) -> Bool {
79+
if allocRef.isObjC || allocRef.canAllocOnStack {
80+
return false
81+
}
82+
83+
// The most important check: does the object escape the current function?
84+
if escapeInfo.isEscaping(object:allocRef) {
85+
return false
86+
}
87+
88+
// Try to find the top most dominator block which dominates all use points.
89+
// * This block can be located "earlier" than the actual allocation block, in case the
90+
// promoted object is stored into an "outer" object, e.g.
91+
//
92+
// bb0: // outerDominatingBlock _
93+
// %o = alloc_ref $Outer |
94+
// ... |
95+
// bb1: // allocation block _ |
96+
// %k = alloc_ref $Klass | | "outer"
97+
// %f = ref_element_addr %o, #Outer.f | "inner" | liferange
98+
// store %k to %f | liferange |
99+
// ... | |
100+
// destroy_value %o _| _|
101+
//
102+
// * Finding the `outerDominatingBlock` is not guaranteed to work.
103+
// In this example, the top most dominator block is `bb0`, but `bb0` has no
104+
// use points in the outer liferange. We'll get `bb3` as outerDominatingBlock.
105+
// This is no problem because 1. it's an unusual case and 2. the `outerBlockRange`
106+
// is invalid in this case and we'll bail later.
107+
//
108+
// bb0: // real top most dominating block
109+
// cond_br %c, bb1, bb2
110+
// bb1:
111+
// %o1 = alloc_ref $Outer
112+
// br bb3(%o1)
113+
// bb2:
114+
// %o2 = alloc_ref $Outer
115+
// br bb3(%o1)
116+
// bb3(%o): // resulting outerDominatingBlock: wrong!
117+
// %k = alloc_ref $Klass
118+
// %f = ref_element_addr %o, #Outer.f
119+
// store %k to %f
120+
// destroy_value %o
121+
//
122+
let domTree = context.dominatorTree
123+
let outerDominatingBlock = escapeInfo.getDominatingBlockOfAllUsePoints(allocRef, domTree: domTree)
124+
125+
// The "inner" liferange contains all use points which are dominated by the allocation block
126+
var innerRange = InstructionRange(begin: allocRef, context)
127+
defer { innerRange.deinitialize() }
128+
129+
// The "outer" liferange contains all use points.
130+
var outerBlockRange = BasicBlockRange(begin: outerDominatingBlock, context)
131+
defer { outerBlockRange.deinitialize() }
132+
133+
escapeInfo.computeInnerAndOuterLiferanges(&innerRange, &outerBlockRange, domTree: domTree)
134+
135+
precondition(innerRange.blockRange.isValid, "inner range should be valid because we did a dominance check")
136+
137+
if !outerBlockRange.isValid {
138+
// This happens if we fail to find a correct outerDominatingBlock.
139+
return false
140+
}
141+
142+
// Check if there is a control flow edge from the inner to the outer liferange, which
143+
// would mean that the promoted object can escape to the outer liferange.
144+
// This can e.g. be the case if the inner liferange does not post dominate the outer range:
145+
// _
146+
// %o = alloc_ref $Outer |
147+
// cond_br %c, bb1, bb2 |
148+
// bb1: _ |
149+
// %k = alloc_ref $Klass | | outer
150+
// %f = ref_element_addr %o, #Outer.f | inner | range
151+
// store %k to %f | range |
152+
// br bb2 // branch from inner to outer _| |
153+
// bb2: |
154+
// destroy_value %o _|
155+
//
156+
// Or if there is a loop with a back-edge from the inner to the outer range:
157+
// _
158+
// %o = alloc_ref $Outer |
159+
// br bb1 |
160+
// bb1: _ |
161+
// %k = alloc_ref $Klass | | outer
162+
// %f = ref_element_addr %o, #Outer.f | inner | range
163+
// store %k to %f | range |
164+
// cond_br %c, bb1, bb2 // inner -> outer _| |
165+
// bb2: |
166+
// destroy_value %o _|
167+
//
168+
if innerRange.blockRange.isControlFlowEdge(to: outerBlockRange) {
169+
return false
170+
}
171+
172+
// There shouldn't be any critical exit edges from the liferange, because that would mean
173+
// that the promoted allocation is leaking.
174+
// Just to be on the safe side, do a check and bail if we find critical exit edges: we
175+
// cannot insert instructions on critical edges.
176+
if innerRange.blockRange.containsCriticalExitEdges(deadEndBlocks: deadEndBlocks) {
177+
return false
178+
}
179+
180+
// Do the transformation!
181+
// Insert `dealloc_stack_ref` instructions at the exit- and end-points of the inner liferange.
182+
for exitInst in innerRange.exits {
183+
if !deadEndBlocks.isDeadEnd(exitInst.block) {
184+
let builder = Builder(at: exitInst, context)
185+
_ = builder.createDeallocStackRef(allocRef)
186+
}
187+
}
188+
189+
for endInst in innerRange.ends {
190+
Builder.insert(after: endInst, location: allocRef.location, context) {
191+
(builder) in _ = builder.createDeallocStackRef(allocRef)
192+
}
193+
}
194+
195+
allocRef.setIsStackAllocatable(context)
196+
return true
197+
}
198+
199+
//===----------------------------------------------------------------------===//
200+
// utility functions
201+
//===----------------------------------------------------------------------===//
202+
203+
private extension EscapeInfo {
204+
mutating func getDominatingBlockOfAllUsePoints(_ value: SingleValueInstruction,
205+
domTree: DominatorTree) -> BasicBlock {
206+
// The starting point
207+
var dominatingBlock = value.block
208+
209+
_ = isEscaping(object: value, visitUse: { op, _, _ in
210+
if let defBlock = op.value.definingBlock, defBlock.dominates(dominatingBlock, domTree) {
211+
dominatingBlock = defBlock
212+
}
213+
return .continueWalking
214+
})
215+
return dominatingBlock
216+
}
217+
218+
/// All uses which are dominated by the `innerInstRange`s begin-block are included
219+
/// in both, the `innerInstRange` and the `outerBlockRange`.
220+
/// All _not_ dominated uses are only included in the `outerBlockRange`.
221+
mutating
222+
func computeInnerAndOuterLiferanges(_ innerInstRange: inout InstructionRange,
223+
_ outerBlockRange: inout BasicBlockRange,
224+
domTree: DominatorTree) {
225+
_ = isEscaping(object: innerInstRange.begin as! SingleValueInstruction, visitUse: { op, _, _ in
226+
let user = op.instruction
227+
if innerInstRange.blockRange.begin.dominates(user.block, domTree) {
228+
innerInstRange.insert(user)
229+
}
230+
outerBlockRange.insert(user.block)
231+
232+
let val = op.value
233+
if let defBlock = val.definingBlock {
234+
// Also insert the operand's definition. Otherwise we would miss allocation
235+
// instructions (for which the `visitUse` closure is not called).
236+
outerBlockRange.insert(defBlock)
237+
238+
// We need to explicitly add predecessor blocks of phi-arguments becaues they
239+
// are not necesesarily visited by `EscapeInfo.walkDown`.
240+
// This is important for the special case where there is a back-edge from the
241+
// inner range to the inner rage's begin-block:
242+
//
243+
// bb0: // <- need to be in the outer range
244+
// br bb1(%some_init_val)
245+
// bb1(%arg):
246+
// %k = alloc_ref $Klass // innerInstRange.begin
247+
// cond_br bb2, bb1(%k) // back-edge to bb1 == innerInstRange.blockRange.begin
248+
//
249+
if val is BlockArgument {
250+
outerBlockRange.insert(contentsOf: defBlock.predecessors)
251+
}
252+
}
253+
return .continueWalking
254+
})
255+
}
256+
}
257+
258+
private extension BasicBlockRange {
259+
/// Returns true if there is a direct edge connecting this range with the `otherRange`.
260+
func isControlFlowEdge(to otherRange: BasicBlockRange) -> Bool {
261+
func isOnlyInOtherRange(_ block: BasicBlock) -> Bool {
262+
return !inclusiveRangeContains(block) &&
263+
otherRange.inclusiveRangeContains(block) && block != otherRange.begin
264+
}
265+
266+
for lifeBlock in inclusiveRange {
267+
precondition(otherRange.inclusiveRangeContains(lifeBlock), "range must be a subset of other range")
268+
for succ in lifeBlock.successors {
269+
if isOnlyInOtherRange(succ) {
270+
return true
271+
}
272+
// The entry of the begin-block is conceptually not part of the range. We can check if
273+
// it's part of the `otherRange` by checking the begin-block's predecessors.
274+
if succ == begin && begin.predecessors.contains(where: { isOnlyInOtherRange($0) }) {
275+
return true
276+
}
277+
}
278+
}
279+
return false
280+
}
281+
282+
func containsCriticalExitEdges(deadEndBlocks: DeadEndBlocksAnalysis) -> Bool {
283+
exits.contains { !deadEndBlocks.isDeadEnd($0) && !$0.hasSinglePredecessor }
284+
}
285+
}

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ private func registerSwiftPasses() {
4343
registerPass(escapeInfoDumper, { escapeInfoDumper.run($0) })
4444
registerPass(addressEscapeInfoDumper, { addressEscapeInfoDumper.run($0) })
4545
registerPass(computeEffects, { computeEffects.run($0) })
46+
registerPass(stackPromotion, { stackPromotion.run($0) })
4647
registerPass(simplifyBeginCOWMutationPass, { simplifyBeginCOWMutationPass.run($0) })
4748
registerPass(simplifyGlobalValuePass, { simplifyGlobalValuePass.run($0) })
4849
registerPass(simplifyStrongRetainPass, { simplifyStrongRetainPass.run($0) })

cmake/modules/SwiftSharedCMakeConfig.cmake

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ macro(swift_common_standalone_build_config_cmark product)
230230
get_filename_component(CMARK_LIBRARY_DIR "${${product}_CMARK_LIBRARY_DIR}"
231231
ABSOLUTE)
232232

233-
set(CMARK_MAIN_INCLUDE_DIR "${CMARK_MAIN_SRC_DIR}/src")
233+
set(CMARK_MAIN_INCLUDE_DIR "${CMARK_MAIN_SRC_DIR}/src/include")
234234
set(CMARK_BUILD_INCLUDE_DIR "${PATH_TO_CMARK_BUILD}/src")
235235

236236
file(TO_CMAKE_PATH "${CMARK_MAIN_INCLUDE_DIR}" CMARK_MAIN_INCLUDE_DIR)
@@ -290,7 +290,7 @@ macro(swift_common_unified_build_config product)
290290
ABSOLUTE)
291291

292292
set(CMARK_BUILD_INCLUDE_DIR "${PATH_TO_CMARK_BUILD}/src")
293-
set(CMARK_MAIN_INCLUDE_DIR "${CMARK_MAIN_SRC_DIR}/src")
293+
set(CMARK_MAIN_INCLUDE_DIR "${CMARK_MAIN_SRC_DIR}/src/include")
294294
endif()
295295

296296
include_directories(

include/swift/AST/Attr.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ TYPE_ATTR(pseudogeneric)
8484
TYPE_ATTR(yields)
8585
TYPE_ATTR(yield_once)
8686
TYPE_ATTR(yield_many)
87+
TYPE_ATTR(captures_generics)
8788

8889
// SIL metatype attributes.
8990
TYPE_ATTR(thin)

include/swift/AST/SILLayout.h

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,17 @@ class SILLayout final : public llvm::FoldingSetNode,
8989

9090
enum : unsigned {
9191
IsMutable = 0x1,
92+
CapturesGenericEnvironment = 0x2,
9293
};
9394

94-
static constexpr const unsigned NumFlags = 1;
95+
static constexpr const unsigned NumFlags = 2;
9596

96-
static unsigned getFlagsValue(bool Mutable) {
97+
static unsigned getFlagsValue(bool Mutable, bool CapturesGenerics) {
9798
unsigned flags = 0;
9899
if (Mutable)
99100
flags |= IsMutable;
101+
if (CapturesGenerics)
102+
flags |= CapturesGenericEnvironment;
100103

101104
assert(flags >> NumFlags == 0
102105
&& "more flags than flag bits?!");
@@ -109,15 +112,17 @@ class SILLayout final : public llvm::FoldingSetNode,
109112
unsigned NumFields;
110113

111114
SILLayout(CanGenericSignature Signature,
112-
ArrayRef<SILField> Fields);
115+
ArrayRef<SILField> Fields,
116+
bool CapturesGenericEnvironment);
113117

114118
SILLayout(const SILLayout &) = delete;
115119
SILLayout &operator=(const SILLayout &) = delete;
116120
public:
117121
/// Get or create a layout.
118122
static SILLayout *get(ASTContext &C,
119123
CanGenericSignature Generics,
120-
ArrayRef<SILField> Fields);
124+
ArrayRef<SILField> Fields,
125+
bool CapturesGenericEnvironment);
121126

122127
/// Get the generic signature in which this layout exists.
123128
CanGenericSignature getGenericSignature() const {
@@ -129,6 +134,12 @@ class SILLayout final : public llvm::FoldingSetNode,
129134
return GenericSigAndFlags.getInt() & IsMutable;
130135
}
131136

137+
/// True if the layout captures the generic arguments it is substituted with
138+
/// and can provide generic bindings when passed as a closure argument.
139+
bool capturesGenericEnvironment() const {
140+
return GenericSigAndFlags.getInt() & CapturesGenericEnvironment;
141+
}
142+
132143
/// Get the fields inside the layout.
133144
ArrayRef<SILField> getFields() const {
134145
return llvm::makeArrayRef(getTrailingObjects<SILField>(), NumFields);
@@ -137,11 +148,13 @@ class SILLayout final : public llvm::FoldingSetNode,
137148
/// Produce a profile of this layout, for use in a folding set.
138149
static void Profile(llvm::FoldingSetNodeID &id,
139150
CanGenericSignature Generics,
140-
ArrayRef<SILField> Fields);
151+
ArrayRef<SILField> Fields,
152+
bool CapturesGenericEnvironment);
141153

142154
/// Produce a profile of this locator, for use in a folding set.
143155
void Profile(llvm::FoldingSetNodeID &id) {
144-
Profile(id, getGenericSignature(), getFields());
156+
Profile(id, getGenericSignature(), getFields(),
157+
capturesGenericEnvironment());
145158
}
146159
};
147160

0 commit comments

Comments
 (0)