2195c3282a
These changes were originally submitted separately on different days, and a major performance regression was seen after the first change when creating snapshots that led to both being reverted. However, that performance regression should be addressed by the followup. First change: "[vm] Treat the dispatch table as a root in the snapshot. Additional changes: * Only serialize a dispatch table in precompiled snapshots. * Add information in v8 snapshot profiles for the dispatch table. * Fix a typo in a field name. * Print the number of Instructions objects (or payloads, for precompiled bare instructions mode) in the fake cluster for the data section. * Fix v8 snapshots profiles so objects in memory mapped segments and only those are prefixed with "(RO) ". * Add names for Instructions objects in v8 snapshot profiles when we can use the assembly namer. * Add command line flag for old #define'd false flag." Second change: "[vm/aot] Keep GC-visible references to dispatch table Code entries. This change splits dispatch table handling into four distinct parts: * The dispatch table generator does not make a dispatch table directly, but rather creates an Array that contains the Code objects for dispatch table entries. * The precompiler takes this Array and puts it in the object store, which makes it a new GC root. * The serializer takes this information and serializes the dispatch table information in the same form as before. * The deserializer creates a DispatchTable object and populates it using the serialized information. The change in the precompiler ensures that the Code objects used in the dispatch table have GC-visible references. Thus, even if all other references to them from the other GC roots were removed, they would be accessible in the serializer in the case of a GC pass between the precompiler and serializer. This change also means that the serializer can retrieve and trace the Code objects directly rather than first looking up the Code objects by their entry point." Bug: https://github.com/dart-lang/sdk/issues/41022 Change-Id: I52c83b0536fc588da0bef9aed1f0c72e8ee4663f Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-linux-release-simarm-try,vm-kernel-precomp-linux-release-simarm64-try,vm-kernel-precomp-linux-release-simarm_x64-try,vm-kernel-precomp-android-release-arm64-try,vm-kernel-precomp-android-release-arm_x64-try,vm-kernel-precomp-mac-release-simarm64-try,vm-kernel-precomp-win-release-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/139285 Commit-Queue: Teagan Strickland <sstrickl@google.com> Reviewed-by: Alexander Aprelev <aam@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
435 lines
13 KiB
C++
435 lines
13 KiB
C++
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#ifndef RUNTIME_VM_DWARF_H_
|
|
#define RUNTIME_VM_DWARF_H_
|
|
|
|
#include "vm/allocation.h"
|
|
#include "vm/hash_map.h"
|
|
#include "vm/object.h"
|
|
#include "vm/zone.h"
|
|
|
|
namespace dart {
|
|
|
|
#ifdef DART_PRECOMPILER
|
|
|
|
class Elf;
|
|
class InliningNode;
|
|
class SnapshotTextObjectNamer;
|
|
|
|
struct ScriptIndexPair {
|
|
// Typedefs needed for the DirectChainedHashMap template.
|
|
typedef const Script* Key;
|
|
typedef intptr_t Value;
|
|
typedef ScriptIndexPair Pair;
|
|
|
|
static Key KeyOf(Pair kv) { return kv.script_; }
|
|
|
|
static Value ValueOf(Pair kv) { return kv.index_; }
|
|
|
|
static inline intptr_t Hashcode(Key key) {
|
|
return String::Handle(key->url()).Hash();
|
|
}
|
|
|
|
static inline bool IsKeyEqual(Pair pair, Key key) {
|
|
return pair.script_->raw() == key->raw();
|
|
}
|
|
|
|
ScriptIndexPair(const Script* s, intptr_t index) : script_(s), index_(index) {
|
|
ASSERT(!s->IsNull());
|
|
ASSERT(s->IsNotTemporaryScopedHandle());
|
|
}
|
|
|
|
ScriptIndexPair() : script_(NULL), index_(-1) {}
|
|
|
|
void Print() const;
|
|
|
|
const Script* script_;
|
|
intptr_t index_;
|
|
};
|
|
|
|
typedef DirectChainedHashMap<ScriptIndexPair> ScriptIndexMap;
|
|
|
|
struct FunctionIndexPair {
|
|
// Typedefs needed for the DirectChainedHashMap template.
|
|
typedef const Function* Key;
|
|
typedef intptr_t Value;
|
|
typedef FunctionIndexPair Pair;
|
|
|
|
static Key KeyOf(Pair kv) { return kv.function_; }
|
|
|
|
static Value ValueOf(Pair kv) { return kv.index_; }
|
|
|
|
static inline intptr_t Hashcode(Key key) { return key->token_pos().value(); }
|
|
|
|
static inline bool IsKeyEqual(Pair pair, Key key) {
|
|
return pair.function_->raw() == key->raw();
|
|
}
|
|
|
|
FunctionIndexPair(const Function* f, intptr_t index)
|
|
: function_(f), index_(index) {
|
|
ASSERT(!f->IsNull());
|
|
ASSERT(f->IsNotTemporaryScopedHandle());
|
|
}
|
|
|
|
FunctionIndexPair() : function_(NULL), index_(-1) {}
|
|
|
|
void Print() const;
|
|
|
|
const Function* function_;
|
|
intptr_t index_;
|
|
};
|
|
|
|
typedef DirectChainedHashMap<FunctionIndexPair> FunctionIndexMap;
|
|
|
|
struct CodeAddressPair {
|
|
// Typedefs needed for the DirectChainedHashMap template.
|
|
typedef const Code* Key;
|
|
typedef intptr_t Value;
|
|
typedef CodeAddressPair Pair;
|
|
|
|
static Key KeyOf(Pair kv) { return kv.code_; }
|
|
|
|
static Value ValueOf(Pair kv) { return kv.address_; }
|
|
|
|
static inline intptr_t Hashcode(Key key) {
|
|
// Code objects are always allocated in old space, so they don't move.
|
|
return key->PayloadStart();
|
|
}
|
|
|
|
static inline bool IsKeyEqual(Pair pair, Key key) {
|
|
return pair.code_->raw() == key->raw();
|
|
}
|
|
|
|
CodeAddressPair(const Code* c, intptr_t address)
|
|
: code_(c), address_(address) {
|
|
ASSERT(!c->IsNull());
|
|
ASSERT(c->IsNotTemporaryScopedHandle());
|
|
ASSERT(address >= 0);
|
|
}
|
|
|
|
CodeAddressPair() : code_(NULL), address_(-1) {}
|
|
|
|
void Print() const;
|
|
|
|
const Code* code_;
|
|
intptr_t address_;
|
|
};
|
|
|
|
typedef DirectChainedHashMap<CodeAddressPair> CodeAddressMap;
|
|
|
|
template <typename T>
|
|
class Trie : public ZoneAllocated {
|
|
public:
|
|
// Returns whether [key] is a valid trie key (that is, a C string that
|
|
// contains only characters for which charIndex returns a non-negative value).
|
|
static bool IsValidKey(const char* key) {
|
|
for (intptr_t i = 0; key[i] != '\0'; i++) {
|
|
if (ChildIndex(key[i]) < 0) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Adds a binding of [key] to [value] in [trie]. Assumes that the string in
|
|
// [key] is a valid trie key and does not already have a value in [trie].
|
|
//
|
|
// If [trie] is nullptr, then a new trie is created and a pointer to the new
|
|
// trie is returned. Otherwise, [trie] will be returned.
|
|
static Trie<T>* AddString(Zone* zone,
|
|
Trie<T>* trie,
|
|
const char* key,
|
|
const T* value);
|
|
|
|
// Adds a binding of [key] to [value]. Assumes that the string in [key] is a
|
|
// valid trie key and does not already have a value in this trie.
|
|
void AddString(Zone* zone, const char* key, const T* value) {
|
|
AddString(zone, this, key, value);
|
|
}
|
|
|
|
// Looks up the value stored for [key] in [trie]. If one is not found, then
|
|
// nullptr is returned.
|
|
//
|
|
// If [end] is not nullptr, then the longest prefix of [key] that is a valid
|
|
// trie key prefix will be used for the lookup and the value pointed to by
|
|
// [end] is set to the index after that prefix. Otherwise, the whole [key]
|
|
// is used.
|
|
static const T* Lookup(const Trie<T>* trie,
|
|
const char* key,
|
|
intptr_t* end = nullptr);
|
|
|
|
// Looks up the value stored for [key]. If one is not found, then nullptr is
|
|
// returned.
|
|
//
|
|
// If [end] is not nullptr, then the longest prefix of [key] that is a valid
|
|
// trie key prefix will be used for the lookup and the value pointed to by
|
|
// [end] is set to the index after that prefix. Otherwise, the whole [key]
|
|
// is used.
|
|
const T* Lookup(const char* key, intptr_t* end = nullptr) const {
|
|
return Lookup(this, key, end);
|
|
}
|
|
|
|
private:
|
|
// Currently, only the following characters can appear in obfuscated names:
|
|
// '_', '@', '0-9', 'a-z', 'A-Z'
|
|
static const intptr_t kNumValidChars = 64;
|
|
|
|
Trie() {
|
|
for (intptr_t i = 0; i < kNumValidChars; i++) {
|
|
children_[i] = nullptr;
|
|
}
|
|
}
|
|
|
|
static intptr_t ChildIndex(char c) {
|
|
if (c == '_') return 0;
|
|
if (c == '@') return 1;
|
|
if (c >= '0' && c <= '9') return ('9' - c) + 2;
|
|
if (c >= 'a' && c <= 'z') return ('z' - c) + 12;
|
|
if (c >= 'A' && c <= 'Z') return ('Z' - c) + 38;
|
|
return -1;
|
|
}
|
|
|
|
const T* value_ = nullptr;
|
|
Trie<T>* children_[kNumValidChars];
|
|
};
|
|
|
|
class Dwarf : public ZoneAllocated {
|
|
public:
|
|
Dwarf(Zone* zone, StreamingWriteStream* stream, Elf* elf);
|
|
|
|
Elf* elf() const { return elf_; }
|
|
|
|
// Stores the code object for later creating the line number program.
|
|
//
|
|
// Should only be called when the output is not ELF.
|
|
//
|
|
// Returns the stored index of the code object.
|
|
intptr_t AddCode(const Code& code);
|
|
|
|
// Stores the code object for later creating the line number program.
|
|
//
|
|
// [payload_offset] should be the offset of the payload within the text
|
|
// section. [name] is used to create an ELF static symbol for the payload.
|
|
//
|
|
// Should only be called when the output is ELF.
|
|
void AddCode(const Code& code, const char* name, intptr_t payload_offset);
|
|
|
|
intptr_t AddFunction(const Function& function);
|
|
intptr_t AddScript(const Script& script);
|
|
intptr_t LookupFunction(const Function& function);
|
|
intptr_t LookupScript(const Script& script);
|
|
|
|
void Write() {
|
|
WriteAbbreviations();
|
|
WriteCompilationUnit();
|
|
WriteLines();
|
|
}
|
|
|
|
private:
|
|
// Implements shared functionality for the two AddCode calls. Assumes the
|
|
// Code handle is appropriately zoned.
|
|
intptr_t AddCodeHelper(const Code& code);
|
|
|
|
static const intptr_t DW_TAG_compile_unit = 0x11;
|
|
static const intptr_t DW_TAG_inlined_subroutine = 0x1d;
|
|
static const intptr_t DW_TAG_subprogram = 0x2e;
|
|
|
|
static const intptr_t DW_CHILDREN_no = 0x0;
|
|
static const intptr_t DW_CHILDREN_yes = 0x1;
|
|
|
|
static const intptr_t DW_AT_sibling = 0x1;
|
|
static const intptr_t DW_AT_name = 0x3;
|
|
static const intptr_t DW_AT_stmt_list = 0x10;
|
|
static const intptr_t DW_AT_low_pc = 0x11;
|
|
static const intptr_t DW_AT_high_pc = 0x12;
|
|
static const intptr_t DW_AT_comp_dir = 0x1b;
|
|
static const intptr_t DW_AT_inline = 0x20;
|
|
static const intptr_t DW_AT_producer = 0x25;
|
|
static const intptr_t DW_AT_abstract_origin = 0x31;
|
|
static const intptr_t DW_AT_decl_column = 0x39;
|
|
static const intptr_t DW_AT_decl_file = 0x3a;
|
|
static const intptr_t DW_AT_decl_line = 0x3b;
|
|
static const intptr_t DW_AT_call_column = 0x57;
|
|
static const intptr_t DW_AT_call_file = 0x58;
|
|
static const intptr_t DW_AT_call_line = 0x59;
|
|
|
|
static const intptr_t DW_FORM_addr = 0x01;
|
|
static const intptr_t DW_FORM_string = 0x08;
|
|
static const intptr_t DW_FORM_udata = 0x0f;
|
|
static const intptr_t DW_FORM_ref4 = 0x13;
|
|
static const intptr_t DW_FORM_ref_udata = 0x15;
|
|
static const intptr_t DW_FORM_sec_offset = 0x17;
|
|
|
|
static const intptr_t DW_INL_not_inlined = 0x0;
|
|
static const intptr_t DW_INL_inlined = 0x1;
|
|
|
|
static const intptr_t DW_LNS_copy = 0x1;
|
|
static const intptr_t DW_LNS_advance_pc = 0x2;
|
|
static const intptr_t DW_LNS_advance_line = 0x3;
|
|
static const intptr_t DW_LNS_set_file = 0x4;
|
|
|
|
static const intptr_t DW_LNE_end_sequence = 0x01;
|
|
static const intptr_t DW_LNE_set_address = 0x02;
|
|
|
|
enum {
|
|
kCompilationUnit = 1,
|
|
kAbstractFunction,
|
|
kConcreteFunction,
|
|
kInlinedFunction,
|
|
};
|
|
|
|
void Print(const char* format, ...) PRINTF_ATTRIBUTE(2, 3);
|
|
|
|
#if defined(TARGET_ARCH_IS_32_BIT)
|
|
#define FORM_ADDR ".4byte"
|
|
#elif defined(TARGET_ARCH_IS_64_BIT)
|
|
#define FORM_ADDR ".8byte"
|
|
#endif
|
|
|
|
void PrintNamedAddress(const char* name) { Print(FORM_ADDR " %s\n", name); }
|
|
void PrintNamedAddressWithOffset(const char* name, intptr_t offset) {
|
|
Print(FORM_ADDR " %s + %" Pd "\n", name, offset);
|
|
}
|
|
|
|
#undef FORM_ADDR
|
|
|
|
void sleb128(intptr_t value) {
|
|
if (asm_stream_ != nullptr) {
|
|
Print(".sleb128 %" Pd "\n", value);
|
|
}
|
|
if (elf_ != nullptr) {
|
|
bool is_last_part = false;
|
|
while (!is_last_part) {
|
|
uint8_t part = value & 0x7F;
|
|
value >>= 7;
|
|
if ((value == 0 && (part & 0x40) == 0) ||
|
|
(value == static_cast<intptr_t>(-1) && (part & 0x40) != 0)) {
|
|
is_last_part = true;
|
|
} else {
|
|
part |= 0x80;
|
|
}
|
|
bin_stream_->WriteBytes(reinterpret_cast<const uint8_t*>(&part),
|
|
sizeof(part));
|
|
}
|
|
}
|
|
}
|
|
void uleb128(uintptr_t value) {
|
|
if (asm_stream_ != nullptr) {
|
|
Print(".uleb128 %" Pd "\n", value);
|
|
}
|
|
if (elf_ != nullptr) {
|
|
bool is_last_part = false;
|
|
while (!is_last_part) {
|
|
uint8_t part = value & 0x7F;
|
|
value >>= 7;
|
|
if (value == 0) {
|
|
is_last_part = true;
|
|
} else {
|
|
part |= 0x80;
|
|
}
|
|
bin_stream_->WriteBytes(reinterpret_cast<const uint8_t*>(&part),
|
|
sizeof(part));
|
|
}
|
|
}
|
|
}
|
|
void u1(uint8_t value) {
|
|
if (asm_stream_ != nullptr) {
|
|
Print(".byte %u\n", value);
|
|
}
|
|
if (elf_ != nullptr) {
|
|
bin_stream_->WriteBytes(reinterpret_cast<const uint8_t*>(&value),
|
|
sizeof(value));
|
|
}
|
|
}
|
|
void u2(uint16_t value) {
|
|
if (asm_stream_ != nullptr) {
|
|
Print(".2byte %u\n", value);
|
|
}
|
|
if (elf_ != nullptr) {
|
|
bin_stream_->WriteBytes(reinterpret_cast<const uint8_t*>(&value),
|
|
sizeof(value));
|
|
}
|
|
}
|
|
intptr_t u4(uint32_t value) {
|
|
if (asm_stream_ != nullptr) {
|
|
Print(".4byte %" Pu32 "\n", value);
|
|
}
|
|
if (elf_ != nullptr) {
|
|
intptr_t fixup = position();
|
|
bin_stream_->WriteBytes(reinterpret_cast<const uint8_t*>(&value),
|
|
sizeof(value));
|
|
return fixup;
|
|
}
|
|
return -1;
|
|
}
|
|
void fixup_u4(intptr_t position, uint32_t value) {
|
|
RELEASE_ASSERT(elf_ != nullptr);
|
|
memmove(bin_stream_->buffer() + position, &value, sizeof(value));
|
|
}
|
|
void u8(uint64_t value) {
|
|
if (asm_stream_ != nullptr) {
|
|
Print(".8byte %" Pu64 "\n", value);
|
|
}
|
|
if (elf_ != nullptr) {
|
|
bin_stream_->WriteBytes(reinterpret_cast<const uint8_t*>(&value),
|
|
sizeof(value));
|
|
}
|
|
}
|
|
void addr(uword value) {
|
|
RELEASE_ASSERT(elf_ != nullptr);
|
|
#if defined(TARGET_ARCH_IS_32_BIT)
|
|
u4(value);
|
|
#else
|
|
u8(value);
|
|
#endif
|
|
}
|
|
void string(const char* cstr) { // NOLINT
|
|
if (asm_stream_ != nullptr) {
|
|
Print(".string \"%s\"\n", cstr); // NOLINT
|
|
}
|
|
if (elf_ != nullptr) {
|
|
bin_stream_->WriteBytes(reinterpret_cast<const uint8_t*>(cstr),
|
|
strlen(cstr) + 1);
|
|
}
|
|
}
|
|
intptr_t position() {
|
|
RELEASE_ASSERT(elf_ != nullptr);
|
|
return bin_stream_->Position();
|
|
}
|
|
|
|
void WriteAbbreviations();
|
|
void WriteCompilationUnit();
|
|
void WriteAbstractFunctions();
|
|
void WriteConcreteFunctions();
|
|
InliningNode* ExpandInliningTree(const Code& code);
|
|
void WriteInliningNode(InliningNode* node,
|
|
intptr_t root_code_index,
|
|
intptr_t root_code_offset,
|
|
const Script& parent_script,
|
|
SnapshotTextObjectNamer* namer);
|
|
void WriteLines();
|
|
|
|
const char* Deobfuscate(const char* cstr);
|
|
static Trie<const char>* CreateReverseObfuscationTrie(Zone* zone);
|
|
|
|
Zone* const zone_;
|
|
Elf* const elf_;
|
|
Trie<const char>* const reverse_obfuscation_trie_;
|
|
StreamingWriteStream* asm_stream_;
|
|
WriteStream* bin_stream_;
|
|
ZoneGrowableArray<const Code*> codes_;
|
|
CodeAddressMap code_to_address_;
|
|
ZoneGrowableArray<const Function*> functions_;
|
|
FunctionIndexMap function_to_index_;
|
|
ZoneGrowableArray<const Script*> scripts_;
|
|
ScriptIndexMap script_to_index_;
|
|
uint32_t* abstract_origins_;
|
|
intptr_t temp_;
|
|
};
|
|
|
|
#endif // DART_PRECOMPILER
|
|
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_VM_DWARF_H_
|