dart-sdk/runtime/vm/image_snapshot.h
Vyacheslav Egorov 13d27d669d [vm] Improve Code cluster layout for startup
This change bakes binary search table which maps PC ranges
to corresponding stack maps and Code objects (if still present
in the snapshot) into RO data section of the snapshot - instead
of constructing it at load time.

This allows to considerably reduce amount of work done when
loading Code cluster for programs which have majority of their
Code objects discarded (i.e. in DWARF stack traces mode): as
we no longer write / read any information for discarded Code
objects.

This CL also changes program visitor to deduplicate Code objects
if their instructions are deduplicated in AOT mode. Only a single
Code object can be choose as a representative for the given
PC range so it does not make sense to write multiple Code objects
into the snapshot which refer to the same Instructions.

The overall improvement is hard to quantify but ReadProgramSnapshot
shows the following improvement when starting a large
Flutter application on a slow Android device:

  before  223.55±59.94 (192.02 .. 391.74) ms
  after   178.06±47.03 (151.31 .. 291.34) ms

This CL packs CompressedStackMaps next to the binary search table
itself allowing us to address them via offsets instead of
pointers.

Snapshot sizes are actually affected positively by this change. On
the same large Flutter application I see

  DWARF stack traces on:  -1.34% total SO size
  DWARF stack traces off: -1.63% total SO size

Issue https://github.com/dart-lang/sdk/issues/46116

TEST=ci

Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-debug-simarm64c-try,vm-kernel-precomp-linux-debug-simarm_x64-try,vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-debug-x64c-try,vm-kernel-precomp-linux-product-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-linux-release-x64-try
Change-Id: Ic997045a33daa81ec68df462a0792915885df66b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/220766
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
2021-12-16 10:39:49 +00:00

