[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>
diff --git a/runtime/tests/vm/dart/disassemble_determinism_test.dart b/runtime/tests/vm/dart/disassemble_determinism_test.dart
new file mode 100644
index 0000000..1ff1091
--- /dev/null
+++ b/runtime/tests/vm/dart/disassemble_determinism_test.dart
@@ -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);
+}
diff --git a/runtime/tests/vm/dart/print_flow_graph_determinism_test.dart b/runtime/tests/vm/dart/print_flow_graph_determinism_test.dart
new file mode 100644
index 0000000..7b2eae5
--- /dev/null
+++ b/runtime/tests/vm/dart/print_flow_graph_determinism_test.dart
@@ -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);
+}
diff --git a/runtime/vm/compiler/assembler/disassembler.cc b/runtime/vm/compiler/assembler/disassembler.cc
index a2fe6a1..e39e721 100644
--- a/runtime/vm/compiler/assembler/disassembler.cc
+++ b/runtime/vm/compiler/assembler/disassembler.cc
@@ -28,8 +28,11 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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);
}
}
}
diff --git a/runtime/vm/compiler/assembler/disassembler_arm.cc b/runtime/vm/compiler/assembler/disassembler_arm.cc
index f5266ae..888f97b 100644
--- a/runtime/vm/compiler/assembler/disassembler_arm.cc
+++ b/runtime/vm/compiler/assembler/disassembler_arm.cc
@@ -403,11 +403,17 @@
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);
diff --git a/runtime/vm/compiler/assembler/disassembler_arm64.cc b/runtime/vm/compiler/assembler/disassembler_arm64.cc
index e074048..7ed5a44 100644
--- a/runtime/vm/compiler/assembler/disassembler_arm64.cc
+++ b/runtime/vm/compiler/assembler/disassembler_arm64.cc
@@ -395,29 +395,28 @@
}
}
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;
}
diff --git a/runtime/vm/compiler/assembler/disassembler_x86.cc b/runtime/vm/compiler/assembler/disassembler_x86.cc
index 756e5d4..7e4c016 100644
--- a/runtime/vm/compiler/assembler/disassembler_x86.cc
+++ b/runtime/vm/compiler/assembler/disassembler_x86.cc
@@ -324,6 +324,7 @@
}
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 @@
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 @@
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::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::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 @@
}
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;
}
diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h
index a406f01..2852a43 100644
--- a/runtime/vm/flag_list.h
+++ b/runtime/vm/flag_list.h
@@ -79,6 +79,8 @@
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") \
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 1ff3a45..fb5737d 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -5536,8 +5536,8 @@
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 @@
}
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 @@
}
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 @@
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 @@
}
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);
}
}