Skip to content

Commit 89e972f

Browse files
committed
SILGen: Implement withoutActuallyEscaping verification
Check that an ``withoutActuallyEscaping(noescape_closure) { // scope}`` closure has not escaped in the scope using the ``is_escaping_closure %closure`` instruction. rdar://35525730
1 parent 5940796 commit 89e972f

File tree

9 files changed

+153
-52
lines changed

9 files changed

+153
-52
lines changed

include/swift/Runtime/HeapObject.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -280,13 +280,6 @@ SWIFT_RUNTIME_EXPORT
280280
bool swift_isUniquelyReferencedOrPinned_nonNull_native(
281281
const struct HeapObject *);
282282

283-
/// Is this native Swift pointer non-null and has a reference count greater than
284-
/// one.
285-
/// This runtime call will print an error message if the closure is escaping but
286-
/// it will not abort.
287-
SWIFT_RUNTIME_EXPORT
288-
bool swift_isEscapingClosure(const struct HeapObject *object);
289-
290283
/// Is this native Swift pointer non-null and has a reference count greater than
291284
/// one.
292285
/// This runtime call will print an error message with file name and location if

include/swift/Runtime/RuntimeFunctions.def

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -677,13 +677,6 @@ FUNCTION(IsUniquelyReferencedOrPinned_nonNull_native,
677677
ARGS(RefCountedPtrTy),
678678
ATTRS(NoUnwind, ZExt))
679679

680-
// bool swift_isEscapingClosure(const struct HeapObject *object);
681-
FUNCTION(IsEscapingClosure, swift_isEscapingClosure,
682-
C_CC,
683-
RETURNS(Int1Ty),
684-
ARGS(RefCountedPtrTy),
685-
ATTRS(NoUnwind, ZExt))
686-
687680
// bool swift_isEscapingClosureAtFileLocation(const struct HeapObject *object,
688681
// const unsigned char *filename,
689682
// int32_t filenameLength,

include/swift/SIL/SILBuilder.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1621,7 +1621,14 @@ class SILBuilder {
16211621
// Runtime failure
16221622
//===--------------------------------------------------------------------===//
16231623

1624-
CondFailInst *createCondFail(SILLocation Loc, SILValue Operand) {
1624+
CondFailInst *createCondFail(SILLocation Loc, SILValue Operand,
1625+
bool Inverted = false) {
1626+
if (Inverted) {
1627+
SILType Ty = Operand->getType();
1628+
SILValue True(createIntegerLiteral(Loc, Ty, 1));
1629+
Operand =
1630+
createBuiltinBinaryFunction(Loc, "xor", Ty, Ty, {Operand, True});
1631+
}
16251632
return insert(new (getModule())
16261633
CondFailInst(getSILDebugLocation(Loc), Operand));
16271634
}

lib/SILGen/SILGenExpr.cpp

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5310,29 +5310,41 @@ RValue RValueEmitter::visitOpenExistentialExpr(OpenExistentialExpr *E,
53105310
}
53115311

53125312
RValue RValueEmitter::visitMakeTemporarilyEscapableExpr(
5313-
MakeTemporarilyEscapableExpr *E,
5314-
SGFContext C) {
5313+
MakeTemporarilyEscapableExpr *E, SGFContext C) {
53155314
// Emit the non-escaping function value.
53165315
auto functionValue =
53175316
visit(E->getNonescapingClosureValue()).getAsSingleValue(SGF, E);
53185317

53195318
auto escapingFnTy = SGF.getLoweredType(E->getOpaqueValue()->getType());
53205319

53215320
// Convert it to an escaping function value.
5322-
functionValue =
5321+
auto escapingClosure =
53235322
SGF.createWithoutActuallyEscapingClosure(E, functionValue, escapingFnTy);
53245323

5325-
// Bind the opaque value to the escaping function.
5326-
SILGenFunction::OpaqueValueState opaqueValue{
5327-
functionValue,
5328-
/*consumable*/ true,
5329-
/*hasBeenConsumed*/ false,
5330-
};
5331-
SILGenFunction::OpaqueValueRAII pushOpaqueValue(SGF, E->getOpaqueValue(),
5332-
opaqueValue);
5324+
RValue rvalue;
5325+
auto loc = SILLocation(E);
5326+
auto borrowedClosure = escapingClosure.borrow(SGF, loc);
5327+
{
5328+
// Bind the opaque value to the escaping function.
5329+
SILGenFunction::OpaqueValueState opaqueValue{
5330+
borrowedClosure,
5331+
/*consumable*/ false,
5332+
/*hasBeenConsumed*/ false,
5333+
};
5334+
SILGenFunction::OpaqueValueRAII pushOpaqueValue(SGF, E->getOpaqueValue(),
5335+
opaqueValue);
53335336

5334-
// Emit the guarded expression.
5335-
return visit(E->getSubExpr(), C);
5337+
// Emit the guarded expression.
5338+
rvalue = visit(E->getSubExpr(), C);
5339+
}
5340+
5341+
// Now create the verification of the withoutActuallyEscaping operand.
5342+
// Either we fail the uniquenes check (which means the closure has escaped)
5343+
// and abort or we continue and destroy the ultimate reference.
5344+
auto isEscaping =
5345+
SGF.B.createIsEscapingClosure(loc, borrowedClosure.getValue());
5346+
SGF.B.createCondFail(loc, isEscaping);
5347+
return rvalue;
53365348
}
53375349

53385350
RValue RValueEmitter::visitOpaqueValueExpr(OpaqueValueExpr *E, SGFContext C) {

lib/SILGen/SILGenPoly.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
//===----------------------------------------------------------------------===//
8383

8484
#include "SILGen.h"
85+
#include "SILGenFunction.h"
8586
#include "Scope.h"
8687
#include "swift/AST/GenericSignatureBuilder.h"
8788
#include "swift/AST/Decl.h"
@@ -3147,7 +3148,8 @@ static void buildWithoutActuallyEscapingThunkBody(SILGenFunction &SGF) {
31473148
SGF.B.createReturn(loc, result);
31483149
}
31493150

3150-
ManagedValue SILGenFunction::createWithoutActuallyEscapingClosure(
3151+
ManagedValue
3152+
SILGenFunction::createWithoutActuallyEscapingClosure(
31513153
SILLocation loc, ManagedValue noEscapingFunctionValue, SILType escapingTy) {
31523154

31533155
auto escapingFnTy = escapingTy.castTo<SILFunctionType>();
@@ -3189,7 +3191,6 @@ ManagedValue SILGenFunction::createWithoutActuallyEscapingClosure(
31893191
loc, thunkValue, SILType::getPrimitiveObjectType(substFnType), subs,
31903192
noEscapeValue,
31913193
SILType::getPrimitiveObjectType(escapingFnTy));
3192-
31933194
// We need to ensure the 'lifetime' of the trivial values context captures. As
31943195
// long as we rerpresent these captures by the same value the following works.
31953196
thunkedFn = B.createMarkDependence(loc, thunkedFn, noEscapeValue);

lib/SILOptimizer/Transforms/SimplifyCFG.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,13 +1412,7 @@ static CondFailInst *getUnConditionalFail(SILBasicBlock *BB, SILValue Cond,
14121412
/// condition.
14131413
static void createCondFail(CondFailInst *Orig, SILValue Cond, bool inverted,
14141414
SILBuilder &Builder) {
1415-
if (inverted) {
1416-
auto *True = Builder.createIntegerLiteral(Orig->getLoc(), Cond->getType(), 1);
1417-
Cond = Builder.createBuiltinBinaryFunction(Orig->getLoc(), "xor",
1418-
Cond->getType(), Cond->getType(),
1419-
{Cond, True});
1420-
}
1421-
Builder.createCondFail(Orig->getLoc(), Cond);
1415+
Builder.createCondFail(Orig->getLoc(), Cond, inverted);
14221416
}
14231417

14241418
/// Inverts the expected value of 'PotentialExpect' (if it is an expect

stdlib/public/runtime/SwiftObject.mm

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,20 +1383,6 @@ static bool usesNativeSwiftReferenceCounting_nonNull(
13831383
swift_isUniquelyReferencedOrPinned_nonNull_native(object);
13841384
}
13851385

1386-
// Given a non-@objc object reference, return true iff the
1387-
// object is non-nil and has a strong reference count greather than 1
1388-
bool swift::swift_isEscapingClosure(const HeapObject *object) {
1389-
bool isEscaping =
1390-
object != nullptr && !object->refCounts.isUniquelyReferenced();
1391-
if (isEscaping) {
1392-
auto *fatalErr = reinterpret_cast<const unsigned char *>("Fatal error");
1393-
auto *message = reinterpret_cast<const unsigned char *>(
1394-
"closure argument was escaped in withoutActuallyEscaping block");
1395-
_swift_stdlib_reportFatalError(fatalErr, 11, message, 62, 0 /* flags */);
1396-
}
1397-
return isEscaping;
1398-
}
1399-
14001386
// Given a non-@objc object reference, return true iff the
14011387
// object is non-nil and has a strong reference count greather than 1
14021388
bool swift::swift_isEscapingClosureAtFileLocation(const HeapObject *object,
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
4+
import StdlibUnittest
5+
6+
var WithoutEscapingSuite = TestSuite("WithoutActuallyEscaping")
7+
8+
var sink: Any = ()
9+
10+
func dontEscape(f: () -> ()) {
11+
withoutActuallyEscaping(f) {
12+
$0()
13+
}
14+
}
15+
16+
func letEscape(f: () -> ()) -> () -> () {
17+
return withoutActuallyEscaping(f) { return $0 }
18+
}
19+
20+
var testShouldThrow = false
21+
22+
struct MyError : Error {}
23+
24+
func letEscapeThrowing(f: () -> ()) throws -> () -> () {
25+
return try withoutActuallyEscaping(f) {
26+
if testShouldThrow {
27+
throw MyError()
28+
}
29+
return $0
30+
}
31+
}
32+
33+
WithoutEscapingSuite.test("ExpectNoCrash") {
34+
dontEscape(f: { print("foo") })
35+
}
36+
37+
WithoutEscapingSuite.test("ExpectDebugCrash") {
38+
// Optimize versions pass a nil closure context.
39+
if _isDebugAssertConfiguration() {
40+
expectCrashLater()
41+
}
42+
sink = letEscape(f: { print("foo") })
43+
}
44+
45+
struct Context {
46+
var a = 0
47+
var b = 1
48+
}
49+
50+
WithoutEscapingSuite.test("ExpectCrash") {
51+
expectCrashLater()
52+
let context = Context()
53+
sink = letEscape(f: { print("Context: \(context.a) \(context.b)") })
54+
}
55+
56+
WithoutEscapingSuite.test("ExpectThrowingCrash") {
57+
expectCrashLater()
58+
let context = Context()
59+
var testDidThrow = false
60+
testShouldThrow = false
61+
do {
62+
sink = try letEscapeThrowing(f: { print("Context: \(context.a) \(context.b)") })
63+
} catch {
64+
testDidThrow = true
65+
}
66+
expectFalse(testDidThrow)
67+
}
68+
69+
WithoutEscapingSuite.test("ExpectThrowingNoCrash") {
70+
let context = Context()
71+
var testDidThrow = false
72+
testShouldThrow = true
73+
do {
74+
sink = try letEscapeThrowing(f: { print("Context: \(context.a) \(context.b)") })
75+
} catch {
76+
testDidThrow = true
77+
}
78+
expectTrue(testDidThrow)
79+
}
80+
81+
runAllTests()

test/SILGen/without_actually_escaping.swift

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,42 @@ func letEscape(f: () -> ()) -> () -> () {
99
// TODO: verify that the partial_apply's reference count is one at the end of the scope.
1010
// CHECK: [[CLOSURE:%.*]] = partial_apply [callee_guaranteed] [[CVT]]([[ARG]]) : $@convention(thin) (@noescape @callee_guaranteed () -> ()) -> ()
1111
// CHECK: [[MD:%.*]] = mark_dependence [[CLOSURE]] : $@callee_guaranteed () -> () on [[ARG]] : $@noescape @callee_guaranteed () -> ()
12+
// CHECK: [[BORROW:%.*]] = begin_borrow [[MD]] : $@callee_guaranteed () -> ()
13+
// CHECK: [[COPY:%.*]] = copy_value [[BORROW]] : $@callee_guaranteed () -> ()
1214
// CHECK: [[USER:%.*]] = function_ref @$S25without_actually_escaping9letEscape1fyycyyXE_tFyycyycXEfU_ : $@convention(thin) (@owned @callee_guaranteed () -> ()) -> @owned @callee_guaranteed () -> ()
13-
// CHECK: [[RES:%.*]] = apply %5([[MD]]) : $@convention(thin) (@owned @callee_guaranteed () -> ()) -> @owned @callee_guaranteed () -> ()
15+
// CHECK: [[RES:%.*]] = apply [[USER]]([[COPY]]) : $@convention(thin) (@owned @callee_guaranteed () -> ()) -> @owned @callee_guaranteed () -> ()
16+
// CHECK: [[ESCAPED:%.*]] = is_escaping_closure [[BORROW]] : $@callee_guaranteed () -> ()
17+
// CHECK: cond_fail [[ESCAPED]] : $Builtin.Int1
18+
// CHECK: end_borrow [[BORROW]] from [[MD]] : $@callee_guaranteed () -> (), $@callee_guaranteed () -> ()
19+
// CHECK: destroy_value [[MD]]
1420
// CHECK: return [[RES]] : $@callee_guaranteed () -> ()
1521
return withoutActuallyEscaping(f) { return $0 }
1622
}
23+
24+
25+
// CHECK-LABEL: sil hidden @$S25without_actually_escaping14letEscapeThrow1fyycyycyKXE_tKF
26+
// CHECK: bb0([[ARG:%.*]] : @trivial $@noescape @callee_guaranteed () -> (@owned @callee_guaranteed () -> (), @error Error)):
27+
// CHECK: [[CVT:%.*]] = function_ref @$SIeg_s5Error_pIgozo_Ieg_sAA_pIegozo_TR
28+
// CHECK: [[CLOSURE:%.*]] = partial_apply [callee_guaranteed] [[CVT]]([[ARG]])
29+
// CHECK: [[MD:%.*]] = mark_dependence [[CLOSURE]] : {{.*}} on [[ARG]]
30+
// CHECK: [[BORROW:%.*]] = begin_borrow [[MD]]
31+
// CHECK: [[COPY:%.*]] = copy_value [[BORROW]]
32+
// CHECK: [[USER:%.*]] = function_ref @$S25without_actually_escaping14letEscapeThrow1fyycyycyKXE_tKFyycyycyKcKXEfU_
33+
// CHECK: try_apply [[USER]]([[COPY]]) : {{.*}}, normal bb1, error bb2
34+
//
35+
// CHECK: bb1([[RES:%.*]] : @owned $@callee_guaranteed () -> ()):
36+
// CHECK: [[ESCAPED:%.*]] = is_escaping_closure [[BORROW]]
37+
// CHECK: cond_fail [[ESCAPED]] : $Builtin.Int1
38+
// CHECK: end_borrow [[BORROW]] from [[MD]]
39+
// CHECK: destroy_value [[MD]]
40+
// CHECK: return [[RES]]
41+
//
42+
// CHECK: bb2([[ERR:%.*]] : @owned $Error):
43+
// CHECK: end_borrow [[BORROW]] from [[MD]]
44+
// CHECK: destroy_value [[MD]]
45+
// CHECK: throw [[ERR]] : $Error
46+
// CHECK: }
47+
48+
func letEscapeThrow(f: () throws -> () -> ()) throws -> () -> () {
49+
return try withoutActuallyEscaping(f) { return try $0() }
50+
}

0 commit comments

Comments
 (0)