Skip to content

Commit e795eb0

Browse files
authored
Cache the last ObjC bridging conformance we looked up (#81545)
Fixes rdar://151475392
1 parent 779c31a commit e795eb0

File tree

3 files changed

+131
-6
lines changed

3 files changed

+131
-6
lines changed

benchmark/single-source/ObjectiveCBridging.swift

Lines changed: 31 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
]
101104

102105
#if _runtime(_ObjC)
@@ -794,6 +797,7 @@ public func run_UnicodeStringFromCodable(_ n: Int) {
794797

795798
#if _runtime(_ObjC)
796799
var bridgedArray:NSArray! = nil
800+
var bridgedDictionaryOfNumbersToNumbers:NSDictionary! = nil
797801
var bridgedArrayMutableCopy:NSMutableArray! = nil
798802
var nsArray:NSArray! = nil
799803
var nsArrayMutableCopy:NSMutableArray! = nil
@@ -804,11 +808,21 @@ public func setup_bridgedArrays() {
804808
var arr = Array(repeating: NSObject(), count: 100) as [AnyObject]
805809
bridgedArray = arr as NSArray
806810
bridgedArrayMutableCopy = (bridgedArray.mutableCopy() as! NSMutableArray)
811+
807812
nsArray = NSArray(objects: &arr, count: 100)
808813
nsArrayMutableCopy = (nsArray.mutableCopy() as! NSMutableArray)
809814
#endif
810815
}
811816

817+
public func setup_bridgedDictionaries() {
818+
var numDict = Dictionary<Int, Int>()
819+
for i in 0 ..< 100 {
820+
numDict[i] = i
821+
}
822+
bridgedDictionaryOfNumbersToNumbers = numDict as NSDictionary
823+
}
824+
825+
812826
@inline(never)
813827
public func run_BridgedNSArrayObjectAtIndex(_ n: Int) {
814828
#if _runtime(_ObjC)
@@ -820,6 +834,23 @@ public func run_BridgedNSArrayObjectAtIndex(_ n: Int) {
820834
#endif
821835
}
822836

837+
private func dictionaryApplier(
838+
_ keyPtr: UnsafeRawPointer?,
839+
_ valuePtr :UnsafeRawPointer?,
840+
_ contextPtr: UnsafeMutableRawPointer?
841+
) -> Void {}
842+
843+
@inline(never)
844+
public func run_BridgedNSDictionaryEnumerate(_ n: Int) {
845+
#if _runtime(_ObjC)
846+
let cf = bridgedDictionaryOfNumbersToNumbers as CFDictionary
847+
for _ in 0 ..< n * 50 {
848+
// Use CF to prevent Swift from providing an override, forcing going through ObjC bridging
849+
CFDictionaryApplyFunction(cf, dictionaryApplier, nil)
850+
}
851+
#endif
852+
}
853+
823854
@inline(never)
824855
public func run_BridgedNSArrayBufferAccess(_ n: Int) {
825856
#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: 76 additions & 3 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;

0 commit comments

Comments
 (0)