Files
Erik Eckstein 0f0aa0c17b Optimizer: require that there are no unreachable blocks and infinite loops in OSSA
These two new invariants eliminate corner cases which caused bugs if optimization didn't handle them.
Also, it will significantly simplify lifetime completion.

The implementation basically consists of these changes:
* add a flag in SILFunction which tells optimization if they need to take care of infinite loops
* add a utility to break infinite loops
* let all optimizations remove unreachable blocks and break infinite loops if necessary
* add verification to check the new SIL invariants

The new `breakIfniniteLoops` utility breaks infinite loops in the control flow by inserting an "artificial" loop exit to a new dead-end block with an `unreachable`.
It inserts a `cond_br` with a `builtin "infinite_loop_true_condition"`:
```
bb0:
  br bb1
bb1:
  br bb1              // back-end branch
```
->
```
bb0:
  br bb1
bb1:
  %1 = builtin "infinite_loop_true_condition"() // always true, but the compiler doesn't know
  cond_br %1, bb2, bb3
bb2:                  // new back-end block
  br bb1
bb3:                  // new dead-end block
  unreachable
```
2026-01-22 17:41:23 +01:00

152 lines
5.3 KiB
Swift

//===--- ControlFlowUtils.swift -------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import SIL
import OptimizerBridging
/// Breaks infinite loops in the control flow by inserting an "artificial" loop exit to a new
/// dead-end block with an `unreachable`.
///
/// Inserts a `cond_br` with a `builtin "infinite_loop_true_condition"`:
/// ```
/// bb0:
/// br bb1
/// bb1:
/// br bb1 // back-end branch
/// ```
/// ->
/// ```
/// bb0:
/// br bb1
/// bb1:
/// %1 = builtin "infinite_loop_true_condition"() // always true, but the compiler doesn't know
/// cond_br %1, bb2, bb3
/// bb2: // new back-end block
/// br bb1
/// bb3: // new dead-end block
/// unreachable
/// ```
///
func breakInfiniteLoops(in function: Function, _ context: FunctionPassContext) {
context.setNeedBreakInfiniteLoops(to: false)
guard function.hasOwnership else {
// The algorithm relies on not having critical edges in the CFG. Therefore it only works for OSSA which
// enforces no critical edges.
return
}
var noInfiniteLoops = findBlocksWhichDontEndInInfiniteLoops(in: function, context)
defer { noInfiniteLoops.deinitialize() }
var changed: Bool
repeat {
changed = false
for block in function.blocks where block.isEntryToInfiniteLoopRegion(noInfiniteLoops) {
let newDeadEndBlock = breakInfiniteLoop(startingAt: block, context)
noInfiniteLoops.transitivelyAddBlockWithPredecessors(startingAt: newDeadEndBlock)
changed = true
}
} while changed
}
/// Returns a BasicBlockWorklist containing all blocks in `function` from which there is a path
/// to a function exit or to a block with an `unreachable`.
/// All other blocks are inside an infinite loop or on a path to an infinite loop (e.g. an infinite
/// loop's pre-header block).
private func findBlocksWhichDontEndInInfiniteLoops(in function: Function,
_ context: FunctionPassContext
) -> BasicBlockWorklist {
var noInfiniteLoops = BasicBlockWorklist(context)
for block in function.blocks {
if block.successors.isEmpty {
noInfiniteLoops.transitivelyAddBlockWithPredecessors(startingAt: block)
}
}
return noInfiniteLoops
}
private func breakInfiniteLoop(startingAt startBlock: BasicBlock, _ context: FunctionPassContext) -> BasicBlock {
let backEdgeBranch = getInfiniteLoopBackEdgeBranch(reachableFrom: startBlock, context)
let newBackEdgeBlock = context.createBlock(after: backEdgeBranch.parentBlock)
Builder(atEndOf: newBackEdgeBlock, location: backEdgeBranch.location, context)
.createBranch(to: backEdgeBranch.targetBlock, arguments: Array(backEdgeBranch.operands.values))
let deadEndBlock = context.createBlock(after: newBackEdgeBlock)
Builder(atEndOf: deadEndBlock, location: backEdgeBranch.location, context)
.createUnreachable()
let builder = Builder(before: backEdgeBranch, context)
let trueValue = builder.createBuiltin(name: "infinite_loop_true_condition",
type: context.getBuiltinIntegerType(bitWidth: 1),
arguments: [])
builder.createCondBranch(condition: trueValue, trueBlock: newBackEdgeBlock, falseBlock: deadEndBlock)
context.erase(instruction: backEdgeBranch)
return deadEndBlock
}
private func getInfiniteLoopBackEdgeBranch(reachableFrom startBlock: BasicBlock,
_ context: FunctionPassContext
) -> BranchInst {
var visited = BasicBlockSet(context)
defer { visited.deinitialize() }
var block = startBlock
while true {
guard let succ = block.successors.first else {
fatalError("all blocks in an inifinite loop region must have at least one successor")
}
guard visited.insert(succ) else {
break
}
block = succ
}
guard let backEdgeBranch = block.terminator as? BranchInst else {
fatalError("back-edge of a loop must be a branch instruction")
}
return backEdgeBranch
}
private extension BasicBlock {
func isEntryToInfiniteLoopRegion(_ noInfiniteLoops: BasicBlockWorklist) -> Bool {
return !noInfiniteLoops.hasBeenPushed(self) &&
(predecessors.contains{ noInfiniteLoops.hasBeenPushed($0) } ||
predecessors.isEmpty)
}
}
func registerControlFlowUtils() {
BridgedOptimizerUtilities.registerControlFlowUtils(
{ (bridgedCtxt: BridgedContext, bridgedFunction: BridgedFunction) in
let context = FunctionPassContext(_bridged: bridgedCtxt)
let function = bridgedFunction.function;
breakInfiniteLoops(in: function, context)
}
)
}
//===--------------------------------------------------------------------===//
// Tests
//===--------------------------------------------------------------------===//
let breakInfiniteLoopsTest = FunctionTest("break_infinite_loops") {
function, arguments, context in
breakInfiniteLoops(in: function, context)
}