Skip to content

Commit 3d670f7

Browse files
authored
Merge pull request swiftlang#35018 from gottesmm/pr-e08ad4b2552526ca2dc2718f49e8f6db35144c8c
[ownership][canonicalize] Canonicalize load_borrow the same way we canonicalize load.
2 parents 446c327 + 8a6f6cb commit 3d670f7

File tree

2 files changed

+177
-59
lines changed

2 files changed

+177
-59
lines changed

lib/SILOptimizer/Utils/CanonicalizeInstruction.cpp

Lines changed: 144 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "swift/SIL/Projection.h"
2525
#include "swift/SIL/SILBuilder.h"
2626
#include "swift/SIL/SILFunction.h"
27+
#include "swift/SIL/SILInstruction.h"
2728
#include "swift/SILOptimizer/Analysis/SimplifyInstruction.h"
2829
#include "llvm/ADT/Statistic.h"
2930
#include "llvm/Support/Debug.h"
@@ -110,6 +111,54 @@ simplifyAndReplace(SILInstruction *inst, CanonicalizeInstruction &pass) {
110111
// Canonicalize Memory Operations
111112
//===----------------------------------------------------------------------===//
112113

114+
namespace {
115+
116+
struct LoadOperation {
117+
llvm::PointerUnion<LoadInst *, LoadBorrowInst *> value;
118+
119+
LoadOperation(SILInstruction *input) : value(nullptr) {
120+
if (auto *li = dyn_cast<LoadInst>(input)) {
121+
value = li;
122+
return;
123+
}
124+
125+
if (auto *lbi = dyn_cast<LoadBorrowInst>(input)) {
126+
value = lbi;
127+
return;
128+
}
129+
}
130+
131+
operator bool() const { return !value.isNull(); }
132+
133+
SingleValueInstruction *operator*() const {
134+
if (value.is<LoadInst *>())
135+
return value.get<LoadInst *>();
136+
return value.get<LoadBorrowInst *>();
137+
}
138+
139+
const SingleValueInstruction *operator->() const {
140+
if (value.is<LoadInst *>())
141+
return value.get<LoadInst *>();
142+
return value.get<LoadBorrowInst *>();
143+
}
144+
145+
SingleValueInstruction *operator->() {
146+
if (value.is<LoadInst *>())
147+
return value.get<LoadInst *>();
148+
return value.get<LoadBorrowInst *>();
149+
}
150+
151+
Optional<LoadOwnershipQualifier> getOwnershipQualifier() const {
152+
if (auto *lbi = value.dyn_cast<LoadBorrowInst *>()) {
153+
return None;
154+
}
155+
156+
return value.get<LoadInst *>()->getOwnershipQualifier();
157+
}
158+
};
159+
160+
} // anonymous namespace
161+
113162
// Replace all uses of an original struct or tuple extract instruction with the
114163
// given load instruction. The caller ensures that the load only loads the
115164
// extracted field.
@@ -120,27 +169,29 @@ simplifyAndReplace(SILInstruction *inst, CanonicalizeInstruction &pass) {
120169
// \p loadInst has the form:
121170
// (load (struct_element_addr %base, #field)
122171
static void replaceUsesOfExtract(SingleValueInstruction *extract,
123-
LoadInst *loadInst,
172+
LoadOperation loadInst,
124173
CanonicalizeInstruction &pass) {
125174
assert(extract->getType() == loadInst->getType());
126175

127-
SingleValueInstruction *loadedVal = loadInst;
128-
if (loadInst->getOwnershipQualifier() == LoadOwnershipQualifier::Copy) {
129-
// Borrow the load-copied subelement, with precisely the same scope as
130-
// the aggregate borrow.
131-
assert(extract->getNumOperands() == 1);
132-
auto *origBorrow = cast<BeginBorrowInst>(extract->getOperand(0));
133-
auto *newBorrow = SILBuilderWithScope(origBorrow)
134-
.createBeginBorrow(loadInst->getLoc(), loadInst);
135-
pass.notifyNewInstruction(newBorrow);
136-
137-
assert(extract == origBorrow->getSingleNonEndingUse()->getUser());
138-
for (auto *origEnd : origBorrow->getEndBorrows()) {
139-
auto *endBorrow = SILBuilderWithScope(origEnd).createEndBorrow(
140-
origEnd->getLoc(), newBorrow);
141-
pass.notifyNewInstruction(endBorrow);
176+
SingleValueInstruction *loadedVal = *loadInst;
177+
if (auto qual = loadInst.getOwnershipQualifier()) {
178+
if (*qual == LoadOwnershipQualifier::Copy) {
179+
// Borrow the load-copied subelement, with precisely the same scope as
180+
// the aggregate borrow.
181+
assert(extract->getNumOperands() == 1);
182+
auto *origBorrow = cast<BeginBorrowInst>(extract->getOperand(0));
183+
auto *newBorrow = SILBuilderWithScope(origBorrow)
184+
.createBeginBorrow(loadInst->getLoc(), *loadInst);
185+
pass.notifyNewInstruction(newBorrow);
186+
187+
assert(extract == origBorrow->getSingleNonEndingUse()->getUser());
188+
for (auto *origEnd : origBorrow->getEndBorrows()) {
189+
auto *endBorrow = SILBuilderWithScope(origEnd).createEndBorrow(
190+
origEnd->getLoc(), newBorrow);
191+
pass.notifyNewInstruction(endBorrow);
192+
}
193+
loadedVal = newBorrow;
142194
}
143-
loadedVal = newBorrow;
144195
}
145196
LLVM_DEBUG(llvm::dbgs() << "Replacing " << *extract << " with "
146197
<< *loadedVal << "\n");
@@ -156,28 +207,34 @@ static void replaceUsesOfExtract(SingleValueInstruction *extract,
156207
//
157208
// TODO: Consider handling LoadBorrowInst.
158209
static SILBasicBlock::iterator
159-
splitAggregateLoad(LoadInst *loadInst, CanonicalizeInstruction &pass) {
210+
splitAggregateLoad(LoadOperation loadInst, CanonicalizeInstruction &pass) {
160211
// Keep track of the next iterator after any newly added or to-be-deleted
161212
// instructions. This must be valid regardless of whether the pass immediately
162213
// deletes the instructions or simply records them for later deletion.
163214
auto nextII = std::next(loadInst->getIterator());
164215

165216
bool needsBorrow;
166-
switch (loadInst->getOwnershipQualifier()) {
167-
case LoadOwnershipQualifier::Unqualified:
168-
case LoadOwnershipQualifier::Trivial:
217+
if (auto qual = loadInst.getOwnershipQualifier()) {
218+
switch (*qual) {
219+
case LoadOwnershipQualifier::Unqualified:
220+
case LoadOwnershipQualifier::Trivial:
221+
needsBorrow = false;
222+
break;
223+
case LoadOwnershipQualifier::Copy:
224+
needsBorrow = true;
225+
break;
226+
case LoadOwnershipQualifier::Take:
227+
// TODO: To handle a "take", we would need to generate additional destroys
228+
// for any fields that aren't already extracted. This would be
229+
// out-of-place for this transform, and I'm not sure if this a case that
230+
// needs to be handled in CanonicalizeInstruction.
231+
return nextII;
232+
}
233+
} else {
234+
// If we don't have a qual, we have a borrow.
169235
needsBorrow = false;
170-
break;
171-
case LoadOwnershipQualifier::Copy:
172-
needsBorrow = true;
173-
break;
174-
case LoadOwnershipQualifier::Take:
175-
// TODO: To handle a "take", we would need to generate additional destroys
176-
// for any fields that aren't already extracted. This would be out-of-place
177-
// for this transform, and I'm not sure if this a case that needs to be
178-
// handled in SILGenCleanup.
179-
return nextII;
180236
}
237+
181238
struct ProjInstPair {
182239
Projection proj;
183240
SingleValueInstruction *extract;
@@ -191,12 +248,12 @@ splitAggregateLoad(LoadInst *loadInst, CanonicalizeInstruction &pass) {
191248
// Add load projections to a projection list.
192249
llvm::SmallVector<ProjInstPair, 8> projections;
193250
llvm::SmallVector<BeginBorrowInst *, 8> borrows;
194-
llvm::SmallVector<DestroyValueInst *, 8> destroys;
195-
for (auto *use : getNonDebugUses(loadInst)) {
251+
llvm::SmallVector<SILInstruction *, 8> lifetimeEndingInsts;
252+
for (auto *use : getNonDebugUses(*loadInst)) {
196253
auto *user = use->getUser();
197254
if (needsBorrow) {
198255
if (auto *destroy = dyn_cast<DestroyValueInst>(user)) {
199-
destroys.push_back(destroy);
256+
lifetimeEndingInsts.push_back(destroy);
200257
continue;
201258
}
202259
auto *borrow = dyn_cast<BeginBorrowInst>(user);
@@ -210,7 +267,14 @@ splitAggregateLoad(LoadInst *loadInst, CanonicalizeInstruction &pass) {
210267

211268
borrows.push_back(borrow);
212269
user = borrowedOper->getUser();
270+
} else {
271+
if (isa<EndBorrowInst>(user) &&
272+
!loadInst.getOwnershipQualifier().hasValue()) {
273+
lifetimeEndingInsts.push_back(user);
274+
continue;
275+
}
213276
}
277+
214278
// If we have any non SEI, TEI instruction, don't do anything here.
215279
if (!isa<StructExtractInst>(user) && !isa<TupleExtractInst>(user))
216280
return nextII;
@@ -252,61 +316,80 @@ splitAggregateLoad(LoadInst *loadInst, CanonicalizeInstruction &pass) {
252316
// Create a new address projection instruction and load instruction for each
253317
// unique projection.
254318
Projection *lastProj = nullptr;
255-
LoadInst *lastNewLoad = nullptr;
319+
Optional<LoadOperation> lastNewLoad;
256320
for (auto &pair : projections) {
257321
auto &proj = pair.proj;
258322
auto *extract = pair.extract;
259323

260324
// If this projection is the same as the last projection we processed, just
261325
// replace all uses of the projection with the load we created previously.
262326
if (lastProj && proj == *lastProj) {
263-
replaceUsesOfExtract(extract, lastNewLoad, pass);
327+
replaceUsesOfExtract(extract, *lastNewLoad, pass);
264328
nextII = killInstruction(extract, nextII, pass);
265329
continue;
266330
}
267331

268332
// This is a unique projection. Create the new address projection and load.
269333
lastProj = &proj;
270334
// Insert new instructions before the original load.
271-
SILBuilderWithScope LoadBuilder(loadInst);
335+
SILBuilderWithScope LoadBuilder(*loadInst);
272336
auto *projInst =
273337
proj.createAddressProjection(LoadBuilder, loadInst->getLoc(),
274-
loadInst->getOperand())
338+
loadInst->getOperand(0))
275339
.get();
276340
pass.notifyNewInstruction(projInst);
277341

278342
// When loading a trivial subelement, convert ownership.
279-
LoadOwnershipQualifier loadOwnership = loadInst->getOwnershipQualifier();
280-
if (loadOwnership != LoadOwnershipQualifier::Unqualified
281-
&& projInst->getType().isTrivial(*projInst->getFunction())) {
282-
loadOwnership = LoadOwnershipQualifier::Trivial;
343+
Optional<LoadOwnershipQualifier> loadOwnership =
344+
loadInst.getOwnershipQualifier();
345+
if (loadOwnership.hasValue()) {
346+
if (*loadOwnership != LoadOwnershipQualifier::Unqualified &&
347+
projInst->getType().isTrivial(*projInst->getFunction()))
348+
loadOwnership = LoadOwnershipQualifier::Trivial;
349+
} else {
350+
if (projInst->getType().isTrivial(*projInst->getFunction()))
351+
loadOwnership = LoadOwnershipQualifier::Trivial;
283352
}
284353

285-
lastNewLoad =
286-
LoadBuilder.createLoad(loadInst->getLoc(), projInst, loadOwnership);
287-
pass.notifyNewInstruction(lastNewLoad);
288-
289-
if (loadOwnership == LoadOwnershipQualifier::Copy) {
290-
// Destroy the loaded value wherever the aggregate load was destroyed.
291-
assert(loadInst->getOwnershipQualifier() == LoadOwnershipQualifier::Copy);
292-
for (DestroyValueInst *destroy : destroys) {
293-
SILBuilderWithScope(destroy).createDestroyValue(destroy->getLoc(),
294-
lastNewLoad);
295-
pass.notifyNewInstruction(destroy);
354+
if (loadOwnership) {
355+
lastNewLoad =
356+
LoadBuilder.createLoad(loadInst->getLoc(), projInst, *loadOwnership);
357+
} else {
358+
lastNewLoad = LoadBuilder.createLoadBorrow(loadInst->getLoc(), projInst);
359+
}
360+
pass.notifyNewInstruction(**lastNewLoad);
361+
362+
if (loadOwnership) {
363+
if (*loadOwnership == LoadOwnershipQualifier::Copy) {
364+
// Destroy the loaded value wherever the aggregate load was destroyed.
365+
assert(loadInst.getOwnershipQualifier() ==
366+
LoadOwnershipQualifier::Copy);
367+
for (SILInstruction *destroy : lifetimeEndingInsts) {
368+
auto *newInst = SILBuilderWithScope(destroy).createDestroyValue(
369+
destroy->getLoc(), **lastNewLoad);
370+
pass.notifyNewInstruction(newInst);
371+
}
372+
}
373+
} else {
374+
for (SILInstruction *destroy : lifetimeEndingInsts) {
375+
auto *newInst = SILBuilderWithScope(destroy).createEndBorrow(
376+
destroy->getLoc(), **lastNewLoad);
377+
pass.notifyNewInstruction(newInst);
296378
}
297379
}
298-
replaceUsesOfExtract(extract, lastNewLoad, pass);
380+
replaceUsesOfExtract(extract, *lastNewLoad, pass);
299381
nextII = killInstruction(extract, nextII, pass);
300382
}
383+
301384
// Remove the now unused borrows.
302385
for (auto *borrow : borrows)
303386
nextII = killInstAndIncidentalUses(borrow, nextII, pass);
304387

305388
// Erase the old load.
306-
for (auto *destroy : destroys)
389+
for (auto *destroy : lifetimeEndingInsts)
307390
nextII = killInstruction(destroy, nextII, pass);
308391

309-
return killInstAndIncidentalUses(loadInst, nextII, pass);
392+
return killInstAndIncidentalUses(*loadInst, nextII, pass);
310393
}
311394

312395
// Given a store within a single property struct, recursively form the parent
@@ -451,11 +534,13 @@ CanonicalizeInstruction::canonicalize(SILInstruction *inst) {
451534
if (auto nextII = simplifyAndReplace(inst, *this))
452535
return nextII.getValue();
453536

454-
if (auto *loadInst = dyn_cast<LoadInst>(inst))
455-
return splitAggregateLoad(loadInst, *this);
537+
if (auto li = LoadOperation(inst)) {
538+
return splitAggregateLoad(li, *this);
539+
}
456540

457-
if (auto *storeInst = dyn_cast<StoreInst>(inst))
541+
if (auto *storeInst = dyn_cast<StoreInst>(inst)) {
458542
return broadenSingleElementStores(storeInst, *this);
543+
}
459544

460545
if (auto *cvi = dyn_cast<CopyValueInst>(inst))
461546
return eliminateSimpleCopies(cvi, *this);

test/SILOptimizer/silgen_cleanup.sil

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,39 @@ bb0(%0 : $*X1):
155155
return %result : $(Int, Builtin.NativeObject, Builtin.NativeObject)
156156
}
157157

158+
// CHECK-LABEL: sil private [ossa] @testLoadBorrowNontrivial : $@convention(thin) (@in_guaranteed X1) -> (Int, @owned Builtin.NativeObject, @owned Builtin.NativeObject) {
159+
// CHECK: bb0([[ADDRESS:%.*]] : $*X1):
160+
// CHECK: [[AA:%.*]] = struct_element_addr [[ADDRESS]] : $*X1, #X1.a
161+
// CHECK: load [trivial] [[AA]] : $*Int
162+
// CHECK: [[OA1:%.*]] = struct_element_addr [[ADDRESS]] : $*X1, #X1.obj1
163+
// CHECK: [[OV1:%.*]] = load_borrow [[OA1]] : $*Builtin.NativeObject
164+
// CHECK: [[OA2:%.*]] = struct_element_addr [[ADDRESS]] : $*X1, #X1.obj2
165+
// CHECK: [[OV2:%.*]] = load_borrow [[OA2]] : $*Builtin.NativeObject
166+
// CHECK: copy_value [[OV1]] : $Builtin.NativeObject
167+
// CHECK: copy_value [[OV2]] : $Builtin.NativeObject
168+
// CHECK: end_borrow [[OV1]]
169+
// CHECK: end_borrow [[OV2]]
170+
// CHECK: return
171+
// CHECK-LABEL: } // end sil function 'testLoadBorrowNontrivial'
172+
sil private [ossa] @testLoadBorrowNontrivial : $@convention(thin) (@in_guaranteed X1) -> (Int, @owned Builtin.NativeObject, @owned Builtin.NativeObject) {
173+
bb0(%0 : $*X1):
174+
%load = load_borrow %0 : $*X1
175+
176+
%a = struct_extract %load : $X1, #X1.a
177+
178+
%o1 = struct_extract %load : $X1, #X1.obj1
179+
%copy1 = copy_value %o1 : $Builtin.NativeObject
180+
181+
%o2 = struct_extract %load : $X1, #X1.obj2
182+
%copy2 = copy_value %o2 : $Builtin.NativeObject
183+
184+
end_borrow %load : $X1
185+
186+
%result = tuple (%a : $Int, %copy1 : $Builtin.NativeObject, %copy2 : $Builtin.NativeObject)
187+
return %result : $(Int, Builtin.NativeObject, Builtin.NativeObject)
188+
}
189+
190+
158191
struct X2 {
159192
@_hasStorage @_hasInitialValue var obj: Builtin.NativeObject { get set }
160193
}

0 commit comments

Comments
 (0)