Skip to content

Commit 7a4f5dd

Browse files
committed
Use @_rawLayout and ~Copyable to ensure bounds variables don't move.
1 parent d44ccf8 commit 7a4f5dd

File tree

7 files changed

+109
-94
lines changed

7 files changed

+109
-94
lines changed

Documentation/Porting.md

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

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

142142
// ...
143143
+#elseif os(Classic)
@@ -209,38 +209,44 @@ start with `"SWT_"`).
209209
## Runtime test discovery with static linkage
210210

211211
If your platform does not support dynamic linking and loading, you will need to
212-
use static linkage instead. Define the `"SWT_NO_DYNAMIC_LINKING"` compiler
213-
conditional for your platform in both `Package.swift` and
214-
`CompilerSettings.cmake`, then define the symbols `testContentSectionBegin`,
215-
`testContentSectionEnd`, `typeMetadataSectionBegin`, and
216-
`typeMetadataSectionEnd` in `Discovery.cpp`.
212+
use static linkage instead. To enable static linkage in Swift Testing, add your
213+
platform to the `BuildSettingCondition.whenStaticallyLinked` definition in
214+
`Package.swift` and to the `SWT_STATICALLY_LINKED_LIST` definition in
215+
`CompilerSettings.cmake`, then define the symbols `_testContentSectionBegin`,
216+
`_testContentSectionEnd`, `_typeMetadataSectionBegin`, and
217+
`_typeMetadataSectionEnd` in `SectionBounds.swift`:
217218

218219
```diff
219-
diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp
220+
--- a/Sources/_TestDiscovery/SectionBounds.swift
221+
+++ b/Sources/_TestDiscovery/SectionBounds.swift
220222
// ...
221-
+#elif defined(macintosh)
222-
+extern "C" const char testContentSectionBegin __asm__("...");
223-
+extern "C" const char testContentSectionEnd __asm__("...");
224-
+#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
225-
+extern "C" const char typeMetadataSectionBegin __asm__("...");
226-
+extern "C" const char typeMetadataSectionEnd __asm__("...");
223+
+#elseif os(Classic)
224+
+@_silgen_name(raw: "...") private let _testContentSectionBegin: _SectionBound
225+
+@_silgen_name(raw: "...") private let _testContentSectionEnd: _SectionBound
226+
+#if !SWT_NO_LEGACY_TEST_DISCOVERY
227+
+@_silgen_name(raw: "...") private let _typeMetadataSectionBegin: _SectionBound
228+
+@_silgen_name(raw: "...") private let _typeMetadataSectionEnd: _SectionBound
227229
+#endif
228230
#else
229-
#warning Platform-specific implementation missing: Runtime test discovery unavailable (static)
230-
static const char testContentSectionBegin = 0;
231-
static const char& testContentSectionEnd = testContentSectionBegin;
232-
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
233-
static const char typeMetadataSectionBegin = 0;
234-
static const char& typeMetadataSectionEnd = typeMetadataSectionBegin;
235-
#endif
231+
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)")
232+
private let _testContentSectionBegin = _SectionBound()
233+
private var _testContentSectionEnd: _SectionBound { ... }
234+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
235+
private let _typeMetadataSectionBegin = _SectionBound()
236+
private var _typeMetadataSectionEnd: _SectionBound { ... }
236237
#endif
238+
// ...
237239
```
238240

239241
These symbols must have unique addresses corresponding to the first byte of the
240242
test content section and the first byte _after_ the test content section,
241243
respectively. Their linker-level names will be platform-dependent: refer to the
242244
linker documentation for your platform to determine what names to place in the
243-
`__asm__` attribute applied to each.
245+
`@_silgen_name` attribute applied to each.
246+
247+
If your target platform statically links Swift Testing but the linker does not
248+
provide section bounds symbols for it, please reach out to us in the Swift
249+
forums for advice.
244250

245251
## C++ stub implementations
246252

@@ -332,7 +338,7 @@ to include the necessary linker flags.
332338
## Adding CI jobs for the new platform
333339

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

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

Package.swift

+17-4
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,18 @@ package.targets.append(contentsOf: [
185185
])
186186
#endif
187187

