Skip to content

Rewrite static section bounds discovery in Swift. #1012

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 10 commits into from
Mar 19, 2025
57 changes: 33 additions & 24 deletions Documentation/Porting.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ emits section information into the resource fork on Classic, you would use the
to load that information:

```diff
--- a/Sources/Testing/Discovery+Platform.swift
+++ b/Sources/Testing/Discovery+Platform.swift
--- a/Sources/_TestDiscovery/SectionBounds.swift
+++ b/Sources/_TestDiscovery/SectionBounds.swift

// ...
+#elseif os(Classic)
Expand Down Expand Up @@ -176,10 +176,13 @@ to load that information:
+ } while noErr == GetNextResourceFile(refNum, &refNum))
+ return result
+}
#else
private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)")
return []
+
#elseif !SWT_NO_DYNAMIC_LINKING
// MARK: - Missing dynamic implementation

private func _sectionBounds(_ kind: SectionBounds.Kind) -> EmptyCollection<SectionBounds> {
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)")
return EmptyCollection()
}
#endif
```
Expand Down Expand Up @@ -211,36 +214,42 @@ start with `"SWT_"`).
If your platform does not support dynamic linking and loading, you will need to
use static linkage instead. Define the `"SWT_NO_DYNAMIC_LINKING"` compiler
conditional for your platform in both `Package.swift` and
`CompilerSettings.cmake`, then define the symbols `testContentSectionBegin`,
`testContentSectionEnd`, `typeMetadataSectionBegin`, and
`typeMetadataSectionEnd` in `Discovery.cpp`.
`CompilerSettings.cmake`, then define the symbols `_testContentSectionBegin`,
`_testContentSectionEnd`, `_typeMetadataSectionBegin`, and
`_typeMetadataSectionEnd` in `SectionBounds.swift`:

```diff
diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp
--- a/Sources/_TestDiscovery/SectionBounds.swift
+++ b/Sources/_TestDiscovery/SectionBounds.swift
// ...
+#elif defined(macintosh)
+extern "C" const char testContentSectionBegin __asm__("...");
+extern "C" const char testContentSectionEnd __asm__("...");
+#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
+extern "C" const char typeMetadataSectionBegin __asm__("...");
+extern "C" const char typeMetadataSectionEnd __asm__("...");
+#elseif os(Classic)
+@_silgen_name(raw: "...") private nonisolated(unsafe) var _testContentSectionBegin: _SectionBound
+@_silgen_name(raw: "...") private nonisolated(unsafe) var _testContentSectionEnd: _SectionBound
+#if !SWT_NO_LEGACY_TEST_DISCOVERY
+@_silgen_name(raw: "...") private nonisolated(unsafe) var _typeMetadataSectionBegin: _SectionBound
+@_silgen_name(raw: "...") private nonisolated(unsafe) var _typeMetadataSectionEnd: _SectionBound
+#endif
#else
#warning Platform-specific implementation missing: Runtime test discovery unavailable (static)
static const char testContentSectionBegin = 0;
static const char& testContentSectionEnd = testContentSectionBegin;
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
static const char typeMetadataSectionBegin = 0;
static const char& typeMetadataSectionEnd = typeMetadataSectionBegin;
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)")
private nonisolated(unsafe) let _testContentSectionBegin = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 16)
private nonisolated(unsafe) let _testContentSectionEnd = _testContentSectionBegin
#if !SWT_NO_LEGACY_TEST_DISCOVERY
private nonisolated(unsafe) let _typeMetadataSectionBegin = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 16)
private nonisolated(unsafe) let _typeMetadataSectionEnd = _typeMetadataSectionBegin
#endif
#endif
// ...
```

These symbols must have unique addresses corresponding to the first byte of the
test content section and the first byte _after_ the test content section,
respectively. Their linker-level names will be platform-dependent: refer to the
linker documentation for your platform to determine what names to place in the
`__asm__` attribute applied to each.
`@_silgen_name` attribute applied to each.

If your target platform statically links Swift Testing but the linker does not
define section bounds symbols, please reach out to us in the Swift forums for
advice.

## C++ stub implementations

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

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

