[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);
   }
 }