Skip to content

Commit 00cc011

Browse files
author
Harlan
authored
[SILOptimizer] Don't diagnose infinite recursion if a branch terminates the program (#19781)
* [SILOptimizer] Don't diagnose infinite recursion if a branch terminates the program This patch augments the infinite recursion checker to not warn if a branch terminates, but still warns if a branch calls into something with @_semantics("programtermination_point"). This way, calling fatalError doesn't disqualify you for the diagnostic, but calling exit does. This also removes the warning workaround in the standard library, and annotates the internal _assertionFailure functions as programtermination_points, so they get this treatment too. * Fix formatting in SILInstructions.cpp * Re-add missing test
1 parent dd711e8 commit 00cc011

16 files changed

+143
-34
lines changed

docs/ARCOptimization.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ Semantic Tags
373373
ARC takes advantage of certain semantic tags. This section documents these
374374
semantics and their meanings.
375375

376-
arc.programtermination_point
376+
programtermination_point
377377
----------------------------
378378

379379
If this semantic tag is applied to a function, then we know that:
@@ -396,7 +396,7 @@ scope's lifetime may never end. This means that:
396396

397397
1. While we can not ignore all such unreachable terminated blocks for ARC
398398
purposes for instance, if we sink a retain past a br into a non
399-
arc.programtermination_point block, we must sink the retain into the block.
399+
programtermination_point block, we must sink the retain into the block.
400400

401401
2. If we are able to infer that an object's lifetime scope would never end due
402402
to the unreachable/no-return function, then we do not need to end the lifetime

include/swift/SIL/ApplySite.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ class ApplySite {
171171
return SILFunctionConventions(getSubstCalleeType(), getModule());
172172
}
173173

174+
/// Returns true if the callee function is annotated with
175+
/// @_semantics("programtermination_point")
176+
bool isCalleeKnownProgramTerminationPoint() const {
177+
FOREACH_IMPL_RETURN(isCalleeKnownProgramTerminationPoint());
178+
}
179+
174180
/// Check if this is a call of a never-returning function.
175181
bool isCalleeNoReturn() const { FOREACH_IMPL_RETURN(isCalleeNoReturn()); }
176182

