Skip to content

Commit e227502

Browse files
committed
Implement an SPI to iterate types who conform to some protocol
1 parent 2e8ae40 commit e227502

File tree

8 files changed

+412
-1
lines changed

8 files changed

+412
-1
lines changed

stdlib/public/Reflection/Sources/Reflection/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ list(APPEND SWIFT_REFLECTION_SWIFT_FLAGS
1717

1818
add_swift_target_library(swiftReflection ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB
1919
Case.swift
20+
Conformances.swift
2021
Field.swift
2122
GenericArguments.swift
2223
KeyPath.swift
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022 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+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
13+
14+
import Swift
15+
import _Runtime
16+
17+
@available(SwiftStdlib 5.9, *)
18+
@_silgen_name("_swift_reflection_withConformanceCache")
19+
func _withConformanceCache(
20+
_ proto: ProtocolDescriptor,
21+
_ context: UnsafeMutableRawPointer,
22+
_ callback: @convention(c) (
23+
/* Array of types */ UnsafePointer<UnsafeRawPointer>,
24+
/* Number of types */ Int,
25+
/* Context we just passed */ UnsafeMutableRawPointer
26+
) -> ()
27+
)
28+
29+
@available(SwiftStdlib 5.9, *)
30+
@_spi(Reflection)
31+
public func _typesThatConform(to type: Any.Type) -> [Any.Type]? {
32+
let meta = Metadata(type)
33+
34+
guard meta.kind == .existential else {
35+
return nil
36+
}
37+
38+
let existential = meta.existential
39+
40+
let protos = existential.protocols
41+
42+
guard protos.count == 1 else {
43+
return nil
44+
}
45+
46+
let proto = protos[0]
47+
48+
var result: [Any.Type] = []
49+
50+
withUnsafeMutablePointer(to: &result) {
51+
_withConformanceCache(proto, UnsafeMutableRawPointer($0)) {
52+
let buffer = UnsafeBufferPointer<Any.Type>(
53+
start: UnsafePointer<Any.Type>($0._rawValue),
54+
count: $1
55+
)
56+
57+
let arrayPtr = $2.assumingMemoryBound(to: [Any.Type].self)
58+
59+
arrayPtr.pointee.reserveCapacity($1)
60+
61+
for type in buffer {
62+
arrayPtr.pointee.append(type)
63+
}
64+
}
65+
}
66+
67+
return result
68+
}
69+
70+
#endif

stdlib/public/Reflection/Sources/_Runtime/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,12 @@ add_swift_target_library(swift_Runtime ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_ST
6464
Utils/RelativePointer.swift
6565
Utils/TypeCache.swift
6666

67+
Caches.cpp
6768
ConformanceDescriptor.swift
6869
ExistentialContainer.swift
6970
Functions.swift
7071
HeapObject.swift
72+
ImageInspection.cpp
7173
WitnessTable.swift
7274

7375
C_COMPILE_FLAGS
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 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+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
// Only as SPI for Darwin platforms for right now...
13+
#if defined(__MACH__)
14+
15+
#include "ImageInspection.h"
16+
#include "swift/ABI/Metadata.h"
17+
#include "swift/Basic/Lazy.h"
18+
#include "swift/Runtime/Concurrent.h"
19+
#include "swift/Runtime/Config.h"
20+
#include <cstdint>
21+
22+
using namespace swift;
23+
24+
//===----------------------------------------------------------------------===//
25+
// Protocol Conformance Cache
26+
//===----------------------------------------------------------------------===//
27+
28+
namespace {
29+
struct ConformanceSection {
30+
const ProtocolConformanceRecord *Begin, *End;
31+
32+
ConformanceSection(const ProtocolConformanceRecord *begin,
33+
const ProtocolConformanceRecord *end)
34+
: Begin(begin), End(end) {}
35+
36+
ConformanceSection(const void *ptr, uintptr_t size) {
37+
auto bytes = reinterpret_cast<const char *>(ptr);
38+
Begin = reinterpret_cast<const ProtocolConformanceRecord *>(ptr);
39+
End = reinterpret_cast<const ProtocolConformanceRecord *>(bytes + size);
40+
}
41+
42+
const ProtocolConformanceRecord *begin() const {
43+
return Begin;
44+
}
45+
const ProtocolConformanceRecord *end() const {
46+
return End;
47+
}
48+
};
49+
}
50+
51+
struct ConformanceCache {
52+
// All accesses to Cache and LastSectionCount must be within CacheMutex's
53+
// lock scope.
54+
55+
std::unordered_map<
56+
/* Key */ const ProtocolDescriptor *,
57+
/* Value */ std::vector<const Metadata *>
58+
> Cache;
59+
Mutex CacheMutex;
60+
ConcurrentReadableArray<ConformanceSection> Sections;
61+
62+
size_t LastSectionCount = 0;
63+
64+
ConformanceCache() {
65+
initializeConformanceLookup();
66+
}
67+
};
68+
69+
static Lazy<ConformanceCache> Conformances;
70+
71+
void swift::registerConformances(const void *baseAddress,
72+
const void *conformances,
73+
uintptr_t conformancesSize) {
74+
// Conformance cache should always be sufficiently initialized by this point.
75+
auto &C = Conformances.unsafeGetAlreadyInitialized();
76+
77+
C.Sections.push_back(ConformanceSection{conformances, conformancesSize});
78+
}
79+
80+
static const Metadata *_getCanonicalTypeMetadata(
81+
const ProtocolConformanceDescriptor *conformance) {
82+
switch (conformance->getTypeKind()) {
83+
case TypeReferenceKind::DirectTypeDescriptor:
84+
case TypeReferenceKind::IndirectTypeDescriptor: {
85+
if (auto anyType = conformance->getTypeDescriptor()) {
86+
if (auto type = dyn_cast<TypeContextDescriptor>(anyType)) {
87+
if (!type->isGeneric()) {
88+
if (auto accessFn = type->getAccessFunction()) {
89+
return accessFn(MetadataState::Abstract).Value;
90+
}
91+
}
92+
}
93+
}
94+
95+
return nullptr;
96+
}
97+
98+
case TypeReferenceKind::DirectObjCClassName:
99+
case TypeReferenceKind::IndirectObjCClass:
100+
return nullptr;
101+
}
102+
}
103+
104+
using ConformanceCacheCallback = void (*)(const Metadata **,
105+
size_t, void *);
106+
107+
SWIFT_RUNTIME_STDLIB_SPI SWIFT_CC(swift)
108+
void _swift_reflection_withConformanceCache(const ProtocolDescriptor *proto,
109+
void *context,
110+
ConformanceCacheCallback callback) {
111+
auto &C = Conformances.get();
112+
113+
auto snapshot = C.Sections.snapshot();
114+
115+
Mutex::ScopedLock lock(C.CacheMutex);
116+
117+
if (C.LastSectionCount > 0 && snapshot.count() <= C.LastSectionCount) {
118+
auto entry = C.Cache.find(proto);
119+
120+
if (entry != C.Cache.end()) {
121+
callback(entry->second.data(), entry->second.size(), context);
122+
return;
123+
}
124+
}
125+
126+
std::vector<const Metadata *> types = {};
127+
128+
for (auto &section : snapshot) {
129+
for (auto &record : section) {
130+
auto conformance = record.get();
131+
132+
if (conformance->getProtocol() != proto) {
133+
continue;
134+
}
135+
136+
if (conformance->hasConditionalRequirements()) {
137+
continue;
138+
}
139+
140+
if (auto type = _getCanonicalTypeMetadata(conformance)) {
141+
types.push_back(type);
142+
}
143+
}
144+
}
145+
146+
callback(types.data(), types.size(), context);
147+
148+
C.Cache[proto] = types;
149+
C.LastSectionCount = snapshot.count();
150+
}
151+
152+
#endif
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 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+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#include "ImageInspection.h"
13+
14+
//===----------------------------------------------------------------------===//
15+
// Mach-O Image Inspection
16+
//===----------------------------------------------------------------------===//
17+
18+
#if defined(__MACH__)
19+
20+
#include "swift/Runtime/Config.h"
21+
#include "swift/Threading/Once.h"
22+
#include <mach-o/dyld.h>
23+
#include <mach-o/getsect.h>
24+
#include <objc/runtime.h>
25+
#include <stdint.h>
26+
27+
#if __POINTER_WIDTH__ == 64
28+
using mach_header_platform = mach_header_64;
29+
#else
30+
using mach_header_platform = mach_header;
31+
#endif
32+
33+
template <const char *SEGMENT_NAME, const char *SECTION_NAME,
34+
void CONSUME_BLOCK(const void *baseAddress,
35+
const void *start, uintptr_t size)>
36+
void addImageCallback(const mach_header *mh) {
37+
#if __POINTER_WIDTH__ == 64
38+
assert(mh->magic == MH_MAGIC_64 && "loaded non-64-bit image?!");
39+
#endif
40+
41+
// Look for a __swift5_proto section.
42+
unsigned long size;
43+
const uint8_t *section =
44+
getsectiondata(reinterpret_cast<const mach_header_platform *>(mh),
45+
SEGMENT_NAME, SECTION_NAME,
46+
&size);
47+
48+
if (!section)
49+
return;
50+
51+
CONSUME_BLOCK(mh, section, size);
52+
}
53+
template <const char *SEGMENT_NAME, const char *SECTION_NAME,
54+
void CONSUME_BLOCK(const void *baseAddress,
55+
const void *start, uintptr_t size)>
56+
void addImageCallback(const mach_header *mh, intptr_t vmaddr_slide) {
57+
addImageCallback<SEGMENT_NAME, SECTION_NAME, CONSUME_BLOCK>(mh);
58+
}
59+
60+
#if OBJC_ADDLOADIMAGEFUNC_DEFINED && SWIFT_OBJC_INTEROP
61+
#define REGISTER_FUNC(...) \
62+
if (__builtin_available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)) { \
63+
objc_addLoadImageFunc(__VA_ARGS__); \
64+
} else { \
65+
_dyld_register_func_for_add_image(__VA_ARGS__); \
66+
}
67+
#else
68+
#define REGISTER_FUNC(...) _dyld_register_func_for_add_image(__VA_ARGS__)
69+
#endif
70+
71+
// WARNING: the callbacks are called from unsafe contexts (with the dyld and
72+
// ObjC runtime locks held) and must be very careful in what they do. Locking
73+
// must be arranged to avoid deadlocks (other code must never call out to dyld
74+
// or ObjC holding a lock that gets taken in one of these callbacks) and the
75+
// new/delete operators must not be called, in case a program supplies an
76+
// overload which does not cooperate with these requirements.
77+
78+
void swift::initializeConformanceLookup() {
79+
REGISTER_FUNC(addImageCallback<TextSegment, ProtocolConformancesSection,
80+
registerConformances>);
81+
}
82+
83+
#endif
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 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+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#ifndef SWIFT_REFLECTION_IMAGE_INSPECTION_H
13+
#define SWIFT_REFLECTION_IMAGE_INSPECTION_H
14+
15+
#include "swift/Runtime/Config.h"
16+
#include <cstdint>
17+
18+
#if defined(__MACH__)
19+
20+
/// The Mach-O section name for the section containing protocol descriptor
21+
/// references. This lives within SEG_TEXT.
22+
#define MachOProtocolsSection "__swift5_protos"
23+
/// The Mach-O section name for the section containing protocol conformances.
24+
/// This lives within SEG_TEXT.
25+
#define MachOProtocolConformancesSection "__swift5_proto"
26+
/// The Mach-O section name for the section containing type references.
27+
/// This lives within SEG_TEXT.
28+
#define MachOTypeMetadataRecordSection "__swift5_types"
29+
/// The Mach-O section name for the section containing dynamic replacements.
30+
/// This lives within SEG_TEXT.
31+
#define MachODynamicReplacementSection "__swift5_replace"
32+
#define MachODynamicReplacementSomeSection "__swift5_replac2"
33+
/// The Mach-O section name for the section containing accessible functions.
34+
/// This lives within SEG_TEXT.
35+
#define MachOAccessibleFunctionsSection "__swift5_acfuncs"
36+
37+
#define MachOTextSegment "__TEXT"
38+
39+
constexpr const char ProtocolsSection[] = MachOProtocolsSection;
40+
constexpr const char ProtocolConformancesSection[] =
41+
MachOProtocolConformancesSection;
42+
constexpr const char TypeMetadataRecordSection[] =
43+
MachOTypeMetadataRecordSection;
44+
constexpr const char DynamicReplacementSection[] =
45+
MachODynamicReplacementSection;
46+
constexpr const char DynamicReplacementSomeSection[] =
47+
MachODynamicReplacementSomeSection;
48+
constexpr const char AccessibleFunctionsSection[] =
49+
MachOAccessibleFunctionsSection;
50+
constexpr const char TextSegment[] = MachOTextSegment;
51+
52+
#endif
53+
54+
namespace swift {
55+
void initializeConformanceLookup();
56+
void registerConformances(const void *baseAddress, const void *conformances,
57+
uintptr_t conformancesSize);
58+
} // namespace swift
59+
60+
#endif

0 commit comments

Comments
 (0)