Skip to content

Commit b35987f

Browse files
authored
Merge pull request #68720 from tbkka/tbkka-SwiftValue-Equatable
Make ObjC isEqual: delegate to Equatable when Hashable isn't available
2 parents 954c0f5 + b1b7818 commit b35987f

File tree

5 files changed

+269
-46
lines changed

5 files changed

+269
-46
lines changed

stdlib/public/core/Equatable.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,16 @@ extension Equatable {
196196
}
197197
}
198198

199+
// Called by the SwiftValue implementation.
200+
@_silgen_name("_swift_stdlib_Equatable_isEqual_indirect")
201+
internal func Equatable_isEqual_indirect<T: Equatable>(
202+
_ lhs: UnsafePointer<T>,
203+
_ rhs: UnsafePointer<T>
204+
) -> Bool {
205+
return lhs.pointee == rhs.pointee
206+
}
207+
208+
199209
//===----------------------------------------------------------------------===//
200210
// Reference comparison
201211
//===----------------------------------------------------------------------===//
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: 165 additions & 46 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>
@@ -63,65 +64,169 @@ - (id)copyWithZone:(NSZone *)zone;
6364
/// The type of the value contained in the `__SwiftValue` box.
6465
const Metadata *type;
6566

66-
/// The base type that introduces the `Hashable` conformance.
67-
/// This member is only available for native Swift errors.
67+
/// The base type that introduces the `Hashable` or `Equatable` conformance.
6868
/// This member is lazily-initialized.
69-
/// Instead of using it directly, call `getHashableBaseType()`.
70-
mutable std::atomic<const Metadata *> hashableBaseType;
69+
/// Instead of using it directly, call `getHashableBaseType()` or `getEquatableBaseType()`
70+
/// Value here is encoded:
71+
/// * Least-significant bit set: This is an Equatable base type
72+
/// * Least-significant bit not set: This is a Hashable base type
73+
mutable std::atomic<uintptr_t> cachedBaseType;
7174

7275
/// The witness table for `Hashable` conformance.
73-
/// This member is only available for native Swift errors.
7476
/// This member is lazily-initialized.
7577
/// Instead of using it directly, call `getHashableConformance()`.
76-
mutable std::atomic<const hashable_support::HashableWitnessTable *>
77-
hashableConformance;
78+
/// Value here is encoded:
79+
/// * Least-significant bit set: This is an Equatable conformance
80+
/// * Least-significant bit not set: This is a Hashable conformance
81+
mutable std::atomic<uintptr_t> cachedConformance;
7882

7983
/// Get the base type that conforms to `Hashable`.
8084
/// Returns NULL if the type does not conform.
8185
const Metadata *getHashableBaseType() const;
8286

87+
/// Get the base type that conforms to `Equatable`.
88+
/// Returns NULL if the type does not conform.
89+
const Metadata *getEquatableBaseType() const;
90+
8391
/// Get the `Hashable` protocol witness table for the contained type.
8492
/// Returns NULL if the type does not conform.
8593
const hashable_support::HashableWitnessTable *getHashableConformance() const;
8694

95+
/// Get the `Equatable` protocol witness table for the contained type.
96+
/// Returns NULL if the type does not conform.
97+
const equatable_support::EquatableWitnessTable *getEquatableConformance() const;
98+
99+
/// Populate the `cachedConformance` with the Hashable conformance
100+
/// (if there is one), else the Equatable conformance.
101+
/// Returns the encoded conformance: least-significant
102+
/// bit is set if this is an Equatable conformance,
103+
/// else it is a Hashable conformance. 0 (or 1) indicates
104+
/// neither was found.
105+
uintptr_t cacheHashableEquatableConformance() const;
106+
107+
87108
SwiftValueHeader()
88-
: hashableBaseType(nullptr), hashableConformance(nullptr) {}
109+
: cachedBaseType(0), cachedConformance(0) {}
89110
};
90111

