mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
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
550 lines
19 KiB
Swift
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: ¤tIndex)
|
|
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
|