188+
extension BuildSettingCondition {
189+
/// A build setting condition matching all Apple platforms.
190+
static var whenApple: Self {
191+
.when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS])
192+
}
193+
194+
/// A build setting condition matching platforms that use static linkage.
195+
static var whenStaticallyLinked: Self {
196+
.when(platforms: [.wasi])
197+
}
198+
}
199+
188200
extension Array where Element == PackageDescription.SwiftSetting {
189201
/// Settings intended to be applied to every Swift target in this package.
190202
/// Analogous to project-level build settings in an Xcode project.
@@ -208,17 +220,18 @@ extension Array where Element == PackageDescription.SwiftSetting {
208220
// (via CMake). Enabling it is dependent on acceptance of the @section
209221
// proposal via Swift Evolution.
210222
.enableExperimentalFeature("SymbolLinkageMarkers"),
223+
.enableExperimentalFeature("RawLayout", .whenStaticallyLinked),
211224

212225
// When building as a package, the macro plugin always builds as an
213226
// executable rather than a library.
214227
.define("SWT_NO_LIBRARY_MACRO_PLUGINS"),
215228

216-
.define("SWT_TARGET_OS_APPLE", .when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS])),
229+
.define("SWT_TARGET_OS_APPLE", .whenApple),
217230

218231
.define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])),
219232
.define("SWT_NO_PROCESS_SPAWNING", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])),
220233
.define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .custom("freebsd"), .openbsd, .windows, .wasi, .android])),
221-
.define("SWT_NO_DYNAMIC_LINKING", .when(platforms: [.wasi])),
234+
.define("SWT_NO_DYNAMIC_LINKING", .whenStaticallyLinked),
222235
.define("SWT_NO_PIPES", .when(platforms: [.wasi])),
223236
]
224237

@@ -256,7 +269,7 @@ extension Array where Element == PackageDescription.SwiftSetting {
256269
if buildingForDevelopment {
257270
var condition: BuildSettingCondition?
258271
if applePlatformsOnly {
259-
condition = .when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS])
272+
condition = .whenApple
260273
}
261274
result.append(.unsafeFlags(["-enable-library-evolution"], condition))
262275
}
@@ -275,7 +288,7 @@ extension Array where Element == PackageDescription.CXXSetting {
275288
.define("SWT_NO_EXIT_TESTS", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])),
276289
.define("SWT_NO_PROCESS_SPAWNING", .when(platforms: [.iOS, .watchOS, .tvOS, .visionOS, .wasi, .android])),
277290
.define("SWT_NO_SNAPSHOT_TYPES", .when(platforms: [.linux, .custom("freebsd"), .openbsd, .windows, .wasi, .android])),
278-
.define("SWT_NO_DYNAMIC_LINKING", .when(platforms: [.wasi])),
291+
.define("SWT_NO_DYNAMIC_LINKING", .whenStaticallyLinked),
279292
.define("SWT_NO_PIPES", .when(platforms: [.wasi])),
280293
]
281294

Sources/_TestDiscovery/SectionBounds.swift

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

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

@@ -297,14 +297,57 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> some Sequence<Section
297297
/// - kind: Ignored.
298298
///
299299
/// - Returns: The empty array.
300-
private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
300+
private func _sectionBounds(_ kind: SectionBounds.Kind) -> EmptyCollection<SectionBounds> {
301301
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)")
302-
return []
302+
return EmptyCollection()
303303
}
304304
#endif
305305
#else
306306
// MARK: - Statically-linked implementation
307307

