// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef TOOLS_CLANG_STACK_MAPS_GC_GC_API_H_ #define TOOLS_CLANG_STACK_MAPS_GC_GC_API_H_ #include #include #include #include "objects.h" using ReturnAddress = uint64_t; using FramePtr = uintptr_t*; using RBPOffset = uint32_t; using DWARF = uint16_t; using HeapAddress = long*; // The place where HeapObjects live. For simplicity, the underlying data in a // HeapObject is always a single uintptr_t. The heap layout mocks a simple // semi-space collector where objects can be moved between two heap fragments. // // Note that this is a no-op collector: unreachable objects are not reclaimed // and allocation will keep filling the heap until its limited memory is // exhausted. class Heap { public: // Allocates a HeapObject's underlying data field on the heap and returns a // pointer to it. This allocation will use the heap fragment returned from a // fromspace() call. HeapAddress AllocRaw(long value); // Moves all values from fromspace to tospace. fromspace becomes tospace and // vice versa (i.e. future allocations take place on the opposite heap // fragment). Note no objects are dropped in the process. void MoveObjects(); // For an arbitrary pointer into the heap, this will return a new pointer with // a corresponding offset into the opposite heap fragment. E.g. a pointer to // an address at offset +4 into heap fragment A would return an address at // offset +4 into heap fragment B. // // This is used for relocating root pointer values across a collection during // stack walking. HeapAddress UpdatePointer(HeapAddress ptr); private: static constexpr int kHeapSize = 24; HeapAddress fromspace() { if (alloc_on_a_) { return a_frag_; } else { return b_frag_; } } HeapAddress tospace() { if (alloc_on_a_) { return b_frag_; } else { return a_frag_; } } int heap_ptr = 0; long a_frag_[kHeapSize]; long b_frag_[kHeapSize]; bool alloc_on_a_ = true; }; // A FrameRoots object contains all the information needed to precisely identify // live roots for a given safepoint. It contains a list of registers which are // known to contain roots, and a list of offsets from the stack pointer to known // on-stack-roots. // // Each stackmap entry in .llvm_stackmaps has two parts: a base pointer (not to // be confused with EBP), which simply points to an object header; and a derived // pointer which specifies an offset (if any) into the object's interior. In the // case where only a base object pointer is desired, the derived pointer will be // 0. // // DWARF Register number mapping can be found here: // Pg.63 // https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf class FrameRoots { public: FrameRoots(std::vector reg_roots, std::vector stack_roots) : reg_roots_(reg_roots), stack_roots_(stack_roots) {} const std::vector* reg_roots() { return ®_roots_; } const std::vector* stack_roots() { return &stack_roots_; } bool empty() { return reg_roots_.empty() && stack_roots_.empty(); } void Print() const; private: std::vector reg_roots_; std::vector stack_roots_; }; // A SafepointTable provides a runtime mapping of function return addresses to // on-stack and in-register gc root locations. Return addresses are used as a // function call site is the only place where safepoints can exist. This map is // a convenient format for the collector to use while walking a call stack // looking for the rootset. class SafepointTable { public: SafepointTable(std::map roots) : roots_(std::move(roots)) {} const std::map* roots() { return &roots_; } void Print() const; private: const std::map roots_; }; SafepointTable GenSafepointTable(); extern SafepointTable spt; extern Heap* heap; // During stack scanning, the GC must know when it has reached the top of the // stack so that it can hand execution back over to the mutator. This global // variable serves that purpose - it is initialised in main to be equal to // main's RBP value, and checked against each time the gc steps up into the next // stack frame. For non-main threads this could be pthread top. extern "C" void InitTopOfStack(); extern uintptr_t TopOfStack; void PrintSafepointTable(); // Walks the execution stack looking for live gc roots. This function should // never be called directly. Instead, the void |GC| function should be // called. |GC| is an assembly shim which jumps to this function after // placing the value of RBP in RDI (First arg slot mandated by Sys V ABI). // // Stack walking starts from the address in `fp` (assumed to be RBP's // address). The stack is traversed from bottom to top until the frame pointer // hits a terminal value (usually main's RBP value). // // This works by assuming the calling convention for each frame adheres to the // Sys V ABI, where the frame pointer is known to point to the address of last // saved frame pointer (and so on), creating a linked list of frames on the // stack (shown below). // // +--------------------+ // | ... | // +--------------------+ // | Saved RBP |<--+ // +--------------------+ | // | | | // | ... | | // | | | // +--------------------+ | // | Return Address | | // +--------------------+ | // RBP--> | Saved RBP |---+ // +--------------------+ // | | // | Args | // | | // +--------------------+ // // This therefore requires that the optimisation -fomit-frame-pointer is // disabled in order to guarantee that RBP will not be used as a // general-purpose register. extern "C" void StackWalkAndMoveObjects(FramePtr fp); // A very simple allocator for a HeapObject. For the purposes of this // experiment, a HeapObject's contents is simply a 64 bit integer. The data // itself is not important, what is, however, is that it can be accessed through // the rootset after the collector moves it. Handle AllocateHeapObject(HeapAddress data); #endif // TOOLS_CLANG_STACK_MAPS_GC_GC_API_H_