1 // Copyright 2016 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/profiler-listener.h"
6
7 #include <algorithm>
8
9 #include "src/base/vector.h"
10 #include "src/codegen/reloc-info.h"
11 #include "src/codegen/source-position-table.h"
12 #include "src/deoptimizer/deoptimizer.h"
13 #include "src/handles/handles-inl.h"
14 #include "src/objects/code-inl.h"
15 #include "src/objects/code.h"
16 #include "src/objects/objects-inl.h"
17 #include "src/objects/script-inl.h"
18 #include "src/objects/shared-function-info-inl.h"
19 #include "src/objects/string-inl.h"
20 #include "src/profiler/cpu-profiler.h"
21 #include "src/profiler/profile-generator-inl.h"
22
23 #if V8_ENABLE_WEBASSEMBLY
24 #include "src/wasm/wasm-code-manager.h"
25 #endif // V8_ENABLE_WEBASSEMBLY
26
27 namespace v8 {
28 namespace internal {
29
ProfilerListener(Isolate * isolate,CodeEventObserver * observer,CodeEntryStorage & code_entry_storage,WeakCodeRegistry & weak_code_registry,CpuProfilingNamingMode naming_mode)30 ProfilerListener::ProfilerListener(Isolate* isolate,
31 CodeEventObserver* observer,
32 CodeEntryStorage& code_entry_storage,
33 WeakCodeRegistry& weak_code_registry,
34 CpuProfilingNamingMode naming_mode)
35 : isolate_(isolate),
36 observer_(observer),
37 code_entries_(code_entry_storage),
38 weak_code_registry_(weak_code_registry),
39 naming_mode_(naming_mode) {}
40
41 ProfilerListener::~ProfilerListener() = default;
42
CodeCreateEvent(LogEventsAndTags tag,Handle<AbstractCode> code,const char * name)43 void ProfilerListener::CodeCreateEvent(LogEventsAndTags tag,
44 Handle<AbstractCode> code,
45 const char* name) {
46 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeCreation);
47 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
48 rec->instruction_start = code->InstructionStart();
49 rec->entry =
50 code_entries_.Create(tag, GetName(name), CodeEntry::kEmptyResourceName,
51 CpuProfileNode::kNoLineNumberInfo,
52 CpuProfileNode::kNoColumnNumberInfo, nullptr);
53 rec->instruction_size = code->InstructionSize();
54 weak_code_registry_.Track(rec->entry, code);
55 DispatchCodeEvent(evt_rec);
56 }
57
CodeCreateEvent(LogEventsAndTags tag,Handle<AbstractCode> code,Handle<Name> name)58 void ProfilerListener::CodeCreateEvent(LogEventsAndTags tag,
59 Handle<AbstractCode> code,
60 Handle<Name> name) {
61 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeCreation);
62 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
63 rec->instruction_start = code->InstructionStart();
64 rec->entry =
65 code_entries_.Create(tag, GetName(*name), CodeEntry::kEmptyResourceName,
66 CpuProfileNode::kNoLineNumberInfo,
67 CpuProfileNode::kNoColumnNumberInfo, nullptr);
68 rec->instruction_size = code->InstructionSize();
69 weak_code_registry_.Track(rec->entry, code);
70 DispatchCodeEvent(evt_rec);
71 }
72
CodeCreateEvent(LogEventsAndTags tag,Handle<AbstractCode> code,Handle<SharedFunctionInfo> shared,Handle<Name> script_name)73 void ProfilerListener::CodeCreateEvent(LogEventsAndTags tag,
74 Handle<AbstractCode> code,
75 Handle<SharedFunctionInfo> shared,
76 Handle<Name> script_name) {
77 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeCreation);
78 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
79 rec->instruction_start = code->InstructionStart();
80 rec->entry =
81 code_entries_.Create(tag, GetName(shared->DebugNameCStr().get()),
82 GetName(InferScriptName(*script_name, *shared)),
83 CpuProfileNode::kNoLineNumberInfo,
84 CpuProfileNode::kNoColumnNumberInfo, nullptr);
85 DCHECK_IMPLIES(code->IsCode(), code->kind() == CodeKind::BASELINE);
86 rec->entry->FillFunctionInfo(*shared);
87 rec->instruction_size = code->InstructionSize();
88 weak_code_registry_.Track(rec->entry, code);
89 DispatchCodeEvent(evt_rec);
90 }
91
92 namespace {
93
GetOrInsertCachedEntry(std::unordered_set<CodeEntry *,CodeEntry::Hasher,CodeEntry::Equals> * entries,CodeEntry * search_value,CodeEntryStorage & storage)94 CodeEntry* GetOrInsertCachedEntry(
95 std::unordered_set<CodeEntry*, CodeEntry::Hasher, CodeEntry::Equals>*
96 entries,
97 CodeEntry* search_value, CodeEntryStorage& storage) {
98 auto it = entries->find(search_value);
99 if (it != entries->end()) {
100 storage.DecRef(search_value);
101 return *it;
102 }
103 entries->insert(search_value);
104 return search_value;
105 }
106
107 } // namespace
108
CodeCreateEvent(LogEventsAndTags tag,Handle<AbstractCode> abstract_code,Handle<SharedFunctionInfo> shared,Handle<Name> script_name,int line,int column)109 void ProfilerListener::CodeCreateEvent(LogEventsAndTags tag,
110 Handle<AbstractCode> abstract_code,
111 Handle<SharedFunctionInfo> shared,
112 Handle<Name> script_name, int line,
113 int column) {
114 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeCreation);
115 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
116 rec->instruction_start = abstract_code->InstructionStart();
117 std::unique_ptr<SourcePositionTable> line_table;
118 std::unordered_map<int, std::vector<CodeEntryAndLineNumber>> inline_stacks;
119 std::unordered_set<CodeEntry*, CodeEntry::Hasher, CodeEntry::Equals>
120 cached_inline_entries;
121 bool is_shared_cross_origin = false;
122 if (shared->script().IsScript()) {
123 Handle<Script> script = handle(Script::cast(shared->script()), isolate_);
124 line_table.reset(new SourcePositionTable());
125
126 is_shared_cross_origin = script->origin_options().IsSharedCrossOrigin();
127
128 bool is_baseline = abstract_code->kind() == CodeKind::BASELINE;
129 Handle<ByteArray> source_position_table(
130 abstract_code->SourcePositionTable(*shared), isolate_);
131 std::unique_ptr<baseline::BytecodeOffsetIterator> baseline_iterator =
132 nullptr;
133 if (is_baseline) {
134 Handle<BytecodeArray> bytecodes(shared->GetBytecodeArray(isolate_),
135 isolate_);
136 Handle<ByteArray> bytecode_offsets(
137 abstract_code->GetCode().bytecode_offset_table(), isolate_);
138 baseline_iterator = std::make_unique<baseline::BytecodeOffsetIterator>(
139 bytecode_offsets, bytecodes);
140 }
141 // Add each position to the source position table and store inlining stacks
142 // for inline positions. We store almost the same information in the
143 // profiler as is stored on the code object, except that we transform source
144 // positions to line numbers here, because we only care about attributing
145 // ticks to a given line.
146 for (SourcePositionTableIterator it(source_position_table); !it.done();
147 it.Advance()) {
148 int position = it.source_position().ScriptOffset();
149 int inlining_id = it.source_position().InliningId();
150 int code_offset = it.code_offset();
151 if (is_baseline) {
152 // Use the bytecode offset to calculate pc offset for baseline code.
153 baseline_iterator->AdvanceToBytecodeOffset(code_offset);
154 code_offset =
155 static_cast<int>(baseline_iterator->current_pc_start_offset());
156 }
157
158 if (inlining_id == SourcePosition::kNotInlined) {
159 int line_number = script->GetLineNumber(position) + 1;
160 line_table->SetPosition(code_offset, line_number, inlining_id);
161 } else {
162 DCHECK(!is_baseline);
163 DCHECK(abstract_code->IsCode());
164 Handle<Code> code = handle(abstract_code->GetCode(), isolate_);
165 std::vector<SourcePositionInfo> stack =
166 it.source_position().InliningStack(code);
167 DCHECK(!stack.empty());
168
169 // When we have an inlining id and we are doing cross-script inlining,
170 // then the script of the inlined frames may be different to the script
171 // of |shared|.
172 int line_number = stack.front().line + 1;
173 line_table->SetPosition(code_offset, line_number, inlining_id);
174
175 std::vector<CodeEntryAndLineNumber> inline_stack;
176 for (SourcePositionInfo& pos_info : stack) {
177 if (pos_info.position.ScriptOffset() == kNoSourcePosition) continue;
178 if (pos_info.script.is_null()) continue;
179
180 line_number =
181 pos_info.script->GetLineNumber(pos_info.position.ScriptOffset()) +
182 1;
183
184 const char* resource_name =
185 (pos_info.script->name().IsName())
186 ? GetName(Name::cast(pos_info.script->name()))
187 : CodeEntry::kEmptyResourceName;
188
189 bool inline_is_shared_cross_origin =
190 pos_info.script->origin_options().IsSharedCrossOrigin();
191
192 // We need the start line number and column number of the function for
193 // kLeafNodeLineNumbers mode. Creating a SourcePositionInfo is a handy
194 // way of getting both easily.
195 SourcePositionInfo start_pos_info(
196 SourcePosition(pos_info.shared->StartPosition()),
197 pos_info.shared);
198
199 CodeEntry* inline_entry = code_entries_.Create(
200 tag, GetFunctionName(*pos_info.shared), resource_name,
201 start_pos_info.line + 1, start_pos_info.column + 1, nullptr,
202 inline_is_shared_cross_origin);
203 inline_entry->FillFunctionInfo(*pos_info.shared);
204
205 // Create a canonical CodeEntry for each inlined frame and then re-use
206 // them for subsequent inline stacks to avoid a lot of duplication.
207 CodeEntry* cached_entry = GetOrInsertCachedEntry(
208 &cached_inline_entries, inline_entry, code_entries_);
209
210 inline_stack.push_back({cached_entry, line_number});
211 }
212 DCHECK(!inline_stack.empty());
213 inline_stacks.emplace(inlining_id, std::move(inline_stack));
214 }
215 }
216 }
217 rec->entry = code_entries_.Create(
218 tag, GetFunctionName(*shared),
219 GetName(InferScriptName(*script_name, *shared)), line, column,
220 std::move(line_table), is_shared_cross_origin);
221 if (!inline_stacks.empty()) {
222 rec->entry->SetInlineStacks(std::move(cached_inline_entries),
223 std::move(inline_stacks));
224 }
225
226 rec->entry->FillFunctionInfo(*shared);
227 rec->instruction_size = abstract_code->InstructionSize();
228 weak_code_registry_.Track(rec->entry, abstract_code);
229 DispatchCodeEvent(evt_rec);
230 }
231
232 #if V8_ENABLE_WEBASSEMBLY
CodeCreateEvent(LogEventsAndTags tag,const wasm::WasmCode * code,wasm::WasmName name,const char * source_url,int code_offset,int script_id)233 void ProfilerListener::CodeCreateEvent(LogEventsAndTags tag,
234 const wasm::WasmCode* code,
235 wasm::WasmName name,
236 const char* source_url, int code_offset,
237 int script_id) {
238 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeCreation);
239 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
240 rec->instruction_start = code->instruction_start();
241 rec->entry = code_entries_.Create(tag, GetName(name), GetName(source_url), 1,
242 code_offset + 1, nullptr, true,
243 CodeEntry::CodeType::WASM);
244 rec->entry->set_script_id(script_id);
245 rec->entry->set_position(code_offset);
246 rec->instruction_size = code->instructions().length();
247 DispatchCodeEvent(evt_rec);
248 }
249 #endif // V8_ENABLE_WEBASSEMBLY
250
CallbackEvent(Handle<Name> name,Address entry_point)251 void ProfilerListener::CallbackEvent(Handle<Name> name, Address entry_point) {
252 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeCreation);
253 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
254 rec->instruction_start = entry_point;
255 rec->entry =
256 code_entries_.Create(CodeEventListener::CALLBACK_TAG, GetName(*name));
257 rec->instruction_size = 1;
258 DispatchCodeEvent(evt_rec);
259 }
260
GetterCallbackEvent(Handle<Name> name,Address entry_point)261 void ProfilerListener::GetterCallbackEvent(Handle<Name> name,
262 Address entry_point) {
263 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeCreation);
264 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
265 rec->instruction_start = entry_point;
266 rec->entry = code_entries_.Create(CodeEventListener::CALLBACK_TAG,
267 GetConsName("get ", *name));
268 rec->instruction_size = 1;
269 DispatchCodeEvent(evt_rec);
270 }
271
SetterCallbackEvent(Handle<Name> name,Address entry_point)272 void ProfilerListener::SetterCallbackEvent(Handle<Name> name,
273 Address entry_point) {
274 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeCreation);
275 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
276 rec->instruction_start = entry_point;
277 rec->entry = code_entries_.Create(CodeEventListener::CALLBACK_TAG,
278 GetConsName("set ", *name));
279 rec->instruction_size = 1;
280 DispatchCodeEvent(evt_rec);
281 }
282
RegExpCodeCreateEvent(Handle<AbstractCode> code,Handle<String> source)283 void ProfilerListener::RegExpCodeCreateEvent(Handle<AbstractCode> code,
284 Handle<String> source) {
285 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeCreation);
286 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
287 rec->instruction_start = code->InstructionStart();
288 rec->entry = code_entries_.Create(
289 CodeEventListener::REG_EXP_TAG, GetConsName("RegExp: ", *source),
290 CodeEntry::kEmptyResourceName, CpuProfileNode::kNoLineNumberInfo,
291 CpuProfileNode::kNoColumnNumberInfo, nullptr);
292 rec->instruction_size = code->InstructionSize();
293 weak_code_registry_.Track(rec->entry, code);
294 DispatchCodeEvent(evt_rec);
295 }
296
CodeMoveEvent(AbstractCode from,AbstractCode to)297 void ProfilerListener::CodeMoveEvent(AbstractCode from, AbstractCode to) {
298 DisallowGarbageCollection no_gc;
299 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeMove);
300 CodeMoveEventRecord* rec = &evt_rec.CodeMoveEventRecord_;
301 rec->from_instruction_start = from.InstructionStart();
302 rec->to_instruction_start = to.InstructionStart();
303 DispatchCodeEvent(evt_rec);
304 }
305
NativeContextMoveEvent(Address from,Address to)306 void ProfilerListener::NativeContextMoveEvent(Address from, Address to) {
307 CodeEventsContainer evt_rec(CodeEventRecord::Type::kNativeContextMove);
308 evt_rec.NativeContextMoveEventRecord_.from_address = from;
309 evt_rec.NativeContextMoveEventRecord_.to_address = to;
310 DispatchCodeEvent(evt_rec);
311 }
312
CodeDisableOptEvent(Handle<AbstractCode> code,Handle<SharedFunctionInfo> shared)313 void ProfilerListener::CodeDisableOptEvent(Handle<AbstractCode> code,
314 Handle<SharedFunctionInfo> shared) {
315 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeDisableOpt);
316 CodeDisableOptEventRecord* rec = &evt_rec.CodeDisableOptEventRecord_;
317 rec->instruction_start = code->InstructionStart();
318 rec->bailout_reason =
319 GetBailoutReason(shared->disabled_optimization_reason());
320 DispatchCodeEvent(evt_rec);
321 }
322
CodeDeoptEvent(Handle<Code> code,DeoptimizeKind kind,Address pc,int fp_to_sp_delta)323 void ProfilerListener::CodeDeoptEvent(Handle<Code> code, DeoptimizeKind kind,
324 Address pc, int fp_to_sp_delta) {
325 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeDeopt);
326 CodeDeoptEventRecord* rec = &evt_rec.CodeDeoptEventRecord_;
327 Deoptimizer::DeoptInfo info = Deoptimizer::GetDeoptInfo(*code, pc);
328 rec->instruction_start = code->InstructionStart();
329 rec->deopt_reason = DeoptimizeReasonToString(info.deopt_reason);
330 rec->deopt_id = info.deopt_id;
331 rec->pc = pc;
332 rec->fp_to_sp_delta = fp_to_sp_delta;
333
334 // When a function is deoptimized, we store the deoptimized frame information
335 // for the use of GetDeoptInfos().
336 AttachDeoptInlinedFrames(code, rec);
337 DispatchCodeEvent(evt_rec);
338 }
339
WeakCodeClearEvent()340 void ProfilerListener::WeakCodeClearEvent() { weak_code_registry_.Sweep(this); }
341
OnHeapObjectDeletion(CodeEntry * entry)342 void ProfilerListener::OnHeapObjectDeletion(CodeEntry* entry) {
343 CodeEventsContainer evt_rec(CodeEventRecord::Type::kCodeDelete);
344 evt_rec.CodeDeleteEventRecord_.entry = entry;
345 DispatchCodeEvent(evt_rec);
346 }
347
CodeSweepEvent()348 void ProfilerListener::CodeSweepEvent() { weak_code_registry_.Sweep(this); }
349
GetName(base::Vector<const char> name)350 const char* ProfilerListener::GetName(base::Vector<const char> name) {
351 // TODO(all): Change {StringsStorage} to accept non-null-terminated strings.
352 base::OwnedVector<char> null_terminated =
353 base::OwnedVector<char>::New(name.size() + 1);
354 std::copy(name.begin(), name.end(), null_terminated.begin());
355 null_terminated[name.size()] = '\0';
356 return GetName(null_terminated.begin());
357 }
358
InferScriptName(Name name,SharedFunctionInfo info)359 Name ProfilerListener::InferScriptName(Name name, SharedFunctionInfo info) {
360 if (name.IsString() && String::cast(name).length()) return name;
361 if (!info.script().IsScript()) return name;
362 Object source_url = Script::cast(info.script()).source_url();
363 return source_url.IsName() ? Name::cast(source_url) : name;
364 }
365
GetFunctionName(SharedFunctionInfo shared)366 const char* ProfilerListener::GetFunctionName(SharedFunctionInfo shared) {
367 switch (naming_mode_) {
368 case kDebugNaming:
369 return GetName(shared.DebugNameCStr().get());
370 case kStandardNaming:
371 return GetName(shared.Name());
372 default:
373 UNREACHABLE();
374 }
375 }
376
AttachDeoptInlinedFrames(Handle<Code> code,CodeDeoptEventRecord * rec)377 void ProfilerListener::AttachDeoptInlinedFrames(Handle<Code> code,
378 CodeDeoptEventRecord* rec) {
379 int deopt_id = rec->deopt_id;
380 SourcePosition last_position = SourcePosition::Unknown();
381 int mask = RelocInfo::ModeMask(RelocInfo::DEOPT_ID) |
382 RelocInfo::ModeMask(RelocInfo::DEOPT_SCRIPT_OFFSET) |
383 RelocInfo::ModeMask(RelocInfo::DEOPT_INLINING_ID);
384
385 rec->deopt_frames = nullptr;
386 rec->deopt_frame_count = 0;
387
388 for (RelocIterator it(*code, mask); !it.done(); it.next()) {
389 RelocInfo* info = it.rinfo();
390 if (info->rmode() == RelocInfo::DEOPT_SCRIPT_OFFSET) {
391 int script_offset = static_cast<int>(info->data());
392 it.next();
393 DCHECK(it.rinfo()->rmode() == RelocInfo::DEOPT_INLINING_ID);
394 int inlining_id = static_cast<int>(it.rinfo()->data());
395 last_position = SourcePosition(script_offset, inlining_id);
396 continue;
397 }
398 if (info->rmode() == RelocInfo::DEOPT_ID) {
399 if (deopt_id != static_cast<int>(info->data())) continue;
400 DCHECK(last_position.IsKnown());
401
402 // SourcePosition::InliningStack allocates a handle for the SFI of each
403 // frame. These don't escape this function, but quickly add up. This
404 // scope limits their lifetime.
405 HandleScope scope(isolate_);
406 std::vector<SourcePositionInfo> stack = last_position.InliningStack(code);
407 CpuProfileDeoptFrame* deopt_frames =
408 new CpuProfileDeoptFrame[stack.size()];
409
410 int deopt_frame_count = 0;
411 for (SourcePositionInfo& pos_info : stack) {
412 if (pos_info.position.ScriptOffset() == kNoSourcePosition) continue;
413 if (pos_info.script.is_null()) continue;
414 int script_id = pos_info.script->id();
415 size_t offset = static_cast<size_t>(pos_info.position.ScriptOffset());
416 deopt_frames[deopt_frame_count++] = {script_id, offset};
417 }
418 rec->deopt_frames = deopt_frames;
419 rec->deopt_frame_count = deopt_frame_count;
420 break;
421 }
422 }
423 }
424
425 } // namespace internal
426 } // namespace v8
427