#include "CtxInstrProfiling.h"
#include "RootAutoDetector.h"
#include "sanitizer_common/sanitizer_allocator_internal.h"
#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_atomic_clang.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_dense_map.h"
#include "sanitizer_common/sanitizer_libc.h"
#include "sanitizer_common/sanitizer_mutex.h"
#include "sanitizer_common/sanitizer_placement_new.h"
#include "sanitizer_common/sanitizer_thread_safety.h"
#include "sanitizer_common/sanitizer_vector.h"
#include <assert.h>
using namespace __ctx_profile;
namespace {
__sanitizer::SpinMutex AllContextsMutex;
SANITIZER_GUARDED_BY(AllContextsMutex)
__sanitizer::Vector<ContextRoot *> AllContextRoots;
__sanitizer::atomic_uintptr_t AllFunctionsData = {};
__sanitizer::SpinMutex FlatCtxArenaMutex;
SANITIZER_GUARDED_BY(FlatCtxArenaMutex)
Arena *FlatCtxArenaHead = nullptr;
SANITIZER_GUARDED_BY(FlatCtxArenaMutex)
Arena *FlatCtxArena = nullptr;
__thread bool IsUnderContext = false;
__sanitizer::atomic_uint8_t ProfilingStarted = {};
__sanitizer::atomic_uintptr_t RootDetector = {};
RootAutoDetector *getRootDetector() {
return reinterpret_cast<RootAutoDetector *>(
__sanitizer::atomic_load_relaxed(&RootDetector));
}
ContextNode *markAsScratch(const ContextNode *Ctx) {
return reinterpret_cast<ContextNode *>(reinterpret_cast<uint64_t>(Ctx) | 1);
}
template <typename T> inline T consume(T &V) {
auto R = V;
V = {0};
return R;
}
constexpr size_t kPower = 20;
constexpr size_t kBuffSize = 1 << kPower;
size_t getArenaAllocSize(size_t Needed) {
if (Needed >= kBuffSize)
return 2 * Needed;
return kBuffSize;
}
bool validate(const ContextRoot *Root) {
__sanitizer::DenseMap<uint64_t, bool> ContextStartAddrs;
for (const auto *Mem = Root->FirstMemBlock; Mem; Mem = Mem->next()) {
const auto *Pos = Mem->start();
while (Pos < Mem->pos()) {
const auto *Ctx = reinterpret_cast<const ContextNode *>(Pos);
if (!ContextStartAddrs.insert({reinterpret_cast<uint64_t>(Ctx), true})
.second)
return false;
Pos += Ctx->size();
}
}
for (const auto *Mem = Root->FirstMemBlock; Mem; Mem = Mem->next()) {
const auto *Pos = Mem->start();
while (Pos < Mem->pos()) {
const auto *Ctx = reinterpret_cast<const ContextNode *>(Pos);
for (uint32_t I = 0; I < Ctx->callsites_size(); ++I)
for (auto *Sub = Ctx->subContexts()[I]; Sub; Sub = Sub->next())
if (!ContextStartAddrs.find(reinterpret_cast<uint64_t>(Sub)))
return false;
Pos += Ctx->size();
}
}
return true;
}
inline ContextNode *allocContextNode(char *Place, GUID Guid,
uint32_t NumCounters,
uint32_t NumCallsites,
ContextNode *Next = nullptr) {
assert(reinterpret_cast<uint64_t>(Place) % ExpectedAlignment == 0);
return new (Place) ContextNode(Guid, NumCounters, NumCallsites, Next);
}
void resetContextNode(ContextNode &Node) {
for (uint32_t I = 0; I < Node.counters_size(); ++I)
Node.counters()[I] = 0;
for (uint32_t I = 0; I < Node.callsites_size(); ++I)
for (auto *Next = Node.subContexts()[I]; Next; Next = Next->next())
resetContextNode(*Next);
}
ContextNode *onContextEnter(ContextNode &Node) {
++Node.counters()[0];
return &Node;
}
}
__thread char __Buffer[kBuffSize] = {0};
#define TheScratchContext \
markAsScratch(reinterpret_cast<ContextNode *>(__Buffer))
__thread void *volatile __llvm_ctx_profile_expected_callee[2] = {nullptr,
nullptr};
__thread ContextNode **volatile __llvm_ctx_profile_callsite[2] = {0, 0};
__thread ContextRoot *volatile __llvm_ctx_profile_current_context_root =
nullptr;
Arena::Arena(uint32_t Size) : Size(Size) {
__sanitizer::internal_memset(start(), 0, Size);
}
Arena *Arena::allocateNewArena(size_t Size, Arena *Prev) {
assert(!Prev || Prev->Next == nullptr);
Arena *NewArena = new (__sanitizer::InternalAlloc(
Size + sizeof(Arena), nullptr, ExpectedAlignment))
Arena(Size);
if (Prev)
Prev->Next = NewArena;
return NewArena;
}
void Arena::freeArenaList(Arena *&A) {
assert(A);
for (auto *I = A; I != nullptr;) {
auto *Current = I;
I = I->Next;
__sanitizer::InternalFree(Current);
}
A = nullptr;
}
ContextNode *getCallsiteSlow(GUID Guid, ContextNode **InsertionPoint,
uint32_t NumCounters, uint32_t NumCallsites) {
auto AllocSize = ContextNode::getAllocSize(NumCounters, NumCallsites);
auto *Mem = __llvm_ctx_profile_current_context_root->CurrentMem;
char *AllocPlace = Mem->tryBumpAllocate(AllocSize);
if (!AllocPlace) {
__llvm_ctx_profile_current_context_root->CurrentMem = Mem =
Mem->allocateNewArena(getArenaAllocSize(AllocSize), Mem);
AllocPlace = Mem->tryBumpAllocate(AllocSize);
}
auto *Ret = allocContextNode(AllocPlace, Guid, NumCounters, NumCallsites,
*InsertionPoint);
*InsertionPoint = Ret;
return Ret;
}
ContextNode *getFlatProfile(FunctionData &Data, void *Callee, GUID Guid,
uint32_t NumCounters) {
if (ContextNode *Existing = Data.FlatCtx)
return Existing;
{
__sanitizer::GenericScopedLock<__sanitizer::StaticSpinMutex> L(&Data.Mutex);
if (ContextNode *Existing = Data.FlatCtx)
return Existing;
auto NeededSize = ContextNode::getAllocSize(NumCounters, 0);
char *AllocBuff = nullptr;
{
__sanitizer::GenericScopedLock<__sanitizer::SpinMutex> FL(
&FlatCtxArenaMutex);
if (FlatCtxArena)
AllocBuff = FlatCtxArena->tryBumpAllocate(NeededSize);
if (!AllocBuff) {
FlatCtxArena = Arena::allocateNewArena(getArenaAllocSize(NeededSize),
FlatCtxArena);
AllocBuff = FlatCtxArena->tryBumpAllocate(NeededSize);
}
if (!FlatCtxArenaHead)
FlatCtxArenaHead = FlatCtxArena;
}
auto *Ret = allocContextNode(AllocBuff, Guid, NumCounters, 0);
Data.FlatCtx = Ret;
Data.EntryAddress = Callee;
Data.Next = reinterpret_cast<FunctionData *>(
__sanitizer::atomic_load_relaxed(&AllFunctionsData));
while (!__sanitizer::atomic_compare_exchange_strong(
&AllFunctionsData, reinterpret_cast<uintptr_t *>(&Data.Next),
reinterpret_cast<uintptr_t>(&Data),
__sanitizer::memory_order_release)) {
}
}
return Data.FlatCtx;
}
void setupContext(ContextRoot *Root, GUID Guid, uint32_t NumCounters,
uint32_t NumCallsites) {
__sanitizer::GenericScopedLock<__sanitizer::SpinMutex> Lock(
&AllContextsMutex);
if (Root->FirstMemBlock)
return;
const auto Needed = ContextNode::getAllocSize(NumCounters, NumCallsites);
auto *M = Arena::allocateNewArena(getArenaAllocSize(Needed));
Root->FirstMemBlock = M;
Root->CurrentMem = M;
Root->FirstNode = allocContextNode(M->tryBumpAllocate(Needed), Guid,
NumCounters, NumCallsites);
AllContextRoots.PushBack(Root);
}
ContextRoot *FunctionData::getOrAllocateContextRoot() {
auto *Root = CtxRoot;
if (!canBeRoot(Root))
return Root;
if (Root)
return Root;
__sanitizer::GenericScopedLock<__sanitizer::StaticSpinMutex> L(&Mutex);
Root = CtxRoot;
if (!Root) {
Root = new (__sanitizer::InternalAlloc(sizeof(ContextRoot))) ContextRoot();
CtxRoot = Root;
}
assert(Root);
return Root;
}
ContextNode *tryStartContextGivenRoot(ContextRoot *Root, GUID Guid,
uint32_t Counters, uint32_t Callsites)
SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
IsUnderContext = true;
__sanitizer::atomic_fetch_add(&Root->TotalEntries, 1,
__sanitizer::memory_order_relaxed);
if (!Root->FirstMemBlock) {
setupContext(Root, Guid, Counters, Callsites);
}
if (Root->Taken.TryLock()) {
__llvm_ctx_profile_current_context_root = Root;
onContextEnter(*Root->FirstNode);
return Root->FirstNode;
}
__llvm_ctx_profile_current_context_root = nullptr;
return TheScratchContext;
}
ContextNode *getUnhandledContext(FunctionData &Data, void *Callee, GUID Guid,
uint32_t NumCounters, uint32_t NumCallsites,
ContextRoot *CtxRoot) {
if (!CtxRoot) {
if (auto *RAD = getRootDetector())
RAD->sample();
else if (auto *CR = Data.CtxRoot) {
if (canBeRoot(CR))
return tryStartContextGivenRoot(CR, Guid, NumCounters, NumCallsites);
}
if (IsUnderContext || !__sanitizer::atomic_load_relaxed(&ProfilingStarted))
return TheScratchContext;
else
return markAsScratch(
onContextEnter(*getFlatProfile(Data, Callee, Guid, NumCounters)));
}
auto [Iter, Ins] = CtxRoot->Unhandled.insert({Guid, nullptr});
if (Ins)
Iter->second = getCallsiteSlow(Guid, &CtxRoot->FirstUnhandledCalleeNode,
NumCounters, 0);
return markAsScratch(onContextEnter(*Iter->second));
}
ContextNode *__llvm_ctx_profile_get_context(FunctionData *Data, void *Callee,
GUID Guid, uint32_t NumCounters,
uint32_t NumCallsites) {
auto *CtxRoot = __llvm_ctx_profile_current_context_root;
if (!CtxRoot)
return getUnhandledContext(*Data, Callee, Guid, NumCounters, NumCallsites,
nullptr);
auto **CallsiteContext = consume(__llvm_ctx_profile_callsite[0]);
if (!CallsiteContext || isScratch(CallsiteContext))
return getUnhandledContext(*Data, Callee, Guid, NumCounters, NumCallsites,
CtxRoot);
auto *ExpectedCallee = consume(__llvm_ctx_profile_expected_callee[0]);
if (ExpectedCallee != Callee)
return getUnhandledContext(*Data, Callee, Guid, NumCounters, NumCallsites,
CtxRoot);
auto *Callsite = *CallsiteContext;
while (Callsite && Callsite->guid() != Guid) {
Callsite = Callsite->next();
}
auto *Ret = Callsite ? Callsite
: getCallsiteSlow(Guid, CallsiteContext, NumCounters,
NumCallsites);
if (Ret->callsites_size() != NumCallsites ||
Ret->counters_size() != NumCounters)
__sanitizer::Printf("[ctxprof] Returned ctx differs from what's asked: "
"Context: %p, Asked: %lu %u %u, Got: %lu %u %u \n",
reinterpret_cast<void *>(Ret), Guid, NumCallsites,
NumCounters, Ret->guid(), Ret->callsites_size(),
Ret->counters_size());
onContextEnter(*Ret);
return Ret;
}
ContextNode *__llvm_ctx_profile_start_context(FunctionData *FData, GUID Guid,
uint32_t Counters,
uint32_t Callsites) {
auto *Root = FData->getOrAllocateContextRoot();
assert(canBeRoot(Root));
return tryStartContextGivenRoot(Root, Guid, Counters, Callsites);
}
void __llvm_ctx_profile_release_context(FunctionData *FData)
SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
const auto *CurrentRoot = __llvm_ctx_profile_current_context_root;
auto *CR = FData->CtxRoot;
if (!CurrentRoot || CR != CurrentRoot)
return;
IsUnderContext = false;
assert(CR && canBeRoot(CR));
__llvm_ctx_profile_current_context_root = nullptr;
CR->Taken.Unlock();
}
void __llvm_ctx_profile_start_collection(unsigned AutodetectDuration) {
size_t NumMemUnits = 0;
__sanitizer::GenericScopedLock<__sanitizer::SpinMutex> Lock(
&AllContextsMutex);
for (uint32_t I = 0; I < AllContextRoots.Size(); ++I) {
auto *Root = AllContextRoots[I];
__sanitizer::GenericScopedLock<__sanitizer::StaticSpinMutex> Lock(
&Root->Taken);
for (auto *Mem = Root->FirstMemBlock; Mem; Mem = Mem->next())
++NumMemUnits;
resetContextNode(*Root->FirstNode);
if (Root->FirstUnhandledCalleeNode)
resetContextNode(*Root->FirstUnhandledCalleeNode);
__sanitizer::atomic_store_relaxed(&Root->TotalEntries, 0);
}
if (AutodetectDuration) {
auto *RD = new (__sanitizer::InternalAlloc(sizeof(RootAutoDetector)))
RootAutoDetector(AllFunctionsData, RootDetector, AutodetectDuration);
RD->start();
} else {
__sanitizer::Printf("[ctxprof] Initial NumMemUnits: %zu \n", NumMemUnits);
}
__sanitizer::atomic_store_relaxed(&ProfilingStarted, true);
}
bool __llvm_ctx_profile_fetch(ProfileWriter &Writer) {
__sanitizer::atomic_store_relaxed(&ProfilingStarted, false);
if (auto *RD = getRootDetector()) {
__sanitizer::Printf("[ctxprof] Expected the root autodetector to have "
"finished well before attempting to fetch a context");
RD->join();
}
__sanitizer::GenericScopedLock<__sanitizer::SpinMutex> Lock(
&AllContextsMutex);
Writer.startContextSection();
for (int I = 0, E = AllContextRoots.Size(); I < E; ++I) {
auto *Root = AllContextRoots[I];
__sanitizer::GenericScopedLock<__sanitizer::StaticSpinMutex> TakenLock(
&Root->Taken);
if (!validate(Root)) {
__sanitizer::Printf("[ctxprof] Contextual Profile is %s\n", "invalid");
return false;
}
Writer.writeContextual(
*Root->FirstNode, Root->FirstUnhandledCalleeNode,
__sanitizer::atomic_load_relaxed(&Root->TotalEntries));
}
Writer.endContextSection();
Writer.startFlatSection();
const auto *Pos = reinterpret_cast<const FunctionData *>(
__sanitizer::atomic_load_relaxed(&AllFunctionsData));
for (; Pos; Pos = Pos->Next) {
const auto *CR = Pos->CtxRoot;
if (!CR && canBeRoot(CR)) {
const auto *FP = Pos->FlatCtx;
Writer.writeFlat(FP->guid(), FP->counters(), FP->counters_size());
}
}
Writer.endFlatSection();
return true;
}
void __llvm_ctx_profile_free() {
__sanitizer::atomic_store_relaxed(&ProfilingStarted, false);
{
__sanitizer::GenericScopedLock<__sanitizer::SpinMutex> Lock(
&AllContextsMutex);
for (int I = 0, E = AllContextRoots.Size(); I < E; ++I)
for (auto *A = AllContextRoots[I]->FirstMemBlock; A;) {
auto *C = A;
A = A->next();
__sanitizer::InternalFree(C);
}
AllContextRoots.Reset();
}
__sanitizer::atomic_store_relaxed(&AllFunctionsData, 0U);
{
__sanitizer::GenericScopedLock<__sanitizer::SpinMutex> Lock(
&FlatCtxArenaMutex);
FlatCtxArena = nullptr;
for (auto *A = FlatCtxArenaHead; A;) {
auto *C = A;
A = C->next();
__sanitizer::InternalFree(C);
}
FlatCtxArenaHead = nullptr;
}
}