If you wish to host your own CI jobs, let us know: we'd be happy to run them as
Expand Down
79 changes: 67 additions & 12 deletions Sources/_TestDiscovery/SectionBounds.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct SectionBounds: Sendable {

/// An enumeration describing the different sections discoverable by the
/// testing library.
enum Kind: Int, Equatable, Hashable, CaseIterable {
enum Kind: Equatable, Hashable, CaseIterable {
/// The test content metadata section.
case testContent

Expand All @@ -45,8 +45,7 @@ struct SectionBounds: Sendable {
}
}

#if !SWT_NO_DYNAMIC_LINKING
#if SWT_TARGET_OS_APPLE
#if SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING
// MARK: - Apple implementation

extension SectionBounds.Kind {
Expand Down Expand Up @@ -157,7 +156,7 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
}
}

#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)
#elseif (os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)) && !SWT_NO_DYNAMIC_LINKING
// MARK: - ELF implementation

private import SwiftShims // For MetadataSections
Expand Down Expand Up @@ -278,6 +277,9 @@ private func _findSection(named sectionName: String, in hModule: HMODULE) -> Sec
///
/// - Returns: An array of structures describing the bounds of all known test
/// content sections in the current process.
///
/// This implementation is always used on Windows (even when the testing library
/// is statically linked.)
private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence<SectionBounds> {
let sectionName = switch kind {
case .testContent:
Expand All @@ -289,22 +291,71 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence<Section
}
return HMODULE.all.lazy.compactMap { _findSection(named: sectionName, in: $0) }
}
#else

#elseif !SWT_NO_DYNAMIC_LINKING
// MARK: - Missing dynamic implementation

/// The fallback implementation of ``SectionBounds/all(_:)`` for platforms that
/// support dynamic linking.
///
/// - Parameters:
/// - kind: Ignored.
///
/// - Returns: The empty array.
private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)")
return []
private func _sectionBounds(_ kind: SectionBounds.Kind) -> EmptyCollection<SectionBounds> {
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)")
return EmptyCollection()
}
#endif

#else
// MARK: - Statically-linked implementation

/// A type representing the upper or lower bound of a metadata section.
///
/// This type is move-only and instances of it are declared as global mutable
/// variables below to ensure that they have fixed addresses. Use the `..<`
/// operator to get a range of addresses from two instances of this type.
///
/// On platforms that use static linkage and have well-defined bounds symbols,
/// those symbols are imported into Swift below using the experimental
/// `@_silgen_name` attribute.
private struct _SectionBound: Sendable, ~Copyable {
/// A property that forces the structure to have an in-memory representation.
private var _storage: CChar = 0

static func ..<(lhs: inout Self, rhs: inout Self) -> Range<UnsafeRawPointer> {
withUnsafeMutablePointer(to: &lhs) { lhs in
withUnsafeMutablePointer(to: &rhs) { rhs in
UnsafeRawPointer(lhs) ..< UnsafeRawPointer(rhs)
}
}
}
}

#if SWT_TARGET_OS_APPLE
@_silgen_name(raw: "section$start$__DATA_CONST$__swift5_tests") private nonisolated(unsafe) var _testContentSectionBegin: _SectionBound
@_silgen_name(raw: "section$end$__DATA_CONST$__swift5_tests") private nonisolated(unsafe) var _testContentSectionEnd: _SectionBound
#if !SWT_NO_LEGACY_TEST_DISCOVERY
@_silgen_name(raw: "section$start$__TEXT$__swift5_types") private nonisolated(unsafe) var _typeMetadataSectionBegin: _SectionBound
@_silgen_name(raw: "section$end$__TEXT$__swift5_types") private nonisolated(unsafe) var _typeMetadataSectionEnd: _SectionBound
#endif
#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || os(WASI)
@_silgen_name(raw: "__start_swift5_tests") private nonisolated(unsafe) var _testContentSectionBegin: _SectionBound
@_silgen_name(raw: "__stop_swift5_tests") private nonisolated(unsafe) var _testContentSectionEnd: _SectionBound
#if !SWT_NO_LEGACY_TEST_DISCOVERY
@_silgen_name(raw: "__start_swift5_type_metadata") private nonisolated(unsafe) var _typeMetadataSectionBegin: _SectionBound
@_silgen_name(raw: "__stop_swift5_type_metadata") private nonisolated(unsafe) var _typeMetadataSectionEnd: _SectionBound
#endif
#else
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)")
private nonisolated(unsafe) let _testContentSectionBegin = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 16)
private nonisolated(unsafe) let _testContentSectionEnd = _testContentSectionBegin
#if !SWT_NO_LEGACY_TEST_DISCOVERY
private nonisolated(unsafe) let _typeMetadataSectionBegin = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 16)
private nonisolated(unsafe) let _typeMetadataSectionEnd = _typeMetadataSectionBegin
#endif
#endif