include/swift/SIL/SILBasicBlock.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ public llvm::ilist_node<SILBasicBlock>, public SILAllocated<SILBasicBlock> {
370370
/// no-return apply or builtin.
371371
bool isNoReturn() const;
372372

373-
/// Returns true if this instruction only contains a branch instruction.
373+
/// Returns true if this block only contains a branch instruction.
374374
bool isTrampoline() const;
375375

376376
/// Returns true if it is legal to hoist instructions into this block.

include/swift/SIL/SILInstruction.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "swift/SIL/SILLocation.h"
3636
#include "swift/SIL/SILSuccessor.h"
3737
#include "swift/SIL/SILValue.h"
38+
#include "swift/Strings.h"
3839
#include "llvm/ADT/APFloat.h"
3940
#include "llvm/ADT/APInt.h"
4041
#include "llvm/ADT/ilist.h"
@@ -1818,6 +1819,14 @@ class ApplyInstBase<Impl, Base, false> : public Base {
18181819
return Rep == FunctionType::Representation::Thin;
18191820
}
18201821

1822+
/// Returns true if the callee function is annotated with
1823+
/// @_semantics("programtermination_point")
1824+
bool isCalleeKnownProgramTerminationPoint() const {
1825+
auto calleeFn = getCalleeFunction();
1826+
if (!calleeFn) return false;
1827+
return calleeFn->hasSemanticsAttr(SEMANTICS_PROGRAMTERMINATION_POINT);
1828+
}
1829+
18211830
/// True if this application has generic substitutions.
18221831
bool hasSubstitutions() const {
18231832
return Substitutions.hasAnySubstitutableParams();
@@ -6645,6 +6654,9 @@ class TermInst : public NonValueInstruction {
66456654
/// Returns true if this terminator exits the function.
66466655
bool isFunctionExiting() const;
66476656

6657+
/// Returns true if this terminator terminates the program.
6658+
bool isProgramTerminating() const;
6659+
66486660
TermKind getTermKind() const { return TermKind(getKind()); }
66496661
};
66506662

include/swift/Strings.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ constexpr static const char BUILTIN_TYPE_NAME_VEC[] = "Builtin.Vec";
7777
constexpr static const char BUILTIN_TYPE_NAME_SILTOKEN[] = "Builtin.SILToken";
7878
/// The name of the Builtin type for Word
7979
constexpr static const char BUILTIN_TYPE_NAME_WORD[] = "Builtin.Word";
80-
constexpr static StringLiteral SEMANTICS_ARC_PROGRAMTERMINATION_POINT =
81-
"arc.programtermination_point";
80+
constexpr static StringLiteral SEMANTICS_PROGRAMTERMINATION_POINT =
81+
"programtermination_point";
8282
} // end namespace swift
8383

8484
#endif // SWIFT_STRINGS_H

lib/SIL/SILBasicBlock.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "swift/SIL/SILFunction.h"
2525
#include "swift/SIL/SILInstruction.h"
2626
#include "swift/SIL/SILModule.h"
27+
#include "swift/Strings.h"
2728
using namespace swift;
2829

2930
//===----------------------------------------------------------------------===//

lib/SIL/SILInstructions.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,6 +1061,30 @@ bool TermInst::isFunctionExiting() const {
10611061
llvm_unreachable("Unhandled TermKind in switch.");
10621062
}
10631063

1064+
bool TermInst::isProgramTerminating() const {
1065+
switch (getTermKind()) {
1066+
case TermKind::BranchInst:
1067+
case TermKind::CondBranchInst:
1068+
case TermKind::SwitchValueInst:
1069+
case TermKind::SwitchEnumInst:
1070+
case TermKind::SwitchEnumAddrInst:
1071+
case TermKind::DynamicMethodBranchInst:
1072+
case TermKind::CheckedCastBranchInst:
1073+
case TermKind::CheckedCastValueBranchInst:
1074+
case TermKind::CheckedCastAddrBranchInst:
1075+
case TermKind::ReturnInst:
1076+
case TermKind::ThrowInst:
1077+
case TermKind::UnwindInst:
1078+
case TermKind::TryApplyInst:
1079+
case TermKind::YieldInst:
1080+
return false;
1081+
case TermKind::UnreachableInst:
1082+
return true;
1083+
}
1084+
1085+
llvm_unreachable("Unhandled TermKind in switch.");
1086+
}
1087+
10641088
TermInst::SuccessorBlockArgumentsListTy
10651089
TermInst::getSuccessorBlockArguments() const {
10661090
function_ref<PhiArgumentArrayRef(const SILSuccessor &)> op;

lib/SILOptimizer/Analysis/ARCAnalysis.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,11 +1092,8 @@ bool swift::getFinalReleasesForValue(SILValue V, ReleaseTracker &Tracker) {
10921092
//===----------------------------------------------------------------------===//
10931093

10941094
static bool ignorableApplyInstInUnreachableBlock(const ApplyInst *AI) {
1095-
const auto *Fn = AI->getReferencedFunction();
1096-
if (!Fn)
1097-
return false;
1098-
1099-
return Fn->hasSemanticsAttr("arc.programtermination_point");
1095+
auto applySite = FullApplySite(const_cast<ApplyInst *>(AI));
1096+
return applySite.isCalleeKnownProgramTerminationPoint();
11001097
}
11011098

11021099
static bool ignorableBuiltinInstInUnreachableBlock(const BuiltinInst *BI) {

lib/SILOptimizer/Analysis/SideEffectAnalysis.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ FunctionSideEffectFlags *FunctionSideEffects::getEffectsOn(SILValue Addr) {
321321
// Return true if the given function has defined effects that were successfully
322322
// recorded in this FunctionSideEffects object.
323323
bool FunctionSideEffects::setDefinedEffects(SILFunction *F) {
324-
if (F->hasSemanticsAttr("arc.programtermination_point")) {
324+
if (F->hasSemanticsAttr(SEMANTICS_PROGRAMTERMINATION_POINT)) {
325325
Traps = true;
326326
return true;
327327
}

lib/SILOptimizer/Mandatory/DiagnoseInfiniteRecursion.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,24 @@ static bool hasRecursiveCallInPath(SILBasicBlock &Block,
9494
return false;
9595
}
9696

97+
/// Returns true if the block has a call to a function marked with
98+
/// @_semantics("programtermination_point").
99+
static bool isKnownProgramTerminationPoint(const SILBasicBlock *bb) {
100+
// Skip checking anything if this block doesn't end in a program terminator.
101+
if (!bb->getTerminator()->isProgramTerminating())
102+
return false;
103+
104+
// Check each instruction for a call to something that's a known
105+
// programtermination_point
106+
for (auto it = bb->rbegin(); it != bb->rend(); ++it) {
107+
auto applySite = FullApplySite::isa(const_cast<SILInstruction *>(&*it));
108+
if (!applySite) continue;
109+
if (applySite.isCalleeKnownProgramTerminationPoint())
110+
return true;
111+
}
112+
return false;
113+
}
114+
97115
/// Perform a DFS through the target function to find any paths to an exit node
98116
/// that do not call into the target.
99117
static bool hasInfinitelyRecursiveApply(SILFunction *targetFn) {
@@ -109,6 +127,13 @@ static bool hasInfinitelyRecursiveApply(SILFunction *targetFn) {
109127
while (!workList.empty()) {
110128
SILBasicBlock *curBlock = workList.pop_back_val();
111129

130+
// Before checking for infinite recursion, see if we're calling something
131+
// that's @_semantics("programtermination_point"). We explicitly don't
132+
// want this call to disqualify the warning for infinite recursion,
133+
// because they're reserved for exceptional circumstances.
134+
if (isKnownProgramTerminationPoint(curBlock))
135+
continue;
136+
112137
// We're looking for functions that are recursive on _all_ paths. If this
113138
// block is recursive, mark that we found recursion and check the next
114139
// block in the work list.
@@ -119,7 +144,8 @@ static bool hasInfinitelyRecursiveApply(SILFunction *targetFn) {
119144

120145
// If this block doesn't have a recursive call, and it exits the function,
121146
// then we know the function is not infinitely recursive.
122-
if (curBlock->getTerminator()->isFunctionExiting())
147+
auto term = curBlock->getTerminator();
148+
if (term->isFunctionExiting() || term->isProgramTerminating())
123149
return false;
124150

125151
// Otherwise, push the successors onto the stack if we haven't visited them.

lib/SILOptimizer/Transforms/ARCCodeMotion.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,9 +1072,7 @@ static void eliminateRetainsPrecedingProgramTerminationPoints(SILFunction *f) {
10721072
// such a case, we can ignore it. All other functions though imply we must
10731073
// bail. If we don't have a function here, check for side
10741074
if (auto apply = FullApplySite::isa(&*iter)) {
1075-
SILFunction *callee = apply.getCalleeFunction();
1076-
if (!callee ||
1077-
!callee->hasSemanticsAttr(SEMANTICS_ARC_PROGRAMTERMINATION_POINT)) {
1075+
if (!apply.isCalleeKnownProgramTerminationPoint()) {
10781076
continue;
10791077
}
10801078
} else {

stdlib/public/core/AssertCommon.swift

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ internal func _fatalErrorFlags() -> UInt32 {
7676
/// bloats code.
7777
@usableFromInline
7878
@inline(never)
79+
@_semantics("programtermination_point")
7980
internal func _assertionFailure(
8081
_ prefix: StaticString, _ message: StaticString,
8182
file: StaticString, line: UInt,
@@ -106,6 +107,7 @@ internal func _assertionFailure(
106107
/// bloats code.
107108
@usableFromInline
108109
@inline(never)
110+
@_semantics("programtermination_point")
109111
internal func _assertionFailure(
110112
_ prefix: StaticString, _ message: String,
111113
file: StaticString, line: UInt,
@@ -136,6 +138,7 @@ internal func _assertionFailure(
136138
/// bloats code.
137139
@usableFromInline
138140
@inline(never)
141+
@_semantics("programtermination_point")
139142
internal func _assertionFailure(
140143
_ prefix: StaticString, _ message: String,
141144
flags: UInt32
@@ -161,27 +164,18 @@ internal func _assertionFailure(
161164
/// bloats code.
162165
@usableFromInline
163166
@inline(never)
164-
@_semantics("arc.programtermination_point")
167+
@_semantics("programtermination_point")
165168
internal func _fatalErrorMessage(
166169
_ prefix: StaticString, _ message: StaticString,
167170
file: StaticString, line: UInt,
168171
flags: UInt32
169172
) -> Never {
170-
// This function breaks the infinite recursion detection pass by introducing
171-
// an edge the pass doesn't look through.
172-
func _withUTF8Buffer<R>(
173-
_ string: StaticString,
174-
_ body: (UnsafeBufferPointer<UInt8>) -> R
175-
) -> R {
176-
return string.withUTF8Buffer(body)
177-
}
178-
179173
#if INTERNAL_CHECKS_ENABLED
180-
_withUTF8Buffer(prefix) {
174+
prefix.withUTF8Buffer() {
181175
(prefix) in
182-
_withUTF8Buffer(message) {
176+
message.withUTF8Buffer() {
183177
(message) in
184-
_withUTF8Buffer(file) {
178+
file.withUTF8Buffer() {
185179
(file) in
186180
_swift_stdlib_reportFatalErrorInFile(
187181
prefix.baseAddress!, CInt(prefix.count),
@@ -192,9 +186,9 @@ internal func _fatalErrorMessage(
192186
}
193187
}
194188
#else
195-
_withUTF8Buffer(prefix) {
189+
prefix.withUTF8Buffer() {
196190
(prefix) in
197-
_withUTF8Buffer(message) {
191+
message.withUTF8Buffer() {
198192
(message) in
199193
_swift_stdlib_reportFatalError(
200194
prefix.baseAddress!, CInt(prefix.count),

test/SILOptimizer/arcsequenceopts.sil

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1553,7 +1553,7 @@ bb0(%0 : $<τ_0_0> { var τ_0_0 } <Builtin.Int32>):
15531553
return %2 : $()
15541554
}
15551555

1556-
sil [_semantics "arc.programtermination_point"] @fatalError : $@convention(thin) (StaticString, StaticString, StaticString) -> Never
1556+
sil [_semantics "programtermination_point"] @fatalError : $@convention(thin) (StaticString, StaticString, StaticString) -> Never
15571557

15581558
// CHECK-LABEL: sil @ignore_fatalErrorMsgBB : $@convention(thin) (Builtin.NativeObject) -> () {
15591559
// CHECK-NOT: strong_retain

test/SILOptimizer/infinite_recursion.swift

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func f() { e() }
3333

3434
func g() { // expected-warning {{all paths through this function will call itself}}
3535
while true { // expected-note {{condition always evaluates to true}}
36-
g()
36+
g()
3737
}
3838

3939
g() // expected-warning {{will never be executed}}
@@ -61,6 +61,57 @@ func k() -> Any { // expected-warning {{all paths through this function will ca
6161
return type(of: k())
6262
}
6363

64+
@_silgen_name("exit") func exit(_: Int32) -> Never
65+
66+
func l() {
67+
guard Bool.random() else {
68+
exit(0) // no warning; calling 'exit' terminates the program
69+
}
70+
l()
71+
}
72+
73+
func m() { // expected-warning {{all paths through this function will call itself}}
74+
guard Bool.random() else {
75+
fatalError() // we _do_ warn here, because fatalError is a programtermination_point
76+
}
77+
m()
78+
}
79+
80+
enum MyNever {}
81+
82+
func blackHole() -> MyNever { // expected-warning {{all paths through this function will call itself}}
83+
blackHole()
84+
}
85+
86+
@_semantics("programtermination_point")
87+
func terminateMe() -> MyNever {
88+
terminateMe() // no warning; terminateMe is a programtermination_point
89+
}
90+
91+
func n() -> MyNever {
92+
if Bool.random() {
93+
blackHole() // no warning; blackHole() will terminate the program
94+
}
95+
n()
96+
}
97+
98+
func o() -> MyNever {
99+
if Bool.random() {
100+
o()
101+
}
102+
blackHole() // no warning; blackHole() will terminate the program
103+
}
104+
105+
func mayHaveSideEffects() {}
106+
107+
func p() { // expected-warning {{all paths through this function will call itself}}
108+
if Bool.random() {
109+
mayHaveSideEffects() // presence of side-effects doesn't alter the check for the programtermination_point apply
110+
fatalError()
111+
}
112+
p()
113+
}
114+
64115
class S {
65116
convenience init(a: Int) { // expected-warning {{all paths through this function will call itself}}
66117
self.init(a: a)

test/SILOptimizer/retain_release_code_motion.sil

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ bb1:
667667
return %5 : $()
668668
}
669669

670-
sil [_semantics "arc.programtermination_point"] @fatalError : $@convention(thin) () -> Never
670+
sil [_semantics "programtermination_point"] @fatalError : $@convention(thin) () -> Never
671671

672672
// This should eliminate all retains except for the first one b/c of user.
673673
// CHECK-LABEL: sil @eliminate_retains_on_fatalError_path_single_bb : $@convention(thin) (@owned Builtin.NativeObject) -> () {

test/SILOptimizer/side-effect.sil

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class X {
3333
init()
3434
}
3535

36-
sil [_semantics "arc.programtermination_point"] @exitfunc : $@convention(thin) () -> Never
36+
sil [_semantics "programtermination_point"] @exitfunc : $@convention(thin) () -> Never
3737
sil [readnone] @pure_func : $@convention(thin) () -> ()
3838
sil [releasenone] @releasenone_func : $@convention(thin) () -> ()
3939
sil [readonly] @readonly_owned : $@convention(thin) (@owned X) -> ()

0 commit comments

Comments
 (0)