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