Skip to content

Commit 11774e5

Browse files
committed
[Runtime] Check function types against suppressible protocols
Form a set of suppressed protocols for a function type based on the extended flags (where future compilers can start recording suppressible protocols) and the existing "noescape" bit. Compare that against the "ignored" suppressible protocol requirements, as we do for other types. This involves a behavior change if any client has managed to evade the static checking for noescape function types, but it's unlikely that existing code has done so (and it was unsafe anyway).
1 parent 5b02006 commit 11774e5

File tree

6 files changed

+155
-3
lines changed

6 files changed

+155
-3
lines changed

include/swift/ABI/MetadataValues.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
#include "swift/ABI/KeyPath.h"
2727
#include "swift/ABI/ProtocolDispatchStrategy.h"
28+
#include "swift/ABI/SuppressibleProtocols.h"
2829

2930
// FIXME: this include shouldn't be here, but removing it causes symbol
3031
// mangling mismatches on Windows for some reason?
@@ -1198,6 +1199,10 @@ class TargetExtendedFunctionTypeFlags {
11981199

11991200
// Values if we have a transferring result.
12001201
HasTransferringResult = 0x00000010U,
1202+
1203+
/// A SuppressibleProtocolSet in the high bits.
1204+
SuppressedProtocolShift = 16,
1205+
SuppressedProtocolMask = 0xFFFFU << SuppressedProtocolShift,
12011206
};
12021207
int_type Data;
12031208

@@ -1229,6 +1234,13 @@ class TargetExtendedFunctionTypeFlags {
12291234
(newValue ? HasTransferringResult : 0));
12301235
}
12311236

1237+
const TargetExtendedFunctionTypeFlags<int_type>
1238+
withSuppressedProtocols(SuppressibleProtocolSet suppressed) const {
1239+
return TargetExtendedFunctionTypeFlags<int_type>(
1240+
(Data & ~SuppressedProtocolMask) |
1241+
(suppressed.rawBits() << SuppressedProtocolShift));
1242+
}
1243+
12321244
bool isTypedThrows() const { return bool(Data & TypedThrowsMask); }
12331245

