Skip to content

Commit 3f35a1d

Browse files
committed
Swift Optimizer: add Onone simplification of branch instructions
1 parent f1c6ed6 commit 3f35a1d

File tree

3 files changed

+215
-0
lines changed

3 files changed

+215
-0
lines changed

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
swift_compiler_sources(Optimizer
1010
SimplifyApply.swift
1111
SimplifyBeginCOWMutation.swift
12+
SimplifyBranch.swift
1213
SimplifyGlobalValue.swift
1314
SimplifyStrongRetainRelease.swift)
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//===--- SimplifyBranch.swift ---------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SIL
14+
15+
extension BranchInst : OnoneSimplifyable {
16+
func simplify(_ context: SimplifyContext) {
17+
tryMergeWithTargetBlock(context)
18+
}
19+
}
20+
21+
private extension BranchInst {
22+
func tryMergeWithTargetBlock(_ context: SimplifyContext) {
23+
if canMergeWithTargetBlock {
24+
mergeWithTargetBlock(context)
25+
}
26+
}
27+
28+
var canMergeWithTargetBlock: Bool {
29+
// We can only merge if there is a 1:1 relation to the target block.
30+
guard let pred = targetBlock.singlePredecessor else {
31+
return false
32+
}
33+
assert(pred == parentBlock)
34+
35+
// Ignore self cycles
36+
if targetBlock == parentBlock {
37+
return false
38+
}
39+
40+
if hasInvalidDominanceCycle {
41+
return false
42+
}
43+
44+
return true
45+
}
46+
47+
func mergeWithTargetBlock(_ context: SimplifyContext) {
48+
let targetBB = targetBlock
49+
let parentBB = parentBlock
50+
51+
for (argIdx, op) in operands.enumerated() {
52+
targetBB.arguments[argIdx].uses.replaceAll(with: op.value, context)
53+
}
54+
targetBB.eraseAllArguments(context)
55+
56+
if context.preserveDebugInfo {
57+
let builder = Builder(before: self, context)
58+
builder.createDebugStep()
59+
}
60+
context.erase(instruction: self)
61+
62+
// Move instruction from the smaller block to the larger block.
63+
// The order is essential because if many blocks are merged and this is done
64+
// in the wrong order, we end up with quadratic complexity.
65+
//
66+
if parentBB.hasLessInstructions(than: targetBB) &&
67+
parentBB != parentBB.parentFunction.entryBlock {
68+
for pred in parentBB.predecessors {
69+
pred.terminator.replaceBranchTarget(from: parentBB, to: targetBB, context)
70+
}
71+
parentBB.moveAllInstructions(toBeginOf: targetBB, context)
72+
parentBB.moveAllArguments(to: targetBB, context)
73+
} else {
74+
targetBB.moveAllInstructions(toEndOf: parentBB, context)
75+
}
76+
}
77+
}
78+
79+
private extension BasicBlock {
80+
func hasLessInstructions(than otherBlock: BasicBlock) -> Bool {
81+
var insts = instructions
82+
var otherInsts = otherBlock.instructions
83+
while true {
84+
if otherInsts.next() == nil {
85+
return false
86+
}
87+
if insts.next() == nil {
88+
return true
89+
}
90+
}
91+
}
92+
}
93+
94+
private extension BranchInst {
95+
96+
// True if this block is part of an unreachable cfg cycle, where an argument dominates itself.
97+
// For example:
98+
// ```
99+
// bb1(arg1): // preds: bb2
100+
// br bb2
101+
//
102+
// bb2: // preds: bb1
103+
// br bb1(arg1)
104+
// ```
105+
var hasInvalidDominanceCycle: Bool {
106+
for (argIdx, op) in operands.enumerated() {
107+
if targetBlock.arguments[argIdx] == op.value {
108+
return true
109+
}
110+
}
111+
return false
112+
}
113+
}

