Skip to content

Commit cf94bdc

Browse files
authoredMar 19, 2025
Rewrite static section bounds discovery in Swift. (#1012)
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. ### Why do we need to rewrite this code in Swift? In Embedded Swift, there is only one (statically linked) image in the process containing Swift code, and if the target is a microcontroller or similar, it probably doesn't even have a dynamic linker/loader in the first place for us to query. So we need, on Embedded Swift targets, to perform static discovery of exactly one test content section. (This is potentially something we can make configurable via the target's toolset in a future PR.) The bounds of the test content section are currently defined in C++ using `__asm__` to reference linker-defined symbols. However, our C++ target can't reliably tell if it is being built for Embedded Swift. That knowledge is treated by the Swift toolchain as a Swift language feature and can be checked in Swift with `hasFeature(Embedded)`, but there's no C++ equivalent. If the implementation is written in Swift, we can know at compile time whether or not we're dynamically or statically linking to a test target (and therefore whether we need to do dynamic or static lookup of images, customized toolset aside.) Rewriting this logic in Swift reduces our C++ code size (and our direct dependencies on the C++ STL) which is also a nice bonus. ### 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.

File tree

5 files changed

+104
-98
lines changed

5 files changed

+104
-98
lines changed
 

‎Documentation/Porting.md

+33-24
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)
@@ -176,10 +176,13 @@ to load that information:
176176
+ } while noErr == GetNextResourceFile(refNum, &refNum))
177177
+ return result
178178
+}
179-
#else
180-
private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
181-
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)")
182-
return []
179+
+
180+
#elseif !SWT_NO_DYNAMIC_LINKING
181+
// MARK: - Missing dynamic implementation
182+
183+
private func _sectionBounds(_ kind: SectionBounds.Kind) -> EmptyCollection<SectionBounds> {
184+
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)")
185+
return EmptyCollection()
183186
}
184187
#endif
185188
```
@@ -211,36 +214,42 @@ start with `"SWT_"`).
211214
If your platform does not support dynamic linking and loading, you will need to
212215
use static linkage instead. Define the `"SWT_NO_DYNAMIC_LINKING"` compiler
213216
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`.
217+
`CompilerSettings.cmake`, then define the symbols `_testContentSectionBegin`,
218+
`_testContentSectionEnd`, `_typeMetadataSectionBegin`, and
219+
`_typeMetadataSectionEnd` in `SectionBounds.swift`:
217220

218221
```diff
219-
diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp
222+
--- a/Sources/_TestDiscovery/SectionBounds.swift
223+
+++ b/Sources/_TestDiscovery/SectionBounds.swift
220224
// ...
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__("...");
225+
+#elseif os(Classic)
226+
+@_silgen_name(raw: "...") private nonisolated(unsafe) var _testContentSectionBegin: _SectionBound
227+
+@_silgen_name(raw: "...") private nonisolated(unsafe) var _testContentSectionEnd: _SectionBound
228+
+#if !SWT_NO_LEGACY_TEST_DISCOVERY
229+
+@_silgen_name(raw: "...") private nonisolated(unsafe) var _typeMetadataSectionBegin: _SectionBound
230+
+@_silgen_name(raw: "...") private nonisolated(unsafe) var _typeMetadataSectionEnd: _SectionBound
227231
+#endif
228232
#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;
233+
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)")
234+
private nonisolated(unsafe) let _testContentSectionBegin = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 16)
235+
private nonisolated(unsafe) let _testContentSectionEnd = _testContentSectionBegin
236+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
237+
private nonisolated(unsafe) let _typeMetadataSectionBegin = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 16)
238+
private nonisolated(unsafe) let _typeMetadataSectionEnd = _typeMetadataSectionBegin
235239
#endif
236240
#endif
241+
// ...
237242
```
238243

239244
These symbols must have unique addresses corresponding to the first byte of the
240245
test content section and the first byte _after_ the test content section,
241246
respectively. Their linker-level names will be platform-dependent: refer to the
242247
linker documentation for your platform to determine what names to place in the
243-
`__asm__` attribute applied to each.
248+
`@_silgen_name` attribute applied to each.
249+
250+
If your target platform statically links Swift Testing but the linker does not
251+
define section bounds symbols, please reach out to us in the Swift forums for
252+
advice.
244253

