[vm] Support disassembling snapshot code at runtime for JIT and AOT snapshots.

--code-comments should be passed both at compile/training-time and run-time to get
comments in the disassembled instructions.

The required --disassemble-* flags need only be passed at run-time. Flow-graph printing
is not yet supported because we don't save flow-graph like assembly comments.

This functionality is excluded from product builds.

Change-Id: If35fbb08cff6eae40069f916ecec9c6f589c0cad
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/124989
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Samir Jindel 2019-11-14 14:09:21 +00:00 committed by commit-bot@chromium.org
parent 3bd7ed38fb
commit dd7760a07d
11 changed files with 114 additions and 74 deletions

View file

@ -9,9 +9,12 @@
#include "vm/bss_relocs.h"
#include "vm/class_id.h"
#include "vm/code_observers.h"
#include "vm/compiler/assembler/disassembler.h"
#include "vm/compiler/backend/code_statistics.h"
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/relocation.h"
#include "vm/dart.h"
#include "vm/flag_list.h"
#include "vm/heap/heap.h"
#include "vm/image_snapshot.h"
#include "vm/native_entry.h"
@ -1387,7 +1390,12 @@ class CodeSerializationCluster : public SerializationCluster {
s->Push(code->ptr()->deopt_info_array_);
s->Push(code->ptr()->static_calls_target_table_);
}
NOT_IN_PRODUCT(s->Push(code->ptr()->return_address_metadata_));
#if !defined(PRODUCT)
s->Push(code->ptr()->return_address_metadata_);
if (FLAG_code_comments) {
s->Push(code->ptr()->comments_);
}
#endif
}
void WriteAlloc(Serializer* s) {
@ -1448,8 +1456,12 @@ class CodeSerializationCluster : public SerializationCluster {
WriteField(code, deopt_info_array_);
WriteField(code, static_calls_target_table_);
}
NOT_IN_PRODUCT(WriteField(code, return_address_metadata_));
#if !defined(PRODUCT)
WriteField(code, return_address_metadata_);
if (FLAG_code_comments) {
WriteField(code, comments_);
}
#endif
s->Write<int32_t>(code->ptr()->state_bits_);
}
}
@ -1565,7 +1577,9 @@ class CodeDeserializationCluster : public DeserializationCluster {
#if !defined(PRODUCT)
code->ptr()->return_address_metadata_ = d->ReadRef();
code->ptr()->var_descriptors_ = LocalVarDescriptors::null();
code->ptr()->comments_ = Array::null();
code->ptr()->comments_ = FLAG_code_comments
? reinterpret_cast<RawArray*>(d->ReadRef())
: Array::null();
code->ptr()->compile_timestamp_ = 0;
#endif
@ -1573,13 +1587,29 @@ class CodeDeserializationCluster : public DeserializationCluster {
}
}
#if !(defined(DART_PRECOMPILED_RUNTIME) || defined(PRODUCT))
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
void PostLoad(const Array& refs, Snapshot::Kind kind, Zone* zone) {
if (!CodeObservers::AreActive()) return;
if (!CodeObservers::AreActive() && !FLAG_support_disassembler) return;
Code& code = Code::Handle(zone);
Object& owner = Object::Handle(zone);
for (intptr_t id = start_index_; id < stop_index_; id++) {
code ^= refs.At(id);
Code::NotifyCodeObservers(code, code.is_optimized());
#if !defined(DART_PRECOMPILED_RUNTIME)
if (CodeObservers::AreActive()) {
Code::NotifyCodeObservers(code, code.is_optimized());
}
#endif
owner = code.owner();
if (owner.IsFunction()) {
if ((FLAG_disassemble ||
(code.is_optimized() && FLAG_disassemble_optimized)) &&
FlowGraphPrinter::ShouldPrint(Function::Cast(owner))) {
Disassembler::DisassembleCode(Function::Cast(owner), code,
code.is_optimized());
}
} else if (FLAG_disassemble_stubs) {
Disassembler::DisassembleStub(code.Name(), code);
}
}
}
#endif // !DART_PRECOMPILED_RUNTIME

View file

