Skip to content

Commit f055bdc

Browse files
committed
[reference-bindings] Add initial prototype of the reference binding transform pass.
1 parent 00d54eb commit f055bdc

File tree

6 files changed

+280
-0
lines changed

6 files changed

+280
-0
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,5 +816,16 @@ WARNING(nontrivial_string_to_rawpointer_conversion,none,
816816
"forming %0 to an inout variable of type String exposes the internal representation "
817817
"rather than the string contents.", (Type))
818818

819+
// MARK: Reference Binding Warnings
820+
ERROR(sil_referencebinding_unknown_pattern, none,
821+
"reference binding that the compiler does not understand how to check. Please file a bug",
822+
())
823+
ERROR(sil_referencebinding_src_used_within_inout_scope, none,
824+
"var bound to inout binding cannot be used within the inout binding's scope",
825+
())
826+
NOTE(sil_referencebinding_inout_binding_here, none,
827+
"inout binding here",
828+
())
829+
819830
#define UNDEFINE_DIAGNOSTIC_MACROS
820831
#include "DefineDiagnosticMacros.h"

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,8 @@ PASS(MoveOnlyBorrowToDestructureTransform,
468468
"used to convert borrow+projection to destructures. Once this has run, the move "
469469
"only object checker runs and ensures that the destructures do not create "
470470
"any move only errors with respect to non-borrow+projection uses")
471+
PASS(ReferenceBindingTransform, "sil-reference-binding-transform",
472+
"Check/transform reference bindings")
471473
PASS(PruneVTables, "prune-vtables",
472474
"Mark class methods that do not require vtable dispatch")
473475
PASS_RANGE(AllPasses, AADumper, PruneVTables)

