Skip to content

Cache the last ObjC bridging conformance we looked up #81545

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions benchmark/single-source/ObjectiveCBridging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ public let benchmarks = [
BenchmarkInfo(name: "NSArray.bridged.repeatedBufferAccess",
runFunction: run_BridgedNSArrayRepeatedBufferAccess, tags: t,
setUpFunction: setup_bridgedArrays),
BenchmarkInfo(name: "NSDictionary.bridged.enumerate",
runFunction: run_BridgedNSDictionaryEnumerate, tags: t,
setUpFunction: setup_bridgedDictionaries),
]

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

#if _runtime(_ObjC)
var bridgedArray:NSArray! = nil
var bridgedDictionaryOfNumbersToNumbers:NSDictionary! = nil
var bridgedArrayMutableCopy:NSMutableArray! = nil
var nsArray:NSArray! = nil
var nsArrayMutableCopy:NSMutableArray! = nil
Expand All @@ -804,11 +808,21 @@ public func setup_bridgedArrays() {
var arr = Array(repeating: NSObject(), count: 100) as [AnyObject]
bridgedArray = arr as NSArray
bridgedArrayMutableCopy = (bridgedArray.mutableCopy() as! NSMutableArray)

nsArray = NSArray(objects: &arr, count: 100)
nsArrayMutableCopy = (nsArray.mutableCopy() as! NSMutableArray)
#endif
}

public func setup_bridgedDictionaries() {
var numDict = Dictionary<Int, Int>()
for i in 0 ..< 100 {
numDict[i] = i
}
bridgedDictionaryOfNumbersToNumbers = numDict as NSDictionary
}