12341246
bool isIsolatedAny() const {
@@ -1243,6 +1255,10 @@ class TargetExtendedFunctionTypeFlags {
12431255
return Data;
12441256
}
12451257

1258+
SuppressibleProtocolSet getSuppressedProtocols() const {
1259+
return SuppressibleProtocolSet(Data >> SuppressedProtocolShift);
1260+
}
1261+
12461262
static TargetExtendedFunctionTypeFlags<int_type> fromIntValue(int_type Data) {
12471263
return TargetExtendedFunctionTypeFlags(Data);
12481264
}
@@ -2003,7 +2019,7 @@ class GenericContextDescriptorFlags {
20032019

20042020
/// Whether this generic context has any conditional conformances to
20052021
/// suppressed protocols, in which case the generic context will have a
2006-
/// trailing SuppressedProtocolSet and conditional requirements.
2022+
/// trailing SuppressibleProtocolSet and conditional requirements.
20072023
constexpr bool hasConditionalSuppressedProtocols() const {
20082024
return (Value & 0x2) != 0;
20092025
}

include/swift/ABI/SuppressibleProtocols.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#define SWIFT_ABI_SUPPRESSIBLEPROTOCOLS_H
2020

2121
#include <cstdint>
22+
#include <iterator>
2223

2324
namespace swift {
2425

lib/IRGen/MetadataRequest.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1416,11 +1416,35 @@ getFunctionTypeFlags(CanFunctionType type) {
14161416
break;
14171417
}
14181418

1419+
// Compute the set of suppressed protocols.
1420+
SuppressibleProtocolSet suppressedProtocols;
1421+
for (auto suppressibleKind : SuppressibleProtocolSet::allKnown()) {
1422+
switch (suppressibleKind) {
1423+
case SuppressibleProtocolKind::Copyable: {
1424+
// If the function type is noncopyable, note that in the suppressed
1425+
// protocols.
1426+
auto proto =
1427+
type->getASTContext().getProtocol(KnownProtocolKind::Copyable);
1428+
if (proto &&
1429+
proto->getParentModule()->lookupConformance(type, proto).isInvalid())
1430+
suppressedProtocols.insert(suppressibleKind);
1431+
break;
1432+
}
1433+
1434+
case SuppressibleProtocolKind::Escapable:
1435+
// We intentionally do not record the "escapable" bit here, because it's
1436+
// already in the normal function type flags. The runtime will
1437+
// introduce it as necessary.
1438+
break;
1439+
}
1440+
}
1441+
14191442
auto isolation = type->getIsolation();
14201443

14211444
auto extFlags = ExtendedFunctionTypeFlags()
14221445
.withTypedThrows(!type->getThrownError().isNull())
1423-
.withTransferringResult(type->hasTransferringResult());
1446+
.withTransferringResult(type->hasTransferringResult())
1447+
.withSuppressedProtocols(suppressedProtocols);
14241448

14251449
if (isolation.isErased())
14261450
extFlags = extFlags.withIsolatedAny();

stdlib/public/runtime/ProtocolConformance.cpp

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1673,7 +1673,43 @@ checkSuppressibleRequirementsStructural(const Metadata *type,
16731673
}
16741674

16751675
case MetadataKind::Function: {
1676-
// FIXME: Implement me
1676+
auto functionMetadata = cast<FunctionTypeMetadata>(type);
1677+
1678+
// Determine the set of protocols that are suppressed by the function
1679+
// type.
1680+
SuppressibleProtocolSet suppressed;
1681+
if (functionMetadata->hasExtendedFlags()) {
1682+
suppressed = functionMetadata->getExtendedFlags()
1683+
.getSuppressedProtocols();
1684+
}
1685+
1686+
// Map the existing "noescape" bit as a suppressed protocol, when
1687+
// appropriate.
1688+
switch (functionMetadata->getConvention()) {
1689+
case FunctionMetadataConvention::Swift:
1690+
// Swift function types can be non-escaping, so honor the bit.
1691+
if (!functionMetadata->isEscaping())
1692+
suppressed.insert(SuppressibleProtocolKind::Escapable);
1693+
break;
1694+
1695+
case FunctionMetadataConvention::Block:
1696+
// Objective-C block types don't encode non-escaping-ness in metadata,
1697+
// so we assume that they are always escaping.
1698+
break;
1699+
1700+
case FunctionMetadataConvention::Thin:
1701+
case FunctionMetadataConvention::CFunctionPointer:
1702+
// Thin and C function pointers have no captures, so whether they
1703+
// escape is irrelevant.
1704+
break;
1705+
}
1706+
1707+
auto missing = suppressed - ignored;
1708+
if (!missing.empty()) {
1709+
return TYPE_LOOKUP_ERROR_FMT(
1710+
"function type missing suppressible protocols %x", missing.rawBits());
1711+
}
1712+
16771713
return std::nullopt;
16781714
}
16791715

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -sil-verify-all -enable-experimental-feature NoncopyableGenerics -enable-experimental-feature NonescapableTypes) | %FileCheck %s
2+
// RUN: %target-run-simple-swift(-O -Xfrontend -sil-verify-all -enable-experimental-feature NoncopyableGenerics -enable-experimental-feature NonescapableTypes) | %FileCheck %s
3+
4+
// REQUIRES: executable_test, asserts
5+
6+
protocol P {
7+
func speak()
8+
}
9+
10+
extension P {
11+
func speak() { print("hello") }
12+
}
13+
14+
struct Nonescapable: ~Escapable {}
15+
struct Ordinary {}
16+
17+
struct Dog<T: ~Escapable>: Escapable {}
18+
extension Dog: P where T: Escapable {}
19+
20+
func attemptCall(_ a: Any) {
21+
if let value = a as? P {
22+
value.speak()
23+
return
24+
}
25+
print("failed to cast (attemptCall)")
26+
}
27+
28+
defer { main({}) }
29+
func main(_ noEscapeFunc: () -> Void) {
30+
// CHECK: hello
31+
attemptCall(Dog<Ordinary>())
32+
33+
// CHECK: failed to cast (attemptCall)
34+
attemptCall(Dog<Nonescapable>())
35+
36+
// CHECK: function types
37+
print("function types")
38+
39+
// CHECK: hello
40+
attemptCall(Dog<() -> Void>())
41+
42+
// CHECK: failed to cast (attemptCall)
43+
func doFuncCall<F>(_: F.Type) {
44+
attemptCall(Dog<F>())
45+
}
46+
// This is the mangled name for a non-escaping Swift function type.
47+
let fnType = _typeByName("yyXE")!
48+
_openExistential(fnType, do: doFuncCall)
49+
}
50+

test/Interpreter/moveonly_generics_casting.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ func attemptCall(_ a: Any) {
4040
print("failed to cast (attemptCall)")
4141
}
4242

43+
@_silgen_name("swift_getExtendedFunctionTypeMetadata")
44+
func _getExtendedFunctionTypeMetadata(
45+
flags: UInt, differentiabilityKind: UInt,
46+
parameterTypes: UnsafePointer<Any.Type>?,
47+
parameterFlags: UnsafePointer<UInt32>?,
48+
resultType: Any.Type, globalActorType: Any.Type? = nil,
49+
extendedFlags: UInt32, thrownErrorType: Any.Type? = nil) -> Any.Type
50+
51+
4352
defer { main() }
4453
func main() {
4554
// CHECK: hello
@@ -90,6 +99,22 @@ func main() {
9099

91100
attemptCall(Dog<(Ordinary) -> Noncopyable>())
92101

102+
// This is a nonmovable function type, which cannot currently be
103+
// expressed in the language.
104+
let noncopyableFnType = _getExtendedFunctionTypeMetadata(
105+
flags: 0x04000000 | 0x80000000,
106+
differentiabilityKind: 0,
107+
parameterTypes: nil,
108+
parameterFlags: nil,
109+
resultType: Void.self,
110+
extendedFlags: 0x1 << 16)
111+
112+
// CHECK: failed to cast (attemptCall)
113+
func doFuncCall<F>(_: F.Type) {
114+
attemptCall(Dog<F>())
115+
}
116+
_openExistential(noncopyableFnType, do: doFuncCall)
117+
93118
// CHECK: existential types
94119
print("existential types")
95120

0 commit comments

Comments
 (0)