Files
swift-mirror/stdlib/public/RuntimeModule/CompactBacktrace.swift
Alastair Houghton ffda416d36 [Backtracing] Add support for repeats to Compact Backtrace Format.
This lets us repeat a frame over and over; it only works for a single
frame, but that's the common case for infinite recursions.

rdar://124913332
2025-01-17 10:09:37 +00:00

661 lines
19 KiB
Swift

//===--- CompactBacktrace.swift -------------------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Definitions for Compact Backtrace Format
//
//===----------------------------------------------------------------------===//
import Swift
enum CompactBacktraceFormat {
/// Tells us what size of machine words were used when generating the
/// backtrace.
enum WordSize: UInt8 {
case sixteenBit = 0
case thirtyTwoBit = 1
case sixtyFourBit = 2
}
// Instruction encodings
struct Instruction: RawRepresentable {
typealias RawValue = UInt8
private(set) var rawValue: UInt8
init?(rawValue: Self.RawValue) {
self.rawValue = rawValue
}
static let end = Instruction(rawValue: 0b00000000)!
static let trunc = Instruction(rawValue: 0b00000001)!
static let pc_first = Instruction(rawValue: 0b00010000)!
static let pc_last = Instruction(rawValue: 0b00011111)!
static let ra_first = Instruction(rawValue: 0b00100000)!
static let ra_last = Instruction(rawValue: 0b00101111)!
static let async_first = Instruction(rawValue: 0b00110000)!
static let async_last = Instruction(rawValue: 0b00111111)!
static let omit_first = Instruction(rawValue: 0b01000000)!
static let omit_last = Instruction(rawValue: 0b01111111)!
static let rep_first = Instruction(rawValue: 0b10000000)!
static let rep_last = Instruction(rawValue: 0b10001111)!
private static func addressInstr(
_ code: UInt8, _ absolute: Bool, _ count: Int
) -> Instruction {
return Instruction(rawValue: code
| (absolute ? 0b00001000 : 0)
| UInt8(count - 1))!
}
static func pc(absolute: Bool, count: Int) -> Instruction {
return addressInstr(0b00010000, absolute, count)
}
static func ra(absolute: Bool, count: Int) -> Instruction {
return addressInstr(0b00100000, absolute, count)
}
static func `async`(absolute: Bool, count: Int) -> Instruction {
return addressInstr(0b00110000, absolute, count)
}
static func omit(external: Bool, count: Int) -> Instruction {
return Instruction(rawValue: 0b01000000
| (external ? 0b00100000 : 0)
| UInt8(count - 1))!
}
static func rep(external: Bool, count: Int) -> Instruction {
return Instruction(rawValue: 0b10000000
| (external ? 0b00001000 : 0)
| UInt8(count - 1))!
}
}
// Represents a decoded instruction
enum DecodedInstruction {
case end
case trunc
case pc(absolute: Bool, count: Int)
case ra(absolute: Bool, count: Int)
case `async`(absolute: Bool, count: Int)
case omit(external: Bool, count: Int)
case rep(external: Bool, count: Int)
}
/// Accumulates bytes until the end of a Compact Backtrace Format
/// sequence is detected.
public struct Accumulator<S: Sequence<UInt8>>: Sequence {
public typealias Element = UInt8
typealias Source = S
private var source: S
public init(_ source: S) {
self.source = source
}
public func makeIterator() -> Iterator {
return Iterator(source.makeIterator())
}
public struct Iterator: IteratorProtocol {
var iterator: Source.Iterator?
enum State {
case infoByte
case instruction
case argumentData(Int)
}
var state: State
init(_ iterator: Source.Iterator?) {
self.iterator = iterator
self.state = .infoByte
}
private mutating func finished() {
iterator = nil
}
private mutating func fail() {
iterator = nil
}
public mutating func next() -> UInt8? {
if iterator == nil {
return nil
}
switch state {
case .infoByte:
guard let infoByte = iterator!.next() else {
fail()
return nil
}
let version = infoByte >> 2
guard let _ = WordSize(rawValue: infoByte & 0x3) else {
fail()
return nil
}
guard version == 0 else {
fail()
return nil
}
state = .instruction
return infoByte
case .instruction:
guard let instr = iterator!.next() else {
finished()
return nil
}
guard let decoded = Instruction(rawValue: instr)?.decoded() else {
fail()
return nil
}
switch decoded {
case .end, .trunc:
finished()
return instr
case let .pc(_, count), let .ra(_, count), let .async(_, count):
state = .argumentData(count)
return instr
case let .omit(external, count), let .rep(external, count):
if external {
state = .argumentData(count)
}
return instr
}
case let .argumentData(count):
guard let byte = iterator!.next() else {
fail()
return nil
}
let newCount = count - 1
if newCount == 0 {
state = .instruction
} else {
state = .argumentData(newCount)
}
return byte
}
}
}
}
/// Adapts a Sequence containing Compact Backtrace Format data into a
/// Sequence of `Backtrace.Frame`s.
struct Decoder<S: Sequence<UInt8>>: Sequence {
typealias Frame = Backtrace.Frame
typealias Address = Backtrace.Address
typealias Storage = S
private var storage: Storage
init(_ storage: S) {
self.storage = storage
}
public func makeIterator() -> Iterator {
var iterator = storage.makeIterator()
guard let infoByte = iterator.next() else {
return Iterator(nil, .sixtyFourBit)
}
let version = infoByte >> 2
guard let size = WordSize(rawValue: infoByte & 0x3) else {
return Iterator(nil, .sixtyFourBit)
}
guard version == 0 else {
return Iterator(nil, .sixtyFourBit)
}
return Iterator(iterator, size)
}
struct Iterator: IteratorProtocol {
var iterator: Storage.Iterator?
let wordSize: WordSize
let wordMask: UInt64
var lastAddress: UInt64
var lastFrame: Backtrace.Frame?
var repeatCount: Int = 0
init(_ iterator: Storage.Iterator?, _ size: WordSize) {
self.iterator = iterator
self.wordSize = size
switch size {
case .sixteenBit:
self.wordMask = 0xff00
case .thirtyTwoBit:
self.wordMask = 0xffffff00
case .sixtyFourBit:
self.wordMask = 0xffffffffffffff00
}
self.lastAddress = 0
}
private mutating func decodeAddress(
_ absolute: Bool, _ count: Int
) -> Address? {
var word: UInt64
guard let firstByte = iterator!.next() else {
return nil
}
if (firstByte & 0x80) != 0 {
word = wordMask | UInt64(firstByte)
} else {
word = UInt64(firstByte)
}
for _ in 1..<count {
guard let byte = iterator!.next() else {
return nil
}
word = (word << 8) | UInt64(byte)
}
if absolute {
lastAddress = word
} else {
lastAddress = lastAddress &+ word
}
switch wordSize {
case .sixteenBit:
return Address(UInt16(truncatingIfNeeded: lastAddress))
case .thirtyTwoBit:
return Address(UInt32(truncatingIfNeeded: lastAddress))
case .sixtyFourBit:
return Address(UInt64(truncatingIfNeeded: lastAddress))
}
}
private mutating func decodeWord(
_ count: Int
) -> Int? {
var word: Int = 0
for _ in 0..<count {
guard let byte = iterator!.next() else {
return nil
}
word = (word << 8) | Int(byte)
}
return word
}
private mutating func finished() {
iterator = nil
}
private mutating func fail() {
iterator = nil
}
// Note: If we hit an error while decoding, we will return .truncated.
public mutating func next() -> Backtrace.Frame? {
if repeatCount > 0 {
repeatCount -= 1
return lastFrame
}
if iterator == nil {
return nil
}
guard let instr = iterator!.next() else {
finished()
return .truncated
}
guard let decoded = Instruction(rawValue: instr)?.decoded() else {
fail()
return .truncated
}
let result: Backtrace.Frame
switch decoded {
case .end:
finished()
return nil
case .trunc:
finished()
return .truncated
case let .pc(absolute, count):
guard let addr = decodeAddress(absolute, count) else {
finished()
return .truncated
}
result = .programCounter(addr)
case let .ra(absolute, count):
guard let addr = decodeAddress(absolute, count) else {
finished()
return .truncated
}
result = .returnAddress(addr)
case let .async(absolute, count):
guard let addr = decodeAddress(absolute, count) else {
finished()
return .truncated
}
result = .asyncResumePoint(addr)
case let .omit(external, count):
if !external {
result = .omittedFrames(count)
} else {
guard let word = decodeWord(count) else {
finished()
return .truncated
}
result = .omittedFrames(word)
}
case let .rep(external, count):
if lastFrame == nil {
finished()
return .truncated
}
if !external {
repeatCount = count - 1
} else {
guard let word = decodeWord(count) else {
finished()
return .truncated
}
repeatCount = word - 1
}
result = lastFrame!
}
lastFrame = result
return result
}
}
}
/// Adapts a Sequence of RichFrames into a sequence containing Compact
/// Backtrace Format data.
struct Encoder<A: FixedWidthInteger, S: Sequence<RichFrame<A>>>: Sequence {
typealias Element = UInt8
typealias Frame = Backtrace.Frame
typealias SourceFrame = RichFrame<A>
typealias Address = A
typealias Source = S
private var source: Source
init(_ source: Source) {
self.source = source
}
public func makeIterator() -> Iterator {
return Iterator(source.makeIterator())
}
struct Iterator: IteratorProtocol {
var iterator: Source.Iterator
var lastAddress: Address = 0
enum State {
case start
case ready
case emittingBytes(Int, SourceFrame?)
case stashedFrame(SourceFrame)
case done
}
var bytes = EightByteBuffer()
var state: State = .start
var lastFrame: SourceFrame? = nil
init(_ iterator: Source.Iterator) {
self.iterator = iterator
}
/// Set up to emit the bytes of `address`, returning the number of bytes
/// we will need to emit
private mutating func emitNext(
address: Address
) -> (absolute: Bool, count: Int) {
let delta = address &- lastAddress
let absCount: Int
if address & (1 << (Address.bitWidth - 1)) != 0 {
let ones = ((~address).leadingZeroBitCount - 1) >> 3
absCount = (Address.bitWidth >> 3) - ones
} else {
let zeroes = (address.leadingZeroBitCount - 1) >> 3
absCount = (Address.bitWidth >> 3) - zeroes
}
let deltaCount: Int
if delta & (1 << (Address.bitWidth - 1)) != 0 {
let ones = ((~delta).leadingZeroBitCount - 1) >> 3
deltaCount = (Address.bitWidth >> 3) - ones
} else {
let zeroes = (delta.leadingZeroBitCount - 1) >> 3
deltaCount = (Address.bitWidth >> 3) - zeroes
}
lastAddress = address
if absCount < deltaCount {
bytes = EightByteBuffer(address)
state = .emittingBytes(8 - absCount, nil)
return (absolute: true, count: absCount)
} else {
bytes = EightByteBuffer(delta)
state = .emittingBytes(8 - deltaCount, nil)
return (absolute: false, count: deltaCount)
}
}
/// Set up to emit the bytes of `count`, returning the number of bytes
/// we will need to emit
private mutating func emitNext(
externalCount count: Int
) -> Int {
let ucount = UInt64(count)
let zeroes = ucount.leadingZeroBitCount >> 3
let byteCount = 8 - zeroes
bytes = EightByteBuffer(ucount)
state = .emittingBytes(zeroes, nil)
return byteCount
}
private mutating func emitNext(
frame: SourceFrame?,
externalCount count: Int? = nil
) -> Int {
if let count {
let ucount = UInt64(count)
let zeroes = ucount.leadingZeroBitCount >> 3
let byteCount = 8 - zeroes
bytes = EightByteBuffer(ucount)
state = .emittingBytes(zeroes, frame)
return byteCount
} else if let frame {
state = .stashedFrame(frame)
} else {
state = .ready
}
return 0
}
private mutating func emit(frame: SourceFrame) -> UInt8 {
lastFrame = frame
switch frame {
case let .programCounter(addr):
let (absolute, count) = emitNext(address: addr)
return Instruction.pc(absolute: absolute,
count: count).rawValue
case let .returnAddress(addr):
let (absolute, count) = emitNext(address: addr)
return Instruction.ra(absolute: absolute,
count: count).rawValue
case let .asyncResumePoint(addr):
let (absolute, count) = emitNext(address: addr)
return Instruction.async(absolute: absolute,
count: count).rawValue
case let .omittedFrames(count):
if count <= 0x1f {
return Instruction.omit(external: false,
count: count).rawValue
}
let countCount = emitNext(externalCount: count)
return Instruction.omit(external: true,
count: countCount).rawValue
case .truncated:
self.state = .done
return Instruction.trunc.rawValue
}
}
public mutating func next() -> UInt8? {
switch state {
case .done:
return nil
case .start:
// The first thing we emit is the info byte
let size: WordSize
switch Address.bitWidth {
case 16:
size = .sixteenBit
case 32:
size = .thirtyTwoBit
case 64:
size = .sixtyFourBit
default:
state = .done
return nil
}
state = .ready
let version: UInt8 = 0
let infoByte = (version << 2) | size.rawValue
return infoByte
case let .emittingBytes(ndx, frame):
let byte = bytes[ndx]
if ndx + 1 == 8 {
if let frame {
state = .stashedFrame(frame)
} else {
state = .ready
}
} else {
state = .emittingBytes(ndx + 1, frame)
}
return byte
case .ready:
// Grab a rich frame and encode it
guard let frame = iterator.next() else {
state = .done
return nil
}
if let lastFrame, lastFrame == frame {
var count = 1
var nextFrame: SourceFrame? = nil
while let frame = iterator.next() {
if frame != lastFrame {
nextFrame = frame
break
} else {
count += 1
}
}
if count <= 8 {
_ = emitNext(frame: nextFrame)
return Instruction.rep(external: false,
count: count).rawValue
} else {
let countCount = emitNext(frame: nextFrame,
externalCount: count)
return Instruction.rep(external: true,
count: countCount).rawValue
}
}
return emit(frame: frame)
case let .stashedFrame(frame):
state = .ready
return emit(frame: frame)
}
}
}
}
}
extension CompactBacktraceFormat.Instruction: Comparable {
public static func < (lhs: Self, rhs: Self) -> Bool {
return lhs.rawValue < rhs.rawValue
}
public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.rawValue == rhs.rawValue
}
}
extension CompactBacktraceFormat.Instruction {
func decoded() -> CompactBacktraceFormat.DecodedInstruction? {
switch self {
case .end:
return .end
case .trunc:
return .trunc
case .pc_first ... .pc_last:
let count = Int((self.rawValue & 0x7) + 1)
let absolute = (self.rawValue & 0x8) != 0
return .pc(absolute: absolute, count: count)
case .ra_first ... .ra_last:
let count = Int((self.rawValue & 0x7) + 1)
let absolute = (self.rawValue & 0x8) != 0
return .ra(absolute: absolute, count: count)
case .async_first ... .async_last:
let count = Int((self.rawValue & 0x7) + 1)
let absolute = (self.rawValue & 0x8) != 0
return .async(absolute: absolute, count: count)
case .omit_first ... .omit_last:
let count = Int((self.rawValue & 0x1f) + 1)
let external = (self.rawValue & 0x20) != 0
return .omit(external: external, count: count)
case .rep_first ... .rep_last:
let count = Int((self.rawValue & 0x7) + 1)
let external = (self.rawValue & 0x8) != 0
return .rep(external: external, count: count)
default:
return nil
}
}
}