245254
## C++ stub implementations
246255

@@ -332,7 +341,7 @@ to include the necessary linker flags.
332341
## Adding CI jobs for the new platform
333342

334343
The Swift project maintains a set of CI jobs that target various platforms. To
335-
add CI jobs for Swift Testing or the Swift toolchain, please contain the CI
344+
add CI jobs for Swift Testing or the Swift toolchain, please contact the CI
336345
maintainers on the Swift forums.
337346

338347
If you wish to host your own CI jobs, let us know: we'd be happy to run them as

‎Sources/_TestDiscovery/SectionBounds.swift

+67-12
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

@@ -45,8 +45,7 @@ struct SectionBounds: Sendable {
4545
}
4646
}
4747

48-
#if !SWT_NO_DYNAMIC_LINKING
49-
#if SWT_TARGET_OS_APPLE
48+
#if SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING
5049
// MARK: - Apple implementation
5150

5251
extension SectionBounds.Kind {
@@ -157,7 +156,7 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
157156
}
158157
}
159158

160-
#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)
159+
#elseif (os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)) && !SWT_NO_DYNAMIC_LINKING
161160
// MARK: - ELF implementation
162161

163162
private import SwiftShims // For MetadataSections
@@ -278,6 +277,9 @@ private func _findSection(named sectionName: String, in hModule: HMODULE) -> Sec
278277
///
279278
/// - Returns: An array of structures describing the bounds of all known test
280279
/// content sections in the current process.
280+
///
281+
/// This implementation is always used on Windows (even when the testing library
282+
/// is statically linked.)
281283
private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence<SectionBounds> {
282284
let sectionName = switch kind {
283285
case .testContent:
@@ -289,22 +291,71 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence<Section
289291
}
290292
return HMODULE.all.lazy.compactMap { _findSection(named: sectionName, in: $0) }
291293
}
292-
#else
294+
295+
#elseif !SWT_NO_DYNAMIC_LINKING
296+
// MARK: - Missing dynamic implementation
297+
293298
/// The fallback implementation of ``SectionBounds/all(_:)`` for platforms that
294299
/// support dynamic linking.
295300
///
296301
/// - Parameters:
297302
/// - kind: Ignored.
298303
///
299304
/// - Returns: The empty array.
300-
private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
301-
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)")
302-
return []
305+
private func _sectionBounds(_ kind: SectionBounds.Kind) -> EmptyCollection<SectionBounds> {
306+
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)")
307+
return EmptyCollection()
303308
}
304-
#endif
309+
305310
#else
306311
// MARK: - Statically-linked implementation
307312

