Skip to content

Rename dealloc_ref [stack] and enable StackPromotion for OSSA #40738

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 3 commits into from
Jan 7, 2022
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 @@ -208,6 +208,10 @@ final public class DeallocStackInst : Instruction, UnaryInstruction {
}
}

final public class DeallocStackRefInst : Instruction, UnaryInstruction {
public var allocRef: AllocRefInst { operand as! AllocRefInst }
}

final public class CondFailInst : Instruction, UnaryInstruction {
public override var mayTrap: Bool { true }

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 @@ -42,6 +42,7 @@ public func registerSILClasses() {
register(EndAccessInst.self)
register(EndBorrowInst.self)
register(DeallocStackInst.self)
register(DeallocStackRefInst.self)
register(CondFailInst.self)
register(FixLifetimeInst.self)
register(DebugValueInst.self)
Expand Down
20 changes: 16 additions & 4 deletions docs/SIL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3143,8 +3143,8 @@ optional ``objc`` attribute indicates that the object should be
allocated using Objective-C's allocation methods (``+allocWithZone:``).

The optional ``stack`` attribute indicates that the object can be allocated
on the stack instead on the heap. In this case the instruction must have
balanced with a ``dealloc_ref [stack]`` instruction to mark the end of the
on the stack instead on the heap. In this case the instruction must be
balanced with a ``dealloc_stack_ref`` instruction to mark the end of the
object's lifetime.
Note that the ``stack`` attribute only specifies that stack allocation is
possible. The final decision on stack allocation is done during llvm IR
Expand Down Expand Up @@ -3381,13 +3381,25 @@ project_box

Given a ``@box T`` reference, produces the address of the value inside the box.

dealloc_stack_ref
`````````````````
::

sil-instruction ::= 'dealloc_stack_ref' sil-operand

dealloc_stack_ref %0 : $T
// $T must be a class type
// %0 must be an 'alloc_ref [stack]' instruction

Marks the deallocation of the stack space for an ``alloc_ref [stack]``.

dealloc_ref
```````````
::

sil-instruction ::= 'dealloc_ref' ('[' 'stack' ']')? sil-operand
sil-instruction ::= 'dealloc_ref' sil-operand

dealloc_ref [stack] %0 : $T
dealloc_ref %0 : $T
// $T must be a class type

Deallocates an uninitialized class type instance, bypassing the reference
Expand Down
10 changes: 7 additions & 3 deletions include/swift/SIL/SILBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -2005,10 +2005,14 @@ class SILBuilder {
return insert(new (getModule())
DeallocStackInst(getSILDebugLocation(Loc), operand));
}
DeallocRefInst *createDeallocRef(SILLocation Loc, SILValue operand,
bool canBeOnStack) {
DeallocStackRefInst *createDeallocStackRef(SILLocation Loc,
SILValue operand) {
return insert(new (getModule())
DeallocStackRefInst(getSILDebugLocation(Loc), operand));
}
DeallocRefInst *createDeallocRef(SILLocation Loc, SILValue operand) {
return insert(new (getModule()) DeallocRefInst(
getSILDebugLocation(Loc), operand, canBeOnStack));
getSILDebugLocation(Loc), operand));
}
DeallocPartialRefInst *createDeallocPartialRef(SILLocation Loc,
SILValue operand,
Expand Down
12 changes: 10 additions & 2 deletions include/swift/SIL/SILCloner.h
Original file line number Diff line number Diff line change
Expand Up @@ -2488,8 +2488,16 @@ SILCloner<ImplClass>::visitDeallocRefInst(DeallocRefInst *Inst) {
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
recordClonedInstruction(
Inst, getBuilder().createDeallocRef(getOpLocation(Inst->getLoc()),
getOpValue(Inst->getOperand()),
Inst->canAllocOnStack()));
getOpValue(Inst->getOperand())));
}

template<typename ImplClass>
void
SILCloner<ImplClass>::visitDeallocStackRefInst(DeallocStackRefInst *Inst) {
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
recordClonedInstruction(
Inst, getBuilder().createDeallocStackRef(getOpLocation(Inst->getLoc()),
getOpValue(Inst->getOperand())));
}

template<typename ImplClass>
Expand Down
29 changes: 14 additions & 15 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -7619,6 +7619,18 @@ class DeallocStackInst :
: UnaryInstructionBase(DebugLoc, operand) {}
};

/// Like DeallocStackInst, but for `alloc_ref [stack]`.
class DeallocStackRefInst
: public UnaryInstructionBase<SILInstructionKind::DeallocStackRefInst,
DeallocationInst> {
friend SILBuilder;

DeallocStackRefInst(SILDebugLocation DebugLoc, SILValue Operand)
: UnaryInstructionBase(DebugLoc, Operand) {}
public:
AllocRefInst *getAllocRef() { return cast<AllocRefInst>(getOperand()); }
};

