https://github.com/mtrofin updated https://github.com/llvm/llvm-project/pull/131417
>From 5916e7d345d84368b18cc08e32e2c63e4b967ec3 Mon Sep 17 00:00:00 2001 From: Mircea Trofin <mtro...@google.com> Date: Fri, 14 Mar 2025 15:59:22 -0700 Subject: [PATCH] [ctxprof] Track unhandled call targets --- .../lib/ctx_profile/CtxInstrProfiling.cpp | 29 +++++++++++++++---- .../lib/ctx_profile/CtxInstrProfiling.h | 20 +++++++++++-- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/compiler-rt/lib/ctx_profile/CtxInstrProfiling.cpp b/compiler-rt/lib/ctx_profile/CtxInstrProfiling.cpp index 6ef7076d93e31..49b5e8167fb95 100644 --- a/compiler-rt/lib/ctx_profile/CtxInstrProfiling.cpp +++ b/compiler-rt/lib/ctx_profile/CtxInstrProfiling.cpp @@ -246,22 +246,37 @@ ContextNode *getFlatProfile(FunctionData &Data, GUID Guid, ContextNode *getUnhandledContext(FunctionData &Data, GUID Guid, uint32_t NumCounters) { - // 1) if we are under a root (regardless if this thread is collecting or not a + + // 1) if we are currently collecting a contextual profile, fetch a ContextNode + // in the `Unhandled` set. We want to do this regardless of `ProfilingStarted` + // to (hopefully) offset the penalty of creating these contexts to before + // profiling. + // + // 2) if we are under a root (regardless if this thread is collecting or not a // contextual profile for that root), do not collect a flat profile. We want // to keep flat profiles only for activations that can't happen under a root, // to avoid confusing profiles. We can, for example, combine flattened and // flat profiles meaningfully, as we wouldn't double-count anything. // - // 2) to avoid lengthy startup, don't bother with flat profiles until the + // 3) to avoid lengthy startup, don't bother with flat profiles until the // profiling started. We would reset them anyway when profiling starts. // HOWEVER. This does lose profiling for message pumps: those functions are // entered once and never exit. They should be assumed to be entered before // profiling starts - because profiling should start after the server is up // and running (which is equivalent to "message pumps are set up"). - if (IsUnderContext || !__sanitizer::atomic_load_relaxed(&ProfilingStarted)) - return TheScratchContext; - return markAsScratch( - onContextEnter(*getFlatProfile(Data, Guid, NumCounters))); + ContextRoot *R = __llvm_ctx_profile_current_context_root; + if (!R) { + if (IsUnderContext || !__sanitizer::atomic_load_relaxed(&ProfilingStarted)) + return TheScratchContext; + else + return markAsScratch( + onContextEnter(*getFlatProfile(Data, Guid, NumCounters))); + } + auto It = R->Unhandled.insert({Guid, nullptr}); + if (It.second) + It.first->second = + getCallsiteSlow(Guid, &R->FirstUnhandledCalleeNode, NumCounters, 0); + return markAsScratch(onContextEnter(*It.first->second)); } ContextNode *__llvm_ctx_profile_get_context(FunctionData *Data, void *Callee, @@ -396,6 +411,8 @@ void __llvm_ctx_profile_start_collection() { ++NumMemUnits; resetContextNode(*Root->FirstNode); + if (Root->FirstUnhandledCalleeNode) + resetContextNode(*Root->FirstUnhandledCalleeNode); __sanitizer::atomic_store_relaxed(&Root->TotalEntries, 0); } __sanitizer::atomic_store_relaxed(&ProfilingStarted, true); diff --git a/compiler-rt/lib/ctx_profile/CtxInstrProfiling.h b/compiler-rt/lib/ctx_profile/CtxInstrProfiling.h index 6bb954da950c4..5478a3b67ac25 100644 --- a/compiler-rt/lib/ctx_profile/CtxInstrProfiling.h +++ b/compiler-rt/lib/ctx_profile/CtxInstrProfiling.h @@ -10,6 +10,7 @@ #define CTX_PROFILE_CTXINSTRPROFILING_H_ #include "CtxInstrContextNode.h" +#include "sanitizer_common/sanitizer_dense_map.h" #include "sanitizer_common/sanitizer_mutex.h" #include <sanitizer/common_interface_defs.h> @@ -73,9 +74,8 @@ static_assert(alignof(Arena) == ExpectedAlignment); // sure the inlined vectors are appropriately aligned. static_assert(alignof(ContextNode) == ExpectedAlignment); -/// ContextRoots are allocated by LLVM for entrypoints. LLVM is only concerned -/// with allocating and zero-initializing the global value (as in, GlobalValue) -/// for it. +/// ContextRoots hold memory and the start of the contextual profile tree for a +/// root function. struct ContextRoot { ContextNode *FirstNode = nullptr; Arena *FirstMemBlock = nullptr; @@ -84,6 +84,20 @@ struct ContextRoot { // Count the number of entries - regardless if we could take the `Taken` mutex ::__sanitizer::atomic_uint64_t TotalEntries = {}; + // Profiles for functions we encounter when collecting a contexutal profile, + // that are not associated with a callsite. This is expected to happen for + // signal handlers, but it also - problematically - currently happens for + // mem{memset|copy|move|set}, which are currently inserted after profile + // instrumentation. + // `Unhandled` serves 2 purposes: + // 1. identifying such cases (like the memops) + // 2. collecting a profile for them, which can be at least used as a flat + // profile + ::__sanitizer::DenseMap<GUID, ContextNode *> Unhandled; + // Keep the unhandled contexts in a list, as we allocate them, as it makes it + // simpler to send to the writer when the profile is fetched. + ContextNode *FirstUnhandledCalleeNode = nullptr; + // Taken is used to ensure only one thread traverses the contextual graph - // either to read it or to write it. On server side, the same entrypoint will // be entered by numerous threads, but over time, the profile aggregated by _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits