Skip to content

Commit eed68e7

Browse files
authored
Only call getsectiondata() once per image. (#734)
This PR improves the performance of test discovery by only calling `getsectiondata()` once per loaded image instead of twice. It also refactors the C++ platform-specific enumerator function to take a new structure, `SWTSectionBounds`, instead of multiple arguments. This lets us use a C++ range-for-loop (`for (const auto& record : sectionBounds)`) instead of a raw C for-loop, reduces register traffic on Windows, and is all around a bit more object-oriented (which I hope is an improvement, yeah?) A non-scientific comparison at my desk shows that, before the change, **cold** test discovery on macOS took an average of 0.0376 seconds. After the change, the average was 0.0217 seconds for an approximate 1.73x speedup. ### 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 05890bc commit eed68e7

File tree

1 file changed

+108
-90
lines changed

1 file changed

+108
-90
lines changed

Sources/_TestingInternals/Discovery.cpp

Lines changed: 108 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@
2828
#include <os/lock.h>
2929
#endif
3030

31+
/// Enumerate over all Swift type metadata sections in the current process.
32+
///
33+
/// - Parameters:
34+
/// - body: A function to call once for every section in the current process.
35+
/// A pointer to the first type metadata record and the number of records
36+
/// are passed to this function.
37+
template <typename SectionEnumerator>
38+
static void enumerateTypeMetadataSections(const SectionEnumerator& body);
39+
3140
/// A type that acts as a C++ [Allocator](https://en.cppreference.com/w/cpp/named_req/Allocator)
3241
/// without using global `operator new` or `operator delete`.
3342
///
@@ -49,18 +58,35 @@ struct SWTHeapAllocator {
4958
}
5059
};
5160

52-
/// A `std::vector` that uses `SWTHeapAllocator`.
61+
/// A structure describing the bounds of a Swift metadata section.
62+
///
63+
/// The template argument `T` is the element type of the metadata section.
64+
/// Instances of this type can be used with a range-based `for`-loop to iterate
65+
/// the contents of the section.
5366
template <typename T>
54-
using SWTVector = std::vector<T, SWTHeapAllocator<T>>;
67+
struct SWTSectionBounds {
68+
/// The base address of the image containing the section, if known.
69+
const void *imageAddress;
5570

56-
/// Enumerate over all Swift type metadata sections in the current process.
57-
///
58-
/// - Parameters:
59-
/// - body: A function to call once for every section in the current process.
60-
/// A pointer to the first type metadata record and the number of records
61-
/// are passed to this function.
62-
template <typename SectionEnumerator>
63-
static void enumerateTypeMetadataSections(const SectionEnumerator& body);
71+
/// The base address of the section.
72+
const void *start;
73+
74+
/// The size of the section in bytes.
75+
size_t size;
76+
77+
const struct SWTTypeMetadataRecord *begin(void) const {
78+
return reinterpret_cast<const T *>(start);
79+
}
80+
81+
const struct SWTTypeMetadataRecord *end(void) const {
82+
return reinterpret_cast<const T *>(reinterpret_cast<uintptr_t>(start) + size);
83+
}
84+
};
85+
86+
/// A type that acts as a C++ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
87+
/// and which contains a sequence of instances of `SWTSectionBounds<T>`.
88+
template <typename T>
89+
using SWTSectionBoundsList = std::vector<SWTSectionBounds<T>, SWTHeapAllocator<SWTSectionBounds<T>>>;
6490

6591
#pragma mark - Swift ABI
6692

