Skip to content

Commit a5921e0

Browse files
authored
Don't allocate a buffer on the heap when enumerating type metadata. (#918)
This PR eliminates the need for us to allocate a large buffer to contain type metadata pointers we discover in type metadata sections using the legacy test discovery mechanism. Why do I keep opening these PRs? Because for each line of C++ I remove from the Swift toolchain, I get a cookie! 🍪 ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 6d20200 commit a5921e0

File tree

3 files changed

+40
-63
lines changed

3 files changed

+40
-63
lines changed

Sources/Testing/Test+Discovery+Legacy.swift

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,8 @@ let exitTestContainerTypeNameMagic = "__🟠$exit_test_body__"
5555
/// - Returns: A sequence of Swift types whose names contain `nameSubstring`.
5656
func types(withNamesContaining nameSubstring: String) -> some Sequence<Any.Type> {
5757
SectionBounds.all(.typeMetadata).lazy.flatMap { sb in
58-
var count = 0
59-
let start = swt_copyTypes(in: sb.buffer.baseAddress!, sb.buffer.count, withNamesContaining: nameSubstring, count: &count)
60-
defer {
61-
free(start)
62-
}
63-
return UnsafeBufferPointer(start: start, count: count)
64-
.withMemoryRebound(to: Any.Type.self) { Array($0) }
58+
stride(from: sb.buffer.baseAddress!, to: sb.buffer.baseAddress! + sb.buffer.count, by: SWTTypeMetadataRecordByteCount).lazy
59+
.compactMap { swt_getType(fromTypeMetadataRecord: $0, ifNameContains: nameSubstring) }
60+
.map { unsafeBitCast($0, to: Any.Type.self) }
6561
}
6662
}

Sources/_TestingInternals/Discovery.cpp

Lines changed: 24 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,9 @@
1010

1111
#include "Discovery.h"
1212

13-
#include <cstdlib>
1413
#include <cstdint>
1514
#include <cstring>
1615
#include <type_traits>
17-
#include <vector>
1816

1917
#if defined(SWT_NO_DYNAMIC_LINKING)
2018
#pragma mark - Statically-linked section bounds
@@ -189,46 +187,32 @@ struct SWTTypeMetadataRecord {
189187

190188
#pragma mark - Legacy test discovery
191189

192-
void **swt_copyTypesWithNamesContaining(const void *sectionBegin, size_t sectionSize, const char *nameSubstring, size_t *outCount) {
193-
void **result = nullptr;
194-
size_t resultCount = 0;
195-
196-
auto records = reinterpret_cast<const SWTTypeMetadataRecord *>(sectionBegin);
197-
size_t recordCount = sectionSize / sizeof(SWTTypeMetadataRecord);
198-
for (size_t i = 0; i < recordCount; i++) {
199-
auto contextDescriptor = records[i].getContextDescriptor();
200-
if (!contextDescriptor) {
201-
// This type metadata record is invalid (or we don't understand how to
202-
// get its context descriptor), so skip it.
203-
continue;
204-
} else if (contextDescriptor->isGeneric()) {
205-
// Generic types cannot be fully instantiated without generic
206-
// parameters, which is not something we can know abstractly.
207-
continue;
208-
}
190+
const size_t SWTTypeMetadataRecordByteCount = sizeof(SWTTypeMetadataRecord);
209191

210-
// Check that the type's name passes. This will be more expensive than the
211-
// checks above, but should be cheaper than realizing the metadata.
212-
const char *typeName = contextDescriptor->getName();
213-
bool nameOK = typeName && nullptr != std::strstr(typeName, nameSubstring);
214-
if (!nameOK) {
215-
continue;
216-
}
192+
const void *swt_getTypeFromTypeMetadataRecord(const void *recordAddress, const char *nameSubstring) {
193+
auto record = reinterpret_cast<const SWTTypeMetadataRecord *>(recordAddress);
194+
auto contextDescriptor = record->getContextDescriptor();
195+
if (!contextDescriptor) {
196+
// This type metadata record is invalid (or we don't understand how to
197+
// get its context descriptor), so skip it.
198+
return nullptr;
199+
} else if (contextDescriptor->isGeneric()) {
200+
// Generic types cannot be fully instantiated without generic
201+
// parameters, which is not something we can know abstractly.
202+
return nullptr;
203+
}
217204

218-
if (void *typeMetadata = contextDescriptor->getMetadata()) {
219-
if (!result) {
220-
// This is the first matching type we've found. That presumably means
221-
// we'll find more, so allocate enough space for all remaining types in
222-
// the section. Is this necessarily space-efficient? No, but this
223-
// allocation is short-lived and is immediately copied and freed in the
224-
// Swift caller.
225-
result = reinterpret_cast<void **>(std::calloc(recordCount - i, sizeof(void *)));
226-
}
227-
result[resultCount] = typeMetadata;
228-
resultCount += 1;
229-
}
205+
// Check that the type's name passes. This will be more expensive than the
206+
// checks above, but should be cheaper than realizing the metadata.
207+
const char *typeName = contextDescriptor->getName();
208+
bool nameOK = typeName && nullptr != std::strstr(typeName, nameSubstring);
209+
if (!nameOK) {
210+
return nullptr;
211+
}
212+
213+
if (void *typeMetadata = contextDescriptor->getMetadata()) {
214+
return typeMetadata;
230215
}
231216

232-
*outCount = resultCount;
233-
return result;
217+
return nullptr;
234218
}

Sources/_TestingInternals/include/Discovery.h

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,25 +84,22 @@ SWT_EXTERN const void *_Nonnull const SWTTypeMetadataSectionBounds[2];
8484

8585
#pragma mark - Legacy test discovery
8686

87-
/// Copy all types known to Swift found in the given type metadata section with
88-
/// a name containing the given substring.
87+
/// The size, in bytes, of a Swift type metadata record.
88+
SWT_EXTERN const size_t SWTTypeMetadataRecordByteCount;
89+
90+
/// Get the type represented by the type metadata record at the given address if
91+
/// its name contains the given string.
8992
///
9093
/// - Parameters:
91-
/// - sectionBegin: The address of the first byte of the Swift type metadata
92-
/// section.
93-
/// - sectionSize: The size, in bytes, of the Swift type metadata section.
94-
/// - nameSubstring: A string which the names of matching classes all contain.
95-
/// - outCount: On return, the number of type metadata pointers returned.
94+
/// - recordAddress: The address of the Swift type metadata record.
95+
/// - nameSubstring: A string which the names of matching types contain.
9696
///
97-
/// - Returns: A pointer to an array of type metadata pointers, or `nil` if no
98-
/// matching types were found. The caller is responsible for freeing this
99-
/// memory with `free()` when done.
100-
SWT_EXTERN void *_Nonnull *_Nullable swt_copyTypesWithNamesContaining(
101-
const void *sectionBegin,
102-
size_t sectionSize,
103-
const char *nameSubstring,
104-
size_t *outCount
105-
) SWT_SWIFT_NAME(swt_copyTypes(in:_:withNamesContaining:count:));
97+
/// - Returns: A Swift metatype (as `const void *`) or `nullptr` if it wasn't a
98+
/// usable type metadata record or its name did not contain `nameSubstring`.
99+
SWT_EXTERN const void *_Nullable swt_getTypeFromTypeMetadataRecord(
100+
const void *recordAddress,
101+
const char *nameSubstring
102+
) SWT_SWIFT_NAME(swt_getType(fromTypeMetadataRecord:ifNameContains:));
106103

107104
SWT_ASSUME_NONNULL_END
108105

0 commit comments

Comments
 (0)