| OLD | NEW |
| 1 // Copyright 2012 the V8 project authors. All rights reserved. | 1 // Copyright 2012 the V8 project authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "src/profiler/profile-generator.h" | 5 #include "src/profiler/profile-generator.h" |
| 6 | 6 |
| 7 #include "src/ast/scopeinfo.h" | 7 #include "src/ast/scopeinfo.h" |
| 8 #include "src/debug/debug.h" | 8 #include "src/debug/debug.h" |
| 9 #include "src/deoptimizer.h" | 9 #include "src/deoptimizer.h" |
| 10 #include "src/global-handles.h" | 10 #include "src/global-handles.h" |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 43 | 43 |
| 44 | 44 |
| 45 const char* const CodeEntry::kEmptyNamePrefix = ""; | 45 const char* const CodeEntry::kEmptyNamePrefix = ""; |
| 46 const char* const CodeEntry::kEmptyResourceName = ""; | 46 const char* const CodeEntry::kEmptyResourceName = ""; |
| 47 const char* const CodeEntry::kEmptyBailoutReason = ""; | 47 const char* const CodeEntry::kEmptyBailoutReason = ""; |
| 48 const char* const CodeEntry::kNoDeoptReason = ""; | 48 const char* const CodeEntry::kNoDeoptReason = ""; |
| 49 | 49 |
| 50 | 50 |
| 51 CodeEntry::~CodeEntry() { | 51 CodeEntry::~CodeEntry() { |
| 52 delete line_info_; | 52 delete line_info_; |
| 53 for (auto location : inline_locations_) { |
| 54 for (auto entry : location.second) { |
| 55 delete entry; |
| 56 } |
| 57 } |
| 53 } | 58 } |
| 54 | 59 |
| 55 | 60 |
| 56 uint32_t CodeEntry::GetHash() const { | 61 uint32_t CodeEntry::GetHash() const { |
| 57 uint32_t hash = ComputeIntegerHash(tag(), v8::internal::kZeroHashSeed); | 62 uint32_t hash = ComputeIntegerHash(tag(), v8::internal::kZeroHashSeed); |
| 58 if (script_id_ != v8::UnboundScript::kNoScriptId) { | 63 if (script_id_ != v8::UnboundScript::kNoScriptId) { |
| 59 hash ^= ComputeIntegerHash(static_cast<uint32_t>(script_id_), | 64 hash ^= ComputeIntegerHash(static_cast<uint32_t>(script_id_), |
| 60 v8::internal::kZeroHashSeed); | 65 v8::internal::kZeroHashSeed); |
| 61 hash ^= ComputeIntegerHash(static_cast<uint32_t>(position_), | 66 hash ^= ComputeIntegerHash(static_cast<uint32_t>(position_), |
| 62 v8::internal::kZeroHashSeed); | 67 v8::internal::kZeroHashSeed); |
| (...skipping 30 matching lines...) Expand all Loading... |
| 93 } | 98 } |
| 94 | 99 |
| 95 | 100 |
| 96 int CodeEntry::GetSourceLine(int pc_offset) const { | 101 int CodeEntry::GetSourceLine(int pc_offset) const { |
| 97 if (line_info_ && !line_info_->empty()) { | 102 if (line_info_ && !line_info_->empty()) { |
| 98 return line_info_->GetSourceLineNumber(pc_offset); | 103 return line_info_->GetSourceLineNumber(pc_offset); |
| 99 } | 104 } |
| 100 return v8::CpuProfileNode::kNoLineNumberInfo; | 105 return v8::CpuProfileNode::kNoLineNumberInfo; |
| 101 } | 106 } |
| 102 | 107 |
| 108 void CodeEntry::AddInlineStack(int pc_offset, |
| 109 std::vector<CodeEntry*>& inline_stack) { |
| 110 // It's better to use std::move to place the vector into the map, |
| 111 // but it's not supported by the current stdlibc++ on MacOS. |
| 112 inline_locations_.insert(std::make_pair(pc_offset, std::vector<CodeEntry*>())) |
| 113 .first->second.swap(inline_stack); |
| 114 } |
| 115 |
| 116 const std::vector<CodeEntry*>* CodeEntry::GetInlineStack(int pc_offset) const { |
| 117 auto it = inline_locations_.find(pc_offset); |
| 118 return it != inline_locations_.end() ? &it->second : NULL; |
| 119 } |
| 103 | 120 |
| 104 void CodeEntry::FillFunctionInfo(SharedFunctionInfo* shared) { | 121 void CodeEntry::FillFunctionInfo(SharedFunctionInfo* shared) { |
| 105 if (!shared->script()->IsScript()) return; | 122 if (!shared->script()->IsScript()) return; |
| 106 Script* script = Script::cast(shared->script()); | 123 Script* script = Script::cast(shared->script()); |
| 107 set_script_id(script->id()); | 124 set_script_id(script->id()); |
| 108 set_position(shared->start_position()); | 125 set_position(shared->start_position()); |
| 109 set_bailout_reason(GetBailoutReason(shared->disable_optimization_reason())); | 126 set_bailout_reason(GetBailoutReason(shared->disable_optimization_reason())); |
| 110 } | 127 } |
| 111 | 128 |
| 112 | |
| 113 CpuProfileDeoptInfo CodeEntry::GetDeoptInfo() { | 129 CpuProfileDeoptInfo CodeEntry::GetDeoptInfo() { |
| 114 DCHECK(has_deopt_info()); | 130 DCHECK(has_deopt_info()); |
| 115 | 131 |
| 116 CpuProfileDeoptInfo info; | 132 CpuProfileDeoptInfo info; |
| 117 info.deopt_reason = deopt_reason_; | 133 info.deopt_reason = deopt_reason_; |
| 118 if (inlined_function_infos_.empty()) { | 134 if (inlined_function_infos_.empty()) { |
| 119 info.stack.push_back(CpuProfileDeoptFrame( | 135 info.stack.push_back(CpuProfileDeoptFrame( |
| 120 {script_id_, position_ + deopt_position_.position()})); | 136 {script_id_, position_ + deopt_position_.position()})); |
| 121 return info; | 137 return info; |
| 122 } | 138 } |
| (...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 267 unsigned ProfileTree::GetFunctionId(const ProfileNode* node) { | 283 unsigned ProfileTree::GetFunctionId(const ProfileNode* node) { |
| 268 CodeEntry* code_entry = node->entry(); | 284 CodeEntry* code_entry = node->entry(); |
| 269 HashMap::Entry* entry = | 285 HashMap::Entry* entry = |
| 270 function_ids_.LookupOrInsert(code_entry, code_entry->GetHash()); | 286 function_ids_.LookupOrInsert(code_entry, code_entry->GetHash()); |
| 271 if (!entry->value) { | 287 if (!entry->value) { |
| 272 entry->value = reinterpret_cast<void*>(next_function_id_++); | 288 entry->value = reinterpret_cast<void*>(next_function_id_++); |
| 273 } | 289 } |
| 274 return static_cast<unsigned>(reinterpret_cast<uintptr_t>(entry->value)); | 290 return static_cast<unsigned>(reinterpret_cast<uintptr_t>(entry->value)); |
| 275 } | 291 } |
| 276 | 292 |
| 277 ProfileNode* ProfileTree::AddPathFromEnd(const Vector<CodeEntry*>& path, | 293 ProfileNode* ProfileTree::AddPathFromEnd(const std::vector<CodeEntry*>& path, |
| 278 int src_line, bool update_stats) { | 294 int src_line, bool update_stats) { |
| 279 ProfileNode* node = root_; | 295 ProfileNode* node = root_; |
| 280 CodeEntry* last_entry = NULL; | 296 CodeEntry* last_entry = NULL; |
| 281 for (CodeEntry** entry = path.start() + path.length() - 1; | 297 for (auto it = path.rbegin(); it != path.rend(); ++it) { |
| 282 entry != path.start() - 1; | 298 if (*it == NULL) continue; |
| 283 --entry) { | 299 last_entry = *it; |
| 284 if (*entry != NULL) { | 300 node = node->FindOrAddChild(*it); |
| 285 node = node->FindOrAddChild(*entry); | |
| 286 last_entry = *entry; | |
| 287 } | |
| 288 } | 301 } |
| 289 if (last_entry && last_entry->has_deopt_info()) { | 302 if (last_entry && last_entry->has_deopt_info()) { |
| 290 node->CollectDeoptInfo(last_entry); | 303 node->CollectDeoptInfo(last_entry); |
| 291 } | 304 } |
| 292 if (update_stats) { | 305 if (update_stats) { |
| 293 node->IncrementSelfTicks(); | 306 node->IncrementSelfTicks(); |
| 294 if (src_line != v8::CpuProfileNode::kNoLineNumberInfo) { | 307 if (src_line != v8::CpuProfileNode::kNoLineNumberInfo) { |
| 295 node->IncrementLineTicks(src_line); | 308 node->IncrementLineTicks(src_line); |
| 296 } | 309 } |
| 297 } | 310 } |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 349 } | 362 } |
| 350 | 363 |
| 351 | 364 |
| 352 CpuProfile::CpuProfile(Isolate* isolate, const char* title, bool record_samples) | 365 CpuProfile::CpuProfile(Isolate* isolate, const char* title, bool record_samples) |
| 353 : title_(title), | 366 : title_(title), |
| 354 record_samples_(record_samples), | 367 record_samples_(record_samples), |
| 355 start_time_(base::TimeTicks::HighResolutionNow()), | 368 start_time_(base::TimeTicks::HighResolutionNow()), |
| 356 top_down_(isolate) {} | 369 top_down_(isolate) {} |
| 357 | 370 |
| 358 void CpuProfile::AddPath(base::TimeTicks timestamp, | 371 void CpuProfile::AddPath(base::TimeTicks timestamp, |
| 359 const Vector<CodeEntry*>& path, int src_line, | 372 const std::vector<CodeEntry*>& path, int src_line, |
| 360 bool update_stats) { | 373 bool update_stats) { |
| 361 ProfileNode* top_frame_node = | 374 ProfileNode* top_frame_node = |
| 362 top_down_.AddPathFromEnd(path, src_line, update_stats); | 375 top_down_.AddPathFromEnd(path, src_line, update_stats); |
| 363 if (record_samples_ && !timestamp.IsNull()) { | 376 if (record_samples_ && !timestamp.IsNull()) { |
| 364 timestamps_.Add(timestamp); | 377 timestamps_.Add(timestamp); |
| 365 samples_.Add(top_frame_node); | 378 samples_.Add(top_frame_node); |
| 366 } | 379 } |
| 367 } | 380 } |
| 368 | 381 |
| 369 | 382 |
| (...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 518 for (int i = 0; i < finished_profiles_.length(); i++) { | 531 for (int i = 0; i < finished_profiles_.length(); i++) { |
| 519 if (profile == finished_profiles_[i]) { | 532 if (profile == finished_profiles_[i]) { |
| 520 finished_profiles_.Remove(i); | 533 finished_profiles_.Remove(i); |
| 521 return; | 534 return; |
| 522 } | 535 } |
| 523 } | 536 } |
| 524 UNREACHABLE(); | 537 UNREACHABLE(); |
| 525 } | 538 } |
| 526 | 539 |
| 527 void CpuProfilesCollection::AddPathToCurrentProfiles( | 540 void CpuProfilesCollection::AddPathToCurrentProfiles( |
| 528 base::TimeTicks timestamp, const Vector<CodeEntry*>& path, int src_line, | 541 base::TimeTicks timestamp, const std::vector<CodeEntry*>& path, |
| 529 bool update_stats) { | 542 int src_line, bool update_stats) { |
| 530 // As starting / stopping profiles is rare relatively to this | 543 // As starting / stopping profiles is rare relatively to this |
| 531 // method, we don't bother minimizing the duration of lock holding, | 544 // method, we don't bother minimizing the duration of lock holding, |
| 532 // e.g. copying contents of the list to a local vector. | 545 // e.g. copying contents of the list to a local vector. |
| 533 current_profiles_semaphore_.Wait(); | 546 current_profiles_semaphore_.Wait(); |
| 534 for (int i = 0; i < current_profiles_.length(); ++i) { | 547 for (int i = 0; i < current_profiles_.length(); ++i) { |
| 535 current_profiles_[i]->AddPath(timestamp, path, src_line, update_stats); | 548 current_profiles_[i]->AddPath(timestamp, path, src_line, update_stats); |
| 536 } | 549 } |
| 537 current_profiles_semaphore_.Signal(); | 550 current_profiles_semaphore_.Signal(); |
| 538 } | 551 } |
| 539 | 552 |
| (...skipping 29 matching lines...) Expand all Loading... |
| 569 gc_entry_( | 582 gc_entry_( |
| 570 profiles->NewCodeEntry(Logger::BUILTIN_TAG, | 583 profiles->NewCodeEntry(Logger::BUILTIN_TAG, |
| 571 kGarbageCollectorEntryName)), | 584 kGarbageCollectorEntryName)), |
| 572 unresolved_entry_( | 585 unresolved_entry_( |
| 573 profiles->NewCodeEntry(Logger::FUNCTION_TAG, | 586 profiles->NewCodeEntry(Logger::FUNCTION_TAG, |
| 574 kUnresolvedFunctionName)) { | 587 kUnresolvedFunctionName)) { |
| 575 } | 588 } |
| 576 | 589 |
| 577 | 590 |
| 578 void ProfileGenerator::RecordTickSample(const TickSample& sample) { | 591 void ProfileGenerator::RecordTickSample(const TickSample& sample) { |
| 579 // Allocate space for stack frames + pc + function + vm-state. | 592 std::vector<CodeEntry*> entries; |
| 580 ScopedVector<CodeEntry*> entries(sample.frames_count + 3); | 593 // Conservatively reserve space for stack frames + pc + function + vm-state. |
| 581 // As actual number of decoded code entries may vary, initialize | 594 // There could in fact be more of them because of inlined entries. |
| 582 // entries vector with NULL values. | 595 entries.reserve(sample.frames_count + 3); |
| 583 CodeEntry** entry = entries.start(); | |
| 584 memset(entry, 0, entries.length() * sizeof(*entry)); | |
| 585 | 596 |
| 586 // The ProfileNode knows nothing about all versions of generated code for | 597 // The ProfileNode knows nothing about all versions of generated code for |
| 587 // the same JS function. The line number information associated with | 598 // the same JS function. The line number information associated with |
| 588 // the latest version of generated code is used to find a source line number | 599 // the latest version of generated code is used to find a source line number |
| 589 // for a JS function. Then, the detected source line is passed to | 600 // for a JS function. Then, the detected source line is passed to |
| 590 // ProfileNode to increase the tick count for this source line. | 601 // ProfileNode to increase the tick count for this source line. |
| 591 int src_line = v8::CpuProfileNode::kNoLineNumberInfo; | 602 int src_line = v8::CpuProfileNode::kNoLineNumberInfo; |
| 592 bool src_line_not_found = true; | 603 bool src_line_not_found = true; |
| 593 | 604 |
| 594 if (sample.pc != NULL) { | 605 if (sample.pc != NULL) { |
| 595 if (sample.has_external_callback && sample.state == EXTERNAL && | 606 if (sample.has_external_callback && sample.state == EXTERNAL && |
| 596 sample.top_frame_type == StackFrame::EXIT) { | 607 sample.top_frame_type == StackFrame::EXIT) { |
| 597 // Don't use PC when in external callback code, as it can point | 608 // Don't use PC when in external callback code, as it can point |
| 598 // inside callback's code, and we will erroneously report | 609 // inside callback's code, and we will erroneously report |
| 599 // that a callback calls itself. | 610 // that a callback calls itself. |
| 600 *entry++ = code_map_.FindEntry(sample.external_callback); | 611 entries.push_back(code_map_.FindEntry(sample.external_callback)); |
| 601 } else { | 612 } else { |
| 602 CodeEntry* pc_entry = code_map_.FindEntry(sample.pc); | 613 CodeEntry* pc_entry = code_map_.FindEntry(sample.pc); |
| 603 // If there is no pc_entry we're likely in native code. | 614 // If there is no pc_entry we're likely in native code. |
| 604 // Find out, if top of stack was pointing inside a JS function | 615 // Find out, if top of stack was pointing inside a JS function |
| 605 // meaning that we have encountered a frameless invocation. | 616 // meaning that we have encountered a frameless invocation. |
| 606 if (!pc_entry && (sample.top_frame_type == StackFrame::JAVA_SCRIPT || | 617 if (!pc_entry && (sample.top_frame_type == StackFrame::JAVA_SCRIPT || |
| 607 sample.top_frame_type == StackFrame::INTERPRETED || | 618 sample.top_frame_type == StackFrame::INTERPRETED || |
| 608 sample.top_frame_type == StackFrame::OPTIMIZED)) { | 619 sample.top_frame_type == StackFrame::OPTIMIZED)) { |
| 609 pc_entry = code_map_.FindEntry(sample.tos); | 620 pc_entry = code_map_.FindEntry(sample.tos); |
| 610 } | 621 } |
| 611 // If pc is in the function code before it set up stack frame or after the | 622 // If pc is in the function code before it set up stack frame or after the |
| 612 // frame was destroyed SafeStackFrameIterator incorrectly thinks that | 623 // frame was destroyed SafeStackFrameIterator incorrectly thinks that |
| 613 // ebp contains return address of the current function and skips caller's | 624 // ebp contains return address of the current function and skips caller's |
| 614 // frame. Check for this case and just skip such samples. | 625 // frame. Check for this case and just skip such samples. |
| 615 if (pc_entry) { | 626 if (pc_entry) { |
| 616 int pc_offset = | 627 int pc_offset = |
| 617 static_cast<int>(sample.pc - pc_entry->instruction_start()); | 628 static_cast<int>(sample.pc - pc_entry->instruction_start()); |
| 618 src_line = pc_entry->GetSourceLine(pc_offset); | 629 src_line = pc_entry->GetSourceLine(pc_offset); |
| 619 if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) { | 630 if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) { |
| 620 src_line = pc_entry->line_number(); | 631 src_line = pc_entry->line_number(); |
| 621 } | 632 } |
| 622 src_line_not_found = false; | 633 src_line_not_found = false; |
| 623 *entry++ = pc_entry; | 634 entries.push_back(pc_entry); |
| 624 | 635 |
| 625 if (pc_entry->builtin_id() == Builtins::kFunctionPrototypeApply || | 636 if (pc_entry->builtin_id() == Builtins::kFunctionPrototypeApply || |
| 626 pc_entry->builtin_id() == Builtins::kFunctionPrototypeCall) { | 637 pc_entry->builtin_id() == Builtins::kFunctionPrototypeCall) { |
| 627 // When current function is either the Function.prototype.apply or the | 638 // When current function is either the Function.prototype.apply or the |
| 628 // Function.prototype.call builtin the top frame is either frame of | 639 // Function.prototype.call builtin the top frame is either frame of |
| 629 // the calling JS function or internal frame. | 640 // the calling JS function or internal frame. |
| 630 // In the latter case we know the caller for sure but in the | 641 // In the latter case we know the caller for sure but in the |
| 631 // former case we don't so we simply replace the frame with | 642 // former case we don't so we simply replace the frame with |
| 632 // 'unresolved' entry. | 643 // 'unresolved' entry. |
| 633 if (sample.top_frame_type == StackFrame::JAVA_SCRIPT) { | 644 if (sample.top_frame_type == StackFrame::JAVA_SCRIPT) { |
| 634 *entry++ = unresolved_entry_; | 645 entries.push_back(unresolved_entry_); |
| 635 } | 646 } |
| 636 } | 647 } |
| 637 } | 648 } |
| 638 } | 649 } |
| 639 | 650 |
| 640 for (const Address *stack_pos = sample.stack, | 651 for (const Address *stack_pos = sample.stack, |
| 641 *stack_end = stack_pos + sample.frames_count; | 652 *stack_end = stack_pos + sample.frames_count; |
| 642 stack_pos != stack_end; ++stack_pos) { | 653 stack_pos != stack_end; ++stack_pos) { |
| 643 *entry = code_map_.FindEntry(*stack_pos); | 654 CodeEntry* entry = code_map_.FindEntry(*stack_pos); |
| 644 | 655 |
| 645 // Skip unresolved frames (e.g. internal frame) and get source line of | 656 if (entry) { |
| 646 // the first JS caller. | 657 // Find out if the entry has an inlining stack associated. |
| 647 if (src_line_not_found && *entry) { | |
| 648 int pc_offset = | 658 int pc_offset = |
| 649 static_cast<int>(*stack_pos - (*entry)->instruction_start()); | 659 static_cast<int>(*stack_pos - entry->instruction_start()); |
| 650 src_line = (*entry)->GetSourceLine(pc_offset); | 660 const std::vector<CodeEntry*>* inline_stack = |
| 651 if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) { | 661 entry->GetInlineStack(pc_offset); |
| 652 src_line = (*entry)->line_number(); | 662 if (inline_stack) { |
| 663 entries.insert(entries.end(), inline_stack->rbegin(), |
| 664 inline_stack->rend()); |
| 653 } | 665 } |
| 654 src_line_not_found = false; | 666 // Skip unresolved frames (e.g. internal frame) and get source line of |
| 667 // the first JS caller. |
| 668 if (src_line_not_found) { |
| 669 src_line = entry->GetSourceLine(pc_offset); |
| 670 if (src_line == v8::CpuProfileNode::kNoLineNumberInfo) { |
| 671 src_line = entry->line_number(); |
| 672 } |
| 673 src_line_not_found = false; |
| 674 } |
| 655 } | 675 } |
| 656 | 676 entries.push_back(entry); |
| 657 entry++; | |
| 658 } | 677 } |
| 659 } | 678 } |
| 660 | 679 |
| 661 if (FLAG_prof_browser_mode) { | 680 if (FLAG_prof_browser_mode) { |
| 662 bool no_symbolized_entries = true; | 681 bool no_symbolized_entries = true; |
| 663 for (CodeEntry** e = entries.start(); e != entry; ++e) { | 682 for (auto e : entries) { |
| 664 if (*e != NULL) { | 683 if (e != NULL) { |
| 665 no_symbolized_entries = false; | 684 no_symbolized_entries = false; |
| 666 break; | 685 break; |
| 667 } | 686 } |
| 668 } | 687 } |
| 669 // If no frames were symbolized, put the VM state entry in. | 688 // If no frames were symbolized, put the VM state entry in. |
| 670 if (no_symbolized_entries) { | 689 if (no_symbolized_entries) { |
| 671 *entry++ = EntryForVMState(sample.state); | 690 entries.push_back(EntryForVMState(sample.state)); |
| 672 } | 691 } |
| 673 } | 692 } |
| 674 | 693 |
| 675 profiles_->AddPathToCurrentProfiles(sample.timestamp, entries, src_line, | 694 profiles_->AddPathToCurrentProfiles(sample.timestamp, entries, src_line, |
| 676 sample.update_stats); | 695 sample.update_stats); |
| 677 } | 696 } |
| 678 | 697 |
| 679 | 698 |
| 680 CodeEntry* ProfileGenerator::EntryForVMState(StateTag tag) { | 699 CodeEntry* ProfileGenerator::EntryForVMState(StateTag tag) { |
| 681 switch (tag) { | 700 switch (tag) { |
| 682 case GC: | 701 case GC: |
| 683 return gc_entry_; | 702 return gc_entry_; |
| 684 case JS: | 703 case JS: |
| 685 case COMPILER: | 704 case COMPILER: |
| 686 // DOM events handlers are reported as OTHER / EXTERNAL entries. | 705 // DOM events handlers are reported as OTHER / EXTERNAL entries. |
| 687 // To avoid confusing people, let's put all these entries into | 706 // To avoid confusing people, let's put all these entries into |
| 688 // one bucket. | 707 // one bucket. |
| 689 case OTHER: | 708 case OTHER: |
| 690 case EXTERNAL: | 709 case EXTERNAL: |
| 691 return program_entry_; | 710 return program_entry_; |
| 692 case IDLE: | 711 case IDLE: |
| 693 return idle_entry_; | 712 return idle_entry_; |
| 694 default: return NULL; | 713 default: return NULL; |
| 695 } | 714 } |
| 696 } | 715 } |
| 697 | 716 |
| 698 } // namespace internal | 717 } // namespace internal |
| 699 } // namespace v8 | 718 } // namespace v8 |
| OLD | NEW |