Skip to content

Optimizations to Backtrace.current(). #647

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 6 commits into from
Sep 3, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 39 additions & 28 deletions Sources/Testing/SourceAttribution/Backtrace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<UnsafeRawPointer?>) {
self.init(
addresses: addresses.lazy
.map(UInt.init(bitPattern:))
.map(Address.init)
)
self.addresses = addresses.map { Address(UInt(bitPattern: $0)) }
}

/// Get the current backtrace.
Expand All @@ -61,33 +57,42 @@ 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
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)
} else {
initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count)))
}
#elseif os(Linux)
initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count)))
if #available(_backtraceAsyncAPI, *) {
initializedCount = backtrace_async(addresses.baseAddress!, addresses.count, nil)
} else {
initializedCount = .init(clamping: backtrace(addresses.baseAddress!, .init(clamping: 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(clamping: backtrace(addresses.baseAddress!, .init(clamping: addresses.count)))
}
#elseif os(Linux)
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
initializedCount = 0
// SEE: https://github.com/WebAssembly/WASI/issues/159
// SEE: https://github.com/swiftlang/swift/pull/31693
#else
#warning("Platform-specific implementation missing: backtraces unavailable")
initializedCount = 0
#endif

let endIndex = addresses.index(addresses.startIndex, offsetBy: initializedCount)
#if _pointerBitWidth(_64)
// The width of a pointer equals the width of an `Address`, so we can just
// bitcast the memory rather than mapping through UInt first.
return addresses[..<endIndex].withMemoryRebound(to: Address.self) { addresses in
Self(addresses: addresses)
}
#else
return addresses[..<endIndex].withMemoryRebound(to: UnsafeRawPointer?.self) { addresses in
Self(addresses: addresses)
}
#endif
}
return Self(addresses: addresses)
}
}

Expand Down Expand Up @@ -164,12 +169,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
Expand All @@ -183,9 +188,15 @@ 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 { _willThrow($0) }
oldWillThrowHandler = swt_setWillThrowHandler { errorAddress in
let backtrace = Backtrace.current()
_willThrow(errorAddress, from: backtrace)
}
}
}()

Expand All @@ -196,7 +207,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.
Expand Down