/// Deallocate memory for a reference type instance from a destructor or
/// failure path of a constructor.
///
Expand All @@ -7632,21 +7644,8 @@ class DeallocRefInst :
DeallocationInst> {
friend SILBuilder;

private:
DeallocRefInst(SILDebugLocation DebugLoc, SILValue Operand,
bool canBeOnStack = false)
: UnaryInstructionBase(DebugLoc, Operand) {
SILNode::Bits.DeallocRefInst.OnStack = canBeOnStack;
}

public:
bool canAllocOnStack() const {
return SILNode::Bits.DeallocRefInst.OnStack;
}

void setStackAllocatable(bool OnStack) {
SILNode::Bits.DeallocRefInst.OnStack = OnStack;
}
DeallocRefInst(SILDebugLocation DebugLoc, SILValue Operand)
: UnaryInstructionBase(DebugLoc, Operand) { }
};

/// Deallocate memory for a reference type instance from a failure path of a
Expand Down
4 changes: 0 additions & 4 deletions include/swift/SIL/SILNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,6 @@ class alignas(8) SILNode :
Length : 32
);

SWIFT_INLINE_BITFIELD(DeallocRefInst, DeallocationInst, 1,
OnStack : 1
);

// Ensure that AllocBoxInst bitfield does not overflow.
IBWTO_BITFIELD_EMPTY(AllocBoxInst, AllocationInst);
// Ensure that AllocExistentialBoxInst bitfield does not overflow.
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 @@ -808,6 +808,8 @@ ABSTRACT_INST(TermInst, SILInstruction)
ABSTRACT_INST(DeallocationInst, SILInstruction)
BRIDGED_NON_VALUE_INST(DeallocStackInst, dealloc_stack,
DeallocationInst, MayHaveSideEffects, DoesNotRelease)
BRIDGED_NON_VALUE_INST(DeallocStackRefInst, dealloc_stack_ref,
DeallocationInst, MayHaveSideEffects, DoesNotRelease)
BRIDGED_NON_VALUE_INST(DeallocRefInst, dealloc_ref,
DeallocationInst, MayHaveSideEffects, DoesNotRelease)
NON_VALUE_INST(DeallocPartialRefInst, dealloc_partial_ref,
Expand Down
3 changes: 2 additions & 1 deletion include/swift/SILOptimizer/Utils/ValueLifetime.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ class ValueLifetimeAnalysis {
(hasUsersBeforeDef || bb != getDefValueParentBlock());
}

/// Checks if there is a dealloc_ref inside the value's live range.
/// Checks if there is a dealloc_ref or dealloc_stack_ref inside the
/// value's live range.
bool containsDeallocRef(const FrontierImpl &frontier);

/// For debug dumping.
Expand Down
50 changes: 24 additions & 26 deletions lib/IRGen/IRGenSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,7 @@ class IRGenSILFunction :
void visitEndCOWMutationInst(EndCOWMutationInst *i);
void visitIsEscapingClosureInst(IsEscapingClosureInst *i);
void visitDeallocStackInst(DeallocStackInst *i);
void visitDeallocStackRefInst(DeallocStackRefInst *i);
void visitDeallocBoxInst(DeallocBoxInst *i);
void visitDeallocRefInst(DeallocRefInst *i);
void visitDeallocPartialRefInst(DeallocPartialRefInst *i);
Expand Down Expand Up @@ -4515,9 +4516,9 @@ void IRGenSILFunction::visitSetDeallocatingInst(SetDeallocatingInst *i) {
// ...
// set_deallocating %0 // not needed
// // code which does not depend on the RC_DEALLOCATING_FLAG flag.
// dealloc_ref %0 // not needed (stems from the inlined deallocator)
// dealloc_ref %0 // stems from the inlined deallocator
// ...
// dealloc_ref [stack] %0
// dealloc_stack_ref %0
SILBasicBlock::iterator Iter(i);
SILBasicBlock::iterator End = i->getParent()->end();
for (++Iter; Iter != End; ++Iter) {
Expand Down Expand Up @@ -5432,32 +5433,10 @@ void IRGenSILFunction::visitDeallocStackInst(swift::DeallocStackInst *i) {
allocatedTI.deallocateStack(*this, stackAddr, allocatedType);
}

void IRGenSILFunction::visitDeallocRefInst(swift::DeallocRefInst *i) {
// Lower the operand.
void IRGenSILFunction::visitDeallocStackRefInst(DeallocStackRefInst *i) {
Explosion self = getLoweredExplosion(i->getOperand());
auto selfValue = self.claimNext();
auto *ARI = dyn_cast<AllocRefInst>(i->getOperand());
if (!i->canAllocOnStack()) {
if (ARI && StackAllocs.count(ARI)) {
// We can ignore dealloc_refs (without [stack]) for stack allocated
// objects.
//
// %0 = alloc_ref [stack]
// ...
// dealloc_ref %0 // not needed (stems from the inlined deallocator)
// ...
// dealloc_ref [stack] %0
return;
}

auto classType = i->getOperand()->getType();
emitClassDeallocation(*this, classType, selfValue);
return;
}
// It's a dealloc_ref [stack]. Even if the alloc_ref did not allocate the
// object on the stack, we don't have to deallocate it, because it is
// deallocated in the final release.
assert(ARI->canAllocOnStack());
auto *ARI = i->getAllocRef();
if (StackAllocs.count(ARI)) {
if (IGM.IRGen.Opts.EmitStackPromotionChecks) {
selfValue = Builder.CreateBitCast(selfValue, IGM.RefCountedPtrTy);
Expand All @@ -5473,6 +5452,25 @@ void IRGenSILFunction::visitDeallocRefInst(swift::DeallocRefInst *i) {
}
}

void IRGenSILFunction::visitDeallocRefInst(swift::DeallocRefInst *i) {
// Lower the operand.
Explosion self = getLoweredExplosion(i->getOperand());
auto selfValue = self.claimNext();
auto *ARI = dyn_cast<AllocRefInst>(i->getOperand());
if (ARI && StackAllocs.count(ARI)) {
// We can ignore dealloc_refs for stack allocated objects.
//
// %0 = alloc_ref [stack]
// ...
// dealloc_ref %0 // not needed (stems from the inlined deallocator)
// ...
// dealloc_stack_ref %0
return;
}
auto classType = i->getOperand()->getType();
emitClassDeallocation(*this, classType, selfValue);
}

void IRGenSILFunction::visitDeallocPartialRefInst(swift::DeallocPartialRefInst *i) {
Explosion self = getLoweredExplosion(i->getInstance());
auto selfValue = self.claimNext();
Expand Down
4 changes: 4 additions & 0 deletions lib/SIL/IR/OperandOwnership.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ OPERAND_OWNERSHIP(TrivialUse, UncheckedRefCastAddr)
OPERAND_OWNERSHIP(TrivialUse, UncheckedTakeEnumDataAddr)
OPERAND_OWNERSHIP(TrivialUse, UnconditionalCheckedCastAddr)

// The dealloc_stack_ref operand needs to have NonUse ownership because
// this use comes after the last consuming use (which is usually a dealloc_ref).
OPERAND_OWNERSHIP(NonUse, DeallocStackRef)

// Use an owned or guaranteed value only for the duration of the operation.
OPERAND_OWNERSHIP(InstantaneousUse, ExistentialMetatype)
OPERAND_OWNERSHIP(InstantaneousUse, FixLifetime)
Expand Down
11 changes: 5 additions & 6 deletions lib/SIL/IR/SILInstruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ namespace {
return true;
}

bool visitDeallocStackRefInst(const DeallocStackRefInst *RHS) {
return true;
}

bool visitAllocStackInst(const AllocStackInst *RHS) {
return true;
}
Expand Down Expand Up @@ -1285,14 +1289,9 @@ bool SILInstruction::isAllocatingStack() const {
}

bool SILInstruction::isDeallocatingStack() const {
if (isa<DeallocStackInst>(this))
if (isa<DeallocStackInst>(this) || isa<DeallocStackRefInst>(this))
return true;

if (auto *DRI = dyn_cast<DeallocRefInst>(this)) {
if (DRI->canAllocOnStack())
return true;
}

if (auto *BI = dyn_cast<BuiltinInst>(this)) {
if (BI->getBuiltinKind() == BuiltinValueKind::StackDealloc) {
return true;
Expand Down
5 changes: 3 additions & 2 deletions lib/SIL/IR/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2211,9 +2211,10 @@ class SILPrinter : public SILInstructionVisitor<SILPrinter> {
void visitDeallocStackInst(DeallocStackInst *DI) {
*this << getIDAndType(DI->getOperand());
}
void visitDeallocStackRefInst(DeallocStackRefInst *ESRL) {
*this << getIDAndType(ESRL->getOperand());
}
void visitDeallocRefInst(DeallocRefInst *DI) {
if (DI->canAllocOnStack())
*this << "[stack] ";
*this << getIDAndType(DI->getOperand());
}
void visitDeallocPartialRefInst(DeallocPartialRefInst *DPI) {
Expand Down
11 changes: 6 additions & 5 deletions lib/SIL/Parser/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4301,14 +4301,15 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B,
return true;
ResultVal = B.createDeallocStack(InstLoc, Val);
break;
case SILInstructionKind::DeallocRefInst: {
bool OnStack = false;
if (parseSILOptional(OnStack, *this, "stack"))
case SILInstructionKind::DeallocStackRefInst:
if (parseTypedValueRef(Val, B) || parseSILDebugLocation(InstLoc, B))
return true;

ResultVal = B.createDeallocStackRef(InstLoc, Val);
break;
case SILInstructionKind::DeallocRefInst: {
if (parseTypedValueRef(Val, B) || parseSILDebugLocation(InstLoc, B))
return true;
ResultVal = B.createDeallocRef(InstLoc, Val, OnStack);
ResultVal = B.createDeallocRef(InstLoc, Val);
break;
}
case SILInstructionKind::DeallocPartialRefInst: {
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 @@ -461,6 +461,7 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType)
case SILInstructionKind::SwitchValueInst:
case SILInstructionKind::SwitchEnumInst:
case SILInstructionKind::DeallocStackInst:
case SILInstructionKind::DeallocStackRefInst:
case SILInstructionKind::AutoreleaseValueInst:
case SILInstructionKind::BindMemoryInst:
case SILInstructionKind::RebindMemoryInst:
Expand Down
4 changes: 2 additions & 2 deletions lib/SIL/Utils/LoopInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ bool SILLoop::canDuplicate(SILInstruction *I) const {
if (isa<AllocStackInst>(address) || isa<PartialApplyInst>(address))
alloc = cast<SingleValueInstruction>(address);
}
if (auto *dealloc = dyn_cast<DeallocRefInst>(I))
alloc = dyn_cast<AllocRefInst>(dealloc->getOperand());
if (auto *dealloc = dyn_cast<DeallocStackRefInst>(I))
alloc = dealloc->getAllocRef();

return alloc && contains(alloc);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/SIL/Verifier/SILOwnershipVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,9 @@ bool SILValueOwnershipChecker::gatherNonGuaranteedUsers(
for (auto *op : value->getUses()) {
auto *user = op->getUser();

// If this op is a type dependent operand, skip it. It is not interesting
// For example, type dependent operands are non-use. It is not interesting
// from an ownership perspective.
if (user->isTypeDependentOperand(*op))
if (op->getOperandOwnership() == OperandOwnership::NonUse)
continue;

// First check if this recursive use is compatible with our values ownership
Expand Down
8 changes: 3 additions & 5 deletions lib/SIL/Verifier/SILVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2992,11 +2992,9 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
auto *cd = DI->getOperand()->getType().getClassOrBoundGenericClass();
require(cd, "Operand of dealloc_ref must be of class type");

if (!DI->canAllocOnStack()) {
require(!checkResilience(cd, F.getModule().getSwiftModule(),
F.getResilienceExpansion()),
"cannot directly deallocate resilient class");
}
require(!checkResilience(cd, F.getModule().getSwiftModule(),
F.getResilienceExpansion()),
"cannot directly deallocate resilient class");
}
void checkDeallocPartialRefInst(DeallocPartialRefInst *DPRI) {
require(DPRI->getInstance()->getType().isObject(),
Expand Down
2 changes: 1 addition & 1 deletion lib/SILGen/SILGenDestructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ void SILGenFunction::emitDeallocatingDestructor(DestructorDecl *dd) {

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

emitProfilerIncrement(dd->getTypecheckedBody());

Expand Down
1 change: 1 addition & 0 deletions lib/SILOptimizer/Analysis/EscapeAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2163,6 +2163,7 @@ void EscapeAnalysis::analyzeInstruction(SILInstruction *I,
case SILInstructionKind::InitExistentialMetatypeInst:
case SILInstructionKind::OpenExistentialMetatypeInst:
case SILInstructionKind::ExistentialMetatypeInst:
case SILInstructionKind::DeallocStackRefInst:
case SILInstructionKind::DeallocRefInst:
case SILInstructionKind::SetDeallocatingInst:
case SILInstructionKind::FixLifetimeInst:
Expand Down
3 changes: 2 additions & 1 deletion lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,8 @@ bool SILCombiner::eraseApply(FullApplySite FAS, const UserListTy &Users) {
if (!VLA.computeFrontier(Frontier, ValueLifetimeAnalysis::DontModifyCFG))
return false;
// As we are extending the lifetimes of owned parameters, we have to make
// sure that no dealloc_ref instructions are within this extended liferange.
// sure that no dealloc_ref or dealloc_stack_ref instructions are
// within this extended liferange.
// It could be that the dealloc_ref is deallocating a parameter and then
// we would have a release after the dealloc.
if (VLA.containsDeallocRef(Frontier))
Expand Down
Loading