diff --git a/Documentation/Porting.md b/Documentation/Porting.md index 8b230ff22..6e83e0eb0 100644 --- a/Documentation/Porting.md +++ b/Documentation/Porting.md @@ -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) @@ -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 { + #warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)") + return EmptyCollection() } #endif ``` @@ -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 @@ -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 diff --git a/Sources/_TestDiscovery/SectionBounds.swift b/Sources/_TestDiscovery/SectionBounds.swift index 212edbfbf..1b4cbaa2a 100644 --- a/Sources/_TestDiscovery/SectionBounds.swift +++ b/Sources/_TestDiscovery/SectionBounds.swift @@ -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 @@ -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 { @@ -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 @@ -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 { let sectionName = switch kind { case .testContent: @@ -289,7 +291,10 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence
some Sequence
[SectionBounds] { - #warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)") - return [] +private func _sectionBounds(_ kind: SectionBounds.Kind) -> EmptyCollection { +#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 { + 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. /// @@ -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 { - 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) } diff --git a/Sources/_TestDiscovery/TestContentRecord.swift b/Sources/_TestDiscovery/TestContentRecord.swift index 5235d9c64..25f46fa44 100644 --- a/Sources/_TestDiscovery/TestContentRecord.swift +++ b/Sources/_TestDiscovery/TestContentRecord.swift @@ -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) diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp index ad898534f..1e70a038d 100644 --- a/Sources/_TestingInternals/Discovery.cpp +++ b/Sources/_TestingInternals/Discovery.cpp @@ -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 @@ -10,55 +10,11 @@ #include "Discovery.h" -#include #if !defined(SWT_NO_LEGACY_TEST_DISCOVERY) #include #include #include -#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__) diff --git a/Sources/_TestingInternals/include/Discovery.h b/Sources/_TestingInternals/include/Discovery.h index 9bda12b93..25c3603b3 100644 --- a/Sources/_TestingInternals/include/Discovery.h +++ b/Sources/_TestingInternals/include/Discovery.h @@ -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 @@ -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.