Files
swift-mirror/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBranch.swift
Erik Eckstein d9826f6043 SimplifyBranch: don't leave an empty block after merging two basic blocks
Although empty blocks are cleaned up at the end of every Simplification pass, it's not legal SIL to have empty blocks and subsequent simplification passes may crash because of this.

rdar://115169880
2023-09-11 14:11:46 +02:00

116 lines
3.1 KiB
Swift

//===--- SimplifyBranch.swift ---------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 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
extension BranchInst : OnoneSimplifyable {
func simplify(_ context: SimplifyContext) {
tryMergeWithTargetBlock(context)
}
}
private extension BranchInst {
func tryMergeWithTargetBlock(_ context: SimplifyContext) {
if canMergeWithTargetBlock {
mergeWithTargetBlock(context)
}
}
var canMergeWithTargetBlock: Bool {
// We can only merge if there is a 1:1 relation to the target block.
guard let pred = targetBlock.singlePredecessor else {
return false
}
assert(pred == parentBlock)
// Ignore self cycles
if targetBlock == parentBlock {
return false
}
if hasInvalidDominanceCycle {
return false
}
return true
}
func mergeWithTargetBlock(_ context: SimplifyContext) {
let targetBB = targetBlock
let parentBB = parentBlock
for (argIdx, op) in operands.enumerated() {
targetBB.arguments[argIdx].uses.replaceAll(with: op.value, context)
}
targetBB.eraseAllArguments(context)
if context.preserveDebugInfo {
let builder = Builder(before: self, context)
builder.createDebugStep()
}
context.erase(instruction: self)
// Move instruction from the smaller block to the larger block.
// The order is essential because if many blocks are merged and this is done
// in the wrong order, we end up with quadratic complexity.
//
if parentBB.hasLessInstructions(than: targetBB) &&
parentBB != parentBB.parentFunction.entryBlock {
for pred in parentBB.predecessors {
pred.terminator.replaceBranchTarget(from: parentBB, to: targetBB, context)
}
parentBB.moveAllInstructions(toBeginOf: targetBB, context)
parentBB.moveAllArguments(to: targetBB, context)
context.erase(block: parentBB)
} else {
targetBB.moveAllInstructions(toEndOf: parentBB, context)
context.erase(block: targetBB)
}
}
}
private extension BasicBlock {
func hasLessInstructions(than otherBlock: BasicBlock) -> Bool {
var insts = instructions
var otherInsts = otherBlock.instructions
while true {
if otherInsts.next() == nil {
return false
}
if insts.next() == nil {
return true
}
}
}
}
private extension BranchInst {
// True if this block is part of an unreachable cfg cycle, where an argument dominates itself.
// For example:
// ```
// bb1(arg1): // preds: bb2
// br bb2
//
// bb2: // preds: bb1
// br bb1(arg1)
// ```
var hasInvalidDominanceCycle: Bool {
for (argIdx, op) in operands.enumerated() {
if targetBlock.arguments[argIdx] == op.value {
return true
}
}
return false
}
}