Skip to content

Commit 52aa355

Browse files
authored
Remove C++-based section discovery logic. (#902)
This PR removes the C++ _section_ discovery logic that's used to look up Swift's type metadata sections. Instead, we reuse the new logic used for test content sections (written in Swift.) I did not attempt to translate to Swift the logic to extract Swift types from Swift's metadata records. This logic relies pretty heavily on precise C++ data structure layouts and uses pointer authentication as well as relative pointers. Swift does not guarantee the same structure layout as C++, nor does it provide API for pointer authentication, nor do value types in Swift have stable addresses that can be used to compute addresses from relative offsets. Resolves #764. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 801d5ea commit 52aa355

File tree

8 files changed

+281
-453
lines changed

8 files changed

+281
-453
lines changed

Documentation/Porting.md

Lines changed: 83 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ platform-specific attention.
6666
> These errors are produced when the configuration you're trying to build has
6767
> conflicting requirements (for example, attempting to enable support for pipes
6868
> without also enabling support for file I/O.) You should be able to resolve
69-
> these issues by updating Package.swift and/or CompilerSettings.cmake.
69+
> these issues by updating `Package.swift` and/or `CompilerSettings.cmake`.
7070
7171
Most platform dependencies can be resolved through the use of platform-specific
7272
API. For example, Swift Testing uses the C11 standard [`timespec`](https://en.cppreference.com/w/c/chrono/timespec)
@@ -123,69 +123,110 @@ Once the header is included, we can call `GetDateTime()` from `Clock.swift`:
123123
## Runtime test discovery
124124

125125
When porting to a new platform, you may need to provide a new implementation for
126-
`enumerateTypeMetadataSections()` in `Discovery.cpp`. Test discovery is
127-
dependent on Swift metadata discovery which is an inherently platform-specific
128-
operation.
129-
130-
_Most_ platforms will be able to reuse the implementation used by Linux and
131-
Windows that calls an internal Swift runtime function to enumerate available
132-
metadata. If you are porting Swift Testing to Classic, this function won't be
133-
available, so you'll need to write a custom implementation instead. Assuming
134-
that the Swift compiler emits section information into the resource fork on
135-
Classic, you could use the [Resource Manager](https://developer.apple.com/library/archive/documentation/mac/pdf/MoreMacintoshToolbox.pdf)
126+
`_sectionBounds(_:)` in `Discovery+Platform.swift`. Test discovery is dependent
127+
on Swift metadata discovery which is an inherently platform-specific operation.
128+
129+
_Most_ platforms in use today use the ELF image format and will be able to reuse
130+
the implementation used by Linux.
131+
132+
Classic does not use the ELF image format, so you'll need to write a custom
133+
implementation of `_sectionBounds(_:)` instead. Assuming that the Swift compiler
134+
emits section information into the resource fork on Classic, you would use the
135+
[Resource Manager](https://developer.apple.com/library/archive/documentation/mac/pdf/MoreMacintoshToolbox.pdf)
136136
to load that information:
137137

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

142142
// ...
143-
+#elif defined(macintosh)
144-
+template <typename SectionEnumerator>
145-
+static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
146-
+ ResFileRefNum refNum;
147-
+ if (noErr == GetTopResourceFile(&refNum)) {
148-
+ ResFileRefNum oldRefNum = refNum;
149-
+ do {
150-
+ UseResFile(refNum);
151-
+ Handle handle = Get1NamedResource('swft', "\p__swift5_types");
152-
+ if (handle && *handle) {
153-
+ auto imageAddress = reinterpret_cast<const void *>(static_cast<uintptr_t>(refNum));
154-
+ SWTSectionBounds sb = { imageAddress, *handle, GetHandleSize(handle) };
155-
+ bool stop = false;
156-
+ body(sb, &stop);
157-
+ if (stop) {
158-
+ break;
159-
+ }
160-
+ }
161-
+ } while (noErr == GetNextResourceFile(refNum, &refNum));
162-
+ UseResFile(oldRefNum);
143+
+#elseif os(Classic)
144+
+private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
145+
+ let resourceName: Str255 = switch kind {
146+
+ case .testContent:
147+
+ "__swift5_tests"
148+
+ case .typeMetadata:
149+
+ "__swift5_types"
150+
+ }
151+
+
152+
+ let oldRefNum = CurResFile()
153+
+ defer {
154+
+ UseResFile(oldRefNum)
155+
+ }
156+
+
157+
+ var refNum = ResFileRefNum(0)
158+
+ guard noErr == GetTopResourceFile(&refNum) else {
159+
+ return []
163160
+ }
161+
+
162+
+ var result = [SectionBounds]()
163+
+ repeat {
164+
+ UseResFile(refNum)
165+
+ guard let handle = Get1NamedResource(ResType("swft"), resourceName) else {
166+
+ continue
167+
+ }
168+
+ let sb = SectionBounds(
169+
+ imageAddress: UnsafeRawPointer(bitPattern: UInt(refNum)),
170+
+ start: handle.pointee!,
171+
+ size: GetHandleSize(handle)
172+
+ )
173+
+ result.append(sb)
174+
+ } while noErr == GetNextResourceFile(refNum, &refNum))
175+
+ return result
164176
+}
165177
#else
166-
#warning Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)
167-
template <typename SectionEnumerator>
168-
static void enumerateTypeMetadataSections(const SectionEnumerator& body) {}
178+
private func _sectionBounds(_ kind: SectionBounds.Kind) -> [SectionBounds] {
179+
#warning("Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)")
180+
return []
181+
}
169182
#endif
170183
```
171184

185+
You will also need to update the `makeTestContentRecordDecl()` function in the
186+
`TestingMacros` target to emit the correct `@_section` attribute for your
187+
platform. If your platform uses the ELF image format and supports the
188+
`dl_iterate_phdr()` function, add it to the existing `#elseif os(Linux) || ...`
189+
case. Otherwise, add a new case for your platform:
190+
191+
```diff
192+
--- a/Sources/TestingMacros/Support/TestContentGeneration.swift
193+
+++ b/Sources/TestingMacros/Support/TestContentGeneration.swift
194+
// ...
195+
+ #elseif os(Classic)
196+
+ @_section(".rsrc,swft,__swift5_tests")
197+
#else
198+
@__testing(warning: "Platform-specific implementation missing: test content section name unavailable")
199+
#endif
200+
```
201+
202+
Keep in mind that this code is emitted by the `@Test` and `@Suite` macros
203+
directly into test authors' test targets, so you will not be able to use
204+
compiler conditionals defined in the Swift Testing package (including those that
205+
start with `"SWT_"`).
206+
172207
## Runtime test discovery with static linkage
173208

174209
If your platform does not support dynamic linking and loading, you will need to
175210
use static linkage instead. Define the `"SWT_NO_DYNAMIC_LINKING"` compiler
176-
conditional for your platform in both Package.swift and CompilerSettings.cmake,
177-
then define the `sectionBegin` and `sectionEnd` symbols in Discovery.cpp:
211+
conditional for your platform in both `Package.swift` and
212+
`CompilerSettings.cmake`, then define the symbols `testContentSectionBegin`,
213+
`testContentSectionEnd`, `typeMetadataSectionBegin`, and
214+
`typeMetadataSectionEnd` in `Discovery.cpp`.
178215

179216
```diff
180217
diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp
181218
// ...
182219
+#elif defined(macintosh)
183-
+extern "C" const char sectionBegin __asm__("...");
184-
+extern "C" const char sectionEnd __asm__("...");
220+
+extern "C" const char testContentSectionBegin __asm__("...");
221+
+extern "C" const char testContentSectionEnd __asm__("...");
222+
+extern "C" const char typeMetadataSectionBegin __asm__("...");
223+
+extern "C" const char typeMetadataSectionEnd __asm__("...");
185224
#else
186225
#warning Platform-specific implementation missing: Runtime test discovery unavailable (static)
187-
static const char sectionBegin = 0;
188-
static const char& sectionEnd = sectionBegin;
226+
static const char testContentSectionBegin = 0;
227+
static const char& testContentSectionEnd = testContentSectionBegin;
228+
static const char typeMetadataSectionBegin = 0;
229+
static const char& typeMetadataSectionEnd = testContentSectionBegin;
189230
#endif
190231
```
191232

@@ -195,27 +236,6 @@ respectively. Their linker-level names will be platform-dependent: refer to the
195236
linker documentation for your platform to determine what names to place in the
196237
`__asm__` attribute applied to each.
197238

198-
If you can't use `__asm__` on your platform, you can declare these symbols as
199-
C++ references to linker-defined symbols:
200-
201-
```diff
202-
diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp
203-
// ...
204-
+#elif defined(macintosh)
205-
+extern "C" const char __linker_defined_begin_symbol;
206-
+extern "C" const char __linker_defined_end_symbol;
207-
+static const auto& sectionBegin = __linker_defined_begin_symbol;
208-
+static const auto& sectionEnd = __linker_defined_end_symbol;
209-
#else
210-
#warning Platform-specific implementation missing: Runtime test discovery unavailable (static)
211-
static const char sectionBegin = 0;
212-
static const char& sectionEnd = sectionBegin;
213-
#endif
214-
```
215-
216-
The names of `__linker_defined_begin_symbol` and `__linker_defined_end_symbol`
217-
in this example are, as with the shorter implementation, platform-dependent.
218-
219239
## C++ stub implementations
220240

221241
Some symbols defined in C and C++ headers, especially "complex" macros, cannot

0 commit comments

Comments
 (0)