blob: 0ac262661e3757a828f361270b073f656afe48ee [file] [log] [blame]
//===--- 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: &currentIndex)
_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")
public static func _getRuntimeFunctionNames() ->
UnsafePointer<UnsafePointer<CChar>>
public static 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")
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() -> Int
/// 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] = []
_collectAllReferencesInsideObjectImpl(
value, references: &references, visitedItems: &visited)
return 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 =
_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")
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: " +
"\(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