Skip to content

[5.9🍒] Add the drop_deinit instruction & consume-on-all-paths checking for discard #65449

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions SwiftCompilerSources/Sources/SIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,10 @@ final public class MoveValueInst : SingleValueInstruction, UnaryInstruction {
public var fromValue: Value { operand.value }
}

final public class DropDeinitInst : SingleValueInstruction, UnaryInstruction {
public var fromValue: Value { operand.value }
}

final public class StrongCopyUnownedValueInst : SingleValueInstruction, UnaryInstruction {}

final public class StrongCopyUnmanagedValueInst : SingleValueInstruction, UnaryInstruction {}
Expand Down
1 change: 1 addition & 0 deletions SwiftCompilerSources/Sources/SIL/Registration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public func registerSILClasses() {
register(ProjectBoxInst.self)
register(CopyValueInst.self)
register(MoveValueInst.self)
register(DropDeinitInst.self)
register(EndCOWMutationInst.self)
register(ClassifyBridgeObjectInst.self)
register(PartialApplyInst.self)
Expand Down
23 changes: 23 additions & 0 deletions docs/SIL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6148,6 +6148,29 @@ a type `T` into the move only value space.
The ``lexical`` attribute specifies that the value corresponds to a local
variable in the Swift source.


drop_deinit
```````````

::

sil-instruction ::= 'drop_deinit' sil-operand

%1 = drop_deinit %0 : $T
// T must be a move-only type
// %1 is an @owned T
%3 = drop_deinit %2 : $*T
// T must be a move-only type
// %2 has type *T

This instruction is a marker for a following destroy instruction to suppress
the call of the move-only type's deinitializer.
The instruction accepts an object or address type.
If its argument is an object type it takes in an `@owned T` and produces a new
`@owned T`. If its argument is an address type, it's an identity projection.

The instruction is only valid in ownership SIL.

release_value
`````````````

Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSIL.def
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,11 @@ ERROR(sil_movechecking_notconsumable_but_assignable_was_consumed, none,
ERROR(sil_movechecking_cannot_destructure_has_deinit, none,
"cannot partially consume '%0' when it has a deinitializer",
(StringRef))
ERROR(sil_movechecking_discard_missing_consume_self, none,
"must consume 'self' before exiting method that discards self", ())

NOTE(sil_movechecking_discard_self_here, none,
"discarded self here", ())
NOTE(sil_movechecking_partial_consume_here, none,
"partially consumed here", ())
NOTE(sil_movechecking_consuming_use_here, none,
Expand Down
1 change: 1 addition & 0 deletions include/swift/SIL/MemAccessUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -1632,6 +1632,7 @@ inline bool isAccessStorageIdentityCast(SingleValueInstruction *svi) {
// Simply pass-thru the incoming address.
case SILInstructionKind::MarkUninitializedInst:
case SILInstructionKind::MarkMustCheckInst:
case SILInstructionKind::DropDeinitInst:
case SILInstructionKind::MarkUnresolvedReferenceBindingInst:
case SILInstructionKind::MarkDependenceInst:
case SILInstructionKind::CopyValueInst:
Expand Down
8 changes: 8 additions & 0 deletions include/swift/SIL/SILBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,14 @@ class SILBuilder {
operand, isLexical));
}

DropDeinitInst *createDropDeinit(SILLocation loc, SILValue operand) {
assert(getFunction().hasOwnership());
assert(!operand->getType().isTrivial(getFunction()) &&
"Should not be passing trivial values to this api.");
return insert(new (getModule()) DropDeinitInst(getSILDebugLocation(loc),
operand));
}

MarkUnresolvedMoveAddrInst *createMarkUnresolvedMoveAddr(SILLocation loc,
SILValue srcAddr,
SILValue takeAddr) {
Expand Down
11 changes: 11 additions & 0 deletions include/swift/SIL/SILCloner.h
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,17 @@ void SILCloner<ImplClass>::visitMoveValueInst(MoveValueInst *Inst) {
recordClonedInstruction(Inst, MVI);
}

template <typename ImplClass>
void SILCloner<ImplClass>::visitDropDeinitInst(DropDeinitInst *Inst) {
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
if (!getBuilder().hasOwnership()) {
return recordFoldedValue(Inst, getOpValue(Inst->getOperand()));
}
auto *MVI = getBuilder().createDropDeinit(getOpLocation(Inst->getLoc()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you use git-clang-format here? I am pretty sure this isn't formatted correctly.

Copy link
Member Author

@kavon kavon Jun 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What in particular about the formatting is bad? It's even identical to visitMoveValueInst just above, so I don't see a problem here.

Copy link
Contributor

@gottesmm gottesmm Jun 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you always please use git-clang-format. That way we all use consistent formatting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like everyone uses git-clang-format, since Erik wrote this bit of code that I'm cherry-picking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I am going to let this go for now since you are cherry-picking. But I think as a team/project we need to have a larger conversation around this. Generally it is considered to be a good citizen thing to do: https://forums.swift.org/t/using-git-clang-format-in-the-swift-compiler-code-base/4996.

getOpValue(Inst->getOperand()));
recordClonedInstruction(Inst, MVI);
}

template <typename ImplClass>
void SILCloner<ImplClass>::visitMarkMustCheckInst(MarkMustCheckInst *Inst) {
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
Expand Down
9 changes: 9 additions & 0 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -8301,6 +8301,15 @@ class MoveValueInst
void removeIsLexical() { lexical = false; }
};

class DropDeinitInst
: public UnaryInstructionBase<SILInstructionKind::DropDeinitInst,
SingleValueInstruction> {
friend class SILBuilder;

DropDeinitInst(SILDebugLocation DebugLoc, SILValue operand)
: UnaryInstructionBase(DebugLoc, operand, operand->getType()) {}
};

/// Equivalent to a copy_addr to [init] except that it is used for diagnostics
/// and should not be pattern matched. During the diagnostic passes, the "move
/// function" checker for addresses always converts this to a copy_addr [init]
Expand Down
2 changes: 2 additions & 0 deletions include/swift/SIL/SILNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,8 @@ ABSTRACT_VALUE_AND_INST(SingleValueInstruction, ValueBase, SILInstruction)
// effects relative to other OSSA values like copy_value.
SINGLE_VALUE_INST(MoveValueInst, move_value, SingleValueInstruction, None,
DoesNotRelease)
SINGLE_VALUE_INST(DropDeinitInst, drop_deinit, SingleValueInstruction, None,
DoesNotRelease)
// A canary value inserted by a SIL generating frontend to signal to the move
// checker to check a specific value. Valid only in Raw SIL. The relevant
// checkers should remove the mark_must_check instruction after successfully
Expand Down
4 changes: 4 additions & 0 deletions lib/IRGen/IRGenSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,10 @@ class IRGenSILFunction :
auto e = getLoweredExplosion(i->getOperand());
setLoweredExplosion(i, e);
}
void visitDropDeinitInst(DropDeinitInst *i) {
auto e = getLoweredExplosion(i->getOperand());
setLoweredExplosion(i, e);
}
void visitMarkMustCheckInst(MarkMustCheckInst *i) {
llvm_unreachable("Invalid in Lowered SIL");
}
Expand Down
6 changes: 6 additions & 0 deletions lib/SIL/IR/OperandOwnership.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,12 @@ OperandOwnershipClassifier::visitStoreBorrowInst(StoreBorrowInst *i) {
return OperandOwnership::TrivialUse;
}

OperandOwnership
OperandOwnershipClassifier::visitDropDeinitInst(DropDeinitInst *i) {
return i->getType().isAddress() ? OperandOwnership::TrivialUse
: OperandOwnership::ForwardingConsume;
}

// Get the OperandOwnership for instantaneous apply, yield, and return uses.
// This does not apply to uses that begin an explicit borrow scope in the
// caller, such as begin_apply.
Expand Down
4 changes: 4 additions & 0 deletions lib/SIL/IR/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1999,6 +1999,10 @@ class SILPrinter : public SILInstructionVisitor<SILPrinter> {
*this << getIDAndType(I->getOperand());
}

void visitDropDeinitInst(DropDeinitInst *I) {
*this << getIDAndType(I->getOperand());
}

void visitMarkMustCheckInst(MarkMustCheckInst *I) {
using CheckKind = MarkMustCheckInst::CheckKind;
switch (I->getCheckKind()) {
Expand Down
4 changes: 4 additions & 0 deletions lib/SIL/IR/ValueOwnership.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,10 @@ ValueOwnershipKind ValueOwnershipKindClassifier::visitLoadInst(LoadInst *LI) {
llvm_unreachable("Unhandled LoadOwnershipQualifier in switch.");
}

ValueOwnershipKind ValueOwnershipKindClassifier::visitDropDeinitInst(DropDeinitInst *ddi) {
return ddi->getType().isAddress() ? OwnershipKind::None : OwnershipKind::Owned;
}

ValueOwnershipKind
ValueOwnershipKindClassifier::visitPartialApplyInst(PartialApplyInst *PA) {
// partial_apply instructions are modeled as creating an owned value during
Expand Down
9 changes: 9 additions & 0 deletions lib/SIL/Parser/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3702,6 +3702,15 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B,
break;
}

case SILInstructionKind::DropDeinitInst: {
if (parseTypedValueRef(Val, B))
return true;
if (parseSILDebugLocation(InstLoc, B))
return true;
ResultVal = B.createDropDeinit(InstLoc, Val);
break;
}

case SILInstructionKind::MarkMustCheckInst: {
StringRef AttrName;
if (!parseSILOptional(AttrName, *this)) {
Expand Down
2 changes: 1 addition & 1 deletion lib/SIL/Utils/AddressWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ AddressUseKind TransitiveAddressWalker::walk(SILValue projectedAddress) && {
isa<BeginAccessInst>(user) || isa<TailAddrInst>(user) ||
isa<IndexAddrInst>(user) || isa<StoreBorrowInst>(user) ||
isa<UncheckedAddrCastInst>(user) || isa<MarkMustCheckInst>(user) ||
isa<MarkUninitializedInst>(user) ||
isa<MarkUninitializedInst>(user) || isa<DropDeinitInst>(user) ||
isa<ProjectBlockStorageInst>(user) || isa<UpcastInst>(user) ||
isa<TuplePackElementAddrInst>(user)) {
transitiveResultUses(op);
Expand Down
1 change: 1 addition & 0 deletions lib/SIL/Utils/InstructionUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType)
case SILInstructionKind::ValueToBridgeObjectInst:
case SILInstructionKind::MarkDependenceInst:
case SILInstructionKind::MoveValueInst:
case SILInstructionKind::DropDeinitInst:
case SILInstructionKind::MarkMustCheckInst:
case SILInstructionKind::MarkUnresolvedReferenceBindingInst:
case SILInstructionKind::CopyableToMoveOnlyWrapperValueInst:
Expand Down
1 change: 1 addition & 0 deletions lib/SIL/Utils/MemAccessUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1866,6 +1866,7 @@ AccessPathDefUseTraversal::visitSingleValueUser(SingleValueInstruction *svi,
return IgnoredUse;
}

case SILInstructionKind::DropDeinitInst:
case SILInstructionKind::MarkMustCheckInst: {
// Mark must check goes on the project_box, so it isn't a ref.
assert(!dfs.isRef());
Expand Down
8 changes: 8 additions & 0 deletions lib/SIL/Verifier/SILVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5968,6 +5968,14 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
"Result and operand must have the same type, today.");
}

void checkDropDeinitInst(DropDeinitInst *ddi) {
require(ddi->getType() == ddi->getOperand()->getType(),
"Result and operand must have the same type.");
require(ddi->getType().isMoveOnlyNominalType(),
"drop_deinit only allowed for move-only types");
require(F.hasOwnership(), "drop_deinit only allowed in OSSA");
}

void checkMarkMustCheckInst(MarkMustCheckInst *i) {
require(i->getModule().getStage() == SILStage::Raw,
"Only valid in Raw SIL! Should have been eliminated by /some/ "
Expand Down
1 change: 1 addition & 0 deletions lib/SILGen/SILGenDestructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ void SILGenFunction::emitMoveOnlyMemberDestruction(SILValue selfValue,
NominalTypeDecl *nom,
CleanupLocation cleanupLoc,
SILBasicBlock *finishBB) {
selfValue = B.createDropDeinit(cleanupLoc, selfValue);
if (selfValue->getType().isAddress()) {
if (auto *structDecl = dyn_cast<StructDecl>(nom)) {
for (VarDecl *vd : nom->getStoredProperties()) {
Expand Down
74 changes: 72 additions & 2 deletions lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,9 @@ struct UseState {
/// [assign] that are reinits that we will convert to inits and true reinits.
llvm::SmallMapVector<SILInstruction *, TypeTreeLeafTypeRange, 4> reinitInsts;

/// The set of drop_deinits of this mark_must_check
SmallSetVector<SILInstruction *, 2> dropDeinitInsts;

/// A "inout terminator use" is an implicit liveness use of the entire value
/// placed on a terminator. We use this both so we add liveness for the
/// terminator user and so that we can use the set to quickly identify later
Expand All @@ -712,6 +715,31 @@ struct UseState {
return inoutTermUsers.count(inst);
}

/// Returns true if the given instruction is within the same block as a reinit
/// and precedes a reinit instruction in that block.
bool precedesReinitInSameBlock(SILInstruction *inst) const {
SILBasicBlock *block = inst->getParent();
SmallSetVector<SILInstruction *, 8> sameBlockReinits;

// First, search for all reinits that are within the same block.
for (auto &reinit : reinitInsts) {
if (reinit.first->getParent() != block)
continue;
sameBlockReinits.insert(reinit.first);
}

if (sameBlockReinits.empty())
return false;

// Walk down from the given instruction to see if we encounter a reinit.
for (auto ii = std::next(inst->getIterator()); ii != block->end(); ++ii) {
if (sameBlockReinits.contains(&*ii))
return true;
}

return false;
}

void clear() {
address = nullptr;
destroys.clear();
Expand All @@ -721,6 +749,7 @@ struct UseState {
takeInsts.clear();
initInsts.clear();
reinitInsts.clear();
dropDeinitInsts.clear();
inoutTermUsers.clear();
debugValue = nullptr;
}
Expand Down Expand Up @@ -755,6 +784,10 @@ struct UseState {
for (auto pair : reinitInsts) {
llvm::dbgs() << *pair.first;
}
llvm::dbgs() << "DropDeinits:\n";
for (auto *inst : dropDeinitInsts) {
llvm::dbgs() << *inst;
}
llvm::dbgs() << "InOut Term Users:\n";
for (auto *inst : inoutTermUsers) {
llvm::dbgs() << *inst;
Expand Down Expand Up @@ -1737,6 +1770,12 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
LLVM_DEBUG(llvm::dbgs() << "Running copy propagation!\n");
moveChecker.changed |= moveChecker.canonicalizer.canonicalize();

// Export the drop_deinit's discovered by the ObjectChecker into the
// AddressChecker to preserve it for later use. We need to do this since
// the ObjectChecker's state gets cleared after running on this LoadInst.
for (auto *dropDeinit : moveChecker.canonicalizer.getDropDeinitUses())
moveChecker.addressUseState.dropDeinitInsts.insert(dropDeinit);

// If we are asked to perform no_consume_or_assign checking or
// assignable_but_not_consumable checking, if we found any consumes of our
// load, then we need to emit an error.
Expand Down Expand Up @@ -2458,10 +2497,41 @@ void MoveOnlyAddressCheckerPImpl::rewriteUses(
FieldSensitiveMultiDefPrunedLiveRange &liveness,
const FieldSensitivePrunedLivenessBoundary &boundary) {
LLVM_DEBUG(llvm::dbgs() << "MoveOnlyAddressChecker Rewrite Uses!\n");
// First remove all destroy_addr that have not been claimed.

/// Whether the marked value appeared in a discard statement.
const bool isDiscardingContext = !addressUseState.dropDeinitInsts.empty();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC there really isn't a point in doing a const bool. We (if you look through the code base) generally do not use const with value types like bool.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether it's a discarding context should not change, so expressing that via const is only beneficial to prevent mistakes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, in our code base we do not put constants on types like this. It was definitely against LLVM style for a long time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That being said, given that we need to get stuff in, I am fine letting this through for now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using const is fine.


// Process destroys
for (auto destroyPair : addressUseState.destroys) {
if (!consumes.claimConsume(destroyPair.first, destroyPair.second)) {
/// Is this destroy instruction a final consuming use?
bool isFinalConsume =
consumes.claimConsume(destroyPair.first, destroyPair.second);

// Remove destroys that are not the final consuming use.
if (!isFinalConsume) {
destroyPair.first->eraseFromParent();
continue;
}

// Otherwise, if we're in a discarding context, flag this final destroy_addr
// as a point where we're missing an explicit `consume self`. The reasoning
// here is that if a destroy of self is the final consuming use,
// then these are the points where we implicitly destroy self to clean-up
// that self var before exiting the scope. An explicit 'consume self'
// that is thrown away is a consume of this mark_must_check'd var and not a
// destroy of it, according to the use classifier.
if (isDiscardingContext) {

// Since the boundary computations treat a newly-added destroy prior to
// a reinit within that same block as a "final consuming use", exclude
// such destroys-before-reinit. We are only interested in the final
// destroy of a var, not intermediate destroys of the var.
if (addressUseState.precedesReinitInSameBlock(destroyPair.first))
continue;

auto *dropDeinit = addressUseState.dropDeinitInsts.front();
diagnosticEmitter.emitMissingConsumeInDiscardingContext(destroyPair.first,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another example of a place where it seems like you didn't git-clang-format.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What in particular about the way I've formatted this is bad?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am pretty sure that git-clang-format would put the arguments on the next line. We should all just be using git-clang-format.

dropDeinit);
}
}

Expand Down
6 changes: 4 additions & 2 deletions lib/SILOptimizer/Mandatory/MoveOnlyDeinitInsertion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ static bool performTransform(SILFunction &fn) {

if (auto *dvi = dyn_cast<DestroyValueInst>(inst)) {
auto destroyType = dvi->getOperand()->getType();
if (destroyType.isMoveOnlyNominalType()) {
if (destroyType.isMoveOnlyNominalType() &&
!isa<DropDeinitInst>(lookThroughOwnershipInsts(dvi->getOperand()))) {
LLVM_DEBUG(llvm::dbgs() << "Handling: " << *dvi);
auto *nom = destroyType.getNominalOrBoundGenericNominal();
assert(nom);
Expand Down Expand Up @@ -100,7 +101,8 @@ static bool performTransform(SILFunction &fn) {

if (auto *dai = dyn_cast<DestroyAddrInst>(inst)) {
auto destroyType = dai->getOperand()->getType();
if (destroyType.isLoadable(fn) && destroyType.isMoveOnlyNominalType()) {
if (destroyType.isLoadable(fn) && destroyType.isMoveOnlyNominalType() &&
!isa<DropDeinitInst>(dai->getOperand())) {
LLVM_DEBUG(llvm::dbgs() << "Handling: " << *dai);
auto *nom = destroyType.getNominalOrBoundGenericNominal();
assert(nom);
Expand Down
Loading