Skip to content

Commit 9264b4a

Browse files
committed
Implement an SPI to iterate types who conform to some protocol
Incorporate feedback
1 parent 2e8ae40 commit 9264b4a

File tree

8 files changed

+410
-1
lines changed

8 files changed

+410
-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: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
#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 = Array(buffer)
60+
}
61+
}
62+
63+
return result
64+
}
65+
66+
#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: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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+
#include <unordered_map>
22+
23+
using namespace swift;
24+
25+
//===----------------------------------------------------------------------===//
26+
// Protocol Conformance Cache
27+
//===----------------------------------------------------------------------===//
28+
29+
namespace {
30+
struct ConformanceSection {
31+
const ProtocolConformanceRecord *Begin, *End;
32+
33+
ConformanceSection(const ProtocolConformanceRecord *begin,
34+
const ProtocolConformanceRecord *end)
35+
: Begin(begin), End(end) {}
36+
37+
ConformanceSection(const void *ptr, uintptr_t size) {
38+
auto bytes = reinterpret_cast<const char *>(ptr);
39+
Begin = reinterpret_cast<const ProtocolConformanceRecord *>(ptr);
40+
End = reinterpret_cast<const ProtocolConformanceRecord *>(bytes + size);
41+
}
42+
43+
const ProtocolConformanceRecord *begin() const {
44+
return Begin;
45+
}
46+
const ProtocolConformanceRecord *end() const {
47+
return End;
48+
}
49+
};
50+
}
51+
52+
struct ConformanceCache {
53+
// All accesses to Cache and LastSectionCount must be within CacheMutex's
54+
// lock scope.
55+
56+
std::unordered_map<
57+
/* Key */ const ProtocolDescriptor *,
58+
/* Value */ std::vector<const Metadata *>
59+
> Cache;
60+
Mutex CacheMutex;
61+
ConcurrentReadableArray<ConformanceSection> Sections;
62+
63+
size_t LastSectionCount = 0;
64+
65+
ConformanceCache() {
66+
initializeConformanceLookup();
67+
}
68+
};
69+
70+
static Lazy<ConformanceCache> Conformances;
71+
72+
void swift::registerConformances(const void *baseAddress,
73+
const void *conformances,
74+
uintptr_t conformancesSize) {
75+
// Conformance cache should always be sufficiently initialized by this point.
76+
auto &C = Conformances.unsafeGetAlreadyInitialized();
77+
78+
C.Sections.push_back(ConformanceSection{conformances, conformancesSize});
79+
}
80+
81+
static const Metadata *_getCanonicalTypeMetadata(
82+
const ProtocolConformanceDescriptor *conformance) {
83+
switch (conformance->getTypeKind()) {
84+
case TypeReferenceKind::DirectTypeDescriptor:
85+
case TypeReferenceKind::IndirectTypeDescriptor: {
86+
if (auto anyType = conformance->getTypeDescriptor()) {
87+
if (auto type = dyn_cast<TypeContextDescriptor>(anyType)) {
88+
if (!type->isGeneric()) {
89+
if (auto accessFn = type->getAccessFunction()) {
90+
return accessFn(MetadataState::Abstract).Value;
91+
}
92+
}
93+
}
94+
}
95+
96+
return nullptr;
97+
}
98+
99+
case TypeReferenceKind::DirectObjCClassName:
100+
case TypeReferenceKind::IndirectObjCClass:
101+
return nullptr;
102+
}
103+
}
104+
105+
using ConformanceCacheCallback = void (*)(const Metadata **,
106+
size_t, void *);
107+
108+
SWIFT_RUNTIME_STDLIB_SPI SWIFT_CC(swift)
109+
void _swift_reflection_withConformanceCache(const ProtocolDescriptor *proto,
110+
void *context,
111+
ConformanceCacheCallback callback) {
112+
auto &C = Conformances.get();
113+
114+
auto snapshot = C.Sections.snapshot();
115+
116+
Mutex::ScopedLock lock(C.CacheMutex);
117+
118+
if (C.LastSectionCount > 0 && snapshot.count() <= C.LastSectionCount) {
119+
auto entry = C.Cache.find(proto);
120+
121+
if (entry != C.Cache.end()) {
122+
callback(entry->second.data(), entry->second.size(), context);
123+
return;
124+
}
125+
}
126+
127+
std::vector<const Metadata *> types = {};
128+
129+
for (auto &section : snapshot) {
130+
for (auto &record : section) {
131+
auto conformance = record.get();
132+
133+
if (conformance->getProtocol() != proto) {
134+
continue;
135+
}
136+
137+
if (conformance->hasConditionalRequirements()) {
138+
continue;
139+
}
140+
141+
if (auto type = _getCanonicalTypeMetadata(conformance)) {
142+
types.push_back(type);
143+
}
144+
}
145+
}
146+
147+
callback(types.data(), types.size(), context);
148+
149+
C.Cache[proto] = types;
150+
C.LastSectionCount = snapshot.count();
151+
}
152+
153+
#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)