@@ -48,35 +48,104 @@ struct SectionBounds: Sendable, BitwiseCopyable {
4848#if SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING
4949// MARK: - Apple implementation
5050
51- /// Get section bounds for the given section from the given Mach header.
52- ///
53- /// - Parameters:
54- /// - segmentName: The Mach-O segment name of interest.
55- /// - sectionName: The Mach-O section name of interest.
56- /// - mh: The Mach header.
51+ extension SectionBounds . Kind {
52+ /// The Mach-O segment and section name for this instance as a pair of
53+ /// null-terminated UTF-8 C strings and pass them to a function.
54+ ///
55+ /// The values of this property are instances of `StaticString` rather than
56+ /// `String` because the latter's inner storage is sometimes backed by
57+ /// Objective-C and touching it here can cause a recursive access to an
58+ /// internal libobjc lock, whereas `StaticString`'s internal storage is
59+ /// immediately available.
60+ fileprivate var segmentAndSectionName : ( segmentName: StaticString , sectionName: StaticString ) {
61+ switch self {
62+ case . testContent:
63+ ( " __DATA_CONST " , " __swift5_tests " )
64+ #if !SWT_NO_LEGACY_TEST_DISCOVERY
65+ case . typeMetadata:
66+ ( " __TEXT " , " __swift5_types " )
67+ #endif
68+ }
69+ }
70+ }
71+
72+ /// An array containing all of the test content section bounds known to the
73+ /// testing library.
5774///
58- /// - Returns: The requested section bounds, or `nil` if they couldn't be found.
59- private func _sectionBounds( _ segmentName: String , _ sectionName: String , in mh: UnsafePointer < mach_header > ) -> SectionBounds ? {
75+ /// Indices into this array are equivalent to the `rawValue` values of instances
76+ /// of ``SectionBounds/Kind``.
77+ private nonisolated ( unsafe) let _sectionBounds = {
78+ // We generate a contiguous array here rather than a dictionary because the
79+ // former has less overall bridging with the Objective-C runtime (reducing the
80+ // risk of reentrance while holding the libobjc lock) and because the set of
81+ // keys or indices is closed, so an array lookup is always more efficient than
82+ // a hashtable lookup.
83+ let kindCount = SectionBounds . Kind. allCases. count
84+ let result = ManagedBuffer < ContiguousArray < ContiguousArray < SectionBounds > > , pthread_mutex_t > . create (
85+ minimumCapacity: 1 ,
86+ makingHeaderWith: { _ in . init( repeating: [ ] , count: kindCount) }
87+ )
88+
89+ result. withUnsafeMutablePointers { sectionBounds, lock in
90+ _ = pthread_mutex_init ( lock, nil )
91+
92+ let imageCount = Int ( clamping: _dyld_image_count ( ) )
93+ for kind in SectionBounds . Kind. allCases {
94+ sectionBounds. pointee [ kind. rawValue] . reserveCapacity ( imageCount)
95+ }
96+ }
97+
98+ return result
99+ } ( )
100+
101+ /// A call-once function that initializes `_sectionBounds` and starts listening
102+ /// for loaded Mach headers.
103+ private let _startCollectingSectionBounds : Void = {
104+ // Ensure _sectionBounds is initialized before we touch libobjc or dyld.
105+ _ = _sectionBounds
106+
107+ func addSectionBounds( from mh: UnsafePointer < mach_header > ) {
60108#if _pointerBitWidth(_64)
61- let mh = UnsafeRawPointer ( mh) . assumingMemoryBound ( to: mach_header_64. self)
109+ let mh = UnsafeRawPointer ( mh) . assumingMemoryBound ( to: mach_header_64. self)
62110#endif
63111
64- // Ignore this Mach header if it is in the shared cache. On platforms that
65- // support it (Darwin), most system images are contained in this range.
66- // System images can be expected not to contain test declarations, so we
67- // don't need to walk them.
68- guard 0 == mh. pointee. flags & MH_DYLIB_IN_CACHE else {
69- return nil
70- }
112+ // Ignore this Mach header if it is in the shared cache. On platforms that
113+ // support it (Darwin), most system images are contained in this range.
114+ // System images can be expected not to contain test declarations, so we
115+ // don't need to walk them.
116+ guard 0 == mh. pointee. flags & MH_DYLIB_IN_CACHE else {
117+ return
118+ }
71119
72- var size = CUnsignedLong ( 0 )
73- if let start = getsectiondata ( mh, segmentName, sectionName, & size) , size > 0 {
74- let buffer = UnsafeRawBufferPointer ( start: start, count: Int ( clamping: size) )
75- return SectionBounds ( imageAddress: mh, buffer: buffer)
120+ // If this image contains the Swift section(s) we need, acquire the lock and
121+ // store the section's bounds.
122+ for kind in SectionBounds . Kind. allCases {
123+ let ( segmentName, sectionName) = kind. segmentAndSectionName
124+ var size = CUnsignedLong ( 0 )
125+ if let start = getsectiondata ( mh, segmentName. utf8Start, sectionName. utf8Start, & size) , size > 0 {
126+ let buffer = UnsafeRawBufferPointer ( start: start, count: Int ( clamping: size) )
127+ let sb = SectionBounds ( imageAddress: mh, buffer: buffer)
128+ _sectionBounds. withUnsafeMutablePointers { sectionBounds, lock in
129+ pthread_mutex_lock ( lock)
130+ defer {
131+ pthread_mutex_unlock ( lock)
132+ }
133+ sectionBounds. pointee [ kind. rawValue] . append ( sb)
134+ }
135+ }
136+ }
76137 }
77138
78- return nil
79- }
139+ #if _runtime(_ObjC)
140+ objc_addLoadImageFunc { mh in
141+ addSectionBounds ( from: mh)
142+ }
143+ #else
144+ _dyld_register_func_for_add_image { mh, _ in
145+ addSectionBounds ( from: mh!)
146+ }
147+ #endif
148+ } ( )
80149
81150/// The Apple-specific implementation of ``SectionBounds/all(_:)``.
82151///
@@ -86,43 +155,14 @@ private func _sectionBounds(_ segmentName: String, _ sectionName: String, in mh:
86155/// - Returns: An array of structures describing the bounds of all known test
87156/// content sections in the current process.
88157private func _sectionBounds( _ kind: SectionBounds . Kind ) -> some RandomAccessCollection < SectionBounds > {
89- // If this image contains the Swift section(s) we need, get the section bounds
90- // of interest.
91- let ( segmentName, sectionName) = switch kind {
92- case . testContent:
93- ( " __DATA_CONST " , " __swift5_tests " )
94- #if !SWT_NO_LEGACY_TEST_DISCOVERY
95- case . typeMetadata:
96- ( " __TEXT " , " __swift5_types " )
97- #endif
98- }
99-
100- #if _runtime(_ObjC)
101- var imageCount = UInt32 ( 0 )
102- let imageNames = objc_copyImageNames ( & imageCount)
103- defer {
104- free ( imageNames)
105- }
106- let imageNameBuffer = UnsafeBufferPointer ( start: imageNames, count: Int ( imageCount) )
107-
108- return imageNameBuffer. compactMap { imageName in
109- guard let handle = dlopen ( imageName, RTLD_LAZY | RTLD_NOLOAD) else {
110- return nil
111- }
158+ _startCollectingSectionBounds
159+ return _sectionBounds. withUnsafeMutablePointers { sectionBounds, lock in
160+ pthread_mutex_lock ( lock)
112161 defer {
113- dlclose ( handle)
114- }
115- guard let mh = _dyld_get_dlopen_image_header ( handle) else {
116- return nil
162+ pthread_mutex_unlock ( lock)
117163 }
118- return _sectionBounds ( segmentName , sectionName , in : mh )
164+ return sectionBounds . pointee [ kind . rawValue ]
119165 }
120- #else
121- let imageCount = _dyld_image_count ( )
122- return ( 0 ..< imageCount)
123- . compactMap ( _dyld_get_image_header)
124- . compactMap { _sectionBounds ( segmentName, sectionName, in: $0) }
125- #endif
126166}
127167
128168#elseif (os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)) && !SWT_NO_DYNAMIC_LINKING
0 commit comments