313+
/// A type representing the upper or lower bound of a metadata section.
314+
///
315+
/// This type is move-only and instances of it are declared as global mutable
316+
/// variables below to ensure that they have fixed addresses. Use the `..<`
317+
/// operator to get a range of addresses from two instances of this type.
318+
///
319+
/// On platforms that use static linkage and have well-defined bounds symbols,
320+
/// those symbols are imported into Swift below using the experimental
321+
/// `@_silgen_name` attribute.
322+
private struct _SectionBound: Sendable, ~Copyable {
323+
/// A property that forces the structure to have an in-memory representation.
324+
private var _storage: CChar = 0
325+
326+
static func ..<(lhs: inout Self, rhs: inout Self) -> Range<UnsafeRawPointer> {
327+
withUnsafeMutablePointer(to: &lhs) { lhs in
328+
withUnsafeMutablePointer(to: &rhs) { rhs in
329+
UnsafeRawPointer(lhs) ..< UnsafeRawPointer(rhs)
330+
}
331+
}
332+
}
333+
}
334+
335+
#if SWT_TARGET_OS_APPLE
336+
@_silgen_name(raw: "section$start$__DATA_CONST$__swift5_tests") private nonisolated(unsafe) var _testContentSectionBegin: _SectionBound
337+
@_silgen_name(raw: "section$end$__DATA_CONST$__swift5_tests") private nonisolated(unsafe) var _testContentSectionEnd: _SectionBound
338+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
339+
@_silgen_name(raw: "section$start$__TEXT$__swift5_types") private nonisolated(unsafe) var _typeMetadataSectionBegin: _SectionBound
340+
@_silgen_name(raw: "section$end$__TEXT$__swift5_types") private nonisolated(unsafe) var _typeMetadataSectionEnd: _SectionBound
341+
#endif
342+
#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI)
343+
@_silgen_name(raw: "__start_swift5_tests") private nonisolated(unsafe) var _testContentSectionBegin: _SectionBound
344+
@_silgen_name(raw: "__stop_swift5_tests") private nonisolated(unsafe) var _testContentSectionEnd: _SectionBound
345+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
346+
@_silgen_name(raw: "__start_swift5_type_metadata") private nonisolated(unsafe) var _typeMetadataSectionBegin: _SectionBound
347+
@_silgen_name(raw: "__stop_swift5_type_metadata") private nonisolated(unsafe) var _typeMetadataSectionEnd: _SectionBound
348+
#endif
349+
#else
350+
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)")
351+
private nonisolated(unsafe) let _testContentSectionBegin = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 16)
352+
private nonisolated(unsafe) let _testContentSectionEnd = _testContentSectionBegin
353+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
354+
private nonisolated(unsafe) let _typeMetadataSectionBegin = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 16)
355+
private nonisolated(unsafe) let _typeMetadataSectionEnd = _typeMetadataSectionBegin
356+
#endif
357+
#endif
358+
308359
/// The common implementation of ``SectionBounds/all(_:)`` for platforms that do
309360
/// not support dynamic linking.
310361
///
@@ -314,9 +365,13 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
314365
/// - Returns: A structure describing the bounds of the type metadata section
315366
/// contained in the same image as the testing library itself.
316367
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)
368+
let range = switch kind {
369+
case .testContent:
370+
_testContentSectionBegin ..< _testContentSectionEnd
371+
case .typeMetadata:
372+
_typeMetadataSectionBegin ..< _typeMetadataSectionEnd
373+
}
374+
let buffer = UnsafeRawBufferPointer(start: range.lowerBound, count: range.count)
320375
let sb = SectionBounds(imageAddress: nil, buffer: buffer)
321376
return CollectionOfOne(sb)
322377
}

‎Sources/_TestDiscovery/TestContentRecord.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,8 @@ extension DiscoverableAsTestContent where Self: ~Copyable {
276276
}
277277

278278
let result = SectionBounds.all(.typeMetadata).lazy.flatMap { sb in
279-
stride(from: sb.buffer.baseAddress!, to: sb.buffer.baseAddress! + sb.buffer.count, by: SWTTypeMetadataRecordByteCount).lazy
279+
stride(from: 0, to: sb.buffer.count, by: SWTTypeMetadataRecordByteCount).lazy
280+
.map { sb.buffer.baseAddress! + $0 }
280281
.compactMap { swt_getType(fromTypeMetadataRecord: $0, ifNameContains: typeNameHint) }
281282
.map { unsafeBitCast($0, to: Any.Type.self) }
282283
.compactMap(loader)

‎Sources/_TestingInternals/Discovery.cpp

+1-45
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// This source file is part of the Swift.org open source project
33
//
4-
// Copyright (c) 2023 Apple Inc. and the Swift project authors
4+
// Copyright (c) 2023–2025 Apple Inc. and the Swift project authors
55
// Licensed under Apache License v2.0 with Runtime Library Exception
66
//
77
// See https://swift.org/LICENSE.txt for license information
@@ -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

+1-16
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// This source file is part of the Swift.org open source project
33
//
4-
// Copyright (c) 2023 Apple Inc. and the Swift project authors
4+
// Copyright (c) 2023–2025 Apple Inc. and the Swift project authors
55
// Licensed under Apache License v2.0 with Runtime Library Exception
66
//
77
// See https://swift.org/LICENSE.txt for license information
@@ -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)
Please sign in to comment.