Skip to content

Commit 597d709

Browse files
authored
Merge pull request #41621 from grynspan/jgrynspan/copy-metadata-sections
Add exported symbol on non-Apple platforms to enumerate MetadataSection structures at runtime.
2 parents 0d4e041 + 8451822 commit 597d709

File tree

7 files changed

+188
-82
lines changed

7 files changed

+188
-82
lines changed

include/swift/Runtime/Concurrent.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -520,10 +520,14 @@ template <class ElemTy> struct ConcurrentReadableArray {
520520
// These are marked as ref-qualified (the &) to make sure they can't be
521521
// called on temporaries, since the temporary would be destroyed before the
522522
// return value can be used, making it invalid.
523-
const ElemTy *begin() & { return Start; }
524-
const ElemTy *end() & { return Start + Count; }
523+
const ElemTy *begin() const& { return Start; }
524+
const ElemTy *end() const& { return Start + Count; }
525+
const ElemTy& operator [](size_t index) const& {
526+
assert(index < count() && "out-of-bounds access to snapshot element");
527+
return Start[index];
528+
}
525529

526-
size_t count() { return Count; }
530+
size_t count() const { return Count; }
527531
};
528532

529533
// This type cannot be safely copied or moved.

stdlib/public/SwiftShims/MetadataSections.h

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
#ifndef SWIFT_STDLIB_SHIMS_METADATASECTIONS_H
2222
#define SWIFT_STDLIB_SHIMS_METADATASECTIONS_H
2323

24+
#if defined(__cplusplus) && !defined(__swift__)
25+
#include <atomic>
26+
#endif
27+
2428
#include "SwiftStddef.h"
2529
#include "SwiftStdint.h"
2630

@@ -36,7 +40,12 @@ typedef struct MetadataSectionRange {
3640
} MetadataSectionRange;
3741

3842

