Skip to content

Commit 5b726bc

Browse files
authored
[6.2] Improve memoization of conformance lookups during casting (#82746)
1 parent 8cec61e commit 5b726bc

File tree

3 files changed

+146
-18
lines changed

3 files changed

+146
-18
lines changed

benchmark/single-source/ObjectiveCBridging.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ public let benchmarks = [
9797
BenchmarkInfo(name: "NSArray.bridged.repeatedBufferAccess",
9898
runFunction: run_BridgedNSArrayRepeatedBufferAccess, tags: t,
9999
setUpFunction: setup_bridgedArrays),
100+
BenchmarkInfo(name: "NSDictionary.bridged.enumerate",
101+
runFunction: run_BridgedNSDictionaryEnumerate, tags: t,
102+
setUpFunction: setup_bridgedDictionaries),
100103
BenchmarkInfo(name: "NSString.bridged.byteCount.ascii.ascii",
101104
runFunction: run_BridgedNSStringLengthASCII_ASCII, tags: ts,
102105
setUpFunction: setup_bridgedStrings),
@@ -812,6 +815,7 @@ public func run_UnicodeStringFromCodable(_ n: Int) {
812815

813816
#if _runtime(_ObjC)
814817
var bridgedArray:NSArray! = nil
818+
var bridgedDictionaryOfNumbersToNumbers:NSDictionary! = nil
815819
var bridgedArrayMutableCopy:NSMutableArray! = nil
816820
var nsArray:NSArray! = nil
817821
var nsArrayMutableCopy:NSMutableArray! = nil
@@ -824,11 +828,20 @@ public func setup_bridgedArrays() {
824828
var arr = Array(repeating: NSObject(), count: 100) as [AnyObject]
825829
bridgedArray = arr as NSArray
826830
bridgedArrayMutableCopy = (bridgedArray.mutableCopy() as! NSMutableArray)
831+
827832
nsArray = NSArray(objects: &arr, count: 100)
828833
nsArrayMutableCopy = (nsArray.mutableCopy() as! NSMutableArray)
829834
#endif
830835
}
831836

837+
public func setup_bridgedDictionaries() {
838+
var numDict = Dictionary<Int, Int>()
839+
for i in 0 ..< 100 {
840+
numDict[i] = i
841+
}
842+
bridgedDictionaryOfNumbersToNumbers = numDict as NSDictionary
843+
}
844+
832845
public func setup_bridgedStrings() {
833846
#if _runtime(_ObjC)
834847
let str = Array(repeating: "The quick brown fox jumps over the lazy dog.", count: 100).joined()
@@ -849,6 +862,23 @@ public func run_BridgedNSArrayObjectAtIndex(_ n: Int) {
849862
#endif
850863
}
851864

865+
private func dictionaryApplier(
866+
_ keyPtr: UnsafeRawPointer?,
867+
_ valuePtr :UnsafeRawPointer?,
868+
_ contextPtr: UnsafeMutableRawPointer?
869+
) -> Void {}
870+
871+
@inline(never)
872+
public func run_BridgedNSDictionaryEnumerate(_ n: Int) {
873+
#if _runtime(_ObjC)
874+
let cf = bridgedDictionaryOfNumbersToNumbers as CFDictionary
875+
for _ in 0 ..< n * 50 {
876+
// Use CF to prevent Swift from providing an override, forcing going through ObjC bridging
877+
CFDictionaryApplyFunction(cf, dictionaryApplier, nil)
878+
}
879+
#endif
880+
}
881+
852882
@inline(never)
853883
public func run_BridgedNSArrayBufferAccess(_ n: Int) {
854884
#if _runtime(_ObjC)

stdlib/public/runtime/Casting.cpp

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,10 +1436,31 @@ extern "C" const _ObjectiveCBridgeableWitnessTable BRIDGING_CONFORMANCE_SYM;
14361436
/// Nominal type descriptor for Swift.String.
14371437
extern "C" const StructDescriptor NOMINAL_TYPE_DESCR_SYM(SS);
14381438

1439+
struct ObjCBridgeWitnessCacheEntry {
1440+
const Metadata *metadata;
1441+
const _ObjectiveCBridgeableWitnessTable *witness;
1442+
};
1443+
1444+
// String is so important that we cache it permanently, so we don't want to
1445+
// pollute this temporary cache with the String entry
1446+
static const _ObjectiveCBridgeableWitnessTable *
1447+
swift_conformsToObjectiveCBridgeableNoCache(const Metadata *T) {
1448+
auto w = swift_conformsToProtocolCommon(
1449+
T, &PROTOCOL_DESCR_SYM(s21_ObjectiveCBridgeable));
1450+
return reinterpret_cast<const _ObjectiveCBridgeableWitnessTable *>(w);
1451+
}
1452+
14391453
static const _ObjectiveCBridgeableWitnessTable *
14401454
swift_conformsToObjectiveCBridgeable(const Metadata *T) {
1441-
return reinterpret_cast<const _ObjectiveCBridgeableWitnessTable *>
1442-
(swift_conformsToProtocolCommon(T, &PROTOCOL_DESCR_SYM(s21_ObjectiveCBridgeable)));
1455+
static std::atomic<ObjCBridgeWitnessCacheEntry> _objcBridgeWitnessCache = {};
1456+
auto cached = _objcBridgeWitnessCache.load(SWIFT_MEMORY_ORDER_CONSUME);
1457+
if (cached.metadata == T) {
1458+
return cached.witness;
1459+
}
1460+
cached.witness = swift_conformsToObjectiveCBridgeableNoCache(T);
1461+
cached.metadata = T;
1462+
_objcBridgeWitnessCache.store(cached, std::memory_order_release);
1463+
return cached.witness;
14431464
}
14441465

14451466
static const _ObjectiveCBridgeableWitnessTable *
@@ -1451,7 +1472,7 @@ findBridgeWitness(const Metadata *T) {
14511472
if (T->getKind() == MetadataKind::Struct) {
14521473
auto structDescription = cast<StructMetadata>(T)->Description;
14531474
if (structDescription == &NOMINAL_TYPE_DESCR_SYM(SS)) {
1454-
static auto *Swift_String_ObjectiveCBridgeable = swift_conformsToObjectiveCBridgeable(T);
1475+
static auto *Swift_String_ObjectiveCBridgeable = swift_conformsToObjectiveCBridgeableNoCache(T);
14551476
return Swift_String_ObjectiveCBridgeable;
14561477
}
14571478
}

stdlib/public/runtime/DynamicCast.cpp

Lines changed: 92 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,86 @@ struct _ObjectiveCBridgeableWitnessTable : WitnessTable {
206206
extern "C" const ProtocolDescriptor
207207
PROTOCOL_DESCR_SYM(s21_ObjectiveCBridgeable);
208208

209+
#if SWIFT_OBJC_INTEROP
210+
#define BRIDGING_CONFORMANCE_SYM \
211+
MANGLE_SYM(s19_BridgeableMetatypeVs21_ObjectiveCBridgeablesWP)
212+
213+
extern "C" const _ObjectiveCBridgeableWitnessTable BRIDGING_CONFORMANCE_SYM;
214+
#endif
215+
216+
/// Nominal type descriptor for Swift.String.
217+
extern "C" const StructDescriptor NOMINAL_TYPE_DESCR_SYM(SS);
218+
219+
struct ObjCBridgeWitnessCacheEntry {
220+
const Metadata *metadata;
221+
const _ObjectiveCBridgeableWitnessTable *witness;
222+
};
223+
209224
static const _ObjectiveCBridgeableWitnessTable *
210-
findBridgeWitness(const Metadata *T) {
225+
swift_conformsToObjectiveCBridgeableNoCache(const Metadata *T) {
211226
auto w = swift_conformsToProtocolCommon(
212-
T, &PROTOCOL_DESCR_SYM(s21_ObjectiveCBridgeable));
227+
T, &PROTOCOL_DESCR_SYM(s21_ObjectiveCBridgeable));
213228
return reinterpret_cast<const _ObjectiveCBridgeableWitnessTable *>(w);
214229
}
215230

231+
static const _ObjectiveCBridgeableWitnessTable *
232+
swift_conformsToObjectiveCBridgeable(const Metadata *T) {
233+
static std::atomic<ObjCBridgeWitnessCacheEntry> _objcBridgeWitnessCache = {};
234+
auto cached = _objcBridgeWitnessCache.load(SWIFT_MEMORY_ORDER_CONSUME);
235+
if (cached.metadata == T) {
236+
return cached.witness;
237+
}
238+
cached.witness = swift_conformsToObjectiveCBridgeableNoCache(T);
239+
cached.metadata = T;
240+
_objcBridgeWitnessCache.store(cached, std::memory_order_release);
241+
return cached.witness;
242+
}
243+
244+
static const _ObjectiveCBridgeableWitnessTable *
245+
findBridgeWitness(const Metadata *T) {
246+
// Special case: Memoize the bridge witness for Swift.String.
247+
// Swift.String is the most heavily used bridge because of the prevalence of
248+
// string-keyed dictionaries in Obj-C. It's worth burning a few words of static
249+
// storage to avoid repeatedly looking up this conformance.
250+
if (T->getKind() == MetadataKind::Struct) {
251+
auto structDescription = cast<StructMetadata>(T)->Description;
252+
if (structDescription == &NOMINAL_TYPE_DESCR_SYM(SS)) {
253+
static auto *Swift_String_ObjectiveCBridgeable = swift_conformsToObjectiveCBridgeableNoCache(T);
254+
return Swift_String_ObjectiveCBridgeable;
255+
}
256+
}
257+
258+
auto w = swift_conformsToObjectiveCBridgeable(T);
259+
if (SWIFT_LIKELY(w))
260+
return reinterpret_cast<const _ObjectiveCBridgeableWitnessTable *>(w);
261+
// Class and ObjC existential metatypes can be bridged, but metatypes can't
262+
// directly conform to protocols yet. Use a stand-in conformance for a type
263+
// that looks like a metatype value if the metatype can be bridged.
264+
switch (T->getKind()) {
265+
case MetadataKind::Metatype: {
266+
#if SWIFT_OBJC_INTEROP
267+
auto metaTy = static_cast<const MetatypeMetadata *>(T);
268+
if (metaTy->InstanceType->isAnyClass())
269+
return &BRIDGING_CONFORMANCE_SYM;
270+
#endif
271+
break;
272+
}
273+
case MetadataKind::ExistentialMetatype: {
274+
#if SWIFT_OBJC_INTEROP
275+
auto existentialMetaTy =
276+
static_cast<const ExistentialMetatypeMetadata *>(T);
277+
if (existentialMetaTy->isObjC())
278+
return &BRIDGING_CONFORMANCE_SYM;
279+
#endif
280+
break;
281+
}
282+
283+
default:
284+
break;
285+
}
286+
return nullptr;
287+
}
288+
216289
/// Retrieve the bridged Objective-C type for the given type that
217290
/// conforms to \c _ObjectiveCBridgeable.
218291
MetadataResponse
@@ -734,7 +807,7 @@ struct ObjCBridgeMemo {
734807
#if !NDEBUG
735808
memo->destType = setupData->destType;
736809
#endif
737-
memo->destBridgeWitness = findBridgeWitness(setupData->destType);
810+
memo->destBridgeWitness = swift_conformsToObjectiveCBridgeableNoCache(setupData->destType);
738811
if (memo->destBridgeWitness == nullptr) {
739812
memo->targetBridgedType = nullptr;
740813
memo->targetBridgedObjCClass = nullptr;
@@ -777,6 +850,17 @@ struct ObjCBridgeMemo {
777850
destBridgeWitness, targetBridgedType);
778851
}
779852
};
853+
854+
static const HashableWitnessTable* tryMemoizeNSStringHashableConformance(const Metadata *cls) {
855+
auto nsString = getNSStringMetadata();
856+
do {
857+
if (cls == nsString) {
858+
return getNSStringHashableConformance();
859+
}
860+
cls = _swift_class_getSuperclass(cls);
861+
} while (cls != nullptr);
862+
return nullptr;
863+
}
780864
#endif
781865

782866
static DynamicCastResult
@@ -794,23 +878,16 @@ tryCastToAnyHashable(
794878
const HashableWitnessTable *hashableConformance = nullptr;
795879

796880
switch (srcType->getKind()) {
881+
case MetadataKind::Existential: {
882+
return DynamicCastResult::Failure;
883+
}
797884
case MetadataKind::ForeignClass: // CF -> String
798885
case MetadataKind::ObjCClassWrapper: { // Obj-C -> String
799886
#if SWIFT_OBJC_INTEROP
800-
auto cls = srcType;
801-
auto nsString = getNSStringMetadata();
802-
do {
803-
if (cls == nsString) {
804-
hashableConformance = getNSStringHashableConformance();
805-
break;
806-
}
807-
cls = _swift_class_getSuperclass(cls);
808-
} while (cls != nullptr);
809-
break;
810-
#else
887+
hashableConformance = tryMemoizeNSStringHashableConformance(srcType);
888+
#endif
811889
// If no Obj-C interop, just fall through to the general case.
812890
break;
813-
#endif
814891
}
815892
case MetadataKind::Optional: {
816893
// FIXME: https://github.com/apple/swift/issues/51550

0 commit comments

Comments
 (0)