[vm] Add --disassemble-relative to use PC offsets instead of absolute PCs in --disassemble's output.

Makes it much easier to compare the output when making VM changes.

--disassemble:
        ;; Invocation Count Check
0x7f82b8900320    498b7c2437             movq rdi,[r12+0x37]
0x7f82b8900325    ff8783000000           incl [rdi+0x83]
0x7f82b890032b    81bf8300000030750000   cmpl [rdi+0x83],0x7530
0x7f82b8900335    7c07                   jl 0x00007f82b890033e
0x7f82b8900337    41ffa6b8010000         jmp [thr+0x1b8]

--disassemble --disassemble-relative:
        ;; Invocation Count Check
0x0    498b7c2437             movq rdi,[r12+0x37]
0x5    ff8783000000           incl [rdi+0x83]
0xb    81bf8300000030750000   cmpl [rdi+0x83],0x7530
0x15    7c07                   jl +9
0x17    41ffa6b8010000         jmp [thr+0x1b8]

Change-Id: Ie05c532f830027b928d7d1d57509eb237157a127
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106600
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Régis Crelier <regis@google.com>
Reviewed-by: Aart Bik <ajcbik@google.com>
This commit is contained in:
Ryan Macnak
2019-06-19 22:36:00 +00:00
committed by commit-bot@chromium.org
parent a468780d96
commit 240f25a940
8 changed files with 200 additions and 65 deletions
@@ -0,0 +1,57 @@
// Copyright (c) 2019, 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.
// Verify running with --disassemble --disassemble-relative produces
// deterministic output. This is useful for removing noise when tracking down
// the effects of compiler changes.
import 'dart:async';
import 'dart:io';
import 'snapshot_test_helper.dart';
import 'package:expect/expect.dart';
int fib(int n) {
if (n <= 1) return 1;
return fib(n - 1) + fib(n - 2);
}
Future<void> main(List<String> args) async {
if (args.contains('--child')) {
print(fib(35));
return;
}
if (!Platform.script.toString().endsWith(".dart")) {
return; // Not running from source: skip for app-jit and app-aot.
}
if (Platform.executable.contains("Product")) {
return; // No disassembler in product mode.
}
if (Platform.executable.contains("IA32")) {
return; // Our IA32 code is not position independent.
}
final result1 = await runDart('GENERATE DISASSEMBLY 1', [
'--deterministic',
'--disassemble',
'--disassemble-relative',
Platform.script.toFilePath(),
'--child'
]);
final asm1 = result1.processResult.stderr;
final result2 = await runDart('GENERATE DISASSEMBLY 2', [
'--deterministic',
'--disassemble',
'--disassemble-relative',
Platform.script.toFilePath(),
'--child'
]);
final asm2 = result2.processResult.stderr;
Expect.isTrue(
asm1.contains("Code for function"), "Printed at least one function");
Expect.stringEquals(asm1, asm2);
}
@@ -0,0 +1,51 @@
// Copyright (c) 2019, 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.
// Verify running with --print-flow-graph produces deterministic output. This is
// useful for removing noise when tracking down the effects of compiler changes.
import 'dart:async';
import 'dart:io';
import 'snapshot_test_helper.dart';
import 'package:expect/expect.dart';
int fib(int n) {
if (n <= 1) return 1;
return fib(n - 1) + fib(n - 2);
}
Future<void> main(List<String> args) async {
if (args.contains('--child')) {
print(fib(35));
return;
}
if (!Platform.script.toString().endsWith(".dart")) {
return; // Not running from source: skip for app-jit and app-aot.
}
if (Platform.executable.contains("Product")) {
return; // No flow graph printer in product mode.
}
final result1 = await runDart('GENERATE CFG 1', [
'--deterministic',
'--print-flow-graph',
Platform.script.toFilePath(),
'--child'
]);
final cfg1 = result1.processResult.stderr;
final result2 = await runDart('GENERATE CFG 2', [
'--deterministic',
'--print-flow-graph',
Platform.script.toFilePath(),
'--child'
]);
final cfg2 = result2.processResult.stderr;
Expect.isTrue(
cfg1.contains("*** BEGIN CFG"), "Printed at least one function");
Expect.stringEquals(cfg1, cfg2);
}
+23 -19
View File
@@ -28,8 +28,11 @@ void DisassembleToStdout::ConsumeInstruction(char* hex_buffer,
Object* object,
uword pc) {
static const int kHexColumnWidth = 23;
uint8_t* pc_ptr = reinterpret_cast<uint8_t*>(pc);
THR_Print("%p %s", pc_ptr, hex_buffer);
#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--) {
@@ -201,7 +204,8 @@ void Disassembler::Disassemble(uword start,
sizeof(human_buffer), &instruction_length, code, &object,
pc);
formatter->ConsumeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer,
sizeof(human_buffer), object, pc);
sizeof(human_buffer), object,
FLAG_disassemble_relative ? offset : pc);
pc += instruction_length;
}
}
@@ -248,6 +252,7 @@ void Disassembler::DisassembleCodeHelper(const char* function_fullname,
const auto& instructions = Instructions::Handle(code.instructions());
const uword start = instructions.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());
@@ -263,7 +268,7 @@ void Disassembler::DisassembleCodeHelper(const char* function_fullname,
DeoptTable::ReasonField::decode(reason_and_flags.Value());
ASSERT((0 <= reason) && (reason < ICData::kDeoptNumReasons));
THR_Print(
"%4" Pd ": 0x%" Px " %s (%s)\n", i, start + offset.Value(),
"%4" Pd ": 0x%" Px " %s (%s)\n", i, base + offset.Value(),
DeoptInfo::ToCString(deopt_table, info),
DeoptReasonToCString(static_cast<ICData::DeoptReasonId>(reason)));
}
@@ -322,19 +327,20 @@ void Disassembler::DisassembleCodeHelper(const char* function_fullname,
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,
Instructions::EntryPoint(instructions.raw()));
THR_Print(
" [code+0x%02" Px "] %" Px " kUnchecked\n",
Code::entry_point_offset(CodeEntryKind::kUnchecked) - kHeapObjectTag,
Instructions::UncheckedEntryPoint(instructions.raw()));
Instructions::EntryPoint(instructions.raw()) - start + base);
THR_Print(
" [code+0x%02" Px "] %" Px " kMonomorphic\n",
Code::entry_point_offset(CodeEntryKind::kMonomorphic) - kHeapObjectTag,
Instructions::MonomorphicEntryPoint(instructions.raw()));
Instructions::MonomorphicEntryPoint(instructions.raw()) - start + base);
THR_Print(
" [code+0x%02" Px "] %" Px " kUnchecked\n",
Code::entry_point_offset(CodeEntryKind::kUnchecked) - kHeapObjectTag,
Instructions::UncheckedEntryPoint(instructions.raw()) - start + base);
THR_Print(" [code+0x%02" Px "] %" Px " kMonomorphicUnchecked\n",
Code::entry_point_offset(CodeEntryKind::kMonomorphicUnchecked) -
kHeapObjectTag,
Instructions::MonomorphicUncheckedEntryPoint(instructions.raw()));
Instructions::MonomorphicUncheckedEntryPoint(instructions.raw()) -
start + base);
THR_Print("}\n");
}
@@ -376,17 +382,15 @@ void Disassembler::DisassembleCodeHelper(const char* function_fullname,
if (function.IsNull()) {
cls ^= code.owner();
if (cls.IsNull()) {
THR_Print(" 0x%" Px ": %s, %p (%s)%s\n", start + offset,
code.QualifiedName(), code.raw(), skind, s_entry_point);
THR_Print(" 0x%" Px ": %s, (%s)%s\n", base + offset,
code.QualifiedName(), skind, s_entry_point);
} else {
THR_Print(" 0x%" Px ": allocation stub for %s, %p (%s)%s\n",
start + offset, cls.ToCString(), code.raw(), skind,
s_entry_point);
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, %p (%s)%s\n", start + offset,
function.ToFullyQualifiedCString(), code.raw(), skind,
s_entry_point);
THR_Print(" 0x%" Px ": %s, (%s)%s\n", base + offset,
function.ToFullyQualifiedCString(), skind, s_entry_point);
}
}
}
@@ -403,11 +403,17 @@ int ARMDecoder::FormatOption(Instr* instr, const char* format) {
case 'd': {
if (format[1] == 'e') { // 'dest: branch destination
ASSERT(STRING_STARTS_WITH(format, "dest"));
int off = (instr->SImmed24Field() << 2) + 8;
uword destination = reinterpret_cast<uword>(instr) + off;
buffer_pos_ +=
Utils::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(), "%#" Px "", destination);
const int32_t off = (instr->SImmed24Field() << 2) + 8;
if (FLAG_disassemble_relative) {
buffer_pos_ +=
Utils::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(), "%+" Pd32 "", off);
} else {
uword destination = reinterpret_cast<uword>(instr) + off;
buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(), "%#" Px "",
destination);
}
return 4;
} else {
return FormatDRegister(instr, format);
@@ -395,29 +395,28 @@ int ARM64Decoder::FormatOption(Instr* instr, const char* format) {
}
}
case 'd': {
int64_t off;
if (format[4] == '2') {
ASSERT(STRING_STARTS_WITH(format, "dest26"));
int64_t off = instr->SImm26Field() << 2;
off = instr->SImm26Field() << 2;
} else {
if (format[5] == '4') {
ASSERT(STRING_STARTS_WITH(format, "dest14"));
off = instr->SImm14Field() << 2;
} else {
ASSERT(STRING_STARTS_WITH(format, "dest19"));
off = instr->SImm19Field() << 2;
}
}
if (FLAG_disassemble_relative) {
buffer_pos_ +=
Utils::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(), "%+" Pd64 "", off);
} else {
uword destination = reinterpret_cast<uword>(instr) + off;
buffer_pos_ +=
Utils::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(), "%#" Px "", destination);
} else {
if (format[5] == '4') {
ASSERT(STRING_STARTS_WITH(format, "dest14"));
int64_t off = instr->SImm14Field() << 2;
uword destination = reinterpret_cast<uword>(instr) + off;
buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(), "%#" Px "",
destination);
} else {
ASSERT(STRING_STARTS_WITH(format, "dest19"));
int64_t off = instr->SImm19Field() << 2;
uword destination = reinterpret_cast<uword>(instr) + off;
buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(), "%#" Px "",
destination);
}
}
return 6;
}
@@ -324,6 +324,7 @@ class DisassemblerX64 : public ValueObject {
}
void Print(const char* format, ...) PRINTF_ATTRIBUTE(2, 3);
void PrintJump(uint8_t* pc, int32_t disp);
void PrintAddress(uint8_t* addr);
int PrintOperands(const char* mnem, OperandType op_order, uint8_t* data);
@@ -772,6 +773,14 @@ int DisassemblerX64::PrintOperands(const char* mnem,
return advance;
}
void DisassemblerX64::PrintJump(uint8_t* pc, int32_t disp) {
if (FLAG_disassemble_relative) {
Print("%+d", disp);
} else {
PrintAddress(pc + disp);
}
}
void DisassemblerX64::PrintAddress(uint8_t* addr_byte_ptr) {
#if defined(TARGET_ARCH_X64)
Print("%#018" Px64 "", reinterpret_cast<uint64_t>(addr_byte_ptr));
@@ -790,9 +799,9 @@ void DisassemblerX64::PrintAddress(uint8_t* addr_byte_ptr) {
int DisassemblerX64::JumpShort(uint8_t* data) {
ASSERT(0xEB == *data);
uint8_t b = *(data + 1);
uint8_t* dest = data + static_cast<int8_t>(b) + 2;
int32_t disp = static_cast<int8_t>(b) + 2;
Print("jmp ");
PrintAddress(dest);
PrintJump(data, disp);
return 2;
}
@@ -800,10 +809,10 @@ int DisassemblerX64::JumpShort(uint8_t* data) {
int DisassemblerX64::JumpConditional(uint8_t* data) {
ASSERT(0x0F == *data);
uint8_t cond = *(data + 1) & 0x0F;
uint8_t* dest = data + *reinterpret_cast<int32_t*>(data + 2) + 6;
int32_t disp = *reinterpret_cast<int32_t*>(data + 2) + 6;
const char* mnem = conditional_code_suffix[cond];
Print("j%s ", mnem);
PrintAddress(dest);
PrintJump(data, disp);
return 6; // includes 0x0F
}
@@ -811,10 +820,10 @@ int DisassemblerX64::JumpConditional(uint8_t* data) {
int DisassemblerX64::JumpConditionalShort(uint8_t* data) {
uint8_t cond = *data & 0x0F;
uint8_t b = *(data + 1);
uint8_t* dest = data + static_cast<int8_t>(b) + 2;
int32_t disp = static_cast<int8_t>(b) + 2;
const char* mnem = conditional_code_suffix[cond];
Print("j%s ", mnem);
PrintAddress(dest);
PrintJump(data, disp);
return 2;
}
@@ -1190,9 +1199,9 @@ bool DisassemblerX64::DecodeInstructionType(uint8_t** data) {
}
case CALL_JUMP_INSTR: {
uint8_t* addr = *data + *reinterpret_cast<int32_t*>(*data + 1) + 5;
int32_t disp = *reinterpret_cast<int32_t*>(*data + 1) + 5;
Print("%s ", idesc.mnem);
PrintAddress(addr);
PrintJump(*data, disp);
(*data) += 5;
break;
}
+2
View File
@@ -79,6 +79,8 @@ constexpr bool kDartPrecompiledRuntime = false;
R(disable_alloc_stubs_after_gc, false, bool, false, "Stress testing flag.") \
R(disassemble, false, bool, false, "Disassemble dart code.") \
R(disassemble_optimized, false, bool, false, "Disassemble optimized code.") \
R(disassemble_relative, false, bool, false, \
"Use offsets instead of absolute PCs") \
R(dump_megamorphic_stats, false, bool, false, \
"Dump megamorphic cache statistics") \
R(dump_symbol_stats, false, bool, false, "Dump symbol table statistics") \
+23 -16
View File
@@ -5536,8 +5536,8 @@ const char* TypeArguments::ToCString() const {
return "TypeArguments: null";
}
Zone* zone = Thread::Current()->zone();
const char* prev_cstr = OS::SCreate(zone, "TypeArguments: (@%p H%" Px ")",
raw(), Smi::Value(raw_ptr()->hash_));
const char* prev_cstr = OS::SCreate(zone, "TypeArguments: (H%" Px ")",
Smi::Value(raw_ptr()->hash_));
for (int i = 0; i < Length(); i++) {
const AbstractType& type_at = AbstractType::Handle(zone, TypeAt(i));
const char* type_cstr = type_at.IsNull() ? "null" : type_at.ToCString();
@@ -12619,17 +12619,24 @@ const char* ObjectPool::ToCString() const {
}
void ObjectPool::DebugPrint() const {
THR_Print("Object Pool: 0x%" Px "{\n", reinterpret_cast<uword>(raw()));
THR_Print("ObjectPool len:%" Pd " {\n", Length());
for (intptr_t i = 0; i < Length(); i++) {
intptr_t offset = OffsetFromIndex(i);
THR_Print(" %" Pd " PP+0x%" Px ": ", i, offset);
THR_Print(" [pp+0x%" Px "] ", offset);
if ((TypeAt(i) == EntryType::kTaggedObject) ||
(TypeAt(i) == EntryType::kNativeEntryData)) {
RawObject* obj = ObjectAt(i);
THR_Print("0x%" Px " %s (obj)\n", reinterpret_cast<uword>(obj),
Object::Handle(obj).ToCString());
const Object& obj = Object::Handle(ObjectAt(i));
THR_Print("%s (obj)\n", obj.ToCString());
} else if (TypeAt(i) == EntryType::kNativeFunction) {
THR_Print("0x%" Px " (native function)\n", RawValueAt(i));
uword pc = RawValueAt(i);
uintptr_t start = 0;
char* name = NativeSymbolResolver::LookupSymbolName(pc, &start);
if (name != NULL) {
THR_Print("%s (native function)\n", name);
NativeSymbolResolver::FreeSymbolName(name);
} else {
THR_Print("0x%" Px " (native function)\n", pc);
}
} else if (TypeAt(i) == EntryType::kNativeFunctionWrapper) {
THR_Print("0x%" Px " (native function wrapper)\n", RawValueAt(i));
} else {
@@ -15451,7 +15458,7 @@ void Context::Dump(int indent) const {
}
IndentN(indent);
THR_Print("Context@%p vars(%" Pd ") {\n", this->raw(), num_variables());
THR_Print("Context vars(%" Pd ") {\n", num_variables());
Object& obj = Object::Handle();
for (intptr_t i = 0; i < num_variables(); i++) {
IndentN(indent + 2);
@@ -18061,8 +18068,8 @@ const char* Type::ToCString() const {
return OS::SCreate(zone, "Type: class '%s'", class_name);
} else if (IsFinalized() && IsRecursive()) {
const intptr_t hash = Hash();
return OS::SCreate(zone, "Type: (@%p H%" Px ") class '%s', args:[%s]",
raw(), hash, class_name, args_cstr);
return OS::SCreate(zone, "Type: (H%" Px ") class '%s', args:[%s]", hash,
class_name, args_cstr);
} else {
return OS::SCreate(zone, "Type: class '%s', args:[%s]", class_name,
args_cstr);
@@ -18196,17 +18203,17 @@ RawTypeRef* TypeRef::New(const AbstractType& type) {
}
const char* TypeRef::ToCString() const {
AbstractType& ref_type = AbstractType::Handle(type());
Zone* zone = Thread::Current()->zone();
AbstractType& ref_type = AbstractType::Handle(zone, type());
if (ref_type.IsNull()) {
return "TypeRef: null";
}
const char* type_cstr = String::Handle(ref_type.Name()).ToCString();
const char* type_cstr = String::Handle(zone, ref_type.Name()).ToCString();
if (ref_type.IsFinalized()) {
const intptr_t hash = ref_type.Hash();
return OS::SCreate(Thread::Current()->zone(), "TypeRef: %s (@%p H%" Px ")",
type_cstr, ref_type.raw(), hash);
return OS::SCreate(zone, "TypeRef: %s (H%" Px ")", type_cstr, hash);
} else {
return OS::SCreate(Thread::Current()->zone(), "TypeRef: %s", type_cstr);
return OS::SCreate(zone, "TypeRef: %s", type_cstr);
}
}