Line data Source code
1 : // Copyright 2012 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #include "src/profiler/profile-generator.h"
6 :
7 : #include "src/base/adapters.h"
8 : #include "src/debug/debug.h"
9 : #include "src/deoptimizer.h"
10 : #include "src/global-handles.h"
11 : #include "src/objects-inl.h"
12 : #include "src/profiler/cpu-profiler.h"
13 : #include "src/profiler/profile-generator-inl.h"
14 : #include "src/tracing/trace-event.h"
15 : #include "src/tracing/traced-value.h"
16 : #include "src/unicode.h"
17 :
18 : namespace v8 {
19 : namespace internal {
20 :
21 2710892 : void SourcePositionTable::SetPosition(int pc_offset, int line,
22 : int inlining_id) {
23 : DCHECK_GE(pc_offset, 0);
24 : DCHECK_GT(line, 0); // The 1-based number of the source line.
25 : // Check that we are inserting in ascending order, so that the vector remains
26 : // sorted.
27 : DCHECK(pc_offsets_to_lines_.empty() ||
28 : pc_offsets_to_lines_.back().pc_offset < pc_offset);
29 4819210 : if (pc_offsets_to_lines_.empty() ||
30 4217424 : pc_offsets_to_lines_.back().line_number != line ||
31 1506532 : pc_offsets_to_lines_.back().inlining_id != inlining_id) {
32 2408726 : pc_offsets_to_lines_.push_back({pc_offset, line, inlining_id});
33 : }
34 2710892 : }
35 :
36 78236 : int SourcePositionTable::GetSourceLineNumber(int pc_offset) const {
37 78236 : if (pc_offsets_to_lines_.empty()) {
38 : return v8::CpuProfileNode::kNoLineNumberInfo;
39 : }
40 : auto it = std::lower_bound(
41 : pc_offsets_to_lines_.begin(), pc_offsets_to_lines_.end(),
42 : SourcePositionTuple{pc_offset, 0, SourcePosition::kNotInlined});
43 78181 : if (it != pc_offsets_to_lines_.begin()) --it;
44 78181 : return it->line_number;
45 : }
46 :
47 49670 : int SourcePositionTable::GetInliningId(int pc_offset) const {
48 49670 : if (pc_offsets_to_lines_.empty()) {
49 : return SourcePosition::kNotInlined;
50 : }
51 : auto it = std::lower_bound(
52 : pc_offsets_to_lines_.begin(), pc_offsets_to_lines_.end(),
53 : SourcePositionTuple{pc_offset, 0, SourcePosition::kNotInlined});
54 49660 : if (it != pc_offsets_to_lines_.begin()) --it;
55 49660 : return it->inlining_id;
56 : }
57 :
58 0 : void SourcePositionTable::print() const {
59 0 : base::OS::Print(" - source position table at %p\n", this);
60 0 : for (const SourcePositionTuple& pos_info : pc_offsets_to_lines_) {
61 : base::OS::Print(" %d --> line_number: %d inlining_id: %d\n",
62 : pos_info.pc_offset, pos_info.line_number,
63 0 : pos_info.inlining_id);
64 : }
65 0 : }
66 :
67 : const char* const CodeEntry::kWasmResourceNamePrefix = "wasm ";
68 : const char* const CodeEntry::kEmptyResourceName = "";
69 : const char* const CodeEntry::kEmptyBailoutReason = "";
70 : const char* const CodeEntry::kNoDeoptReason = "";
71 :
72 : const char* const CodeEntry::kProgramEntryName = "(program)";
73 : const char* const CodeEntry::kIdleEntryName = "(idle)";
74 : const char* const CodeEntry::kGarbageCollectorEntryName = "(garbage collector)";
75 : const char* const CodeEntry::kUnresolvedFunctionName = "(unresolved function)";
76 :
77 : base::LazyDynamicInstance<CodeEntry, CodeEntry::ProgramEntryCreateTrait>::type
78 : CodeEntry::kProgramEntry = LAZY_DYNAMIC_INSTANCE_INITIALIZER;
79 :
80 : base::LazyDynamicInstance<CodeEntry, CodeEntry::IdleEntryCreateTrait>::type
81 : CodeEntry::kIdleEntry = LAZY_DYNAMIC_INSTANCE_INITIALIZER;
82 :
83 : base::LazyDynamicInstance<CodeEntry, CodeEntry::GCEntryCreateTrait>::type
84 : CodeEntry::kGCEntry = LAZY_DYNAMIC_INSTANCE_INITIALIZER;
85 :
86 : base::LazyDynamicInstance<CodeEntry,
87 : CodeEntry::UnresolvedEntryCreateTrait>::type
88 : CodeEntry::kUnresolvedEntry = LAZY_DYNAMIC_INSTANCE_INITIALIZER;
89 :
90 134 : CodeEntry* CodeEntry::ProgramEntryCreateTrait::Create() {
91 : return new CodeEntry(CodeEventListener::FUNCTION_TAG,
92 268 : CodeEntry::kProgramEntryName);
93 : }
94 :
95 5 : CodeEntry* CodeEntry::IdleEntryCreateTrait::Create() {
96 : return new CodeEntry(CodeEventListener::FUNCTION_TAG,
97 10 : CodeEntry::kIdleEntryName);
98 : }
99 :
100 20 : CodeEntry* CodeEntry::GCEntryCreateTrait::Create() {
101 : return new CodeEntry(CodeEventListener::BUILTIN_TAG,
102 40 : CodeEntry::kGarbageCollectorEntryName);
103 : }
104 :
105 0 : CodeEntry* CodeEntry::UnresolvedEntryCreateTrait::Create() {
106 : return new CodeEntry(CodeEventListener::FUNCTION_TAG,
107 0 : CodeEntry::kUnresolvedFunctionName);
108 : }
109 :
110 77992 : uint32_t CodeEntry::GetHash() const {
111 : uint32_t hash = ComputeUnseededHash(tag());
112 77992 : if (script_id_ != v8::UnboundScript::kNoScriptId) {
113 117686 : hash ^= ComputeUnseededHash(static_cast<uint32_t>(script_id_));
114 117686 : hash ^= ComputeUnseededHash(static_cast<uint32_t>(position_));
115 : } else {
116 : hash ^= ComputeUnseededHash(
117 38298 : static_cast<uint32_t>(reinterpret_cast<uintptr_t>(name_)));
118 : hash ^= ComputeUnseededHash(
119 38298 : static_cast<uint32_t>(reinterpret_cast<uintptr_t>(resource_name_)));
120 38298 : hash ^= ComputeUnseededHash(line_number_);
121 : }
122 77992 : return hash;
123 : }
124 :
125 72824 : bool CodeEntry::IsSameFunctionAs(const CodeEntry* entry) const {
126 72824 : if (this == entry) return true;
127 1717 : if (script_id_ != v8::UnboundScript::kNoScriptId) {
128 1707 : return script_id_ == entry->script_id_ && position_ == entry->position_;
129 : }
130 20 : return name_ == entry->name_ && resource_name_ == entry->resource_name_ &&
131 20 : line_number_ == entry->line_number_;
132 : }
133 :
134 :
135 26418 : void CodeEntry::SetBuiltinId(Builtins::Name id) {
136 52836 : bit_field_ = TagField::update(bit_field_, CodeEventListener::BUILTIN_TAG);
137 26418 : bit_field_ = BuiltinIdField::update(bit_field_, id);
138 26418 : }
139 :
140 :
141 0 : int CodeEntry::GetSourceLine(int pc_offset) const {
142 79499 : if (line_info_) return line_info_->GetSourceLineNumber(pc_offset);
143 : return v8::CpuProfileNode::kNoLineNumberInfo;
144 : }
145 :
146 10 : void CodeEntry::SetInlineStacks(
147 : std::unordered_set<std::unique_ptr<CodeEntry>, Hasher, Equals>
148 : inline_entries,
149 : std::unordered_map<int, std::vector<CodeEntryAndLineNumber>>
150 : inline_stacks) {
151 10 : EnsureRareData()->inline_entries_ = std::move(inline_entries);
152 : rare_data_->inline_stacks_ = std::move(inline_stacks);
153 10 : }
154 :
155 50981 : const std::vector<CodeEntryAndLineNumber>* CodeEntry::GetInlineStack(
156 : int pc_offset) const {
157 50981 : if (!line_info_) return nullptr;
158 :
159 49640 : int inlining_id = line_info_->GetInliningId(pc_offset);
160 49640 : if (inlining_id == SourcePosition::kNotInlined) return nullptr;
161 : DCHECK(rare_data_);
162 :
163 : auto it = rare_data_->inline_stacks_.find(inlining_id);
164 2712 : return it != rare_data_->inline_stacks_.end() ? &it->second : nullptr;
165 : }
166 :
167 0 : void CodeEntry::set_deopt_info(
168 : const char* deopt_reason, int deopt_id,
169 : std::vector<CpuProfileDeoptFrame> inlined_frames) {
170 : DCHECK(!has_deopt_info());
171 0 : RareData* rare_data = EnsureRareData();
172 0 : rare_data->deopt_reason_ = deopt_reason;
173 0 : rare_data->deopt_id_ = deopt_id;
174 0 : rare_data->deopt_inlined_frames_ = std::move(inlined_frames);
175 0 : }
176 :
177 603431 : void CodeEntry::FillFunctionInfo(SharedFunctionInfo shared) {
178 1810293 : if (!shared->script()->IsScript()) return;
179 603431 : Script script = Script::cast(shared->script());
180 : set_script_id(script->id());
181 603431 : set_position(shared->StartPosition());
182 603431 : if (shared->optimization_disabled()) {
183 61 : set_bailout_reason(GetBailoutReason(shared->disable_optimization_reason()));
184 : }
185 : }
186 :
187 0 : CpuProfileDeoptInfo CodeEntry::GetDeoptInfo() {
188 : DCHECK(has_deopt_info());
189 :
190 : CpuProfileDeoptInfo info;
191 0 : info.deopt_reason = rare_data_->deopt_reason_;
192 : DCHECK_NE(kNoDeoptimizationId, rare_data_->deopt_id_);
193 0 : if (rare_data_->deopt_inlined_frames_.empty()) {
194 : info.stack.push_back(CpuProfileDeoptFrame(
195 0 : {script_id_, static_cast<size_t>(std::max(0, position()))}));
196 : } else {
197 0 : info.stack = rare_data_->deopt_inlined_frames_;
198 : }
199 0 : return info;
200 : }
201 :
202 71 : CodeEntry::RareData* CodeEntry::EnsureRareData() {
203 71 : if (!rare_data_) {
204 71 : rare_data_.reset(new RareData());
205 : }
206 71 : return rare_data_.get();
207 : }
208 :
209 0 : void CodeEntry::print() const {
210 0 : base::OS::Print("CodeEntry: at %p\n", this);
211 :
212 0 : base::OS::Print(" - name: %s\n", name_);
213 0 : base::OS::Print(" - resource_name: %s\n", resource_name_);
214 0 : base::OS::Print(" - line_number: %d\n", line_number_);
215 0 : base::OS::Print(" - column_number: %d\n", column_number_);
216 0 : base::OS::Print(" - script_id: %d\n", script_id_);
217 0 : base::OS::Print(" - position: %d\n", position_);
218 : base::OS::Print(" - instruction_start: %p\n",
219 0 : reinterpret_cast<void*>(instruction_start_));
220 :
221 0 : if (line_info_) {
222 0 : line_info_->print();
223 : }
224 :
225 0 : if (rare_data_) {
226 0 : base::OS::Print(" - deopt_reason: %s\n", rare_data_->deopt_reason_);
227 0 : base::OS::Print(" - bailout_reason: %s\n", rare_data_->bailout_reason_);
228 0 : base::OS::Print(" - deopt_id: %d\n", rare_data_->deopt_id_);
229 :
230 0 : if (!rare_data_->inline_stacks_.empty()) {
231 0 : base::OS::Print(" - inline stacks:\n");
232 0 : for (auto it = rare_data_->inline_stacks_.begin();
233 : it != rare_data_->inline_stacks_.end(); it++) {
234 0 : base::OS::Print(" inlining_id: [%d]\n", it->first);
235 0 : for (const auto& e : it->second) {
236 : base::OS::Print(" %s --> %d\n", e.code_entry->name(),
237 0 : e.line_number);
238 : }
239 : }
240 : } else {
241 0 : base::OS::Print(" - inline stacks: (empty)\n");
242 : }
243 :
244 0 : if (!rare_data_->deopt_inlined_frames_.empty()) {
245 0 : base::OS::Print(" - deopt inlined frames:\n");
246 0 : for (const CpuProfileDeoptFrame& frame :
247 : rare_data_->deopt_inlined_frames_) {
248 : base::OS::Print("script_id: %d position: %zu\n", frame.script_id,
249 0 : frame.position);
250 : }
251 : } else {
252 0 : base::OS::Print(" - deopt inlined frames: (empty)\n");
253 : }
254 : }
255 0 : base::OS::Print("\n");
256 0 : }
257 :
258 0 : void ProfileNode::CollectDeoptInfo(CodeEntry* entry) {
259 0 : deopt_infos_.push_back(entry->GetDeoptInfo());
260 : entry->clear_deopt_info();
261 0 : }
262 :
263 349 : ProfileNode* ProfileNode::FindChild(CodeEntry* entry, int line_number) {
264 698 : auto map_entry = children_.find({entry, line_number});
265 349 : return map_entry != children_.end() ? map_entry->second : nullptr;
266 : }
267 :
268 74965 : ProfileNode* ProfileNode::FindOrAddChild(CodeEntry* entry, int line_number) {
269 149930 : auto map_entry = children_.find({entry, line_number});
270 74965 : if (map_entry == children_.end()) {
271 2514 : ProfileNode* node = new ProfileNode(tree_, entry, this, line_number);
272 5028 : children_[{entry, line_number}] = node;
273 2514 : children_list_.push_back(node);
274 2514 : return node;
275 : } else {
276 72451 : return map_entry->second;
277 : }
278 : }
279 :
280 :
281 25767 : void ProfileNode::IncrementLineTicks(int src_line) {
282 51534 : if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) return;
283 : // Increment a hit counter of a certain source line.
284 : // Add a new source line if not found.
285 : auto map_entry = line_ticks_.find(src_line);
286 25767 : if (map_entry == line_ticks_.end()) {
287 230 : line_ticks_[src_line] = 1;
288 : } else {
289 25537 : line_ticks_[src_line]++;
290 : }
291 : }
292 :
293 :
294 13 : bool ProfileNode::GetLineTicks(v8::CpuProfileNode::LineTick* entries,
295 : unsigned int length) const {
296 13 : if (entries == nullptr || length == 0) return false;
297 :
298 13 : unsigned line_count = static_cast<unsigned>(line_ticks_.size());
299 :
300 13 : if (line_count == 0) return true;
301 13 : if (length < line_count) return false;
302 :
303 : v8::CpuProfileNode::LineTick* entry = entries;
304 :
305 35 : for (auto p = line_ticks_.begin(); p != line_ticks_.end(); p++, entry++) {
306 22 : entry->line = p->first;
307 22 : entry->hit_count = p->second;
308 : }
309 :
310 : return true;
311 : }
312 :
313 :
314 2796 : void ProfileNode::Print(int indent) {
315 4223 : int line_number = line_number_ != 0 ? line_number_ : entry_->line_number();
316 : base::OS::Print("%5u %*s %s:%d %d #%d", self_ticks_, indent, "",
317 2796 : entry_->name(), line_number, entry_->script_id(), id());
318 2796 : if (entry_->resource_name()[0] != '\0')
319 29 : base::OS::Print(" %s:%d", entry_->resource_name(), entry_->line_number());
320 1398 : base::OS::Print("\n");
321 2796 : for (size_t i = 0; i < deopt_infos_.size(); ++i) {
322 1398 : CpuProfileDeoptInfo& info = deopt_infos_[i];
323 : base::OS::Print("%*s;;; deopted at script_id: %d position: %" PRIuS
324 : " with reason '%s'.\n",
325 : indent + 10, "", info.stack[0].script_id,
326 0 : info.stack[0].position, info.deopt_reason);
327 0 : for (size_t index = 1; index < info.stack.size(); ++index) {
328 : base::OS::Print("%*s;;; Inline point: script_id %d position: %" PRIuS
329 : ".\n",
330 : indent + 10, "", info.stack[index].script_id,
331 0 : info.stack[index].position);
332 : }
333 : }
334 1398 : const char* bailout_reason = entry_->bailout_reason();
335 1398 : if (bailout_reason != GetBailoutReason(BailoutReason::kNoReason) &&
336 : bailout_reason != CodeEntry::kEmptyBailoutReason) {
337 : base::OS::Print("%*s bailed out due to '%s'\n", indent + 10, "",
338 88 : bailout_reason);
339 : }
340 3621 : for (auto child : children_) {
341 825 : child.second->Print(indent + 2);
342 : }
343 1398 : }
344 :
345 :
346 : class DeleteNodesCallback {
347 : public:
348 : void BeforeTraversingChild(ProfileNode*, ProfileNode*) { }
349 :
350 3856 : void AfterAllChildrenTraversed(ProfileNode* node) {
351 3856 : delete node;
352 3856 : }
353 :
354 : void AfterChildTraversed(ProfileNode*, ProfileNode*) { }
355 : };
356 :
357 1342 : ProfileTree::ProfileTree(Isolate* isolate)
358 : : root_entry_(CodeEventListener::FUNCTION_TAG, "(root)"),
359 : next_node_id_(1),
360 1342 : root_(new ProfileNode(this, &root_entry_, nullptr)),
361 : isolate_(isolate),
362 4026 : next_function_id_(1) {}
363 :
364 2684 : ProfileTree::~ProfileTree() {
365 : DeleteNodesCallback cb;
366 1342 : TraverseDepthFirst(&cb);
367 1342 : }
368 :
369 :
370 0 : unsigned ProfileTree::GetFunctionId(const ProfileNode* node) {
371 0 : CodeEntry* code_entry = node->entry();
372 : auto map_entry = function_ids_.find(code_entry);
373 0 : if (map_entry == function_ids_.end()) {
374 0 : return function_ids_[code_entry] = next_function_id_++;
375 : }
376 0 : return function_ids_[code_entry];
377 : }
378 :
379 95 : ProfileNode* ProfileTree::AddPathFromEnd(const std::vector<CodeEntry*>& path,
380 : int src_line, bool update_stats) {
381 95 : ProfileNode* node = root_;
382 : CodeEntry* last_entry = nullptr;
383 290 : for (auto it = path.rbegin(); it != path.rend(); ++it) {
384 195 : if (*it == nullptr) continue;
385 : last_entry = *it;
386 145 : node = node->FindOrAddChild(*it, v8::CpuProfileNode::kNoLineNumberInfo);
387 : }
388 190 : if (last_entry && last_entry->has_deopt_info()) {
389 0 : node->CollectDeoptInfo(last_entry);
390 : }
391 95 : if (update_stats) {
392 : node->IncrementSelfTicks();
393 95 : if (src_line != v8::CpuProfileNode::kNoLineNumberInfo) {
394 0 : node->IncrementLineTicks(src_line);
395 : }
396 : }
397 95 : return node;
398 : }
399 :
400 28014 : ProfileNode* ProfileTree::AddPathFromEnd(const ProfileStackTrace& path,
401 : int src_line, bool update_stats,
402 : ProfilingMode mode) {
403 28014 : ProfileNode* node = root_;
404 : CodeEntry* last_entry = nullptr;
405 : int parent_line_number = v8::CpuProfileNode::kNoLineNumberInfo;
406 115917 : for (auto it = path.rbegin(); it != path.rend(); ++it) {
407 87903 : if ((*it).code_entry == nullptr) continue;
408 : last_entry = (*it).code_entry;
409 74735 : node = node->FindOrAddChild((*it).code_entry, parent_line_number);
410 : parent_line_number = mode == ProfilingMode::kCallerLineNumbers
411 : ? (*it).line_number
412 74735 : : v8::CpuProfileNode::kNoLineNumberInfo;
413 : }
414 55956 : if (last_entry && last_entry->has_deopt_info()) {
415 0 : node->CollectDeoptInfo(last_entry);
416 : }
417 28014 : if (update_stats) {
418 : node->IncrementSelfTicks();
419 27225 : if (src_line != v8::CpuProfileNode::kNoLineNumberInfo) {
420 25677 : node->IncrementLineTicks(src_line);
421 : }
422 : }
423 28014 : return node;
424 : }
425 :
426 :
427 : class Position {
428 : public:
429 : explicit Position(ProfileNode* node)
430 3856 : : node(node), child_idx_(0) { }
431 : V8_INLINE ProfileNode* current_child() {
432 5028 : return node->children()->at(child_idx_);
433 : }
434 : V8_INLINE bool has_current_child() {
435 6370 : return child_idx_ < static_cast<int>(node->children()->size());
436 : }
437 2514 : V8_INLINE void next_child() { ++child_idx_; }
438 :
439 : ProfileNode* node;
440 : private:
441 : int child_idx_;
442 : };
443 :
444 :
445 : // Non-recursive implementation of a depth-first post-order tree traversal.
446 : template <typename Callback>
447 1342 : void ProfileTree::TraverseDepthFirst(Callback* callback) {
448 : std::vector<Position> stack;
449 1342 : stack.emplace_back(root_);
450 16766 : while (stack.size() > 0) {
451 11398 : Position& current = stack.back();
452 6370 : if (current.has_current_child()) {
453 : callback->BeforeTraversingChild(current.node, current.current_child());
454 2514 : stack.emplace_back(current.current_child());
455 : } else {
456 3856 : callback->AfterAllChildrenTraversed(current.node);
457 7712 : if (stack.size() > 1) {
458 2514 : Position& parent = stack[stack.size() - 2];
459 : callback->AfterChildTraversed(parent.node, current.node);
460 : parent.next_child();
461 : }
462 : // Remove child from the stack.
463 : stack.pop_back();
464 : }
465 : }
466 1342 : }
467 :
468 : using v8::tracing::TracedValue;
469 :
470 : std::atomic<uint32_t> CpuProfile::last_id_;
471 :
472 2594 : CpuProfile::CpuProfile(CpuProfiler* profiler, const char* title,
473 : bool record_samples, ProfilingMode mode)
474 : : title_(title),
475 : record_samples_(record_samples),
476 : mode_(mode),
477 : start_time_(base::TimeTicks::HighResolutionNow()),
478 : top_down_(profiler->isolate()),
479 : profiler_(profiler),
480 : streaming_next_sample_(0),
481 3891 : id_(++last_id_) {
482 1297 : auto value = TracedValue::Create();
483 : value->SetDouble("startTime",
484 2594 : (start_time_ - base::TimeTicks()).InMicroseconds());
485 2594 : TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"),
486 : "Profile", id_, "data", std::move(value));
487 1297 : }
488 :
489 28009 : void CpuProfile::AddPath(base::TimeTicks timestamp,
490 : const ProfileStackTrace& path, int src_line,
491 : bool update_stats) {
492 : ProfileNode* top_frame_node =
493 28009 : top_down_.AddPathFromEnd(path, src_line, update_stats, mode_);
494 :
495 28662 : if (record_samples_ && !timestamp.IsNull()) {
496 1246 : samples_.push_back({top_frame_node, timestamp, src_line});
497 : }
498 :
499 : const int kSamplesFlushCount = 100;
500 : const int kNodesFlushCount = 10;
501 56013 : if (samples_.size() - streaming_next_sample_ >= kSamplesFlushCount ||
502 : top_down_.pending_nodes_count() >= kNodesFlushCount) {
503 16 : StreamPendingTraceEvents();
504 : }
505 28009 : }
506 :
507 : namespace {
508 :
509 9291 : void BuildNodeValue(const ProfileNode* node, TracedValue* value) {
510 15485 : const CodeEntry* entry = node->entry();
511 3097 : value->BeginDictionary("callFrame");
512 3097 : value->SetString("functionName", entry->name());
513 3097 : if (*entry->resource_name()) {
514 38 : value->SetString("url", entry->resource_name());
515 : }
516 3097 : value->SetInteger("scriptId", entry->script_id());
517 3097 : if (entry->line_number()) {
518 259 : value->SetInteger("lineNumber", entry->line_number() - 1);
519 : }
520 3097 : if (entry->column_number()) {
521 259 : value->SetInteger("columnNumber", entry->column_number() - 1);
522 : }
523 3097 : value->EndDictionary();
524 3097 : value->SetInteger("id", node->id());
525 3097 : if (node->parent()) {
526 2340 : value->SetInteger("parent", node->parent()->id());
527 : }
528 : const char* deopt_reason = entry->bailout_reason();
529 3097 : if (deopt_reason && deopt_reason[0] && strcmp(deopt_reason, "no reason")) {
530 89 : value->SetString("deoptReason", deopt_reason);
531 : }
532 3097 : }
533 :
534 : } // namespace
535 :
536 773 : void CpuProfile::StreamPendingTraceEvents() {
537 : std::vector<const ProfileNode*> pending_nodes = top_down_.TakePendingNodes();
538 1557 : if (pending_nodes.empty() && samples_.empty()) return;
539 767 : auto value = TracedValue::Create();
540 :
541 772 : if (!pending_nodes.empty() || streaming_next_sample_ != samples_.size()) {
542 766 : value->BeginDictionary("cpuProfile");
543 766 : if (!pending_nodes.empty()) {
544 762 : value->BeginArray("nodes");
545 4621 : for (auto node : pending_nodes) {
546 3097 : value->BeginDictionary();
547 3097 : BuildNodeValue(node, value.get());
548 3097 : value->EndDictionary();
549 : }
550 762 : value->EndArray();
551 : }
552 1532 : if (streaming_next_sample_ != samples_.size()) {
553 49 : value->BeginArray("samples");
554 1344 : for (size_t i = streaming_next_sample_; i < samples_.size(); ++i) {
555 1246 : value->AppendInteger(samples_[i].node->id());
556 : }
557 49 : value->EndArray();
558 : }
559 766 : value->EndDictionary();
560 : }
561 1534 : if (streaming_next_sample_ != samples_.size()) {
562 49 : value->BeginArray("timeDeltas");
563 : base::TimeTicks lastTimestamp =
564 4 : streaming_next_sample_ ? samples_[streaming_next_sample_ - 1].timestamp
565 53 : : start_time();
566 1344 : for (size_t i = streaming_next_sample_; i < samples_.size(); ++i) {
567 : value->AppendInteger(static_cast<int>(
568 1246 : (samples_[i].timestamp - lastTimestamp).InMicroseconds()));
569 623 : lastTimestamp = samples_[i].timestamp;
570 : }
571 49 : value->EndArray();
572 : bool has_non_zero_lines =
573 147 : std::any_of(samples_.begin() + streaming_next_sample_, samples_.end(),
574 98 : [](const SampleInfo& sample) { return sample.line != 0; });
575 49 : if (has_non_zero_lines) {
576 41 : value->BeginArray("lines");
577 1292 : for (size_t i = streaming_next_sample_; i < samples_.size(); ++i) {
578 1210 : value->AppendInteger(samples_[i].line);
579 : }
580 41 : value->EndArray();
581 : }
582 49 : streaming_next_sample_ = samples_.size();
583 : }
584 :
585 1534 : TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"),
586 : "ProfileChunk", id_, "data", std::move(value));
587 : }
588 :
589 757 : void CpuProfile::FinishProfile() {
590 757 : end_time_ = base::TimeTicks::HighResolutionNow();
591 757 : StreamPendingTraceEvents();
592 757 : auto value = TracedValue::Create();
593 1514 : value->SetDouble("endTime", (end_time_ - base::TimeTicks()).InMicroseconds());
594 1514 : TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"),
595 : "ProfileChunk", id_, "data", std::move(value));
596 757 : }
597 :
598 550 : void CpuProfile::Print() {
599 550 : base::OS::Print("[Top down]:\n");
600 550 : top_down_.Print();
601 550 : }
602 :
603 : CodeMap::CodeMap() = default;
604 :
605 767 : CodeMap::~CodeMap() {
606 : // First clean the free list as it's otherwise impossible to tell
607 : // the slot type.
608 767 : unsigned free_slot = free_list_head_;
609 1539 : while (free_slot != kNoFreeSlot) {
610 5 : unsigned next_slot = code_entries_[free_slot].next_free_slot;
611 5 : code_entries_[free_slot].entry = nullptr;
612 : free_slot = next_slot;
613 : }
614 2946487 : for (auto slot : code_entries_) delete slot.entry;
615 767 : }
616 :
617 1479655 : void CodeMap::AddCode(Address addr, CodeEntry* entry, unsigned size) {
618 1479655 : ClearCodesInRange(addr, addr + size);
619 1479655 : unsigned index = AddCodeEntry(addr, entry);
620 2959305 : code_map_.emplace(addr, CodeEntryMapInfo{index, size});
621 : DCHECK(entry->instruction_start() == kNullAddress ||
622 : addr == entry->instruction_start());
623 1479654 : }
624 :
625 1479666 : void CodeMap::ClearCodesInRange(Address start, Address end) {
626 : auto left = code_map_.upper_bound(start);
627 1479666 : if (left != code_map_.begin()) {
628 : --left;
629 1475347 : if (left->first + left->second.size <= start) ++left;
630 : }
631 : auto right = left;
632 2966126 : for (; right != code_map_.end() && right->first < end; ++right) {
633 20385 : if (!entry(right->second.index)->used()) {
634 6786 : DeleteCodeEntry(right->second.index);
635 : }
636 : }
637 : code_map_.erase(left, right);
638 1479665 : }
639 :
640 1186151 : CodeEntry* CodeMap::FindEntry(Address addr) {
641 : auto it = code_map_.upper_bound(addr);
642 1186151 : if (it == code_map_.begin()) return nullptr;
643 : --it;
644 1181618 : Address start_address = it->first;
645 1181618 : Address end_address = start_address + it->second.size;
646 1181618 : CodeEntry* ret = addr < end_address ? entry(it->second.index) : nullptr;
647 : if (ret && ret->instruction_start() != kNullAddress) {
648 : DCHECK_EQ(start_address, ret->instruction_start());
649 : DCHECK(addr >= start_address && addr < end_address);
650 : }
651 1181618 : return ret;
652 : }
653 :
654 10 : void CodeMap::MoveCode(Address from, Address to) {
655 10 : if (from == to) return;
656 : auto it = code_map_.find(from);
657 10 : if (it == code_map_.end()) return;
658 10 : CodeEntryMapInfo info = it->second;
659 : code_map_.erase(it);
660 : DCHECK(from + info.size <= to || to + info.size <= from);
661 10 : ClearCodesInRange(to, to + info.size);
662 : code_map_.emplace(to, info);
663 :
664 20 : CodeEntry* entry = code_entries_[info.index].entry;
665 10 : entry->set_instruction_start(to);
666 : }
667 :
668 1479655 : unsigned CodeMap::AddCodeEntry(Address start, CodeEntry* entry) {
669 1479655 : if (free_list_head_ == kNoFreeSlot) {
670 2945748 : code_entries_.push_back(CodeEntrySlotInfo{entry});
671 1472874 : return static_cast<unsigned>(code_entries_.size()) - 1;
672 : }
673 : unsigned index = free_list_head_;
674 6781 : free_list_head_ = code_entries_[index].next_free_slot;
675 6781 : code_entries_[index].entry = entry;
676 6781 : return index;
677 : }
678 :
679 6786 : void CodeMap::DeleteCodeEntry(unsigned index) {
680 6786 : delete code_entries_[index].entry;
681 6785 : code_entries_[index].next_free_slot = free_list_head_;
682 6785 : free_list_head_ = index;
683 6785 : }
684 :
685 0 : void CodeMap::Print() {
686 0 : for (const auto& pair : code_map_) {
687 : base::OS::Print("%p %5d %s\n", reinterpret_cast<void*>(pair.first),
688 0 : pair.second.size, entry(pair.second.index)->name());
689 : }
690 0 : }
691 :
692 927 : CpuProfilesCollection::CpuProfilesCollection(Isolate* isolate)
693 2781 : : profiler_(nullptr), current_profiles_semaphore_(1) {}
694 :
695 1312 : bool CpuProfilesCollection::StartProfiling(const char* title,
696 : bool record_samples,
697 : ProfilingMode mode) {
698 1312 : current_profiles_semaphore_.Wait();
699 2624 : if (static_cast<int>(current_profiles_.size()) >= kMaxSimultaneousProfiles) {
700 5 : current_profiles_semaphore_.Signal();
701 5 : return false;
702 : }
703 26092 : for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) {
704 24795 : if (strcmp(profile->title(), title) == 0) {
705 : // Ignore attempts to start profile with the same title...
706 10 : current_profiles_semaphore_.Signal();
707 : // ... though return true to force it collect a sample.
708 : return true;
709 : }
710 : }
711 : current_profiles_.emplace_back(
712 1297 : new CpuProfile(profiler_, title, record_samples, mode));
713 1297 : current_profiles_semaphore_.Signal();
714 1297 : return true;
715 : }
716 :
717 :
718 762 : CpuProfile* CpuProfilesCollection::StopProfiling(const char* title) {
719 1524 : const int title_len = StrLength(title);
720 : CpuProfile* profile = nullptr;
721 762 : current_profiles_semaphore_.Wait();
722 :
723 : auto it =
724 : std::find_if(current_profiles_.rbegin(), current_profiles_.rend(),
725 767 : [&](const std::unique_ptr<CpuProfile>& p) {
726 1472 : return title_len == 0 || strcmp(p->title(), title) == 0;
727 767 : });
728 :
729 762 : if (it != current_profiles_.rend()) {
730 757 : (*it)->FinishProfile();
731 : profile = it->get();
732 757 : finished_profiles_.push_back(std::move(*it));
733 : // Convert reverse iterator to matching forward iterator.
734 757 : current_profiles_.erase(--(it.base()));
735 : }
736 :
737 762 : current_profiles_semaphore_.Signal();
738 762 : return profile;
739 : }
740 :
741 :
742 728 : bool CpuProfilesCollection::IsLastProfile(const char* title) {
743 : // Called from VM thread, and only it can mutate the list,
744 : // so no locking is needed here.
745 1456 : if (current_profiles_.size() != 1) return false;
746 : return StrLength(title) == 0
747 1368 : || strcmp(current_profiles_[0]->title(), title) == 0;
748 : }
749 :
750 :
751 630 : void CpuProfilesCollection::RemoveProfile(CpuProfile* profile) {
752 : // Called from VM thread for a completed profile.
753 : auto pos =
754 : std::find_if(finished_profiles_.begin(), finished_profiles_.end(),
755 : [&](const std::unique_ptr<CpuProfile>& finished_profile) {
756 630 : return finished_profile.get() == profile;
757 630 : });
758 : DCHECK(pos != finished_profiles_.end());
759 630 : finished_profiles_.erase(pos);
760 630 : }
761 :
762 27999 : void CpuProfilesCollection::AddPathToCurrentProfiles(
763 : base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line,
764 : bool update_stats) {
765 : // As starting / stopping profiles is rare relatively to this
766 : // method, we don't bother minimizing the duration of lock holding,
767 : // e.g. copying contents of the list to a local vector.
768 27999 : current_profiles_semaphore_.Wait();
769 84007 : for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) {
770 56018 : profile->AddPath(timestamp, path, src_line, update_stats);
771 : }
772 27999 : current_profiles_semaphore_.Signal();
773 27999 : }
774 :
775 762 : ProfileGenerator::ProfileGenerator(CpuProfilesCollection* profiles)
776 1524 : : profiles_(profiles) {}
777 :
778 27999 : void ProfileGenerator::RecordTickSample(const TickSample& sample) {
779 : ProfileStackTrace stack_trace;
780 : // Conservatively reserve space for stack frames + pc + function + vm-state.
781 : // There could in fact be more of them because of inlined entries.
782 27999 : stack_trace.reserve(sample.frames_count + 3);
783 :
784 : // The ProfileNode knows nothing about all versions of generated code for
785 : // the same JS function. The line number information associated with
786 : // the latest version of generated code is used to find a source line number
787 : // for a JS function. Then, the detected source line is passed to
788 : // ProfileNode to increase the tick count for this source line.
789 : const int no_line_info = v8::CpuProfileNode::kNoLineNumberInfo;
790 : int src_line = no_line_info;
791 : bool src_line_not_found = true;
792 :
793 27999 : if (sample.pc != nullptr) {
794 27269 : if (sample.has_external_callback && sample.state == EXTERNAL) {
795 : // Don't use PC when in external callback code, as it can point
796 : // inside a callback's code, and we will erroneously report
797 : // that a callback calls itself.
798 : stack_trace.push_back(
799 12812 : {FindEntry(reinterpret_cast<Address>(sample.external_callback_entry)),
800 25624 : no_line_info});
801 : } else {
802 14457 : Address attributed_pc = reinterpret_cast<Address>(sample.pc);
803 15865 : CodeEntry* pc_entry = FindEntry(attributed_pc);
804 : // If there is no pc_entry, we're likely in native code. Find out if the
805 : // top of the stack (the return address) was pointing inside a JS
806 : // function, meaning that we have encountered a frameless invocation.
807 14457 : if (!pc_entry && !sample.has_external_callback) {
808 13981 : attributed_pc = reinterpret_cast<Address>(sample.tos);
809 13981 : pc_entry = FindEntry(attributed_pc);
810 : }
811 : // If pc is in the function code before it set up stack frame or after the
812 : // frame was destroyed, SafeStackFrameIterator incorrectly thinks that
813 : // ebp contains the return address of the current function and skips the
814 : // caller's frame. Check for this case and just skip such samples.
815 14457 : if (pc_entry) {
816 : int pc_offset =
817 1352 : static_cast<int>(attributed_pc - pc_entry->instruction_start());
818 : // TODO(petermarshall): pc_offset can still be negative in some cases.
819 : src_line = pc_entry->GetSourceLine(pc_offset);
820 676 : if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) {
821 : src_line = pc_entry->line_number();
822 : }
823 : src_line_not_found = false;
824 1352 : stack_trace.push_back({pc_entry, src_line});
825 :
826 676 : if (pc_entry->builtin_id() == Builtins::kFunctionPrototypeApply ||
827 : pc_entry->builtin_id() == Builtins::kFunctionPrototypeCall) {
828 : // When current function is either the Function.prototype.apply or the
829 : // Function.prototype.call builtin the top frame is either frame of
830 : // the calling JS function or internal frame.
831 : // In the latter case we know the caller for sure but in the
832 : // former case we don't so we simply replace the frame with
833 : // 'unresolved' entry.
834 0 : if (!sample.has_external_callback) {
835 : stack_trace.push_back(
836 0 : {CodeEntry::unresolved_entry(), no_line_info});
837 : }
838 : }
839 : }
840 : }
841 :
842 64148 : for (unsigned i = 0; i < sample.frames_count; ++i) {
843 64148 : Address stack_pos = reinterpret_cast<Address>(sample.stack[i]);
844 115129 : CodeEntry* entry = FindEntry(stack_pos);
845 : int line_number = no_line_info;
846 64148 : if (entry) {
847 : // Find out if the entry has an inlining stack associated.
848 : int pc_offset =
849 101962 : static_cast<int>(stack_pos - entry->instruction_start());
850 : // TODO(petermarshall): pc_offset can still be negative in some cases.
851 5424 : const std::vector<CodeEntryAndLineNumber>* inline_stack =
852 50981 : entry->GetInlineStack(pc_offset);
853 50981 : if (inline_stack) {
854 : int most_inlined_frame_line_number = entry->GetSourceLine(pc_offset);
855 : stack_trace.insert(stack_trace.end(), inline_stack->begin(),
856 2712 : inline_stack->end());
857 : // This is a bit of a messy hack. The line number for the most-inlined
858 : // frame (the function at the end of the chain of function calls) has
859 : // the wrong line number in inline_stack. The actual line number in
860 : // this function is stored in the SourcePositionTable in entry. We fix
861 : // up the line number for the most-inlined frame here.
862 : // TODO(petermarshall): Remove this and use a tree with a node per
863 : // inlining_id.
864 : DCHECK(!inline_stack->empty());
865 5424 : size_t index = stack_trace.size() - inline_stack->size();
866 2712 : stack_trace[index].line_number = most_inlined_frame_line_number;
867 : }
868 : // Skip unresolved frames (e.g. internal frame) and get source line of
869 : // the first JS caller.
870 50981 : if (src_line_not_found) {
871 : src_line = entry->GetSourceLine(pc_offset);
872 25130 : if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) {
873 : src_line = entry->line_number();
874 : }
875 : src_line_not_found = false;
876 : }
877 : line_number = entry->GetSourceLine(pc_offset);
878 :
879 : // The inline stack contains the top-level function i.e. the same
880 : // function as entry. We don't want to add it twice. The one from the
881 : // inline stack has the correct line number for this particular inlining
882 : // so we use it instead of pushing entry to stack_trace.
883 50981 : if (inline_stack) continue;
884 : }
885 122872 : stack_trace.push_back({entry, line_number});
886 : }
887 : }
888 :
889 27999 : if (FLAG_prof_browser_mode) {
890 : bool no_symbolized_entries = true;
891 56088 : for (auto e : stack_trace) {
892 26070 : if (e.code_entry != nullptr) {
893 : no_symbolized_entries = false;
894 : break;
895 : }
896 : }
897 : // If no frames were symbolized, put the VM state entry in.
898 27884 : if (no_symbolized_entries) {
899 4268 : stack_trace.push_back({EntryForVMState(sample.state), no_line_info});
900 : }
901 : }
902 :
903 : profiles_->AddPathToCurrentProfiles(sample.timestamp, stack_trace, src_line,
904 27999 : sample.update_stats);
905 27999 : }
906 :
907 2134 : CodeEntry* ProfileGenerator::EntryForVMState(StateTag tag) {
908 2134 : switch (tag) {
909 : case GC:
910 682 : return CodeEntry::gc_entry();
911 : case JS:
912 : case PARSER:
913 : case COMPILER:
914 : case BYTECODE_COMPILER:
915 : // DOM events handlers are reported as OTHER / EXTERNAL entries.
916 : // To avoid confusing people, let's put all these entries into
917 : // one bucket.
918 : case OTHER:
919 : case EXTERNAL:
920 1437 : return CodeEntry::program_entry();
921 : case IDLE:
922 15 : return CodeEntry::idle_entry();
923 : }
924 0 : UNREACHABLE();
925 : }
926 :
927 : } // namespace internal
928 178779 : } // namespace v8
|