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(abstract_code->source_position_table());
117 !it.done(); it.Advance()) {
118 int position = it.source_position().ScriptOffset();
119 int inlining_id = it.source_position().InliningId();
120
121 if (inlining_id == SourcePosition::kNotInlined) {
122 int line_number = script->GetLineNumber(position) + 1;
123 line_table->SetPosition(it.code_offset(), line_number, inlining_id);
124 } else {
125 DCHECK(abstract_code->IsCode());
126 Handle<Code> code = handle(abstract_code->GetCode(), isolate_);
127 std::vector<SourcePositionInfo> stack =
128 it.source_position().InliningStack(code);
129 DCHECK(!stack.empty());
130
131 // When we have an inlining id and we are doing cross-script inlining,
132 // then the script of the inlined frames may be different to the script
133 // of |shared|.
134 int line_number = stack.front().line + 1;
135 line_table->SetPosition(it.code_offset(), line_number, inlining_id);
136
137 std::vector<CodeEntryAndLineNumber> inline_stack;
138 for (SourcePositionInfo& pos_info : stack) {
139 if (pos_info.position.ScriptOffset() == kNoSourcePosition) continue;
140 if (pos_info.script.is_null()) continue;
141
142 int line_number =
143 pos_info.script->GetLineNumber(pos_info.position.ScriptOffset()) +
144 1;
145
146 const char* resource_name =
147 (pos_info.script->name().IsName())
148 ? GetName(Name::cast(pos_info.script->name()))
149 : CodeEntry::kEmptyResourceName;
150
151 bool inline_is_shared_cross_origin =
152 pos_info.script->origin_options().IsSharedCrossOrigin();
153
154 // We need the start line number and column number of the function for
155 // kLeafNodeLineNumbers mode. Creating a SourcePositionInfo is a handy
156 // way of getting both easily.
157 SourcePositionInfo start_pos_info(
158 SourcePosition(pos_info.shared->StartPosition()),
159 pos_info.shared);
160
161 std::unique_ptr<CodeEntry> inline_entry = std::make_unique<CodeEntry>(
162 tag, GetFunctionName(*pos_info.shared), resource_name,
163 start_pos_info.line + 1, start_pos_info.column + 1, nullptr,
164 inline_is_shared_cross_origin);
165 inline_entry->FillFunctionInfo(*pos_info.shared);
166
167 // Create a canonical CodeEntry for each inlined frame and then re-use
168 // them for subsequent inline stacks to avoid a lot of duplication.
169 CodeEntry* cached_entry = GetOrInsertCachedEntry(
170 &cached_inline_entries, std::move(inline_entry));
171
172 inline_stack.push_back({cached_entry, line_number});
173 }
174 DCHECK(!inline_stack.empty());
175 inline_stacks.emplace(inlining_id, std::move(inline_stack));
176 }
177 }
178 }
179 rec->entry =
180 new CodeEntry(tag, GetFunctionName(*shared),
181 GetName(InferScriptName(*script_name, *shared)), line,
182 column, std::move(line_table), is_shared_cross_origin);
183 if (!inline_stacks.empty()) {
184 rec->entry->SetInlineStacks(std::move(cached_inline_entries),
185 std::move(inline_stacks));
186 }
187
188 rec->entry->FillFunctionInfo(*shared);
189 rec->instruction_size = abstract_code->InstructionSize();
190 DispatchCodeEvent(evt_rec);
191 }
192
CodeCreateEvent(LogEventsAndTags tag,const wasm::WasmCode * code,wasm::WasmName name)193 void ProfilerListener::CodeCreateEvent(LogEventsAndTags tag,
194 const wasm::WasmCode* code,
195 wasm::WasmName name) {
196 CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION);
197 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
198 rec->instruction_start = code->instruction_start();
199 rec->entry =
200 new CodeEntry(tag, GetName(name), CodeEntry::kWasmResourceNamePrefix,
201 CpuProfileNode::kNoLineNumberInfo,
202 CpuProfileNode::kNoColumnNumberInfo, nullptr, true);
203 rec->instruction_size = code->instructions().length();
204 DispatchCodeEvent(evt_rec);
205 }
206
CallbackEvent(Handle<Name> name,Address entry_point)207 void ProfilerListener::CallbackEvent(Handle<Name> name, Address entry_point) {
208 CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION);
209 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
210 rec->instruction_start = entry_point;
211 rec->entry = new CodeEntry(CodeEventListener::CALLBACK_TAG, GetName(*name));
212 rec->instruction_size = 1;
213 DispatchCodeEvent(evt_rec);
214 }
215
GetterCallbackEvent(Handle<Name> name,Address entry_point)216 void ProfilerListener::GetterCallbackEvent(Handle<Name> name,
217 Address entry_point) {
218 CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION);
219 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
220 rec->instruction_start = entry_point;
221 rec->entry = new CodeEntry(CodeEventListener::CALLBACK_TAG,
222 GetConsName("get ", *name));
223 rec->instruction_size = 1;
224 DispatchCodeEvent(evt_rec);
225 }
226
SetterCallbackEvent(Handle<Name> name,Address entry_point)227 void ProfilerListener::SetterCallbackEvent(Handle<Name> name,
228 Address entry_point) {
229 CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION);
230 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
231 rec->instruction_start = entry_point;
232 rec->entry = new CodeEntry(CodeEventListener::CALLBACK_TAG,
233 GetConsName("set ", *name));
234 rec->instruction_size = 1;
235 DispatchCodeEvent(evt_rec);
236 }
237
RegExpCodeCreateEvent(Handle<AbstractCode> code,Handle<String> source)238 void ProfilerListener::RegExpCodeCreateEvent(Handle<AbstractCode> code,
239 Handle<String> source) {
240 CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION);
241 CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_;
242 rec->instruction_start = code->InstructionStart();
243 rec->entry = new CodeEntry(
244 CodeEventListener::REG_EXP_TAG, GetConsName("RegExp: ", *source),
245 CodeEntry::kEmptyResourceName, CpuProfileNode::kNoLineNumberInfo,
246 CpuProfileNode::kNoColumnNumberInfo, nullptr);
247 rec->instruction_size = code->InstructionSize();
248 DispatchCodeEvent(evt_rec);
249 }
250
CodeMoveEvent(AbstractCode from,AbstractCode to)251 void ProfilerListener::CodeMoveEvent(AbstractCode from, AbstractCode to) {
252 DisallowHeapAllocation no_gc;
253 CodeEventsContainer evt_rec(CodeEventRecord::CODE_MOVE);
254 CodeMoveEventRecord* rec = &evt_rec.CodeMoveEventRecord_;
255 rec->from_instruction_start = from.InstructionStart();
256 rec->to_instruction_start = to.InstructionStart();
257 DispatchCodeEvent(evt_rec);
258 }
259
NativeContextMoveEvent(Address from,Address to)260 void ProfilerListener::NativeContextMoveEvent(Address from, Address to) {
261 CodeEventsContainer evt_rec(CodeEventRecord::NATIVE_CONTEXT_MOVE);
262 evt_rec.NativeContextMoveEventRecord_.from_address = from;
263 evt_rec.NativeContextMoveEventRecord_.to_address = to;
264 DispatchCodeEvent(evt_rec);
265 }
266
CodeDisableOptEvent(Handle<AbstractCode> code,Handle<SharedFunctionInfo> shared)267 void ProfilerListener::CodeDisableOptEvent(Handle<AbstractCode> code,
268 Handle<SharedFunctionInfo> shared) {
269 CodeEventsContainer evt_rec(CodeEventRecord::CODE_DISABLE_OPT);
270 CodeDisableOptEventRecord* rec = &evt_rec.CodeDisableOptEventRecord_;
271 rec->instruction_start = code->InstructionStart();
272 rec->bailout_reason = GetBailoutReason(shared->disable_optimization_reason());
273 DispatchCodeEvent(evt_rec);
274 }
275
CodeDeoptEvent(Handle<Code> code,DeoptimizeKind kind,Address pc,int fp_to_sp_delta)276 void ProfilerListener::CodeDeoptEvent(Handle<Code> code, DeoptimizeKind kind,
277 Address pc, int fp_to_sp_delta) {
278 CodeEventsContainer evt_rec(CodeEventRecord::CODE_DEOPT);
279 CodeDeoptEventRecord* rec = &evt_rec.CodeDeoptEventRecord_;
280 Deoptimizer::DeoptInfo info = Deoptimizer::GetDeoptInfo(*code, pc);
281 rec->instruction_start = code->InstructionStart();
282 rec->deopt_reason = DeoptimizeReasonToString(info.deopt_reason);
283 rec->deopt_id = info.deopt_id;
284 rec->pc = pc;
285 rec->fp_to_sp_delta = fp_to_sp_delta;
286
287 // When a function is deoptimized, we store the deoptimized frame information
288 // for the use of GetDeoptInfos().
289 AttachDeoptInlinedFrames(code, rec);
290 DispatchCodeEvent(evt_rec);
291 }
292
GetName(Vector<const char> name)293 const char* ProfilerListener::GetName(Vector<const char> name) {
294 // TODO(all): Change {StringsStorage} to accept non-null-terminated strings.
295 OwnedVector<char> null_terminated = OwnedVector<char>::New(name.size() + 1);
296 std::copy(name.begin(), name.end(), null_terminated.begin());
297 null_terminated[name.size()] = '\0';
298 return GetName(null_terminated.begin());
299 }
300
InferScriptName(Name name,SharedFunctionInfo info)301 Name ProfilerListener::InferScriptName(Name name, SharedFunctionInfo info) {
302 if (name.IsString() && String::cast(name).length()) return name;
303 if (!info.script().IsScript()) return name;
304 Object source_url = Script::cast(info.script()).source_url();
305 return source_url.IsName() ? Name::cast(source_url) : name;
306 }
307
GetFunctionName(SharedFunctionInfo shared)308 const char* ProfilerListener::GetFunctionName(SharedFunctionInfo shared) {
309 switch (naming_mode_) {
310 case kDebugNaming:
311 return GetName(shared.DebugName());
312 case kStandardNaming:
313 return GetName(shared.Name());
314 default:
315 UNREACHABLE();
316 }
317 }
318
AttachDeoptInlinedFrames(Handle<Code> code,CodeDeoptEventRecord * rec)319 void ProfilerListener::AttachDeoptInlinedFrames(Handle<Code> code,
320 CodeDeoptEventRecord* rec) {
321 int deopt_id = rec->deopt_id;
322 SourcePosition last_position = SourcePosition::Unknown();
323 int mask = RelocInfo::ModeMask(RelocInfo::DEOPT_ID) |
324 RelocInfo::ModeMask(RelocInfo::DEOPT_SCRIPT_OFFSET) |
325 RelocInfo::ModeMask(RelocInfo::DEOPT_INLINING_ID);
326
327 rec->deopt_frames = nullptr;
328 rec->deopt_frame_count = 0;
329
330 for (RelocIterator it(*code, mask); !it.done(); it.next()) {
331 RelocInfo* info = it.rinfo();
332 if (info->rmode() == RelocInfo::DEOPT_SCRIPT_OFFSET) {
333 int script_offset = static_cast<int>(info->data());
334 it.next();
335 DCHECK(it.rinfo()->rmode() == RelocInfo::DEOPT_INLINING_ID);
336 int inlining_id = static_cast<int>(it.rinfo()->data());
337 last_position = SourcePosition(script_offset, inlining_id);
338 continue;
339 }
340 if (info->rmode() == RelocInfo::DEOPT_ID) {
341 if (deopt_id != static_cast<int>(info->data())) continue;
342 DCHECK(last_position.IsKnown());
343
344 // SourcePosition::InliningStack allocates a handle for the SFI of each
345 // frame. These don't escape this function, but quickly add up. This
346 // scope limits their lifetime.
347 HandleScope scope(isolate_);
348 std::vector<SourcePositionInfo> stack = last_position.InliningStack(code);
349 CpuProfileDeoptFrame* deopt_frames =
350 new CpuProfileDeoptFrame[stack.size()];
351
352 int deopt_frame_count = 0;
353 for (SourcePositionInfo& pos_info : stack) {
354 if (pos_info.position.ScriptOffset() == kNoSourcePosition) continue;
355 if (pos_info.script.is_null()) continue;
356 int script_id = pos_info.script->id();
357 size_t offset = static_cast<size_t>(pos_info.position.ScriptOffset());
358 deopt_frames[deopt_frame_count++] = {script_id, offset};
359 }
360 rec->deopt_frames = deopt_frames;
361 rec->deopt_frame_count = deopt_frame_count;
362 break;
363 }
364 }
365 }
366
367 } // namespace internal
368 } // namespace v8
369