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 00000000000..1ff1091dd4c --- /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 main(List 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 00000000000..7b2eae52c88 --- /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 main(List 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 a2fe6a1e674..e39e721c5d2 100644 --- a/runtime/vm/compiler/assembler/disassembler.cc +++ b/runtime/vm/compiler/assembler/disassembler.cc @@ -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(pc); - THR_Print("%p %s", pc_ptr, hex_buffer); +#if defined(TARGET_ARCH_IS_32_BIT) + THR_Print("0x%" Px32 " %s", static_cast(pc), hex_buffer); +#else + THR_Print("0x%" Px64 " %s", static_cast(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(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); } } } diff --git a/runtime/vm/compiler/assembler/disassembler_arm.cc b/runtime/vm/compiler/assembler/disassembler_arm.cc index f5266aeb7e3..888f97b005a 100644 --- a/runtime/vm/compiler/assembler/disassembler_arm.cc +++ b/runtime/vm/compiler/assembler/disassembler_arm.cc @@ -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(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(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 e074048c6da..7ed5a443c15 100644 --- a/runtime/vm/compiler/assembler/disassembler_arm64.cc +++ b/runtime/vm/compiler/assembler/disassembler_arm64.cc @@ -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(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(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(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 756e5d47f39..7e4c0168e6a 100644 --- a/runtime/vm/compiler/assembler/disassembler_x86.cc +++ b/runtime/vm/compiler/assembler/disassembler_x86.cc @@ -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(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(b) + 2; + int32_t disp = static_cast(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(data + 2) + 6; + int32_t disp = *reinterpret_cast(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(b) + 2; + int32_t disp = static_cast(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(*data + 1) + 5; + int32_t disp = *reinterpret_cast(*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 a406f010f54..2852a43836d 100644 --- a/runtime/vm/flag_list.h +++ b/runtime/vm/flag_list.h @@ -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") \ diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 1ff3a454564..fb5737dd9d7 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -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(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(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); } }