@inline(never)
public func run_BridgedNSArrayObjectAtIndex(_ n: Int) {
#if _runtime(_ObjC)
Expand All @@ -820,6 +834,23 @@ public func run_BridgedNSArrayObjectAtIndex(_ n: Int) {
#endif
}

private func dictionaryApplier(
_ keyPtr: UnsafeRawPointer?,
_ valuePtr :UnsafeRawPointer?,
_ contextPtr: UnsafeMutableRawPointer?
) -> Void {}

@inline(never)
public func run_BridgedNSDictionaryEnumerate(_ n: Int) {
#if _runtime(_ObjC)
let cf = bridgedDictionaryOfNumbersToNumbers as CFDictionary
for _ in 0 ..< n * 50 {
// Use CF to prevent Swift from providing an override, forcing going through ObjC bridging
CFDictionaryApplyFunction(cf, dictionaryApplier, nil)
}
#endif
}

@inline(never)
public func run_BridgedNSArrayBufferAccess(_ n: Int) {
#if _runtime(_ObjC)
Expand Down
27 changes: 24 additions & 3 deletions stdlib/public/runtime/Casting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1436,10 +1436,31 @@ extern "C" const _ObjectiveCBridgeableWitnessTable BRIDGING_CONFORMANCE_SYM;
/// Nominal type descriptor for Swift.String.
extern "C" const StructDescriptor NOMINAL_TYPE_DESCR_SYM(SS);

struct ObjCBridgeWitnessCacheEntry {
const Metadata *metadata;
const _ObjectiveCBridgeableWitnessTable *witness;
};

// String is so important that we cache it permanently, so we don't want to
// pollute this temporary cache with the String entry
static const _ObjectiveCBridgeableWitnessTable *
swift_conformsToObjectiveCBridgeableNoCache(const Metadata *T) {
auto w = swift_conformsToProtocolCommon(
T, &PROTOCOL_DESCR_SYM(s21_ObjectiveCBridgeable));
return reinterpret_cast<const _ObjectiveCBridgeableWitnessTable *>(w);
}

static const _ObjectiveCBridgeableWitnessTable *
swift_conformsToObjectiveCBridgeable(const Metadata *T) {
return reinterpret_cast<const _ObjectiveCBridgeableWitnessTable *>
(swift_conformsToProtocolCommon(T, &PROTOCOL_DESCR_SYM(s21_ObjectiveCBridgeable)));
static std::atomic<ObjCBridgeWitnessCacheEntry> _objcBridgeWitnessCache = {};
auto cached = _objcBridgeWitnessCache.load(SWIFT_MEMORY_ORDER_CONSUME);
if (cached.metadata == T) {
return cached.witness;
}
cached.witness = swift_conformsToObjectiveCBridgeableNoCache(T);
cached.metadata = T;
_objcBridgeWitnessCache.store(cached, std::memory_order_release);
return cached.witness;
}

static const _ObjectiveCBridgeableWitnessTable *
Expand All @@ -1451,7 +1472,7 @@ findBridgeWitness(const Metadata *T) {
if (T->getKind() == MetadataKind::Struct) {
auto structDescription = cast<StructMetadata>(T)->Description;
if (structDescription == &NOMINAL_TYPE_DESCR_SYM(SS)) {
static auto *Swift_String_ObjectiveCBridgeable = swift_conformsToObjectiveCBridgeable(T);
static auto *Swift_String_ObjectiveCBridgeable = swift_conformsToObjectiveCBridgeableNoCache(T);
return Swift_String_ObjectiveCBridgeable;
}
}
Expand Down
79 changes: 76 additions & 3 deletions stdlib/public/runtime/DynamicCast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,86 @@ struct _ObjectiveCBridgeableWitnessTable : WitnessTable {
extern "C" const ProtocolDescriptor
PROTOCOL_DESCR_SYM(s21_ObjectiveCBridgeable);

#if SWIFT_OBJC_INTEROP
#define BRIDGING_CONFORMANCE_SYM \
MANGLE_SYM(s19_BridgeableMetatypeVs21_ObjectiveCBridgeablesWP)

extern "C" const _ObjectiveCBridgeableWitnessTable BRIDGING_CONFORMANCE_SYM;
#endif

/// Nominal type descriptor for Swift.String.
extern "C" const StructDescriptor NOMINAL_TYPE_DESCR_SYM(SS);

struct ObjCBridgeWitnessCacheEntry {
const Metadata *metadata;
const _ObjectiveCBridgeableWitnessTable *witness;
};

static const _ObjectiveCBridgeableWitnessTable *
findBridgeWitness(const Metadata *T) {
swift_conformsToObjectiveCBridgeableNoCache(const Metadata *T) {
auto w = swift_conformsToProtocolCommon(
T, &PROTOCOL_DESCR_SYM(s21_ObjectiveCBridgeable));
T, &PROTOCOL_DESCR_SYM(s21_ObjectiveCBridgeable));
return reinterpret_cast<const _ObjectiveCBridgeableWitnessTable *>(w);
}

static const _ObjectiveCBridgeableWitnessTable *
swift_conformsToObjectiveCBridgeable(const Metadata *T) {
static std::atomic<ObjCBridgeWitnessCacheEntry> _objcBridgeWitnessCache = {};
auto cached = _objcBridgeWitnessCache.load(SWIFT_MEMORY_ORDER_CONSUME);
if (cached.metadata == T) {
return cached.witness;
}
cached.witness = swift_conformsToObjectiveCBridgeableNoCache(T);
cached.metadata = T;
_objcBridgeWitnessCache.store(cached, std::memory_order_release);
return cached.witness;
}

static const _ObjectiveCBridgeableWitnessTable *
findBridgeWitness(const Metadata *T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire function is duplicated between DynamicCast.cpp and Casting.cpp, along with its memoization variables. There's also a separate memoization of String stuff in DynamicCast.cpp. I'm unifying the behavior of these (yes they had behavior differences), but saving actually unifying the implementations for later.

// Special case: Memoize the bridge witness for Swift.String.
// Swift.String is the most heavily used bridge because of the prevalence of
// string-keyed dictionaries in Obj-C. It's worth burning a few words of static
// storage to avoid repeatedly looking up this conformance.
if (T->getKind() == MetadataKind::Struct) {
auto structDescription = cast<StructMetadata>(T)->Description;
if (structDescription == &NOMINAL_TYPE_DESCR_SYM(SS)) {
static auto *Swift_String_ObjectiveCBridgeable = swift_conformsToObjectiveCBridgeableNoCache(T);
return Swift_String_ObjectiveCBridgeable;
}
}

auto w = swift_conformsToObjectiveCBridgeable(T);
if (SWIFT_LIKELY(w))
return reinterpret_cast<const _ObjectiveCBridgeableWitnessTable *>(w);
// Class and ObjC existential metatypes can be bridged, but metatypes can't
// directly conform to protocols yet. Use a stand-in conformance for a type
// that looks like a metatype value if the metatype can be bridged.
switch (T->getKind()) {
case MetadataKind::Metatype: {
#if SWIFT_OBJC_INTEROP
auto metaTy = static_cast<const MetatypeMetadata *>(T);
if (metaTy->InstanceType->isAnyClass())
return &BRIDGING_CONFORMANCE_SYM;
#endif
break;
}
case MetadataKind::ExistentialMetatype: {
#if SWIFT_OBJC_INTEROP
auto existentialMetaTy =
static_cast<const ExistentialMetatypeMetadata *>(T);
if (existentialMetaTy->isObjC())
return &BRIDGING_CONFORMANCE_SYM;
#endif
break;
}

default:
break;
}
return nullptr;
}

/// Retrieve the bridged Objective-C type for the given type that
/// conforms to \c _ObjectiveCBridgeable.
MetadataResponse
Expand Down Expand Up @@ -734,7 +807,7 @@ struct ObjCBridgeMemo {
#if !NDEBUG
memo->destType = setupData->destType;
#endif
memo->destBridgeWitness = findBridgeWitness(setupData->destType);
memo->destBridgeWitness = swift_conformsToObjectiveCBridgeableNoCache(setupData->destType);
if (memo->destBridgeWitness == nullptr) {
memo->targetBridgedType = nullptr;
memo->targetBridgedObjCClass = nullptr;
Expand Down