308+
/// A type representing the upper or lower bound of a metadata section.
309+
///
310+
/// This type uses the experimental `@_rawLayout` attribute to ensure that
311+
/// instances have fixed addresses. Use the `unsafeAddress` property to get the
312+
/// address of an instance of this type.
313+
///
314+
/// On platforms that use static linkage and have well-defined bounds symbols,
315+
/// those symbols are imported into Swift below using `@_silgen_name`, another
316+
/// experimental attribute.
317+
@_rawLayout(like: CChar) private struct _SectionBound: @unchecked Sendable, ~Copyable {
318+
static func ..<(lhs: borrowing Self, rhs: borrowing Self) -> UnsafeRawBufferPointer {
319+
withUnsafePointer(to: lhs) { lhs in
320+
withUnsafePointer(to: rhs) { rhs in
321+
UnsafeRawBufferPointer(start: lhs, count: UnsafeRawPointer(rhs) - UnsafeRawPointer(lhs))
322+
}
323+
}
324+
}
325+
}
326+
327+
#if SWT_TARGET_OS_APPLE
328+
@_silgen_name(raw: "section$start$__DATA_CONST$__swift5_tests") private let _testContentSectionBegin: _SectionBound
329+
@_silgen_name(raw: "section$end$__DATA_CONST$__swift5_tests") private let _testContentSectionEnd: _SectionBound
330+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
331+
@_silgen_name(raw: "section$start$__TEXT$__swift5_types") private let _typeMetadataSectionBegin: _SectionBound
332+
@_silgen_name(raw: "section$end$__TEXT$__swift5_types") private let _typeMetadataSectionEnd: _SectionBound
333+
#endif
334+
#elseif os(WASI)
335+
@_silgen_name(raw: "__start_swift5_tests") private let _testContentSectionBegin: _SectionBound
336+
@_silgen_name(raw: "__stop_swift5_tests") private let _testContentSectionEnd: _SectionBound
337+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
338+
@_silgen_name(raw: "__start_swift5_type_metadata") private let _typeMetadataSectionBegin: _SectionBound
339+
@_silgen_name(raw: "__stop_swift5_type_metadata") private let _typeMetadataSectionEnd: _SectionBound
340+
#endif
341+
#else
342+
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (static)")
343+
private let _testContentSectionBegin = _SectionBound()
344+
private var _testContentSectionEnd: _SectionBound { _read { yield _testContentSectionBegin } }
345+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
346+
private let _typeMetadataSectionBegin = _SectionBound()
347+
private var _typeMetadataSectionEnd: _SectionBound { _read { yield _typeMetadataSectionBegin } }
348+
#endif
349+
#endif
350+
308351
/// The common implementation of ``SectionBounds/all(_:)`` for platforms that do
309352
/// not support dynamic linking.
310353
///
@@ -314,9 +357,12 @@ private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
314357
/// - Returns: A structure describing the bounds of the type metadata section
315358
/// contained in the same image as the testing library itself.
316359
private func _sectionBounds(_ kind: SectionBounds.Kind) -> CollectionOfOne<SectionBounds> {
317-
var (baseAddress, count): (UnsafeRawPointer?, Int) = (nil, 0)
318-
swt_getStaticallyLinkedSectionBounds(kind.rawValue, &baseAddress, &count)
319-
let buffer = UnsafeRawBufferPointer(start: baseAddress, count: count)
360+
let buffer = switch kind {
361+
case .testContent:
362+
_testContentSectionBegin ..< _testContentSectionEnd
363+
case .typeMetadata:
364+
_typeMetadataSectionBegin ..< _typeMetadataSectionEnd
365+
}
320366
let sb = SectionBounds(imageAddress: nil, buffer: buffer)
321367
return CollectionOfOne(sb)
322368
}

Sources/_TestDiscovery/TestContentRecord.swift

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

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

Sources/_TestingInternals/Discovery.cpp

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

1111
#include "Discovery.h"
1212

13-
#include <algorithm>
1413
#if !defined(SWT_NO_LEGACY_TEST_DISCOVERY)
1514
#include <cstdint>
1615
#include <cstring>
1716
#include <type_traits>
1817
#endif
1918

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

Sources/_TestingInternals/include/Discovery.h

+1-16
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// This source file is part of the Swift.org open source project
33
//
4-
// Copyright (c) 2023 Apple Inc. and the Swift project authors
4+
// Copyright (c) 2023–2025 Apple Inc. and the Swift project authors
55
// Licensed under Apache License v2.0 with Runtime Library Exception
66
//
77
// See https://swift.org/LICENSE.txt for license information
@@ -30,21 +30,6 @@ SWT_IMPORT_FROM_STDLIB void swift_enumerateAllMetadataSections(
3030
);
3131
#endif
3232

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

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

cmake/modules/shared/CompilerSettings.cmake

+7-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ if(NOT APPLE)
3636
add_compile_definitions("SWT_NO_SNAPSHOT_TYPES")
3737
endif()
3838
if(CMAKE_SYSTEM_NAME STREQUAL "WASI")
39-
add_compile_definitions("SWT_NO_DYNAMIC_LINKING")
4039
add_compile_definitions("SWT_NO_PIPES")
4140
endif()
41+
42+
set(SWT_STATICALLY_LINKED_LIST "WASI")
43+
if(CMAKE_SYSTEM_NAME IN_LIST SWT_STATICALLY_LINKED_LIST)
44+
add_compile_definitions("SWT_NO_DYNAMIC_LINKING")
45+
add_compile_options(
46+
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -enable-experimental-feature -Xfrontend RawLayout>")
47+
endif()

0 commit comments

Comments
 (0)