Skip to content

DI: Correctly handle conditional destroy of 'self' in root class #33734

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 lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ class DIMemoryObjectInfo {
MemoryInst->isDerivedClassSelfOnly();
}

/// True if this memory object is the 'self' of a root class init method.
bool isRootClassSelf() const {
return isClassInitSelf() && MemoryInst->isRootSelf();
}

/// True if this memory object is the 'self' of a non-root class init method.
bool isNonRootClassSelf() const {
return isClassInitSelf() && !MemoryInst->isRootSelf();
Expand Down
146 changes: 111 additions & 35 deletions lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,13 @@ void LifetimeChecker::handleStoreUse(unsigned UseID) {
// it for later. Once we've collected all of the conditional init/assigns,
// we can insert a single control variable for the memory object for the
// whole function.
if (!Use.onlyTouchesTrivialElements(TheMemory))
//
// For root class initializers, we must keep track of initializations of
// trivial stored properties also, since we need to know when the object
// has been fully initialized when deciding if a strong_release should
// lower to a partial_dealloc_ref.
if (TheMemory.isRootClassSelf() ||
!Use.onlyTouchesTrivialElements(TheMemory))
HasConditionalInitAssign = true;
return;
}
Expand Down Expand Up @@ -2186,12 +2192,12 @@ static void updateControlVariable(SILLocation Loc,
}

/// Test a bit in the control variable at the current insertion point.
static SILValue testControlVariable(SILLocation Loc,
unsigned Elt,
SILValue ControlVariableAddr,
Identifier &ShiftRightFn,
Identifier &TruncateFn,
SILBuilder &B) {
static SILValue testControlVariableBit(SILLocation Loc,
unsigned Elt,
SILValue ControlVariableAddr,
Identifier &ShiftRightFn,
Identifier &TruncateFn,
SILBuilder &B) {
SILValue ControlVariable =
B.createLoad(Loc, ControlVariableAddr, LoadOwnershipQualifier::Trivial);

Expand Down Expand Up @@ -2224,6 +2230,32 @@ static SILValue testControlVariable(SILLocation Loc,
{}, CondVal);
}

/// Test if all bits in the control variable are set at the current
/// insertion point.
static SILValue testAllControlVariableBits(SILLocation Loc,
SILValue ControlVariableAddr,
Identifier &CmpEqFn,
SILBuilder &B) {
SILValue ControlVariable =
B.createLoad(Loc, ControlVariableAddr, LoadOwnershipQualifier::Trivial);

SILValue CondVal = ControlVariable;
CanBuiltinIntegerType IVType = CondVal->getType().castTo<BuiltinIntegerType>();

if (IVType->getFixedWidth() == 1)
return CondVal;

SILValue AllBitsSet = B.createIntegerLiteral(Loc, CondVal->getType(), -1);
if (!CmpEqFn.get())
CmpEqFn = getBinaryFunction("cmp_eq", CondVal->getType(),
B.getASTContext());
SILValue Args[] = { CondVal, AllBitsSet };

return B.createBuiltin(Loc, CmpEqFn,
SILType::getBuiltinIntegerType(1, B.getASTContext()),
{}, Args);
}

/// handleConditionalInitAssign - This memory object has some stores
/// into (some element of) it that is either an init or an assign based on the
/// control flow path through the function, or have a destroy event that happens
Expand Down Expand Up @@ -2285,7 +2317,13 @@ SILValue LifetimeChecker::handleConditionalInitAssign() {
// If this ambiguous store is only of trivial types, then we don't need to
// do anything special. We don't even need keep the init bit for the
// element precise.
if (Use.onlyTouchesTrivialElements(TheMemory))
//
// For root class initializers, we must keep track of initializations of
// trivial stored properties also, since we need to know when the object
// has been fully initialized when deciding if a strong_release should
// lower to a partial_dealloc_ref.
if (!TheMemory.isRootClassSelf() &&
Use.onlyTouchesTrivialElements(TheMemory))
continue;

B.setInsertionPoint(Use.Inst);
Expand Down Expand Up @@ -2324,9 +2362,9 @@ SILValue LifetimeChecker::handleConditionalInitAssign() {
// initialization.
for (unsigned Elt = Use.FirstElement, e = Elt+Use.NumElements;
Elt != e; ++Elt) {
auto CondVal = testControlVariable(Loc, Elt, ControlVariableAddr,
ShiftRightFn, TruncateFn,
B);
auto CondVal = testControlVariableBit(Loc, Elt, ControlVariableAddr,
ShiftRightFn, TruncateFn,
B);

SILBasicBlock *TrueBB, *FalseBB, *ContBB;
InsertCFGDiamond(CondVal, Loc, B,
Expand Down Expand Up @@ -2395,7 +2433,7 @@ SILValue LifetimeChecker::handleConditionalInitAssign() {
void LifetimeChecker::
handleConditionalDestroys(SILValue ControlVariableAddr) {
SILBuilderWithScope B(TheMemory.getUninitializedValue());
Identifier ShiftRightFn, TruncateFn;
Identifier ShiftRightFn, TruncateFn, CmpEqFn;

unsigned NumMemoryElements = TheMemory.getNumElements();

Expand Down Expand Up @@ -2465,9 +2503,9 @@ handleConditionalDestroys(SILValue ControlVariableAddr) {

// Insert a load of the liveness bitmask and split the CFG into a diamond
// right before the destroy_addr, if we haven't already loaded it.
auto CondVal = testControlVariable(Loc, Elt, ControlVariableAddr,
ShiftRightFn, TruncateFn,
B);
auto CondVal = testControlVariableBit(Loc, Elt, ControlVariableAddr,
ShiftRightFn, TruncateFn,
B);

SILBasicBlock *ReleaseBlock, *DeallocBlock, *ContBlock;

Expand All @@ -2486,11 +2524,11 @@ handleConditionalDestroys(SILValue ControlVariableAddr) {
// depending on if the self box was initialized or not.
auto emitReleaseOfSelfWhenNotConsumed = [&](SILLocation Loc,
SILInstruction *Release) {
auto CondVal = testControlVariable(Loc, SelfInitializedElt,
ControlVariableAddr,
ShiftRightFn,
TruncateFn,
B);
auto CondVal = testControlVariableBit(Loc, SelfInitializedElt,
ControlVariableAddr,
ShiftRightFn,
TruncateFn,
B);

SILBasicBlock *ReleaseBlock, *ConsumedBlock, *ContBlock;

Expand Down Expand Up @@ -2522,12 +2560,50 @@ handleConditionalDestroys(SILValue ControlVariableAddr) {
// Just conditionally destroy each memory element, and for classes,
// also free the partially initialized object.
if (!TheMemory.isNonRootClassSelf()) {
destroyMemoryElements(Loc, Availability);
processUninitializedRelease(Release, false, B.getInsertionPoint());
assert(!Availability.isAllYes() &&
"Should not end up here if fully initialized");

// For root class initializers, we check if all proeprties were
// dynamically initialized, and if so, treat this as a release of
// an initialized 'self', instead of tearing down the fields
// one by one and deallocating memory.
//
// This is required for correctness, since the condition that
// allows 'self' to escape is that all stored properties were
// initialized. So we cannot deallocate the memory if 'self' may
// have escaped.
//
// This also means the deinitializer will run if all stored
// properties were initialized.
if (TheMemory.isClassInitSelf() &&
Availability.hasAny(DIKind::Partial)) {
auto CondVal = testAllControlVariableBits(Loc, ControlVariableAddr,
CmpEqFn, B);

SILBasicBlock *ReleaseBlock, *DeallocBlock, *ContBlock;

InsertCFGDiamond(CondVal, Loc, B,
ReleaseBlock, DeallocBlock, ContBlock);

// If true, self was fully initialized and must be released.
B.setInsertionPoint(ReleaseBlock->begin());
B.setCurrentDebugScope(ReleaseBlock->begin()->getDebugScope());
Release->moveBefore(&*B.getInsertionPoint());

// If false, self is uninitialized and must be freed.
B.setInsertionPoint(DeallocBlock->begin());
B.setCurrentDebugScope(DeallocBlock->begin()->getDebugScope());
destroyMemoryElements(Loc, Availability);
processUninitializedRelease(Release, false, B.getInsertionPoint());
} else {
destroyMemoryElements(Loc, Availability);
processUninitializedRelease(Release, false, B.getInsertionPoint());

// The original strong_release or destroy_addr instruction is
// always dead at this point.
deleteDeadRelease(CDElt.ReleaseID);
}

// The original strong_release or destroy_addr instruction is
// always dead at this point.
deleteDeadRelease(CDElt.ReleaseID);
continue;
}

Expand Down Expand Up @@ -2573,11 +2649,11 @@ handleConditionalDestroys(SILValue ControlVariableAddr) {
// self.init or super.init may or may not have been called.
// We have not yet stored 'self' into the box.

auto CondVal = testControlVariable(Loc, SuperInitElt,
ControlVariableAddr,
ShiftRightFn,
TruncateFn,
B);
auto CondVal = testControlVariableBit(Loc, SuperInitElt,
ControlVariableAddr,
ShiftRightFn,
TruncateFn,
B);

SILBasicBlock *ConsumedBlock, *DeallocBlock, *ContBlock;

Expand Down Expand Up @@ -2607,11 +2683,11 @@ handleConditionalDestroys(SILValue ControlVariableAddr) {
// self.init or super.init may or may not have been called.
// We may or may have stored 'self' into the box.

auto CondVal = testControlVariable(Loc, SuperInitElt,
ControlVariableAddr,
ShiftRightFn,
TruncateFn,
B);
auto CondVal = testControlVariableBit(Loc, SuperInitElt,
ControlVariableAddr,
ShiftRightFn,
TruncateFn,
B);

SILBasicBlock *LiveBlock, *DeallocBlock, *ContBlock;

Expand Down
Loading