1// Copyright (c) 2015, 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#include "vm/globals.h"
5#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
6#include "vm/source_report.h"
7
8#include "vm/bit_vector.h"
9#include "vm/compiler/jit/compiler.h"
10#include "vm/isolate.h"
11#include "vm/kernel_loader.h"
12#include "vm/object.h"
13#include "vm/object_store.h"
14#include "vm/profiler.h"
15#include "vm/profiler_service.h"
16
17namespace dart {
18
19const char* SourceReport::kCallSitesStr = "_CallSites";
20const char* SourceReport::kCoverageStr = "Coverage";
21const char* SourceReport::kPossibleBreakpointsStr = "PossibleBreakpoints";
22const char* SourceReport::kProfileStr = "_Profile";
23
24SourceReport::SourceReport(intptr_t report_set, CompileMode compile_mode)
25 : report_set_(report_set),
26 compile_mode_(compile_mode),
27 thread_(NULL),
28 script_(NULL),
29 start_pos_(TokenPosition::kNoSource),
30 end_pos_(TokenPosition::kNoSource),
31 profile_(Isolate::Current()),
32 next_script_index_(0) {}
33
34SourceReport::~SourceReport() {
35 ClearScriptTable();
36}
37
38void SourceReport::ClearScriptTable() {
39 for (intptr_t i = 0; i < script_table_entries_.length(); i++) {
40 delete script_table_entries_[i];
41 script_table_entries_[i] = NULL;
42 }
43 script_table_entries_.Clear();
44 script_table_.Clear();
45 next_script_index_ = 0;
46}
47
48void SourceReport::Init(Thread* thread,
49 const Script* script,
50 TokenPosition start_pos,
51 TokenPosition end_pos) {
52 thread_ = thread;
53 script_ = script;
54 start_pos_ = start_pos;
55 end_pos_ = end_pos;
56 ClearScriptTable();
57 if (IsReportRequested(kProfile)) {
58 // Build the profile.
59 SampleFilter samplesForIsolate(thread_->isolate()->main_port(),
60 Thread::kMutatorTask, -1, -1);
61 profile_.Build(thread, &samplesForIsolate, Profiler::sample_buffer());
62 }
63}
64
65bool SourceReport::IsReportRequested(ReportKind report_kind) {
66 return (report_set_ & report_kind) != 0;
67}
68
69bool SourceReport::ShouldSkipFunction(const Function& func) {
70 // TODO(32315): Verify that the check is still needed after the issue is
71 // resolved.
72 if (!func.token_pos().IsReal() || !func.end_token_pos().IsReal()) {
73 // At least one of the token positions is not known.
74 return true;
75 }
76
77 if (script_ != NULL && !script_->IsNull()) {
78 if (func.script() != script_->raw()) {
79 // The function is from the wrong script.
80 return true;
81 }
82 if (((start_pos_ > TokenPosition::kMinSource) &&
83 (func.end_token_pos() < start_pos_)) ||
84 ((end_pos_ > TokenPosition::kMinSource) &&
85 (func.token_pos() > end_pos_))) {
86 // The function does not intersect with the requested token range.
87 return true;
88 }
89 }
90
91 // These don't have unoptimized code and are only used for synthetic stubs.
92 if (func.ForceOptimize()) return true;
93
94 switch (func.kind()) {
95 case FunctionLayout::kRegularFunction:
96 case FunctionLayout::kClosureFunction:
97 case FunctionLayout::kImplicitClosureFunction:
98 case FunctionLayout::kImplicitStaticGetter:
99 case FunctionLayout::kFieldInitializer:
100 case FunctionLayout::kGetterFunction:
101 case FunctionLayout::kSetterFunction:
102 case FunctionLayout::kConstructor:
103 break;
104 default:
105 return true;
106 }
107 if (func.is_abstract() || func.IsImplicitConstructor() ||
108 func.IsRedirectingFactory() || func.is_synthetic()) {
109 return true;
110 }
111 // Note that context_scope() remains null for closures declared in bytecode,
112 // because the same information is retrieved from the parent's local variable
113 // descriptors.
114 // See IsLocalFunction() case in BytecodeReader::ComputeLocalVarDescriptors.
115 if (!func.is_declared_in_bytecode() && func.IsNonImplicitClosureFunction() &&
116 (func.context_scope() == ContextScope::null())) {
117 // TODO(iposva): This can arise if we attempt to compile an inner function
118 // before we have compiled its enclosing function or if the enclosing
119 // function failed to compile.
120 return true;
121 }
122 return false;
123}
124
125bool SourceReport::ShouldSkipField(const Field& field) {
126 if (!field.token_pos().IsReal() || !field.end_token_pos().IsReal()) {
127 // At least one of the token positions is not known.
128 return true;
129 }
130
131 if (script_ != NULL && !script_->IsNull()) {
132 if (field.Script() != script_->raw()) {
133 // The field is from the wrong script.
134 return true;
135 }
136 if (((start_pos_ > TokenPosition::kMinSource) &&
137 (field.end_token_pos() < start_pos_)) ||
138 ((end_pos_ > TokenPosition::kMinSource) &&
139 (field.token_pos() > end_pos_))) {
140 // The field does not intersect with the requested token range.
141 return true;
142 }
143 }
144 return false;
145}
146
147intptr_t SourceReport::GetScriptIndex(const Script& script) {
148 ScriptTableEntry wrapper;
149 const String& url = String::Handle(zone(), script.url());
150 wrapper.key = &url;
151 wrapper.script = &Script::Handle(zone(), script.raw());
152 ScriptTableEntry* pair = script_table_.LookupValue(&wrapper);
153 if (pair != NULL) {
154 return pair->index;
155 }
156 ScriptTableEntry* tmp = new ScriptTableEntry();
157 tmp->key = &url;
158 tmp->index = next_script_index_++;
159 tmp->script = wrapper.script;
160 script_table_entries_.Add(tmp);
161 script_table_.Insert(tmp);
162 ASSERT(script_table_entries_.length() == next_script_index_);
163#if defined(DEBUG)
164 VerifyScriptTable();
165#endif
166 return tmp->index;
167}
168
169#if defined(DEBUG)
170void SourceReport::VerifyScriptTable() {
171 for (intptr_t i = 0; i < script_table_entries_.length(); i++) {
172 const String* url = script_table_entries_[i]->key;
173 const Script* script = script_table_entries_[i]->script;
174 intptr_t index = script_table_entries_[i]->index;
175 ASSERT(i == index);
176 const String& url2 = String::Handle(zone(), script->url());
177 ASSERT(url2.Equals(*url));
178 ScriptTableEntry wrapper;
179 wrapper.key = &url2;
180 wrapper.script = &Script::Handle(zone(), script->raw());
181 ScriptTableEntry* pair = script_table_.LookupValue(&wrapper);
182 ASSERT(i == pair->index);
183 }
184}
185#endif
186
187bool SourceReport::ScriptIsLoadedByLibrary(const Script& script,
188 const Library& lib) {
189 const Array& scripts = Array::Handle(zone(), lib.LoadedScripts());
190 for (intptr_t j = 0; j < scripts.Length(); j++) {
191 if (scripts.At(j) == script.raw()) {
192 return true;
193 }
194 }
195 return false;
196}
197
198void SourceReport::PrintCallSitesData(JSONObject* jsobj,
199 const Function& function,
200 const Code& code) {
201 if (code.IsNull()) {
202 // TODO(regis): implement for bytecode.
203 return;
204 }
205 const TokenPosition begin_pos = function.token_pos();
206 const TokenPosition end_pos = function.end_token_pos();
207
208 ZoneGrowableArray<const ICData*>* ic_data_array =
209 new (zone()) ZoneGrowableArray<const ICData*>();
210 function.RestoreICDataMap(ic_data_array, false /* clone ic-data */);
211 const PcDescriptors& descriptors =
212 PcDescriptors::Handle(zone(), code.pc_descriptors());
213
214 JSONArray sites(jsobj, "callSites");
215
216 PcDescriptors::Iterator iter(
217 descriptors,
218 PcDescriptorsLayout::kIcCall | PcDescriptorsLayout::kUnoptStaticCall);
219 while (iter.MoveNext()) {
220 HANDLESCOPE(thread());
221 ASSERT(iter.DeoptId() < ic_data_array->length());
222 const ICData* ic_data = (*ic_data_array)[iter.DeoptId()];
223 if (ic_data != NULL) {
224 const TokenPosition token_pos = iter.TokenPos();
225 if ((token_pos < begin_pos) || (token_pos > end_pos)) {
226 // Does not correspond to a valid source position.
227 continue;
228 }
229 ic_data->PrintToJSONArray(sites, token_pos);
230 }
231 }
232}
233
234void SourceReport::PrintCoverageData(JSONObject* jsobj,
235 const Function& function,
236 const Code& code) {
237 if (code.IsNull()) {
238 // TODO(regis): implement for bytecode.
239 return;
240 }
241 const TokenPosition begin_pos = function.token_pos();
242 const TokenPosition end_pos = function.end_token_pos();
243
244 ZoneGrowableArray<const ICData*>* ic_data_array =
245 new (zone()) ZoneGrowableArray<const ICData*>();
246 function.RestoreICDataMap(ic_data_array, false /* clone ic-data */);
247 const PcDescriptors& descriptors =
248 PcDescriptors::Handle(zone(), code.pc_descriptors());
249
250 const int kCoverageNone = 0;
251 const int kCoverageMiss = 1;
252 const int kCoverageHit = 2;
253
254 intptr_t func_length = (end_pos.Pos() - begin_pos.Pos()) + 1;
255 GrowableArray<char> coverage(func_length);
256 coverage.SetLength(func_length);
257 for (int i = 0; i < func_length; i++) {
258 coverage[i] = kCoverageNone;
259 }
260
261 if (function.WasExecuted()) {
262 coverage[0] = kCoverageHit;
263 } else {
264 coverage[0] = kCoverageMiss;
265 }
266
267 PcDescriptors::Iterator iter(
268 descriptors,
269 PcDescriptorsLayout::kIcCall | PcDescriptorsLayout::kUnoptStaticCall);
270 while (iter.MoveNext()) {
271 HANDLESCOPE(thread());
272 ASSERT(iter.DeoptId() < ic_data_array->length());
273 const ICData* ic_data = (*ic_data_array)[iter.DeoptId()];
274 if (ic_data != NULL) {
275 const TokenPosition token_pos = iter.TokenPos();
276 if ((token_pos < begin_pos) || (token_pos > end_pos)) {
277 // Does not correspond to a valid source position.
278 continue;
279 }
280 intptr_t count = ic_data->AggregateCount();
281 intptr_t token_offset = token_pos.Pos() - begin_pos.Pos();
282 if (count > 0) {
283 coverage[token_offset] = kCoverageHit;
284 } else {
285 if (coverage[token_offset] == kCoverageNone) {
286 coverage[token_offset] = kCoverageMiss;
287 }
288 }
289 }
290 }
291
292 JSONObject cov(jsobj, "coverage");
293 {
294 JSONArray hits(&cov, "hits");
295 for (int i = 0; i < func_length; i++) {
296 if (coverage[i] == kCoverageHit) {
297 // Add the token position of the hit.
298 hits.AddValue(begin_pos.Pos() + i);
299 }
300 }
301 }
302 {
303 JSONArray misses(&cov, "misses");
304 for (int i = 0; i < func_length; i++) {
305 if (coverage[i] == kCoverageMiss) {
306 // Add the token position of the miss.
307 misses.AddValue(begin_pos.Pos() + i);
308 }
309 }
310 }
311}
312
313void SourceReport::PrintPossibleBreakpointsData(JSONObject* jsobj,
314 const Function& func,
315 const Code& code) {
316 const TokenPosition begin_pos = func.token_pos();
317 const TokenPosition end_pos = func.end_token_pos();
318 intptr_t func_length = (end_pos.Pos() - begin_pos.Pos()) + 1;
319
320 BitVector possible(zone(), func_length);
321
322 if (code.IsNull()) {
323 const Bytecode& bytecode = Bytecode::Handle(func.bytecode());
324 ASSERT(!bytecode.IsNull());
325 kernel::BytecodeSourcePositionsIterator iter(zone(), bytecode);
326 intptr_t token_offset = -1;
327 uword pc_offset = kUwordMax;
328 // Ignore all possible breakpoint positions until the first DebugCheck
329 // opcode of the function.
330 const uword debug_check_pc = bytecode.GetFirstDebugCheckOpcodePc();
331 if (debug_check_pc != 0) {
332 const uword debug_check_pc_offset =
333 debug_check_pc - bytecode.PayloadStart();
334 while (iter.MoveNext()) {
335 if (pc_offset != kUwordMax) {
336 // Check that there is at least one 'debug checked' opcode in the last
337 // source position range.
338 if (bytecode.GetDebugCheckedOpcodeReturnAddress(
339 pc_offset, iter.PcOffset()) != 0) {
340 possible.Add(token_offset);
341 }
342 pc_offset = kUwordMax;
343 }
344 const TokenPosition token_pos = iter.TokenPos();
345 if ((token_pos < begin_pos) || (token_pos > end_pos)) {
346 // Does not correspond to a valid source position.
347 continue;
348 }
349 if (iter.PcOffset() < debug_check_pc_offset) {
350 // No breakpoints in prologue.
351 continue;
352 }
353 pc_offset = iter.PcOffset();
354 token_offset = token_pos.Pos() - begin_pos.Pos();
355 }
356 }
357 if (pc_offset != kUwordMax && bytecode.GetDebugCheckedOpcodeReturnAddress(
358 pc_offset, bytecode.Size()) != 0) {
359 possible.Add(token_offset);
360 }
361 } else {
362 const uint8_t kSafepointKind =
363 (PcDescriptorsLayout::kIcCall | PcDescriptorsLayout::kUnoptStaticCall |
364 PcDescriptorsLayout::kRuntimeCall);
365
366 const PcDescriptors& descriptors =
367 PcDescriptors::Handle(zone(), code.pc_descriptors());
368
369 PcDescriptors::Iterator iter(descriptors, kSafepointKind);
370 while (iter.MoveNext()) {
371 const TokenPosition token_pos = iter.TokenPos();
372 if ((token_pos < begin_pos) || (token_pos > end_pos)) {
373 // Does not correspond to a valid source position.
374 continue;
375 }
376 intptr_t token_offset = token_pos.Pos() - begin_pos.Pos();
377 possible.Add(token_offset);
378 }
379 }
380
381 JSONArray bpts(jsobj, "possibleBreakpoints");
382 for (int i = 0; i < func_length; i++) {
383 if (possible.Contains(i)) {
384 // Add the token position.
385 bpts.AddValue(begin_pos.Pos() + i);
386 }
387 }
388}
389
390void SourceReport::PrintProfileData(JSONObject* jsobj,
391 ProfileFunction* profile_function) {
392 ASSERT(profile_function != NULL);
393 ASSERT(profile_function->NumSourcePositions() > 0);
394
395 {
396 JSONObject profile(jsobj, "profile");
397
398 {
399 JSONObject profileData(&profile, "metadata");
400 profileData.AddProperty("sampleCount", profile_.sample_count());
401 }
402
403 // Positions.
404 {
405 JSONArray positions(&profile, "positions");
406 for (intptr_t i = 0; i < profile_function->NumSourcePositions(); i++) {
407 const ProfileFunctionSourcePosition& position =
408 profile_function->GetSourcePosition(i);
409 if (position.token_pos().IsSourcePosition()) {
410 // Add as an integer.
411 positions.AddValue(position.token_pos().Pos());
412 } else {
413 // Add as a string.
414 positions.AddValue(position.token_pos().ToCString());
415 }
416 }
417 }
418
419 // Exclusive ticks.
420 {
421 JSONArray exclusiveTicks(&profile, "exclusiveTicks");
422 for (intptr_t i = 0; i < profile_function->NumSourcePositions(); i++) {
423 const ProfileFunctionSourcePosition& position =
424 profile_function->GetSourcePosition(i);
425 exclusiveTicks.AddValue(position.exclusive_ticks());
426 }
427 }
428 // Inclusive ticks.
429 {
430 JSONArray inclusiveTicks(&profile, "inclusiveTicks");
431 for (intptr_t i = 0; i < profile_function->NumSourcePositions(); i++) {
432 const ProfileFunctionSourcePosition& position =
433 profile_function->GetSourcePosition(i);
434 inclusiveTicks.AddValue(position.inclusive_ticks());
435 }
436 }
437 }
438}
439
440void SourceReport::PrintScriptTable(JSONArray* scripts) {
441 for (intptr_t i = 0; i < script_table_entries_.length(); i++) {
442 const Script* script = script_table_entries_[i]->script;
443 scripts->AddValue(*script);
444 }
445}
446
447void SourceReport::VisitFunction(JSONArray* jsarr, const Function& func) {
448 if (ShouldSkipFunction(func)) {
449 return;
450 }
451
452 const Script& script = Script::Handle(zone(), func.script());
453 const TokenPosition begin_pos = func.token_pos();
454 const TokenPosition end_pos = func.end_token_pos();
455
456 Code& code = Code::Handle(zone(), func.unoptimized_code());
457 Bytecode& bytecode = Bytecode::Handle(zone());
458 if (FLAG_enable_interpreter && !func.HasCode() && func.HasBytecode()) {
459 // When the bytecode of a function is loaded, the function code is not null,
460 // but pointing to the stub to interpret the bytecode. The various Print
461 // functions below take code as an argument and know to process the bytecode
462 // if code is null.
463 code = Code::null(); // Ignore installed stub to interpret bytecode.
464 bytecode = func.bytecode();
465 }
466 if (code.IsNull() && bytecode.IsNull()) {
467 if (func.HasCode() || (compile_mode_ == kForceCompile)) {
468 const Error& err =
469 Error::Handle(Compiler::EnsureUnoptimizedCode(thread(), func));
470 if (!err.IsNull()) {
471 // Emit an uncompiled range for this function with error information.
472 JSONObject range(jsarr);
473 range.AddProperty("scriptIndex", GetScriptIndex(script));
474 range.AddProperty("startPos", begin_pos);
475 range.AddProperty("endPos", end_pos);
476 range.AddProperty("compiled", false);
477 range.AddProperty("error", err);
478 return;
479 }
480 code = func.unoptimized_code();
481 if (FLAG_enable_interpreter && !func.HasCode() && func.HasBytecode()) {
482 code = Code::null(); // Ignore installed stub to interpret bytecode.
483 bytecode = func.bytecode();
484 }
485 } else {
486 // This function has not been compiled yet.
487 JSONObject range(jsarr);
488 range.AddProperty("scriptIndex", GetScriptIndex(script));
489 range.AddProperty("startPos", begin_pos);
490 range.AddProperty("endPos", end_pos);
491 range.AddProperty("compiled", false);
492 return;
493 }
494 }
495 ASSERT(!code.IsNull() || !bytecode.IsNull());
496
497 // We skip compiled async functions. Once an async function has
498 // been compiled, there is another function with the same range which
499 // actually contains the user code.
500 if (!func.IsAsyncFunction() && !func.IsAsyncGenerator() &&
501 !func.IsSyncGenerator()) {
502 JSONObject range(jsarr);
503 range.AddProperty("scriptIndex", GetScriptIndex(script));
504 range.AddProperty("startPos", begin_pos);
505 range.AddProperty("endPos", end_pos);
506 range.AddProperty("compiled", true); // bytecode or code.
507
508 if (IsReportRequested(kCallSites)) {
509 PrintCallSitesData(&range, func, code);
510 }
511 if (IsReportRequested(kCoverage)) {
512 PrintCoverageData(&range, func, code);
513 }
514 if (IsReportRequested(kPossibleBreakpoints)) {
515 PrintPossibleBreakpointsData(&range, func, code);
516 }
517 if (IsReportRequested(kProfile)) {
518 ProfileFunction* profile_function = profile_.FindFunction(func);
519 if ((profile_function != NULL) &&
520 (profile_function->NumSourcePositions() > 0)) {
521 PrintProfileData(&range, profile_function);
522 }
523 }
524 }
525}
526
527void SourceReport::VisitField(JSONArray* jsarr, const Field& field) {
528 if (ShouldSkipField(field) || !field.HasInitializerFunction()) return;
529 const Function& func = Function::Handle(field.InitializerFunction());
530 VisitFunction(jsarr, func);
531}
532
533void SourceReport::VisitLibrary(JSONArray* jsarr, const Library& lib) {
534 Class& cls = Class::Handle(zone());
535 Array& functions = Array::Handle(zone());
536 Array& fields = Array::Handle(zone());
537 Function& func = Function::Handle(zone());
538 Field& field = Field::Handle(zone());
539 Script& script = Script::Handle(zone());
540 ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
541 while (it.HasNext()) {
542 cls = it.GetNextClass();
543 if (!cls.is_finalized()) {
544 if (compile_mode_ == kForceCompile) {
545 Error& err = Error::Handle(cls.EnsureIsFinalized(thread()));
546 if (!err.IsNull()) {
547 // Emit an uncompiled range for this class with error information.
548 JSONObject range(jsarr);
549 script = cls.script();
550 range.AddProperty("scriptIndex", GetScriptIndex(script));
551 range.AddProperty("startPos", cls.token_pos());
552 range.AddProperty("endPos", cls.end_token_pos());
553 range.AddProperty("compiled", false);
554 range.AddProperty("error", err);
555 continue;
556 }
557 ASSERT(cls.is_finalized());
558 } else {
559 cls.EnsureDeclarationLoaded();
560 // Emit one range for the whole uncompiled class.
561 JSONObject range(jsarr);
562 script = cls.script();
563 range.AddProperty("scriptIndex", GetScriptIndex(script));
564 range.AddProperty("startPos", cls.token_pos());
565 range.AddProperty("endPos", cls.end_token_pos());
566 range.AddProperty("compiled", false);
567 continue;
568 }
569 }
570
571 functions = cls.functions();
572 for (int i = 0; i < functions.Length(); i++) {
573 func ^= functions.At(i);
574 // Skip getter functions of static const field.
575 if (func.kind() == FunctionLayout::kImplicitStaticGetter) {
576 field ^= func.accessor_field();
577 if (field.is_const() && field.is_static()) {
578 continue;
579 }
580 }
581 VisitFunction(jsarr, func);
582 }
583
584 fields = cls.fields();
585 for (intptr_t i = 0; i < fields.Length(); i++) {
586 field ^= fields.At(i);
587 VisitField(jsarr, field);
588 }
589 }
590}
591
592void SourceReport::VisitClosures(JSONArray* jsarr) {
593 // Note that closures declared in bytecode are not visited here, but in
594 // VisitFunction while traversing the object pool of their owner functions.
595 const GrowableObjectArray& closures = GrowableObjectArray::Handle(
596 thread()->isolate()->object_store()->closure_functions());
597
598 // We need to keep rechecking the length of the closures array, as handling
599 // a closure potentially adds new entries to the end.
600 Function& func = Function::Handle(zone());
601 for (int i = 0; i < closures.Length(); i++) {
602 func ^= closures.At(i);
603 VisitFunction(jsarr, func);
604 }
605}
606
607void SourceReport::PrintJSON(JSONStream* js,
608 const Script& script,
609 TokenPosition start_pos,
610 TokenPosition end_pos) {
611 Init(Thread::Current(), &script, start_pos, end_pos);
612
613 JSONObject report(js);
614 report.AddProperty("type", "SourceReport");
615 {
616 JSONArray ranges(&report, "ranges");
617
618 const GrowableObjectArray& libs = GrowableObjectArray::Handle(
619 zone(), thread()->isolate()->object_store()->libraries());
620
621 // We only visit the libraries which actually load the specified script.
622 Library& lib = Library::Handle(zone());
623 for (int i = 0; i < libs.Length(); i++) {
624 lib ^= libs.At(i);
625 if (script.IsNull() || ScriptIsLoadedByLibrary(script, lib)) {
626 VisitLibrary(&ranges, lib);
627 }
628 }
629
630 // Visit all closures for this isolate.
631 VisitClosures(&ranges);
632 }
633
634 // Print the script table.
635 JSONArray scripts(&report, "scripts");
636 PrintScriptTable(&scripts);
637}
638
639} // namespace dart
640#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
641