From e0cdd7f2d7d373ecac141a40edd59a927ce3d2b8 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 26 Aug 2024 19:58:01 -0400 Subject: [PATCH 1/6] Reduce the number of captured symbols in backtraces. --- .../Testing/SourceAttribution/Backtrace.swift | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index a37916f71..94c4118b2 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -61,33 +61,36 @@ public struct Backtrace: Sendable { public static func current(maximumAddressCount addressCount: Int = 128) -> Self { // NOTE: the exact argument/return types for backtrace() vary across // platforms, hence the use of .init() when calling it below. - let addresses = [UnsafeRawPointer?](unsafeUninitializedCapacity: addressCount) { addresses, initializedCount in - addresses.withMemoryRebound(to: UnsafeMutableRawPointer?.self) { addresses in + let addresses = [UnsafeMutableRawPointer?](unsafeUninitializedCapacity: addressCount) { addresses, initializedCount in #if SWT_TARGET_OS_APPLE - if #available(_backtraceAsyncAPI, *) { - initializedCount = backtrace_async(addresses.baseAddress!, addresses.count, nil) - } else { - initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count))) - } -#elseif os(Linux) + if #available(_backtraceAsyncAPI, *) { + initializedCount = backtrace_async(addresses.baseAddress!, addresses.count, nil) + } else { initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count))) + } #elseif os(Android) - addresses.withMemoryRebound(to: UnsafeMutableRawPointer.self) { addresses in - initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count))) - } + initializedCount = addresses.withMemoryRebound(to: UnsafeMutableRawPointer.self) { addresses in + .init(backtrace(addresses.baseAddress!, .init(addresses.count))) + } +#elseif os(Linux) + initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count))) #elseif os(Windows) - initializedCount = Int(RtlCaptureStackBackTrace(0, ULONG(addresses.count), addresses.baseAddress!, nil)) + initializedCount = Int(RtlCaptureStackBackTrace(0, ULONG(addresses.count), addresses.baseAddress!, nil)) #elseif os(WASI) - // SEE: https://github.com/WebAssembly/WASI/issues/159 - // SEE: https://github.com/swiftlang/swift/pull/31693 - initializedCount = 0 + // SEE: https://github.com/WebAssembly/WASI/issues/159 + // SEE: https://github.com/swiftlang/swift/pull/31693 + initializedCount = 0 #else #warning("Platform-specific implementation missing: backtraces unavailable") - initializedCount = 0 + initializedCount = 0 #endif + } + + return addresses.withUnsafeBufferPointer { addresses in + addresses.withMemoryRebound(to: UnsafeRawPointer?.self) { addresses in + Self(addresses: addresses) } } - return Self(addresses: addresses) } } @@ -164,12 +167,12 @@ extension Backtrace { /// - errorAddress: The error that is about to be thrown. This pointer /// refers to an instance of `SwiftError` or (on platforms with /// Objective-C interop) an instance of `NSError`. - @Sendable private static func _willThrow(_ errorAddress: UnsafeMutableRawPointer) { + /// - backtrace: The backtrace from where the error was thrown. + private static func _willThrow(_ errorAddress: UnsafeMutableRawPointer, from backtrace: Backtrace) { _oldWillThrowHandler.rawValue?(errorAddress) let errorObject = unsafeBitCast(errorAddress, to: (any AnyObject & Sendable).self) let errorID = ObjectIdentifier(errorObject) - let backtrace = Backtrace.current() let newEntry = _ErrorMappingCacheEntry(errorObject: errorObject, backtrace: backtrace) _errorMappingCache.withLock { cache in @@ -185,7 +188,10 @@ extension Backtrace { /// only once. private static let _startCachingForThrownErrors: Void = { _oldWillThrowHandler.withLock { oldWillThrowHandler in - oldWillThrowHandler = swt_setWillThrowHandler { _willThrow($0) } + oldWillThrowHandler = swt_setWillThrowHandler { errorAddress in + let backtrace = Backtrace.current() + _willThrow(errorAddress, from: backtrace) + } } }() From 924a6e568ad2d81206cb7635a9e611c5262a1480 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 28 Aug 2024 09:19:57 -0400 Subject: [PATCH 2/6] Avoid an extra heap allocation during backtracing --- Sources/Testing/SourceAttribution/Backtrace.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index 94c4118b2..fd2befc76 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -61,7 +61,8 @@ public struct Backtrace: Sendable { public static func current(maximumAddressCount addressCount: Int = 128) -> Self { // NOTE: the exact argument/return types for backtrace() vary across // platforms, hence the use of .init() when calling it below. - let addresses = [UnsafeMutableRawPointer?](unsafeUninitializedCapacity: addressCount) { addresses, initializedCount in + withUnsafeTemporaryAllocation(of: UnsafeMutableRawPointer?.self, capacity: addressCount) { addresses in + var initializedCount = 0 #if SWT_TARGET_OS_APPLE if #available(_backtraceAsyncAPI, *) { initializedCount = backtrace_async(addresses.baseAddress!, addresses.count, nil) @@ -79,15 +80,12 @@ public struct Backtrace: Sendable { #elseif os(WASI) // SEE: https://github.com/WebAssembly/WASI/issues/159 // SEE: https://github.com/swiftlang/swift/pull/31693 - initializedCount = 0 #else #warning("Platform-specific implementation missing: backtraces unavailable") - initializedCount = 0 #endif - } - return addresses.withUnsafeBufferPointer { addresses in - addresses.withMemoryRebound(to: UnsafeRawPointer?.self) { addresses in + let endIndex = addresses.index(addresses.startIndex, offsetBy: initializedCount) + return addresses[.. Date: Wed, 28 Aug 2024 10:11:20 -0400 Subject: [PATCH 3/6] Avoid casting through UInt on 64-bit where we can just rebind the whole buffer to Address --- Sources/Testing/SourceAttribution/Backtrace.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index fd2befc76..8d0eda6c1 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -85,9 +85,17 @@ public struct Backtrace: Sendable { #endif let endIndex = addresses.index(addresses.startIndex, offsetBy: initializedCount) - return addresses[.. Date: Wed, 28 Aug 2024 10:43:27 -0400 Subject: [PATCH 4/6] Avoid runtime bounds checks on backtrace counts --- Sources/Testing/SourceAttribution/Backtrace.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index 8d0eda6c1..9f07dd04c 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -67,16 +67,16 @@ public struct Backtrace: Sendable { if #available(_backtraceAsyncAPI, *) { initializedCount = backtrace_async(addresses.baseAddress!, addresses.count, nil) } else { - initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count))) + initializedCount = .init(clamping: backtrace(addresses.baseAddress!, .init(clamping: addresses.count))) } #elseif os(Android) initializedCount = addresses.withMemoryRebound(to: UnsafeMutableRawPointer.self) { addresses in - .init(backtrace(addresses.baseAddress!, .init(addresses.count))) + .init(clamping: backtrace(addresses.baseAddress!, .init(clamping: addresses.count))) } #elseif os(Linux) - initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count))) + initializedCount = .init(clamping: backtrace(addresses.baseAddress!, .init(clamping: addresses.count))) #elseif os(Windows) - initializedCount = Int(RtlCaptureStackBackTrace(0, ULONG(addresses.count), addresses.baseAddress!, nil)) + initializedCount = Int(clamping: RtlCaptureStackBackTrace(0, ULONG(clamping: addresses.count), addresses.baseAddress!, nil)) #elseif os(WASI) // SEE: https://github.com/WebAssembly/WASI/issues/159 // SEE: https://github.com/swiftlang/swift/pull/31693 From 1853c6f0155cfd2e3b4e707aaea11a29a2b256df Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 28 Aug 2024 10:44:20 -0400 Subject: [PATCH 5/6] Rename _startCachingForThrownErrors since it shows up in backtraces, make it clearer what it's there for --- Sources/Testing/SourceAttribution/Backtrace.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index 9f07dd04c..de7368b6c 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -192,7 +192,10 @@ extension Backtrace { /// The implementation of ``Backtrace/startCachingForThrownErrors()``, run /// only once. - private static let _startCachingForThrownErrors: Void = { + /// + /// This value is named oddly so that it shows up clearly in symbolicated + /// backtraces. + private static let __SWIFT_TESTING_IS_CAPTURING_A_BACKTRACE_FOR_A_THROWN_ERROR__: Void = { _oldWillThrowHandler.withLock { oldWillThrowHandler in oldWillThrowHandler = swt_setWillThrowHandler { errorAddress in let backtrace = Backtrace.current() @@ -208,7 +211,7 @@ extension Backtrace { /// developer-supplied code to ensure that thrown errors' backtraces are /// always captured. static func startCachingForThrownErrors() { - _startCachingForThrownErrors + __SWIFT_TESTING_IS_CAPTURING_A_BACKTRACE_FOR_A_THROWN_ERROR__ } /// Flush stale entries from the error-mapping cache. From 0ca5c8dad51ce97ec3d3651b350b8ecaa55d0ae8 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 28 Aug 2024 11:56:00 -0400 Subject: [PATCH 6/6] Optimize the 32-bit path too --- Sources/Testing/SourceAttribution/Backtrace.swift | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index de7368b6c..6e32b3de2 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -37,11 +37,7 @@ public struct Backtrace: Sendable { /// The pointers in `addresses` are converted to instances of ``Address``. Any /// `nil` addresses are represented as `0`. public init(addresses: some Sequence) { - self.init( - addresses: addresses.lazy - .map(UInt.init(bitPattern:)) - .map(Address.init) - ) + self.addresses = addresses.map { Address(UInt(bitPattern: $0)) } } /// Get the current backtrace. @@ -93,7 +89,7 @@ public struct Backtrace: Sendable { } #else return addresses[..