Files
swift-mirror/stdlib/public/core/RuntimeFunctionCounters.swift
Gábor Horváth 99f9c318ca [6.2][StrictMemorySafety] Check the safety of return types of calls
Explanation: There were some scenarios where we could call an unsafe
function without marking the expression as unsafe. These affect mostly
cases where the function's result is passed to another function or
returned. This PR makes sure we always flag functions with unsafe return
types, even if their result is not stored anywhere for later use.
Issues: rdar://157237301
Original PRs: #83520
Risk: Low, worst case scenario the user has to add redundant unsafe
keywords in strict memory safe mode.
Testing: Added a compiler test.
Reviewers: @DougGregor
2025-08-13 11:49:22 +01:00

550 lines
19 KiB
Swift

//===--- RuntimeFunctionCounters.swift ------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
//
//===----------------------------------------------------------------------===//
//
// This file implements the experimental support for collecting the state of
// runtime function counters, which are used to determine how many times
// a given runtime function was called.
//
// It is possible to get the global counters, which represent the total
// number of invocations, or per-object counters, which represent the
// number of runtime functions calls for a specific object.
// By default, this feature is enabled only when assertions are enabled. To control it
// separately, set the SWIFT_ENABLE_RUNTIME_FUNCTION_COUNTERS environment variable when
// invoking build-script:
// SWIFT_ENABLE_RUNTIME_FUNCTION_COUNTERS=TRUE ./utils/build-script ...
#if SWIFT_ENABLE_RUNTIME_FUNCTION_COUNTERS
/// Collect all references inside the object using Mirrors.
/// - Parameter value: the value to be inspected
/// - Parameter references: the array which should contain the collected
/// references
/// - Parameter visitedItems: the dictionary for keeping track of visited
/// objects
internal func _collectAllReferencesInsideObjectImpl(
_ value: Any,
references: inout [UnsafeRawPointer],
visitedItems: inout [ObjectIdentifier: Int]
) {
// Use the structural reflection and ignore any
// custom reflectable overrides.
let mirror = Mirror(internalReflecting: value)
let id: ObjectIdentifier?
let ref: UnsafeRawPointer?
if type(of: value) is AnyObject.Type {
// Object is a class (but not an ObjC-bridged struct)
let toAnyObject = _unsafeDowncastToAnyObject(fromAny: value)
unsafe ref = unsafe UnsafeRawPointer(Unmanaged.passUnretained(toAnyObject).toOpaque())
id = ObjectIdentifier(toAnyObject)
} else if type(of: value) is Builtin.BridgeObject.Type {
unsafe ref = UnsafeRawPointer(
Builtin.bridgeToRawPointer(value as! Builtin.BridgeObject))
id = nil
} else if type(of: value) is Builtin.NativeObject.Type {
unsafe ref = UnsafeRawPointer(
Builtin.bridgeToRawPointer(value as! Builtin.NativeObject))
id = nil
} else if let metatypeInstance = value as? Any.Type {
// Object is a metatype
id = ObjectIdentifier(metatypeInstance)
unsafe ref = nil
} else {
id = nil
unsafe ref = nil
}
if let theId = id {
// Bail if this object was seen already.
if visitedItems[theId] != nil {
return
}
// Remember that this object was seen already.
let identifier = visitedItems.count
visitedItems[theId] = identifier
}
// If it is a reference, add it to the result.
if let ref = unsafe ref {
unsafe references.append(ref)
}
// Recursively visit the children of the current value.
let count = mirror.children.count
var currentIndex = mirror.children.startIndex
for _ in 0..<count {
let (_, child) = mirror.children[currentIndex]
mirror.children.formIndex(after: &currentIndex)
unsafe _collectAllReferencesInsideObjectImpl(
child,
references: &references,
visitedItems: &visitedItems)
}
}
// This is a namespace for runtime functions related to management
// of runtime function counters.
public // @testable
struct _RuntimeFunctionCounters {
#if os(Windows) && arch(x86_64)
public typealias RuntimeFunctionCountersUpdateHandler =
@convention(c) (_ object: UnsafeRawPointer, _ functionId: Int) -> Void
#else
public typealias RuntimeFunctionCountersUpdateHandler =
@convention(c) (_ object: UnsafeRawPointer, _ functionId: Int64) -> Void
#endif
public static let runtimeFunctionNames =
getRuntimeFunctionNames()
public static let runtimeFunctionCountersOffsets =
unsafe _RuntimeFunctionCounters.getRuntimeFunctionCountersOffsets()
public static let numRuntimeFunctionCounters =
Int(_RuntimeFunctionCounters.getNumRuntimeFunctionCounters())
public static let runtimeFunctionNameToIndex: [String: Int] =
getRuntimeFunctionNameToIndex()
/// Get the names of all runtime functions whose calls are being
/// tracked.
@_silgen_name("_swift_getRuntimeFunctionNames")
public static func _getRuntimeFunctionNames() ->
UnsafePointer<UnsafePointer<CChar>>
public static func getRuntimeFunctionNames() -> [String] {
let names = unsafe _RuntimeFunctionCounters._getRuntimeFunctionNames()
let numRuntimeFunctionCounters =
Int(_RuntimeFunctionCounters.getNumRuntimeFunctionCounters())
var functionNames: [String] = []
functionNames.reserveCapacity(numRuntimeFunctionCounters)
for index in 0..<numRuntimeFunctionCounters {
let name = unsafe String(cString: names[index])
functionNames.append(name)
}
return functionNames
}
/// Get the offsets of the collected runtime function counters inside
/// the state.
@_silgen_name("_swift_getRuntimeFunctionCountersOffsets")
public static func getRuntimeFunctionCountersOffsets() ->
UnsafePointer<UInt16>
/// Get the number of different runtime functions whose calls are being
/// tracked.
@_silgen_name("_swift_getNumRuntimeFunctionCounters")
public static func getNumRuntimeFunctionCounters() -> UInt64
/// Dump all per-object runtime function counters.
@_silgen_name("_swift_dumpObjectsRuntimeFunctionPointers")
public static func dumpObjectsRuntimeFunctionPointers()
@discardableResult
@_silgen_name("_swift_setGlobalRuntimeFunctionCountersUpdateHandler")
public static func setGlobalRuntimeFunctionCountersUpdateHandler(
handler: RuntimeFunctionCountersUpdateHandler?
) -> RuntimeFunctionCountersUpdateHandler?
/// Collect all references inside the object using Mirrors.
public static func collectAllReferencesInsideObject(_ value: Any) ->
[UnsafeRawPointer] {
var visited: [ObjectIdentifier: Int] = [:]
var references: [UnsafeRawPointer] = unsafe []
unsafe _collectAllReferencesInsideObjectImpl(
value, references: &references, visitedItems: &visited)
return unsafe references
}
/// Build a map from counter name to counter index inside the state struct.
internal static func getRuntimeFunctionNameToIndex() -> [String: Int] {
let runtimeFunctionNames = _RuntimeFunctionCounters.getRuntimeFunctionNames()
let numRuntimeFunctionCounters =
Int(_RuntimeFunctionCounters.getNumRuntimeFunctionCounters())
var runtimeFunctionNameToIndex: [String: Int] = [:]
runtimeFunctionNameToIndex.reserveCapacity(numRuntimeFunctionCounters)
for index in 0..<numRuntimeFunctionCounters {
let name = runtimeFunctionNames[index]
runtimeFunctionNameToIndex[name] = index
}
return runtimeFunctionNameToIndex
}
}
/// This protocol defines a set of operations for accessing runtime function
/// counters statistics.
public // @testable
protocol _RuntimeFunctionCountersStats: CustomDebugStringConvertible {
init()
/// Dump the current state of all counters.
func dump<T: TextOutputStream>(skipUnchanged: Bool, to: inout T)
/// Dump the diff between the current state and a different state of all
/// counters.
func dumpDiff<T: TextOutputStream>(
_ after: Self, skipUnchanged: Bool, to: inout T
)
/// Compute a diff between two states of runtime function counters.
/// Return a new state representing the diff.
func diff(_ other: Self) -> Self
/// Access counters by name.
subscript(_ counterName: String) -> UInt32 { get set }
/// Access counters by index.
subscript(_ index: Int) -> UInt32 { get set }
}
extension _RuntimeFunctionCountersStats {
/// Dump the current state of all counters.
public func dump(skipUnchanged: Bool) {
var output = _Stdout()
dump(skipUnchanged: skipUnchanged, to: &output)
}
/// Dump the diff between the current state and a different state of all
/// counters.
public func dumpDiff(_ after: Self, skipUnchanged: Bool) {
var output = _Stdout()
dumpDiff(after, skipUnchanged: skipUnchanged, to: &output)
}
}
extension _RuntimeFunctionCountersStats {
public var debugDescription: String {
var result = ""
dump(skipUnchanged: true, to: &result)
return result
}
}
// A helper type that encapsulates the logic for collecting runtime function
// counters. This type should not be used directly. You should use its
// wrappers GlobalRuntimeFunctionCountersState and
// ObjectRuntimeFunctionCountersState instead.
internal struct _RuntimeFunctionCountersState: _RuntimeFunctionCountersStats {
/// Reserve enough space for 64 elements.
typealias Counters =
(
UInt32, UInt32, UInt32, UInt32, UInt32,
UInt32, UInt32, UInt32, UInt32, UInt32,
UInt32, UInt32, UInt32, UInt32, UInt32,
UInt32, UInt32, UInt32, UInt32, UInt32,
UInt32, UInt32, UInt32, UInt32, UInt32,
UInt32, UInt32, UInt32, UInt32, UInt32,
UInt32, UInt32, UInt32, UInt32, UInt32,
UInt32, UInt32, UInt32, UInt32, UInt32,
UInt32, UInt32, UInt32, UInt32, UInt32,
UInt32, UInt32, UInt32, UInt32, UInt32,
UInt32, UInt32, UInt32, UInt32, UInt32,
UInt32, UInt32, UInt32, UInt32, UInt32,
UInt32, UInt32, UInt32, UInt32
)
private var counters: Counters = (
UInt32(0), UInt32(0), UInt32(0), UInt32(0), UInt32(0),
UInt32(0), UInt32(0), UInt32(0), UInt32(0), UInt32(0),
UInt32(0), UInt32(0), UInt32(0), UInt32(0), UInt32(0),
UInt32(0), UInt32(0), UInt32(0), UInt32(0), UInt32(0),
UInt32(0), UInt32(0), UInt32(0), UInt32(0), UInt32(0),
UInt32(0), UInt32(0), UInt32(0), UInt32(0), UInt32(0),
UInt32(0), UInt32(0), UInt32(0), UInt32(0), UInt32(0),
UInt32(0), UInt32(0), UInt32(0), UInt32(0), UInt32(0),
UInt32(0), UInt32(0), UInt32(0), UInt32(0), UInt32(0),
UInt32(0), UInt32(0), UInt32(0), UInt32(0), UInt32(0),
UInt32(0), UInt32(0), UInt32(0), UInt32(0), UInt32(0),
UInt32(0), UInt32(0), UInt32(0), UInt32(0), UInt32(0),
UInt32(0), UInt32(0), UInt32(0), UInt32(0)
)
// Use counter name as index.
subscript(_ counterName: String) -> UInt32 {
get {
if let index = _RuntimeFunctionCounters.runtimeFunctionNameToIndex[counterName] {
return self[index]
}
fatalError("Unknown counter name: \(counterName)")
}
set {
if let index = _RuntimeFunctionCounters.runtimeFunctionNameToIndex[counterName] {
self[index] = newValue
return
}
fatalError("Unknown counter name: \(counterName)")
}
}
subscript(_ index: Int) -> UInt32 {
@inline(never)
get {
if (index >= _RuntimeFunctionCounters.numRuntimeFunctionCounters) {
fatalError("Counter index should be in the range " +
"0..<\(_RuntimeFunctionCounters.numRuntimeFunctionCounters)")
}
var tmpCounters = counters
let counter: UInt32 = unsafe withUnsafePointer(to: &tmpCounters) { ptr in
return unsafe ptr.withMemoryRebound(to: UInt32.self, capacity: 64) { buf in
return unsafe buf[index]
}
}
return counter
}
@inline(never)
set {
if (index >= _RuntimeFunctionCounters.numRuntimeFunctionCounters) {
fatalError("Counter index should be in the range " +
"0..<\(_RuntimeFunctionCounters.numRuntimeFunctionCounters)")
}
unsafe withUnsafeMutablePointer(to: &counters) {
unsafe $0.withMemoryRebound(to: UInt32.self, capacity: 64) {
unsafe $0[index] = newValue
}
}
}
}
}
extension _RuntimeFunctionCounters {
@_silgen_name("_swift_getObjectRuntimeFunctionCounters")
internal static func getObjectRuntimeFunctionCounters(
_ object: UnsafeRawPointer, _ result: inout _RuntimeFunctionCountersState)
@_silgen_name("_swift_getGlobalRuntimeFunctionCounters")
internal static func getGlobalRuntimeFunctionCounters(
_ result: inout _RuntimeFunctionCountersState)
@_silgen_name("_swift_setGlobalRuntimeFunctionCounters")
internal static func setGlobalRuntimeFunctionCounters(
_ state: inout _RuntimeFunctionCountersState)
@_silgen_name("_swift_setObjectRuntimeFunctionCounters")
internal static func setObjectRuntimeFunctionCounters(
_ object: UnsafeRawPointer,
_ state: inout _RuntimeFunctionCountersState)
@discardableResult
@_silgen_name("_swift_setGlobalRuntimeFunctionCountersMode")
static
public // @testable
func setGlobalRuntimeFunctionCountersMode(enable: Bool) -> Bool
@discardableResult
@_silgen_name("_swift_setPerObjectRuntimeFunctionCountersMode")
static
public // @testable
func setPerObjectRuntimeFunctionCountersMode(enable: Bool) -> Bool
/// Enable runtime function counters updates by the runtime.
static
public // @testable
func enableRuntimeFunctionCountersUpdates(
mode: (globalMode: Bool, perObjectMode: Bool) = (true, true)) {
_RuntimeFunctionCounters.setGlobalRuntimeFunctionCountersMode(
enable: mode.globalMode)
_RuntimeFunctionCounters.setPerObjectRuntimeFunctionCountersMode(
enable: mode.perObjectMode)
}
/// Disable runtime function counters updates by the runtime.
static
public // @testable
func disableRuntimeFunctionCountersUpdates() ->
(globalMode: Bool, perObjectMode: Bool) {
let oldGlobalMode =
_RuntimeFunctionCounters.setGlobalRuntimeFunctionCountersMode(
enable: false)
let oldPerObjectMode =
_RuntimeFunctionCounters.setPerObjectRuntimeFunctionCountersMode(
enable: false)
return (oldGlobalMode, oldPerObjectMode)
}
}
extension _RuntimeFunctionCountersStats {
typealias Counters = _RuntimeFunctionCounters
@inline(never)
public // @testable
func dump<T: TextOutputStream>(skipUnchanged: Bool, to: inout T) {
for i in 0..<Counters.numRuntimeFunctionCounters {
if skipUnchanged && self[i] == 0 {
continue
}
print("counter \(i) : " +
"\(Counters.runtimeFunctionNames[i])" +
" at offset: " +
"\(unsafe Counters.runtimeFunctionCountersOffsets[i]):" +
" \(self[i])", to: &to)
}
}
@inline(never)
public // @testable
func dumpDiff<T: TextOutputStream>(
_ after: Self, skipUnchanged: Bool, to: inout T
) {
for i in 0..<Counters.numRuntimeFunctionCounters {
if self[i] == 0 && after[i] == 0 {
continue
}
if skipUnchanged && self[i] == after[i] {
continue
}
print("counter \(i) : " +
"\(Counters.runtimeFunctionNames[i])" +
" at offset: " +
"\(unsafe Counters.runtimeFunctionCountersOffsets[i]): " +
"before \(self[i]) " +
"after \(after[i])" + " diff=\(after[i]-self[i])", to: &to)
}
}
public // @testable
func diff(_ other: Self) -> Self {
var result = Self()
for i in 0..<Counters.numRuntimeFunctionCounters {
result[i] = other[i] - self[i]
}
return result
}
}
/// This type should be used to collect statistics about the global runtime
/// function pointers.
public // @testable
struct _GlobalRuntimeFunctionCountersState: _RuntimeFunctionCountersStats {
var state = _RuntimeFunctionCountersState()
public init() {
getGlobalRuntimeFunctionCounters()
}
mutating public func getGlobalRuntimeFunctionCounters() {
_RuntimeFunctionCounters.getGlobalRuntimeFunctionCounters(&state)
}
mutating public func setGlobalRuntimeFunctionCounters() {
_RuntimeFunctionCounters.setGlobalRuntimeFunctionCounters(&state)
}
public subscript(_ index: String) -> UInt32 {
get {
return state[index]
}
set {
state[index] = newValue
}
}
public subscript(_ index: Int) -> UInt32 {
get {
return state[index]
}
set {
state[index] = newValue
}
}
}
/// This type should be used to collect statistics about object runtime
/// function pointers.
public // @testable
struct _ObjectRuntimeFunctionCountersState: _RuntimeFunctionCountersStats {
var state = _RuntimeFunctionCountersState()
// Initialize with the counters for a given object.
public init(_ p: UnsafeRawPointer) {
unsafe getObjectRuntimeFunctionCounters(p)
}
public init() {
}
mutating public func getObjectRuntimeFunctionCounters(_ o: UnsafeRawPointer) {
unsafe _RuntimeFunctionCounters.getObjectRuntimeFunctionCounters(o, &state)
}
mutating public func setObjectRuntimeFunctionCounters(_ o: UnsafeRawPointer) {
unsafe _RuntimeFunctionCounters.setObjectRuntimeFunctionCounters(o, &state)
}
public subscript(_ index: String) -> UInt32 {
get {
return state[index]
}
set {
state[index] = newValue
}
}
public subscript(_ index: Int) -> UInt32 {
get {
return state[index]
}
set {
state[index] = newValue
}
}
}
/// Collects all references inside an object.
/// Runtime counters tracking is disabled for the duration of this operation
/// so that it does not affect those counters.
public // @testable
func _collectReferencesInsideObject(_ value: Any) -> [UnsafeRawPointer] {
let savedMode = _RuntimeFunctionCounters.disableRuntimeFunctionCountersUpdates()
// Collect all references inside the object
let refs = unsafe _RuntimeFunctionCounters.collectAllReferencesInsideObject(value)
_RuntimeFunctionCounters.enableRuntimeFunctionCountersUpdates(mode: savedMode)
return unsafe refs
}
/// A helper method to measure how global and per-object function counters
/// were changed during execution of a closure provided as a parameter.
/// Returns counter diffs for global counters and for the object-specific
/// counters related to a given object.
public // @testable
func _measureRuntimeFunctionCountersDiffs(
objects: [UnsafeRawPointer], _ body: () -> Void) ->
(_GlobalRuntimeFunctionCountersState, [_ObjectRuntimeFunctionCountersState]) {
let savedMode =
_RuntimeFunctionCounters.disableRuntimeFunctionCountersUpdates()
let globalCountersBefore = _GlobalRuntimeFunctionCountersState()
var objectsCountersBefore: [_ObjectRuntimeFunctionCountersState] = []
for unsafe object in unsafe objects {
unsafe objectsCountersBefore.append(_ObjectRuntimeFunctionCountersState(object))
}
// Enable counters updates.
_RuntimeFunctionCounters.enableRuntimeFunctionCountersUpdates(
mode: (globalMode: true, perObjectMode: true))
// Execute the provided user's code.
body()
// Disable counters updates.
_RuntimeFunctionCounters.enableRuntimeFunctionCountersUpdates(
mode: (globalMode: false, perObjectMode: false))
let globalCountersAfter = _GlobalRuntimeFunctionCountersState()
var objectsCountersDiff: [_ObjectRuntimeFunctionCountersState] = []
for unsafe (idx, object) in unsafe objects.enumerated() {
let objectCountersAfter = unsafe _ObjectRuntimeFunctionCountersState(object)
objectsCountersDiff.append(
objectsCountersBefore[idx].diff(objectCountersAfter))
}
_RuntimeFunctionCounters.enableRuntimeFunctionCountersUpdates(
mode: savedMode)
return (globalCountersBefore.diff(globalCountersAfter), objectsCountersDiff)
}
#endif