lib/SILGen/SILGenDecl.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,11 @@ class LocalVariableInitialization : public SingleBufferInitialization {
368368
if (kind)
369369
Box = SGF.B.createMarkUninitialized(decl, Box, kind.value());
370370

371+
// If we have a reference binding, mark it.
372+
if (decl->getIntroducer() == VarDecl::Introducer::InOut)
373+
Box = SGF.B.createMarkUnresolvedReferenceBindingInst(
374+
decl, Box, MarkUnresolvedReferenceBindingInst::Kind::InOut);
375+
371376
if (SGF.getASTContext().SILOpts.supportsLexicalLifetimes(SGF.getModule())) {
372377
auto loweredType = SGF.getTypeLowering(decl->getType()).getLoweredType();
373378
auto lifetime = SGF.F.getLifetime(decl, loweredType);

lib/SILOptimizer/Mandatory/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ target_sources(swiftSILOptimizer PRIVATE
4040
PredictableMemOpt.cpp
4141
PMOMemoryUseCollector.cpp
4242
RawSILInstLowering.cpp
43+
ReferenceBindingTransform.cpp
4344
SILGenCleanup.cpp
4445
YieldOnceCheck.cpp
4546
OSLogOptimization.cpp
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
//===--- ReferenceBindingTransform.cpp ------------------------------------===//
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+
#define DEBUG_TYPE "sil-reference-binding-transform"
14+
15+
#include "swift/AST/DiagnosticsSIL.h"
16+
#include "swift/Basic/Defer.h"
17+
#include "swift/SIL/BasicBlockDatastructures.h"
18+
#include "swift/SIL/FieldSensitivePrunedLiveness.h"
19+
#include "swift/SIL/PrunedLiveness.h"
20+
#include "swift/SIL/SILBuilder.h"
21+
#include "swift/SILOptimizer/PassManager/Transforms.h"
22+
23+
using namespace swift;
24+
25+
static llvm::cl::opt<bool> SilentlyEmitDiagnostics(
26+
"sil-reference-binding-diagnostics-silently-emit-diagnostics",
27+
llvm::cl::desc(
28+
"For testing purposes, emit the diagnostic silently so we can "
29+
"filecheck the result of emitting an error from the move checkers"),
30+
llvm::cl::init(false));
31+
32+
//===----------------------------------------------------------------------===//
33+
// MARK: Diagnosis Helpers
34+
//===----------------------------------------------------------------------===//
35+
36+
template <typename... T, typename... U>
37+
static void diagnose(SILInstruction *inst, Diag<T...> diag, U &&...args) {
38+
// If we asked to actually emit diagnostics, skip it. This lets us when
39+
// testing to write FileCheck tests for tests without an error along side
40+
// other tests where we want to use -verify.
41+
if (SilentlyEmitDiagnostics)
42+
return;
43+
44+
// See if the consuming use is an owned moveonly_to_copyable whose only
45+
// user is a return. In that case, use the return loc instead. We do this
46+
// b/c it is illegal to put a return value location on a non-return value
47+
// instruction... so we have to hack around this slightly.
48+
auto loc = inst->getLoc();
49+
if (auto *mtc = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(inst)) {
50+
if (auto *ri = mtc->getSingleUserOfType<ReturnInst>()) {
51+
loc = ri->getLoc();
52+
}
53+
}
54+
55+
auto &context = inst->getModule().getASTContext();
56+
context.Diags.diagnose(loc.getSourceLoc(), diag, std::forward<U>(args)...);
57+
}
58+
59+
//===----------------------------------------------------------------------===//
60+
// MARK: Gather Address Uses
61+
//===----------------------------------------------------------------------===//
62+
63+
namespace {
64+
65+
struct ValidateAllUsesWithinLiveness : public AccessUseVisitor {
66+
SSAPrunedLiveness &liveness;
67+
SILInstruction *markInst;
68+
SILInstruction *initInst;
69+
bool emittedDiagnostic = false;
70+
71+
ValidateAllUsesWithinLiveness(SSAPrunedLiveness &gatherUsesLiveness,
72+
SILInstruction *markInst,
73+
SILInstruction *initInst)
74+
: AccessUseVisitor(AccessUseType::Overlapping,
75+
NestedAccessType::IgnoreAccessBegin),
76+
liveness(gatherUsesLiveness), markInst(markInst), initInst(initInst) {}
77+
78+
bool visitUse(Operand *op, AccessUseType useTy) override {
79+
auto *user = op->getUser();
80+
81+
// Skip our initInst which is going to be within the lifetime.
82+
if (user == initInst)
83+
return true;
84+
85+
// Skip end_access. We care about the end_access uses.
86+
if (isa<EndAccessInst>(user))
87+
return true;
88+
89+
if (liveness.isWithinBoundary(user)) {
90+
diagnose(op->getUser(),
91+
diag::sil_referencebinding_src_used_within_inout_scope);
92+
diagnose(markInst, diag::sil_referencebinding_inout_binding_here);
93+
emittedDiagnostic = true;
94+
}
95+
96+
return true;
97+
}
98+
};
99+
100+
} // end anonymous namespace
101+
102+
//===----------------------------------------------------------------------===//
103+
// MARK: Transform
104+
//===----------------------------------------------------------------------===//
105+
106+
static bool runTransform(SILFunction *fn) {
107+
bool madeChange = false;
108+
109+
StackList<MarkUnresolvedReferenceBindingInst *> targets(fn);
110+
for (auto &block : *fn) {
111+
for (auto &ii : block) {
112+
if (auto *murbi = dyn_cast<MarkUnresolvedReferenceBindingInst>(&ii))
113+
targets.push_back(murbi);
114+
}
115+
}
116+
117+
bool emittedError = false;
118+
while (!targets.empty()) {
119+
auto *mark = targets.pop_back_val();
120+
SWIFT_DEFER {
121+
mark->replaceAllUsesWith(mark->getOperand());
122+
mark->eraseFromParent();
123+
madeChange = true;
124+
};
125+
126+
// We rely on the following semantics to find our initialization:
127+
//
128+
// 1. SILGen always initializes values completely.
129+
// 2. Boxes always have operations guarded /except/ for their
130+
// initialization.
131+
// 3. We force inout to always be initialized once.
132+
//
133+
// Thus to find our initialization, we need to find a project_box use of our
134+
// target that directly initializes the value.
135+
CopyAddrInst *initInst = nullptr;
136+
for (auto *use : mark->getUses()) {
137+
if (auto *pbi = dyn_cast<ProjectBoxInst>(use->getUser())) {
138+
for (auto *use : pbi->getUses()) {
139+
if (auto *cai = dyn_cast<CopyAddrInst>(use->getUser())) {
140+
if (cai->getDest() == pbi) {
141+
if (initInst || !cai->isInitializationOfDest() ||
142+
cai->isTakeOfSrc()) {
143+
emittedError = true;
144+
diagnose(mark, diag::sil_referencebinding_unknown_pattern);
145+
break;
146+
}
147+
assert(!initInst && "Init twice?!");
148+
assert(!cai->isTakeOfSrc());
149+
initInst = cai;
150+
continue;
151+
}
152+
}
153+
}
154+
155+
if (emittedError)
156+
break;
157+
}
158+
}
159+
160+
if (emittedError)
161+
continue;
162+
assert(initInst && "Failed to find single initialization");
163+
164+
// Ok, we have our initialization. Now gather our destroys of our value and
165+
// initialize an SSAPrunedLiveness, using our initInst as our def.
166+
SmallVector<SILBasicBlock *, 8> discoveredBlocks;
167+
SSAPrunedLiveness liveness(&discoveredBlocks);
168+
StackList<DestroyValueInst *> destroyValueInst(fn);
169+
liveness.initializeDef(mark);
170+
for (auto *consume : mark->getConsumingUses()) {
171+
// Make sure that the destroy_value is not within the boundary.
172+
auto *dai = dyn_cast<DestroyValueInst>(consume->getUser());
173+
if (!dai) {
174+
emittedError = true;
175+
diagnose(mark, diag::sil_referencebinding_unknown_pattern);
176+
break;
177+
}
178+
179+
liveness.updateForUse(dai, true /*is lifetime ending*/);
180+
destroyValueInst.push_back(dai);
181+
}
182+
if (emittedError)
183+
continue;
184+
185+
// Now make sure that our source address doesn't have any uses within the
186+
// lifetime of our box. Or emit an error.
187+
auto accessPathWithBase = AccessPathWithBase::compute(initInst->getSrc());
188+
assert(accessPathWithBase.base);
189+
190+
// Then search up for a single begin_access from src to find our
191+
// begin_access we need to fix. We know that it will always be directly on
192+
// the type since we only allow for inout today to be on decl_ref expr. This
193+
// will change in the future. Once we find that begin_access, we need to
194+
// convert it to a modify and expand it.
195+
auto *bai = cast<BeginAccessInst>(initInst->getSrc());
196+
assert(bai->getAccessKind() == SILAccessKind::Read);
197+
StackList<SILInstruction *> endAccesses(fn);
198+
for (auto *eai : bai->getEndAccesses())
199+
endAccesses.push_back(eai);
200+
201+
{
202+
ValidateAllUsesWithinLiveness initGatherer(liveness, mark, initInst);
203+
if (!visitAccessPathBaseUses(initGatherer, accessPathWithBase, fn)) {
204+
emittedError = true;
205+
diagnose(mark, diag::sil_referencebinding_unknown_pattern);
206+
}
207+
emittedError |= initGatherer.emittedDiagnostic;
208+
}
209+
if (emittedError)
210+
continue;
211+
212+
initInst->setIsTakeOfSrc(IsTake);
213+
bai->setAccessKind(SILAccessKind::Modify);
214+
madeChange = true;
215+
216+
while (!endAccesses.empty())
217+
endAccesses.pop_back_val()->eraseFromParent();
218+
219+
{
220+
while (!destroyValueInst.empty()) {
221+
auto *consume = destroyValueInst.pop_back_val();
222+
SILBuilderWithScope builder(consume);
223+
auto *pbi = builder.createProjectBox(consume->getLoc(), mark, 0);
224+
builder.createCopyAddr(consume->getLoc(), pbi, initInst->getSrc(),
225+
IsTake, IsInitialization);
226+
builder.createEndAccess(consume->getLoc(), bai, false /*aborted*/);
227+
builder.createDeallocBox(consume->getLoc(), mark);
228+
consume->eraseFromParent();
229+
}
230+
}
231+
}
232+
233+
return madeChange;
234+
}
235+
236+
//===----------------------------------------------------------------------===//
237+
// MARK: Top Level Entrypoint
238+
//===----------------------------------------------------------------------===//
239+
240+
namespace {
241+
242+
struct ReferenceBindingTransformPass : SILFunctionTransform {
243+
void run() override {
244+
auto *fn = getFunction();
245+
246+
// Only run this if the reference binding feature is enabled.
247+
if (!fn->getASTContext().LangOpts.hasFeature(Feature::ReferenceBindings))
248+
return;
249+
250+
if (runTransform(fn)) {
251+
invalidateAnalysis(SILAnalysis::Instructions);
252+
}
253+
}
254+
};
255+
256+
} // namespace
257+
258+
SILTransform *swift::createReferenceBindingTransform() {
259+
return new ReferenceBindingTransformPass();
260+
}

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ static void addDefiniteInitialization(SILPassPipelinePlan &P) {
116116
static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
117117
P.startPipeline("Mandatory Diagnostic Passes + Enabling Optimization Passes");
118118
P.addDiagnoseInvalidEscapingCaptures();
119+
P.addReferenceBindingTransform();
119120
P.addDiagnoseStaticExclusivity();
120121
P.addNestedSemanticFunctionCheck();
121122
P.addCapturePromotion();

0 commit comments

Comments
 (0)