From dbd44fc809c10e1818b567cc4e14b57a20489b39 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 11 Mar 2025 10:16:40 -0400 Subject: [PATCH 01/10] 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. --- Documentation/Porting.md | 39 +++++----- Sources/_TestDiscovery/SectionBounds.swift | 72 ++++++++++++++++--- .../_TestDiscovery/TestContentRecord.swift | 3 +- Sources/_TestingInternals/Discovery.cpp | 44 ------------ Sources/_TestingInternals/include/Discovery.h | 15 ---- 5 files changed, 85 insertions(+), 88 deletions(-) diff --git a/Documentation/Porting.md b/Documentation/Porting.md index 8b230ff22..06ce93833 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) @@ -211,29 +211,30 @@ 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: CChar ++@_silgen_name(raw: "...") private nonisolated(unsafe) var _testContentSectionEnd: CChar ++#if !SWT_NO_LEGACY_TEST_DISCOVERY ++@_silgen_name(raw: "...") private nonisolated(unsafe) var _typeMetadataSectionBegin: CChar ++@_silgen_name(raw: "...") private nonisolated(unsafe) var _typeMetadataSectionEnd: CChar +#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 + #warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)") + private nonisolated(unsafe) var _testContentSectionBegin: Void + private nonisolated(unsafe) var _testContentSectionEnd: Void + #if !SWT_NO_LEGACY_TEST_DISCOVERY + private nonisolated(unsafe) var _typeMetadataSectionBegin: Void + private nonisolated(unsafe) var _typeMetadataSectionEnd: Void #endif + // ... ``` These symbols must have unique addresses corresponding to the first byte of the diff --git a/Sources/_TestDiscovery/SectionBounds.swift b/Sources/_TestDiscovery/SectionBounds.swift index 212edbfbf..9bc37a014 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 @@ -146,7 +146,7 @@ private let _startCollectingSectionBounds: Void = { /// /// - Returns: An array of structures describing the bounds of all known test /// content sections in the current process. -private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] { +private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence { _startCollectingSectionBounds return _sectionBounds.withUnsafeMutablePointers { sectionBounds, lock in pthread_mutex_lock(lock) @@ -169,7 +169,7 @@ private import SwiftShims // For MetadataSections /// /// - Returns: An array of structures describing the bounds of all known test /// content sections in the current process. -private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] { +private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence { struct Context { var kind: SectionBounds.Kind var result = [SectionBounds]() @@ -297,14 +297,63 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence
[SectionBounds] { +private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence { #warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)") - return [] + return EmptyCollection() } #endif #else // MARK: - Statically-linked implementation +/// The bounds of the statically-linked sections of interest to the testing +/// library. +/// +/// We use `@_silgen_name(raw:)` to reference these linker-defined symbols. +/// Then, to get their addresses, we must use `&` (`withPointer(to:)` is not +/// guaranteed to avoid a copy in this context.) Hence, they must also be +/// declared `var` rather than `let`, but they should be treated as read-only. +#if SWT_TARGET_OS_APPLE +@_silgen_name(raw: "section$start$__DATA_CONST$__swift5_tests") private nonisolated(unsafe) var _testContentSectionBegin: CChar +@_silgen_name(raw: "section$end$__DATA_CONST$__swift5_tests") private nonisolated(unsafe) var _testContentSectionEnd: CChar +#if !SWT_NO_LEGACY_TEST_DISCOVERY +@_silgen_name(raw: "section$start$__TEXT$__swift5_types") private nonisolated(unsafe) var _typeMetadataSectionBegin: CChar +@_silgen_name(raw: "section$end$__TEXT$__swift5_types") private nonisolated(unsafe) var _typeMetadataSectionEnd: CChar +#endif +#elseif os(WASI) +@_silgen_name(raw: "__start_swift5_tests") private nonisolated(unsafe) var _testContentSectionBegin: CChar +@_silgen_name(raw: "__stop_swift5_tests") private nonisolated(unsafe) var _testContentSectionEnd: CChar +#if !SWT_NO_LEGACY_TEST_DISCOVERY +@_silgen_name(raw: "__start_swift5_type_metadata") private nonisolated(unsafe) var _typeMetadataSectionBegin: CChar +@_silgen_name(raw: "__stop_swift5_type_metadata") private nonisolated(unsafe) var _typeMetadataSectionEnd: CChar +#endif +#else +#warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)") +private nonisolated(unsafe) var _testContentSectionBegin: Void +private nonisolated(unsafe) var _testContentSectionEnd: Void +#if !SWT_NO_LEGACY_TEST_DISCOVERY +private nonisolated(unsafe) var _typeMetadataSectionBegin: Void +private nonisolated(unsafe) var _typeMetadataSectionEnd: Void +#endif + +extension UnsafeRawBufferPointer { + /// Construct an empty buffer. + fileprivate init(_: Void = (), sectionBegin _: UnsafeRawPointer, sectionEnd _: UnsafeRawPointer) { + self.init(start: nil, count: 0) + } +} +#endif + +extension UnsafeRawBufferPointer { + /// Construct a buffer representing a section's byte bounds. + /// + /// - Parameters: + /// - sectionBegin: The address of the first byte of the section. + /// - sectionEnd: The address of the first byte _after_ the section. + @_disfavoredOverload fileprivate init(sectionBegin: UnsafeRawPointer, sectionEnd: UnsafeRawPointer) { + self.init(start: sectionBegin, count: sectionEnd - sectionBegin) + } +} + /// The common implementation of ``SectionBounds/all(_:)`` for platforms that do /// not support dynamic linking. /// @@ -313,10 +362,15 @@ 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) +private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence { + let buffer = switch kind { + case .testContent: + UnsafeRawBufferPointer(sectionBegin: &_testContentSectionBegin, sectionEnd: &_testContentSectionEnd) +#if !SWT_NO_LEGACY_TEST_DISCOVERY + case .typeMetadata: + UnsafeRawBufferPointer(sectionBegin: &_typeMetadataSectionBegin, sectionEnd: &_typeMetadataSectionEnd) +#endif + } 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..ce7f5a196 100644 --- a/Sources/_TestingInternals/Discovery.cpp +++ b/Sources/_TestingInternals/Discovery.cpp @@ -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..769b34c61 100644 --- a/Sources/_TestingInternals/include/Discovery.h +++ b/Sources/_TestingInternals/include/Discovery.h @@ -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. From d44ccf89e5581d864247ff143173522422f94884 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 17 Mar 2025 10:05:23 -0400 Subject: [PATCH 02/10] Revert "Rewrite static section bounds discovery in Swift." This reverts commit e107fa450d5dedf9fe357b519ed7144513585e97. --- Documentation/Porting.md | 39 +++++----- Sources/_TestDiscovery/SectionBounds.swift | 72 +++---------------- .../_TestDiscovery/TestContentRecord.swift | 3 +- Sources/_TestingInternals/Discovery.cpp | 44 ++++++++++++ Sources/_TestingInternals/include/Discovery.h | 15 ++++ 5 files changed, 88 insertions(+), 85 deletions(-) diff --git a/Documentation/Porting.md b/Documentation/Porting.md index 06ce93833..8b230ff22 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/_TestDiscovery/SectionBounds.swift -+++ b/Sources/_TestDiscovery/SectionBounds.swift +--- a/Sources/Testing/Discovery+Platform.swift ++++ b/Sources/Testing/Discovery+Platform.swift // ... +#elseif os(Classic) @@ -211,30 +211,29 @@ 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 `SectionBounds.swift`: +`CompilerSettings.cmake`, then define the symbols `testContentSectionBegin`, +`testContentSectionEnd`, `typeMetadataSectionBegin`, and +`typeMetadataSectionEnd` in `Discovery.cpp`. ```diff ---- a/Sources/_TestDiscovery/SectionBounds.swift -+++ b/Sources/_TestDiscovery/SectionBounds.swift +diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp // ... -+#elseif os(Classic) -+@_silgen_name(raw: "...") private nonisolated(unsafe) var _testContentSectionBegin: CChar -+@_silgen_name(raw: "...") private nonisolated(unsafe) var _testContentSectionEnd: CChar -+#if !SWT_NO_LEGACY_TEST_DISCOVERY -+@_silgen_name(raw: "...") private nonisolated(unsafe) var _typeMetadataSectionBegin: CChar -+@_silgen_name(raw: "...") private nonisolated(unsafe) var _typeMetadataSectionEnd: CChar ++#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__("..."); +#endif #else - #warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)") - private nonisolated(unsafe) var _testContentSectionBegin: Void - private nonisolated(unsafe) var _testContentSectionEnd: Void - #if !SWT_NO_LEGACY_TEST_DISCOVERY - private nonisolated(unsafe) var _typeMetadataSectionBegin: Void - private nonisolated(unsafe) var _typeMetadataSectionEnd: Void + #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 - // ... ``` These symbols must have unique addresses corresponding to the first byte of the diff --git a/Sources/_TestDiscovery/SectionBounds.swift b/Sources/_TestDiscovery/SectionBounds.swift index 9bc37a014..212edbfbf 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: Equatable, Hashable, CaseIterable { + enum Kind: Int, Equatable, Hashable, CaseIterable { /// The test content metadata section. case testContent @@ -146,7 +146,7 @@ private let _startCollectingSectionBounds: Void = { /// /// - Returns: An array of structures describing the bounds of all known test /// content sections in the current process. -private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence { +private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] { _startCollectingSectionBounds return _sectionBounds.withUnsafeMutablePointers { sectionBounds, lock in pthread_mutex_lock(lock) @@ -169,7 +169,7 @@ private import SwiftShims // For MetadataSections /// /// - Returns: An array of structures describing the bounds of all known test /// content sections in the current process. -private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence { +private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] { struct Context { var kind: SectionBounds.Kind var result = [SectionBounds]() @@ -297,63 +297,14 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence
some Sequence { +private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] { #warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)") - return EmptyCollection() + return [] } #endif #else // MARK: - Statically-linked implementation -/// The bounds of the statically-linked sections of interest to the testing -/// library. -/// -/// We use `@_silgen_name(raw:)` to reference these linker-defined symbols. -/// Then, to get their addresses, we must use `&` (`withPointer(to:)` is not -/// guaranteed to avoid a copy in this context.) Hence, they must also be -/// declared `var` rather than `let`, but they should be treated as read-only. -#if SWT_TARGET_OS_APPLE -@_silgen_name(raw: "section$start$__DATA_CONST$__swift5_tests") private nonisolated(unsafe) var _testContentSectionBegin: CChar -@_silgen_name(raw: "section$end$__DATA_CONST$__swift5_tests") private nonisolated(unsafe) var _testContentSectionEnd: CChar -#if !SWT_NO_LEGACY_TEST_DISCOVERY -@_silgen_name(raw: "section$start$__TEXT$__swift5_types") private nonisolated(unsafe) var _typeMetadataSectionBegin: CChar -@_silgen_name(raw: "section$end$__TEXT$__swift5_types") private nonisolated(unsafe) var _typeMetadataSectionEnd: CChar -#endif -#elseif os(WASI) -@_silgen_name(raw: "__start_swift5_tests") private nonisolated(unsafe) var _testContentSectionBegin: CChar -@_silgen_name(raw: "__stop_swift5_tests") private nonisolated(unsafe) var _testContentSectionEnd: CChar -#if !SWT_NO_LEGACY_TEST_DISCOVERY -@_silgen_name(raw: "__start_swift5_type_metadata") private nonisolated(unsafe) var _typeMetadataSectionBegin: CChar -@_silgen_name(raw: "__stop_swift5_type_metadata") private nonisolated(unsafe) var _typeMetadataSectionEnd: CChar -#endif -#else -#warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)") -private nonisolated(unsafe) var _testContentSectionBegin: Void -private nonisolated(unsafe) var _testContentSectionEnd: Void -#if !SWT_NO_LEGACY_TEST_DISCOVERY -private nonisolated(unsafe) var _typeMetadataSectionBegin: Void -private nonisolated(unsafe) var _typeMetadataSectionEnd: Void -#endif - -extension UnsafeRawBufferPointer { - /// Construct an empty buffer. - fileprivate init(_: Void = (), sectionBegin _: UnsafeRawPointer, sectionEnd _: UnsafeRawPointer) { - self.init(start: nil, count: 0) - } -} -#endif - -extension UnsafeRawBufferPointer { - /// Construct a buffer representing a section's byte bounds. - /// - /// - Parameters: - /// - sectionBegin: The address of the first byte of the section. - /// - sectionEnd: The address of the first byte _after_ the section. - @_disfavoredOverload fileprivate init(sectionBegin: UnsafeRawPointer, sectionEnd: UnsafeRawPointer) { - self.init(start: sectionBegin, count: sectionEnd - sectionBegin) - } -} - /// The common implementation of ``SectionBounds/all(_:)`` for platforms that do /// not support dynamic linking. /// @@ -362,15 +313,10 @@ extension UnsafeRawBufferPointer { /// /// - 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) -> some Sequence { - let buffer = switch kind { - case .testContent: - UnsafeRawBufferPointer(sectionBegin: &_testContentSectionBegin, sectionEnd: &_testContentSectionEnd) -#if !SWT_NO_LEGACY_TEST_DISCOVERY - case .typeMetadata: - UnsafeRawBufferPointer(sectionBegin: &_typeMetadataSectionBegin, sectionEnd: &_typeMetadataSectionEnd) -#endif - } +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 sb = SectionBounds(imageAddress: nil, buffer: buffer) return CollectionOfOne(sb) } diff --git a/Sources/_TestDiscovery/TestContentRecord.swift b/Sources/_TestDiscovery/TestContentRecord.swift index 25f46fa44..5235d9c64 100644 --- a/Sources/_TestDiscovery/TestContentRecord.swift +++ b/Sources/_TestDiscovery/TestContentRecord.swift @@ -276,8 +276,7 @@ extension DiscoverableAsTestContent where Self: ~Copyable { } let result = SectionBounds.all(.typeMetadata).lazy.flatMap { sb in - stride(from: 0, to: sb.buffer.count, by: SWTTypeMetadataRecordByteCount).lazy - .map { sb.buffer.baseAddress! + $0 } + stride(from: sb.buffer.baseAddress!, to: sb.buffer.baseAddress! + sb.buffer.count, by: SWTTypeMetadataRecordByteCount).lazy .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 ce7f5a196..ad898534f 100644 --- a/Sources/_TestingInternals/Discovery.cpp +++ b/Sources/_TestingInternals/Discovery.cpp @@ -10,11 +10,55 @@ #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 769b34c61..9bda12b93 100644 --- a/Sources/_TestingInternals/include/Discovery.h +++ b/Sources/_TestingInternals/include/Discovery.h @@ -30,6 +30,21 @@ 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. From 7a4f5dd0b9f79c48540a5d6928b6e694322d50e3 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 17 Mar 2025 10:06:37 -0400 Subject: [PATCH 03/10] Use `@_rawLayout` and `~Copyable` to ensure bounds variables don't move. --- Documentation/Porting.md | 52 +++++++++-------- Package.swift | 21 +++++-- Sources/_TestDiscovery/SectionBounds.swift | 58 +++++++++++++++++-- .../_TestDiscovery/TestContentRecord.swift | 3 +- Sources/_TestingInternals/Discovery.cpp | 44 +------------- Sources/_TestingInternals/include/Discovery.h | 17 +----- cmake/modules/shared/CompilerSettings.cmake | 8 ++- 7 files changed, 109 insertions(+), 94 deletions(-) diff --git a/Documentation/Porting.md b/Documentation/Porting.md index 8b230ff22..3d3f7b778 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) @@ -209,38 +209,44 @@ start with `"SWT_"`). ## Runtime test discovery with static linkage 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`. +use static linkage instead. To enable static linkage in Swift Testing, add your +platform to the `BuildSettingCondition.whenStaticallyLinked` definition in +`Package.swift` and to the `SWT_STATICALLY_LINKED_LIST` definition in +`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 let _testContentSectionBegin: _SectionBound ++@_silgen_name(raw: "...") private let _testContentSectionEnd: _SectionBound ++#if !SWT_NO_LEGACY_TEST_DISCOVERY ++@_silgen_name(raw: "...") private let _typeMetadataSectionBegin: _SectionBound ++@_silgen_name(raw: "...") private let _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; - #endif + #warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)") + private let _testContentSectionBegin = _SectionBound() + private var _testContentSectionEnd: _SectionBound { ... } + #if !SWT_NO_LEGACY_TEST_DISCOVERY + private let _typeMetadataSectionBegin = _SectionBound() + private var _typeMetadataSectionEnd: _SectionBound { ... } #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 +provide section bounds symbols for it, please reach out to us in the Swift +forums for advice. ## C++ stub implementations @@ -332,7 +338,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/Package.swift b/Package.swift index 3a66ef039..aa620ae3c 100644 --- a/Package.swift +++ b/Package.swift @@ -185,6 +185,18 @@ package.targets.append(contentsOf: [ ]) #endif +extension BuildSettingCondition { + /// A build setting condition matching all Apple platforms. + static var whenApple: Self { + .when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS]) + } + + /// A build setting condition matching platforms that use static linkage. + static var whenStaticallyLinked: Self { + .when(platforms: [.wasi]) + } +} + extension Array where Element == PackageDescription.SwiftSetting { /// Settings intended to be applied to every Swift target in this package. /// Analogous to project-level build settings in an Xcode project. @@ -208,17 +220,18 @@ extension Array where Element == PackageDescription.SwiftSetting { // (via CMake). Enabling it is dependent on acceptance of the @section // proposal via Swift Evolution. .enableExperimentalFeature("SymbolLinkageMarkers"), + .enableExperimentalFeature("RawLayout", .whenStaticallyLinked), // When building as a package, the macro plugin always builds as an // executable rather than a library. .define("SWT_NO_LIBRARY_MACRO_PLUGINS"), - .define("SWT_TARGET_OS_APPLE", .when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS])), + .define("SWT_TARGET_OS_APPLE", .whenApple), .define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), .define("SWT_NO_PROCESS_SPAWNING", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), .define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .custom("freebsd"), .openbsd, .windows, .wasi, .android])), - .define("SWT_NO_DYNAMIC_LINKING", .when(platforms: [.wasi])), + .define("SWT_NO_DYNAMIC_LINKING", .whenStaticallyLinked), .define("SWT_NO_PIPES", .when(platforms: [.wasi])), ] @@ -256,7 +269,7 @@ extension Array where Element == PackageDescription.SwiftSetting { if buildingForDevelopment { var condition: BuildSettingCondition? if applePlatformsOnly { - condition = .when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS]) + condition = .whenApple } result.append(.unsafeFlags(["-enable-library-evolution"], condition)) } @@ -275,7 +288,7 @@ extension Array where Element == PackageDescription.CXXSetting { .define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), .define("SWT_NO_PROCESS_SPAWNING", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), .define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .custom("freebsd"), .openbsd, .windows, .wasi, .android])), - .define("SWT_NO_DYNAMIC_LINKING", .when(platforms: [.wasi])), + .define("SWT_NO_DYNAMIC_LINKING", .whenStaticallyLinked), .define("SWT_NO_PIPES", .when(platforms: [.wasi])), ] diff --git a/Sources/_TestDiscovery/SectionBounds.swift b/Sources/_TestDiscovery/SectionBounds.swift index 212edbfbf..71f4b0cc4 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 @@ -297,14 +297,57 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence
[SectionBounds] { +private func _sectionBounds(_ kind: SectionBounds.Kind) -> EmptyCollection { #warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)") - return [] + return EmptyCollection() } #endif #else // MARK: - Statically-linked implementation +/// A type representing the upper or lower bound of a metadata section. +/// +/// This type uses the experimental `@_rawLayout` attribute to ensure that +/// instances have fixed addresses. Use the `unsafeAddress` property to get the +/// address of an instance of this type. +/// +/// On platforms that use static linkage and have well-defined bounds symbols, +/// those symbols are imported into Swift below using `@_silgen_name`, another +/// experimental attribute. +@_rawLayout(like: CChar) private struct _SectionBound: @unchecked Sendable, ~Copyable { + static func ..<(lhs: borrowing Self, rhs: borrowing Self) -> UnsafeRawBufferPointer { + withUnsafePointer(to: lhs) { lhs in + withUnsafePointer(to: rhs) { rhs in + UnsafeRawBufferPointer(start: lhs, count: UnsafeRawPointer(rhs) - UnsafeRawPointer(lhs)) + } + } + } +} + +#if SWT_TARGET_OS_APPLE +@_silgen_name(raw: "section$start$__DATA_CONST$__swift5_tests") private let _testContentSectionBegin: _SectionBound +@_silgen_name(raw: "section$end$__DATA_CONST$__swift5_tests") private let _testContentSectionEnd: _SectionBound +#if !SWT_NO_LEGACY_TEST_DISCOVERY +@_silgen_name(raw: "section$start$__TEXT$__swift5_types") private let _typeMetadataSectionBegin: _SectionBound +@_silgen_name(raw: "section$end$__TEXT$__swift5_types") private let _typeMetadataSectionEnd: _SectionBound +#endif +#elseif os(WASI) +@_silgen_name(raw: "__start_swift5_tests") private let _testContentSectionBegin: _SectionBound +@_silgen_name(raw: "__stop_swift5_tests") private let _testContentSectionEnd: _SectionBound +#if !SWT_NO_LEGACY_TEST_DISCOVERY +@_silgen_name(raw: "__start_swift5_type_metadata") private let _typeMetadataSectionBegin: _SectionBound +@_silgen_name(raw: "__stop_swift5_type_metadata") private let _typeMetadataSectionEnd: _SectionBound +#endif +#else +#warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)") +private let _testContentSectionBegin = _SectionBound() +private var _testContentSectionEnd: _SectionBound { _read { yield _testContentSectionBegin } } +#if !SWT_NO_LEGACY_TEST_DISCOVERY +private let _typeMetadataSectionBegin = _SectionBound() +private var _typeMetadataSectionEnd: _SectionBound { _read { yield _typeMetadataSectionBegin } } +#endif +#endif + /// The common implementation of ``SectionBounds/all(_:)`` for platforms that do /// not support dynamic linking. /// @@ -314,9 +357,12 @@ 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 buffer = switch kind { + case .testContent: + _testContentSectionBegin ..< _testContentSectionEnd + case .typeMetadata: + _typeMetadataSectionBegin ..< _typeMetadataSectionEnd + } 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..d4365581f 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,54 +10,12 @@ #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 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. diff --git a/cmake/modules/shared/CompilerSettings.cmake b/cmake/modules/shared/CompilerSettings.cmake index f526caae3..36b84a841 100644 --- a/cmake/modules/shared/CompilerSettings.cmake +++ b/cmake/modules/shared/CompilerSettings.cmake @@ -36,6 +36,12 @@ if(NOT APPLE) add_compile_definitions("SWT_NO_SNAPSHOT_TYPES") endif() if(CMAKE_SYSTEM_NAME STREQUAL "WASI") - add_compile_definitions("SWT_NO_DYNAMIC_LINKING") add_compile_definitions("SWT_NO_PIPES") endif() + +set(SWT_STATICALLY_LINKED_LIST "WASI") +if(CMAKE_SYSTEM_NAME IN_LIST SWT_STATICALLY_LINKED_LIST) + add_compile_definitions("SWT_NO_DYNAMIC_LINKING") + add_compile_options( + "SHELL:$<$:-Xfrontend -enable-experimental-feature -Xfrontend RawLayout>") +endif() From 3409a6090f336a74f5a553eea3a0257bdd11952a Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 17 Mar 2025 12:07:55 -0400 Subject: [PATCH 04/10] Fix a typo --- Documentation/Porting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Porting.md b/Documentation/Porting.md index 3d3f7b778..0902240f9 100644 --- a/Documentation/Porting.md +++ b/Documentation/Porting.md @@ -245,8 +245,8 @@ linker documentation for your platform to determine what names to place in the `@_silgen_name` attribute applied to each. If your target platform statically links Swift Testing but the linker does not -provide section bounds symbols for it, please reach out to us in the Swift -forums for advice. +define section bounds symbols, please reach out to us in the Swift forums for +advice. ## C++ stub implementations From d4940a50e0bc18380f06759cba6e26297f4d9509 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 17 Mar 2025 12:32:46 -0400 Subject: [PATCH 05/10] Fix another typo --- Sources/_TestDiscovery/SectionBounds.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/_TestDiscovery/SectionBounds.swift b/Sources/_TestDiscovery/SectionBounds.swift index 71f4b0cc4..e0af35d71 100644 --- a/Sources/_TestDiscovery/SectionBounds.swift +++ b/Sources/_TestDiscovery/SectionBounds.swift @@ -308,8 +308,8 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> EmptyCollection Date: Tue, 18 Mar 2025 12:59:17 -0400 Subject: [PATCH 06/10] Clarify Linux/BSD/Windows static linkage behaviour --- Documentation/Porting.md | 11 +++++++---- Sources/_TestDiscovery/SectionBounds.swift | 19 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Documentation/Porting.md b/Documentation/Porting.md index 0902240f9..e2b9a4404 100644 --- a/Documentation/Porting.md +++ b/Documentation/Porting.md @@ -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 ``` diff --git a/Sources/_TestDiscovery/SectionBounds.swift b/Sources/_TestDiscovery/SectionBounds.swift index e0af35d71..6924b81ad 100644 --- a/Sources/_TestDiscovery/SectionBounds.swift +++ b/Sources/_TestDiscovery/SectionBounds.swift @@ -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
EmptyCollection { - #warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)") +#warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)") return EmptyCollection() } -#endif + #else // MARK: - Statically-linked implementation @@ -331,7 +336,7 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> EmptyCollection Date: Tue, 18 Mar 2025 14:15:44 -0400 Subject: [PATCH 07/10] Don't use rawLayout --- Sources/_TestDiscovery/SectionBounds.swift | 50 ++++++++++++---------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/Sources/_TestDiscovery/SectionBounds.swift b/Sources/_TestDiscovery/SectionBounds.swift index 6924b81ad..1b4cbaa2a 100644 --- a/Sources/_TestDiscovery/SectionBounds.swift +++ b/Sources/_TestDiscovery/SectionBounds.swift @@ -312,44 +312,47 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> EmptyCollection UnsafeRawBufferPointer { - withUnsafePointer(to: lhs) { lhs in - withUnsafePointer(to: rhs) { rhs in - UnsafeRawBufferPointer(start: lhs, count: UnsafeRawPointer(rhs) - UnsafeRawPointer(lhs)) +/// 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 let _testContentSectionBegin: _SectionBound -@_silgen_name(raw: "section$end$__DATA_CONST$__swift5_tests") private let _testContentSectionEnd: _SectionBound +@_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 let _typeMetadataSectionBegin: _SectionBound -@_silgen_name(raw: "section$end$__TEXT$__swift5_types") private let _typeMetadataSectionEnd: _SectionBound +@_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 let _testContentSectionBegin: _SectionBound -@_silgen_name(raw: "__stop_swift5_tests") private let _testContentSectionEnd: _SectionBound +@_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 let _typeMetadataSectionBegin: _SectionBound -@_silgen_name(raw: "__stop_swift5_type_metadata") private let _typeMetadataSectionEnd: _SectionBound +@_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 let _testContentSectionBegin = _SectionBound() -private var _testContentSectionEnd: _SectionBound { _read { yield _testContentSectionBegin } } +private nonisolated(unsafe) let _testContentSectionBegin = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 16) +private nonisolated(unsafe) let _testContentSectionEnd = _testContentSectionBegin #if !SWT_NO_LEGACY_TEST_DISCOVERY -private let _typeMetadataSectionBegin = _SectionBound() -private var _typeMetadataSectionEnd: _SectionBound { _read { yield _typeMetadataSectionBegin } } +private nonisolated(unsafe) let _typeMetadataSectionBegin = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 16) +private nonisolated(unsafe) let _typeMetadataSectionEnd = _typeMetadataSectionBegin #endif #endif @@ -362,12 +365,13 @@ private var _typeMetadataSectionEnd: _SectionBound { _read { yield _typeMetadata /// - 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 { - let buffer = switch kind { + 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) } From 2162f4bf747687f8f3e7df93ac751ef9ae8c2ac2 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 18 Mar 2025 14:21:35 -0400 Subject: [PATCH 08/10] Update porting doc (again) --- Documentation/Porting.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Documentation/Porting.md b/Documentation/Porting.md index e2b9a4404..af5308647 100644 --- a/Documentation/Porting.md +++ b/Documentation/Porting.md @@ -224,19 +224,20 @@ platform to the `BuildSettingCondition.whenStaticallyLinked` definition in +++ b/Sources/_TestDiscovery/SectionBounds.swift // ... +#elseif os(Classic) -+@_silgen_name(raw: "...") private let _testContentSectionBegin: _SectionBound -+@_silgen_name(raw: "...") private let _testContentSectionEnd: _SectionBound ++@_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 let _typeMetadataSectionBegin: _SectionBound -+@_silgen_name(raw: "...") private let _typeMetadataSectionEnd: _SectionBound ++@_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)") - private let _testContentSectionBegin = _SectionBound() - private var _testContentSectionEnd: _SectionBound { ... } + private nonisolated(unsafe) let _testContentSectionBegin = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 16) + private nonisolated(unsafe) let _testContentSectionEnd = _testContentSectionBegin #if !SWT_NO_LEGACY_TEST_DISCOVERY - private let _typeMetadataSectionBegin = _SectionBound() - private var _typeMetadataSectionEnd: _SectionBound { ... } + private nonisolated(unsafe) let _typeMetadataSectionBegin = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 16) + private nonisolated(unsafe) let _typeMetadataSectionEnd = _typeMetadataSectionBegin + #endif #endif // ... ``` From 570021e068436feba0ae69cb706c02165b160089 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 18 Mar 2025 15:35:17 -0400 Subject: [PATCH 09/10] Remove RawLayout feature flag --- Documentation/Porting.md | 5 ++--- Package.swift | 21 ++++----------------- cmake/modules/shared/CompilerSettings.cmake | 8 +------- 3 files changed, 7 insertions(+), 27 deletions(-) diff --git a/Documentation/Porting.md b/Documentation/Porting.md index af5308647..6e83e0eb0 100644 --- a/Documentation/Porting.md +++ b/Documentation/Porting.md @@ -212,9 +212,8 @@ start with `"SWT_"`). ## Runtime test discovery with static linkage If your platform does not support dynamic linking and loading, you will need to -use static linkage instead. To enable static linkage in Swift Testing, add your -platform to the `BuildSettingCondition.whenStaticallyLinked` definition in -`Package.swift` and to the `SWT_STATICALLY_LINKED_LIST` definition in +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 `SectionBounds.swift`: diff --git a/Package.swift b/Package.swift index aa620ae3c..3a66ef039 100644 --- a/Package.swift +++ b/Package.swift @@ -185,18 +185,6 @@ package.targets.append(contentsOf: [ ]) #endif -extension BuildSettingCondition { - /// A build setting condition matching all Apple platforms. - static var whenApple: Self { - .when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS]) - } - - /// A build setting condition matching platforms that use static linkage. - static var whenStaticallyLinked: Self { - .when(platforms: [.wasi]) - } -} - extension Array where Element == PackageDescription.SwiftSetting { /// Settings intended to be applied to every Swift target in this package. /// Analogous to project-level build settings in an Xcode project. @@ -220,18 +208,17 @@ extension Array where Element == PackageDescription.SwiftSetting { // (via CMake). Enabling it is dependent on acceptance of the @section // proposal via Swift Evolution. .enableExperimentalFeature("SymbolLinkageMarkers"), - .enableExperimentalFeature("RawLayout", .whenStaticallyLinked), // When building as a package, the macro plugin always builds as an // executable rather than a library. .define("SWT_NO_LIBRARY_MACRO_PLUGINS"), - .define("SWT_TARGET_OS_APPLE", .whenApple), + .define("SWT_TARGET_OS_APPLE", .when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS])), .define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), .define("SWT_NO_PROCESS_SPAWNING", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), .define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .custom("freebsd"), .openbsd, .windows, .wasi, .android])), - .define("SWT_NO_DYNAMIC_LINKING", .whenStaticallyLinked), + .define("SWT_NO_DYNAMIC_LINKING", .when(platforms: [.wasi])), .define("SWT_NO_PIPES", .when(platforms: [.wasi])), ] @@ -269,7 +256,7 @@ extension Array where Element == PackageDescription.SwiftSetting { if buildingForDevelopment { var condition: BuildSettingCondition? if applePlatformsOnly { - condition = .whenApple + condition = .when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS]) } result.append(.unsafeFlags(["-enable-library-evolution"], condition)) } @@ -288,7 +275,7 @@ extension Array where Element == PackageDescription.CXXSetting { .define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), .define("SWT_NO_PROCESS_SPAWNING", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])), .define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .custom("freebsd"), .openbsd, .windows, .wasi, .android])), - .define("SWT_NO_DYNAMIC_LINKING", .whenStaticallyLinked), + .define("SWT_NO_DYNAMIC_LINKING", .when(platforms: [.wasi])), .define("SWT_NO_PIPES", .when(platforms: [.wasi])), ] diff --git a/cmake/modules/shared/CompilerSettings.cmake b/cmake/modules/shared/CompilerSettings.cmake index 36b84a841..f526caae3 100644 --- a/cmake/modules/shared/CompilerSettings.cmake +++ b/cmake/modules/shared/CompilerSettings.cmake @@ -36,12 +36,6 @@ if(NOT APPLE) add_compile_definitions("SWT_NO_SNAPSHOT_TYPES") endif() if(CMAKE_SYSTEM_NAME STREQUAL "WASI") - add_compile_definitions("SWT_NO_PIPES") -endif() - -set(SWT_STATICALLY_LINKED_LIST "WASI") -if(CMAKE_SYSTEM_NAME IN_LIST SWT_STATICALLY_LINKED_LIST) add_compile_definitions("SWT_NO_DYNAMIC_LINKING") - add_compile_options( - "SHELL:$<$:-Xfrontend -enable-experimental-feature -Xfrontend RawLayout>") + add_compile_definitions("SWT_NO_PIPES") endif() From 09db7af5e80d1d4310b16d7276fee8fd2094fef9 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 18 Mar 2025 15:38:07 -0400 Subject: [PATCH 10/10] Remove extra #if --- Sources/_TestingInternals/Discovery.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp index d4365581f..1e70a038d 100644 --- a/Sources/_TestingInternals/Discovery.cpp +++ b/Sources/_TestingInternals/Discovery.cpp @@ -14,9 +14,7 @@ #include #include #include -#endif -#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY) #pragma mark - Swift ABI #if defined(__PTRAUTH_INTRINSICS__)