39-
/// Identifies the address space ranges for the Swift metadata required by the Swift runtime.
43+
/// Identifies the address space ranges for the Swift metadata required by the
44+
/// Swift runtime.
45+
///
46+
/// \warning If you change the size of this structure by adding fields, it is an
47+
/// ABI-breaking change on platforms that use it. Make sure to increment
48+
/// \c CurrentSectionMetadataVersion if you do.
4049
struct MetadataSections {
4150
__swift_uintptr_t version;
4251

@@ -56,20 +65,23 @@ struct MetadataSections {
5665
/// loader (i.e. no equivalent of \c __dso_handle or \c __ImageBase), this
5766
/// field is ignored and should be set to \c nullptr.
5867
///
68+
/// \bug When imported into Swift, this field is not atomic.
69+
///
5970
/// \sa swift_addNewDSOImage()
71+
#if defined(__swift__) || defined(__STDC_NO_ATOMICS__)
6072
const void *baseAddress;
73+
#elif defined(__cplusplus)
74+
std::atomic<const void *> baseAddress;
75+
#else
76+
_Atomic(const void *) baseAddress;
77+
#endif
6178

62-
/// `next` and `prev` are used by the runtime to construct a
63-
/// circularly doubly linked list to quickly iterate over the metadata
64-
/// from each image loaded into the address space. These are invasive
65-
/// to enable the runtime registration, which occurs at image load time, to
66-
/// be allocation-free as it is invoked from an image constructor function
67-
/// context where the system may not yet be ready to perform allocations.
68-
/// Additionally, avoiding the allocation enables a fast load operation, which
69-
/// directly impacts application load time.
70-
struct MetadataSections *next;
71-
struct MetadataSections *prev;
72-
79+
/// Unused.
80+
///
81+
/// These pointers (or the space they occupy) can be repurposed without
82+
/// causing ABI breakage. Set them to \c nullptr.
83+
void *unused0;
84+
void *unused1;
7385

7486
MetadataSectionRange swift5_protocols;
7587
MetadataSectionRange swift5_protocol_conformances;

stdlib/public/runtime/ImageInspectionCommon.cpp

Lines changed: 86 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -24,68 +24,71 @@
2424
#include "../SwiftShims/Visibility.h"
2525
#include "../SwiftShims/MetadataSections.h"
2626
#include "ImageInspection.h"
27+
#include "swift/Basic/Lazy.h"
28+
#include "swift/Runtime/Concurrent.h"
2729

30+
#include <algorithm>
31+
#include <atomic>
32+
#include <cstdlib>
2833

2934
namespace swift {
3035

31-
#ifndef NDEBUG
32-
static swift::MetadataSections *registered = nullptr;
36+
static Lazy<ConcurrentReadableArray<swift::MetadataSections *>> registered;
3337

34-
static void record(swift::MetadataSections *sections) {
35-
if (registered == nullptr) {
36-
registered = sections;
37-
sections->next = sections->prev = sections;
38-
} else {
39-
registered->prev->next = sections;
40-
sections->next = registered;
41-
sections->prev = registered->prev;
42-
registered->prev = sections;
43-
}
44-
}
45-
#endif
46-
47-
static const void *
48-
getMetadataSectionBaseAddress(swift::MetadataSections *sections) {
49-
// If the base address was not set by the caller of swift_addNewDSOImage()
50-
// then we can assume that the caller was built against an older version of
51-
// the runtime that did not capture a value for this field. Currently nothing
52-
// is actively using the image's base address outside of tests that are built
53-
// with the runtime/stdlib, so there's no need to try to fix up the value. If
54-
// something in the runtime starts using it, we will want to either:
55-
// 1. Resolve the address from a known-good address like swift5_protocols when
56-
// the image is first loaded (in this function);
57-
// 1. Resolve the address from a known-good address like swift5_protocols when
58-
// the address is first used (and atomically swap the address back so we
59-
// don't incur the cost of lookupSymbol() each time we need it; or
60-
// 3. Introduce an ABI-breaking change so that all binaries are rebuilt and
61-
// start supplying a value for this field.
38+
/// Adjust the \c baseAddress field of a metadata sections structure.
39+
///
40+
/// \param sections A pointer to a valid \c swift::MetadataSections structure.
41+
///
42+
/// This function should be called at least once before the structure or its
43+
/// address is passed to code outside this file to ensure that the structure's
44+
/// \c baseAddress field correctly points to the base address of the image it
45+
/// is describing.
46+
static void fixupMetadataSectionBaseAddress(swift::MetadataSections *sections) {
47+
bool fixupNeeded = false;
6248

63-
#ifndef NDEBUG
6449
#if defined(__ELF__)
6550
// If the base address was set but the image is an ELF image, it is going to
6651
// be __dso_handle which is not the value we expect (Dl_info::dli_fbase), so
67-
// we need to fix it up. Since the base address is currently unused by the
68-
// runtime outside tests, we don't normally do this work.
69-
if (auto baseAddress = sections->baseAddress) {
70-
swift::SymbolInfo symbolInfo;
71-
if (lookupSymbol(baseAddress, &symbolInfo) && symbolInfo.baseAddress) {
72-
sections->baseAddress = symbolInfo.baseAddress;
73-
}
52+
// we need to fix it up.
53+
fixupNeeded = true;
54+
#elif !defined(__MACH__)
55+
// For non-ELF, non-Apple platforms, if the base address is nullptr, it
56+
// implies that this image was built against an older version of the runtime
57+
// that did not capture any value for the base address.
58+
auto oldBaseAddress = sections->baseAddress.load(std::memory_order_relaxed);
59+
if (!oldBaseAddress) {
60+
fixupNeeded = true;
7461
}
75-
#endif
7662
#endif
7763

78-
return sections->baseAddress;
64+
if (fixupNeeded) {
65+
// We need to fix up the base address. We'll need a known-good address in
66+
// the same image: `sections` itself will work nicely.
67+
swift::SymbolInfo symbolInfo;
68+
if (lookupSymbol(sections, &symbolInfo) && symbolInfo.baseAddress) {
69+
sections->baseAddress.store(symbolInfo.baseAddress,
70+
std::memory_order_relaxed);
71+
}
72+
}
7973
}
8074
}
8175

8276
SWIFT_RUNTIME_EXPORT
8377
void swift_addNewDSOImage(swift::MetadataSections *sections) {
84-
#ifndef NDEBUG
85-
record(sections);
78+
#if 0
79+
// Ensure the base address of the sections structure is correct.
80+
//
81+
// Currently disabled because none of the registration functions below
82+
// actually do anything with the baseAddress field. Instead,
83+
// swift_enumerateAllMetadataSections() is called by other individual
84+
// functions, lower in this file, that yield metadata section pointers.
85+
//
86+
// If one of these registration functions starts needing the baseAddress
87+
// field, this call should be enabled and the calls elsewhere in the file can
88+
// be removed.
89+
swift::fixupMetadataSectionBaseAddress(sections);
8690
#endif
87-
88-
auto baseAddress = swift::getMetadataSectionBaseAddress(sections);
91+
auto baseAddress = sections->baseAddress.load(std::memory_order_relaxed);
8992

9093
const auto &protocols_section = sections->swift5_protocols;
9194
const void *protocols = reinterpret_cast<void *>(protocols_section.start);
@@ -125,6 +128,29 @@ void swift_addNewDSOImage(swift::MetadataSections *sections) {
125128
if (accessible_funcs_section.length)
126129
swift::addImageAccessibleFunctionsBlockCallback(
127130
baseAddress, functions, accessible_funcs_section.length);
131+
132+
// Register this section for future enumeration by clients. This should occur
133+
// after this function has done all other relevant work to avoid a race
134+
// condition when someone calls swift_enumerateAllMetadataSections() on
135+
// another thread.
136+
swift::registered->push_back(sections);
137+
}
138+
139+
SWIFT_RUNTIME_EXPORT
140+
void swift_enumerateAllMetadataSections(
141+
bool (* body)(const swift::MetadataSections *sections, void *context),
142+
void *context
143+
) {
144+
auto snapshot = swift::registered->snapshot();
145+
for (swift::MetadataSections *sections : snapshot) {
146+
// Ensure the base address is fixed up before yielding the pointer.
147+
swift::fixupMetadataSectionBaseAddress(sections);
148+
149+
// Yield the pointer and (if the callback returns false) break the loop.
150+
if (!(* body)(sections, context)) {
151+
return;
152+
}
153+
}
128154
}
129155

130156
void swift::initializeProtocolLookup() {
@@ -146,19 +172,19 @@ void swift::initializeAccessibleFunctionsLookup() {
146172

147173
SWIFT_RUNTIME_EXPORT
148174
const swift::MetadataSections *swift_getMetadataSection(size_t index) {
149-
if (swift::registered == nullptr) {
150-
return nullptr;
175+
swift::MetadataSections *result = nullptr;
176+
177+
auto snapshot = swift::registered->snapshot();
178+
if (index < snapshot.count()) {
179+
result = snapshot[index];
151180
}
152181

153-
auto selected = swift::registered;
154-
while (index > 0) {
155-
selected = selected->next;
156-
if (selected == swift::registered) {
157-
return nullptr;
158-
}
159-
--index;
182+
if (result) {
183+
// Ensure the base address is fixed up before returning it.
184+
swift::fixupMetadataSectionBaseAddress(result);
160185
}
161-
return selected;
186+
187+
return result;
162188
}
163189

164190
SWIFT_RUNTIME_EXPORT
@@ -184,19 +210,16 @@ void swift_getMetadataSectionBaseAddress(const swift::MetadataSections *section,
184210
*out_actual = nullptr;
185211
}
186212

187-
*out_expected = section->baseAddress;
213+
// fixupMetadataSectionBaseAddress() was already called by
214+
// swift_getMetadataSection(), presumably on the same thread, so we don't need
215+
// to call it again here.
216+
*out_expected = section->baseAddress.load(std::memory_order_relaxed);
188217
}
189218

190219
SWIFT_RUNTIME_EXPORT
191220
size_t swift_getMetadataSectionCount() {
192-
if (swift::registered == nullptr)
193-
return 0;
194-
195-
size_t count = 1;
196-
for (const auto *current = swift::registered->next;
197-
current != swift::registered; current = current->next, ++count);
198-
199-
return count;
221+
auto snapshot = swift::registered->snapshot();
222+
return snapshot.count();
200223
}
201224

202225
#endif // NDEBUG

stdlib/public/runtime/ImageInspectionCommon.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ struct SectionInfo {
7171
SWIFT_RUNTIME_EXPORT
7272
void swift_addNewDSOImage(struct swift::MetadataSections *sections);
7373

74+
/// Enumerate all metadata sections in the current process that are known to the
75+
/// Swift runtime.
76+
///
77+
/// \param body A function to invoke once per metadata sections structure.
78+
/// If this function returns \c false, enumeration is stopped.
79+
/// \param context An additional context pointer to pass to \a body.
80+
///
81+
/// On Mach-O-based platforms (i.e. Apple platforms), this function is
82+
/// unavailable. On those plaforms, use dyld API to enumerate loaded images and
83+
/// their corresponding metadata sections.
84+
SWIFT_RUNTIME_EXPORT SWIFT_WEAK_IMPORT
85+
void swift_enumerateAllMetadataSections(
86+
bool (* body)(const swift::MetadataSections *sections, void *context),
87+
void *context
88+
);
89+
7490
#ifndef NDEBUG
7591

7692
SWIFT_RUNTIME_EXPORT

stdlib/public/runtime/SwiftRT-COFF.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "../SwiftShims/MetadataSections.h"
1515

1616
#include <cstdint>
17+
#include <new>
1718

1819
extern "C" const char __ImageBase[];
1920

@@ -64,9 +65,9 @@ static void swift_image_constructor() {
6465
{ reinterpret_cast<uintptr_t>(&__start_##name) + sizeof(__start_##name), \
6566
reinterpret_cast<uintptr_t>(&__stop_##name) - reinterpret_cast<uintptr_t>(&__start_##name) - sizeof(__start_##name) }
6667

67-
sections = {
68+
new (&sections) swift::MetadataSections {
6869
swift::CurrentSectionMetadataVersion,
69-
__ImageBase,
70+
{ __ImageBase },
7071

7172
nullptr,
7273
nullptr,

stdlib/public/runtime/SwiftRT-ELF.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "../SwiftShims/MetadataSections.h"
1515

1616
#include <cstddef>
17+
#include <new>
1718

1819
extern "C" const char __dso_handle[];
1920

@@ -55,9 +56,9 @@ static void swift_image_constructor() {
5556
{ reinterpret_cast<uintptr_t>(&__start_##name), \
5657
static_cast<uintptr_t>(&__stop_##name - &__start_##name) }
5758

58-
sections = {
59+
new (&sections) swift::MetadataSections {
5960
swift::CurrentSectionMetadataVersion,
60-
__dso_handle,
61+
{ __dso_handle },
6162

6263
nullptr,
6364
nullptr,
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
4+
#if os(Linux) || os(Windows)
5+
import Swift
6+
import SwiftShims
7+
import StdlibUnittest
8+
9+
let EnumerateMetadataSections = TestSuite("EnumerateMetadataSections")
10+
11+
@_silgen_name("swift_enumerateAllMetadataSections")
12+
func swift_enumerateAllMetadataSections(
13+
_ body: @convention(c) (
14+
_ sections: UnsafePointer<MetadataSections>,
15+
_ context: UnsafeMutableRawPointer
16+
) -> Bool,
17+
_ context: UnsafeMutableRawPointer
18+
)
19+
20+
public protocol P { }
21+
public struct S: P { }
22+
23+
EnumerateMetadataSections.test("swift_enumerateAllMetadataSections works") {
24+
var sectionsEnumerated = 0
25+
swift_enumerateAllMetadataSections({ sections, context in
26+
let sectionsEnumerated = context.bindMemory(to: Int.self, capacity: 1)
27+
28+
// Confirm that the base address of the metadata sections was loaded.
29+
let baseAddress = sections.pointee.baseAddress
30+
expectNotNil(baseAddress)
31+
32+
// Confirm that P and S above have been emitted.
33+
if baseAddress == #dsohandle {
34+
expectNotNil(sections.pointee.swift5_protocols)
35+
expectNotNil(sections.pointee.swift5_protocol_conformances)
36+
expectNotNil(sections.pointee.swift5_type_metadata)
37+
}
38+
39+
sectionsEnumerated.pointee += 1
40+
return true
41+
}, &sectionsEnumerated)
42+
43+
// Confirm that at least one section was enumerated.
44+
expectGT(sectionsEnumerated, 0)
45+
}
46+
47+
runAllTests()
48+
49+
#endif

0 commit comments

Comments
 (0)