91-
const Metadata *SwiftValueHeader::getHashableBaseType() const {
92-
if (auto type = hashableBaseType.load(std::memory_order_acquire)) {
93-
if (reinterpret_cast<uintptr_t>(type) == 1) {
94-
return nullptr;
112+
// Set cachedConformance to the Hashable conformance if
113+
// there is one, else the Equatable conformance.
114+
// Also set cachedBaseType to the parent type that
115+
// introduced the Hashable/Equatable conformance.
116+
// The cached conformance and type are encoded:
117+
// * If the LSbit is not set, it's the Hashable conformance
118+
// * If the value is exactly 1, neither conformance is present
119+
// * If the LSbit is 1, strip it and you'll have the Equatable conformance
120+
// (Null indicates the cache has not been initialized yet;
121+
// that will never be true on exit of this function.)
122+
// Return: encoded cachedConformance value
123+
uintptr_t
124+
SwiftValueHeader::cacheHashableEquatableConformance() const {
125+
// Relevant conformance and baseType
126+
uintptr_t conformance;
127+
uintptr_t baseType;
128+
129+
// First, see if it's Hashable
130+
const HashableWitnessTable *hashable =
131+
reinterpret_cast<const HashableWitnessTable *>(
132+
swift_conformsToProtocolCommon(type, &HashableProtocolDescriptor));
133+
if (hashable != nullptr) {
134+
conformance = reinterpret_cast<uintptr_t>(hashable);
135+
baseType = reinterpret_cast<uintptr_t>(findHashableBaseType(type));
136+
} else {
137+
// If not Hashable, maybe Equatable?
138+
auto equatable =
139+
swift_conformsToProtocolCommon(type, &equatable_support::EquatableProtocolDescriptor);
140+
// Encode the equatable conformance
141+
conformance = reinterpret_cast<uintptr_t>(equatable) | 1;
142+
143+
if (equatable != nullptr) {
144+
// Find equatable base type
145+
#if SWIFT_STDLIB_USE_RELATIVE_PROTOCOL_WITNESS_TABLES
146+
const auto *description = lookThroughOptionalConditionalWitnessTable(
147+
reinterpret_cast<const RelativeWitnessTable*>(equatable))
148+
->getDescription();
149+
#else
150+
const auto *description = equatable->getDescription();
151+
#endif
152+
const Metadata *baseTypeThatConformsToEquatable =
153+
findConformingSuperclass(type, description);
154+
// Encode the equatable base type
155+
baseType = reinterpret_cast<uintptr_t>(baseTypeThatConformsToEquatable) | 1;
156+
} else {
157+
baseType = 1; // Neither equatable nor hashable
158+
}
95159
}
96-
return type;
160+
161+
// Set the conformance/baseType caches atomically
162+
uintptr_t expectedConformance = 0;
163+
cachedConformance.compare_exchange_strong(
164+
expectedConformance, conformance, std::memory_order_acq_rel);
165+
uintptr_t expectedType = 0;
166+
cachedBaseType.compare_exchange_strong(
167+
expectedType, baseType, std::memory_order_acq_rel);
168+
169+
return conformance;
170+
}
171+
172+
const Metadata *SwiftValueHeader::getHashableBaseType() const {
173+
auto type = cachedBaseType.load(std::memory_order_acquire);
174+
if (type == 0) {
175+
cacheHashableEquatableConformance();
176+
type = cachedBaseType.load(std::memory_order_acquire);
97177
}
178+
if ((type & 1) == 0) {
179+
// A Hashable conformance was found
180+
return reinterpret_cast<const Metadata *>(type);
181+
} else {
182+
// Equatable conformance (or no conformance) found
183+
return nullptr;
184+
}
185+
}
98186

99-
const Metadata *expectedType = nullptr;
100-
const Metadata *hashableBaseType = findHashableBaseType(type);
101-
this->hashableBaseType.compare_exchange_strong(
102-
expectedType, hashableBaseType ? hashableBaseType
103-
: reinterpret_cast<const Metadata *>(1),
104-
std::memory_order_acq_rel);
105-
return type;
187+
const Metadata *SwiftValueHeader::getEquatableBaseType() const {
188+
auto type = cachedBaseType.load(std::memory_order_acquire);
189+
if (type == 0) {
190+
cacheHashableEquatableConformance();
191+
type = cachedBaseType.load(std::memory_order_acquire);
192+
}
193+
if ((type & 1) == 0) {
194+
// A Hashable conformance was found
195+
return nullptr;
196+
} else {
197+
// An Equatable conformance (or neither) was found
198+
return reinterpret_cast<const Metadata *>(type & ~1ULL);
199+
}
106200
}
107201

108202
const hashable_support::HashableWitnessTable *
109203
SwiftValueHeader::getHashableConformance() const {
110-
if (auto wt = hashableConformance.load(std::memory_order_acquire)) {
111-
if (reinterpret_cast<uintptr_t>(wt) == 1) {
112-
return nullptr;
113-
}
114-
return wt;
204+
uintptr_t wt = cachedConformance.load(std::memory_order_acquire);
205+
if (wt == 0) {
206+
wt = cacheHashableEquatableConformance();
207+
}
208+
if ((wt & 1) == 0) {
209+
// Hashable conformance found
210+
return reinterpret_cast<const hashable_support::HashableWitnessTable *>(wt);
211+
} else {
212+
// Equatable conformance (or no conformance) found
213+
return nullptr;
115214
}
215+
}
116216

117-
const HashableWitnessTable *expectedWT = nullptr;
118-
const HashableWitnessTable *wt =
119-
reinterpret_cast<const HashableWitnessTable *>(
120-
swift_conformsToProtocolCommon(type, &HashableProtocolDescriptor));
121-
hashableConformance.compare_exchange_strong(
122-
expectedWT, wt ? wt : reinterpret_cast<const HashableWitnessTable *>(1),
123-
std::memory_order_acq_rel);
124-
return wt;
217+
const equatable_support::EquatableWitnessTable *
218+
SwiftValueHeader::getEquatableConformance() const {
219+
uintptr_t wt = cachedConformance.load(std::memory_order_acquire);
220+
if (wt == 0) {
221+
wt = cacheHashableEquatableConformance();
222+
}
223+
if ((wt & 1) == 0) {
224+
// Hashable conformance found
225+
return nullptr;
226+
} else {
227+
// Equatable conformance (or no conformance) found
228+
return reinterpret_cast<const equatable_support::EquatableWitnessTable *>(wt & ~1ULL);
229+
}
125230
}
126231

127232
static constexpr const size_t SwiftValueHeaderOffset
@@ -300,30 +405,44 @@ - (BOOL)isEqual:(id)other {
300405
return NO;
301406
}
302407

408+
// `other` must also be a _SwiftValue box
303409
if (![other isKindOfClass:getSwiftValueClass()]) {
304410
return NO;
305411
}
306412

307413
auto selfHeader = getSwiftValueHeader(self);
308414
auto otherHeader = getSwiftValueHeader(other);
309415

310-
auto hashableBaseType = selfHeader->getHashableBaseType();
311-
if (!hashableBaseType ||
312-
otherHeader->getHashableBaseType() != hashableBaseType) {
313-
return NO;
416+
if (auto hashableConformance = selfHeader->getHashableConformance()) {
417+
if (auto selfHashableBaseType = selfHeader->getHashableBaseType()) {
418+
auto otherHashableBaseType = otherHeader->getHashableBaseType();
419+
if (selfHashableBaseType == otherHashableBaseType) {
420+
return _swift_stdlib_Hashable_isEqual_indirect(
421+
getSwiftValuePayload(self,
422+
getSwiftValuePayloadAlignMask(selfHeader->type)),
423+
getSwiftValuePayload(other,
424+
getSwiftValuePayloadAlignMask(otherHeader->type)),
425+
selfHashableBaseType, hashableConformance);
426+
}
427+
}
314428
}
315429

316-
auto hashableConformance = selfHeader->getHashableConformance();
317-
if (!hashableConformance) {
318-
return NO;
430+
if (auto equatableConformance = selfHeader->getEquatableConformance()) {
431+
if (auto selfEquatableBaseType = selfHeader->getEquatableBaseType()) {
432+
auto otherEquatableBaseType = otherHeader->getEquatableBaseType();
433+
if (selfEquatableBaseType == otherEquatableBaseType) {
434+
return _swift_stdlib_Equatable_isEqual_indirect(
435+
getSwiftValuePayload(self,
436+
getSwiftValuePayloadAlignMask(selfHeader->type)),
437+
getSwiftValuePayload(other,
438+
getSwiftValuePayloadAlignMask(otherHeader->type)),
439+
selfEquatableBaseType, equatableConformance);
440+
}
441+
}
319442
}
320443

321-
return _swift_stdlib_Hashable_isEqual_indirect(
322-
getSwiftValuePayload(self,
323-
getSwiftValuePayloadAlignMask(selfHeader->type)),
324-
getSwiftValuePayload(other,
325-
getSwiftValuePayloadAlignMask(otherHeader->type)),
326-
hashableBaseType, hashableConformance);
444+
// Not Equatable, not Hashable, and not the same box
445+
return NO;
327446
}
328447

329448
- (NSUInteger)hash {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -g %s -o %t/a.out
3+
// RUN: %target-codesign %t/a.out
4+
// RUN: %target-run %t/a.out
5+
6+
// REQUIRES: executable_test
7+
// REQUIRES: objc_interop
8+
9+
import StdlibUnittest
10+
import Foundation
11+
12+
var BridgeEquatableToObjC = TestSuite("BridgeEquatableToObjC")
13+
14+
struct MyEquatableStruct: Equatable {
15+
var text: String
16+
}
17+
18+
struct MyNonEquatableStruct {
19+
var text: String
20+
}
21+
22+
BridgeEquatableToObjC.test("Bridge equatable struct") {
23+
let swiftA = MyEquatableStruct(text: "xABC")
24+
let swiftB = swiftA
25+
let swiftResult = swiftA == swiftB
26+
27+
let objcA = swiftA as AnyObject
28+
let objcB = swiftB as AnyObject
29+
let objcResult = objcA.isEqual(objcB)
30+
31+
expectEqual(swiftResult, true)
32+
expectEqual(objcResult, true)
33+
}
34+
35+
BridgeEquatableToObjC.test("Bridge non-equatable struct") {
36+
let swiftA = MyNonEquatableStruct(text: "xABC")
37+
let swiftB = swiftA
38+
39+
let objcA = swiftA as AnyObject
40+
let objcB = swiftB as AnyObject
41+
let objcResult = objcA.isEqual(objcB)
42+
43+
expectEqual(objcResult, false)
44+
}
45+
46+
47+
runAllTests()

unittests/runtime/Stdlib.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ bool _swift_anyHashableDownCastConditionalIndirect(
6161
abort();
6262
}
6363

64+
// SwiftEquatableSupport
65+
66+
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL
67+
bool _swift_stdlib_Equatable_isEqual_indirect(
68+
const void *lhsValue, const void *rhsValue, const Metadata *type,
69+
const void *wt) {
70+
abort();
71+
}
72+
6473
// Casting
6574

6675
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_INTERNAL

0 commit comments

Comments
 (0)