Skip to content

Commit

Permalink
[SIL] Add dead_end flag to destroy_value.
Browse files Browse the repository at this point in the history
  • Loading branch information
nate-chandler committed Jul 3, 2024
1 parent 91fe12a commit a8cc3bf
Show file tree
Hide file tree
Showing 15 changed files with 200 additions and 21 deletions.
5 changes: 4 additions & 1 deletion docs/SIL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6664,7 +6664,7 @@ destroy_value

::

sil-instruction ::= 'destroy_value' '[poison]'? sil-operand
sil-instruction ::= 'destroy_value' '[dead_end]'? '[poison]'? sil-operand

destroy_value %0 : $A

Expand All @@ -6682,6 +6682,9 @@ For aggregate types, especially enums, it is typically both easier
and more efficient to reason about aggregate destroys than it is to
reason about destroys of the subobjects.

The optional ``dead_end`` attribute specifies that this instruction was created
during lifetime completion and is eligible for deletion during OSSA lowering.

autorelease_value
`````````````````

Expand Down
10 changes: 5 additions & 5 deletions include/swift/SIL/SILBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -1442,16 +1442,16 @@ class SILBuilder {
ExplicitCopyValueInst(getSILDebugLocation(Loc), operand));
}

DestroyValueInst *
createDestroyValue(SILLocation Loc, SILValue operand,
PoisonRefs_t poisonRefs = DontPoisonRefs) {
DestroyValueInst *createDestroyValue(SILLocation Loc, SILValue operand,
PoisonRefs_t poisonRefs = DontPoisonRefs,
IsDeadEnd_t isDeadEnd = IsntDeadEnd) {
assert(getFunction().hasOwnership());
assert(isLoadableOrOpaque(operand->getType()));
assert(!operand->getType().isTrivial(getFunction()) &&
"Should not be passing trivial values to this api. Use instead "
"emitDestroyValueOperation");
return insert(new (getModule()) DestroyValueInst(getSILDebugLocation(Loc),
operand, poisonRefs));
return insert(new (getModule()) DestroyValueInst(
getSILDebugLocation(Loc), operand, poisonRefs, isDeadEnd));
}

MoveValueInst *createMoveValue(
Expand Down
8 changes: 4 additions & 4 deletions include/swift/SIL/SILCloner.h
Original file line number Diff line number Diff line change
Expand Up @@ -2208,10 +2208,10 @@ void SILCloner<ImplClass>::visitDestroyValueInst(DestroyValueInst *Inst) {
RefCountingInst::Atomicity::Atomic));
}

recordClonedInstruction(
Inst, getBuilder().createDestroyValue(getOpLocation(Inst->getLoc()),
getOpValue(Inst->getOperand()),
Inst->poisonRefs()));
recordClonedInstruction(Inst, getBuilder().createDestroyValue(
getOpLocation(Inst->getLoc()),
getOpValue(Inst->getOperand()),
Inst->poisonRefs(), Inst->isDeadEnd()));
}

template <typename ImplClass>
Expand Down
14 changes: 12 additions & 2 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -8806,16 +8806,22 @@ class UnownedCopyValueInst
};
#include "swift/AST/ReferenceStorage.def"

enum IsDeadEnd_t : bool {
IsntDeadEnd = false,
IsDeadEnd = true,
};

class DestroyValueInst
: public UnaryInstructionBase<SILInstructionKind::DestroyValueInst,
NonValueInstruction> {
friend class SILBuilder;
USE_SHARED_UINT8;

DestroyValueInst(SILDebugLocation DebugLoc, SILValue operand,
PoisonRefs_t poisonRefs)
PoisonRefs_t poisonRefs, IsDeadEnd_t isDeadEnd)
: UnaryInstructionBase(DebugLoc, operand) {
setPoisonRefs(poisonRefs);
sharedUInt8().DestroyValueInst.poisonRefs = poisonRefs;
sharedUInt8().DestroyValueInst.deadEnd = isDeadEnd;
}

public:
Expand Down Expand Up @@ -8843,6 +8849,10 @@ class DestroyValueInst
/// If the value being destroyed is a stack allocation of a nonescaping
/// closure, then return the PartialApplyInst that allocated the closure.
PartialApplyInst *getNonescapingClosureAllocation() const;

IsDeadEnd_t isDeadEnd() const {
return IsDeadEnd_t(sharedUInt8().DestroyValueInst.deadEnd);
}
};

class MoveValueInst
Expand Down
4 changes: 3 additions & 1 deletion include/swift/SIL/SILNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,9 @@ class alignas(8) SILNode :
SHARED_FIELD(AddressToPointerInst, bool needsStackProtection);
SHARED_FIELD(IndexAddrInst, bool needsStackProtection);
SHARED_FIELD(HopToExecutorInst, bool mandatory);
SHARED_FIELD(DestroyValueInst, bool poisonRefs);
SHARED_FIELD(DestroyValueInst, uint8_t
poisonRefs : 1,
deadEnd : 1);
SHARED_FIELD(EndCOWMutationInst, bool keepUnique);
SHARED_FIELD(ConvertFunctionInst, bool withoutActuallyEscaping);
SHARED_FIELD(BeginCOWMutationInst, bool native);
Expand Down
2 changes: 2 additions & 0 deletions lib/SIL/IR/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2231,6 +2231,8 @@ class SILPrinter : public SILInstructionVisitor<SILPrinter> {
void visitDestroyValueInst(DestroyValueInst *I) {
if (I->poisonRefs())
*this << "[poison] ";
if (I->isDeadEnd())
*this << "[dead_end] ";
*this << getIDAndType(I->getOperand());
}

Expand Down
21 changes: 17 additions & 4 deletions lib/SIL/Parser/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3352,11 +3352,24 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B,
break;
}
case SILInstructionKind::DestroyValueInst: {
bool poisonRefs = false;
if (parseSILOptional(poisonRefs, *this, "poison")
|| parseTypedValueRef(Val, B) || parseSILDebugLocation(InstLoc, B))
PoisonRefs_t poisonRefs = DontPoisonRefs;
IsDeadEnd_t isDeadEnd = IsntDeadEnd;
StringRef attributeName;
SourceLoc attributeLoc;
while (parseSILOptional(attributeName, attributeLoc, *this)) {
if (attributeName == "poison")
poisonRefs = PoisonRefs;
else if (attributeName == "dead_end")
isDeadEnd = IsDeadEnd;
else {
P.diagnose(attributeLoc, diag::sil_invalid_attribute_for_instruction,
attributeName, "destroy_value");
return true;
}
}
if (parseTypedValueRef(Val, B) || parseSILDebugLocation(InstLoc, B))
return true;
ResultVal = B.createDestroyValue(InstLoc, Val, PoisonRefs_t(poisonRefs));
ResultVal = B.createDestroyValue(InstLoc, Val, poisonRefs, isDeadEnd);
break;
}
case SILInstructionKind::BeginCOWMutationInst: {
Expand Down
4 changes: 4 additions & 0 deletions lib/SIL/Verifier/SILVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3405,6 +3405,10 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
require(!fnConv.useLoweredAddresses() || F.hasOwnership(),
"destroy_value is only valid in functions with qualified "
"ownership");
if (I->isDeadEnd()) {
require(getDeadEndBlocks().isDeadEnd(I->getParentBlock()),
"a dead_end destroy_value must be in a dead-end block");
}
}

void checkReleaseValueInst(ReleaseValueInst *I) {
Expand Down
3 changes: 2 additions & 1 deletion lib/Serialization/DeserializeSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2323,12 +2323,13 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn,
case SILInstructionKind::DestroyValueInst: {
assert(RecordKind == SIL_ONE_OPERAND && "Layout should be OneOperand.");
PoisonRefs_t poisonRefs = PoisonRefs_t(Attr & 0x1);
IsDeadEnd_t isDeadEnd = IsDeadEnd_t((Attr >> 1) & 0x1);
ResultInst = Builder.createDestroyValue(
Loc,
getLocalValue(
Builder.maybeGetFunction(), ValID,
getSILType(MF->getType(TyID), (SILValueCategory)TyCategory, Fn)),
poisonRefs);
poisonRefs, isDeadEnd);
break;
}

Expand Down
3 changes: 1 addition & 2 deletions lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR =
878; // immortal bit in LifetimeDependence
const uint16_t SWIFTMODULE_VERSION_MINOR = 879; // dead_end flag on destroy_value

/// A standard hash seed used for all string hashes in a serialized module.
///
Expand Down
2 changes: 1 addition & 1 deletion lib/Serialization/SerializeSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1575,7 +1575,7 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) {
} else if (auto *HTE = dyn_cast<HopToExecutorInst>(&SI)) {
Attr = HTE->isMandatory();
} else if (auto *DVI = dyn_cast<DestroyValueInst>(&SI)) {
Attr = DVI->poisonRefs();
Attr = unsigned(DVI->poisonRefs()) | (unsigned(DVI->isDeadEnd()) << 1);
} else if (auto *BCMI = dyn_cast<BeginCOWMutationInst>(&SI)) {
Attr = BCMI->isNative();
} else if (auto *ECMI = dyn_cast<EndCOWMutationInst>(&SI)) {
Expand Down
38 changes: 38 additions & 0 deletions test/SIL/Parser/basic2.sil
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class Klass {}
var rhs: Klass
}

sil @getKlass : $@convention(thin) () -> (@owned Klass)

// CHECK-LABEL: sil [ossa] @test_copy_release_value
// CHECK: bb0([[T0:%[0-9]+]] : @owned $Builtin.NativeObject):
// CHECK-NEXT: [[COPY_RESULT:%.*]] = copy_value [[T0]] : $Builtin.NativeObject
Expand Down Expand Up @@ -347,3 +349,39 @@ bb1:
extend_lifetime %k : $Klass
br bb1
}

// CHECK-LABEL: sil [ossa] @test_destroy_value : {{.*}} {
// CHECK: destroy_value
// CHECK: destroy_value [poison]
// CHECK: destroy_value [dead_end]
// CHECK: destroy_value [poison] [dead_end]
// CHECK-LABEL: } // end sil function 'test_destroy_value'
sil [ossa] @test_destroy_value : $@convention(thin) () -> () {
entry:
%getKlass = function_ref @getKlass : $@convention(thin) () -> (@owned Klass)
cond_br undef, good, bad

good:
cond_br undef, gleft, gright
gleft:
%k1 = apply %getKlass() : $@convention(thin) () -> (@owned Klass)
destroy_value %k1 : $Klass
br exit
gright:
%k2 = apply %getKlass() : $@convention(thin) () -> (@owned Klass)
destroy_value [poison] %k2 : $Klass
br exit
exit:
%retval = tuple()
return %retval : $()
bad:
cond_br undef, bleft, bright
bleft:
%k3 = apply %getKlass() : $@convention(thin) () -> (@owned Klass)
destroy_value [dead_end] %k3 : $Klass
unreachable
bright:
%k4 = apply %getKlass() : $@convention(thin) () -> (@owned Klass)
destroy_value [poison] [dead_end] %k4 : $Klass
unreachable
}
57 changes: 57 additions & 0 deletions test/SIL/Serialization/basic2.sil
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

import Builtin

class C {}
sil @getC : $@convention(thin) () -> (@owned C)

// CHECK-LABEL: sil [ossa] @test_allocbox_moveable_value_debuginfo_flag : $@convention(thin) () -> () {
// CHECK: alloc_box [moveable_value_debuginfo] ${ var Builtin.NativeObject }
// CHECK: } // end sil function 'test_allocbox_moveable_value_debuginfo_flag'
Expand Down Expand Up @@ -59,6 +62,60 @@ bb0(%0 : @owned $Builtin.NativeObject):
return %9999 : $()
}

// CHECK-LABEL: sil [ossa] @test_destroy_value : {{.*}} {
// CHECK: cond_br undef, [[GOOD:bb[0-9]+]], [[BAD:bb[0-9]+]]
// CHECK: [[BAD]]:
// CHECK: cond_br undef, [[BLEFT:bb[0-9]+]], [[BRIGHT:bb[0-9]+]]
// CHECK: [[BRIGHT]]:
// CHECK: %3 = apply %0()
// CHECK: destroy_value [poison] [dead_end] %3
// CHECK: unreachable
// CHECK: [[BLEFT]]:
// CHECK: %6 = apply %0()
// CHECK: destroy_value [dead_end] %6
// CHECK: unreachable
// CHECK: [[GOOD]]:
// CHECK: cond_br undef, [[GLEFT:bb[0-9]+]], [[GRIGHT:bb[0-9]+]]
// CHECK: [[GRIGHT]]:
// CHECK: %10 = apply %0()
// CHECK: destroy_value [poison] %10
// CHECK: br [[EXIT:bb[0-9]+]]
// CHECK: [[GLEFT]]:
// CHECK: %13 = apply %0()
// CHECK: destroy_value %13
// CHECK: br [[EXIT]]
// CHECK: [[EXIT]]:
// CHECK-LABEL: } // end sil function 'test_destroy_value'
sil [ossa] @test_destroy_value : $@convention(thin) () -> () {
entry:
%getC = function_ref @getC : $@convention(thin) () -> (@owned C)
cond_br undef, good, bad

good:
cond_br undef, gleft, gright
gleft:
%k1 = apply %getC() : $@convention(thin) () -> (@owned C)
destroy_value %k1 : $C
br exit
gright:
%k2 = apply %getC() : $@convention(thin) () -> (@owned C)
destroy_value [poison] %k2 : $C
br exit
exit:
%retval = tuple()
return %retval : $()
bad:
cond_br undef, bleft, bright
bleft:
%k3 = apply %getC() : $@convention(thin) () -> (@owned C)
destroy_value [dead_end] %k3 : $C
unreachable
bright:
%k4 = apply %getC() : $@convention(thin) () -> (@owned C)
destroy_value [poison] [dead_end] %k4 : $C
unreachable
}

// CHECK-LABEL: sil [ossa] @test_explicit_copy_addr : $@convention(thin) (@owned Builtin.NativeObject) -> () {
// CHECK: explicit_copy_addr %{{[0-9]+}} to [init] %{{[0-9]+}} :
// CHECK: explicit_copy_addr [take] %{{[0-9]+}} to [init] %{{[0-9]+}} :
Expand Down
37 changes: 37 additions & 0 deletions test/SIL/cloning.sil
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,40 @@ entry(%o : @owned $Builtin.NativeObject):
%retval = apply %callee(%o) : $@convention(thin) (@owned Builtin.NativeObject) -> ()
return %retval : $()
}

sil [ossa] [always_inline] @callee_destroy_value : $@convention(thin) (@owned X) -> () {
entry(%x : @owned $X):
cond_br undef, good, bad
good:
cond_br undef, gleft, gright
gleft:
destroy_value %x : $X
br exit
gright:
destroy_value [poison] %x : $X
br exit
exit:
%retval = tuple()
return %retval : $()
bad:
cond_br undef, bleft, bright
bleft:
destroy_value [dead_end] %x : $X
unreachable
bright:
destroy_value [poison] [dead_end] %x : $X
unreachable
}

// CHECK-LABEL: sil [ossa] @caller_destroy_value : {{.*}} {
// CHECK: destroy_value
// CHECK: destroy_value [poison]
// CHECK: destroy_value [dead_end]
// CHECK: destroy_value [poison] [dead_end]
// CHECK-LABEL: } // end sil function 'caller_destroy_value'
sil [ossa] @caller_destroy_value : $@convention(thin) (@owned X) -> () {
entry(%x : @owned $X):
%callee = function_ref @callee_destroy_value : $@convention(thin) (@owned X) -> ()
%res = apply %callee(%x) : $@convention(thin) (@owned X) -> ()
return %res : $()
}
13 changes: 13 additions & 0 deletions test/SIL/verifier_failures.sil
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,16 @@ sil [ossa] @end_borrow_1_addr_alloc_stack : $@convention(thin) () -> () {
%retval = tuple ()
return %retval : $()
}

// CHECK-LABEL: Begin Error in function destroy_value_dead_end
// CHECK: SIL verification failed: a dead_end destroy_value must be in a dead-end block
// CHECK: Verifying instruction:
// CHECK: [[ARGUMENT:%[^,]+]] = argument
// CHECK: -> destroy_value [dead_end] [[ARGUMENT]]
// CHECK-LABEL: End Error in function destroy_value_dead_end
sil [ossa] @destroy_value_dead_end : $@convention(thin) (@owned C) -> () {
entry(%c : @owned $C):
destroy_value [dead_end] %c : $C
%retval = tuple()
return %retval : $()
}

0 comments on commit a8cc3bf

Please sign in to comment.