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
18namespace dart {
19
20#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
21
22#if !defined(DART_PRECOMPILED_RUNTIME)
23DECLARE_FLAG(bool, trace_inlining_intervals);
24#endif
25
26DEFINE_FLAG(bool, trace_source_positions, false, "Source position diagnostics");
27
28void 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
53void 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
60void 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
81void 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
105void 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
132void 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
160void Disassembler::Disassemble(uword start,
161 uword end,
162 DisassemblyFormatter* formatter,
163 const Code& code,
164 const Code::Comments* 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 comment_finger = 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 old_comment_finger = 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
219void 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
453void 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
470void 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
486void 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