/// The common implementation of ``SectionBounds/all(_:)`` for platforms that do
/// not support dynamic linking.
///
Expand All @@ -314,9 +365,13 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
/// - Returns: A structure describing the bounds of the type metadata section
/// contained in the same image as the testing library itself.
private func _sectionBounds(_ kind: SectionBounds.Kind) -> CollectionOfOne<SectionBounds> {
var (baseAddress, count): (UnsafeRawPointer?, Int) = (nil, 0)
swt_getStaticallyLinkedSectionBounds(kind.rawValue, &baseAddress, &count)
let buffer = UnsafeRawBufferPointer(start: baseAddress, count: count)
let range = switch kind {
case .testContent:
_testContentSectionBegin ..< _testContentSectionEnd
case .typeMetadata:
_typeMetadataSectionBegin ..< _typeMetadataSectionEnd
}
let buffer = UnsafeRawBufferPointer(start: range.lowerBound, count: range.count)
let sb = SectionBounds(imageAddress: nil, buffer: buffer)
return CollectionOfOne(sb)
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/_TestDiscovery/TestContentRecord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ extension DiscoverableAsTestContent where Self: ~Copyable {
}

let result = SectionBounds.all(.typeMetadata).lazy.flatMap { sb in
stride(from: sb.buffer.baseAddress!, to: sb.buffer.baseAddress! + sb.buffer.count, by: SWTTypeMetadataRecordByteCount).lazy
stride(from: 0, to: sb.buffer.count, by: SWTTypeMetadataRecordByteCount).lazy
.map { sb.buffer.baseAddress! + $0 }
.compactMap { swt_getType(fromTypeMetadataRecord: $0, ifNameContains: typeNameHint) }
.map { unsafeBitCast($0, to: Any.Type.self) }
.compactMap(loader)
Expand Down
46 changes: 1 addition & 45 deletions Sources/_TestingInternals/Discovery.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Copyright (c) 2023–2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
Expand All @@ -10,55 +10,11 @@

#include "Discovery.h"

#include <algorithm>
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
#include <cstdint>
#include <cstring>
#include <type_traits>
#endif

#if defined(SWT_NO_DYNAMIC_LINKING)
#pragma mark - Statically-linked section bounds

#if defined(__APPLE__)
extern "C" const char testContentSectionBegin __asm("section$start$__DATA_CONST$__swift5_tests");
extern "C" const char testContentSectionEnd __asm("section$end$__DATA_CONST$__swift5_tests");
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
extern "C" const char typeMetadataSectionBegin __asm__("section$start$__TEXT$__swift5_types");
extern "C" const char typeMetadataSectionEnd __asm__("section$end$__TEXT$__swift5_types");
#endif
#elif defined(__wasi__)
extern "C" const char testContentSectionBegin __asm__("__start_swift5_tests");
extern "C" const char testContentSectionEnd __asm__("__stop_swift5_tests");
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
extern "C" const char typeMetadataSectionBegin __asm__("__start_swift5_type_metadata");
extern "C" const char typeMetadataSectionEnd __asm__("__stop_swift5_type_metadata");
#endif
#else
#warning Platform-specific implementation missing: Runtime test discovery unavailable (static)
static const char testContentSectionBegin = 0;
static const char& testContentSectionEnd = testContentSectionBegin;
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
static const char typeMetadataSectionBegin = 0;
static const char& typeMetadataSectionEnd = typeMetadataSectionBegin;
#endif
#endif

static constexpr const char *const staticallyLinkedSectionBounds[][2] = {
{ &testContentSectionBegin, &testContentSectionEnd },
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
{ &typeMetadataSectionBegin, &typeMetadataSectionEnd },
#endif
};

void swt_getStaticallyLinkedSectionBounds(size_t kind, const void **outSectionBegin, size_t *outByteCount) {
auto [sectionBegin, sectionEnd] = staticallyLinkedSectionBounds[kind];
*outSectionBegin = sectionBegin;
*outByteCount = std::distance(sectionBegin, sectionEnd);
}
#endif

#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
#pragma mark - Swift ABI

#if defined(__PTRAUTH_INTRINSICS__)
Expand Down
17 changes: 1 addition & 16 deletions Sources/_TestingInternals/include/Discovery.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Copyright (c) 2023–2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
Expand Down Expand Up @@ -30,21 +30,6 @@ SWT_IMPORT_FROM_STDLIB void swift_enumerateAllMetadataSections(
);
#endif

#pragma mark - Statically-linked section bounds

/// Get the bounds of a statically linked section in this image.
///
/// - Parameters:
/// - kind: The value of `SectionBounds.Kind.rawValue` for the given section.
/// - outSectionBegin: On return, a pointer to the first byte of the section.
/// - outByteCount: On return, the number of bytes in the section.
///
/// - Note: This symbol is _declared_, but not _defined_, on platforms with
/// dynamic linking because the `SWT_NO_DYNAMIC_LINKING` C++ macro (not the
/// Swift compiler conditional of the same name) is not consistently declared
/// when Swift files import the `_TestingInternals` C++ module.
SWT_EXTERN void swt_getStaticallyLinkedSectionBounds(size_t kind, const void *_Nullable *_Nonnull outSectionBegin, size_t *outByteCount);

#pragma mark - Legacy test discovery

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