• 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/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