@ -20,10 +20,6 @@ DEFINE_FLAG(bool,
false,
"Verify instructions offset in code object."
"NOTE: This breaks the profiler.");
DEFINE_FLAG(bool,
code_comments,
false,
"Include comments into code and disassembly");
#if defined(TARGET_ARCH_ARM)
DEFINE_FLAG(bool, use_far_branches, false, "Enable far branches for ARM.");
#endif

View file

@ -240,10 +240,14 @@ void Disassembler::DisassembleCodeHelper(const char* function_fullname,
ASSERT(code.pointer_offsets_length() == 0);
#endif
const ObjectPool& object_pool =
ObjectPool::Handle(zone, code.GetObjectPool());
if (!object_pool.IsNull()) {
object_pool.DebugPrint();
if (FLAG_use_bare_instructions) {
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();
}
}
THR_Print("PC Descriptors for function '%s' {\n", function_fullname);
@ -258,23 +262,25 @@ void Disassembler::DisassembleCodeHelper(const char* function_fullname,
#if !defined(DART_PRECOMPILED_RUNTIME)
const Array& deopt_table = Array::Handle(zone, code.deopt_info_array());
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)));
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");
}
THR_Print("}\n");
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
@ -343,6 +349,9 @@ void Disassembler::DisassembleCodeHelper(const char* function_fullname,
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());
@ -393,8 +402,9 @@ void Disassembler::DisassembleCodeHelper(const char* function_fullname,
}
}
}
THR_Print("}\n");
}
THR_Print("}\n");
#endif // defined(DART_PRECOMPILED_RUNTIME)
if (optimized && FLAG_trace_inlining_intervals) {
code.DumpInlineIntervals();
@ -411,6 +421,20 @@ void Disassembler::DisassembleCode(const Function& function,
DisassembleCodeHelper(function_fullname, 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_use_bare_instructions) {
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,

View file

@ -184,6 +184,8 @@ class Disassembler : public AllStatic {
const Code& code,
bool optimized);
static void DisassembleStub(const char* name, const Code& code);
private:
static void DisassembleCodeHelper(const char* function_fullname,
const Code& code,

View file

@ -60,7 +60,6 @@ DEFINE_FLAG(int,
"The scale of invocation count, by size of the function.");
DEFINE_FLAG(bool, source_lines, false, "Emit source line as assembly comment.");
DECLARE_FLAG(bool, code_comments);
DECLARE_FLAG(charp, deoptimize_filter);
DECLARE_FLAG(bool, intrinsify);
DECLARE_FLAG(int, regexp_optimization_counter_threshold);

View file

@ -2,8 +2,6 @@
// 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.
#if !defined(DART_PRECOMPILED_RUNTIME)
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/backend/il.h"
@ -15,18 +13,11 @@ namespace dart {
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
DEFINE_FLAG(bool,
display_sorted_ic_data,
false,
"Calls display a unary, sorted-by count form of ICData");
DEFINE_FLAG(bool, print_environments, false, "Print SSA environments.");
DEFINE_FLAG(charp,
print_flow_graph_filter,
NULL,
"Print only IR of functions with matching names");
DECLARE_FLAG(bool, trace_inlining_intervals);
// Checks whether function's name matches the given filter, which is
// a comma-separated list of strings.
bool FlowGraphPrinter::PassesFilter(const char* filter,
@ -74,6 +65,16 @@ bool FlowGraphPrinter::ShouldPrint(const Function& function) {
return PassesFilter(FLAG_print_flow_graph_filter, function);
}
#if !defined(DART_PRECOMPILED_RUNTIME)
DEFINE_FLAG(bool,
display_sorted_ic_data,
false,
"Calls display a unary, sorted-by count form of ICData");
DEFINE_FLAG(bool, print_environments, false, "Print SSA environments.");
DECLARE_FLAG(bool, trace_inlining_intervals);
void FlowGraphPrinter::PrintGraph(const char* phase, FlowGraph* flow_graph) {
LogBlock lb;
THR_Print("*** BEGIN CFG\n%s\n", phase);
@ -1181,8 +1182,12 @@ const char* Environment::ToCString() const {
return Thread::Current()->zone()->MakeCopyOfString(buffer);
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
#else // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
#if !defined(DART_PRECOMPILED_RUNTIME)
const char* Instruction::ToCString() const {
return DebugName();
}
@ -1219,8 +1224,8 @@ bool FlowGraphPrinter::ShouldPrint(const Function& function) {
return false;
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
#endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
} // namespace dart
#endif // !defined(DART_PRECOMPILED_RUNTIME)

View file

@ -17,10 +17,10 @@
#include "vm/compiler/compiler_pass.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/cpu.h"
#include "vm/flag_list.h"
namespace dart {
DECLARE_FLAG(bool, code_comments);
DECLARE_FLAG(bool, print_flow_graph);
DECLARE_FLAG(bool, print_flow_graph_optimized);
@ -299,8 +299,8 @@ static bool IntrinsifyArraySetIndexed(FlowGraph* flow_graph,
break;
case kTypedDataInt32ArrayCid:
case kExternalTypedDataInt32ArrayCid:
// Use same truncating unbox-instruction for int32 and uint32.
FALL_THROUGH;
// Use same truncating unbox-instruction for int32 and uint32.
FALL_THROUGH;
case kTypedDataUint32ArrayCid:
case kExternalTypedDataUint32ArrayCid:
// Supports smi and mint, slow-case for bigints.

View file

@ -851,6 +851,10 @@ const char* Dart::FeaturesString(Isolate* isolate,
buffer.AddString(FLAG_causal_async_stacks ? " causal_async_stacks"
: " no-causal_async_stacks");
#if !defined(PRODUCT)
buffer.AddString(FLAG_code_comments ? " code-comments"
: " no-code-comments");
#endif
// Generated code must match the host architecture and ABI.
#if defined(TARGET_ARCH_ARM)

View file

@ -71,6 +71,8 @@ constexpr bool kDartUseBytecode = false;
R(disassemble_optimized, false, bool, false, "Disassemble optimized code.") \
R(disassemble_relative, false, bool, false, \
"Use offsets instead of absolute PCs") \
R(code_comments, false, bool, false, \
"Include comments into code and disassembly.") \
R(dump_megamorphic_stats, false, bool, false, \
"Dump megamorphic cache statistics") \
R(dump_symbol_stats, false, bool, false, "Dump symbol table statistics") \

View file

@ -74,15 +74,7 @@ RawCode* StubCode::Generate(
/*optimized=*/false));
#ifndef PRODUCT
if (FLAG_support_disassembler && FLAG_disassemble_stubs) {
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 (!object_pool.IsNull()) {
object_pool.DebugPrint();
}
Disassembler::DisassembleStub(name, code);
}
#endif // !PRODUCT
return code.raw();
@ -199,15 +191,7 @@ RawCode* StubCode::GetAllocationStubForClass(const Class& cls) {
Code::NotifyCodeObservers(name, stub, /*optimized=*/false);
#ifndef PRODUCT
if (FLAG_support_disassembler && FLAG_disassemble_stubs) {
LogBlock lb;
THR_Print("Code for allocation stub '%s': {\n", name);
DisassembleToStdout formatter;
stub.Disassemble(&formatter);
THR_Print("}\n");
const ObjectPool& object_pool = ObjectPool::Handle(stub.object_pool());
if (!object_pool.IsNull()) {
object_pool.DebugPrint();
}
Disassembler::DisassembleStub(name, stub);
}
#endif // !PRODUCT
}
@ -245,15 +229,7 @@ RawCode* StubCode::GetBuildMethodExtractorStub(
#ifndef PRODUCT
if (FLAG_support_disassembler && FLAG_disassemble_stubs) {
LogBlock lb;
THR_Print("Code for isolate stub '%s': {\n", name);
DisassembleToStdout formatter;
stub.Disassemble(&formatter);
THR_Print("}\n");
const ObjectPool& object_pool = ObjectPool::Handle(stub.object_pool());
if (!object_pool.IsNull()) {
object_pool.DebugPrint();
}
Disassembler::DisassembleStub(name, stub);
}
#endif // !PRODUCT
return stub.raw();

View file

@ -22,6 +22,8 @@ class RawCode;
class SnapshotReader;
class SnapshotWriter;
DECLARE_FLAG(bool, disassemble_stubs);
// Is it permitted for the stubs above to refer to Object::null(), which is
// allocated in the VM isolate and shared across all isolates.
// However, in cases where a simple GC-safe placeholder is needed on the stack,