Skip to content

Commit 170ba67

Browse files
committed
[move-only] Eliminate some temporary copies inserted by SILGen when accessing fields of lets.
In the next commit, I am modifying the move only checker to ensure that we always error when partially invalidating. While doing this I discovered that these copies were getting in the way of emitting good diagnostics in that case.
1 parent 0bb142f commit 170ba67

File tree

7 files changed

+834
-138
lines changed

7 files changed

+834
-138
lines changed

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ PASS(MoveOnlyAddressChecker, "sil-move-only-address-checker",
448448
"Utility pass that enforces move only invariants on raw SIL for addresses for testing purposes")
449449
PASS(MoveOnlyChecker, "sil-move-only-checker",
450450
"Pass that enforces move only invariants on raw SIL for addresses and objects")
451+
PASS(MoveOnlyTempAllocationFromLetTester, "sil-move-only-temp-allocation-from-let-tester",
452+
"Pass that allows us to run separately SIL test cases for the eliminateTemporaryAllocationsFromLet utility")
451453
PASS(ConsumeOperatorCopyableValuesChecker, "sil-consume-operator-copyable-values-checker",
452454
"Pass that performs checking of the consume operator for copyable values")
453455
PASS(TrivialMoveOnlyTypeEliminator, "sil-trivial-move-only-type-eliminator",

lib/SILOptimizer/Mandatory/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ target_sources(swiftSILOptimizer PRIVATE
3131
MoveOnlyDiagnostics.cpp
3232
MoveOnlyObjectCheckerTester.cpp
3333
MoveOnlyObjectCheckerUtils.cpp
34+
MoveOnlyTempAllocationFromLetTester.cpp
3435
MoveOnlyTypeUtils.cpp
3536
MoveOnlyUtils.cpp
3637
MovedAsyncVarDebugInfoPropagator.cpp

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 30 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -287,108 +287,9 @@ llvm::cl::opt<bool> DisableMoveOnlyAddressCheckerLifetimeExtension(
287287
"move-only values."));
288288

289289
//===----------------------------------------------------------------------===//
290-
// MARK: Memory Utilities
290+
// MARK: Utilities
291291
//===----------------------------------------------------------------------===//
292292

293-
static bool memInstMustInitialize(Operand *memOper) {
294-
SILValue address = memOper->get();
295-
296-
SILInstruction *memInst = memOper->getUser();
297-
298-
switch (memInst->getKind()) {
299-
default:
300-
return false;
301-
302-
case SILInstructionKind::CopyAddrInst: {
303-
auto *CAI = cast<CopyAddrInst>(memInst);
304-
return CAI->getDest() == address && CAI->isInitializationOfDest();
305-
}
306-
case SILInstructionKind::ExplicitCopyAddrInst: {
307-
auto *CAI = cast<ExplicitCopyAddrInst>(memInst);
308-
return CAI->getDest() == address && CAI->isInitializationOfDest();
309-
}
310-
case SILInstructionKind::MarkUnresolvedMoveAddrInst: {
311-
return cast<MarkUnresolvedMoveAddrInst>(memInst)->getDest() == address;
312-
}
313-
case SILInstructionKind::InitExistentialAddrInst:
314-
case SILInstructionKind::InitEnumDataAddrInst:
315-
case SILInstructionKind::InjectEnumAddrInst:
316-
return true;
317-
318-
case SILInstructionKind::BeginApplyInst:
319-
case SILInstructionKind::TryApplyInst:
320-
case SILInstructionKind::ApplyInst: {
321-
FullApplySite applySite(memInst);
322-
return applySite.isIndirectResultOperand(*memOper);
323-
}
324-
case SILInstructionKind::StoreInst: {
325-
auto qual = cast<StoreInst>(memInst)->getOwnershipQualifier();
326-
return qual == StoreOwnershipQualifier::Init ||
327-
qual == StoreOwnershipQualifier::Trivial;
328-
}
329-
330-
#define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
331-
case SILInstructionKind::Store##Name##Inst: \
332-
return cast<Store##Name##Inst>(memInst)->isInitializationOfDest();
333-
#include "swift/AST/ReferenceStorage.def"
334-
}
335-
}
336-
337-
static bool memInstMustReinitialize(Operand *memOper) {
338-
SILValue address = memOper->get();
339-
340-
SILInstruction *memInst = memOper->getUser();
341-
342-
switch (memInst->getKind()) {
343-
default:
344-
return false;
345-
346-
case SILInstructionKind::CopyAddrInst: {
347-
auto *CAI = cast<CopyAddrInst>(memInst);
348-
return CAI->getDest() == address && !CAI->isInitializationOfDest();
349-
}
350-
case SILInstructionKind::ExplicitCopyAddrInst: {
351-
auto *CAI = cast<ExplicitCopyAddrInst>(memInst);
352-
return CAI->getDest() == address && !CAI->isInitializationOfDest();
353-
}
354-
case SILInstructionKind::YieldInst: {
355-
auto *yield = cast<YieldInst>(memInst);
356-
return yield->getYieldInfoForOperand(*memOper).isIndirectInOut();
357-
}
358-
case SILInstructionKind::BeginApplyInst:
359-
case SILInstructionKind::TryApplyInst:
360-
case SILInstructionKind::ApplyInst: {
361-
FullApplySite applySite(memInst);
362-
return applySite.getArgumentOperandConvention(*memOper).isInoutConvention();
363-
}
364-
case SILInstructionKind::StoreInst:
365-
return cast<StoreInst>(memInst)->getOwnershipQualifier() ==
366-
StoreOwnershipQualifier::Assign;
367-
368-
#define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
369-
case SILInstructionKind::Store##Name##Inst: \
370-
return !cast<Store##Name##Inst>(memInst)->isInitializationOfDest();
371-
#include "swift/AST/ReferenceStorage.def"
372-
}
373-
}
374-
375-
/// Is this a reinit instruction that we know how to convert into its init form.
376-
static bool isReinitToInitConvertibleInst(SILInstruction *memInst) {
377-
switch (memInst->getKind()) {
378-
default:
379-
return false;
380-
381-
case SILInstructionKind::CopyAddrInst: {
382-
auto *cai = cast<CopyAddrInst>(memInst);
383-
return !cai->isInitializationOfDest();
384-
}
385-
case SILInstructionKind::StoreInst: {
386-
auto *si = cast<StoreInst>(memInst);
387-
return si->getOwnershipQualifier() == StoreOwnershipQualifier::Assign;
388-
}
389-
}
390-
}
391-
392293
static void insertDebugValueBefore(SILInstruction *insertPt,
393294
DebugVarCarryingInst debugVar,
394295
llvm::function_ref<SILValue ()> operand) {
@@ -432,47 +333,20 @@ static void convertMemoryReinitToInitForm(SILInstruction *memInst,
432333
[&]{ return debugVar.getOperandForDebugValueClone(); });
433334
}
434335

435-
static bool memInstMustConsume(Operand *memOper) {
436-
SILValue address = memOper->get();
437-
438-
SILInstruction *memInst = memOper->getUser();
439-
440-
// FIXME: drop_deinit must be handled here!
336+
/// Is this a reinit instruction that we know how to convert into its init form.
337+
static bool isReinitToInitConvertibleInst(SILInstruction *memInst) {
441338
switch (memInst->getKind()) {
442339
default:
443340
return false;
444341

445342
case SILInstructionKind::CopyAddrInst: {
446-
auto *CAI = cast<CopyAddrInst>(memInst);
447-
return (CAI->getSrc() == address && CAI->isTakeOfSrc()) ||
448-
(CAI->getDest() == address && !CAI->isInitializationOfDest());
449-
}
450-
case SILInstructionKind::ExplicitCopyAddrInst: {
451-
auto *CAI = cast<ExplicitCopyAddrInst>(memInst);
452-
return (CAI->getSrc() == address && CAI->isTakeOfSrc()) ||
453-
(CAI->getDest() == address && !CAI->isInitializationOfDest());
454-
}
455-
case SILInstructionKind::BeginApplyInst:
456-
case SILInstructionKind::TryApplyInst:
457-
case SILInstructionKind::ApplyInst: {
458-
FullApplySite applySite(memInst);
459-
return applySite.getArgumentOperandConvention(*memOper).isOwnedConvention();
460-
}
461-
case SILInstructionKind::PartialApplyInst: {
462-
// If we are on the stack or have an inout convention, we do not
463-
// consume. Otherwise, we do.
464-
auto *pai = cast<PartialApplyInst>(memInst);
465-
if (pai->isOnStack())
466-
return false;
467-
ApplySite applySite(pai);
468-
auto convention = applySite.getArgumentConvention(*memOper);
469-
return !convention.isInoutConvention();
343+
auto *cai = cast<CopyAddrInst>(memInst);
344+
return !cai->isInitializationOfDest();
345+
}
346+
case SILInstructionKind::StoreInst: {
347+
auto *si = cast<StoreInst>(memInst);
348+
return si->getOwnershipQualifier() == StoreOwnershipQualifier::Assign;
470349
}
471-
case SILInstructionKind::DestroyAddrInst:
472-
return true;
473-
case SILInstructionKind::LoadInst:
474-
return cast<LoadInst>(memInst)->getOwnershipQualifier() ==
475-
LoadOwnershipQualifier::Take;
476350
}
477351
}
478352

@@ -1725,7 +1599,7 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
17251599
LLVM_DEBUG(llvm::dbgs() << "Visiting user: " << *user;);
17261600

17271601
// First check if we have init/reinit. These are quick/simple.
1728-
if (::memInstMustInitialize(op)) {
1602+
if (noncopyable::memInstMustInitialize(op)) {
17291603
LLVM_DEBUG(llvm::dbgs() << "Found init: " << *user);
17301604

17311605
// TODO: What about copy_addr of itself. We really should just pre-process
@@ -1739,7 +1613,7 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
17391613
return true;
17401614
}
17411615

1742-
if (::memInstMustReinitialize(op)) {
1616+
if (noncopyable::memInstMustReinitialize(op)) {
17431617
LLVM_DEBUG(llvm::dbgs() << "Found reinit: " << *user);
17441618
assert(!useState.reinitInsts.count(user));
17451619
auto leafRange = TypeTreeLeafTypeRange::get(op->get(), getRootAddress());
@@ -2024,7 +1898,7 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
20241898

20251899
// Now that we have handled or loadTakeOrCopy, we need to now track our
20261900
// additional pure takes.
2027-
if (::memInstMustConsume(op)) {
1901+
if (noncopyable::memInstMustConsume(op)) {
20281902
// If we don't have a consumeable and assignable check kind, then we can't
20291903
// consume. Emit an error.
20301904
//
@@ -3188,6 +3062,24 @@ bool MoveOnlyAddressCheckerPImpl::performSingleCheck(
31883062
state.process();
31893063
}
31903064

3065+
// Then if we have a let allocation, see if we have any copy_addr on our
3066+
// markedAddress that form temporary allocation chains. This occurs when we
3067+
// emit SIL for code like:
3068+
//
3069+
// let x: AddressOnlyType = ...
3070+
// let _ = x.y.z
3071+
//
3072+
// SILGen will treat y as a separate rvalue from x and will create a temporary
3073+
// allocation. In contrast if we have a var, we treat x like an lvalue and
3074+
// just create GEPs appropriately.
3075+
if (eliminateTemporaryAllocationsFromLet(markedAddress)) {
3076+
LLVM_DEBUG(
3077+
llvm::dbgs()
3078+
<< "Succeeded in eliminating temporary allocations! Fn after:\n";
3079+
markedAddress->getFunction()->dump());
3080+
changed = true;
3081+
}
3082+
31913083
// Then gather all uses of our address by walking from def->uses. We use this
31923084
// to categorize the uses of this address into their ownership behavior (e.g.:
31933085
// init, reinit, take, destroy, etc.).
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//===--- MoveOnlyTempAllocationFromLetTester.cpp --------------------------===//
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+
/// \file A simple tester for the utility function
14+
/// eliminateTemporaryAllocationsFromLet that allows us to write separate SIL
15+
/// test cases for the utility.
16+
///
17+
//===----------------------------------------------------------------------===//
18+
19+
#define DEBUG_TYPE "sil-move-only-checker"
20+
21+
#include "MoveOnlyAddressCheckerUtils.h"
22+
#include "MoveOnlyDiagnostics.h"
23+
#include "MoveOnlyUtils.h"
24+
25+
#include "swift/SILOptimizer/PassManager/Passes.h"
26+
#include "swift/SILOptimizer/PassManager/Transforms.h"
27+
28+
using namespace swift;
29+
using namespace swift::siloptimizer;
30+
31+
namespace {
32+
33+
struct MoveOnlyTempAllocationFromLetTester : SILFunctionTransform {
34+
void run() override {
35+
auto *fn = getFunction();
36+
37+
// Only run this pass if the move only language feature is enabled.
38+
if (!fn->getASTContext().supportsMoveOnlyTypes())
39+
return;
40+
41+
// Don't rerun diagnostics on deserialized functions.
42+
if (getFunction()->wasDeserializedCanonical())
43+
return;
44+
45+
assert(fn->getModule().getStage() == SILStage::Raw &&
46+
"Should only run on Raw SIL");
47+
48+
LLVM_DEBUG(llvm::dbgs()
49+
<< "===> MoveOnlyTempAllocationFromLetTester. Visiting: "
50+
<< fn->getName() << '\n');
51+
52+
SmallSetVector<MarkMustCheckInst *, 32> moveIntroducersToProcess;
53+
DiagnosticEmitter diagnosticEmitter(getFunction());
54+
55+
unsigned diagCount = diagnosticEmitter.getDiagnosticCount();
56+
searchForCandidateAddressMarkMustChecks(
57+
getFunction(), moveIntroducersToProcess, diagnosticEmitter);
58+
59+
// Return early if we emitted a diagnostic.
60+
if (diagCount != diagnosticEmitter.getDiagnosticCount())
61+
return;
62+
63+
bool madeChange = false;
64+
while (!moveIntroducersToProcess.empty()) {
65+
auto *next = moveIntroducersToProcess.pop_back_val();
66+
madeChange |= eliminateTemporaryAllocationsFromLet(next);
67+
}
68+
69+
if (madeChange)
70+
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
71+
}
72+
};
73+
74+
} // namespace
75+
76+
SILTransform *swift::createMoveOnlyTempAllocationFromLetTester() {
77+
return new MoveOnlyTempAllocationFromLetTester();
78+
}

0 commit comments

Comments
 (0)