Skip to content

Commit 5cff703

Browse files
committed
Cache Equatable conformance if Hashable is not present
1 parent 659d1cf commit 5cff703

File tree

2 files changed

+153
-78
lines changed

2 files changed

+153
-78
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===------------------------------------------------------------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef SWIFT_RUNTIME_SWIFT_EQUATABLE_SUPPORT_H
14+
#define SWIFT_RUNTIME_SWIFT_EQUATABLE_SUPPORT_H
15+
16+
#include "swift/Runtime/Metadata.h"
17+
#include <stdint.h>
18+
19+
namespace swift {
20+
namespace equatable_support {
21+
22+
extern "C" const ProtocolDescriptor PROTOCOL_DESCR_SYM(SQ);
23+
static constexpr auto &EquatableProtocolDescriptor = PROTOCOL_DESCR_SYM(SQ);
24+
25+
struct EquatableWitnessTable;
26+
27+
/// Calls `Equatable.==` through a `Equatable` (not Equatable!) witness
28+
/// table.
29+
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL
30+
bool _swift_stdlib_Equatable_isEqual_indirect(
31+
const void *lhsValue, const void *rhsValue, const Metadata *type,
32+
const EquatableWitnessTable *wt);
33+
34+
} // namespace equatable_support
35+
} // namespace swift
36+
37+
#endif
38+

stdlib/public/runtime/SwiftValue.mm

Lines changed: 115 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "swift/Runtime/ObjCBridge.h"
3131
#include "swift/Runtime/Debug.h"
3232
#include "Private.h"
33+
#include "SwiftEquatableSupport.h"
3334
#include "SwiftHashableSupport.h"
3435
#include <objc/runtime.h>
3536
#include <Foundation/Foundation.h>
@@ -57,32 +58,22 @@ - (id)copyWithZone:(NSZone *)zone;
5758

5859
@end
5960

