• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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