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