| 1 | // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| 2 | // for details. All rights reserved. Use of this source code is governed by a |
| 3 | // BSD-style license that can be found in the LICENSE file. |
| 4 | |
| 5 | #include "vm/compiler/assembler/disassembler.h" |
| 6 | |
| 7 | #include "platform/text_buffer.h" |
| 8 | #include "platform/unaligned.h" |
| 9 | #include "vm/code_patcher.h" |
| 10 | #include "vm/dart_entry.h" |
| 11 | #include "vm/deopt_instructions.h" |
| 12 | #include "vm/globals.h" |
| 13 | #include "vm/instructions.h" |
| 14 | #include "vm/json_stream.h" |
| 15 | #include "vm/log.h" |
| 16 | #include "vm/os.h" |
| 17 | |
| 18 | namespace dart { |
| 19 | |
| 20 | #if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) |
| 21 | |
| 22 | #if !defined(DART_PRECOMPILED_RUNTIME) |
| 23 | DECLARE_FLAG(bool, trace_inlining_intervals); |
| 24 | #endif |
| 25 | |
| 26 | DEFINE_FLAG(bool, trace_source_positions, false, "Source position diagnostics" ); |
| 27 | |
| 28 | void DisassembleToStdout::ConsumeInstruction(char* hex_buffer, |
| 29 | intptr_t hex_size, |
| 30 | char* human_buffer, |
| 31 | intptr_t human_size, |
| 32 | Object* object, |
| 33 | uword pc) { |
| 34 | static const int kHexColumnWidth = 23; |
| 35 | #if defined(TARGET_ARCH_IS_32_BIT) |
| 36 | THR_Print("0x%" Px32 " %s" , static_cast<uint32_t>(pc), hex_buffer); |
| 37 | #else |
| 38 | THR_Print("0x%" Px64 " %s" , static_cast<uint64_t>(pc), hex_buffer); |
| 39 | #endif |
| 40 | int hex_length = strlen(hex_buffer); |
| 41 | if (hex_length < kHexColumnWidth) { |
| 42 | for (int i = kHexColumnWidth - hex_length; i > 0; i--) { |
| 43 | THR_Print(" " ); |
| 44 | } |
| 45 | } |
| 46 | THR_Print("%s" , human_buffer); |
| 47 | if (object != NULL) { |
| 48 | THR_Print(" %s" , object->ToCString()); |
| 49 | } |
| 50 | THR_Print("\n" ); |
| 51 | } |
| 52 | |
| 53 | void DisassembleToStdout::Print(const char* format, ...) { |
| 54 | va_list args; |
| 55 | va_start(args, format); |
| 56 | THR_VPrint(format, args); |
| 57 | va_end(args); |
| 58 | } |
| 59 | |
| 60 | void DisassembleToJSONStream::ConsumeInstruction(char* hex_buffer, |
| 61 | intptr_t hex_size, |
| 62 | char* human_buffer, |
| 63 | intptr_t human_size, |
| 64 | Object* object, |
| 65 | uword pc) { |
| 66 | // Instructions are represented as four consecutive values in a JSON array. |
| 67 | // The first is the address of the instruction, the second is the hex string, |
| 68 | // of the code, and the third is a human readable string, and the fourth is |
| 69 | // the object loaded by the instruction. |
| 70 | jsarr_.AddValueF("%" Pp "" , pc); |
| 71 | jsarr_.AddValue(hex_buffer); |
| 72 | jsarr_.AddValue(human_buffer); |
| 73 | |
| 74 | if (object != NULL) { |
| 75 | jsarr_.AddValue(*object); |
| 76 | } else { |
| 77 | jsarr_.AddValueNull(); // Not a reference to null. |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | void DisassembleToJSONStream::Print(const char* format, ...) { |
| 82 | va_list args; |
| 83 | va_start(args, format); |
| 84 | intptr_t len = Utils::VSNPrint(NULL, 0, format, args); |
| 85 | va_end(args); |
| 86 | char* p = reinterpret_cast<char*>(malloc(len + 1)); |
| 87 | va_start(args, format); |
| 88 | intptr_t len2 = Utils::VSNPrint(p, len, format, args); |
| 89 | va_end(args); |
| 90 | ASSERT(len == len2); |
| 91 | for (intptr_t i = 0; i < len; i++) { |
| 92 | if (p[i] == '\n' || p[i] == '\r') { |
| 93 | p[i] = ' '; |
| 94 | } |
| 95 | } |
| 96 | // Instructions are represented as four consecutive values in a JSON array. |
| 97 | // Comments only use the third slot. See above comment for more information. |
| 98 | jsarr_.AddValueNull(); |
| 99 | jsarr_.AddValueNull(); |
| 100 | jsarr_.AddValue(p); |
| 101 | jsarr_.AddValueNull(); |
| 102 | free(p); |
| 103 | } |
| 104 | |
| 105 | void DisassembleToMemory::ConsumeInstruction(char* hex_buffer, |
| 106 | intptr_t hex_size, |
| 107 | char* human_buffer, |
| 108 | intptr_t human_size, |
| 109 | Object* object, |
| 110 | uword pc) { |
| 111 | if (overflowed_) { |
| 112 | return; |
| 113 | } |
| 114 | intptr_t len = strlen(human_buffer); |
| 115 | if (remaining_ < len + 100) { |
| 116 | *buffer_++ = '.'; |
| 117 | *buffer_++ = '.'; |
| 118 | *buffer_++ = '.'; |
| 119 | *buffer_++ = '\n'; |
| 120 | *buffer_++ = '\0'; |
| 121 | overflowed_ = true; |
| 122 | return; |
| 123 | } |
| 124 | memmove(buffer_, human_buffer, len); |
| 125 | buffer_ += len; |
| 126 | remaining_ -= len; |
| 127 | *buffer_++ = '\n'; |
| 128 | remaining_--; |
| 129 | *buffer_ = '\0'; |
| 130 | } |
| 131 | |
| 132 | void DisassembleToMemory::Print(const char* format, ...) { |
| 133 | if (overflowed_) { |
| 134 | return; |
| 135 | } |
| 136 | va_list args; |
| 137 | va_start(args, format); |
| 138 | intptr_t len = Utils::VSNPrint(NULL, 0, format, args); |
| 139 | va_end(args); |
| 140 | if (remaining_ < len + 100) { |
| 141 | *buffer_++ = '.'; |
| 142 | *buffer_++ = '.'; |
| 143 | *buffer_++ = '.'; |
| 144 | *buffer_++ = '\n'; |
| 145 | *buffer_++ = '\0'; |
| 146 | overflowed_ = true; |
| 147 | return; |
| 148 | } |
| 149 | va_start(args, format); |
| 150 | intptr_t len2 = Utils::VSNPrint(buffer_, len, format, args); |
| 151 | va_end(args); |
| 152 | ASSERT(len == len2); |
| 153 | buffer_ += len; |
| 154 | remaining_ -= len; |
| 155 | *buffer_++ = '\n'; |
| 156 | remaining_--; |
| 157 | *buffer_ = '\0'; |
| 158 | } |
| 159 | |
| 160 | void Disassembler::(uword start, |
| 161 | uword end, |
| 162 | DisassemblyFormatter* formatter, |
| 163 | const Code& code, |
| 164 | const Code::Comments* ) { |
| 165 | if (comments == nullptr) { |
| 166 | comments = code.IsNull() ? &Code::Comments::New(0) : &code.comments(); |
| 167 | } |
| 168 | ASSERT(formatter != NULL); |
| 169 | char hex_buffer[kHexadecimalBufferSize]; // Instruction in hexadecimal form. |
| 170 | char human_buffer[kUserReadableBufferSize]; // Human-readable instruction. |
| 171 | uword pc = start; |
| 172 | intptr_t = 0; |
| 173 | GrowableArray<const Function*> inlined_functions; |
| 174 | GrowableArray<TokenPosition> token_positions; |
| 175 | while (pc < end) { |
| 176 | const intptr_t offset = pc - start; |
| 177 | const intptr_t = comment_finger; |
| 178 | while (comment_finger < comments->Length() && |
| 179 | comments->PCOffsetAt(comment_finger) <= offset) { |
| 180 | formatter->Print( |
| 181 | " ;; %s\n" , |
| 182 | String::Handle(comments->CommentAt(comment_finger)).ToCString()); |
| 183 | comment_finger++; |
| 184 | } |
| 185 | if (old_comment_finger != comment_finger && !code.IsNull()) { |
| 186 | char str[4000]; |
| 187 | BufferFormatter f(str, sizeof(str)); |
| 188 | // Comment emitted, emit inlining information. |
| 189 | code.GetInlinedFunctionsAtInstruction(offset, &inlined_functions, |
| 190 | &token_positions); |
| 191 | // Skip top scope function printing (last entry in 'inlined_functions'). |
| 192 | bool first = true; |
| 193 | for (intptr_t i = 1; i < inlined_functions.length(); i++) { |
| 194 | const char* name = inlined_functions[i]->ToQualifiedCString(); |
| 195 | if (first) { |
| 196 | f.Printf(" ;; Inlined [%s" , name); |
| 197 | first = false; |
| 198 | } else { |
| 199 | f.Printf(" -> %s" , name); |
| 200 | } |
| 201 | } |
| 202 | if (!first) { |
| 203 | f.AddString("]\n" ); |
| 204 | formatter->Print("%s" , str); |
| 205 | } |
| 206 | } |
| 207 | int instruction_length; |
| 208 | Object* object; |
| 209 | DecodeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer, |
| 210 | sizeof(human_buffer), &instruction_length, code, &object, |
| 211 | pc); |
| 212 | formatter->ConsumeInstruction(hex_buffer, sizeof(hex_buffer), human_buffer, |
| 213 | sizeof(human_buffer), object, |
| 214 | FLAG_disassemble_relative ? offset : pc); |
| 215 | pc += instruction_length; |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | void Disassembler::DisassembleCodeHelper(const char* function_fullname, |
| 220 | const char* function_info, |
| 221 | const Code& code, |
| 222 | bool optimized) { |
| 223 | Zone* zone = Thread::Current()->zone(); |
| 224 | LocalVarDescriptors& var_descriptors = LocalVarDescriptors::Handle(zone); |
| 225 | if (FLAG_print_variable_descriptors) { |
| 226 | var_descriptors = code.GetLocalVarDescriptors(); |
| 227 | } |
| 228 | THR_Print("Code for %sfunction '%s' (%s) {\n" , optimized ? "optimized " : "" , |
| 229 | function_fullname, function_info); |
| 230 | code.Disassemble(); |
| 231 | THR_Print("}\n" ); |
| 232 | |
| 233 | #if defined(TARGET_ARCH_IA32) |
| 234 | THR_Print("Pointer offsets for function: {\n" ); |
| 235 | // Pointer offsets are stored in descending order. |
| 236 | Object& obj = Object::Handle(zone); |
| 237 | for (intptr_t i = code.pointer_offsets_length() - 1; i >= 0; i--) { |
| 238 | const uword addr = code.GetPointerOffsetAt(i) + code.PayloadStart(); |
| 239 | obj = LoadUnaligned(reinterpret_cast<ObjectPtr*>(addr)); |
| 240 | THR_Print(" %d : %#" Px " '%s'\n" , code.GetPointerOffsetAt(i), addr, |
| 241 | obj.ToCString()); |
| 242 | } |
| 243 | THR_Print("}\n" ); |
| 244 | #else |
| 245 | ASSERT(code.pointer_offsets_length() == 0); |
| 246 | #endif |
| 247 | |
| 248 | if (FLAG_precompiled_mode && FLAG_use_bare_instructions) { |
| 249 | THR_Print("(No object pool for bare instructions.)\n" ); |
| 250 | } else { |
| 251 | const ObjectPool& object_pool = |
| 252 | ObjectPool::Handle(zone, code.GetObjectPool()); |
| 253 | if (!object_pool.IsNull()) { |
| 254 | object_pool.DebugPrint(); |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | code.DumpSourcePositions(/*relative_addresses=*/FLAG_disassemble_relative); |
| 259 | |
| 260 | THR_Print("PC Descriptors for function '%s' {\n" , function_fullname); |
| 261 | PcDescriptors::PrintHeaderString(); |
| 262 | const PcDescriptors& descriptors = |
| 263 | PcDescriptors::Handle(zone, code.pc_descriptors()); |
| 264 | THR_Print("%s}\n" , descriptors.ToCString()); |
| 265 | |
| 266 | const uword start = code.PayloadStart(); |
| 267 | const uword base = FLAG_disassemble_relative ? 0 : start; |
| 268 | |
| 269 | #if !defined(DART_PRECOMPILED_RUNTIME) |
| 270 | const Array& deopt_table = Array::Handle(zone, code.deopt_info_array()); |
| 271 | if (!deopt_table.IsNull()) { |
| 272 | intptr_t deopt_table_length = DeoptTable::GetLength(deopt_table); |
| 273 | if (deopt_table_length > 0) { |
| 274 | THR_Print("DeoptInfo: {\n" ); |
| 275 | Smi& offset = Smi::Handle(zone); |
| 276 | TypedData& info = TypedData::Handle(zone); |
| 277 | Smi& reason_and_flags = Smi::Handle(zone); |
| 278 | for (intptr_t i = 0; i < deopt_table_length; ++i) { |
| 279 | DeoptTable::GetEntry(deopt_table, i, &offset, &info, &reason_and_flags); |
| 280 | const intptr_t reason = |
| 281 | DeoptTable::ReasonField::decode(reason_and_flags.Value()); |
| 282 | ASSERT((0 <= reason) && (reason < ICData::kDeoptNumReasons)); |
| 283 | THR_Print( |
| 284 | "%4" Pd ": 0x%" Px " %s (%s)\n" , i, base + offset.Value(), |
| 285 | DeoptInfo::ToCString(deopt_table, info), |
| 286 | DeoptReasonToCString(static_cast<ICData::DeoptReasonId>(reason))); |
| 287 | } |
| 288 | THR_Print("}\n" ); |
| 289 | } |
| 290 | } |
| 291 | #endif // !defined(DART_PRECOMPILED_RUNTIME) |
| 292 | |
| 293 | THR_Print("StackMaps for function '%s' {\n" , function_fullname); |
| 294 | if (code.compressed_stackmaps() != CompressedStackMaps::null()) { |
| 295 | const auto& stackmaps = |
| 296 | CompressedStackMaps::Handle(zone, code.compressed_stackmaps()); |
| 297 | THR_Print("%s\n" , stackmaps.ToCString()); |
| 298 | } |
| 299 | THR_Print("}\n" ); |
| 300 | |
| 301 | if (FLAG_print_variable_descriptors) { |
| 302 | THR_Print("Variable Descriptors for function '%s' {\n" , function_fullname); |
| 303 | intptr_t var_desc_length = |
| 304 | var_descriptors.IsNull() ? 0 : var_descriptors.Length(); |
| 305 | String& var_name = String::Handle(zone); |
| 306 | for (intptr_t i = 0; i < var_desc_length; i++) { |
| 307 | var_name = var_descriptors.GetName(i); |
| 308 | LocalVarDescriptorsLayout::VarInfo var_info; |
| 309 | var_descriptors.GetInfo(i, &var_info); |
| 310 | const int8_t kind = var_info.kind(); |
| 311 | if (kind == LocalVarDescriptorsLayout::kSavedCurrentContext) { |
| 312 | THR_Print(" saved current CTX reg offset %d\n" , var_info.index()); |
| 313 | } else { |
| 314 | if (kind == LocalVarDescriptorsLayout::kContextLevel) { |
| 315 | THR_Print(" context level %d scope %d" , var_info.index(), |
| 316 | var_info.scope_id); |
| 317 | } else if (kind == LocalVarDescriptorsLayout::kStackVar) { |
| 318 | THR_Print(" stack var '%s' offset %d" , var_name.ToCString(), |
| 319 | var_info.index()); |
| 320 | } else { |
| 321 | ASSERT(kind == LocalVarDescriptorsLayout::kContextVar); |
| 322 | THR_Print(" context var '%s' level %d offset %d" , |
| 323 | var_name.ToCString(), var_info.scope_id, var_info.index()); |
| 324 | } |
| 325 | THR_Print(" (valid %s-%s)\n" , var_info.begin_pos.ToCString(), |
| 326 | var_info.end_pos.ToCString()); |
| 327 | } |
| 328 | } |
| 329 | THR_Print("}\n" ); |
| 330 | } |
| 331 | |
| 332 | THR_Print("Exception Handlers for function '%s' {\n" , function_fullname); |
| 333 | const ExceptionHandlers& handlers = |
| 334 | ExceptionHandlers::Handle(zone, code.exception_handlers()); |
| 335 | THR_Print("%s}\n" , handlers.ToCString()); |
| 336 | |
| 337 | #if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER) |
| 338 | if (code.catch_entry_moves_maps() != Object::null()) { |
| 339 | THR_Print("Catch entry moves for function '%s' {\n" , function_fullname); |
| 340 | CatchEntryMovesMapReader reader( |
| 341 | TypedData::Handle(code.catch_entry_moves_maps())); |
| 342 | reader.PrintEntries(); |
| 343 | THR_Print("}\n" ); |
| 344 | } |
| 345 | #endif // defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER) |
| 346 | |
| 347 | { |
| 348 | THR_Print("Entry points for function '%s' {\n" , function_fullname); |
| 349 | THR_Print(" [code+0x%02" Px "] %" Px " kNormal\n" , |
| 350 | Code::entry_point_offset(CodeEntryKind::kNormal) - kHeapObjectTag, |
| 351 | code.EntryPoint() - start + base); |
| 352 | THR_Print( |
| 353 | " [code+0x%02" Px "] %" Px " kMonomorphic\n" , |
| 354 | Code::entry_point_offset(CodeEntryKind::kMonomorphic) - kHeapObjectTag, |
| 355 | code.MonomorphicEntryPoint() - start + base); |
| 356 | THR_Print( |
| 357 | " [code+0x%02" Px "] %" Px " kUnchecked\n" , |
| 358 | Code::entry_point_offset(CodeEntryKind::kUnchecked) - kHeapObjectTag, |
| 359 | code.UncheckedEntryPoint() - start + base); |
| 360 | THR_Print(" [code+0x%02" Px "] %" Px " kMonomorphicUnchecked\n" , |
| 361 | Code::entry_point_offset(CodeEntryKind::kMonomorphicUnchecked) - |
| 362 | kHeapObjectTag, |
| 363 | code.MonomorphicUncheckedEntryPoint() - start + base); |
| 364 | THR_Print("}\n" ); |
| 365 | } |
| 366 | |
| 367 | #if defined(DART_PRECOMPILED_RUNTIME) |
| 368 | THR_Print("(Cannot show static call target functions in AOT runtime.)\n" ); |
| 369 | #else |
| 370 | { |
| 371 | THR_Print("Static call target functions {\n" ); |
| 372 | const auto& table = Array::Handle(zone, code.static_calls_target_table()); |
| 373 | auto& cls = Class::Handle(zone); |
| 374 | auto& kind_type_and_offset = Smi::Handle(zone); |
| 375 | auto& function = Function::Handle(zone); |
| 376 | auto& object = Object::Handle(zone); |
| 377 | auto& code = Code::Handle(zone); |
| 378 | auto& dst_type = AbstractType::Handle(zone); |
| 379 | if (!table.IsNull()) { |
| 380 | StaticCallsTable static_calls(table); |
| 381 | for (auto& call : static_calls) { |
| 382 | kind_type_and_offset = call.Get<Code::kSCallTableKindAndOffset>(); |
| 383 | function = call.Get<Code::kSCallTableFunctionTarget>(); |
| 384 | object = call.Get<Code::kSCallTableCodeOrTypeTarget>(); |
| 385 | |
| 386 | dst_type = AbstractType::null(); |
| 387 | if (object.IsAbstractType()) { |
| 388 | dst_type = AbstractType::Cast(object).raw(); |
| 389 | } else if (object.IsCode()) { |
| 390 | code = Code::Cast(object).raw(); |
| 391 | } |
| 392 | |
| 393 | auto kind = Code::KindField::decode(kind_type_and_offset.Value()); |
| 394 | auto offset = Code::OffsetField::decode(kind_type_and_offset.Value()); |
| 395 | auto entry_point = |
| 396 | Code::EntryPointField::decode(kind_type_and_offset.Value()); |
| 397 | |
| 398 | const char* s_entry_point = |
| 399 | entry_point == Code::kUncheckedEntry ? " <unchecked-entry>" : "" ; |
| 400 | const char* skind = nullptr; |
| 401 | switch (kind) { |
| 402 | case Code::kPcRelativeCall: |
| 403 | skind = "pc-relative-call" ; |
| 404 | break; |
| 405 | case Code::kPcRelativeTTSCall: |
| 406 | skind = "pc-relative-tts-call" ; |
| 407 | break; |
| 408 | case Code::kPcRelativeTailCall: |
| 409 | skind = "pc-relative-tail-call" ; |
| 410 | break; |
| 411 | case Code::kCallViaCode: |
| 412 | skind = "call-via-code" ; |
| 413 | break; |
| 414 | default: |
| 415 | UNREACHABLE(); |
| 416 | } |
| 417 | if (!dst_type.IsNull()) { |
| 418 | THR_Print(" 0x%" Px ": type testing stub %s, (%s)%s\n" , |
| 419 | base + offset, dst_type.ToCString(), skind, s_entry_point); |
| 420 | } else if (function.IsNull()) { |
| 421 | cls ^= code.owner(); |
| 422 | if (cls.IsNull()) { |
| 423 | THR_Print( |
| 424 | " 0x%" Px ": %s, (%s)%s\n" , base + offset, |
| 425 | code.QualifiedName(NameFormattingParams( |
| 426 | Object::kScrubbedName, Object::NameDisambiguation::kYes)), |
| 427 | skind, s_entry_point); |
| 428 | } else { |
| 429 | THR_Print(" 0x%" Px ": allocation stub for %s, (%s)%s\n" , |
| 430 | base + offset, cls.ToCString(), skind, s_entry_point); |
| 431 | } |
| 432 | } else { |
| 433 | THR_Print(" 0x%" Px ": %s, (%s)%s\n" , base + offset, |
| 434 | function.ToFullyQualifiedCString(), skind, s_entry_point); |
| 435 | } |
| 436 | } |
| 437 | } |
| 438 | THR_Print("}\n" ); |
| 439 | } |
| 440 | #endif // defined(DART_PRECOMPILED_RUNTIME) |
| 441 | |
| 442 | #if !defined(DART_PRECOMPILED_RUNTIME) |
| 443 | if (optimized && FLAG_trace_inlining_intervals) { |
| 444 | code.DumpInlineIntervals(); |
| 445 | } |
| 446 | #endif |
| 447 | |
| 448 | if (FLAG_trace_source_positions) { |
| 449 | code.DumpSourcePositions(); |
| 450 | } |
| 451 | } |
| 452 | |
| 453 | void Disassembler::DisassembleCode(const Function& function, |
| 454 | const Code& code, |
| 455 | bool optimized) { |
| 456 | TextBuffer buffer(128); |
| 457 | const char* function_fullname = function.ToFullyQualifiedCString(); |
| 458 | buffer.Printf("%s" , Function::KindToCString(function.kind())); |
| 459 | if (function.IsInvokeFieldDispatcher() || |
| 460 | function.IsNoSuchMethodDispatcher()) { |
| 461 | const auto& args_desc_array = Array::Handle(function.saved_args_desc()); |
| 462 | const ArgumentsDescriptor args_desc(args_desc_array); |
| 463 | buffer.AddString(", " ); |
| 464 | args_desc.PrintTo(&buffer); |
| 465 | } |
| 466 | LogBlock lb; |
| 467 | DisassembleCodeHelper(function_fullname, buffer.buffer(), code, optimized); |
| 468 | } |
| 469 | |
| 470 | void Disassembler::DisassembleStub(const char* name, const Code& code) { |
| 471 | LogBlock lb; |
| 472 | THR_Print("Code for stub '%s': {\n" , name); |
| 473 | DisassembleToStdout formatter; |
| 474 | code.Disassemble(&formatter); |
| 475 | THR_Print("}\n" ); |
| 476 | const ObjectPool& object_pool = ObjectPool::Handle(code.object_pool()); |
| 477 | if (FLAG_precompiled_mode && FLAG_use_bare_instructions) { |
| 478 | THR_Print("(No object pool for bare instructions.)\n" ); |
| 479 | } else if (!object_pool.IsNull()) { |
| 480 | object_pool.DebugPrint(); |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | #else // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) |
| 485 | |
| 486 | void Disassembler::DisassembleCode(const Function& function, |
| 487 | const Code& code, |
| 488 | bool optimized) {} |
| 489 | #endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) |
| 490 | |
| 491 | } // namespace dart |
| 492 | |