Index: runtime/vm/source_report.cc |
diff --git a/runtime/vm/source_report.cc b/runtime/vm/source_report.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..242957bae6aa1dc4d066f9bac05b401f865ddf91 |
--- /dev/null |
+++ b/runtime/vm/source_report.cc |
@@ -0,0 +1,318 @@ |
+// Copyright (c) 2015, 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. |
+ |
+#include "vm/source_report.h" |
+ |
+#include "vm/compiler.h" |
+#include "vm/object.h" |
+#include "vm/object_store.h" |
+ |
+namespace dart { |
+ |
+SourceReport::SourceReport(ReportKind report_kind, CompileMode compile_mode) |
+ : report_kind_(report_kind), |
+ compile_mode_(compile_mode), |
+ thread_(NULL), |
+ script_(NULL), |
+ start_pos_(-1), |
+ end_pos_(-1), |
+ next_script_index_(0) { |
+} |
+ |
+ |
+void SourceReport::Init(Thread* thread, |
+ const Script* script, |
+ intptr_t start_pos, |
+ intptr_t end_pos) { |
+ thread_ = thread; |
+ script_ = script; |
+ start_pos_ = start_pos; |
+ end_pos_ = end_pos; |
+ script_table_entries_.Clear(); |
+ script_table_.Clear(); |
+ next_script_index_ = 0; |
+} |
+ |
+ |
+bool SourceReport::ShouldSkipFunction(const Function& func) { |
+ if (script_ != NULL && !script_->IsNull()) { |
+ if (func.script() != script_->raw()) { |
+ // The function is from the wrong script. |
+ return true; |
+ } |
+ if (((start_pos_ > 0) && (func.end_token_pos() < start_pos_)) || |
+ ((end_pos_ > 0) && (func.token_pos() > end_pos_))) { |
+ // The function does not intersect with the requested token range. |
+ return true; |
+ } |
+ } |
+ |
+ switch (func.kind()) { |
+ case RawFunction::kRegularFunction: |
+ case RawFunction::kClosureFunction: |
+ case RawFunction::kGetterFunction: |
+ case RawFunction::kSetterFunction: |
+ case RawFunction::kConstructor: |
+ break; |
+ default: |
+ return true; |
+ } |
+ if (func.is_abstract() || |
+ func.IsImplicitConstructor() || |
+ func.IsRedirectingFactory()) { |
+ return true; |
+ } |
+ if (func.IsNonImplicitClosureFunction() && |
+ (func.context_scope() == ContextScope::null())) { |
+ // TODO(iposva): This can arise if we attempt to compile an inner function |
+ // before we have compiled its enclosing function or if the enclosing |
+ // function failed to compile. |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+ |
+intptr_t SourceReport::GetScriptIndex(const Script& script) { |
+ const String& url = String::Handle(zone(), script.url()); |
+ ScriptTableEntry* pair = script_table_.Lookup(&url); |
+ if (pair != NULL) { |
+ return pair->index; |
+ } |
+ |
+ ScriptTableEntry tmp; |
+ tmp.key = &url; |
+ tmp.index = next_script_index_++; |
+ tmp.script = &script; |
+ script_table_entries_.Add(tmp); |
+ script_table_.Insert(&(script_table_entries_.Last())); |
+ return tmp.index; |
+} |
+ |
+ |
+bool SourceReport::ScriptIsLoadedByLibrary(const Script& script, |
+ const Library& lib) { |
+ const Array& scripts = Array::Handle(zone(), lib.LoadedScripts()); |
+ for (intptr_t j = 0; j < scripts.Length(); j++) { |
+ if (scripts.At(j) == script.raw()) { |
+ return true; |
+ } |
+ } |
+ return false; |
+} |
+ |
+ |
+void SourceReport::PrintCallSitesData(JSONObject* jsobj, |
+ const Function& func, |
+ const Code& code) { |
+ const intptr_t begin_pos = func.token_pos(); |
+ const intptr_t end_pos = func.end_token_pos(); |
+ |
+ ZoneGrowableArray<const ICData*>* ic_data_array = |
+ new(zone()) ZoneGrowableArray<const ICData*>(); |
+ func.RestoreICDataMap(ic_data_array, false /* clone descriptors */); |
+ const PcDescriptors& descriptors = PcDescriptors::Handle( |
+ zone(), code.pc_descriptors()); |
+ |
+ JSONArray sites(jsobj, "callSites"); |
+ |
+ PcDescriptors::Iterator iter( |
+ descriptors, |
+ RawPcDescriptors::kIcCall | RawPcDescriptors::kUnoptStaticCall); |
+ while (iter.MoveNext()) { |
+ HANDLESCOPE(thread()); |
+ const ICData* ic_data = (*ic_data_array)[iter.DeoptId()]; |
+ if (!ic_data->IsNull()) { |
+ const intptr_t token_pos = iter.TokenPos(); |
+ if ((token_pos < begin_pos) || (token_pos > end_pos)) { |
+ // Does not correspond to a valid source position. |
+ continue; |
+ } |
+ bool is_static_call = iter.Kind() == RawPcDescriptors::kUnoptStaticCall; |
+ ic_data->PrintToJSONArrayNew(sites, token_pos, is_static_call); |
+ } |
+ } |
+} |
+ |
+void SourceReport::PrintCoverageData(JSONObject* jsobj, |
+ const Function& func, |
+ const Code& code) { |
+ const intptr_t begin_pos = func.token_pos(); |
+ const intptr_t end_pos = func.end_token_pos(); |
+ |
+ ZoneGrowableArray<const ICData*>* ic_data_array = |
+ new(zone()) ZoneGrowableArray<const ICData*>(); |
+ func.RestoreICDataMap(ic_data_array, false /* clone descriptors */); |
+ const PcDescriptors& descriptors = PcDescriptors::Handle( |
+ zone(), code.pc_descriptors()); |
+ |
+ const int kCoverageNone = 0; |
+ const int kCoverageMiss = 1; |
+ const int kCoverageHit = 2; |
+ |
+ intptr_t func_length = (end_pos - begin_pos) + 1; |
+ GrowableArray<char> coverage(func_length); |
+ coverage.SetLength(func_length); |
+ for (int i = 0; i < func_length; i++) { |
+ coverage[i] = kCoverageNone; |
+ } |
+ |
+ PcDescriptors::Iterator iter( |
+ descriptors, |
+ RawPcDescriptors::kIcCall | RawPcDescriptors::kUnoptStaticCall); |
+ while (iter.MoveNext()) { |
+ HANDLESCOPE(thread()); |
+ const ICData* ic_data = (*ic_data_array)[iter.DeoptId()]; |
+ if (!ic_data->IsNull()) { |
+ const intptr_t token_pos = iter.TokenPos(); |
+ if ((token_pos < begin_pos) || (token_pos > end_pos)) { |
+ // Does not correspond to a valid source position. |
+ continue; |
+ } |
+ intptr_t count = ic_data->AggregateCount(); |
+ intptr_t token_offset = token_pos - begin_pos; |
+ if (count > 0) { |
+ coverage[token_offset] = kCoverageHit; |
+ } else { |
+ if (coverage[token_offset] == kCoverageNone) { |
+ coverage[token_offset] = kCoverageMiss; |
+ } |
+ } |
+ } |
+ } |
+ |
+ JSONObject cov(jsobj, "coverage"); |
+ { |
+ JSONArray hits(&cov, "hits"); |
+ for (int i = 0; i < func_length; i++) { |
+ if (coverage[i] == kCoverageHit) { |
+ hits.AddValue(begin_pos + i); // Add the token position of the hit. |
+ } |
+ } |
+ } |
+ { |
+ JSONArray misses(&cov, "misses"); |
+ for (int i = 0; i < func_length; i++) { |
+ if (coverage[i] == kCoverageMiss) { |
+ misses.AddValue(begin_pos + i); // Add the token position of the miss. |
+ } |
+ } |
+ } |
+} |
+ |
+ |
+void SourceReport::PrintScriptTable(JSONArray* scripts) { |
+ for (int i = 0; i < script_table_entries_.length(); i++) { |
+ const Script* script = script_table_entries_[i].script; |
+ scripts->AddValue(*script); |
+ } |
+} |
+ |
+ |
+void SourceReport::VisitFunction(JSONArray* jsarr, const Function& func) { |
+ if (ShouldSkipFunction(func)) { |
+ return; |
+ } |
+ |
+ const Script& script = Script::Handle(zone(), func.script()); |
+ const intptr_t begin_pos = func.token_pos(); |
+ const intptr_t end_pos = func.end_token_pos(); |
+ |
+ Code& code = Code::Handle(zone(), func.unoptimized_code()); |
+ if (code.IsNull()) { |
+ if (func.HasCode() || (compile_mode_ == kForceCompile)) { |
+ if (Compiler::EnsureUnoptimizedCode(thread(), func) != Error::null()) { |
+ // Ignore the error and this function entirely. |
+ return; |
+ } |
+ code = func.unoptimized_code(); |
+ } else { |
+ // This function has not been compiled yet. |
+ JSONObject range(jsarr); |
+ range.AddProperty("scriptIndex", GetScriptIndex(script)); |
+ range.AddProperty("startPos", begin_pos); |
+ range.AddProperty("endPos", end_pos); |
+ range.AddProperty("compiled", false); |
+ return; |
+ } |
+ } |
+ ASSERT(!code.IsNull()); |
+ |
+ JSONObject range(jsarr); |
+ range.AddProperty("scriptIndex", GetScriptIndex(script)); |
+ range.AddProperty("startPos", begin_pos); |
+ range.AddProperty("endPos", end_pos); |
+ range.AddProperty("compiled", true); |
+ |
+ if (report_kind_ == kCallSites) { |
+ PrintCallSitesData(&range, func, code); |
+ } else if (report_kind_ == kCoverage) { |
+ PrintCoverageData(&range, func, code); |
+ } |
+} |
+ |
+ |
+void SourceReport::VisitLibrary(JSONArray* jsarr, const Library& lib) { |
+ Class& cls = Class::Handle(zone()); |
+ Array& functions = Array::Handle(zone()); |
+ Function& func = Function::Handle(zone()); |
+ ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate); |
+ while (it.HasNext()) { |
+ cls = it.GetNextClass(); |
+ functions = cls.functions(); |
+ for (int i = 0; i < functions.Length(); i++) { |
+ func ^= functions.At(i); |
+ VisitFunction(jsarr, func); |
+ } |
+ } |
+} |
+ |
+ |
+void SourceReport::VisitClosures(JSONArray* jsarr) { |
+ const GrowableObjectArray& closures = GrowableObjectArray::Handle( |
+ thread()->isolate()->object_store()->closure_functions()); |
+ |
+ // We need to keep rechecking the length of the closures array, as handling |
+ // a closure potentially adds new entries to the end. |
+ Function& func = Function::Handle(zone()); |
+ for (int i = 0; i < closures.Length(); i++) { |
+ func ^= closures.At(i); |
+ VisitFunction(jsarr, func); |
+ } |
+} |
+ |
+ |
+void SourceReport::PrintJSON(JSONStream* js, |
+ const Script& script, |
+ intptr_t start_pos, intptr_t end_pos) { |
+ Init(Thread::Current(), &script, start_pos, end_pos); |
+ |
+ JSONObject report(js); |
+ report.AddProperty("type", "SourceReport"); |
+ { |
+ JSONArray ranges(&report, "ranges"); |
+ |
+ const GrowableObjectArray& libs = GrowableObjectArray::Handle( |
+ zone(), thread()->isolate()->object_store()->libraries()); |
+ |
+ // We only visit the libraries which actually load the specified script. |
+ Library& lib = Library::Handle(zone()); |
+ for (int i = 0; i < libs.Length(); i++) { |
+ lib ^= libs.At(i); |
+ if (script.IsNull() || ScriptIsLoadedByLibrary(script, lib)) { |
+ VisitLibrary(&ranges, lib); |
+ } |
+ } |
+ |
+ // Visit all closures for this isolate. |
+ VisitClosures(&ranges); |
+ } |
+ |
+ // Print the script table. |
+ JSONArray scripts(&report, "scripts"); |
+ PrintScriptTable(&scripts); |
+} |
+ |
+ |
+} // namespace dart |