Skip to content

Commit 97668d5

Browse files
authored
Merge pull request #68349 from eeckstein/let-property-lowering
SIL+Optimizer: a better representation for class let-fields
2 parents e1c6a4b + 480d3f4 commit 97668d5

File tree

100 files changed

+1817
-731
lines changed

Some content is hidden

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

100 files changed

+1817
-731
lines changed

SwiftCompilerSources/Sources/Optimizer/DataStructures/InstructionRange.swift

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,40 +48,45 @@ struct InstructionRange : CustomStringConvertible, NoReflectionChildren {
4848

4949
private var insertedInsts: InstructionSet
5050

51+
// For efficiency, this set does not include instructions in blocks which are not the begin or any end block.
52+
private var inExclusiveRange: InstructionSet
53+
5154
init(begin beginInst: Instruction, _ context: some Context) {
5255
self.begin = beginInst
5356
self.blockRange = BasicBlockRange(begin: beginInst.parentBlock, context)
5457
self.insertedInsts = InstructionSet(context)
58+
self.inExclusiveRange = InstructionSet(context)
59+
self.inExclusiveRange.insert(beginInst)
5560
}
5661

5762
/// Insert a potential end instruction.
5863
mutating func insert(_ inst: Instruction) {
5964
insertedInsts.insert(inst)
65+
insertIntoRange(instructions: ReverseInstructionList(first: inst.previous))
6066
blockRange.insert(inst.parentBlock)
67+
if inst.parentBlock != begin.parentBlock {
68+
// The first time an instruction is inserted in another block than the begin-block we need to insert
69+
// instructions from the begin instruction to the end of the begin block.
70+
// For subsequent insertions this is a no-op: `insertIntoRange` will return immediately because those
71+
// instruction are already inserted.
72+
insertIntoRange(instructions: begin.parentBlock.instructions.reversed())
73+
}
6174
}
6275

6376
/// Insert a sequence of potential end instructions.
64-
mutating func insert<S: Sequence>(contentsOf other: S) where S.Element == Instruction {
77+
mutating func insert<S: Sequence>(contentsOf other: S) where S.Element: Instruction {
6578
for inst in other {
6679
insert(inst)
6780
}
6881
}
6982

7083
/// Returns true if the exclusive range contains `inst`.
7184
func contains(_ inst: Instruction) -> Bool {
72-
let block = inst.parentBlock
73-
if !blockRange.inclusiveRangeContains(block) { return false }
74-
var inRange = false
75-
if blockRange.contains(block) {
76-
if block != blockRange.begin { return true }
77-
inRange = true
85+
if inExclusiveRange.contains(inst) {
86+
return true
7887
}
79-
for i in block.instructions.reversed() {
80-
if i == inst { return inRange }
81-
if insertedInsts.contains(i) { inRange = true }
82-
if i == begin { return false }
83-
}
84-
fatalError("didn't find instruction in its block")
88+
let block = inst.parentBlock
89+
return block != begin.parentBlock && blockRange.contains(block)
8590
}
8691

8792
/// Returns true if the inclusive range contains `inst`.
@@ -129,6 +134,14 @@ struct InstructionRange : CustomStringConvertible, NoReflectionChildren {
129134
}
130135
}
131136

137+
private mutating func insertIntoRange(instructions: ReverseInstructionList) {
138+
for inst in instructions {
139+
if !inExclusiveRange.insert(inst) {
140+
return
141+
}
142+
}
143+
}
144+
132145
var description: String {
133146
return (isValid ? "" : "<invalid>\n") +
134147
"""
@@ -141,6 +154,7 @@ struct InstructionRange : CustomStringConvertible, NoReflectionChildren {
141154

142155
/// TODO: once we have move-only types, make this a real deinit.
143156
mutating func deinitialize() {
157+
inExclusiveRange.deinitialize()
144158
insertedInsts.deinitialize()
145159
blockRange.deinitialize()
146160
}

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ swift_compiler_sources(Optimizer
1313
ComputeSideEffects.swift
1414
DeadStoreElimination.swift
1515
InitializeStaticGlobals.swift
16+
LetPropertyLowering.swift
1617
ObjectOutliner.swift
1718
ObjCBridgingOptimization.swift
1819
MergeCondFails.swift
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
//===--- LetPropertyLowering.swift -----------------------------------------==//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 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+
/// Lowers let property accesses of classes.
16+
///
17+
/// Lowering consists of two tasks:
18+
///
19+
/// * In class initializers, insert `end_init_let_ref` instructions at places where all let-fields are initialized.
20+
/// This strictly separates the life-range of the class into a region where let fields are still written during
21+
/// initialization and a region where let fields are truly immutable.
22+
///
23+
/// * Add the `[immutable]` flag to all `ref_element_addr` instructions (for let-fields) which are in the "immutable"
24+
/// region. This includes the region after an inserted `end_init_let_ref` in an class initializer, but also all
25+
/// let-field accesses in other functions than the initializer and the destructor.
26+
///
27+
/// This pass should run after DefiniteInitialization but before RawSILInstLowering (because it relies on
28+
/// `mark_uninitialized` still present in the class initializer).
29+
///
30+
/// Note that it's not mandatory to run this pass. If it doesn't run, SIL is still correct.
31+
///
32+
/// Simplified example (after lowering):
33+
///
34+
/// bb0(%0 : @owned C): // = self of the class initializer
35+
/// %1 = mark_uninitialized %0
36+
/// %2 = ref_element_addr %1, #C.l // a let-field
37+
/// store %init_value to %2
38+
/// %3 = end_init_let_ref %1 // inserted by lowering
39+
/// %4 = ref_element_addr [immutable] %3, #C.l // set to immutable by lowering
40+
/// %5 = load %4
41+
///
42+
let letPropertyLowering = FunctionPass(name: "let-property-lowering") {
43+
(function: Function, context: FunctionPassContext) in
44+
45+
assert(context.silStage == .raw, "let-property-lowering must run before RawSILInstLowering")
46+
47+
if context.hadError {
48+
// If DefiniteInitialization (or other passes) already reported an error, we cannot assume valid SIL anymore.
49+
return
50+
}
51+
52+
if function.isDestructor {
53+
// Let-fields are not immutable in the class destructor.
54+
return
55+
}
56+
57+
for inst in function.instructions {
58+
switch inst {
59+
60+
// First task of lowering: insert `end_init_let_ref` instructions in class initializers.
61+
case let markUninitialized as MarkUninitializedInst
62+
where markUninitialized.type.isClass &&
63+
// TODO: support move-only classes
64+
!markUninitialized.type.isMoveOnly &&
65+
// We only have to do that for root classes because derived classes call the super-initializer
66+
// _after_ all fields in the derived class are already initialized.
67+
markUninitialized.kind == .rootSelf:
68+
69+
insertEndInitInstructions(for: markUninitialized, context)
70+
71+
// Second task of lowering: set the `immutable` flags.
72+
case let rea as RefElementAddrInst
73+
where rea.fieldIsLet && !rea.isInUninitializedRegion &&
74+
// TODO: support move-only classes
75+
!rea.instance.type.isMoveOnly:
76+
rea.set(isImmutable: true, context)
77+
78+
default:
79+
break
80+
}
81+
}
82+
}
83+
84+
private func insertEndInitInstructions(for markUninitialized: MarkUninitializedInst, _ context: FunctionPassContext) {
85+
assert(!markUninitialized.type.isAddress, "self of class should not be an address")
86+
87+
// The region which contains all let-field initializations, including any partial
88+
// let-field de-initializations (in case of a fail-able or throwing initializer).
89+
var initRegion = InstructionRange(begin: markUninitialized, context)
90+
defer { initRegion.deinitialize() }
91+
92+
constructLetInitRegion(of: markUninitialized, result: &initRegion, context)
93+
94+
insertEndInitInstructions(for: markUninitialized, atEndOf: initRegion, context)
95+
}
96+
97+
private func insertEndInitInstructions(
98+
for markUninitialized: MarkUninitializedInst,
99+
atEndOf initRegion: InstructionRange,
100+
_ context: FunctionPassContext
101+
) {
102+
var ssaUpdater = SSAUpdater(type: markUninitialized.type, ownership: .owned, context)
103+
ssaUpdater.addAvailableValue(markUninitialized, in: markUninitialized.parentBlock)
104+
105+
for endInst in initRegion.ends {
106+
let builder = Builder(after: endInst, context)
107+
let newValue = builder.createEndInitLetRef(operand: markUninitialized)
108+
ssaUpdater.addAvailableValue(newValue, in: endInst.parentBlock)
109+
}
110+
111+
for exitInst in initRegion.exits {
112+
let builder = Builder(before: exitInst, context)
113+
let newValue = builder.createEndInitLetRef(operand: markUninitialized)
114+
ssaUpdater.addAvailableValue(newValue, in: exitInst.parentBlock)
115+
}
116+
117+
for use in markUninitialized.uses {
118+
if !initRegion.inclusiveRangeContains(use.instruction) &&
119+
!(use.instruction is EndInitLetRefInst)
120+
{
121+
use.set(to: ssaUpdater.getValue(atEndOf: use.instruction.parentBlock), context)
122+
}
123+
}
124+
}
125+
126+
private func constructLetInitRegion(
127+
of markUninitialized: MarkUninitializedInst,
128+
result initRegion: inout InstructionRange,
129+
_ context: FunctionPassContext
130+
) {
131+
// Adding the initial `mark_uninitialized` ensures that a single `end_init_let_ref` is inserted (after the
132+
// `mark_uninitialized`) in case there are no let-field accesses at all.
133+
// Note that we have to insert an `end_init_let_ref` even if there are no let-field initializations, because
134+
// derived classes could have let-field initializations in their initializers (which eventually call the
135+
// root-class initializer).
136+
initRegion.insert(markUninitialized)
137+
138+
var beginBorrows = Stack<BeginBorrowInst>(context)
139+
defer { beginBorrows.deinitialize() }
140+
141+
for inst in markUninitialized.parentFunction.instructions {
142+
switch inst {
143+
case let assign as AssignInst
144+
where assign.destination.isLetFieldAddress(of: markUninitialized):
145+
assert(assign.assignOwnership == .initialize)
146+
initRegion.insert(inst)
147+
148+
case let store as StoreInst
149+
where store.destination.isLetFieldAddress(of: markUninitialized):
150+
assert(store.storeOwnership != .assign)
151+
initRegion.insert(inst)
152+
153+
case let copy as CopyAddrInst
154+
where copy.destination.isLetFieldAddress(of: markUninitialized):
155+
assert(copy.isInitializationOfDest)
156+
initRegion.insert(inst)
157+
158+
case let beginAccess as BeginAccessInst
159+
where beginAccess.accessKind == .Deinit &&
160+
beginAccess.address.isLetFieldAddress(of: markUninitialized):
161+
// Include let-field partial de-initializations in the region.
162+
initRegion.insert(inst)
163+
164+
case let beginBorrow as BeginBorrowInst:
165+
beginBorrows.append(beginBorrow)
166+
167+
default:
168+
break
169+
}
170+
}
171+
172+
// Extend the region to whole borrow scopes to avoid that we insert an `end_init_let_ref` in the
173+
// middle of a borrow scope.
174+
for beginBorrow in beginBorrows where initRegion.contains(beginBorrow) {
175+
initRegion.insert(contentsOf: beginBorrow.endBorrows)
176+
}
177+
}
178+
179+
private extension RefElementAddrInst {
180+
var isInUninitializedRegion: Bool {
181+
var root = self.instance
182+
while true {
183+
switch root {
184+
case let beginBorrow as BeginBorrowInst:
185+
root = beginBorrow.borrowedValue
186+
case let loadBorrow as LoadBorrowInst:
187+
// Initializers of derived classes store `self` into a stack location from where
188+
// it's loaded via a `load_borrow`.
189+
root = loadBorrow.address
190+
case is MarkUninitializedInst:
191+
return true
192+
default:
193+
return false
194+
}
195+
}
196+
}
197+
}
198+
199+
private extension Value {
200+
func isLetFieldAddress(of markUninitialized: MarkUninitializedInst) -> Bool {
201+
if case .class(let rea) = self.accessBase,
202+
rea.fieldIsLet,
203+
rea.instance.referenceRoot == markUninitialized
204+
{
205+
return true
206+
}
207+
return false
208+
}
209+
}

0 commit comments

Comments
 (0)