@@ -217,22 +243,14 @@ struct SWTTypeMetadataRecord {
217243
#if !defined(SWT_NO_DYNAMIC_LINKING)
218244
#pragma mark - Apple implementation
219245

220-
/// A type that acts as a C++ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
221-
/// and which contains a sequence of Mach headers.
222-
#if __LP64__
223-
using SWTMachHeaderList = SWTVector<const mach_header_64 *>;
224-
#else
225-
using SWTMachHeaderList = SWTVector<const mach_header *>;
226-
#endif
227-
228-
/// Get a copy of the currently-loaded Mach headers list.
246+
/// Get a copy of the currently-loaded type metadata sections list.
229247
///
230-
/// - Returns: A list of Mach headers loaded into the current process. The order
231-
/// of the resulting list is unspecified.
248+
/// - Returns: A list of type metadata sections in images loaded into the
249+
/// current process. The order of the resulting list is unspecified.
232250
///
233-
/// On non-Apple platforms, the `swift_enumerateAllMetadataSections()` function
251+
/// On ELF-based platforms, the `swift_enumerateAllMetadataSections()` function
234252
/// exported by the runtime serves the same purpose as this function.
235-
static SWTMachHeaderList getMachHeaders(void) {
253+
static SWTSectionBoundsList<SWTTypeMetadataRecord> getSectionBounds(void) {
236254
/// This list is necessarily mutated while a global libobjc- or dyld-owned
237255
/// lock is held. Hence, code using this list must avoid potentially
238256
/// re-entering either library (otherwise it could potentially deadlock.)
@@ -242,17 +260,21 @@ static SWTMachHeaderList getMachHeaders(void) {
242260
/// testing library is not tasked with the same performance constraints as
243261
/// Swift's runtime library, we just use a `std::vector` guarded by an unfair
244262
/// lock.
245-
static constinit SWTMachHeaderList *machHeaders = nullptr;
263+
static constinit SWTSectionBoundsList<SWTTypeMetadataRecord> *sectionBounds = nullptr;
246264
static constinit os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
247265

248266
static constinit dispatch_once_t once = 0;
249267
dispatch_once_f(&once, nullptr, [] (void *) {
250-
machHeaders = reinterpret_cast<SWTMachHeaderList *>(std::malloc(sizeof(SWTMachHeaderList)));
251-
::new (machHeaders) SWTMachHeaderList();
252-
machHeaders->reserve(_dyld_image_count());
268+
sectionBounds = reinterpret_cast<SWTSectionBoundsList<SWTTypeMetadataRecord> *>(std::malloc(sizeof(SWTSectionBoundsList<SWTTypeMetadataRecord>)));
269+
::new (sectionBounds) SWTSectionBoundsList<SWTTypeMetadataRecord>();
270+
sectionBounds->reserve(_dyld_image_count());
253271

254272
objc_addLoadImageFunc([] (const mach_header *mh) {
255-
auto mhn = reinterpret_cast<SWTMachHeaderList::value_type>(mh);
273+
#if __LP64__
274+
auto mhn = reinterpret_cast<const mach_header_64 *>(mh);
275+
#else
276+
auto mhn = mh;
277+
#endif
256278

257279
// Ignore this Mach header if it is in the shared cache. On platforms that
258280
// support it (Darwin), most system images are contained in this range.
@@ -262,41 +284,36 @@ static SWTMachHeaderList getMachHeaders(void) {
262284
return;
263285
}
264286

265-
// Only store the mach header address if the image contains Swift data.
266-
// Swift does not support unloading images, but images that do not contain
267-
// Swift code may be unloaded at runtime and later crash
268-
// the testing library when it calls enumerateTypeMetadataSections().
287+
// If this image contains the Swift section we need, acquire the lock and
288+
// store the section's bounds.
269289
unsigned long size = 0;
270-
if (getsectiondata(mhn, SEG_TEXT, "__swift5_types", &size)) {
290+
auto start = getsectiondata(mhn, SEG_TEXT, "__swift5_types", &size);
291+
if (start && size > 0) {
271292
os_unfair_lock_lock(&lock); {
272-
machHeaders->push_back(mhn);
293+
sectionBounds->emplace_back(mhn, start, size);
273294
} os_unfair_lock_unlock(&lock);
274295
}
275296
});
276297
});
277298

278299
// After the first call sets up the loader hook, all calls take the lock and
279300
// make a copy of whatever has been loaded so far.
280-
SWTMachHeaderList result;
301+
SWTSectionBoundsList<SWTTypeMetadataRecord> result;
281302
result.reserve(_dyld_image_count());
282303
os_unfair_lock_lock(&lock); {
283-
result = *machHeaders;
304+
result = *sectionBounds;
284305
} os_unfair_lock_unlock(&lock);
306+
result.shrink_to_fit();
285307
return result;
286308
}
287309

288310
template <typename SectionEnumerator>
289311
static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
290-
SWTMachHeaderList machHeaders = getMachHeaders();
291-
for (auto mh : machHeaders) {
292-
unsigned long size = 0;
293-
const void *section = getsectiondata(mh, SEG_TEXT, "__swift5_types", &size);
294-
if (section && size > 0) {
295-
bool stop = false;
296-
body(mh, section, size, &stop);
297-
if (stop) {
298-
break;
299-
}
312+
bool stop = false;
313+
for (const auto& sb : getSectionBounds()) {
314+
body(sb, &stop);
315+
if (stop) {
316+
break;
300317
}
301318
}
302319
}
@@ -313,9 +330,13 @@ extern "C" const char sectionEnd __asm("section$end$__TEXT$__swift5_types");
313330

314331
template <typename SectionEnumerator>
315332
static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
316-
auto size = std::distance(&sectionBegin, &sectionEnd);
333+
SWTSectionBounds<SWTTypeMetadataRecord> sb = {
334+
nullptr,
335+
&sectionBegin,
336+
static_cast<size_t>(std::distance(&sectionBegin, &sectionEnd))
337+
};
317338
bool stop = false;
318-
body(nullptr, &sectionBegin, size, &stop);
339+
body(sb, &stop);
319340
}
320341
#endif
321342

@@ -325,37 +346,31 @@ static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
325346
/// Find the section with the given name in the given module.
326347
///
327348
/// - Parameters:
328-
/// - module: The module to inspect.
349+
/// - hModule: The module to inspect.
329350
/// - sectionName: The name of the section to look for. Long section names are
330351
/// not supported.
331352
///
332353
/// - Returns: A pointer to the start of the given section along with its size
333354
/// in bytes, or `std::nullopt` if the section could not be found. If the
334355
/// section was emitted by the Swift toolchain, be aware it will have leading
335356
/// and trailing bytes (`sizeof(uintptr_t)` each.)
336-
static std::optional<std::pair<const void *, size_t>> findSection(HMODULE module, const char *sectionName) {
337-
if (!module) {
357+
static std::optional<SWTSectionBounds<SWTTypeMetadataRecord>> findSection(HMODULE hModule, const char *sectionName) {
358+
if (!hModule) {
338359
return std::nullopt;
339360
}
340361

341362
// Get the DOS header (to which the HMODULE directly points, conveniently!)
342363
// and check it's sufficiently valid for us to walk.
343-
auto dosHeader = reinterpret_cast<const PIMAGE_DOS_HEADER>(module);
364+
auto dosHeader = reinterpret_cast<const PIMAGE_DOS_HEADER>(hModule);
344365
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE || dosHeader->e_lfanew <= 0) {
345366
return std::nullopt;
346367
}
347368

348-
// Check the NT header as well as the optional header.
369+
// Check the NT header. Since we don't use the optional header, skip it.
349370
auto ntHeader = reinterpret_cast<const PIMAGE_NT_HEADERS>(reinterpret_cast<uintptr_t>(dosHeader) + dosHeader->e_lfanew);
350371
if (!ntHeader || ntHeader->Signature != IMAGE_NT_SIGNATURE) {
351372
return std::nullopt;
352373
}
353-
if (ntHeader->FileHeader.SizeOfOptionalHeader < offsetof(decltype(ntHeader->OptionalHeader), Magic) + sizeof(decltype(ntHeader->OptionalHeader)::Magic)) {
354-
return std::nullopt;
355-
}
356-
if (ntHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) {
357-
return std::nullopt;
358-
}
359374

360375
auto sectionCount = ntHeader->FileHeader.NumberOfSections;
361376
auto section = IMAGE_FIRST_SECTION(ntHeader);
@@ -370,7 +385,7 @@ static std::optional<std::pair<const void *, size_t>> findSection(HMODULE module
370385
// FIXME: Handle longer names ("/%u") from string table
371386
auto thisSectionName = reinterpret_cast<const char *>(section->Name);
372387
if (0 == std::strncmp(sectionName, thisSectionName, IMAGE_SIZEOF_SHORT_NAME)) {
373-
return std::make_pair(start, size);
388+
return SWTSectionBounds<SWTTypeMetadataRecord> { hModule, start, size };
374389
}
375390
}
376391
}
@@ -392,27 +407,27 @@ static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
392407
// Look in all the loaded modules for Swift type metadata sections and store
393408
// them in a side table.
394409
//
395-
// This two-step process is less algorithmically efficient than a single loop,
396-
// but it is safer: the callback will eventually invoke developer code that
410+
// This two-step process is more complicated to read than a single loop would
411+
// be but it is safer: the callback will eventually invoke developer code that
397412
// could theoretically unload a module from the list we're enumerating. (Swift
398413
// modules do not support unloading, so we'll just not worry about them.)
399-
using SWTSectionList = SWTVector<std::tuple<HMODULE, const void *, size_t>>;
400-
SWTSectionList sectionList;
414+
SWTSectionBoundsList<SWTTypeMetadataRecord> sectionBounds;
415+
sectionBounds.reserve(hModuleCount);
401416
for (size_t i = 0; i < hModuleCount; i++) {
402-
if (auto section = findSection(hModules[i], ".sw5tymd")) {
403-
sectionList.emplace_back(hModules[i], section->first, section->second);
417+
if (auto sb = findSection(hModules[i], ".sw5tymd")) {
418+
sectionBounds.push_back(*sb);
404419
}
405420
}
406421

407-
// Pass the loaded module and section info back to the body callback.
408-
// Note we ignore the leading and trailing uintptr_t values: they're both
422+
// Pass each discovered section back to the body callback.
423+
//
424+
// NOTE: we ignore the leading and trailing uintptr_t values: they're both
409425
// always set to zero so we'll skip them in the callback, and in the future
410426
// the toolchain might not emit them at all in which case we don't want to
411427
// skip over real section data.
412428
bool stop = false;
413-
for (const auto& section : sectionList) {
414-
// TODO: Use C++17 unstructured binding here.
415-
body(get<0>(section), get<1>(section), get<2>(section), &stop);
429+
for (const auto& sb : sectionBounds) {
430+
body(sb, &stop);
416431
if (stop) {
417432
break;
418433
}
@@ -427,15 +442,19 @@ extern "C" const char __stop_swift5_type_metadata;
427442

428443
template <typename SectionEnumerator>
429444
static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
430-
const auto& sectionBegin = __start_swift5_type_metadata;
431-
const auto& sectionEnd = __stop_swift5_type_metadata;
432-
433445
// WASI only has a single image (so far) and it is statically linked, so all
434446
// Swift metadata ends up in the same section bounded by the named symbols
435447
// above. So we can just yield the section betwixt them.
436-
auto size = std::distance(&sectionBegin, &sectionEnd);
448+
const auto& sectionBegin = __start_swift5_type_metadata;
449+
const auto& sectionEnd = __stop_swift5_type_metadata;
450+
451+
SWTSectionBounds<SWTTypeMetadataRecord> sb = {
452+
nullptr,
453+
&sectionBegin,
454+
static_cast<size_t>(std::distance(&sectionBegin, &sectionEnd))
455+
};
437456
bool stop = false;
438-
body(nullptr, &sectionBegin, size, &stop);
457+
body(sb, &stop);
439458
}
440459

441460
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__ANDROID__)
@@ -481,16 +500,20 @@ SWT_IMPORT_FROM_STDLIB void swift_enumerateAllMetadataSections(
481500
template <typename SectionEnumerator>
482501
static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
483502
swift_enumerateAllMetadataSections([] (const MetadataSections *sections, void *context) {
503+
bool stop = false;
504+
484505
const auto& body = *reinterpret_cast<const SectionEnumerator *>(context);
485506
MetadataSectionRange section = sections->swift5_type_metadata;
486507
if (section.start && section.length > 0) {
487-
bool stop = false;
488-
body(sections->baseAddress.load(), reinterpret_cast<const void *>(section.start), section.length, &stop);
489-
if (stop) {
490-
return false;
491-
}
508+
SWTSectionBounds<SWTTypeMetadataRecord> sb = {
509+
sections->baseAddress.load(),
510+
reinterpret_cast<const void *>(section.start),
511+
section.length
512+
};
513+
body(sb, &stop);
492514
}
493-
return true;
515+
516+
return !stop;
494517
}, const_cast<SectionEnumerator *>(&body));
495518
}
496519
#else
@@ -502,13 +525,8 @@ static void enumerateTypeMetadataSections(const SectionEnumerator& body) {}
502525
#pragma mark -
503526

504527
void swt_enumerateTypesWithNamesContaining(const char *nameSubstring, void *context, SWTTypeEnumerator body) {
505-
enumerateTypeMetadataSections([=] (const void *imageAddress, const void *section, size_t size, bool *stop) {
506-
auto records = reinterpret_cast<const SWTTypeMetadataRecord *>(section);
507-
size_t recordCount = size / sizeof(SWTTypeMetadataRecord);
508-
509-
for (size_t i = 0; i < recordCount && !*stop; i++) {
510-
const auto& record = records[i];
511-
528+
enumerateTypeMetadataSections([=] (const SWTSectionBounds<SWTTypeMetadataRecord>& sectionBounds, bool *stop) {
529+
for (const auto& record : sectionBounds) {
512530
auto contextDescriptor = record.getContextDescriptor();
513531
if (!contextDescriptor) {
514532
// This type metadata record is invalid (or we don't understand how to
@@ -529,7 +547,7 @@ void swt_enumerateTypesWithNamesContaining(const char *nameSubstring, void *cont
529547
}
530548

531549
if (void *typeMetadata = contextDescriptor->getMetadata()) {
532-
body(imageAddress, typeMetadata, stop, context);
550+
body(sectionBounds.imageAddress, typeMetadata, stop, context);
533551
}
534552
}
535553
});

0 commit comments

Comments
 (0)