Skip to content

Commit 1da7173

Browse files
authored
Merge pull request swiftlang#39126 from atrick/verify-ossa-term
Enforce OSSA block terminator invariants
2 parents d798d3f + 6525a61 commit 1da7173

File tree

60 files changed

+1026
-567
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1026
-567
lines changed

include/swift/SIL/SILBuilder.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2221,6 +2221,11 @@ class SILBuilder {
22212221
BranchInst *createBranch(SILLocation Loc, SILBasicBlock *TargetBlock,
22222222
OperandValueArrayRef Args);
22232223

2224+
// This only creates the terminator, not the results. Create the results with
2225+
// OwnershipForwardingTermInst::createResult() and
2226+
// SwitchEnumInst::createDefaultResult() to ensure that the result ownership
2227+
// is correct (it must be consistent with the switch_enum's forwarding
2228+
// ownership, which may differ from \p Operand's ownership).
22242229
SwitchValueInst *
22252230
createSwitchValue(SILLocation Loc, SILValue Operand, SILBasicBlock *DefaultBB,
22262231
ArrayRef<std::pair<SILValue, SILBasicBlock *>> CaseBBs) {
@@ -2232,16 +2237,14 @@ class SILBuilder {
22322237
SILLocation Loc, SILValue Operand, SILBasicBlock *DefaultBB,
22332238
ArrayRef<std::pair<EnumElementDecl *, SILBasicBlock *>> CaseBBs,
22342239
Optional<ArrayRef<ProfileCounter>> CaseCounts = None,
2235-
ProfileCounter DefaultCount = ProfileCounter()) {
2236-
return createSwitchEnum(Loc, Operand, DefaultBB, CaseBBs, CaseCounts,
2237-
DefaultCount, Operand.getOwnershipKind());
2238-
}
2240+
ProfileCounter DefaultCount = ProfileCounter());
22392241

22402242
SwitchEnumInst *createSwitchEnum(
22412243
SILLocation Loc, SILValue Operand, SILBasicBlock *DefaultBB,
22422244
ArrayRef<std::pair<EnumElementDecl *, SILBasicBlock *>> CaseBBs,
22432245
Optional<ArrayRef<ProfileCounter>> CaseCounts,
2244-
ProfileCounter DefaultCount, ValueOwnershipKind forwardingOwnershipKind) {
2246+
ProfileCounter DefaultCount,
2247+
ValueOwnershipKind forwardingOwnershipKind) {
22452248
return insertTerminator(SwitchEnumInst::create(
22462249
getSILDebugLocation(Loc), Operand, DefaultBB, CaseBBs, getFunction(),
22472250
CaseCounts, DefaultCount, forwardingOwnershipKind));

include/swift/SIL/SILInstruction.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,7 @@ class OwnershipForwardingMixin {
10561056
ValueOwnershipKind ownershipKind)
10571057
: ownershipKind(ownershipKind) {
10581058
assert(isa(kind) && "Invalid subclass?!");
1059+
assert(ownershipKind && "invalid forwarding ownership");
10591060
}
10601061

10611062
public:
@@ -7877,6 +7878,7 @@ class TermInst : public NonValueInstruction {
78777878
}
78787879
};
78797880

7881+
// Forwards the first operand to a result in each successor block.
78807882
class OwnershipForwardingTermInst : public TermInst,
78817883
public OwnershipForwardingMixin {
78827884
protected:
@@ -7903,6 +7905,11 @@ class OwnershipForwardingTermInst : public TermInst,
79037905
return kind == SILInstructionKind::SwitchEnumInst ||
79047906
kind == SILInstructionKind::CheckedCastBranchInst;
79057907
}
7908+
7909+
SILValue getOperand() const { return getAllOperands()[0].get(); }
7910+
7911+
/// Create a result for this terminator on the given successor block.
7912+
SILPhiArgument *createResult(SILBasicBlock *succ, SILType resultTy);
79067913
};
79077914

79087915
/// UnreachableInst - Position in the code which would be undefined to reach.
@@ -8642,6 +8649,14 @@ class SwitchEnumInst
86428649
SILFunction &F, Optional<ArrayRef<ProfileCounter>> CaseCounts,
86438650
ProfileCounter DefaultCount,
86448651
ValueOwnershipKind forwardingOwnershipKind);
8652+
8653+
public:
8654+
/// Create the default result for a partially built switch_enum.
8655+
/// Returns nullptr if no default argument is needed.
8656+
SILPhiArgument *createDefaultResult();
8657+
8658+
/// Create the .some result for an optional switch_enum.
8659+
SILPhiArgument *createOptionalSomeResult();
86458660
};
86468661
/// A switch on an enum's discriminator in memory.
86478662
class SwitchEnumAddrInst

