mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Also update how the variable is managed in the build system to allow the test to be conditional based on it, and make it more natural to set it on the command line.
552 lines
19 KiB
Swift
552 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(
|
|
legacy: _reflect(value),
|
|
subjectType: type(of: 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
|