Skip to content

Don't allocate a buffer on the heap when enumerating type metadata. #918

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions Sources/Testing/Test+Discovery+Legacy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,8 @@ let exitTestContainerTypeNameMagic = "__🟠$exit_test_body__"
/// - Returns: A sequence of Swift types whose names contain `nameSubstring`.
func types(withNamesContaining nameSubstring: String) -> some Sequence<Any.Type> {
SectionBounds.all(.typeMetadata).lazy.flatMap { sb in
var count = 0
let start = swt_copyTypes(in: sb.buffer.baseAddress!, sb.buffer.count, withNamesContaining: nameSubstring, count: &count)
defer {
free(start)
}
return UnsafeBufferPointer(start: start, count: count)
.withMemoryRebound(to: Any.Type.self) { Array($0) }
stride(from: sb.buffer.baseAddress!, to: sb.buffer.baseAddress! + sb.buffer.count, by: SWTTypeMetadataRecordByteCount).lazy
.compactMap { swt_getType(fromTypeMetadataRecord: $0, ifNameContains: nameSubstring) }
.map { unsafeBitCast($0, to: Any.Type.self) }
}
}
64 changes: 24 additions & 40 deletions Sources/_TestingInternals/Discovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@

#include "Discovery.h"

#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <vector>

#if defined(SWT_NO_DYNAMIC_LINKING)
#pragma mark - Statically-linked section bounds
Expand Down Expand Up @@ -189,46 +187,32 @@ struct SWTTypeMetadataRecord {

#pragma mark - Legacy test discovery

void **swt_copyTypesWithNamesContaining(const void *sectionBegin, size_t sectionSize, const char *nameSubstring, size_t *outCount) {
void **result = nullptr;
size_t resultCount = 0;

auto records = reinterpret_cast<const SWTTypeMetadataRecord *>(sectionBegin);
size_t recordCount = sectionSize / sizeof(SWTTypeMetadataRecord);
for (size_t i = 0; i < recordCount; i++) {
auto contextDescriptor = records[i].getContextDescriptor();
if (!contextDescriptor) {
// This type metadata record is invalid (or we don't understand how to
// get its context descriptor), so skip it.
continue;
} else if (contextDescriptor->isGeneric()) {
// Generic types cannot be fully instantiated without generic
// parameters, which is not something we can know abstractly.
continue;
}
const size_t SWTTypeMetadataRecordByteCount = sizeof(SWTTypeMetadataRecord);

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

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

if (void *typeMetadata = contextDescriptor->getMetadata()) {
return typeMetadata;
}

*outCount = resultCount;
return result;
return nullptr;
}
29 changes: 13 additions & 16 deletions Sources/_TestingInternals/include/Discovery.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,25 +84,22 @@ SWT_EXTERN const void *_Nonnull const SWTTypeMetadataSectionBounds[2];

#pragma mark - Legacy test discovery

/// Copy all types known to Swift found in the given type metadata section with
/// a name containing the given substring.
/// The size, in bytes, of a Swift type metadata record.
SWT_EXTERN const size_t SWTTypeMetadataRecordByteCount;

/// Get the type represented by the type metadata record at the given address if
/// its name contains the given string.
///
/// - Parameters:
/// - sectionBegin: The address of the first byte of the Swift type metadata
/// section.
/// - sectionSize: The size, in bytes, of the Swift type metadata section.
/// - nameSubstring: A string which the names of matching classes all contain.
/// - outCount: On return, the number of type metadata pointers returned.
/// - recordAddress: The address of the Swift type metadata record.
/// - nameSubstring: A string which the names of matching types contain.
///
/// - Returns: A pointer to an array of type metadata pointers, or `nil` if no
/// matching types were found. The caller is responsible for freeing this
/// memory with `free()` when done.
SWT_EXTERN void *_Nonnull *_Nullable swt_copyTypesWithNamesContaining(
const void *sectionBegin,
size_t sectionSize,
const char *nameSubstring,
size_t *outCount
) SWT_SWIFT_NAME(swt_copyTypes(in:_:withNamesContaining:count:));
/// - Returns: A Swift metatype (as `const void *`) or `nullptr` if it wasn't a
/// usable type metadata record or its name did not contain `nameSubstring`.
SWT_EXTERN const void *_Nullable swt_getTypeFromTypeMetadataRecord(
const void *recordAddress,
const char *nameSubstring
) SWT_SWIFT_NAME(swt_getType(fromTypeMetadataRecord:ifNameContains:));

SWT_ASSUME_NONNULL_END

Expand Down