Skip to content

End lifetime and deallocating deinits #7858

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
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
5 changes: 5 additions & 0 deletions include/swift/SIL/SILBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -1280,6 +1280,11 @@ class SILBuilder {
getSILDebugLocation(Loc), Operand, atomicity));
}

EndLifetimeInst *createEndLifetime(SILLocation Loc, SILValue Operand) {
return insert(new (F.getModule())
EndLifetimeInst(getSILDebugLocation(Loc), Operand));
}

FixLifetimeInst *createFixLifetime(SILLocation Loc, SILValue Operand) {
return insert(new (F.getModule())
FixLifetimeInst(getSILDebugLocation(Loc), Operand));
Expand Down
13 changes: 10 additions & 3 deletions include/swift/SIL/SILCloner.h
Original file line number Diff line number Diff line change
Expand Up @@ -1700,9 +1700,16 @@ SILCloner<ImplClass>::visitFixLifetimeInst(FixLifetimeInst *Inst) {
getOpValue(Inst->getOperand())));
}

template<typename ImplClass>
void
SILCloner<ImplClass>::visitMarkDependenceInst(MarkDependenceInst *Inst) {
template <typename ImplClass>
void SILCloner<ImplClass>::visitEndLifetimeInst(EndLifetimeInst *Inst) {
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
doPostProcess(Inst,
getBuilder().createEndLifetime(getOpLocation(Inst->getLoc()),
getOpValue(Inst->getOperand())));
}

template <typename ImplClass>
void SILCloner<ImplClass>::visitMarkDependenceInst(MarkDependenceInst *Inst) {
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
doPostProcess(Inst,
getBuilder().createMarkDependence(getOpLocation(Inst->getLoc()),
Expand Down
22 changes: 22 additions & 0 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -4337,6 +4337,28 @@ class FixLifetimeInst :
: UnaryInstructionBase(DebugLoc, Operand) {}
};

/// EndLifetimeInst - An artificial end lifetime use of a value for the purpose
/// of working around verification problems.
///
/// Specifically, the signature of destroying deinit takes self at +0 and
/// returns self at +1. This is an issue since a deallocating deinit takes in
/// self at +1. Previously, we could rely on the deallocating bit being set in
/// the object header to allow SILGen to statically balance the +1 from the
/// deallocating deinit. This is because deallocating values used to be
/// immortal. The runtime now asserts if we release a deallocating value,
/// meaning such an approach does not work. This instruction acts as a "fake"
/// lifetime ending use allowing for static verification of deallocating
/// destroyers, without an actual release being emitted (avoiding the runtime
/// assert).
class EndLifetimeInst
: public UnaryInstructionBase<ValueKind::EndLifetimeInst, SILInstruction,
/*HAS_RESULT*/ false> {
friend SILBuilder;

EndLifetimeInst(SILDebugLocation DebugLoc, SILValue Operand)
: UnaryInstructionBase(DebugLoc, Operand) {}
};

/// MarkDependenceInst - Marks that one value depends on another for
/// validity in a non-obvious way.
class MarkDependenceInst : public SILInstruction {
Expand Down
1 change: 1 addition & 0 deletions include/swift/SIL/SILNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ ABSTRACT_VALUE(SILInstruction, ValueBase)
INST(UnmanagedAutoreleaseValueInst, RefCountingInst, unmanaged_autorelease_value, MayHaveSideEffects, DoesNotRelease)
INST(CopyUnownedValueInst, SILInstruction, copy_unowned_value, MayHaveSideEffects, DoesNotRelease)
INST(DestroyValueInst, SILInstruction, destroy_value, MayHaveSideEffects, MayRelease)
INST(EndLifetimeInst, SILInstruction, end_lifetime, MayHaveSideEffects, MayRelease)

// IsUnique does not actually write to memory but should be modeled
// as such. Its operand is a pointer to an object reference. The
Expand Down
3 changes: 3 additions & 0 deletions lib/IRGen/IRGenSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,9 @@ class IRGenSILFunction :
void visitInitBlockStorageHeaderInst(InitBlockStorageHeaderInst *i);

void visitFixLifetimeInst(FixLifetimeInst *i);
void visitEndLifetimeInst(EndLifetimeInst *i) {
llvm_unreachable("unimplemented");
}
void visitBeginBorrowInst(BeginBorrowInst *i) {
llvm_unreachable("unimplemented");
}
Expand Down
1 change: 1 addition & 0 deletions lib/Parse/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2124,6 +2124,7 @@ bool SILParser::parseSILInstruction(SILBasicBlock *BB, SILBuilder &B) {
} break;

UNARY_INSTRUCTION(FixLifetime)
UNARY_INSTRUCTION(EndLifetime)
UNARY_INSTRUCTION(CopyBlock)
UNARY_INSTRUCTION(IsUnique)
UNARY_INSTRUCTION(IsUniqueOrPinned)
Expand Down
1 change: 1 addition & 0 deletions lib/SIL/SILOwnershipVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ CONSTANT_OWNERSHIP_INST(Owned, true, StrongUnpin)
CONSTANT_OWNERSHIP_INST(Owned, true, UnownedRelease)
CONSTANT_OWNERSHIP_INST(Owned, true, InitExistentialRef)
CONSTANT_OWNERSHIP_INST(Owned, true, OpenExistentialOpaque)
CONSTANT_OWNERSHIP_INST(Owned, true, EndLifetime)
CONSTANT_OWNERSHIP_INST(Trivial, false, AddressToPointer)
CONSTANT_OWNERSHIP_INST(Trivial, false, BindMemory)
CONSTANT_OWNERSHIP_INST(Trivial, false, CheckedCastAddrBranch)
Expand Down
5 changes: 5 additions & 0 deletions lib/SIL/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1549,6 +1549,11 @@ class SILPrinter : public SILVisitor<SILPrinter> {
void visitFixLifetimeInst(FixLifetimeInst *RI) {
*this << getIDAndType(RI->getOperand());
}

void visitEndLifetimeInst(EndLifetimeInst *ELI) {
*this << getIDAndType(ELI->getOperand());
}

void visitMarkDependenceInst(MarkDependenceInst *MDI) {
*this << getIDAndType(MDI->getValue()) << " on "
<< getIDAndType(MDI->getBase());
Expand Down
1 change: 1 addition & 0 deletions lib/SIL/SILValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ NO_RESULT_OWNERSHIP_INST(InjectEnumAddr)
NO_RESULT_OWNERSHIP_INST(DeinitExistentialAddr)
NO_RESULT_OWNERSHIP_INST(DeinitExistentialOpaque)
NO_RESULT_OWNERSHIP_INST(CondFail)
NO_RESULT_OWNERSHIP_INST(EndLifetime)

// Terminators. These do not produce SILValue, so they do not have a
// ValueOwnershipKind. They do have ownership implications in terms of the
Expand Down
32 changes: 24 additions & 8 deletions lib/SILGen/SILGenDestructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,29 +85,45 @@ void SILGenFunction::emitDeallocatingDestructor(DestructorDecl *dd) {
loc.markAutoGenerated();

// Emit the prolog.
SILValue selfValue = emitSelfDecl(dd->getImplicitSelfDecl());
SILValue initialSelfValue = emitSelfDecl(dd->getImplicitSelfDecl());

// Form a reference to the destroying destructor.
SILDeclRef dtorConstant(dd, SILDeclRef::Kind::Destroyer);
auto classTy = selfValue->getType();
auto classTy = initialSelfValue->getType();
ManagedValue dtorValue;
SILType dtorTy;
SubstitutionList subs = classTy.gatherAllSubstitutions(SGM.M);
std::tie(dtorValue, dtorTy, subs)
= emitSiblingMethodRef(loc, selfValue, dtorConstant, subs);
= emitSiblingMethodRef(loc, initialSelfValue, dtorConstant, subs);

// Call the destroying destructor.
SILValue selfForDealloc;
{
FullExpr CleanupScope(Cleanups, CleanupLocation::get(loc));
ManagedValue borrowedSelf = emitManagedBeginBorrow(loc, selfValue);
ManagedValue borrowedSelf = emitManagedBeginBorrow(loc, initialSelfValue);
SILType objectPtrTy = SILType::getNativeObjectType(F.getASTContext());
selfValue = B.createApply(loc, dtorValue.forward(*this),
dtorTy, objectPtrTy, subs, borrowedSelf.getUnmanagedValue());
selfForDealloc = B.createApply(loc, dtorValue.forward(*this),
dtorTy, objectPtrTy, subs, borrowedSelf.getUnmanagedValue());
}

// Balance out the +1 from the self argument using end_lifetime.
//
// The issue here is that:
//
// 1. Self is passed into deallocating deinits at +1.
// 2. Destroying deinits take in self as a +0 value that is then returned at
// +1.
//
// This means that the lifetime of self can not be modeled statically in a
// deallocating deinit without analyzing the body of the destroying deinit
// (something that violates semantic sil). Thus we add an artifical destroy of
// self before the actual destroy of self so that the verifier can understand
// that self is being properly balanced.
B.createEndLifetime(loc, initialSelfValue);

// Deallocate the object.
selfValue = B.createUncheckedRefCast(loc, selfValue, classTy);
B.createDeallocRef(loc, selfValue, false);
selfForDealloc = B.createUncheckedRefCast(loc, selfForDealloc, classTy);
B.createDeallocRef(loc, selfForDealloc, false);

// Return.
B.createReturn(loc, emitEmptyTuple(loc));
Expand Down
4 changes: 4 additions & 0 deletions lib/SILOptimizer/Transforms/OwnershipModelEliminator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ struct OwnershipModelEliminatorVisitor
EBI->eraseFromParent();
return true;
}
bool visitEndLifetimeInst(EndLifetimeInst *ELI) {
ELI->eraseFromParent();
return true;
}
bool visitUnmanagedRetainValueInst(UnmanagedRetainValueInst *URVI);
bool visitUnmanagedReleaseValueInst(UnmanagedReleaseValueInst *URVI);
bool visitUnmanagedAutoreleaseValueInst(UnmanagedAutoreleaseValueInst *UAVI);
Expand Down
1 change: 1 addition & 0 deletions lib/SILOptimizer/Utils/SILInliner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ InlineCost swift::instructionInlineCost(SILInstruction &I) {
case ValueKind::FunctionRefInst:
case ValueKind::AllocGlobalInst:
case ValueKind::GlobalAddrInst:
case ValueKind::EndLifetimeInst:
return InlineCost::Free;

// Typed GEPs are free.
Expand Down
1 change: 1 addition & 0 deletions lib/Serialization/DeserializeSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,7 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, SILBasicBlock *BB,
UNARY_INSTRUCTION(Return)
UNARY_INSTRUCTION(Throw)
UNARY_INSTRUCTION(FixLifetime)
UNARY_INSTRUCTION(EndLifetime)
UNARY_INSTRUCTION(CopyBlock)
UNARY_INSTRUCTION(LoadBorrow)
UNARY_INSTRUCTION(BeginBorrow)
Expand Down
1 change: 1 addition & 0 deletions lib/Serialization/SerializeSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,7 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) {
case ValueKind::LoadWeakInst:
case ValueKind::MarkUninitializedInst:
case ValueKind::FixLifetimeInst:
case ValueKind::EndLifetimeInst:
case ValueKind::CopyBlockInst:
case ValueKind::StrongPinInst:
case ValueKind::StrongReleaseInst:
Expand Down
8 changes: 8 additions & 0 deletions test/SIL/Parser/basic.sil
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,14 @@ bb0(%0 : $Builtin.RawPointer, %1 : $Builtin.Word):
return %28 : $()
}

// CHECK-LABEL: sil @test_end_lifetime : $@convention(thin) (@owned Builtin.NativeObject) -> () {
// CHECK: end_lifetime {{%.*}} : $Builtin.NativeObject
sil @test_end_lifetime : $@convention(thin) (@owned Builtin.NativeObject) -> () {
bb0(%0 : $Builtin.NativeObject):
end_lifetime %0 : $Builtin.NativeObject
return undef : $()
}

// CHECK-LABEL: sil_vtable Foo {
// CHECK: #Foo.subscript!getter.1: {{.*}} : hidden _TFC3tmp3Foog9subscriptFTVs5Int32S1__S1_
// CHECK: #Foo.subscript!setter.1: {{.*}} : _TFC3tmp3Foos9subscriptFTVs5Int32S1__S1_
Expand Down
16 changes: 16 additions & 0 deletions test/SIL/Serialization/basic.sil
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// First parse this and then emit a *.sib. Then read in the *.sib, then recreate
// RUN: rm -rfv %t
// RUN: mkdir %t
// RUN: %target-sil-opt %s -emit-sib -o %t/tmp.sib -module-name borrow
// RUN: %target-sil-opt %t/tmp.sib -o %t/tmp.2.sib -module-name borrow
// RUN: %target-sil-opt %t/tmp.2.sib -module-name borrow | %FileCheck %s

import Builtin

// CHECK-LABEL: sil @test_end_lifetime : $@convention(thin) (@owned Builtin.NativeObject) -> () {
// CHECK: end_lifetime {{%.*}} : $Builtin.NativeObject
sil @test_end_lifetime : $@convention(thin) (@owned Builtin.NativeObject) -> () {
bb0(%0 : $Builtin.NativeObject):
end_lifetime %0 : $Builtin.NativeObject
return undef : $()
}
1 change: 1 addition & 0 deletions test/SILGen/lifetime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ class Foo<T> {
// CHECK-NEXT: [[BORROWED_SELF:%.*]] = begin_borrow [[SELF]]
// CHECK-NEXT: [[RESULT_SELF:%[0-9]+]] = apply [[DESTROYING_REF]]<T>([[BORROWED_SELF]]) : $@convention(method) <τ_0_0> (@guaranteed Foo<τ_0_0>) -> @owned Builtin.NativeObject
// CHECK-NEXT: end_borrow [[BORROWED_SELF]] from [[SELF]]
// CHECK-NEXT: end_lifetime [[SELF]]
// CHECK-NEXT: [[SELF:%[0-9]+]] = unchecked_ref_cast [[RESULT_SELF]] : $Builtin.NativeObject to $Foo<T>
// CHECK-NEXT: dealloc_ref [[SELF]] : $Foo<T>
// CHECK-NEXT: [[RESULT:%[0-9]+]] = tuple ()
Expand Down
9 changes: 9 additions & 0 deletions test/SILOptimizer/ownership_model_eliminator.sil
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,12 @@ bb3:
%9999 = tuple()
return %9999 : $()
}

// CHECK-LABEL: sil @end_lifetime_test : $@convention(thin) (@owned Builtin.NativeObject) -> () {
// CHECK-NOT: end_lifetime {{%.*}} : $Builtin.NativeObject
sil @end_lifetime_test : $@convention(thin) (@owned Builtin.NativeObject) -> () {
bb0(%0 : @owned $Builtin.NativeObject):
end_lifetime %0 : $Builtin.NativeObject
%9999 = tuple()
return %9999 : $()
}
1 change: 1 addition & 0 deletions utils/sil-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"load_weak" "store_weak"
"load_unowned" "store_unowned"
"fix_lifetime" "mark_dependence"
"end_lifetime"
"is_unique" "is_unique_or_pinned"
"copy_block"
"strong_unpin" "strong_pin" "is_unique" "is_unique_or_pinned")
Expand Down