//===--- RuntimeInvocationsTracking.cpp - Track runtime invocations -------===// // // 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 // //===----------------------------------------------------------------------===// // // Track invocations of Swift runtime functions. This can be used for performance // analysis. // //===----------------------------------------------------------------------===// #include #include "RuntimeInvocationsTracking.h" #include "swift/Basic/Lazy.h" #include "swift/Runtime/HeapObject.h" #include "swift/Threading/Mutex.h" #include "llvm/ADT/DenseMap.h" #if defined(SWIFT_ENABLE_RUNTIME_FUNCTION_COUNTERS) #define SWIFT_RT_FUNCTION_INVOCATION_COUNTER_NAME(RT_FUNCTION) \ invocationCounter_##RT_FUNCTION namespace swift { // Define counters used for tracking the total number of invocations of runtime // functions. struct RuntimeFunctionCountersState { #define FUNCTION_TO_TRACK(RT_FUNCTION) \ std::uint32_t SWIFT_RT_FUNCTION_INVOCATION_COUNTER_NAME(RT_FUNCTION) = 0; // Provide one counter per runtime function being tracked. #include "RuntimeInvocationsTracking.def" }; } // end namespace swift /// If set, global runtime function counters should be tracked. static bool UpdatePerObjectRuntimeFunctionCounters = false; /// If set, per object runtime function counters should be tracked. static bool UpdateGlobalRuntimeFunctionCounters = false; /// TODO: Add support for enabling/disabling counters on a per object basis? /// Global set of counters tracking the total number of runtime invocations. struct RuntimeFunctionCountersStateSentinel { RuntimeFunctionCountersState State; LazyMutex Lock; }; static RuntimeFunctionCountersStateSentinel RuntimeGlobalFunctionCountersState; /// The object state cache mapping objects to the collected state associated with /// them. struct RuntimeObjectCacheSentinel { llvm::DenseMap Cache; Mutex Lock; }; static Lazy RuntimeObjectStateCache; static const char *RuntimeFunctionNames[] { /// Define names of runtime functions. #define FUNCTION_TO_TRACK(RT_FUNCTION) #RT_FUNCTION, #include "RuntimeInvocationsTracking.def" nullptr }; #define RT_FUNCTION_ID(RT_FUNCTION) ID_##RT_FUNCTION /// Define an enum where each enumerator corresponds to a runtime function being /// tracked. Their order is the same as the order of the counters in the /// RuntimeObjectState structure. enum RuntimeFunctionNamesIDs : std::uint32_t { /// Defines names of enum cases for each function being tracked. #define FUNCTION_TO_TRACK(RT_FUNCTION) RT_FUNCTION_ID(RT_FUNCTION), #include "RuntimeInvocationsTracking.def" ID_LastRuntimeFunctionName, }; /// The global handler to be invoked on runtime function counters updates. static RuntimeFunctionCountersUpdateHandler GlobalRuntimeFunctionCountersUpdateHandler; /// The offsets of the runtime function counters being tracked inside the /// RuntimeObjectState structure. The array is indexed by /// the enumerators from RuntimeFunctionNamesIDs. static std::uint16_t RuntimeFunctionCountersOffsets[] = { /// Define offset for each function being tracked. #define FUNCTION_TO_TRACK(RT_FUNCTION) \ (sizeof(std::uint16_t) * (unsigned)RT_FUNCTION_ID(RT_FUNCTION)), #include "RuntimeInvocationsTracking.def" }; /// Define implementations of tracking functions. /// TODO: Track only objects that were registered for tracking? /// TODO: Perform atomic increments? #define FUNCTION_TO_TRACK(RT_FUNCTION) \ void SWIFT_RT_TRACK_INVOCATION_NAME(RT_FUNCTION)(HeapObject * object) { \ /* Update global counters. */ \ if (UpdateGlobalRuntimeFunctionCounters) { \ LazyMutex::ScopedLock lock(RuntimeGlobalFunctionCountersState.Lock); \ RuntimeGlobalFunctionCountersState.State \ .SWIFT_RT_FUNCTION_INVOCATION_COUNTER_NAME(RT_FUNCTION)++; \ if (GlobalRuntimeFunctionCountersUpdateHandler) { \ auto oldGlobalMode = _swift_setGlobalRuntimeFunctionCountersMode(0); \ auto oldPerObjectMode = \ _swift_setPerObjectRuntimeFunctionCountersMode(0); \ GlobalRuntimeFunctionCountersUpdateHandler( \ object, RT_FUNCTION_ID(RT_FUNCTION)); \ _swift_setGlobalRuntimeFunctionCountersMode(oldGlobalMode); \ _swift_setPerObjectRuntimeFunctionCountersMode(oldPerObjectMode); \ } \ } \ /* Update per object counters. */ \ if (UpdatePerObjectRuntimeFunctionCounters && object) { \ auto &theSentinel = RuntimeObjectStateCache.get(); \ Mutex::ScopedLock lock(theSentinel.Lock); \ theSentinel.Cache[object].SWIFT_RT_FUNCTION_INVOCATION_COUNTER_NAME( \ RT_FUNCTION)++; \ /* TODO: Remember the order/history of operations? */ \ } \ } #include "RuntimeInvocationsTracking.def" /// Public APIs /// Get the runtime object state associated with an object. void _swift_getObjectRuntimeFunctionCounters( HeapObject *object, RuntimeFunctionCountersState *result) { auto &theSentinel = RuntimeObjectStateCache.get(); Mutex::ScopedLock lock(theSentinel.Lock); *result = theSentinel.Cache[object]; } /// Set the runtime object state associated with an object from a provided /// state. void _swift_setObjectRuntimeFunctionCounters( HeapObject *object, RuntimeFunctionCountersState *state) { auto &theSentinel = RuntimeObjectStateCache.get(); Mutex::ScopedLock lock(theSentinel.Lock); theSentinel.Cache[object] = *state; } /// Get the global runtime state containing the total numbers of invocations for /// each runtime function of interest. void _swift_getGlobalRuntimeFunctionCounters( RuntimeFunctionCountersState *result) { LazyMutex::ScopedLock lock(RuntimeGlobalFunctionCountersState.Lock); *result = RuntimeGlobalFunctionCountersState.State; } /// Set the global runtime state of function pointers from a provided state. void _swift_setGlobalRuntimeFunctionCounters( RuntimeFunctionCountersState *state) { LazyMutex::ScopedLock lock(RuntimeGlobalFunctionCountersState.Lock); RuntimeGlobalFunctionCountersState.State = *state; } /// Return the names of the runtime functions being tracked. /// Their order is the same as the order of the counters in the /// RuntimeObjectState structure. All these strings are null terminated. const char **_swift_getRuntimeFunctionNames() { return RuntimeFunctionNames; } /// Return the offsets of the runtime function counters being tracked. /// Their order is the same as the order of the counters in the /// RuntimeObjectState structure. const std::uint16_t *_swift_getRuntimeFunctionCountersOffsets() { return RuntimeFunctionCountersOffsets; } /// Return the number of runtime functions being tracked. std::uint64_t _swift_getNumRuntimeFunctionCounters() { return ID_LastRuntimeFunctionName; } static void _swift_dumpRuntimeCounters(RuntimeFunctionCountersState *State) { std::uint32_t tmp; /// Define how to dump the counter for a given runtime function. #define FUNCTION_TO_TRACK(RT_FUNCTION) \ tmp = State->SWIFT_RT_FUNCTION_INVOCATION_COUNTER_NAME(RT_FUNCTION); \ if (tmp != 0) \ printf("%s = %d\n", \ RuntimeFunctionNames[(int)RT_FUNCTION_ID(RT_FUNCTION)], tmp); #include "RuntimeInvocationsTracking.def" } /// Dump all per-object runtime function pointers. void _swift_dumpObjectsRuntimeFunctionPointers() { auto &theSentinel = RuntimeObjectStateCache.get(); Mutex::ScopedLock lock(theSentinel.Lock); for (auto &Pair : theSentinel.Cache) { printf("\n\nRuntime counters for object at address %p:\n", Pair.getFirst()); _swift_dumpRuntimeCounters(&Pair.getSecond()); printf("\n"); } } /// Set mode for global runtime function counters. /// Return the old value of this flag. int _swift_setGlobalRuntimeFunctionCountersMode(int mode) { int oldMode = UpdateGlobalRuntimeFunctionCounters; UpdateGlobalRuntimeFunctionCounters = mode ? 1 : 0; return oldMode; } /// Set mode for per object runtime function counters. /// Return the old value of this flag. int _swift_setPerObjectRuntimeFunctionCountersMode(int mode) { int oldMode = UpdatePerObjectRuntimeFunctionCounters; UpdatePerObjectRuntimeFunctionCounters = mode ? 1 : 0; return oldMode; } /// Add the ability to call custom handlers when a counter /// is being updated. The handler should take the object and may be /// the name of the runtime function as parameters. And this handler /// could e.g. check some conditions and stop the program under /// a debugger if a certain condition is met, like a refcount has /// reached a certain value. /// We could allow for setting global handlers or even per-object /// handlers. RuntimeFunctionCountersUpdateHandler _swift_setGlobalRuntimeFunctionCountersUpdateHandler( RuntimeFunctionCountersUpdateHandler handler) { auto oldHandler = GlobalRuntimeFunctionCountersUpdateHandler; GlobalRuntimeFunctionCountersUpdateHandler = handler; return oldHandler; } /// TODO: Provide an API to remove any counters related to a specific object /// or all objects. /// This is useful if you want to reset the stats for some/all objects. #endif