Skip to content

Commit 5bc0366

Browse files
committed
SIL optimizer: add the LetPropertyLowering pass
It lowers let property accesses of classes. Lowering consists of two tasks: * In class initializers, insert `end_init_let_ref` instructions at places where all let-fields are initialized. This strictly separates the life-range of the class into a region where let fields are still written during initialization and a region where let fields are truly immutable. * Add the `[immutable]` flag to all `ref_element_addr` instructions (for let-fields) which are in the "immutable" region. This includes the region after an inserted `end_init_let_ref` in an class initializer, but also all let-field accesses in other functions than the initializer and the destructor. This pass should run after DefiniteInitialization but before RawSILInstLowering (because it relies on `mark_uninitialized` still present in the class initializer). Note that it's not mandatory to run this pass. If it doesn't run, SIL is still correct. Simplified example (after lowering): bb0(%0 : @owned C): // = self of the class initializer %1 = mark_uninitialized %0 %2 = ref_element_addr %1, #C.l // a let-field store %init_value to %2 %3 = end_init_let_ref %1 // inserted by lowering %4 = ref_element_addr [immutable] %3, #C.l // set to immutable by lowering %5 = load %4
1 parent 9ae28a2 commit 5bc0366

38 files changed

+906
-127
lines changed

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+
}

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ private func registerSwiftPasses() {
6666
registerPass(stackProtection, { stackProtection.run($0) })
6767

6868
// Function passes
69+
registerPass(letPropertyLowering, { letPropertyLowering.run($0) })
6970
registerPass(mergeCondFailsPass, { mergeCondFailsPass.run($0) })
7071
registerPass(computeEscapeEffects, { computeEscapeEffects.run($0) })
7172
registerPass(computeSideEffects, { computeSideEffects.run($0) })

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ IRGEN_PASS(PackMetadataMarkerInserter, "pack-metadata-marker-inserter",
355355
"Insert markers where pack metadata might be de/allocated.")
356356
PASS(PerformanceSILLinker, "performance-linker",
357357
"Deserialize all referenced SIL functions")
358+
SWIFT_FUNCTION_PASS(LetPropertyLowering, "let-property-lowering",
359+
"Lowers accesses to let properties of classes")
358360
PASS(RawSILInstLowering, "raw-sil-inst-lowering",
359361
"Lower all raw SIL instructions to canonical equivalents.")
360362
PASS(TempLValueOpt, "temp-lvalue-opt",

lib/SILOptimizer/Differentiation/PullbackCloner.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1644,6 +1644,8 @@ class PullbackCloner::Implementation final
16441644
/// Adjoint: adj[x] += adj[y]
16451645
void visitMoveValueInst(MoveValueInst *mvi) { visitValueOwnershipInst(mvi); }
16461646

1647+
void visitEndInitLetRefInst(EndInitLetRefInst *eir) { visitValueOwnershipInst(eir); }
1648+
16471649
/// Handle `begin_access` instruction.
16481650
/// Original: y = begin_access x
16491651
/// Adjoint: nothing

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3702,25 +3702,14 @@ static bool checkDefiniteInitialization(SILFunction &Fn) {
37023702
BlockStates blockStates(&Fn);
37033703

37043704
for (auto &BB : Fn) {
3705-
for (auto I = BB.begin(), E = BB.end(); I != E;) {
3706-
SILInstruction *Inst = &*I;
3707-
3708-
auto *MUI = dyn_cast<MarkUninitializedInst>(Inst);
3709-
if (!MUI) {
3710-
++I;
3711-
continue;
3705+
for (SILInstruction &inst : BB) {
3706+
if (auto *MUI = dyn_cast<MarkUninitializedInst>(&inst)) {
3707+
processMemoryObject(MUI, blockStates);
3708+
Changed = true;
3709+
// mark_uninitialized needs to remain in SIL for mandatory passes which
3710+
// follow DI, like LetPropertyLowering.
3711+
// It will be eventually removed by RawSILInstLowering.
37123712
}
3713-
3714-
// Then process the memory object.
3715-
processMemoryObject(MUI, blockStates);
3716-
3717-
// Move off of the MUI only after we have processed memory objects. The
3718-
// lifetime checker may rewrite instructions, so it is important to not
3719-
// move onto the next element until after it runs.
3720-
++I;
3721-
MUI->replaceAllUsesWith(MUI->getOperand());
3722-
MUI->eraseFromParent();
3723-
Changed = true;
37243713
}
37253714
}
37263715

lib/SILOptimizer/Mandatory/FlowIsolation.cpp

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,30 @@ static bool diagnoseNonSendableFromDeinit(ModuleDecl *module,
546546
return true;
547547
}
548548

549+
class OperandWorklist {
550+
SmallVector<Operand *, 32> worklist;
551+
SmallPtrSet<Operand *, 16> visited;
552+
553+
public:
554+
Operand *pop() {
555+
if (worklist.empty())
556+
return nullptr;
557+
return worklist.pop_back_val();
558+
}
559+
560+
void pushIfNotVisited(Operand *op) {
561+
if (visited.insert(op).second) {
562+
worklist.push_back(op);
563+
}
564+
}
565+
566+
void pushUsesOfValueIfNotVisited(SILValue value) {
567+
for (Operand *use : value->getUses()) {
568+
pushIfNotVisited(use);
569+
}
570+
}
571+
};
572+
549573
/// Analyzes a function for uses of `self` and records the kinds of isolation
550574
/// required.
551575
/// \param selfParam the parameter of \c getFunction() that should be
@@ -556,13 +580,12 @@ void AnalysisInfo::analyze(const SILArgument *selfParam) {
556580
ModuleDecl *module = getFunction()->getModule().getSwiftModule();
557581

558582
// Use a worklist to track the uses left to be searched.
559-
SmallVector<Operand *, 32> worklist;
583+
OperandWorklist worklist;
560584

561585
// Seed with direct users of `self`
562-
worklist.append(selfParam->use_begin(), selfParam->use_end());
586+
worklist.pushUsesOfValueIfNotVisited(selfParam);
563587

564-
while (!worklist.empty()) {
565-
Operand *operand = worklist.pop_back_val();
588+
while (Operand *operand = worklist.pop()) {
566589
SILInstruction *user = operand->getUser();
567590

568591
// First, check if this is an apply that involves `self`
@@ -651,12 +674,20 @@ void AnalysisInfo::analyze(const SILArgument *selfParam) {
651674
break;
652675

653676
case SILInstructionKind::BeginAccessInst:
654-
case SILInstructionKind::BeginBorrowInst: {
677+
case SILInstructionKind::BeginBorrowInst:
678+
case SILInstructionKind::EndInitLetRefInst: {
655679
auto *svi = cast<SingleValueInstruction>(user);
656-
worklist.append(svi->use_begin(), svi->use_end());
680+
worklist.pushUsesOfValueIfNotVisited(svi);
657681
break;
658682
}
659683

684+
case SILInstructionKind::BranchInst: {
685+
auto *arg = cast<BranchInst>(user)->getArgForOperand(operand);
686+
worklist.pushUsesOfValueIfNotVisited(arg);
687+
break;
688+
}
689+
690+
660691
default:
661692
// don't follow this instruction.
662693
LLVM_DEBUG(llvm::dbgs() << DEBUG_TYPE << " def-use walk skipping: "

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ static void addOwnershipModelEliminatorPipeline(SILPassPipelinePlan &P) {
103103
/// order.
104104
static void addDefiniteInitialization(SILPassPipelinePlan &P) {
105105
P.addDefiniteInitialization();
106+
P.addLetPropertyLowering();
106107
P.addRawSILInstLowering();
107108
}
108109

lib/SILOptimizer/Transforms/AssemblyVisionRemarkGenerator.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,14 @@ bool ValueToDeclInferrer::infer(
448448
foundDeclFromUse |= valueUseInferrer.findDecls(use, value);
449449
});
450450

451+
for (Operand *use : value->getUses()) {
452+
if (auto *eir = dyn_cast<EndInitLetRefInst>(use->getUser())) {
453+
rcfi.visitRCUses(eir, [&](Operand *use) {
454+
foundDeclFromUse |= valueUseInferrer.findDecls(use, value);
455+
});
456+
}
457+
}
458+
451459
// At this point, we could not infer any argument. See if we can look up the
452460
// def-use graph and come up with a good location after looking through
453461
// loads and projections.

lib/SILOptimizer/Utils/CastOptimizer.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,8 @@ CastOptimizer::optimizeCheckedCastBranchInst(CheckedCastBranchInst *Inst) {
13011301
// checked_cast_br %1, ....
13021302
if (auto *FoundIERI = dyn_cast<InitExistentialRefInst>(Op)) {
13031303
SILValue op = FoundIERI->getOperand();
1304+
if (auto *eir = dyn_cast<EndInitLetRefInst>(op))
1305+
op = eir->getOperand();
13041306
if (!isa<AllocRefInst>(op))
13051307
return nullptr;
13061308

0 commit comments

Comments
 (0)