LifetimeDependenceScopeFixup: crash handling dead-end coroutine

When extending a coroutine, handle the end_borrow instruction used to end a
coroutine lifetime at a dead-end block.

Fixes rdar://153479358 (Compiler crash when force-unwrapping optional ~Copyable type)

(cherry picked from commit 5b5f370ce1)
This commit is contained in:
Andrew Trick
2025-06-16 18:01:00 -07:00
parent d242750ac9
commit 9bcc3f41dc
4 changed files with 136 additions and 34 deletions

View File

@@ -801,7 +801,7 @@ extension ExtendableScope {
func canExtend(beginApply: BeginApplyInst, over range: inout InstructionRange, _ context: some Context) -> Bool {
let canEndAtBoundary = { (boundaryInst: Instruction) in
switch beginApply.endReaches(block: boundaryInst.parentBlock, context) {
case .abortReaches, .endReaches:
case .abortReaches, .endReaches, .deadEndReaches:
return true
case .none:
return false
@@ -929,64 +929,62 @@ private extension BeginApplyInst {
return builder.createEndApply(beginApply: self)
case .abortReaches:
return builder.createAbortApply(beginApply: self)
case .deadEndReaches:
return builder.createEndBorrow(of: self.token)
}
}
enum EndReaches {
case endReaches
case abortReaches
case deadEndReaches
}
/// Return the single kind of coroutine termination that reaches 'reachableBlock' or nil.
func endReaches(block reachableBlock: BasicBlock, _ context: some Context) -> EndReaches? {
var endBlocks = BasicBlockSet(context)
var abortBlocks = BasicBlockSet(context)
// TODO: use InlineArray<3> once bootstrapping is fixed.
var endingBlockMap: [(EndReaches, BasicBlockSet)] = [
(.endReaches, BasicBlockSet(context)),
(.abortReaches, BasicBlockSet(context)),
(.deadEndReaches, BasicBlockSet(context))
]
defer {
endBlocks.deinitialize()
abortBlocks.deinitialize()
for index in endingBlockMap.indices {
endingBlockMap[index].1.deinitialize()
}
}
for endInst in endInstructions {
let endKind: EndReaches
switch endInst {
case let endApply as EndApplyInst:
// Cannot extend the scope of a coroutine when the resume produces a value.
if !endApply.type.isEmpty(in: parentFunction) {
return nil
}
endBlocks.insert(endInst.parentBlock)
endKind = .endReaches
case is AbortApplyInst:
abortBlocks.insert(endInst.parentBlock)
endKind = .abortReaches
case is EndBorrowInst:
endKind = .deadEndReaches
default:
fatalError("invalid begin_apply ending instruction")
}
let endingBlocksIndex = endingBlockMap.firstIndex(where: { $0.0 == endKind })!
endingBlockMap[endingBlocksIndex].1.insert(endInst.parentBlock)
}
var endReaches: EndReaches?
var backwardWalk = BasicBlockWorklist(context)
defer { backwardWalk.deinitialize() }
let backwardVisit = { (block: BasicBlock) -> WalkResult in
if endBlocks.contains(block) {
switch endReaches {
case .none:
endReaches = .endReaches
break
case .endReaches:
break
case .abortReaches:
return .abortWalk
for (endKind, endingBlocks) in endingBlockMap {
if endingBlocks.contains(block) {
if let endReaches = endReaches, endReaches != endKind {
return .abortWalk
}
endReaches = endKind
return .continueWalk
}
return .continueWalk
}
if abortBlocks.contains(block) {
switch endReaches {
case .none:
endReaches = .abortReaches
break
case .abortReaches:
break
case .endReaches:
return .abortWalk
}
return .continueWalk
}
if block == self.parentBlock {
// the insertion point is not dominated by the coroutine