647 lines
24 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_IMAGE_SNAPSHOT_H_
#define RUNTIME_VM_IMAGE_SNAPSHOT_H_
#include <memory>
#include <utility>
#include "platform/assert.h"
#include "platform/utils.h"
#include "vm/allocation.h"
#include "vm/compiler/runtime_api.h"
#include "vm/datastream.h"
#include "vm/elf.h"
#include "vm/globals.h"
#include "vm/growable_array.h"
#include "vm/hash_map.h"
#include "vm/object.h"
#include "vm/reusable_handles.h"
#include "vm/type_testing_stubs.h"
#include "vm/v8_snapshot_writer.h"
namespace dart {
// Forward declarations.
class BitsContainer;
class Code;
class Dwarf;
class Elf;
class Instructions;
class Object;
class Image : ValueObject {
public:
explicit Image(const void* raw_memory)
: Image(reinterpret_cast<uword>(raw_memory)) {}
explicit Image(const uword raw_memory)
: raw_memory_(raw_memory),
snapshot_size_(FieldValue(raw_memory, HeaderField::ImageSize)),
extra_info_(ExtraInfo(raw_memory_, snapshot_size_)) {
ASSERT(Utils::IsAligned(raw_memory, kMaxObjectAlignment));
}
// Even though an Image is read-only memory, we must return a void* here.
// All objects in an Image are pre-marked, though, so the GC will not attempt
// to change the returned memory.
void* object_start() const {
return reinterpret_cast<void*>(raw_memory_ + kHeaderSize);
}
uword object_size() const { return snapshot_size_ - kHeaderSize; }
bool contains(uword address) const {
uword start = reinterpret_cast<uword>(object_start());
return address >= start && (address - start < object_size());
}
// Returns the address of the BSS section, or nullptr if one is not available.
// Only has meaning for instructions images from precompiled snapshots.
uword* bss() const;
// Returns the relocated address of the isolate's instructions, or 0 if
// one is not available. Only has meaning for instructions images from
// precompiled snapshots.
uword instructions_relocated_address() const;
// Returns the GNU build ID, or nullptr if not available. See
// build_id_length() for the length of the returned buffer. Only has meaning
// for instructions images from precompiled snapshots.
const uint8_t* build_id() const;
// Returns the length of the GNU build ID returned by build_id(). Only has
// meaning for instructions images from precompiled snapshots.
intptr_t build_id_length() const;
// Returns whether this instructions section was compiled to ELF. Only has
// meaning for instructions images from precompiled snapshots.
bool compiled_to_elf() const;
private:
// Word-sized fields in an Image object header.
enum class HeaderField : intptr_t {
// The size of the image (total of header and payload).
ImageSize,
// The offset of the InstructionsSection object in the image. Note this
// offset is from the start of the _image_, _not_ from its payload start,
// so we can detect images without an InstructionsSection by a 0 value here.
InstructionsSectionOffset,
// If adding more fields, updating kHeaderFields below. (However, more
// fields _can't_ be added on 64-bit architectures, see the restrictions
// on kHeaderSize below.)
};
// Number of fields described by the HeaderField enum.
static constexpr intptr_t kHeaderFields =
static_cast<intptr_t>(HeaderField::InstructionsSectionOffset) + 1;
static uword FieldValue(uword raw_memory, HeaderField field) {
return reinterpret_cast<const uword*>(
raw_memory)[static_cast<intptr_t>(field)];
}
// Constants used to denote special values for the offsets in the Image
// object header and the fields of the InstructionsSection object.
static constexpr intptr_t kNoInstructionsSection = 0;
static constexpr intptr_t kNoBssSection = 0;
static constexpr intptr_t kNoRelocatedAddress = 0;
static constexpr intptr_t kNoBuildId = 0;
// The size of the Image object header.
//
// Note: Image::kHeaderSize is _not_ an architecture-dependent constant,
// and so there is no compiler::target::Image::kHeaderSize.
static constexpr intptr_t kHeaderSize = kMaxObjectAlignment;
// Explicitly double-checking kHeaderSize is never changed. Increasing the
// Image header size would mean objects would not start at a place expected
// by parts of the VM (like the GC) that use Image pages as HeapPages.
static_assert(kHeaderSize == kMaxObjectAlignment,
"Image page cannot be used as HeapPage");
// Make sure that the number of fields in the Image header fit both on the
// host and target architectures.
static_assert(kHeaderFields * kWordSize <= kHeaderSize,
"Too many fields in Image header for host architecture");
static_assert(kHeaderFields * compiler::target::kWordSize <= kHeaderSize,
"Too many fields in Image header for target architecture");
// We don't use a handle or the tagged pointer because this object cannot be
// moved in memory by the GC.
static const UntaggedInstructionsSection* ExtraInfo(const uword raw_memory,
const uword size);
// Most internal uses would cast this to uword, so just store it as such.
const uword raw_memory_;
const intptr_t snapshot_size_;
const UntaggedInstructionsSection* const extra_info_;
// For access to private constants.
friend class AssemblyImageWriter;
friend class BitsContainer;
friend class BlobImageWriter;
friend class ImageWriter;
DISALLOW_COPY_AND_ASSIGN(Image);
};
class ImageReader : public ZoneAllocated {
public:
ImageReader(const uint8_t* data_image, const uint8_t* instructions_image);
ApiErrorPtr VerifyAlignment() const;
ONLY_IN_PRECOMPILED(uword GetBareInstructionsAt(uint32_t offset) const);
ONLY_IN_PRECOMPILED(uword GetBareInstructionsEnd() const);
InstructionsPtr GetInstructionsAt(uint32_t offset) const;
ObjectPtr GetObjectAt(uint32_t offset) const;
private:
const uint8_t* data_image_;
const uint8_t* instructions_image_;
DISALLOW_COPY_AND_ASSIGN(ImageReader);
};
struct ObjectOffsetPair {
public:
ObjectOffsetPair() : ObjectOffsetPair(NULL, 0) {}
ObjectOffsetPair(ObjectPtr obj, int32_t off) : object(obj), offset(off) {}
ObjectPtr object;
int32_t offset;
};
class ObjectOffsetTrait {
public:
// Typedefs needed for the DirectChainedHashMap template.
typedef ObjectPtr Key;
typedef int32_t Value;
typedef ObjectOffsetPair Pair;
static Key KeyOf(Pair kv) { return kv.object; }
static Value ValueOf(Pair kv) { return kv.offset; }
static uword Hash(Key key);
static inline bool IsKeyEqual(Pair pair, Key key);
};
typedef DirectChainedHashMap<ObjectOffsetTrait> ObjectOffsetMap;
// A command which instructs the image writer to emit something into the ".text"
// segment.
//
// For now this supports
//
// * emitting the instructions of a [Code] object
// * emitting a trampoline of a certain size
//
struct ImageWriterCommand {
enum Opcode {
InsertInstructionOfCode,
InsertBytesOfTrampoline,
};
ImageWriterCommand(intptr_t expected_offset, CodePtr code)
: expected_offset(expected_offset),
op(ImageWriterCommand::InsertInstructionOfCode),
insert_instruction_of_code({code}) {}
ImageWriterCommand(intptr_t expected_offset,
uint8_t* trampoline_bytes,
intptr_t trampoine_length)
: expected_offset(expected_offset),
op(ImageWriterCommand::InsertBytesOfTrampoline),
insert_trampoline_bytes({trampoline_bytes, trampoine_length}) {}
// The offset (relative to the very first [ImageWriterCommand]) we expect
// this [ImageWriterCommand] to have.
intptr_t expected_offset;
Opcode op;
union {
struct {
CodePtr code;
} insert_instruction_of_code;
struct {
uint8_t* buffer;
intptr_t buffer_length;
} insert_trampoline_bytes;
};
};
class ImageWriter : public ValueObject {
public:
explicit ImageWriter(Thread* thread);
virtual ~ImageWriter() {}
// Alignment constants used in writing ELF or assembly snapshots.
// BSS sections contain word-sized data.
static constexpr intptr_t kBssAlignment = compiler::target::kWordSize;
// ROData sections contain objects wrapped in an Image object.
static constexpr intptr_t kRODataAlignment = kMaxObjectAlignment;
// Text sections contain objects (even in bare instructions mode) wrapped
// in an Image object.
static constexpr intptr_t kTextAlignment = kMaxObjectAlignment;
void ResetOffsets() {
next_data_offset_ = Image::kHeaderSize;
next_text_offset_ = Image::kHeaderSize;
#if defined(DART_PRECOMPILER)
if (FLAG_precompiled_mode) {
// We reserve space for the initial InstructionsSection object. It is
// manually serialized since it includes offsets to other snapshot parts.
// It contains all the payloads which start directly after the header.
next_text_offset_ += compiler::target::InstructionsSection::HeaderSize();
}
#endif
objects_.Clear();
instructions_.Clear();
}
// Will start preparing the ".text" segment by interpreting the provided
// [ImageWriterCommand]s.
void PrepareForSerialization(GrowableArray<ImageWriterCommand>* commands);
bool IsROSpace() const {
return offset_space_ == IdSpace::kVmData ||
offset_space_ == IdSpace::kVmText ||
offset_space_ == IdSpace::kIsolateData ||
offset_space_ == IdSpace::kIsolateText;
}
int32_t GetTextOffsetFor(InstructionsPtr instructions, CodePtr code);
uint32_t GetDataOffsetFor(ObjectPtr raw_object);
uint32_t AddBytesToData(uint8_t* bytes, intptr_t length);
void Write(NonStreamingWriteStream* clustered_stream, bool vm);
intptr_t data_size() const { return next_data_offset_; }
intptr_t text_size() const { return next_text_offset_; }
intptr_t GetTextObjectCount() const;
void GetTrampolineInfo(intptr_t* count, intptr_t* size) const;
void DumpStatistics();
void SetProfileWriter(V8SnapshotProfileWriter* profile_writer) {
profile_writer_ = profile_writer;
}
void ClearProfileWriter() { profile_writer_ = nullptr; }
void TraceInstructions(const Instructions& instructions);
static intptr_t SizeInSnapshot(ObjectPtr object);
static intptr_t SizeInSnapshot(const Object& object) {
return SizeInSnapshot(object.ptr());
}
// Returns nullptr if there is no profile writer.
const char* ObjectTypeForProfile(const Object& object) const;
static const char* TagObjectTypeAsReadOnly(Zone* zone, const char* type);
enum class ProgramSection {
Text, // Instructions.
Data, // Read-only data.
Bss, // Statically allocated variables initialized at load.
BuildId, // GNU build ID (when applicable)
};
protected:
virtual void WriteBss(bool vm) = 0;
virtual void WriteROData(NonStreamingWriteStream* clustered_stream, bool vm);
void WriteText(bool vm);
// Returns the standard Dart dynamic symbol name for the given VM isolate (if
// vm is true) or application isolate (otherwise) section. Some sections are
// shared by both.
const char* SectionSymbol(ProgramSection section, bool vm) const;
static uword GetMarkedTags(classid_t cid,
intptr_t size,
bool is_canonical = false);
static uword GetMarkedTags(const Object& obj);
void DumpInstructionStats();
void DumpInstructionsSizes();
struct InstructionsData {
InstructionsData(InstructionsPtr insns, CodePtr code, intptr_t text_offset)
: raw_insns_(insns),
raw_code_(code),
text_offset_(text_offset),
trampoline_bytes(nullptr),
trampoline_length(0) {}
InstructionsData(uint8_t* trampoline_bytes,
intptr_t trampoline_length,
intptr_t text_offset)
: raw_insns_(nullptr),
raw_code_(nullptr),
text_offset_(text_offset),
trampoline_bytes(trampoline_bytes),
trampoline_length(trampoline_length) {}
union {
InstructionsPtr raw_insns_;
const Instructions* insns_;
};
union {
CodePtr raw_code_;
const Code* code_;
};
intptr_t text_offset_;
uint8_t* trampoline_bytes;
intptr_t trampoline_length;
};
struct ObjectData {
explicit ObjectData(ObjectPtr raw_obj)
: raw_obj(raw_obj), is_object(true) {}
ObjectData(uint8_t* buf, intptr_t length)
: bytes({buf, length}), is_object(false) {}
union {
struct {
uint8_t* buf;
intptr_t length;
} bytes;
ObjectPtr raw_obj;
const Object* obj;
};
bool is_object;
};
// Methods abstracting out the particulars of the underlying concrete writer.
// Marks the entrance into a particular ProgramSection for either the VM
// isolate (if vm is true) or application isolate (if not). Returns false if
// this section should not be written.
virtual bool EnterSection(ProgramSection name,
bool vm,
intptr_t alignment) = 0;
// Marks the exit from a particular ProgramSection, allowing subclasses to
// do any post-writing work.
virtual void ExitSection(ProgramSection name, bool vm, intptr_t size) = 0;
// Writes a prologue to the text section that describes how to interpret
// Dart stack frames using DWARF's Call Frame Information (CFI).
virtual void FrameUnwindPrologue() = 0;
// Writes an epilogue to the text section that marks the end of instructions
// covered by the CFI information in the prologue.
virtual void FrameUnwindEpilogue() = 0;
// Writes a target uword-sized value to the section contents.
virtual intptr_t WriteTargetWord(word value) = 0;
// Writes a sequence of bytes of length [size] from address [bytes] to the
// section contents.
virtual intptr_t WriteBytes(const void* bytes, intptr_t size) = 0;
// Pads the section contents to a given alignment with zeroes.
virtual intptr_t Align(intptr_t alignment, intptr_t offset) = 0;
#if defined(DART_PRECOMPILER)
// Writes a target word-sized value that depends on the final relocated
// addresses of the sections named by the two symbols. If T is the final
// relocated address of the target section and S is the final relocated
// address of the source, the final value is:
// (T + target_offset + target_addend) - (S + source_offset)
virtual intptr_t Relocation(intptr_t section_offset,
const char* source_symbol,
intptr_t source_offset,
const char* target_symbol,
intptr_t target_offset) = 0;
// Writes a target word-sized value that contains the relocated address
// pointed to by the given symbol.
virtual intptr_t RelocatedAddress(intptr_t section_offset,
const char* symbol) = 0;
// Creates a static symbol for the given Code object when appropriate.
virtual void AddCodeSymbol(const Code& code,
const char* symbol,
intptr_t section_offset) = 0;
// Overloaded convenience versions of the above virtual methods.
// An overload of Relocation where the target and source offsets and
// target addend are 0.
intptr_t Relocation(intptr_t section_offset,
const char* source_symbol,
const char* target_symbol) {
return Relocation(section_offset, source_symbol, 0, target_symbol, 0);
}
#endif
// Writes a fixed-sized value of type T to the section contents.
template <typename T>
intptr_t WriteFixed(T value) {
return WriteBytes(&value, sizeof(value));
}
// Like Align, but instead of padding with zeroes, the appropriate break
// instruction for the target architecture is used.
intptr_t AlignWithBreakInstructions(intptr_t alignment, intptr_t offset);
Thread* const thread_;
Zone* const zone_;
intptr_t next_data_offset_;
intptr_t next_text_offset_;
GrowableArray<ObjectData> objects_;
GrowableArray<InstructionsData> instructions_;
IdSpace offset_space_ = IdSpace::kSnapshot;
V8SnapshotProfileWriter* profile_writer_ = nullptr;
const char* const image_type_;
const char* const instructions_section_type_;
const char* const instructions_type_;
const char* const trampoline_type_;
// Used to make sure Code symbols are unique across text sections.
intptr_t unique_symbol_counter_ = 0;
template <class T>
friend class TraceImageObjectScope;
friend class SnapshotTextObjectNamer; // For InstructionsData.
private:
static intptr_t SizeInSnapshotForBytes(intptr_t length);
DISALLOW_COPY_AND_ASSIGN(ImageWriter);
};
#if defined(DART_PRECOMPILER)
#define AutoTraceImage(object, section_offset, stream) \
TraceImageObjectScope<std::remove_pointer<decltype(stream)>::type> \
AutoTraceImageObjectScopeVar##__COUNTER__(this, section_offset, stream, \
object);
template <typename T>
class TraceImageObjectScope : ValueObject {
public:
TraceImageObjectScope(ImageWriter* writer,
intptr_t section_offset,
const T* stream,
const Object& object)
: writer_(ASSERT_NOTNULL(writer)),
stream_(ASSERT_NOTNULL(stream)),
section_offset_(section_offset),
start_offset_(stream_->Position() - section_offset),
object_type_(writer->ObjectTypeForProfile(object)),
object_name_(object.IsString() ? object.ToCString() : nullptr) {}
~TraceImageObjectScope() {
if (writer_->profile_writer_ == nullptr) return;
ASSERT(writer_->IsROSpace());
writer_->profile_writer_->SetObjectTypeAndName(
{writer_->offset_space_, start_offset_}, object_type_, object_name_);
writer_->profile_writer_->AttributeBytesTo(
{writer_->offset_space_, start_offset_},
stream_->Position() - section_offset_ - start_offset_);
}
private:
ImageWriter* const writer_;
const T* const stream_;
const intptr_t section_offset_;
const intptr_t start_offset_;
const char* const object_type_;
const char* const object_name_;
DISALLOW_COPY_AND_ASSIGN(TraceImageObjectScope);
};
class SnapshotTextObjectNamer : ValueObject {
public:
explicit SnapshotTextObjectNamer(Zone* zone)
: zone_(ASSERT_NOTNULL(zone)),
owner_(Object::Handle(zone)),
string_(String::Handle(zone)),
insns_(Instructions::Handle(zone)),
store_(IsolateGroup::Current()->object_store()) {}
const char* StubNameForType(const AbstractType& type) const;
const char* SnapshotNameFor(intptr_t code_index, const Code& code);
const char* SnapshotNameFor(intptr_t index,
const ImageWriter::InstructionsData& data);
private:
Zone* const zone_;
Object& owner_;
String& string_;
Instructions& insns_;
ObjectStore* const store_;
TypeTestingStubNamer namer_;
DISALLOW_COPY_AND_ASSIGN(SnapshotTextObjectNamer);
};
class AssemblyImageWriter : public ImageWriter {
public:
AssemblyImageWriter(Thread* thread,
BaseWriteStream* stream,
bool strip = false,
Elf* debug_elf = nullptr);
void Finalize();
private:
virtual void WriteBss(bool vm);
virtual void WriteROData(NonStreamingWriteStream* clustered_stream, bool vm);
virtual bool EnterSection(ProgramSection section,
bool vm,
intptr_t alignment);
virtual void ExitSection(ProgramSection name, bool vm, intptr_t size);
virtual intptr_t WriteTargetWord(word value);
virtual intptr_t WriteBytes(const void* bytes, intptr_t size);
virtual intptr_t Align(intptr_t alignment, intptr_t offset = 0);
virtual intptr_t Relocation(intptr_t section_offset,
const char* source_symbol,
intptr_t source_offset,
const char* target_symbol,
intptr_t target_offset);
virtual intptr_t RelocatedAddress(intptr_t section_offset,
const char* symbol) {
// Cannot calculate snapshot-relative addresses in assembly snapshots.
return WriteTargetWord(Image::kNoRelocatedAddress);
}
virtual void FrameUnwindPrologue();
virtual void FrameUnwindEpilogue();
virtual void AddCodeSymbol(const Code& code,
const char* symbol,
intptr_t offset);
BaseWriteStream* const assembly_stream_;
Dwarf* const assembly_dwarf_;
Elf* const debug_elf_;
// Used in Relocation to output "(.)" for relocations involving the current
// section position and creating local symbols in AddCodeSymbol.
const char* current_section_symbol_ = nullptr;
// Used for creating local symbols for code objects in the debugging info,
// if separately written.
ZoneGrowableArray<Elf::SymbolData>* current_symbols_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(AssemblyImageWriter);
};
#endif
class BlobImageWriter : public ImageWriter {
public:
BlobImageWriter(Thread* thread,
NonStreamingWriteStream* vm_instructions,
NonStreamingWriteStream* isolate_instructions,
Elf* debug_elf = nullptr,
Elf* elf = nullptr);
private:
virtual void WriteBss(bool vm);
virtual void WriteROData(NonStreamingWriteStream* clustered_stream, bool vm);
virtual bool EnterSection(ProgramSection section,
bool vm,
intptr_t alignment);
virtual void ExitSection(ProgramSection name, bool vm, intptr_t size);
virtual intptr_t WriteTargetWord(word value);
virtual intptr_t WriteBytes(const void* bytes, intptr_t size);
virtual intptr_t Align(intptr_t alignment, intptr_t offset);
// TODO(rmacnak): Generate .debug_frame / .eh_frame / .arm.exidx to
// provide unwinding information.
virtual void FrameUnwindPrologue() {}
virtual void FrameUnwindEpilogue() {}
#if defined(DART_PRECOMPILER)
virtual intptr_t Relocation(intptr_t section_offset,
const char* source_symbol,
intptr_t source_offset,
const char* target_symbol,
intptr_t target_offset);
virtual intptr_t RelocatedAddress(intptr_t section_offset,
const char* target_symbol) {
// ELF symbol tables always have a reserved symbol with name "" and value 0.
return ImageWriter::Relocation(section_offset, "", target_symbol);
}
virtual void AddCodeSymbol(const Code& code,
const char* symbol,
intptr_t offset);
// Set on section entrance to a new array containing the relocations for the
// current section.
ZoneGrowableArray<Elf::Relocation>* current_relocations_ = nullptr;
// Set on section entrance to a new array containing the local symbol data
// for the current section.
ZoneGrowableArray<Elf::SymbolData>* current_symbols_ = nullptr;
#endif
NonStreamingWriteStream* const vm_instructions_;
NonStreamingWriteStream* const isolate_instructions_;
Elf* const elf_;
Elf* const debug_elf_;
// Used to detect relocations or relocated address requests involving the
// current section and creating local symbols in AddCodeSymbol.
const char* current_section_symbol_ = nullptr;
// Set on section entrance to the stream that should be used by the writing
// methods.
NonStreamingWriteStream* current_section_stream_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(BlobImageWriter);
};
} // namespace dart
#endif // RUNTIME_VM_IMAGE_SNAPSHOT_H_