//===-- instr.cpp -----------------------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // This file contains code that is linked to the final binary with a function // that is called at program exit to dump instrumented data collected during // execution. // //===----------------------------------------------------------------------===// // // BOLT runtime instrumentation library for x86 Linux. // //===----------------------------------------------------------------------===// #include #include "config.h" #ifdef HAVE_ELF_H #include #endif //#define ENABLE_DEBUG #ifdef ENABLE_DEBUG #define DEBUG(X) \ { X; } #else #define DEBUG(X) \ {} #endif // All extern declarations here need to be defined by BOLT itself // Counters inserted by instrumentation, incremented during runtime when // points of interest (locations) in the program are reached. extern uint64_t __bolt_instr_locations[]; // Number of call descriptions extern uint32_t __bolt_instr_num_calls; // Number of function descriptions extern uint32_t __bolt_instr_num_funcs; // Filename to dump data to extern char __bolt_instr_filename[]; // Anonymous namespace covering everything but our library entry point namespace { // We use a stack-allocated buffer for string manipulation in some // places with this size. constexpr uint32_t BufSize = 10240; // A location is a function name plus offset. Function name needs to be // retrieved from the string table and is stored as an index to this table. struct Location { uint32_t FunctionName; uint32_t Offset; }; struct CallDescription { Location From; Location To; uint32_t Counter; }; // An edge description defines an instrumented edge in the program, fully // identified by where the jump is located and its destination. struct EdgeDescription { Location From; uint32_t FromNode; Location To; uint32_t ToNode; uint32_t Counter; }; // This description is used for instrumented basic blocks. We only instrument // blocks that are leaves of the spanning tree associated with the CFG. We use // their execution count as a starting point to determine the frequency of the // incoming edge in the spanning tree (the one that is not explictly // instrumented). All other edges (not in the spanning tree) have an associated // counter themselves. We never write a node's counter to the profile, it is // only used for inference of other edges. struct InstrumentedNode { uint32_t Node; uint32_t Counter; }; // General metadata describing a function - number of CFG nodes, leaf nodes and // edges. struct FunctionDescription { uint32_t NumNodes; InstrumentedNode *LeafNodes; uint32_t NumEdges; EdgeDescription *Edges; }; // These need to be read from disk. They are generated by BOLT and written to // an ELF note section in the binary itself. struct InstrumentationInfo { CallDescription *CallDescriptions; uint8_t *FuncDescriptions; char *Strings; // String table with function names used in this binary int FileDesc; // File descriptor for the file on disk backing this // information in memory via mmap void *MMapPtr; // The mmap ptr int MMapSize; // The mmap size }; // Declare some syscall wrappers we use throughout this code to avoid linking // against system libc. uint64_t __open(const char *pathname, uint64_t flags, uint64_t mode) { uint64_t ret; __asm__ __volatile__ ( "movq $2, %%rax\n" "syscall" : "=a"(ret) : "D"(pathname), "S"(flags), "d"(mode) : "cc", "rcx", "r11", "memory"); return ret; } uint64_t __write(uint64_t fd, const void *buf, uint64_t count) { uint64_t ret; __asm__ __volatile__ ( "movq $1, %%rax\n" "syscall\n" : "=a"(ret) : "D"(fd), "S"(buf), "d"(count) : "cc", "rcx", "r11", "memory"); return ret; } uint64_t __lseek(uint64_t fd, uint64_t pos, uint64_t whence) { uint64_t ret; __asm__ __volatile__ ( "movq $8, %%rax\n" "syscall\n" : "=a"(ret) : "D"(fd), "S"(pos), "d"(whence) : "cc", "rcx", "r11", "memory"); return ret; } int __close(uint64_t fd) { uint64_t ret; __asm__ __volatile__ ( "movq $3, %%rax\n" "syscall\n" : "=a"(ret) : "D"(fd) : "cc", "rcx", "r11", "memory"); return ret; } void *__mmap(uint64_t addr, uint64_t size, uint64_t prot, uint64_t flags, uint64_t fd, uint64_t offset) { void *ret; register uint64_t r8 asm("r8") = fd; register uint64_t r9 asm("r9") = offset; register uint64_t r10 asm("r10") = flags; __asm__ __volatile__ ( "movq $9, %%rax\n" "syscall\n" : "=a"(ret) : "D"(addr), "S"(size), "d"(prot), "r"(r10), "r"(r8), "r"(r9) : "cc", "rcx", "r11", "memory"); return ret; } uint64_t __munmap(void *addr, uint64_t size) { uint64_t ret; __asm__ __volatile__ ( "movq $11, %%rax\n" "syscall\n" : "=a"(ret) : "D"(addr), "S"(size) : "cc", "rcx", "r11", "memory"); return ret; } uint64_t __exit(uint64_t code) { uint64_t ret; __asm__ __volatile__ ( "movq $231, %%rax\n" "syscall\n" : "=a"(ret) : "D"(code) : "cc", "rcx", "r11", "memory"); return ret; } // Helper functions for writing strings to the .fdata file // Write number Num using Base to the buffer in OutBuf, returns a pointer to // the end of the string. char *intToStr(char *OutBuf, uint64_t Num, uint32_t Base) { const char *Chars = "0123456789abcdef"; char Buf[21]; char *Ptr = Buf; while (Num) { *Ptr++ = *(Chars + (Num % Base)); Num /= Base; } if (Ptr == Buf) { *OutBuf++ = '0'; return OutBuf; } while (Ptr != Buf) { *OutBuf++ = *--Ptr; } return OutBuf; } // Copy Str to OutBuf, returns a pointer to the end of the copied string char *strCopy(char *OutBuf, const char *Str, int32_t Size = BufSize) { while (*Str) { *OutBuf++ = *Str++; if (--Size <= 0) return OutBuf; } return OutBuf; } void memSet(char *Buf, char C, uint32_t Size) { for (int I = 0; I < Size; ++I) *Buf++ = C; } uint32_t strLen(const char *Str) { uint32_t Size = 0; while (*Str++) ++Size; return Size; } // Print Msg to STDERR and quits with error code 1 void reportError(const char *Msg, uint64_t Size) { __write(2, Msg, Size); __exit(1); } void assert(bool Assertion, const char *Msg) { if (Assertion) return; char Buf[BufSize]; char *Ptr = Buf; Ptr = strCopy(Ptr, "Assertion failed: "); Ptr = strCopy(Ptr, Msg, BufSize - 40); Ptr = strCopy(Ptr, "\n"); reportError(Buf, Ptr - Buf); } void reportNumber(const char *Msg, uint64_t Num, uint32_t Base) { char Buf[BufSize]; char *Ptr = Buf; Ptr = strCopy(Ptr, Msg, BufSize - 23); Ptr = intToStr(Ptr, Num, Base); Ptr = strCopy(Ptr, "\n"); __write(2, Buf, Ptr - Buf); } void report(const char *Msg) { __write(2, Msg, strLen(Msg)); } inline uint64_t alignTo(uint64_t Value, uint64_t Align) { return (Value + Align - 1) / Align * Align; } /// A simple allocator that mmaps a fixed size region and manages this space /// in a stack fashion, meaning you always deallocate the last element that /// was allocated. class BumpPtrAllocator { struct EntryMetadata { uint64_t Magic; uint64_t AllocSize; }; public: void *allocate(uintptr_t Size) { if (StackBase == nullptr) { StackBase = reinterpret_cast(__mmap( 0, MAXSIZE, 0x3 /* PROT_READ | PROT_WRITE*/, 0x22 /* MAP_PRIVATE | MAP_ANONYMOUS*/, -1, 0)); StackSize = 0; } Size = alignTo(Size + sizeof(EntryMetadata), 16); uint8_t * AllocAddress = StackBase + StackSize + sizeof(EntryMetadata); auto *M = reinterpret_cast(StackBase + StackSize); M->Magic = MAGIC; M->AllocSize = Size; StackSize += Size; assert(StackSize < MAXSIZE, "allocator ran out of memory"); return AllocAddress; } void deallocate(void *Ptr) { uint8_t MetadataOffset = sizeof(EntryMetadata); auto *M = reinterpret_cast( reinterpret_cast(Ptr) - MetadataOffset); const uint8_t *StackTop = StackBase + StackSize + MetadataOffset; // Validate size if (Ptr != StackTop - M->AllocSize) { // This could be a pointer returned by operator new [] MetadataOffset += sizeof(uint64_t); // Space for number of elements alloc'ed M = reinterpret_cast(reinterpret_cast(Ptr) - MetadataOffset); assert(Ptr == StackTop - M->AllocSize, "must deallocate the last element alloc'ed"); } assert(M->Magic == MAGIC, "allocator magic is corrupt"); StackSize -= M->AllocSize; } private: static constexpr uint64_t MAGIC = 0x1122334455667788ull; static constexpr uint64_t MAXSIZE = 0xa00000; uint8_t *StackBase{nullptr}; uint64_t StackSize{0}; uint8_t *LastAlloc{nullptr}; }; } // anonymous namespace // User-defined placement new operators. We only use those (as opposed to // overriding the regular operator new) so we can keep our allocator in the // stack instead of in a data section (global). void *operator new(uintptr_t Sz, BumpPtrAllocator &A) { return A.allocate(Sz); } void *operator new (uintptr_t Sz, BumpPtrAllocator &A, char C) { auto *Ptr = reinterpret_cast(A.allocate(Sz)); memSet(Ptr, C, Sz); return Ptr; } void *operator new [] (uintptr_t Sz, BumpPtrAllocator &A) { return A.allocate(Sz); } void *operator new [] (uintptr_t Sz, BumpPtrAllocator &A, char C) { auto *Ptr = reinterpret_cast(A.allocate(Sz)); memSet(Ptr, C, Sz); return Ptr; } // Only called during exception unwinding (useless). We must manually dealloc. // C++ language weirdness void operator delete(void *Ptr, BumpPtrAllocator &A) { A.deallocate(Ptr); } namespace { // Perform a string comparison and returns zero if Str1 matches Str2. Compares // at most Size characters. int compareStr(const char *Str1, const char *Str2, int Size) { while (*Str1 == *Str2) { if (*Str1 == '\0' || --Size == 0) return 0; ++Str1; ++Str2; } return 1; } // Write as a string in OutBuf an identifier for the program point at function // whose name is in the string table index FuncStrIndex plus Offset. char *serializeLoc(const InstrumentationInfo &Info, char *OutBuf, const Location Loc, uint32_t BufSize) { // fdata location format: Type Name Offset // Type 1 - regular symbol OutBuf = strCopy(OutBuf, "1 "); const char *Str = Info.Strings + Loc.FunctionName; uint32_t Size = 25; while (*Str) { *OutBuf++ = *Str++; if (++Size >= BufSize) break; } assert(!*Str, "buffer overflow, function name too large"); *OutBuf++ = ' '; OutBuf = intToStr(OutBuf, Loc.Offset, 16); *OutBuf++ = ' '; return OutBuf; } // Read and map to memory the descriptions written by BOLT into the executable's // notes section #ifdef HAVE_ELF_H InstrumentationInfo readDescriptions() { InstrumentationInfo Result; uint64_t FD = __open("/proc/self/exe", /*flags=*/0 /*O_RDONLY*/, /*mode=*/0666); assert(static_cast(FD) > 0, "Failed to open /proc/self/exe"); Result.FileDesc = FD; // mmap our binary to memory uint64_t Size = __lseek(FD, 0, 2 /*SEEK_END*/); uint8_t *BinContents = reinterpret_cast( __mmap(0, Size, 0x1 /* PROT_READ*/, 0x2 /* MAP_PRIVATE*/, FD, 0)); Result.MMapPtr = BinContents; Result.MMapSize = Size; Elf64_Ehdr *Hdr = reinterpret_cast(BinContents); Elf64_Shdr *Shdr = reinterpret_cast(BinContents + Hdr->e_shoff); Elf64_Shdr *StringTblHeader = reinterpret_cast( BinContents + Hdr->e_shoff + Hdr->e_shstrndx * Hdr->e_shentsize); // Find .bolt.instr.tables with the data we need and set pointers to it for (int I = 0; I < Hdr->e_shnum; ++I) { char *SecName = reinterpret_cast( BinContents + StringTblHeader->sh_offset + Shdr->sh_name); if (compareStr(SecName, ".bolt.instr.tables", 64) != 0) { Shdr = reinterpret_cast(BinContents + Hdr->e_shoff + (I + 1) * Hdr->e_shentsize); continue; } // Actual contents of the ELF note start after offset 20 decimal: // Offset 0: Producer name size (4 bytes) // Offset 4: Contents size (4 bytes) // Offset 8: Note type (4 bytes) // Offset 12: Producer name (BOLT\0) (5 bytes + align to 4-byte boundary) // Offset 20: Contents uint32_t CallDescSize = *reinterpret_cast(BinContents + Shdr->sh_offset + 20); uint32_t FuncDescSize = *reinterpret_cast( BinContents + Shdr->sh_offset + 24 + CallDescSize); Result.CallDescriptions = reinterpret_cast(BinContents + Shdr->sh_offset + 24); Result.FuncDescriptions = BinContents + Shdr->sh_offset + 28 + CallDescSize; Result.Strings = reinterpret_cast(BinContents + Shdr->sh_offset + 28 + CallDescSize + FuncDescSize); return Result; } const char ErrMsg[] = "BOLT instrumentation runtime error: could not find section " ".bolt.instr.tables\n"; reportError(ErrMsg, sizeof(ErrMsg)); return Result; } #else InstrumentationInfo readDescriptions() { InstrumentationInfo Result; const char ErrMsg[] = "BOLT instrumentation runtime error: unsupported binary format.\n"; reportError(ErrMsg, sizeof(ErrMsg)); return Result; } #endif void printStats(const InstrumentationInfo &Info) { char StatMsg[BufSize]; char *StatPtr = StatMsg; StatPtr = strCopy( StatPtr, "\nBOLT INSTRUMENTATION RUNTIME STATISTICS\n\nCallDescSize: "); StatPtr = intToStr(StatPtr, Info.FuncDescriptions - reinterpret_cast(Info.CallDescriptions), 10); StatPtr = strCopy(StatPtr, "\nFuncDescSize: "); StatPtr = intToStr( StatPtr, reinterpret_cast(Info.Strings) - Info.FuncDescriptions, 10); StatPtr = strCopy(StatPtr, "\n__bolt_instr_num_calls: "); StatPtr = intToStr(StatPtr, __bolt_instr_num_calls, 10); StatPtr = strCopy(StatPtr, "\n__bolt_instr_num_funcs: "); StatPtr = intToStr(StatPtr, __bolt_instr_num_funcs, 10); StatPtr = strCopy(StatPtr, "\n"); __write(2, StatMsg, StatPtr - StatMsg); } /// This is part of a simple CFG representation in memory, where we store /// a dynamically sized array of input and output edges per node, and store /// a dynamically sized array of nodes per graph. We also store the spanning /// tree edges for that CFG in a separate array of nodes in /// \p SpanningTreeNodes, while the regular nodes live in \p CFGNodes. struct Edge { uint32_t Node; // Index in nodes array regarding the destination of this edge uint32_t ID; // Edge index in an array comprising all edges of the graph }; /// A regular graph node or a spanning tree node struct Node { uint32_t NumInEdges{0}; // Input edge count used to size InEdge uint32_t NumOutEdges{0}; // Output edge count used to size OutEdges Edge *InEdges{nullptr}; // Created and managed by \p Graph Edge *OutEdges{nullptr}; // ditto }; /// Main class for CFG representation in memory. Manages object creation and /// destruction, populates an array of CFG nodes as well as corresponding /// spanning tree nodes. struct Graph { uint32_t NumNodes; Node *CFGNodes; Node *SpanningTreeNodes; BumpPtrAllocator &Alloc; /// Reads a list of \p NumEdgeDescs descriptions in \p EdgeDescs and builds /// the graph from it. Allocates several internal dynamic structures that are /// later destroyed by ~Graph() and uses \p Alloc. \p LeafNodes contain all /// spanning tree leaf nodes descriptions (their counters). They are the seed /// used to compute the rest of the missing edge counts in a bottom-up /// traversal of the spanning tree. Graph(BumpPtrAllocator &Alloc, const EdgeDescription *EdgeDescs, uint32_t NumEdgeDescs, const InstrumentedNode *LeafNodes, uint32_t NumLeafNodes); ~Graph(); void dump() const; }; Graph::Graph(BumpPtrAllocator &Alloc, const EdgeDescription *EdgeDescs, uint32_t NumEdgeDescs, const InstrumentedNode *LeafNodes, uint32_t NumLeafNodes) : Alloc(Alloc) { DEBUG(reportNumber("G = 0x", (uint64_t)this, 16)); // First pass to determine number of nodes uint32_t MaxNodes = 0; for (int I = 0; I < NumEdgeDescs; ++I) { if (EdgeDescs[I].FromNode > MaxNodes) MaxNodes = EdgeDescs[I].FromNode; if (EdgeDescs[I].ToNode > MaxNodes) MaxNodes = EdgeDescs[I].ToNode; } for (int I = 0; I < NumLeafNodes; ++I) { if (LeafNodes[I].Node > MaxNodes) MaxNodes = LeafNodes[I].Node; } // No edges? Nothing to do if (!MaxNodes) { CFGNodes = nullptr; SpanningTreeNodes = nullptr; NumNodes = 0; return; } ++MaxNodes; DEBUG(reportNumber("NumNodes = ", MaxNodes, 10)); NumNodes = MaxNodes; // Initial allocations CFGNodes = new (Alloc) Node[MaxNodes]; DEBUG(reportNumber("G->CFGNodes = 0x", (uint64_t)CFGNodes, 16)); SpanningTreeNodes = new (Alloc) Node[MaxNodes]; DEBUG(reportNumber("G->SpanningTreeNodes = 0x", (uint64_t)SpanningTreeNodes, 16)); // Figure out how much to allocate to each vector (in/out edge sets) for (int I = 0; I < NumEdgeDescs; ++I) { CFGNodes[EdgeDescs[I].FromNode].NumOutEdges++; CFGNodes[EdgeDescs[I].ToNode].NumInEdges++; if (EdgeDescs[I].Counter != 0xffffffff) continue; SpanningTreeNodes[EdgeDescs[I].FromNode].NumOutEdges++; SpanningTreeNodes[EdgeDescs[I].ToNode].NumInEdges++; } // Allocate in/out edge sets for (int I = 0; I < MaxNodes; ++I) { if (CFGNodes[I].NumInEdges > 0) CFGNodes[I].InEdges = new (Alloc) Edge[CFGNodes[I].NumInEdges]; if (CFGNodes[I].NumOutEdges > 0) CFGNodes[I].OutEdges = new (Alloc) Edge[CFGNodes[I].NumOutEdges]; if (SpanningTreeNodes[I].NumInEdges > 0) SpanningTreeNodes[I].InEdges = new (Alloc) Edge[SpanningTreeNodes[I].NumInEdges]; if (SpanningTreeNodes[I].NumOutEdges > 0) SpanningTreeNodes[I].OutEdges = new (Alloc) Edge[SpanningTreeNodes[I].NumOutEdges]; CFGNodes[I].NumInEdges = 0; CFGNodes[I].NumOutEdges = 0; SpanningTreeNodes[I].NumInEdges = 0; SpanningTreeNodes[I].NumOutEdges = 0; } // Fill in/out edge sets for (int I = 0; I < NumEdgeDescs; ++I) { const uint32_t Src = EdgeDescs[I].FromNode; const uint32_t Dst = EdgeDescs[I].ToNode; Edge *E = &CFGNodes[Src].OutEdges[CFGNodes[Src].NumOutEdges++]; E->Node = Dst; E->ID = I; E = &CFGNodes[Dst].InEdges[CFGNodes[Dst].NumInEdges++]; E->Node = Src; E->ID = I; if (EdgeDescs[I].Counter != 0xffffffff) continue; E = &SpanningTreeNodes[Src] .OutEdges[SpanningTreeNodes[Src].NumOutEdges++]; E->Node = Dst; E->ID = I; E = &SpanningTreeNodes[Dst] .InEdges[SpanningTreeNodes[Dst].NumInEdges++]; E->Node = Src; E->ID = I; } } Graph::~Graph() { for (int I = NumNodes - 1; I >= 0; --I) { if (SpanningTreeNodes[I].OutEdges) Alloc.deallocate(SpanningTreeNodes[I].OutEdges); if (SpanningTreeNodes[I].InEdges) Alloc.deallocate(SpanningTreeNodes[I].InEdges); if (CFGNodes[I].OutEdges) Alloc.deallocate(CFGNodes[I].OutEdges); if (CFGNodes[I].InEdges) Alloc.deallocate(CFGNodes[I].InEdges); } if (SpanningTreeNodes) Alloc.deallocate(SpanningTreeNodes); if (CFGNodes) Alloc.deallocate(CFGNodes); } void Graph::dump() const { reportNumber("Dumping graph with number of nodes: ", NumNodes, 10); report(" Full graph:\n"); for (int I = 0; I < NumNodes; ++I) { const Node *N = &CFGNodes[I]; reportNumber(" Node #", I, 10); reportNumber(" InEdges total ", N->NumInEdges, 10); for (int J = 0; J < N->NumInEdges; ++J) reportNumber(" ", N->InEdges[J].Node, 10); reportNumber(" OutEdges total ", N->NumOutEdges, 10); for (int J = 0; J < N->NumOutEdges; ++J) reportNumber(" ", N->OutEdges[J].Node, 10); report("\n"); } report(" Spanning tree:\n"); for (int I = 0; I < NumNodes; ++I) { const Node *N = &SpanningTreeNodes[I]; reportNumber(" Node #", I, 10); reportNumber(" InEdges total ", N->NumInEdges, 10); for (int J = 0; J < N->NumInEdges; ++J) reportNumber(" ", N->InEdges[J].Node, 10); reportNumber(" OutEdges total ", N->NumOutEdges, 10); for (int J = 0; J < N->NumOutEdges; ++J) reportNumber(" ", N->OutEdges[J].Node, 10); report("\n"); } } void dumpEdgeFreqs(const uint64_t *EdgeFreqs, const EdgeDescription *EdgeDescs, uint32_t NumEdges) { reportNumber("Dumping edge frequencies for graph with num edges: ", NumEdges, 10); for (int I = 0; I < NumEdges; ++I) { reportNumber("* Src: ", EdgeDescs[I].FromNode, 10); reportNumber(" Dst: ", EdgeDescs[I].ToNode, 10); reportNumber(" Cnt: ", EdgeFreqs[I], 10); } } // Return an array with the frequency of each edge in the function represented // by G. uint64_t *computeEdgeFrequencies(BumpPtrAllocator &Alloc, Graph *G, const EdgeDescription *EdgeDescs, uint32_t NumEdges, const InstrumentedNode *LeafNodes, uint32_t NumLeafNodes, const uint64_t *Counters) { if (G->NumNodes == 0 || NumEdges == 0) return 0; assert(NumLeafNodes > 0, "no leaf node frequency"); uint64_t *EdgeFrequency = new (Alloc, 0) uint64_t [NumEdges]; // Perform a bottom-up, BFS traversal of the spanning tree in G. Edges in the // spanning tree don't have explicit counters. We must infer their value using // a linear combination of other counters (sum of counters of the outgoing // edges minus sum of counters of the incoming edges). uint32_t *Stack = new (Alloc) uint32_t [G->NumNodes]; uint32_t StackTop = 0; enum Status : uint8_t { S_NEW = 0, S_VISITING, S_VISITED }; Status *Visited = new (Alloc, 0) Status[G->NumNodes]; uint64_t *LeafFrequency = new (Alloc, 0) uint64_t[G->NumNodes]; // Setup a fast lookup for frequency of leaf nodes, which have special // basic block frequency instrumentation (they are not edge profiled). uint64_t TotalFreq = 0; for (int I = 0; I < NumLeafNodes; ++I) { LeafFrequency[LeafNodes[I].Node] = Counters[LeafNodes[I].Counter]; DEBUG({ if (Counters[LeafNodes[I].Counter] > 0) { reportNumber("Leaf Node# ", LeafNodes[I].Node, 10); reportNumber(" Counter: ", Counters[LeafNodes[I].Counter], 10); } }); TotalFreq += Counters[LeafNodes[I].Counter]; } // Add all root nodes to the stack for (int I = 0; I < G->NumNodes; ++I) { if (G->SpanningTreeNodes[I].NumInEdges == 0) Stack[StackTop++] = I; } // Empty stack? if (StackTop == 0) { Alloc.deallocate(LeafFrequency); Alloc.deallocate(Visited); Alloc.deallocate(Stack); Alloc.deallocate(EdgeFrequency); return 0; } // Add all known edge counts, will infer the rest for (int I = 0; I < NumEdges; ++I) { const uint32_t C = EdgeDescs[I].Counter; if (C == 0xffffffff) // inferred counter - we will compute its value continue; EdgeFrequency[I] = Counters[C]; TotalFreq += Counters[C]; } // This function is completely cold, there is no point in computing anything // since inferred edges will be zero too. #ifndef ENABLE_DEBUG if (TotalFreq == 0) { Alloc.deallocate(LeafFrequency); Alloc.deallocate(Visited); Alloc.deallocate(Stack); return EdgeFrequency; } #endif while (StackTop > 0) { const uint32_t Cur = Stack[--StackTop]; DEBUG({ if (Visited[Cur] == S_VISITING) report("(visiting) "); else report("(new) "); reportNumber("Cur: ", Cur, 10); }); // This shouldn't happen in a tree assert(Visited[Cur] != S_VISITED, "should not have visited nodes in stack"); if (Visited[Cur] == S_NEW) { Visited[Cur] = S_VISITING; Stack[StackTop++] = Cur; assert(StackTop <= G->NumNodes, "stack grew too large"); for (int I = 0, E = G->SpanningTreeNodes[Cur].NumOutEdges; I < E; ++I) { const uint32_t Succ = G->SpanningTreeNodes[Cur].OutEdges[I].Node; Stack[StackTop++] = Succ; assert(StackTop <= G->NumNodes, "stack grew too large"); } continue; } Visited[Cur] = S_VISITED; // No parent? Reached a tree root, nothing to do. if (G->SpanningTreeNodes[Cur].NumInEdges == 0) continue; assert(G->SpanningTreeNodes[Cur].NumInEdges == 1, "must have 1 parent"); const uint32_t Parent = G->SpanningTreeNodes[Cur].InEdges[0].Node; const uint32_t ParentEdge = G->SpanningTreeNodes[Cur].InEdges[0].ID; // Establish our node frequency based on outgoing edges, which should all be // resolved by now. int64_t CurNodeFreq = LeafFrequency[Cur]; // Not a leaf? if (!CurNodeFreq) { for (int I = 0, E = G->CFGNodes[Cur].NumOutEdges; I != E; ++I) { const uint32_t SuccEdge = G->CFGNodes[Cur].OutEdges[I].ID; CurNodeFreq += EdgeFrequency[SuccEdge]; } } int64_t ParentEdgeFreq = CurNodeFreq; // Calculate parent edge freq. for (int I = 0, E = G->CFGNodes[Cur].NumInEdges; I != E; ++I) { const uint32_t PredEdge = G->CFGNodes[Cur].InEdges[I].ID; ParentEdgeFreq -= EdgeFrequency[PredEdge]; } // Sometimes the conservative CFG that BOLT builds will lead to incorrect // flow computation. For example, in a BB that transitively calls the exit // syscall, BOLT will add a fall-through successor even though it should not // have any successors. So this block execution will likely be wrong. We // tolerate this imperfection since this case should be quite infrequent. if (ParentEdgeFreq < 0) { DEBUG(dumpEdgeFreqs(EdgeFrequency, EdgeDescs, NumEdges)); DEBUG(report("WARNING: incorrect flow")); ParentEdgeFreq = 0; } DEBUG(reportNumber(" Setting freq for ParentEdge: ", ParentEdge, 10)); DEBUG(reportNumber(" with ParentEdgeFreq: ", ParentEdgeFreq, 10)); EdgeFrequency[ParentEdge] = ParentEdgeFreq; } Alloc.deallocate(LeafFrequency); Alloc.deallocate(Visited); Alloc.deallocate(Stack); return EdgeFrequency; } // Write to \p FD all of the edge profiles for function \p FuncDesc. Uses // \p Alloc to allocate helper dynamic structures used to compute profile for // edges that we do not explictly instrument. uint8_t *writeFunctionProfile(int FD, const InstrumentationInfo &Info, uint8_t *FuncDesc, BumpPtrAllocator &Alloc) { uint32_t NumLeafNodes = *reinterpret_cast(FuncDesc); DEBUG(reportNumber("NumLeafNodes = ", NumLeafNodes, 10)); InstrumentedNode *LeafNodes = reinterpret_cast(FuncDesc + 4); uint32_t NumEdges = *reinterpret_cast( FuncDesc + 4 + NumLeafNodes * sizeof(InstrumentedNode)); DEBUG(reportNumber("NumEdges = ", NumEdges, 10)); EdgeDescription *EdgeDescs = reinterpret_cast( FuncDesc + 8 + NumLeafNodes * sizeof(InstrumentedNode)); uint8_t *next = (FuncDesc + 8 + NumLeafNodes * sizeof(InstrumentedNode) + NumEdges * sizeof(EdgeDescription)); // Skip funcs we know are cold #ifndef ENABLE_DEBUG uint64_t LeafFreq = 0; for (int I = 0; I < NumLeafNodes; ++I) { LeafFreq += __bolt_instr_locations[LeafNodes[I].Counter]; } if (LeafFreq == 0) return next; #endif Graph *G = new (Alloc) Graph(Alloc, EdgeDescs, NumEdges, LeafNodes, NumLeafNodes); DEBUG(G->dump()); uint64_t *Freqs = computeEdgeFrequencies(Alloc, G, EdgeDescs, NumEdges, LeafNodes, NumLeafNodes, __bolt_instr_locations); if (!Freqs) { G->~Graph(); Alloc.deallocate(G); return next; } DEBUG(dumpEdgeFreqs(Freqs, EdgeDescs, NumEdges)); for (int I = 0; I < NumEdges; ++I) { const uint64_t Freq = Freqs[I]; if (Freq == 0) continue; const EdgeDescription *Desc = &EdgeDescs[I]; char LineBuf[BufSize]; char *Ptr = LineBuf; Ptr = serializeLoc(Info, Ptr, Desc->From, BufSize); Ptr = serializeLoc(Info, Ptr, Desc->To, BufSize - (Ptr - LineBuf)); Ptr = strCopy(Ptr, "0 ", BufSize - (Ptr - LineBuf) - 22); Ptr = intToStr(Ptr, Freq, 10); *Ptr++ = '\n'; __write(FD, LineBuf, Ptr - LineBuf); } Alloc.deallocate(Freqs); G->~Graph(); Alloc.deallocate(G); return next; } } // anonymous namespace // This is the entry point called at program exit. BOLT patches the executable's // FINI entry in the .dynamic section with the address of this function. Our // goal here is to flush to disk all instrumentation data in memory, using // BOLT's fdata format. extern "C" void __bolt_instr_data_dump() { const InstrumentationInfo Info = readDescriptions(); DEBUG(printStats(Info)); uint64_t FD = __open(__bolt_instr_filename, /*flags=*/0x241 /*O_WRONLY|O_TRUNC|O_CREAT*/, /*mode=*/0666); if (static_cast(FD) < 0) { reportNumber("Assertion error: failed to open profile file for writing. " "Error number: 0x", 0 - static_cast(FD), 16); __exit(1); } for (int I = 0, E = __bolt_instr_num_calls; I < E; ++I) { char LineBuf[BufSize]; char *Ptr = LineBuf; CallDescription *Desc = &Info.CallDescriptions[I]; uint64_t HitCount = __bolt_instr_locations[Desc->Counter]; if (!HitCount) continue; Ptr = serializeLoc(Info, Ptr, Desc->From, BufSize); Ptr = serializeLoc(Info, Ptr, Desc->To, BufSize - (Ptr - LineBuf)); Ptr = strCopy(Ptr, "0 ", BufSize - (Ptr - LineBuf) - 25); Ptr = intToStr(Ptr, HitCount, 10); *Ptr++ = '\n'; __write(FD, LineBuf, Ptr - LineBuf); } BumpPtrAllocator Alloc; uint8_t *FuncDesc = Info.FuncDescriptions; for (int I = 0, E = __bolt_instr_num_funcs; I < E; ++I) { FuncDesc = writeFunctionProfile(FD, Info, FuncDesc, Alloc); DEBUG(reportNumber("FuncDesc now: ", (uint64_t)FuncDesc, 16)); } assert(FuncDesc == (void *)Info.Strings, "FuncDesc ptr must be equal to stringtable"); __close(FD); __munmap(Info.MMapPtr, Info.MMapSize); __close(Info.FileDesc); }