Commit f5fee0db authored by Jake Hughes's avatar Jake Hughes Committed by Commit Bot

Layout initial experimental stack map gc design

This is the first CL of an artefact investigating precise stack map
support for garbage collection in Blink / V8 using experimental LLVM
features.

The bulk of this change is made up of two main components:
  - A parser for reading the .llvm_stackmap section (generated by LLVM)
    into a table for use by the GC.

  - A simple stack walking mechanism, which, once called, traverses the
    call stack bottom up frame by frame searching for a gc rootset in the
    root table.

This also fleshes out a few helper API functions (see gc/gc_api.h) which
can be used when calling into the gc library from user code.

The idea for this gc/ directory is to be compiled as a library. It is
then linked to user code which has be compiled with the relevent LLVM IR
passes for generating stack maps at pre-defined safepoints. The code for
which will be forthcoming in a separate CL.

Bug: 978892

Change-Id: Ia2cb40881460b6a084babab9986df6c5303b41f8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1768770
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: default avatarMichael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#691113}
parent f2c1c734
cmake_minimum_required(VERSION 3.13)
enable_language(ASM)
SET(CMAKE_C_COMPILER ../../../../third_party/llvm-build/Release+Asserts/bin/clang)
SET(CMAKE_CXX_COMPILER ../../../../../third_party/llvm-build/Release+Asserts/bin/clang)
SET(CMAKE_BUILD_TYPE Debug)
add_library(GC gc_api.h gc_api.cc stack_map_parser.h stack_map_parser.cc
GC_Shim_x86_64.S)
target_compile_options(GC PUBLIC -fno-omit-frame-pointer)
.text
.globl GetFramePtr
// Places the frame pointer value in the return register
GetFramePtr:
mov (%rbp), %rax
ret
.extern StackWalk
.globl BeginGC
// We place the frame pointer in the first arg slot register. We jump to
// StackWalk instead of calling it so that RET returns to BeginGC's caller
// caller.
BeginGC:
mov %rbp, %rdi
jmp StackWalk
# Stack Map Artefact GC
This directory contains the implementation of the runtime system necessary to
observe and test the LLVM generated stack maps.
## GC API
Provides a few useful function calls which can be made from user-code.
## Stack Map Parser
The Stack Map parser parses the `.llvm_stackmaps` section according to the LLVM
V3 StackMap format. It begins parsing from the global `__LLVM_StackMaps` symbol.
The parser then builds a map from this data which can queried by the stack
walker at a later stage to identify the garbage collection rootset.
// 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.
#include "gc_api.h"
#include <ostream>
std::ostream& operator<<(std::ostream& os, const FrameRoots& fr) {
os << "Register Roots: NYI" << std::endl << "\t";
if (!fr.stack_roots_.size()) {
os << "Stack Roots: []" << std::endl;
return os;
}
os << "Stack Roots: [";
for (auto SR : fr.stack_roots_) {
os << "RBP - " << SR << ", ";
}
os << "\b\b]" << std::endl;
return os;
}
std::ostream& operator<<(std::ostream& os, const SafepointTable& st) {
os << "Safepoint Table:" << std::endl;
for (auto const& pair : st.roots_) {
os << "\t" << pair.first << ":\n\t" << pair.second << std::endl;
}
return os;
}
extern "C" void StackWalk(FramePtr fp) {
while (true) {
// The caller's return address is always 1 machine word above the recorded
// RBP value in the current frame
auto ra = reinterpret_cast<ReturnAddress>(*(fp + 1));
printf("==== Frame %p ====", reinterpret_cast<void*>(ra));
auto it = spt.roots()->find(ra);
if (it != spt.roots()->end()) {
auto fr_roots = it->second;
for (auto root : *fr_roots.stack_roots()) {
auto offset = root / sizeof(uintptr_t);
auto address = reinterpret_cast<uintptr_t>(fp - offset);
printf("\tRoot: [RBP - %d]", root);
printf("\tAddress: %p", reinterpret_cast<void*>(address));
}
}
// Step up into the caller's frame or bail if we're at the top of stack
fp = reinterpret_cast<FramePtr>(*fp);
if (reinterpret_cast<uintptr_t>(fp) < spt.stack_begin())
break;
}
}
// 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 <assert.h>
#include <map>
#include <ostream>
#include <vector>
using ReturnAddress = uint64_t;
using FramePtr = uintptr_t*;
using RBPOffset = uint32_t;
using DWARF = uint16_t;
// 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<DWARF> reg_roots, std::vector<RBPOffset> stack_roots)
: reg_roots_(reg_roots), stack_roots_(stack_roots) {}
const std::vector<DWARF>* reg_roots() { return &reg_roots_; }
const std::vector<RBPOffset>* stack_roots() { return &stack_roots_; }
bool empty() { return reg_roots_.empty() && stack_roots_.empty(); }
friend std::ostream& operator<<(std::ostream& os, const FrameRoots& fr);
private:
std::vector<DWARF> reg_roots_;
std::vector<RBPOffset> stack_roots_;
};
std::ostream& operator<<(std::ostream& os, const FrameRoots& fr);
// 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<ReturnAddress, FrameRoots> roots,
uintptr_t stack_begin)
: roots_(std::move(roots)), stack_begin_(stack_begin) {}
const std::map<ReturnAddress, FrameRoots>* roots() { return &roots_; }
uintptr_t stack_begin() { return stack_begin_; }
friend std::ostream& operator<<(std::ostream& os, const SafepointTable& st);
private:
std::map<ReturnAddress, FrameRoots> roots_;
uintptr_t stack_begin_;
};
std::ostream& operator<<(std::ostream& os, const SafepointTable& st);
SafepointTable GenSafepointTable();
extern SafepointTable spt;
void PrintSafepointTable();
// Walks the execution stack looking for live gc roots. This function should
// never be called directly. Instead, the void |BeginGC| function should be
// called. |BeginGC| 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 StackWalk(FramePtr fp);
#endif // TOOLS_CLANG_STACK_MAPS_GC_GC_API_H_
// 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.
#include "stack_map_parser.h"
namespace stackmap {
FrameRoots StackmapV3Parser::ParseFrame() {
std::vector<DWARF> reg_roots;
std::vector<RBPOffset> stack_roots;
auto* loc =
ptr_offset<const StkMapLocation*>(cur_frame_, sizeof(StkMapRecordHeader));
// The first few locations are reserved constants and not of interest to us.
// We skip over them but assert that they are indeed constants (else
// something has gone very wrong!).
for (int i = 0; i < kSkipLocs; i++) {
assert(loc->kind == kConstant);
loc++;
}
// Deopt locations are not of interest to us either, but the first one
// describes how many will follow, so we need it to jump over the rest in
// order to get to the recorded gc root locations.
int num_deopts = loc->offset;
loc += num_deopts + 1;
int gc_locs = (cur_frame_->num_locations - (num_deopts + 1) - kSkipLocs);
// Locations come in pairs of a base pointer followed by a derived pointer.
// At the moment we assume derived pointers are the same as base pointers so
// we skip over them.
for (uint16_t i = 0; i < gc_locs; i += 2) {
switch (loc->kind) {
case kRegister:
reg_roots.push_back(loc->reg_num);
break;
case kIndirect:
stack_roots.push_back(loc->offset);
break;
default:
// Ignore
break;
}
loc += 2;
}
// The liveouts part of the stack map record is not of interest to us.
// However, it is dynamically sized, so we need to work out many records
// exist so that we can effectively jump over them.
int incr = sizeof(StkMapHeader) +
(cur_frame_->num_locations * sizeof(StkMapLocation));
auto* liveouts = align_8(ptr_offset<const LiveOutsHeader*>(cur_frame_, incr));
incr = sizeof(LiveOutsHeader) + (liveouts->num_liveouts * sizeof(LiveOut));
// LLVM V3 stackmap format requires padding here if we need to align to an 8
// byte boundary.
cur_frame_ = align_8(ptr_offset<const StkMapRecordHeader*>(liveouts, incr));
return FrameRoots(reg_roots, stack_roots);
}
SafepointTable StackmapV3Parser::Parse() {
auto* header = reinterpret_cast<const StkMapHeader*>(cursor_);
assert(header->version == kStackmapVersion &&
"Stackmap Parser is incorrect version");
// Work out the the offset needed to get to the first stack map frame record
// entry (i.e. call site). This needs to jump over the dynamically sized
// function and constant table.
uint32_t size_consts = header->num_constants * kSizeConstantEntry;
uint32_t size_fns = header->num_functions * sizeof(StkSizeRecord);
uint32_t rec_offset = sizeof(StkMapHeader) + size_consts + size_fns;
cur_frame_ = ptr_offset<const StkMapRecordHeader*>(cursor_, rec_offset);
// For each function in the stack map, we iterate over the stack map record
// list looking for its respective callsite, adding its entry to the table.
auto* fn = ptr_offset<const StkSizeRecord*>(cursor_, sizeof(StkMapHeader));
std::map<ReturnAddress, FrameRoots> roots;
for (uint32_t i = 0; i < header->num_functions; i++) {
for (uint32_t j = 0; j < fn->record_count; j++) {
ReturnAddress key = fn->address + cur_frame_->return_addr;
auto frame_roots = ParseFrame();
if (!frame_roots.empty())
roots.insert({key, frame_roots});
}
fn++;
}
return SafepointTable(roots, GetFramePtr());
}
} // namespace stackmap
SafepointTable GenSafepointTable() {
auto parser = stackmap::StackmapV3Parser();
return parser.Parse();
}
// 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_STACK_MAP_PARSER_H_
#define TOOLS_CLANG_STACK_MAPS_GC_STACK_MAP_PARSER_H_
#include "gc_api.h"
// The stackmap section in the binary has a non-trivial layout. We give it a
// char type so it can be iterated byte-by-byte, and re-cast as necessary by the
// parser.
extern const char __LLVM_StackMaps;
// This is done with a non-portable asm shim which places RBP in RAX
extern "C" uintptr_t GetFramePtr();
namespace stackmap {
// These structs group together fields in the stackmap section to be used by the
// parser. They are packed to prevent clang adding its own alignment. We don't
// care about constants. The LLVM docs for stackmaps can be found here:
// https://llvm.org/docs/StackMaps.html#stack-map-format
//
// As per the docs, a version 3 stackmap has the following layout:
//
// Header {
// uint8 : Stack Map Version (current version is 3)
// uint8 : Reserved (expected to be 0)
// uint16 : Reserved (expected to be 0)
// }
// uint32 : NumFunctions
// uint32 : NumConstants
// uint32 : NumRecords
// StkSizeRecord[NumFunctions] {
// uint64 : Function Address
// uint64 : Stack Size
// uint64 : Record Count
// }
// Constants[NumConstants] {
// uint64 : LargeConstant
// }
// StkMapRecord[NumRecords] {
// uint64 : PatchPoint ID
// uint32 : Instruction Offset
// uint16 : Reserved (record flags)
// uint16 : NumLocations
// Location[NumLocations] {
// uint8 : Register | Direct | Indirect | Constant | ConstantIndex
// uint8 : Reserved (expected to be 0)
// uint16 : Location Size
// uint16 : Dwarf RegNum
// uint16 : Reserved (expected to be 0)
// int32 : Offset or SmallConstant
// }
// uint32 : Padding (only if required to align to 8 byte)
// uint16 : Padding
// uint16 : NumLiveOuts
// LiveOuts[NumLiveOuts]
// uint16 : Dwarf RegNum
// uint8 : Reserved
// uint8 : Size in Bytes
// }
// uint32 : Padding (only if required to align to 8 byte)
// }
struct __attribute__((packed)) StkMapHeader {
uint8_t version;
uint8_t reserved1;
uint16_t reserved2;
uint32_t num_functions;
uint32_t num_constants;
uint32_t num_records;
};
struct __attribute__((packed)) StkSizeRecord {
uint64_t address;
uint64_t stack_size;
uint64_t record_count; // see https://reviews.llvm.org/D23487
};
struct __attribute__((packed)) StkMapRecordHeader {
uint64_t patchpoint_id;
uint32_t return_addr; // from the entry of the function
uint16_t flags;
uint16_t num_locations;
};
enum LocationKind {
kRegister = 0x1,
kDirect = 0x2,
kIndirect = 0x3,
kConstant = 0x4,
kConstIndex = 0x5
};
struct __attribute__((packed)) StkMapLocation {
uint8_t kind; // 1 byte sized `LocationKind` variant
uint8_t flags; // expected to be 0
uint16_t location_size;
uint16_t reg_num; // Dwarf register num
uint16_t reserved; // expected to be 0
int32_t offset; // either an offset or a "Small Constant"
};
struct __attribute__((packed)) LiveOutsHeader {
uint16_t padding;
uint16_t num_liveouts;
};
struct __attribute__((packed)) LiveOut {
uint16_t reg_num; // Dwarf register num
uint8_t flags;
uint8_t size; // in bytes
};
// A StackmapV3Parser encapsulates the parsing logic for reading from an
// .llvm_stackmap section in the ELF file. The .llvm_stackmap section is
// versioned and *not* backwards compatible.
class StackmapV3Parser {
public:
StackmapV3Parser() : cursor_(&__LLVM_StackMaps) {}
SafepointTable Parse();
private:
static constexpr uint8_t kStackmapVersion = 3;
static constexpr uint8_t kSizeConstantEntry = 8; // size in bytes
static constexpr uint8_t kSkipLocs = 2;
const char* cursor_;
const StkMapRecordHeader* cur_frame_;
// Get a new pointer of the same type to the one passed in arg0 + some byte(s)
// offset. Useful to prevent littering code with constant char* casting when
// all that's needed is to bump the ptr by a set amount of bytes. Note this
// *does not* perform any alignment.
template <typename T, typename U>
inline T ptr_offset(U ptr, int bytes) {
auto* newptr = reinterpret_cast<const char*>(ptr);
newptr += bytes;
return reinterpret_cast<T>(newptr);
}
// Align a pointer to the next 8 byte boundary
template <typename T>
inline T* align_8(T* ptr) {
auto* c = reinterpret_cast<const char*>(ptr);
return reinterpret_cast<T*>(((uintptr_t)c + 7) & ~7);
}
// Creates a FrameRoot entry for a callsite's stack map record. This jumps
// over and ignores a bunch of values in the stack map record that are not of
// interest to precise stack scanning in V8 / Blink. Stack map records make up
// the bulk of the .llvm_stackmap section. For reference, the format is shown
// below:
// StkMapRecord[NumRecords] {
// uint64 : PatchPoint ID
// uint32 : Instruction Offset
// uint16 : Reserved (record flags)
// uint16 : NumLocations
// Location[NumLocations] {
// uint8 : Register | Direct | Indirect | Constant | ConstantIndex
// uint8 : Reserved (expected to be 0)
// uint16 : Location Size
// uint16 : Dwarf RegNum
// uint16 : Reserved (expected to be 0)
// int32 : Offset or SmallConstant
// }
// uint32 : Padding (only if required to align to 8 byte)
// uint16 : Padding
// uint16 : NumLiveOuts
// LiveOuts[NumLiveOuts]
// uint16 : Dwarf RegNum
// uint8 : Reserved
// uint8 : Size in Bytes
// }
FrameRoots ParseFrame();
};
} // namespace stackmap
#endif // TOOLS_CLANG_STACK_MAPS_GC_STACK_MAP_PARSER_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment