Ben Murdoch | 61f157c | 2016-09-16 13:49:30 +0100 | [diff] [blame] | 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 "src/deoptimizer.h" |
| 8 | #include "src/interpreter/source-position-table.h" |
| 9 | #include "src/profiler/cpu-profiler.h" |
| 10 | #include "src/profiler/profile-generator-inl.h" |
| 11 | |
| 12 | namespace v8 { |
| 13 | namespace internal { |
| 14 | |
| 15 | ProfilerListener::ProfilerListener(Isolate* isolate) |
| 16 | : function_and_resource_names_(isolate->heap()) {} |
| 17 | |
| 18 | ProfilerListener::~ProfilerListener() { |
| 19 | for (auto code_entry : code_entries_) { |
| 20 | delete code_entry; |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | void ProfilerListener::CallbackEvent(Name* name, Address entry_point) { |
| 25 | CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION); |
| 26 | CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_; |
| 27 | rec->start = entry_point; |
| 28 | rec->entry = NewCodeEntry(CodeEventListener::CALLBACK_TAG, GetName(name)); |
| 29 | rec->size = 1; |
| 30 | DispatchCodeEvent(evt_rec); |
| 31 | } |
| 32 | |
| 33 | void ProfilerListener::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag, |
| 34 | AbstractCode* code, const char* name) { |
| 35 | CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION); |
| 36 | CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_; |
| 37 | rec->start = code->address(); |
| 38 | rec->entry = NewCodeEntry( |
| 39 | tag, GetFunctionName(name), CodeEntry::kEmptyNamePrefix, |
| 40 | CodeEntry::kEmptyResourceName, CpuProfileNode::kNoLineNumberInfo, |
| 41 | CpuProfileNode::kNoColumnNumberInfo, NULL, code->instruction_start()); |
| 42 | RecordInliningInfo(rec->entry, code); |
| 43 | rec->size = code->ExecutableSize(); |
| 44 | DispatchCodeEvent(evt_rec); |
| 45 | } |
| 46 | |
| 47 | void ProfilerListener::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag, |
| 48 | AbstractCode* code, Name* name) { |
| 49 | CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION); |
| 50 | CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_; |
| 51 | rec->start = code->address(); |
| 52 | rec->entry = NewCodeEntry( |
| 53 | tag, GetFunctionName(name), CodeEntry::kEmptyNamePrefix, |
| 54 | CodeEntry::kEmptyResourceName, CpuProfileNode::kNoLineNumberInfo, |
| 55 | CpuProfileNode::kNoColumnNumberInfo, NULL, code->instruction_start()); |
| 56 | RecordInliningInfo(rec->entry, code); |
| 57 | rec->size = code->ExecutableSize(); |
| 58 | DispatchCodeEvent(evt_rec); |
| 59 | } |
| 60 | |
| 61 | void ProfilerListener::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag, |
| 62 | AbstractCode* code, |
| 63 | SharedFunctionInfo* shared, |
| 64 | Name* script_name) { |
| 65 | CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION); |
| 66 | CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_; |
| 67 | rec->start = code->address(); |
| 68 | rec->entry = NewCodeEntry( |
| 69 | tag, GetFunctionName(shared->DebugName()), CodeEntry::kEmptyNamePrefix, |
| 70 | GetName(InferScriptName(script_name, shared)), |
| 71 | CpuProfileNode::kNoLineNumberInfo, CpuProfileNode::kNoColumnNumberInfo, |
| 72 | NULL, code->instruction_start()); |
| 73 | RecordInliningInfo(rec->entry, code); |
| 74 | rec->entry->FillFunctionInfo(shared); |
| 75 | rec->size = code->ExecutableSize(); |
| 76 | DispatchCodeEvent(evt_rec); |
| 77 | } |
| 78 | |
| 79 | void ProfilerListener::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag, |
| 80 | AbstractCode* abstract_code, |
| 81 | SharedFunctionInfo* shared, |
| 82 | Name* script_name, int line, |
| 83 | int column) { |
| 84 | CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION); |
| 85 | CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_; |
| 86 | rec->start = abstract_code->address(); |
| 87 | Script* script = Script::cast(shared->script()); |
| 88 | JITLineInfoTable* line_table = NULL; |
| 89 | if (script) { |
| 90 | if (abstract_code->IsCode()) { |
| 91 | Code* code = abstract_code->GetCode(); |
| 92 | int start_position = shared->start_position(); |
| 93 | int end_position = shared->end_position(); |
| 94 | line_table = new JITLineInfoTable(); |
| 95 | for (RelocIterator it(code); !it.done(); it.next()) { |
| 96 | RelocInfo* reloc_info = it.rinfo(); |
| 97 | if (!RelocInfo::IsPosition(reloc_info->rmode())) continue; |
| 98 | int position = static_cast<int>(reloc_info->data()); |
| 99 | // TODO(alph): in case of inlining the position may correspond |
| 100 | // to an inlined function source code. Do not collect positions |
| 101 | // that fall beyond the function source code. There's however a |
| 102 | // chance the inlined function has similar positions but in another |
| 103 | // script. So the proper fix is to store script_id in some form |
| 104 | // along with the inlined function positions. |
| 105 | if (position < start_position || position >= end_position) continue; |
| 106 | int pc_offset = static_cast<int>(reloc_info->pc() - code->address()); |
| 107 | int line_number = script->GetLineNumber(position) + 1; |
| 108 | line_table->SetPosition(pc_offset, line_number); |
| 109 | } |
| 110 | } else { |
| 111 | BytecodeArray* bytecode = abstract_code->GetBytecodeArray(); |
| 112 | line_table = new JITLineInfoTable(); |
| 113 | interpreter::SourcePositionTableIterator it( |
| 114 | bytecode->source_position_table()); |
| 115 | for (; !it.done(); it.Advance()) { |
| 116 | int line_number = script->GetLineNumber(it.source_position()) + 1; |
| 117 | int pc_offset = it.bytecode_offset() + BytecodeArray::kHeaderSize; |
| 118 | line_table->SetPosition(pc_offset, line_number); |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | rec->entry = NewCodeEntry( |
| 123 | tag, GetFunctionName(shared->DebugName()), CodeEntry::kEmptyNamePrefix, |
| 124 | GetName(InferScriptName(script_name, shared)), line, column, line_table, |
| 125 | abstract_code->instruction_start()); |
| 126 | RecordInliningInfo(rec->entry, abstract_code); |
| 127 | RecordDeoptInlinedFrames(rec->entry, abstract_code); |
| 128 | rec->entry->FillFunctionInfo(shared); |
| 129 | rec->size = abstract_code->ExecutableSize(); |
| 130 | DispatchCodeEvent(evt_rec); |
| 131 | } |
| 132 | |
| 133 | void ProfilerListener::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag, |
| 134 | AbstractCode* code, int args_count) { |
| 135 | CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION); |
| 136 | CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_; |
| 137 | rec->start = code->address(); |
| 138 | rec->entry = NewCodeEntry( |
| 139 | tag, GetName(args_count), "args_count: ", CodeEntry::kEmptyResourceName, |
| 140 | CpuProfileNode::kNoLineNumberInfo, CpuProfileNode::kNoColumnNumberInfo, |
| 141 | NULL, code->instruction_start()); |
| 142 | RecordInliningInfo(rec->entry, code); |
| 143 | rec->size = code->ExecutableSize(); |
| 144 | DispatchCodeEvent(evt_rec); |
| 145 | } |
| 146 | |
| 147 | void ProfilerListener::CodeMoveEvent(AbstractCode* from, Address to) { |
| 148 | CodeEventsContainer evt_rec(CodeEventRecord::CODE_MOVE); |
| 149 | CodeMoveEventRecord* rec = &evt_rec.CodeMoveEventRecord_; |
| 150 | rec->from = from->address(); |
| 151 | rec->to = to; |
| 152 | DispatchCodeEvent(evt_rec); |
| 153 | } |
| 154 | |
| 155 | void ProfilerListener::CodeDisableOptEvent(AbstractCode* code, |
| 156 | SharedFunctionInfo* shared) { |
| 157 | CodeEventsContainer evt_rec(CodeEventRecord::CODE_DISABLE_OPT); |
| 158 | CodeDisableOptEventRecord* rec = &evt_rec.CodeDisableOptEventRecord_; |
| 159 | rec->start = code->address(); |
| 160 | rec->bailout_reason = GetBailoutReason(shared->disable_optimization_reason()); |
| 161 | DispatchCodeEvent(evt_rec); |
| 162 | } |
| 163 | |
| 164 | void ProfilerListener::CodeDeoptEvent(Code* code, Address pc, |
| 165 | int fp_to_sp_delta) { |
| 166 | CodeEventsContainer evt_rec(CodeEventRecord::CODE_DEOPT); |
| 167 | CodeDeoptEventRecord* rec = &evt_rec.CodeDeoptEventRecord_; |
| 168 | Deoptimizer::DeoptInfo info = Deoptimizer::GetDeoptInfo(code, pc); |
| 169 | rec->start = code->address(); |
| 170 | rec->deopt_reason = Deoptimizer::GetDeoptReason(info.deopt_reason); |
| 171 | rec->position = info.position; |
| 172 | rec->deopt_id = info.deopt_id; |
| 173 | rec->pc = reinterpret_cast<void*>(pc); |
| 174 | rec->fp_to_sp_delta = fp_to_sp_delta; |
| 175 | DispatchCodeEvent(evt_rec); |
| 176 | } |
| 177 | |
| 178 | void ProfilerListener::GetterCallbackEvent(Name* name, Address entry_point) { |
| 179 | CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION); |
| 180 | CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_; |
| 181 | rec->start = entry_point; |
| 182 | rec->entry = |
| 183 | NewCodeEntry(CodeEventListener::CALLBACK_TAG, GetName(name), "get "); |
| 184 | rec->size = 1; |
| 185 | DispatchCodeEvent(evt_rec); |
| 186 | } |
| 187 | |
| 188 | void ProfilerListener::RegExpCodeCreateEvent(AbstractCode* code, |
| 189 | String* source) { |
| 190 | CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION); |
| 191 | CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_; |
| 192 | rec->start = code->address(); |
| 193 | rec->entry = NewCodeEntry( |
| 194 | CodeEventListener::REG_EXP_TAG, GetName(source), "RegExp: ", |
| 195 | CodeEntry::kEmptyResourceName, CpuProfileNode::kNoLineNumberInfo, |
| 196 | CpuProfileNode::kNoColumnNumberInfo, NULL, code->instruction_start()); |
| 197 | rec->size = code->ExecutableSize(); |
| 198 | DispatchCodeEvent(evt_rec); |
| 199 | } |
| 200 | |
| 201 | void ProfilerListener::SetterCallbackEvent(Name* name, Address entry_point) { |
| 202 | CodeEventsContainer evt_rec(CodeEventRecord::CODE_CREATION); |
| 203 | CodeCreateEventRecord* rec = &evt_rec.CodeCreateEventRecord_; |
| 204 | rec->start = entry_point; |
| 205 | rec->entry = |
| 206 | NewCodeEntry(CodeEventListener::CALLBACK_TAG, GetName(name), "set "); |
| 207 | rec->size = 1; |
| 208 | DispatchCodeEvent(evt_rec); |
| 209 | } |
| 210 | |
| 211 | Name* ProfilerListener::InferScriptName(Name* name, SharedFunctionInfo* info) { |
| 212 | if (name->IsString() && String::cast(name)->length()) return name; |
| 213 | if (!info->script()->IsScript()) return name; |
| 214 | Object* source_url = Script::cast(info->script())->source_url(); |
| 215 | return source_url->IsName() ? Name::cast(source_url) : name; |
| 216 | } |
| 217 | |
| 218 | void ProfilerListener::RecordInliningInfo(CodeEntry* entry, |
| 219 | AbstractCode* abstract_code) { |
| 220 | if (!abstract_code->IsCode()) return; |
| 221 | Code* code = abstract_code->GetCode(); |
| 222 | if (code->kind() != Code::OPTIMIZED_FUNCTION) return; |
| 223 | DeoptimizationInputData* deopt_input_data = |
| 224 | DeoptimizationInputData::cast(code->deoptimization_data()); |
| 225 | int deopt_count = deopt_input_data->DeoptCount(); |
| 226 | for (int i = 0; i < deopt_count; i++) { |
| 227 | int pc_offset = deopt_input_data->Pc(i)->value(); |
| 228 | if (pc_offset == -1) continue; |
| 229 | int translation_index = deopt_input_data->TranslationIndex(i)->value(); |
| 230 | TranslationIterator it(deopt_input_data->TranslationByteArray(), |
| 231 | translation_index); |
| 232 | Translation::Opcode opcode = static_cast<Translation::Opcode>(it.Next()); |
| 233 | DCHECK_EQ(Translation::BEGIN, opcode); |
| 234 | it.Skip(Translation::NumberOfOperandsFor(opcode)); |
| 235 | int depth = 0; |
| 236 | std::vector<CodeEntry*> inline_stack; |
| 237 | while (it.HasNext() && |
| 238 | Translation::BEGIN != |
| 239 | (opcode = static_cast<Translation::Opcode>(it.Next()))) { |
| 240 | if (opcode != Translation::JS_FRAME && |
| 241 | opcode != Translation::INTERPRETED_FRAME) { |
| 242 | it.Skip(Translation::NumberOfOperandsFor(opcode)); |
| 243 | continue; |
| 244 | } |
| 245 | it.Next(); // Skip ast_id |
| 246 | int shared_info_id = it.Next(); |
| 247 | it.Next(); // Skip height |
| 248 | SharedFunctionInfo* shared_info = SharedFunctionInfo::cast( |
| 249 | deopt_input_data->LiteralArray()->get(shared_info_id)); |
| 250 | if (!depth++) continue; // Skip the current function itself. |
| 251 | CodeEntry* inline_entry = new CodeEntry( |
| 252 | entry->tag(), GetFunctionName(shared_info->DebugName()), |
| 253 | CodeEntry::kEmptyNamePrefix, entry->resource_name(), |
| 254 | CpuProfileNode::kNoLineNumberInfo, |
| 255 | CpuProfileNode::kNoColumnNumberInfo, NULL, code->instruction_start()); |
| 256 | inline_entry->FillFunctionInfo(shared_info); |
| 257 | inline_stack.push_back(inline_entry); |
| 258 | } |
| 259 | if (!inline_stack.empty()) { |
| 260 | entry->AddInlineStack(pc_offset, inline_stack); |
| 261 | DCHECK(inline_stack.empty()); |
| 262 | } |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | void ProfilerListener::RecordDeoptInlinedFrames(CodeEntry* entry, |
| 267 | AbstractCode* abstract_code) { |
| 268 | if (abstract_code->kind() != AbstractCode::OPTIMIZED_FUNCTION) return; |
| 269 | Code* code = abstract_code->GetCode(); |
| 270 | DeoptimizationInputData* deopt_input_data = |
| 271 | DeoptimizationInputData::cast(code->deoptimization_data()); |
| 272 | int const mask = RelocInfo::ModeMask(RelocInfo::DEOPT_ID); |
| 273 | for (RelocIterator rit(code, mask); !rit.done(); rit.next()) { |
| 274 | RelocInfo* reloc_info = rit.rinfo(); |
| 275 | DCHECK(RelocInfo::IsDeoptId(reloc_info->rmode())); |
| 276 | int deopt_id = static_cast<int>(reloc_info->data()); |
| 277 | int translation_index = |
| 278 | deopt_input_data->TranslationIndex(deopt_id)->value(); |
| 279 | TranslationIterator it(deopt_input_data->TranslationByteArray(), |
| 280 | translation_index); |
| 281 | Translation::Opcode opcode = static_cast<Translation::Opcode>(it.Next()); |
| 282 | DCHECK_EQ(Translation::BEGIN, opcode); |
| 283 | it.Skip(Translation::NumberOfOperandsFor(opcode)); |
| 284 | std::vector<CodeEntry::DeoptInlinedFrame> inlined_frames; |
| 285 | while (it.HasNext() && |
| 286 | Translation::BEGIN != |
| 287 | (opcode = static_cast<Translation::Opcode>(it.Next()))) { |
| 288 | if (opcode != Translation::JS_FRAME && |
| 289 | opcode != Translation::INTERPRETED_FRAME) { |
| 290 | it.Skip(Translation::NumberOfOperandsFor(opcode)); |
| 291 | continue; |
| 292 | } |
| 293 | BailoutId ast_id = BailoutId(it.Next()); |
| 294 | int shared_info_id = it.Next(); |
| 295 | it.Next(); // Skip height |
| 296 | SharedFunctionInfo* shared = SharedFunctionInfo::cast( |
| 297 | deopt_input_data->LiteralArray()->get(shared_info_id)); |
| 298 | int source_position = Deoptimizer::ComputeSourcePosition(shared, ast_id); |
| 299 | int script_id = v8::UnboundScript::kNoScriptId; |
| 300 | if (shared->script()->IsScript()) { |
| 301 | Script* script = Script::cast(shared->script()); |
| 302 | script_id = script->id(); |
| 303 | } |
| 304 | CodeEntry::DeoptInlinedFrame frame = {source_position, script_id}; |
| 305 | inlined_frames.push_back(frame); |
| 306 | } |
| 307 | if (!inlined_frames.empty() && !entry->HasDeoptInlinedFramesFor(deopt_id)) { |
| 308 | entry->AddDeoptInlinedFrames(deopt_id, inlined_frames); |
| 309 | DCHECK(inlined_frames.empty()); |
| 310 | } |
| 311 | } |
| 312 | } |
| 313 | |
| 314 | CodeEntry* ProfilerListener::NewCodeEntry( |
| 315 | CodeEventListener::LogEventsAndTags tag, const char* name, |
| 316 | const char* name_prefix, const char* resource_name, int line_number, |
| 317 | int column_number, JITLineInfoTable* line_info, Address instruction_start) { |
| 318 | CodeEntry* code_entry = |
| 319 | new CodeEntry(tag, name, name_prefix, resource_name, line_number, |
| 320 | column_number, line_info, instruction_start); |
| 321 | code_entries_.push_back(code_entry); |
| 322 | return code_entry; |
| 323 | } |
| 324 | |
| 325 | void ProfilerListener::AddObserver(CodeEventObserver* observer) { |
| 326 | if (std::find(observers_.begin(), observers_.end(), observer) != |
| 327 | observers_.end()) |
| 328 | return; |
| 329 | observers_.push_back(observer); |
| 330 | } |
| 331 | |
| 332 | void ProfilerListener::RemoveObserver(CodeEventObserver* observer) { |
| 333 | auto it = std::find(observers_.begin(), observers_.end(), observer); |
| 334 | if (it == observers_.end()) return; |
| 335 | observers_.erase(it); |
| 336 | } |
| 337 | |
| 338 | } // namespace internal |
| 339 | } // namespace v8 |