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