include/swift/SIL/SILInstructionWorklist.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,10 @@ class SILInstructionWorklist : SILInstructionWorklistBase {
185185
/// Intended to be called during visitation after \p instruction has been
186186
/// removed from the worklist.
187187
///
188-
/// \p instruction the instruction whose usages will be replaced
188+
/// \p instruction the instruction whose usages will be replaced. This
189+
/// instruction may already be deleted to maintain valid SIL. For example, if
190+
/// it was a block terminator.
191+
///
189192
/// \p result the instruction whose usages will replace \p instruction
190193
///
191194
/// \return whether the instruction was deleted or modified.

lib/SIL/IR/SILBuilder.cpp

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -601,13 +601,74 @@ void SILBuilder::emitScopedBorrowOperation(SILLocation loc, SILValue original,
601601
createEndBorrow(loc, value);
602602
}
603603

604+
/// Attempt to propagate ownership from \p operand to the returned forwarding
605+
/// ownership where the forwarded value has type \p targetType. If this fails,
606+
/// return Owned forwarding ownership instead.
607+
///
608+
/// Propagation only fails when \p operand is dynamically trivial, as indicated
609+
/// by ownership None, AND \p targetType is statically nontrivial.
610+
///
611+
/// Example:
612+
///
613+
/// %e = enum $Optional<AnyObject>, #Optional.none!enumelt
614+
/// switch_enum %e : $Optional<AnyObject>,
615+
/// case #Optional.some!enumelt: bb2...,
616+
/// forwarding: @owned
617+
/// bb2(%arg : @owned AnyObject):
618+
///
619+
/// Example:
620+
///
621+
/// %mt = metatype $@thick C.Type
622+
/// checked_cast_br %mt : $@thick C.Type to AnyObject.Type, bb1, bb2,
623+
/// forwarding: @owned
624+
/// bb1(%arg : @owned AnyObject.Type):
625+
///
626+
/// If the forwarded value is statically known nontrivial, then the forwarding
627+
/// ownership cannot be None. Such a result is unreachable, but the SIL on that
628+
/// path must still be valid. When creating ownership out of thin air, default
629+
/// to Owned because that allows the value to be consumed without generating a
630+
/// copy. This does require the client code to handle ending the lifetime of an
631+
/// owned result even if the input was passed as guaranteed.
632+
///
633+
/// Note: For simplicitly, ownership None is not propagated for any statically
634+
/// nontrivial result, even if \p targetType may also be dynamically
635+
/// trivial. For example, the operand of a switch_enum could be a nested enum
636+
/// such that all switch cases may be dynamically trivial. Or a checked_cast_br
637+
/// could cast from one dynamically trivial enum to another. Figuring out
638+
/// whether the dynamically trivial operand value maps onto a dynamically
639+
/// trivial terminator result would be very complex with no practical benefit.
640+
static ValueOwnershipKind deriveForwardingOwnership(SILValue operand,
641+
SILType targetType,
642+
SILFunction &func) {
643+
if (operand.getOwnershipKind() != OwnershipKind::None
644+
|| targetType.isTrivial(func)) {
645+
return operand.getOwnershipKind();
646+
}
647+
return OwnershipKind::Owned;
648+
}
649+
650+
SwitchEnumInst *SILBuilder::createSwitchEnum(
651+
SILLocation Loc, SILValue Operand, SILBasicBlock *DefaultBB,
652+
ArrayRef<std::pair<EnumElementDecl *, SILBasicBlock *>> CaseBBs,
653+
Optional<ArrayRef<ProfileCounter>> CaseCounts,
654+
ProfileCounter DefaultCount) {
655+
// Consider the operand's type to be the target's type since a switch
656+
// covers all cases including the default argument.
657+
auto forwardingOwnership =
658+
deriveForwardingOwnership(Operand, Operand->getType(), getFunction());
659+
return createSwitchEnum(Loc, Operand, DefaultBB, CaseBBs, CaseCounts,
660+
DefaultCount, forwardingOwnership);
661+
}
662+
604663
CheckedCastBranchInst *SILBuilder::createCheckedCastBranch(
605664
SILLocation Loc, bool isExact, SILValue op,
606665
SILType destLoweredTy, CanType destFormalTy,
607666
SILBasicBlock *successBB, SILBasicBlock *failureBB,
608667
ProfileCounter target1Count, ProfileCounter target2Count) {
668+
auto forwardingOwnership =
669+
deriveForwardingOwnership(op, destLoweredTy, getFunction());
609670
return createCheckedCastBranch(Loc, isExact, op, destLoweredTy, destFormalTy,
610-
successBB, failureBB, op.getOwnershipKind(),
671+
successBB, failureBB, forwardingOwnership,
611672
target1Count, target2Count);
612673
}
613674

@@ -619,10 +680,11 @@ CheckedCastBranchInst *SILBuilder::createCheckedCastBranch(
619680
assert((!hasOwnership() || !failureBB->getNumArguments() ||
620681
failureBB->getArgument(0)->getType() == op->getType()) &&
621682
"failureBB's argument doesn't match incoming argument type");
683+
622684
return insertTerminator(CheckedCastBranchInst::create(
623685
getSILDebugLocation(Loc), isExact, op, destLoweredTy, destFormalTy,
624-
successBB, failureBB, getFunction(), target1Count,
625-
target2Count, forwardingOwnershipKind));
686+
successBB, failureBB, getFunction(), target1Count, target2Count,
687+
forwardingOwnershipKind));
626688
}
627689