60-
struct EquatableWitnessTable;
61-
62-
/// Calls `Equatable.==` through an `Equatable` witness table
63-
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL
64-
bool _swift_stdlib_Equatable_isEqual_indirect(
65-
const void *lhsValue, const void *rhsValue, const Metadata *type,
66-
const EquatableWitnessTable *wt);
67-
6861
/// The fixed-size ivars of `__SwiftValue`. The actual boxed value is
6962
/// tail-allocated.
7063
struct SwiftValueHeader {
7164
/// The type of the value contained in the `__SwiftValue` box.
7265
const Metadata *type;
7366

74-
/// The base type that introduces the `Hashable` conformance.
75-
/// This member is only available for native Swift errors.
67+
/// The base type that introduces the `Hashable` or `Equatable` conformance.
7668
/// This member is lazily-initialized.
77-
/// Instead of using it directly, call `getHashableBaseType()`.
78-
mutable std::atomic<const Metadata *> hashableBaseType;
69+
/// Instead of using it directly, call `getHashableBaseType()` or `getEquatableBaseType()`
70+
mutable std::atomic<const Metadata *> cachedBaseType;
7971

8072
/// The witness table for `Hashable` conformance.
8173
/// This member is only available for native Swift errors.
8274
/// This member is lazily-initialized.
8375
/// Instead of using it directly, call `getHashableConformance()`.
84-
mutable std::atomic<const hashable_support::HashableWitnessTable *>
85-
hashableConformance;
76+
mutable std::atomic<const void *> cachedConformance;
8677

8778
/// Get the base type that conforms to `Hashable`.
8879
/// Returns NULL if the type does not conform.
@@ -98,75 +89,126 @@ bool _swift_stdlib_Equatable_isEqual_indirect(
9889

9990
/// Get the `Equatable` protocol witness table for the contained type.
10091
/// Returns NULL if the type does not conform.
101-
const EquatableWitnessTable *getEquatableConformance() const;
92+
const equatable_support::EquatableWitnessTable *getEquatableConformance() const;
93+
94+
/// Populate the `cachedConformance` with the Hashable conformance
95+
/// (if there is one), else the Equatable conformance.
96+
const void * cacheHashableEquatableConformance() const;
97+
10298

10399
SwiftValueHeader()
104-
: hashableBaseType(nullptr), hashableConformance(nullptr) {}
100+
: cachedBaseType(nullptr), cachedConformance((void *)nullptr) {}
105101
};
106102

107103
const Metadata *SwiftValueHeader::getHashableBaseType() const {
108-
if (auto type = hashableBaseType.load(std::memory_order_acquire)) {
109-
if (reinterpret_cast<uintptr_t>(type) == 1) {
110-
return nullptr;
111-
}
104+
auto type = cachedBaseType.load(std::memory_order_acquire);
105+
if (type == nullptr) {
106+
cacheHashableEquatableConformance();
107+
type = cachedBaseType.load(std::memory_order_acquire);
108+
}
109+
if ((reinterpret_cast<uintptr_t>(type) & 1) == 0) {
112110
return type;
111+
} else {
112+
return nullptr;
113113
}
114-
115-
const Metadata *expectedType = nullptr;
116-
const Metadata *hashableBaseType = findHashableBaseType(type);
117-
this->hashableBaseType.compare_exchange_strong(
118-
expectedType, hashableBaseType ? hashableBaseType
119-
: reinterpret_cast<const Metadata *>(1),
120-
std::memory_order_acq_rel);
121-
return type;
122114
}
123115

124-
extern "C" const ProtocolDescriptor PROTOCOL_DESCR_SYM(SQ);
125-
static constexpr auto &EquatableProtocolDescriptor = PROTOCOL_DESCR_SYM(SQ);
126-
127116
const Metadata *SwiftValueHeader::getEquatableBaseType() const {
128-
auto witnessTable =
129-
swift_conformsToProtocolCommon(type, &EquatableProtocolDescriptor);
130-
if (!witnessTable) {
117+
auto type = cachedBaseType.load(std::memory_order_acquire);
118+
if (type == nullptr) {
119+
cacheHashableEquatableConformance();
120+
type = cachedBaseType.load(std::memory_order_acquire);
121+
}
122+
if ((reinterpret_cast<uintptr_t>(type) & 1) == 0) {
123+
// A Hashable conformance was found
131124
return nullptr;
125+
} else {
126+
// An Equatable conformance (or neither) was found
127+
return reinterpret_cast<const Metadata *>(reinterpret_cast<uintptr_t>(type) & ~1ULL);
132128
}
129+
}
130+
131+
// Set cachedConformance to the Hashable conformance if
132+
// there is one, else the Equatable conformance.
133+
// Also set cachedBaseType to the parent type that
134+
// introduced the Hashable/Equatable conformance.
135+
// The cached conformance and type set the LSbit to indicate
136+
// which conformance is present:
137+
// * If the LSbit is not set, it's the Hashable conformance
138+
// * If the value is exactly 1, neither conformance is present
139+
// * If the LSbit is 1, strip it and you'll have the Equatable conformance
140+
// (Null indicates the cache has not been initialized yet)
141+
const void *
142+
SwiftValueHeader::cacheHashableEquatableConformance() const {
143+
// Relevant conformance and baseType
144+
const void * conformance;
145+
const Metadata * baseType;
146+
147+
// First, see if it's Hashable
148+
const HashableWitnessTable *hashable =
149+
reinterpret_cast<const HashableWitnessTable *>(
150+
swift_conformsToProtocolCommon(type, &HashableProtocolDescriptor));
151+
if (hashable != nullptr) {
152+
conformance = hashable;
153+
baseType = findHashableBaseType(type);
154+
} else {
155+
// If not Hashable, maybe Equatable?
156+
auto equatable =
157+
swift_conformsToProtocolCommon(type, &equatable_support::EquatableProtocolDescriptor);
158+
conformance = reinterpret_cast<const void *>(reinterpret_cast<uintptr_t>(equatable) | 1);
159+
160+
// Find equatable base type
133161
#if SWIFT_STDLIB_USE_RELATIVE_PROTOCOL_WITNESS_TABLES
134-
const auto *conformance = lookThroughOptionalConditionalWitnessTable(
135-
reinterpret_cast<const RelativeWitnessTable*>(witnessTable))
136-
->getDescription();
162+
const auto *conformance = lookThroughOptionalConditionalWitnessTable(
163+
reinterpret_cast<const RelativeWitnessTable*>(equatable))
164+
->getDescription();
137165
#else
138-
const auto *conformance = witnessTable->getDescription();
166+
const auto *conformance = equatable->getDescription();
139167
#endif
140-
const Metadata *baseTypeThatConformsToEquatable =
141-
findConformingSuperclass(type, conformance);
142-
return baseTypeThatConformsToEquatable;
168+
const Metadata *baseTypeThatConformsToEquatable =
169+
findConformingSuperclass(type, conformance);
170+
baseType = reinterpret_cast<const Metadata *>(reinterpret_cast<uintptr_t>(baseTypeThatConformsToEquatable) | 1);
171+
}
172+
173+
// Set the conformance/baseType caches atomically
174+
const void * expectedConformance = nullptr;
175+
cachedConformance.compare_exchange_strong(
176+
expectedConformance, conformance, std::memory_order_acq_rel);
177+
const Metadata * expectedType = (const Metadata *)nullptr;
178+
cachedBaseType.compare_exchange_strong(
179+
expectedType, baseType, std::memory_order_acq_rel);
180+
181+
return conformance;
143182
}
144183

145184
const hashable_support::HashableWitnessTable *
146185
SwiftValueHeader::getHashableConformance() const {
147-
if (auto wt = hashableConformance.load(std::memory_order_acquire)) {
148-
if (reinterpret_cast<uintptr_t>(wt) == 1) {
149-
return nullptr;
150-
}
151-
return wt;
186+
const void * wt = cachedConformance.load(std::memory_order_acquire);
187+
if (wt == nullptr) {
188+
wt = cacheHashableEquatableConformance();
189+
}
190+
if ((reinterpret_cast<uintptr_t>(wt) & 1) == 0) {
191+
// Hashable conformance found
192+
return reinterpret_cast<const hashable_support::HashableWitnessTable *>(wt);
193+
} else {
194+
// Equatable conformance (or no conformance) found
195+
return nullptr;
152196
}
153-
154-
const HashableWitnessTable *expectedWT = nullptr;
155-
const HashableWitnessTable *wt =
156-
reinterpret_cast<const HashableWitnessTable *>(
157-
swift_conformsToProtocolCommon(type, &HashableProtocolDescriptor));
158-
hashableConformance.compare_exchange_strong(
159-
expectedWT, wt ? wt : reinterpret_cast<const HashableWitnessTable *>(1),
160-
std::memory_order_acq_rel);
161-
return wt;
162197
}
163198

164-
const EquatableWitnessTable *
199+
const equatable_support::EquatableWitnessTable *
165200
SwiftValueHeader::getEquatableConformance() const {
166-
const EquatableWitnessTable *wt =
167-
reinterpret_cast<const EquatableWitnessTable *>(
168-
swift_conformsToProtocolCommon(type, &EquatableProtocolDescriptor));
169-
return wt;
201+
const void * wt = cachedConformance.load(std::memory_order_acquire);
202+
if (wt == nullptr) {
203+
wt = cacheHashableEquatableConformance();
204+
}
205+
if ((reinterpret_cast<uintptr_t>(wt) & 1) == 0) {
206+
// Hashable conformance found
207+
return nullptr;
208+
} else {
209+
// Equatable conformance (or no conformance) found
210+
return reinterpret_cast<const equatable_support::EquatableWitnessTable *>(reinterpret_cast<uintptr_t>(wt) & ~1ULL);
211+
}
170212
}
171213

172214
static constexpr const size_t SwiftValueHeaderOffset
@@ -353,16 +395,12 @@ - (BOOL)isEqual:(id)other {
353395
auto selfHeader = getSwiftValueHeader(self);
354396
auto otherHeader = getSwiftValueHeader(other);
355397

356-
// TODO: getHashableBaseType seems to always succeed,
357-
// even if the type is in fact not Hashable. Why?
358-
// Maybe we should try getting the conformance first,
359-
// since that seems to actually fail?
360-
auto selfHashableBaseType = selfHeader->getHashableBaseType();
361-
if (selfHashableBaseType) {
362-
auto otherHashableBaseType = otherHeader->getHashableBaseType();
363-
if (selfHashableBaseType == otherHashableBaseType) {
364-
auto hashableConformance = selfHeader->getHashableConformance();
365-
if (hashableConformance) {
398+
auto hashableConformance = selfHeader->getHashableConformance();
399+
if (hashableConformance) {
400+
auto selfHashableBaseType = selfHeader->getHashableBaseType();
401+
if (selfHashableBaseType) {
402+
auto otherHashableBaseType = otherHeader->getHashableBaseType();
403+
if (selfHashableBaseType == otherHashableBaseType) {
366404
return _swift_stdlib_Hashable_isEqual_indirect(
367405
getSwiftValuePayload(self,
368406
getSwiftValuePayloadAlignMask(selfHeader->type)),
@@ -373,14 +411,12 @@ - (BOOL)isEqual:(id)other {
373411
}
374412
}
375413

376-
// TODO: As above, getEquatableBaseType seems to always succeed
377-
// even if the type is not in fact Equatable.
378-
auto selfEquatableBaseType = selfHeader->getEquatableBaseType();
379-
if (selfEquatableBaseType) {
380-
auto otherEquatableBaseType = otherHeader->getEquatableBaseType();
381-
if (selfEquatableBaseType == otherEquatableBaseType) {
382-
auto equatableConformance = selfHeader->getEquatableConformance();
383-
if (equatableConformance) {
414+
auto equatableConformance = selfHeader->getEquatableConformance();
415+
if (equatableConformance) {
416+
auto selfEquatableBaseType = selfHeader->getEquatableBaseType();
417+
if (selfEquatableBaseType) {
418+
auto otherEquatableBaseType = otherHeader->getEquatableBaseType();
419+
if (selfEquatableBaseType == otherEquatableBaseType) {
384420
return _swift_stdlib_Equatable_isEqual_indirect(
385421
getSwiftValuePayload(self,
386422
getSwiftValuePayloadAlignMask(selfHeader->type)),
@@ -391,6 +427,7 @@ - (BOOL)isEqual:(id)other {
391427
}
392428
}
393429

430+
// Not Equatable, not Hashable, and not the same box
394431
return NO;
395432
}
396433

0 commit comments

Comments
 (0)