Skip to content

Commit 8e967cf

Browse files
committed
Rewrite static section bounds discovery in Swift.
This PR rewrites the static section bounds discovery code in Swift (replacing the current C++ implementation.) We are making use of `@_silgen_name` here to name the section bounds symbols that are defined by the linker, so we'll want to get the core team's approval before merging.
1 parent 1e55aa7 commit 8e967cf

File tree

5 files changed

+85
-88
lines changed

5 files changed

+85
-88
lines changed

Documentation/Porting.md

+20-19
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ emits section information into the resource fork on Classic, you would use the
136136
to load that information:
137137

138138
```diff
139-
--- a/Sources/Testing/Discovery+Platform.swift
140-
+++ b/Sources/Testing/Discovery+Platform.swift
139+
--- a/Sources/_TestDiscovery/SectionBounds.swift
140+
+++ b/Sources/_TestDiscovery/SectionBounds.swift
141141

142142
// ...
143143
+#elseif os(Classic)
@@ -211,29 +211,30 @@ start with `"SWT_"`).
211211
If your platform does not support dynamic linking and loading, you will need to
212212
use static linkage instead. Define the `"SWT_NO_DYNAMIC_LINKING"` compiler
213213
conditional for your platform in both `Package.swift` and
214-
`CompilerSettings.cmake`, then define the symbols `testContentSectionBegin`,
215-
`testContentSectionEnd`, `typeMetadataSectionBegin`, and
216-
`typeMetadataSectionEnd` in `Discovery.cpp`.
214+
`CompilerSettings.cmake`, then define the symbols `_testContentSectionBegin`,
215+
`_testContentSectionEnd`, `_typeMetadataSectionBegin`, and
216+
`_typeMetadataSectionEnd` in `SectionBounds.swift`:
217217

218218
```diff
219-
diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp
219+
--- a/Sources/_TestDiscovery/SectionBounds.swift
220+
+++ b/Sources/_TestDiscovery/SectionBounds.swift
220221
// ...
221-
+#elif defined(macintosh)
222-
+extern "C" const char testContentSectionBegin __asm__("...");
223-
+extern "C" const char testContentSectionEnd __asm__("...");
224-
+#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
225-
+extern "C" const char typeMetadataSectionBegin __asm__("...");
226-
+extern "C" const char typeMetadataSectionEnd __asm__("...");
222+
+#elseif os(Classic)
223+
+@_silgen_name(raw: "...") private nonisolated(unsafe) var _testContentSectionBegin: CChar
224+
+@_silgen_name(raw: "...") private nonisolated(unsafe) var _testContentSectionEnd: CChar
225+
+#if !SWT_NO_LEGACY_TEST_DISCOVERY
226+
+@_silgen_name(raw: "...") private nonisolated(unsafe) var _typeMetadataSectionBegin: CChar
227+
+@_silgen_name(raw: "...") private nonisolated(unsafe) var _typeMetadataSectionEnd: CChar
227228
+#endif
228229
#else
229-
#warning Platform-specific implementation missing: Runtime test discovery unavailable (static)
230-
static const char testContentSectionBegin = 0;
231-
static const char& testContentSectionEnd = testContentSectionBegin;
232-
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
233-
static const char typeMetadataSectionBegin = 0;
234-
static const char& typeMetadataSectionEnd = typeMetadataSectionBegin;
235-
#endif
230+
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)")
231+
private nonisolated(unsafe) var _testContentSectionBegin: Void
232+
private nonisolated(unsafe) var _testContentSectionEnd: Void
233+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
234+
private nonisolated(unsafe) var _typeMetadataSectionBegin: Void
235+
private nonisolated(unsafe) var _typeMetadataSectionEnd: Void
236236
#endif
237+
// ...
237238
```
238239

239240
These symbols must have unique addresses corresponding to the first byte of the

Sources/_TestDiscovery/SectionBounds.swift

+63-9
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ struct SectionBounds: Sendable {
2323

2424
/// An enumeration describing the different sections discoverable by the
2525
/// testing library.
26-
enum Kind: Int, Equatable, Hashable, CaseIterable {
26+
enum Kind: Equatable, Hashable, CaseIterable {
2727
/// The test content metadata section.
2828
case testContent
2929

@@ -146,7 +146,7 @@ private let _startCollectingSectionBounds: Void = {
146146
///
147147
/// - Returns: An array of structures describing the bounds of all known test
148148
/// content sections in the current process.
149-
private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
149+
private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence<SectionBounds> {
150150
_startCollectingSectionBounds
151151
return _sectionBounds.withUnsafeMutablePointers { sectionBounds, lock in
152152
pthread_mutex_lock(lock)
@@ -169,7 +169,7 @@ private import SwiftShims // For MetadataSections
169169
///
170170
/// - Returns: An array of structures describing the bounds of all known test
171171
/// content sections in the current process.
172-
private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
172+
private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence<SectionBounds> {
173173
struct Context {
174174
var kind: SectionBounds.Kind
175175
var result = [SectionBounds]()
@@ -297,14 +297,63 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence<Section
297297
/// - kind: Ignored.
298298
///
299299
/// - Returns: The empty array.
300-
private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
300+
private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence<SectionBounds> {
301301
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)")
302-
return []
302+
return EmptyCollection()
303303
}
304304
#endif
305305
#else
306306
// MARK: - Statically-linked implementation
307307

308+
/// The bounds of the statically-linked sections of interest to the testing
309+
/// library.
310+
///
311+
/// We use `@_silgen_name(raw:)` to reference these linker-defined symbols.
312+
/// Then, to get their addresses, we must use `&` (`withPointer(to:)` is not
313+
/// guaranteed to avoid a copy in this context.) Hence, they must also be
314+
/// declared `var` rather than `let`, but they should be treated as read-only.
315+
#if SWT_TARGET_OS_APPLE
316+
@_silgen_name(raw: "section$start$__DATA_CONST$__swift5_tests") private nonisolated(unsafe) var _testContentSectionBegin: CChar
317+
@_silgen_name(raw: "section$end$__DATA_CONST$__swift5_tests") private nonisolated(unsafe) var _testContentSectionEnd: CChar
318+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
319+
@_silgen_name(raw: "section$start$__TEXT$__swift5_types") private nonisolated(unsafe) var _typeMetadataSectionBegin: CChar
320+
@_silgen_name(raw: "section$end$__TEXT$__swift5_types") private nonisolated(unsafe) var _typeMetadataSectionEnd: CChar
321+
#endif
322+
#elseif os(WASI)
323+
@_silgen_name(raw: "__start_swift5_tests") private nonisolated(unsafe) var _testContentSectionBegin: CChar
324+
@_silgen_name(raw: "__stop_swift5_tests") private nonisolated(unsafe) var _testContentSectionEnd: CChar
325+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
326+
@_silgen_name(raw: "__start_swift5_type_metadata") private nonisolated(unsafe) var _typeMetadataSectionBegin: CChar
327+
@_silgen_name(raw: "__stop_swift5_type_metadata") private nonisolated(unsafe) var _typeMetadataSectionEnd: CChar
328+
#endif
329+
#else
330+
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)")
331+
private nonisolated(unsafe) var _testContentSectionBegin: Void
332+
private nonisolated(unsafe) var _testContentSectionEnd: Void
333+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
334+
private nonisolated(unsafe) var _typeMetadataSectionBegin: Void
335+
private nonisolated(unsafe) var _typeMetadataSectionEnd: Void
336+
#endif
337+
338+
extension UnsafeRawBufferPointer {
339+
/// Construct an empty buffer.
340+
fileprivate init(_: Void = (), sectionBegin _: UnsafeRawPointer, sectionEnd _: UnsafeRawPointer) {
341+
self.init(start: nil, count: 0)
342+
}
343+
}
344+
#endif
345+
346+
extension UnsafeRawBufferPointer {
347+
/// Construct a buffer representing a section's byte bounds.
348+
///
349+
/// - Parameters:
350+
/// - sectionBegin: The address of the first byte of the section.
351+
/// - sectionEnd: The address of the first byte _after_ the section.
352+
@_disfavoredOverload fileprivate init(sectionBegin: UnsafeRawPointer, sectionEnd: UnsafeRawPointer) {
353+
self.init(start: sectionBegin, count: sectionEnd - sectionBegin)
354+
}
355+
}
356+
308357
/// The common implementation of ``SectionBounds/all(_:)`` for platforms that do
309358
/// not support dynamic linking.
310359
///
@@ -313,10 +362,15 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
313362
///
314363
/// - Returns: A structure describing the bounds of the type metadata section
315364
/// contained in the same image as the testing library itself.
316-
private func _sectionBounds(_ kind: SectionBounds.Kind) -> CollectionOfOne<SectionBounds> {
317-
var (baseAddress, count): (UnsafeRawPointer?, Int) = (nil, 0)
318-
swt_getStaticallyLinkedSectionBounds(kind.rawValue, &baseAddress, &count)
319-
let buffer = UnsafeRawBufferPointer(start: baseAddress, count: count)
365+
private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence<SectionBounds> {
366+
let buffer = switch kind {
367+
case .testContent:
368+
UnsafeRawBufferPointer(sectionBegin: &_testContentSectionBegin, sectionEnd: &_testContentSectionEnd)
369+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
370+
case .typeMetadata:
371+
UnsafeRawBufferPointer(sectionBegin: &_typeMetadataSectionBegin, sectionEnd: &_typeMetadataSectionEnd)
372+
#endif
373+
}
320374
let sb = SectionBounds(imageAddress: nil, buffer: buffer)
321375
return CollectionOfOne(sb)
322376
}

Sources/_TestDiscovery/TestContentRecord.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,8 @@ extension DiscoverableAsTestContent where Self: ~Copyable {
336336
validateMemoryLayout()
337337

338338
let result = SectionBounds.all(.typeMetadata).lazy.flatMap { sb in
339-
stride(from: sb.buffer.baseAddress!, to: sb.buffer.baseAddress! + sb.buffer.count, by: SWTTypeMetadataRecordByteCount).lazy
339+
stride(from: 0, to: sb.buffer.count, by: SWTTypeMetadataRecordByteCount).lazy
340+
.map { sb.buffer.baseAddress! + $0 }
340341
.compactMap { swt_getType(fromTypeMetadataRecord: $0, ifNameContains: "__🟡$") }
341342
.map { unsafeBitCast($0, to: Any.Type.self) }
342343
.compactMap { $0 as? any TestContentRecordContainer.Type }

Sources/_TestingInternals/Discovery.cpp

-44
Original file line numberDiff line numberDiff line change
@@ -10,55 +10,11 @@
1010

1111
#include "Discovery.h"
1212

13-
#include <algorithm>
1413
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
1514
#include <cstdint>
1615
#include <cstring>
1716
#include <type_traits>
18-
#endif
19-
20-
#if defined(SWT_NO_DYNAMIC_LINKING)
21-
#pragma mark - Statically-linked section bounds
2217

23-
#if defined(__APPLE__)
24-
extern "C" const char testContentSectionBegin __asm("section$start$__DATA_CONST$__swift5_tests");
25-
extern "C" const char testContentSectionEnd __asm("section$end$__DATA_CONST$__swift5_tests");
26-
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
27-
extern "C" const char typeMetadataSectionBegin __asm__("section$start$__TEXT$__swift5_types");
28-
extern "C" const char typeMetadataSectionEnd __asm__("section$end$__TEXT$__swift5_types");
29-
#endif
30-
#elif defined(__wasi__)
31-
extern "C" const char testContentSectionBegin __asm__("__start_swift5_tests");
32-
extern "C" const char testContentSectionEnd __asm__("__stop_swift5_tests");
33-
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
34-
extern "C" const char typeMetadataSectionBegin __asm__("__start_swift5_type_metadata");
35-
extern "C" const char typeMetadataSectionEnd __asm__("__stop_swift5_type_metadata");
36-
#endif
37-
#else
38-
#warning Platform-specific implementation missing: Runtime test discovery unavailable (static)
39-
static const char testContentSectionBegin = 0;
40-
static const char& testContentSectionEnd = testContentSectionBegin;
41-
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
42-
static const char typeMetadataSectionBegin = 0;
43-
static const char& typeMetadataSectionEnd = typeMetadataSectionBegin;
44-
#endif
45-
#endif
46-
47-
static constexpr const char *const staticallyLinkedSectionBounds[][2] = {
48-
{ &testContentSectionBegin, &testContentSectionEnd },
49-
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
50-
{ &typeMetadataSectionBegin, &typeMetadataSectionEnd },
51-
#endif
52-
};
53-
54-
void swt_getStaticallyLinkedSectionBounds(size_t kind, const void **outSectionBegin, size_t *outByteCount) {
55-
auto [sectionBegin, sectionEnd] = staticallyLinkedSectionBounds[kind];
56-
*outSectionBegin = sectionBegin;
57-
*outByteCount = std::distance(sectionBegin, sectionEnd);
58-
}
59-
#endif
60-
61-
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
6218
#pragma mark - Swift ABI
6319

6420
#if defined(__PTRAUTH_INTRINSICS__)

Sources/_TestingInternals/include/Discovery.h

-15
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,6 @@ SWT_IMPORT_FROM_STDLIB void swift_enumerateAllMetadataSections(
3030
);
3131
#endif
3232

33-
#pragma mark - Statically-linked section bounds
34-
35-
/// Get the bounds of a statically linked section in this image.
36-
///
37-
/// - Parameters:
38-
/// - kind: The value of `SectionBounds.Kind.rawValue` for the given section.
39-
/// - outSectionBegin: On return, a pointer to the first byte of the section.
40-
/// - outByteCount: On return, the number of bytes in the section.
41-
///
42-
/// - Note: This symbol is _declared_, but not _defined_, on platforms with
43-
/// dynamic linking because the `SWT_NO_DYNAMIC_LINKING` C++ macro (not the
44-
/// Swift compiler conditional of the same name) is not consistently declared
45-
/// when Swift files import the `_TestingInternals` C++ module.
46-
SWT_EXTERN void swt_getStaticallyLinkedSectionBounds(size_t kind, const void *_Nullable *_Nonnull outSectionBegin, size_t *outByteCount);
47-
4833
#pragma mark - Legacy test discovery
4934

5035
/// The size, in bytes, of a Swift type metadata record.

0 commit comments

Comments
 (0)