mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
The old-style mirrors were the basis for non-custom, reflection based Mirrors. As part of removing old-style mirrors, reflection based Mirrors are reimplemented without them. Reflection.mm is deleted and replaced with ReflectionMirror.mm, which borrows a chunk of the old code but presents a simpler API to the Swift side. ReflectionMirror.swift then uses that API to implement a reflection-based Mirror init. rdar://problem/20356017
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)
|
|
ref = UnsafeRawPointer(Unmanaged.passUnretained(toAnyObject).toOpaque())
|
|
id = ObjectIdentifier(toAnyObject)
|
|
} else if type(of: value) is Builtin.BridgeObject.Type {
|
|
ref = UnsafeRawPointer(
|
|
Builtin.bridgeToRawPointer(value as! Builtin.BridgeObject))
|
|
id = nil
|
|
} else if type(of: value) is Builtin.NativeObject.Type {
|
|
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)
|
|
ref = nil
|
|
} else {
|
|
id = nil
|
|
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 = ref {
|
|
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)
|
|
_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 =
|
|
_RuntimeFunctionCounters.getRuntimeFunctionCountersOffsets()
|
|
public static let numRuntimeFunctionCounters =
|
|
_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")
|
|
static public func _getRuntimeFunctionNames() ->
|
|
UnsafePointer<UnsafePointer<CChar>>
|
|
|
|
static public func getRuntimeFunctionNames() -> [String] {
|
|
let names = _RuntimeFunctionCounters._getRuntimeFunctionNames()
|
|
let numRuntimeFunctionCounters =
|
|
_RuntimeFunctionCounters.getNumRuntimeFunctionCounters()
|
|
var functionNames : [String] = []
|
|
functionNames.reserveCapacity(numRuntimeFunctionCounters)
|
|
for index in 0..<numRuntimeFunctionCounters {
|
|
let name = 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")
|
|
static public func getRuntimeFunctionCountersOffsets() ->
|
|
UnsafePointer<UInt16>
|
|
|
|
/// Get the number of different runtime functions whose calls are being
|
|
/// tracked.
|
|
@_silgen_name("_swift_getNumRuntimeFunctionCounters")
|
|
static public func getNumRuntimeFunctionCounters() -> Int
|
|
|
|
/// Dump all per-object runtime function counters.
|
|
@_silgen_name("_swift_dumpObjectsRuntimeFunctionPointers")
|
|
static public func dumpObjectsRuntimeFunctionPointers()
|
|
|
|
@discardableResult
|
|
@_silgen_name("_swift_setGlobalRuntimeFunctionCountersUpdateHandler")
|
|
static public func setGlobalRuntimeFunctionCountersUpdateHandler(
|
|
handler: RuntimeFunctionCountersUpdateHandler?
|
|
) -> RuntimeFunctionCountersUpdateHandler?
|
|
|
|
/// Collect all references inside the object using Mirrors.
|
|
static public func collectAllReferencesInsideObject(_ value: Any) ->
|
|
[UnsafeRawPointer] {
|
|
var visited : [ObjectIdentifier : Int] = [:]
|
|
var references: [UnsafeRawPointer] = []
|
|
_collectAllReferencesInsideObjectImpl(
|
|
value, references: &references, visitedItems: &visited)
|
|
return references
|
|
}
|
|
|
|
/// Build a map from counter name to counter index inside the state struct.
|
|
static internal func getRuntimeFunctionNameToIndex() -> [String : Int] {
|
|
let runtimeFunctionNames = _RuntimeFunctionCounters.getRuntimeFunctionNames()
|
|
let numRuntimeFunctionCounters =
|
|
_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 = withUnsafePointer(to: &tmpCounters) { ptr in
|
|
return ptr.withMemoryRebound(to: UInt32.self, capacity: 64) { buf in
|
|
return buf[index]
|
|
}
|
|
}
|
|
return counter
|
|
}
|
|
|
|
@inline(never)
|
|
set {
|
|
if (index >= _RuntimeFunctionCounters.numRuntimeFunctionCounters) {
|
|
fatalError("Counter index should be in the range " +
|
|
"0..<\(_RuntimeFunctionCounters.numRuntimeFunctionCounters)")
|
|
}
|
|
withUnsafeMutablePointer(to: &counters) {
|
|
$0.withMemoryRebound(to: UInt32.self, capacity: 64) {
|
|
$0[index] = newValue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension _RuntimeFunctionCounters {
|
|
@_silgen_name("_swift_getObjectRuntimeFunctionCounters")
|
|
static internal func getObjectRuntimeFunctionCounters(
|
|
_ object: UnsafeRawPointer, _ result: inout _RuntimeFunctionCountersState)
|
|
|
|
@_silgen_name("_swift_getGlobalRuntimeFunctionCounters")
|
|
static internal func getGlobalRuntimeFunctionCounters(
|
|
_ result: inout _RuntimeFunctionCountersState)
|
|
|
|
@_silgen_name("_swift_setGlobalRuntimeFunctionCounters")
|
|
static internal func setGlobalRuntimeFunctionCounters(
|
|
_ state: inout _RuntimeFunctionCountersState)
|
|
|
|
@_silgen_name("_swift_setObjectRuntimeFunctionCounters")
|
|
static internal 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: " +
|
|
"\(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: " +
|
|
"\(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) {
|
|
getObjectRuntimeFunctionCounters(p)
|
|
}
|
|
|
|
public init() {
|
|
}
|
|
|
|
mutating public func getObjectRuntimeFunctionCounters(_ o: UnsafeRawPointer) {
|
|
_RuntimeFunctionCounters.getObjectRuntimeFunctionCounters(o, &state)
|
|
}
|
|
|
|
mutating public func setObjectRuntimeFunctionCounters(_ o: UnsafeRawPointer) {
|
|
_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 = _RuntimeFunctionCounters.collectAllReferencesInsideObject(value)
|
|
_RuntimeFunctionCounters.enableRuntimeFunctionCountersUpdates(mode: savedMode)
|
|
return 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 object in objects {
|
|
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 (idx, object) in objects.enumerated() {
|
|
let objectCountersAfter = _ObjectRuntimeFunctionCountersState(object)
|
|
objectsCountersDiff.append(
|
|
objectsCountersBefore[idx].diff(objectCountersAfter))
|
|
}
|
|
|
|
_RuntimeFunctionCounters.enableRuntimeFunctionCountersUpdates(
|
|
mode: savedMode)
|
|
return (globalCountersBefore.diff(globalCountersAfter), objectsCountersDiff)
|
|
}
|
|
|
|
#endif
|