test/SILOptimizer/simplify_branch.sil

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// RUN: %target-sil-opt -enable-sil-verify-all %s -simplification -simplify-instruction=br | %FileCheck %s
2+
// RUN: %target-sil-opt -enable-sil-verify-all %s -onone-simplification -simplify-instruction=br -sil-print-debuginfo | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-ONONE
3+
4+
// REQUIRES: swift_in_compiler
5+
6+
import Swift
7+
import Builtin
8+
9+
// CHECK-LABEL: sil @merge_blocks_same_location1
10+
// CHECK: %0 = integer_literal $Builtin.Int64, 0
11+
// CHECK: fix_lifetime %0 : $Builtin.Int64
12+
// CHECK: return %0 : $Builtin.Int64
13+
// CHECK: } // end sil function 'merge_blocks_same_location1'
14+
sil @merge_blocks_same_location1 : $@convention(thin) () -> Builtin.Int64 {
15+
bb0:
16+
%0 = integer_literal $Builtin.Int64, 0
17+
br bb1(%0 : $Builtin.Int64), loc "a.swift":1:1
18+
19+
bb1(%2 : $Builtin.Int64):
20+
fix_lifetime %2 : $Builtin.Int64, loc "a.swift":1:1
21+
return %2 : $Builtin.Int64
22+
}
23+
24+
// CHECK-LABEL: sil @merge_blocks_same_location2
25+
// CHECK: %0 = integer_literal $Builtin.Int64, 0
26+
// CHECK: fix_lifetime %0 : $Builtin.Int64
27+
// CHECK: return %0 : $Builtin.Int64
28+
// CHECK: } // end sil function 'merge_blocks_same_location2'
29+
sil @merge_blocks_same_location2 : $@convention(thin) () -> Builtin.Int64 {
30+
bb0:
31+
%0 = integer_literal $Builtin.Int64, 0, loc "a.swift":1:1
32+
br bb1(%0 : $Builtin.Int64), loc "a.swift":1:1
33+
34+
bb1(%2 : $Builtin.Int64):
35+
fix_lifetime %2 : $Builtin.Int64
36+
return %2 : $Builtin.Int64
37+
}
38+
39+
// CHECK-LABEL: sil @merge_blocks_different_locations
40+
// CHECK: %0 = integer_literal $Builtin.Int64, 0
41+
// CHECK-ONONE: debug_step , loc "a.swift":2:1
42+
// CHECK: fix_lifetime %0 : $Builtin.Int64
43+
// CHECK: return %0 : $Builtin.Int64
44+
// CHECK: } // end sil function 'merge_blocks_different_locations'
45+
sil @merge_blocks_different_locations : $@convention(thin) () -> Builtin.Int64 {
46+
bb0:
47+
%0 = integer_literal $Builtin.Int64, 0, loc "a.swift":1:1
48+
br bb1(%0 : $Builtin.Int64), loc "a.swift":2:1
49+
50+
bb1(%2 : $Builtin.Int64):
51+
fix_lifetime %2 : $Builtin.Int64, loc "a.swift":3:1
52+
return %2 : $Builtin.Int64
53+
}
54+
55+
// CHECK-LABEL: sil @dont_merge_blocks
56+
// CHECK: bb1:
57+
// CHECK-NEXT: br bb3
58+
// CHECK: bb2:
59+
// CHECK-NEXT: br bb3
60+
// CHECK: } // end sil function 'dont_merge_blocks'
61+
sil @dont_merge_blocks : $@convention(thin) () -> () {
62+
bb0:
63+
cond_br undef, bb1, bb2
64+
65+
bb1:
66+
br bb3
67+
68+
bb2:
69+
br bb3
70+
71+
bb3:
72+
%r = tuple ()
73+
return %r : $()
74+
}
75+
76+
// CHECK-LABEL: sil @dont_merge_block_in_single_cycle
77+
// CHECK: bb1:
78+
// CHECK-NEXT: br bb1
79+
// CHECK: } // end sil function 'dont_merge_block_in_single_cycle'
80+
sil @dont_merge_block_in_single_cycle : $@convention(thin) () -> () {
81+
bb0:
82+
br bb1
83+
84+
bb1:
85+
br bb1
86+
}
87+
88+
// CHECK-LABEL: sil @dont_crash_with_unreachable_invalid_dominance
89+
// CHECK-NOT: bb1
90+
// CHECK: } // end sil function 'dont_crash_with_unreachable_invalid_dominance'
91+
sil @dont_crash_with_unreachable_invalid_dominance : $@convention(thin) (Builtin.Int64) -> Builtin.Int64 {
92+
bb0(%0 : $Builtin.Int64):
93+
return %0 : $Builtin.Int64
94+
95+
bb1(%2 : $Builtin.Int64):
96+
br bb2
97+
98+
bb2:
99+
br bb1(%2 : $Builtin.Int64)
100+
}
101+

0 commit comments

Comments
 (0)