Files
sdk/runtime/vm/compiler/assembler/disassembler.cc
T
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

500 lines
18 KiB
C++

// Copyright (c) 2011, 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.
#include "vm/compiler/assembler/disassembler.h"
#include "platform/text_buffer.h"
#include "platform/unaligned.h"
#include "vm/code_comments.h"
#include "vm/code_patcher.h"
#include "vm/dart_entry.h"
#include "vm/deopt_instructions.h"
#include "vm/globals.h"
#include "vm/instructions.h"
#include "vm/json_stream.h"
#include "vm/log.h"
#include "vm/os.h"
namespace dart {
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
#if !defined(DART_PRECOMPILED_RUNTIME)
DECLARE_FLAG(bool, trace_inlining_intervals);
#endif
DEFINE_FLAG(bool, trace_source_positions, false, "Source position diagnostics");
void DisassembleToStdout::ConsumeInstruction(char* hex_buffer,
intptr_t hex_size,
char* human_buffer,
intptr_t human_size,
Object* object,
uword pc) {
static const int kHexColumnWidth = 23;
#if defined(TARGET_ARCH_IS_32_BIT)
THR_Print("0x%" Px32 " %s", static_cast<uint32_t>(pc), hex_buffer);
#else
THR_Print("0x%" Px64 " %s", static_cast<uint64_t>(pc), hex_buffer);
#endif
int hex_length = strlen(hex_buffer);
if (hex_length < kHexColumnWidth) {
for (int i = kHexColumnWidth - hex_length; i > 0; i--) {
THR_Print(" ");
}
}
THR_Print("%s", human_buffer);
if (object != NULL) {
THR_Print(" %s", object->ToCString());
}
THR_Print("\n");
}
void DisassembleToStdout::Print(const char* format, ...) {
va_list args;
va_start(args, format);
THR_VPrint(format, args);
va_end(args);
}
void DisassembleToJSONStream::ConsumeInstruction(char* hex_buffer,
intptr_t hex_size,
char* human_buffer,
intptr_t human_size,
Object* object,
uword pc) {
// Instructions are represented as four consecutive values in a JSON array.
// The first is the address of the instruction, the second is the hex string,
// of the code, and the third is a human readable string, and the fourth is
// the object loaded by the instruction.
jsarr_.AddValueF("%" Pp "", pc);
jsarr_.AddValue(hex_buffer);
jsarr_.AddValue(human_buffer);
if (object != NULL) {
jsarr_.AddValue(*object);
} else {
jsarr_.AddValueNull(); // Not a reference to null.
}
}
void DisassembleToJSONStream::Print(const char* format, ...) {
va_list measure_args;
va_start(measure_args, format);
intptr_t len = Utils::VSNPrint(NULL, 0, format, measure_args);
va_end(measure_args);
char* p = reinterpret_cast<char*>(malloc(len + 1));
va_list print_args;
va_start(print_args, format);
intptr_t len2 = Utils::VSNPrint(p, len, format, print_args);
va_end(print_args);
ASSERT(len == len2);
for (intptr_t i = 0; i < len; i++) {
if (p[i] == '\n' || p[i] == '\r') {
p[i] = ' ';
}
}
// Instructions are represented as four consecutive values in a JSON array.
// Comments only use the third slot. See above comment for more information.
jsarr_.AddValueNull();
jsarr_.AddValueNull();
jsarr_.AddValue(p);
jsarr_.AddValueNull();
free(p);
}
void DisassembleToMemory::ConsumeInstruction(char* hex_buffer,
intptr_t hex_size,
char* human_buffer,
intptr_t human_size,
Object* object,
uword pc) {
if (overflowed_) {
return;
}
intptr_t len = strlen(human_buffer);
if (remaining_ < len + 100) {
*buffer_++ = '.';
*buffer_++ = '.';
*buffer_++ = '.';
*buffer_++ = '\n';
*buffer_++ = '\0';
overflowed_ = true;
return;
}
memmove(buffer_, human_buffer, len);
buffer_ += len;
remaining_ -= len;
*buffer_++ = '\n';
remaining_--;
*buffer_ = '\0';
}
void DisassembleToMemory::Print(const char* format, ...) {
if (overflowed_) {
return;
}
va_list measure_args;
va_start(measure_args, format);
intptr_t len = Utils::VSNPrint(NULL, 0, format, measure_args);
va_end(measure_args);
if (remaining_ < len + 100) {
*buffer_++ = '.';
*buffer_++ = '.';
*buffer_++ = '.';
*buffer_++ = '\n';
*buffer_++ = '\0';
overflowed_ = true;
return;
}
va_list print_args;
va_start(print_args, format);
intptr_t len2 = Utils::VSNPrint(buffer_, len, format, print_args);
va_end(print_args);
ASSERT(len == len2);
buffer_ += len;
remaining_ -= len;
*buffer_++ = '\n';
remaining_--;
*buffer_ = '\0';
}
void Disassembler::Disassemble(uword start,
uword end,
DisassemblyFormatter* formatter,
const Code& code,
const CodeComments* comments) {
if (comments == nullptr) {
comments = code.IsNull() ? &Code::Comments::New(0) : &code.comments();
}
ASSERT(formatter != NULL);
char hex_buffer[kHexadecimalBufferSize]; // Instruction in hexadecimal form.
char human_buffer[kUserReadableBufferSize]; // Human-readable instruction.
uword pc = start;
intptr_t comment_finger = 0;
GrowableArray<const Function*> inlined_functions;
GrowableArray<TokenPosition> token_positions;
while (pc < end) {
const intptr_t offset = pc - start;
const intptr_t old_comment_finger = comment_finger;
while (comment_finger < comments->Length() &&
comments->PCOffsetAt(comment_finger) <= offset) {
formatter->Print(" ;; %s\n", comments->CommentAt(comment_finger));
comment_finger++;
}
if (old_comment_finger != comment_finger && !code.IsNull()) {
char str[4000];
BufferFormatter f(str, sizeof(str));
// Comment emitted, emit inlining information.
code.GetInlinedFunctionsAtInstruction(offset, &inlined_functions,
&token_positions);
// Skip top scope function printing (last entry in 'inlined_functions').
bool first = true;
for (intptr_t i = 1; i < inlined_functions.length(); i++) {
const char* name = inlined_functions[i]->ToQualifiedCString();
if (first) {
f.Printf(" ;; Inlined [%s", name);
first = false;
} else {
f.Printf(" -> %s", name);
}
}
if (!first) {
f.AddString("]\n");
formatter->Print("%s", str);
}
}
int instruction_length;
Object* object;
DecodeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer,
sizeof(human_buffer), &instruction_length, code, &object,
pc);
formatter->ConsumeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer,
sizeof(human_buffer), object,
FLAG_disassemble_relative ? offset : pc);
pc += instruction_length;
}
}
void Disassembler::DisassembleCodeHelper(const char* function_fullname,
const char* function_info,
const Code& code,
bool optimized) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
LocalVarDescriptors& var_descriptors = LocalVarDescriptors::Handle(zone);
if (FLAG_print_variable_descriptors) {
var_descriptors = code.GetLocalVarDescriptors();
}
THR_Print("Code for %sfunction '%s' (%s) {\n", optimized ? "optimized " : "",
function_fullname, function_info);
code.Disassemble();
THR_Print("}\n");
#if defined(TARGET_ARCH_IA32)
THR_Print("Pointer offsets for function: {\n");
// Pointer offsets are stored in descending order.
Object& obj = Object::Handle(zone);
for (intptr_t i = code.pointer_offsets_length() - 1; i >= 0; i--) {
const uword addr = code.GetPointerOffsetAt(i) + code.PayloadStart();
obj = LoadUnaligned(reinterpret_cast<ObjectPtr*>(addr));
THR_Print(" %d : %#" Px " '%s'\n", code.GetPointerOffsetAt(i), addr,
obj.ToCString());
}
THR_Print("}\n");
#else
ASSERT(code.pointer_offsets_length() == 0);
#endif
if (FLAG_precompiled_mode) {
THR_Print("(No object pool for bare instructions.)\n");
} else {
const ObjectPool& object_pool =
ObjectPool::Handle(zone, code.GetObjectPool());
if (!object_pool.IsNull()) {
object_pool.DebugPrint();
}
}
code.DumpSourcePositions(/*relative_addresses=*/FLAG_disassemble_relative);
THR_Print("PC Descriptors for function '%s' {\n", function_fullname);
PcDescriptors::PrintHeaderString();
const PcDescriptors& descriptors =
PcDescriptors::Handle(zone, code.pc_descriptors());
THR_Print("%s}\n", descriptors.ToCString());
const uword start = code.PayloadStart();
const uword base = FLAG_disassemble_relative ? 0 : start;
#if !defined(DART_PRECOMPILED_RUNTIME)
const Array& deopt_table = Array::Handle(zone, code.deopt_info_array());
if (!deopt_table.IsNull()) {
intptr_t deopt_table_length = DeoptTable::GetLength(deopt_table);
if (deopt_table_length > 0) {
THR_Print("DeoptInfo: {\n");
Smi& offset = Smi::Handle(zone);
TypedData& info = TypedData::Handle(zone);
Smi& reason_and_flags = Smi::Handle(zone);
for (intptr_t i = 0; i < deopt_table_length; ++i) {
DeoptTable::GetEntry(deopt_table, i, &offset, &info, &reason_and_flags);
const intptr_t reason =
DeoptTable::ReasonField::decode(reason_and_flags.Value());
ASSERT((0 <= reason) && (reason < ICData::kDeoptNumReasons));
THR_Print(
"%4" Pd ": 0x%" Px " %s (%s)\n", i, base + offset.Value(),
DeoptInfo::ToCString(deopt_table, info),
DeoptReasonToCString(static_cast<ICData::DeoptReasonId>(reason)));
}
THR_Print("}\n");
}
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
{
const auto& stackmaps =
CompressedStackMaps::Handle(zone, code.compressed_stackmaps());
TextBuffer buffer(100);
buffer.Printf("StackMaps for function '%s' {\n", function_fullname);
stackmaps.WriteToBuffer(&buffer, "\n");
buffer.AddString("}\n");
THR_Print("%s", buffer.buffer());
}
if (FLAG_print_variable_descriptors) {
THR_Print("Variable Descriptors for function '%s' {\n", function_fullname);
intptr_t var_desc_length =
var_descriptors.IsNull() ? 0 : var_descriptors.Length();
String& var_name = String::Handle(zone);
for (intptr_t i = 0; i < var_desc_length; i++) {
var_name = var_descriptors.GetName(i);
UntaggedLocalVarDescriptors::VarInfo var_info;
var_descriptors.GetInfo(i, &var_info);
const int8_t kind = var_info.kind();
if (kind == UntaggedLocalVarDescriptors::kSavedCurrentContext) {
THR_Print(" saved current CTX reg offset %d\n", var_info.index());
} else {
if (kind == UntaggedLocalVarDescriptors::kContextLevel) {
THR_Print(" context level %d scope %d", var_info.index(),
var_info.scope_id);
} else if (kind == UntaggedLocalVarDescriptors::kStackVar) {
THR_Print(" stack var '%s' offset %d", var_name.ToCString(),
var_info.index());
} else {
ASSERT(kind == UntaggedLocalVarDescriptors::kContextVar);
THR_Print(" context var '%s' level %d offset %d",
var_name.ToCString(), var_info.scope_id, var_info.index());
}
THR_Print(" (valid %s-%s)\n", var_info.begin_pos.ToCString(),
var_info.end_pos.ToCString());
}
}
THR_Print("}\n");
}
THR_Print("Exception Handlers for function '%s' {\n", function_fullname);
const ExceptionHandlers& handlers =
ExceptionHandlers::Handle(zone, code.exception_handlers());
THR_Print("%s}\n", handlers.ToCString());
#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
if (FLAG_precompiled_mode &&
code.catch_entry_moves_maps() != Object::null()) {
THR_Print("Catch entry moves for function '%s' {\n", function_fullname);
CatchEntryMovesMapReader reader(
TypedData::Handle(code.catch_entry_moves_maps()));
reader.PrintEntries();
THR_Print("}\n");
}
#endif // defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
{
THR_Print("Entry points for function '%s' {\n", function_fullname);
THR_Print(" [code+0x%02" Px "] %" Px " kNormal\n",
Code::entry_point_offset(CodeEntryKind::kNormal) - kHeapObjectTag,
code.EntryPoint() - start + base);
THR_Print(
" [code+0x%02" Px "] %" Px " kMonomorphic\n",
Code::entry_point_offset(CodeEntryKind::kMonomorphic) - kHeapObjectTag,
code.MonomorphicEntryPoint() - start + base);
THR_Print(
" [code+0x%02" Px "] %" Px " kUnchecked\n",
Code::entry_point_offset(CodeEntryKind::kUnchecked) - kHeapObjectTag,
code.UncheckedEntryPoint() - start + base);
THR_Print(" [code+0x%02" Px "] %" Px " kMonomorphicUnchecked\n",
Code::entry_point_offset(CodeEntryKind::kMonomorphicUnchecked) -
kHeapObjectTag,
code.MonomorphicUncheckedEntryPoint() - start + base);
THR_Print("}\n");
}
#if defined(DART_PRECOMPILED_RUNTIME)
THR_Print("(Cannot show static call target functions in AOT runtime.)\n");
#else
{
THR_Print("Static call target functions {\n");
const auto& table = Array::Handle(zone, code.static_calls_target_table());
auto& cls = Class::Handle(zone);
auto& kind_type_and_offset = Smi::Handle(zone);
auto& function = Function::Handle(zone);
auto& object = Object::Handle(zone);
auto& code = Code::Handle(zone);
auto& dst_type = AbstractType::Handle(zone);
if (!table.IsNull()) {
StaticCallsTable static_calls(table);
for (auto& call : static_calls) {
kind_type_and_offset = call.Get<Code::kSCallTableKindAndOffset>();
function = call.Get<Code::kSCallTableFunctionTarget>();
object = call.Get<Code::kSCallTableCodeOrTypeTarget>();
dst_type = AbstractType::null();
if (object.IsAbstractType()) {
dst_type = AbstractType::Cast(object).ptr();
} else if (object.IsCode()) {
code = Code::Cast(object).ptr();
}
auto kind = Code::KindField::decode(kind_type_and_offset.Value());
auto offset = Code::OffsetField::decode(kind_type_and_offset.Value());
auto entry_point =
Code::EntryPointField::decode(kind_type_and_offset.Value());
const char* s_entry_point =
entry_point == Code::kUncheckedEntry ? " <unchecked-entry>" : "";
const char* skind = nullptr;
switch (kind) {
case Code::kPcRelativeCall:
skind = "pc-relative-call";
break;
case Code::kPcRelativeTTSCall:
skind = "pc-relative-tts-call";
break;
case Code::kPcRelativeTailCall:
skind = "pc-relative-tail-call";
break;
case Code::kCallViaCode:
skind = "call-via-code";
break;
default:
UNREACHABLE();
}
if (!dst_type.IsNull()) {
THR_Print(" 0x%" Px ": type testing stub %s, (%s)%s\n",
base + offset, dst_type.ToCString(), skind, s_entry_point);
} else if (function.IsNull()) {
cls ^= code.owner();
if (cls.IsNull()) {
THR_Print(
" 0x%" Px ": %s, (%s)%s\n", base + offset,
code.QualifiedName(NameFormattingParams(
Object::kScrubbedName, Object::NameDisambiguation::kYes)),
skind, s_entry_point);
} else {
THR_Print(" 0x%" Px ": allocation stub for %s, (%s)%s\n",
base + offset, cls.ToCString(), skind, s_entry_point);
}
} else {
THR_Print(" 0x%" Px ": %s, (%s)%s\n", base + offset,
function.ToFullyQualifiedCString(), skind, s_entry_point);
}
}
}
THR_Print("}\n");
}
#endif // defined(DART_PRECOMPILED_RUNTIME)
#if !defined(DART_PRECOMPILED_RUNTIME)
if (optimized && FLAG_trace_inlining_intervals) {
code.DumpInlineIntervals();
}
#endif
if (FLAG_trace_source_positions) {
code.DumpSourcePositions();
}
}
void Disassembler::DisassembleCode(const Function& function,
const Code& code,
bool optimized) {
if (code.IsUnknownDartCode()) {
return;
}
TextBuffer buffer(128);
const char* function_fullname = function.ToFullyQualifiedCString();
buffer.Printf("%s", Function::KindToCString(function.kind()));
if (function.HasSavedArgumentsDescriptor()) {
const auto& args_desc_array = Array::Handle(function.saved_args_desc());
const ArgumentsDescriptor args_desc(args_desc_array);
buffer.AddString(", ");
args_desc.PrintTo(&buffer);
}
LogBlock lb;
DisassembleCodeHelper(function_fullname, buffer.buffer(), code, optimized);
}
void Disassembler::DisassembleStub(const char* name, const Code& code) {
LogBlock lb;
THR_Print("Code for stub '%s': {\n", name);
DisassembleToStdout formatter;
code.Disassemble(&formatter);
THR_Print("}\n");
const ObjectPool& object_pool = ObjectPool::Handle(code.object_pool());
if (FLAG_precompiled_mode) {
THR_Print("(No object pool for bare instructions.)\n");
} else if (!object_pool.IsNull()) {
object_pool.DebugPrint();
}
}
#else // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
void Disassembler::DisassembleCode(const Function& function,
const Code& code,
bool optimized) {}
#endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
} // namespace dart