628690
void SILBuilderWithScope::insertAfter(SILInstruction *inst,

lib/SIL/IR/SILInstructions.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2895,3 +2895,68 @@ ReturnInst::ReturnInst(SILFunction &func, SILDebugLocation debugLoc,
28952895
"Conflicting ownership kinds when creating term inst from function "
28962896
"result info?!");
28972897
}
2898+
2899+
// This may be called in an invalid SIL state. SILCombine creates new
2900+
// terminators in non-terminator position and defers deleting the original
2901+
// terminator until after all modification.
2902+
SILPhiArgument *OwnershipForwardingTermInst::createResult(SILBasicBlock *succ,
2903+
SILType resultTy) {
2904+
// The forwarding instruction declares a forwarding ownership kind that
2905+
// determines the ownership of its results.
2906+
auto resultOwnership = getForwardingOwnershipKind();
2907+
2908+
// Trivial results have no ownership. Although it is valid for a trivially
2909+
// typed value to have ownership, it is never necessary and less efficient.
2910+
if (resultTy.isTrivial(*getFunction())) {
2911+
resultOwnership = OwnershipKind::None;
2912+
2913+
} else if (resultOwnership == OwnershipKind::None) {
2914+
// switch_enum strangely allows results to acquire ownership out of thin
2915+
// air whenever the operand has no ownership and result is nontrivial:
2916+
// %e = enum $Optional<AnyObject>, #Optional.none!enumelt
2917+
// switch_enum %e : $Optional<AnyObject>,
2918+
// case #Optional.some!enumelt: bb2...
2919+
// bb2(%arg : @guaranteed T):
2920+
//
2921+
// We can either use None or Guaranteed. None would correctly propagate
2922+
// ownership and would maintain the invariant that guaranteed values are
2923+
// always within a borrow scope. However it would result in a nontrivial
2924+
// type without ownership. The lifetime verifier does not like that.
2925+
resultOwnership = OwnershipKind::Guaranteed;
2926+
}
2927+
return succ->createPhiArgument(resultTy, resultOwnership);
2928+
}
2929+
2930+
SILPhiArgument *SwitchEnumInst::createDefaultResult() {
2931+
auto *f = getFunction();
2932+
if (!f->hasOwnership())
2933+
return nullptr;
2934+
2935+
if (!hasDefault())
2936+
return nullptr;
2937+
2938+
assert(getDefaultBB()->getNumArguments() == 0 && "precondition");
2939+
2940+
auto enumTy = getOperand()->getType();
2941+
NullablePtr<EnumElementDecl> uniqueCase = getUniqueCaseForDefault();
2942+
2943+
// Without a unique default case, the OSSA result simply forwards the
2944+
// switch_enum operand.
2945+
if (!uniqueCase)
2946+
return createResult(getDefaultBB(), enumTy);
2947+
2948+
// With a unique default case, the result is materialized exactly the same way
2949+
// as a matched result. It has a value iff the unique case has a payload.
2950+
if (!uniqueCase.get()->hasAssociatedValues())
2951+
return nullptr;
2952+
2953+
auto resultTy = enumTy.getEnumElementType(uniqueCase.get(), f->getModule(),
2954+
f->getTypeExpansionContext());
2955+
return createResult(getDefaultBB(), resultTy);
2956+
}
2957+
2958+
SILPhiArgument *SwitchEnumInst::createOptionalSomeResult() {
2959+
auto someDecl = getModule().getASTContext().getOptionalSomeDecl();
2960+
auto someBB = getCaseDestination(someDecl);
2961+
return createResult(someBB, getOperand()->getType().unwrapOptionalType());
2962+
}

0 commit comments

Comments
 (0)