diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-01-20 13:40:20 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-01-22 12:41:23 +0000 |
commit | 7961cea6d1041e3e454dae6a1da660b453efd238 (patch) | |
tree | c0eeb4a9ff9ba32986289c1653d9608e53ccb444 /chromium/v8/src/profiler | |
parent | b7034d0803538058e5c9d904ef03cf5eab34f6ef (diff) | |
download | qtwebengine-chromium-7961cea6d1041e3e454dae6a1da660b453efd238.tar.gz |
BASELINE: Update Chromium to 78.0.3904.130
Change-Id: If185e0c0061b3437531c97c9c8c78f239352a68b
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/v8/src/profiler')
-rw-r--r-- | chromium/v8/src/profiler/cpu-profiler.cc | 229 | ||||
-rw-r--r-- | chromium/v8/src/profiler/cpu-profiler.h | 103 | ||||
-rw-r--r-- | chromium/v8/src/profiler/heap-profiler.cc | 11 | ||||
-rw-r--r-- | chromium/v8/src/profiler/heap-profiler.h | 1 | ||||
-rw-r--r-- | chromium/v8/src/profiler/heap-snapshot-generator.cc | 72 | ||||
-rw-r--r-- | chromium/v8/src/profiler/heap-snapshot-generator.h | 9 | ||||
-rw-r--r-- | chromium/v8/src/profiler/profile-generator-inl.h | 2 | ||||
-rw-r--r-- | chromium/v8/src/profiler/profile-generator.cc | 106 | ||||
-rw-r--r-- | chromium/v8/src/profiler/profile-generator.h | 47 | ||||
-rw-r--r-- | chromium/v8/src/profiler/profiler-listener.cc | 10 | ||||
-rw-r--r-- | chromium/v8/src/profiler/profiler-listener.h | 1 | ||||
-rw-r--r-- | chromium/v8/src/profiler/tick-sample.cc | 115 | ||||
-rw-r--r-- | chromium/v8/src/profiler/tick-sample.h | 76 |
13 files changed, 604 insertions, 178 deletions
diff --git a/chromium/v8/src/profiler/cpu-profiler.cc b/chromium/v8/src/profiler/cpu-profiler.cc index 495840fabf5..4c35159b2e9 100644 --- a/chromium/v8/src/profiler/cpu-profiler.cc +++ b/chromium/v8/src/profiler/cpu-profiler.cc @@ -47,21 +47,56 @@ class CpuSampler : public sampler::Sampler { SamplingEventsProcessor* processor_; }; -ProfilerEventsProcessor::ProfilerEventsProcessor(Isolate* isolate, - ProfileGenerator* generator) +ProfilingScope::ProfilingScope(Isolate* isolate, ProfilerListener* listener) + : isolate_(isolate), listener_(listener) { + size_t profiler_count = isolate_->num_cpu_profilers(); + profiler_count++; + isolate_->set_num_cpu_profilers(profiler_count); + isolate_->set_is_profiling(true); + isolate_->wasm_engine()->EnableCodeLogging(isolate_); + + Logger* logger = isolate_->logger(); + logger->AddCodeEventListener(listener_); + // Populate the ProfilerCodeObserver with the initial functions and + // callbacks on the heap. + DCHECK(isolate_->heap()->HasBeenSetUp()); + + if (!FLAG_prof_browser_mode) { + logger->LogCodeObjects(); + } + logger->LogCompiledFunctions(); + logger->LogAccessorCallbacks(); +} + +ProfilingScope::~ProfilingScope() { + isolate_->logger()->RemoveCodeEventListener(listener_); + + size_t profiler_count = isolate_->num_cpu_profilers(); + DCHECK_GT(profiler_count, 0); + profiler_count--; + isolate_->set_num_cpu_profilers(profiler_count); + if (profiler_count == 0) isolate_->set_is_profiling(false); +} + +ProfilerEventsProcessor::ProfilerEventsProcessor( + Isolate* isolate, ProfileGenerator* generator, + ProfilerCodeObserver* code_observer) : Thread(Thread::Options("v8:ProfEvntProc", kProfilerStackSize)), generator_(generator), + code_observer_(code_observer), running_(1), last_code_event_id_(0), last_processed_code_event_id_(0), - isolate_(isolate), - profiling_scope_(isolate) {} - -SamplingEventsProcessor::SamplingEventsProcessor(Isolate* isolate, - ProfileGenerator* generator, - base::TimeDelta period, - bool use_precise_sampling) - : ProfilerEventsProcessor(isolate, generator), + isolate_(isolate) { + DCHECK(!code_observer_->processor()); + code_observer_->set_processor(this); +} + +SamplingEventsProcessor::SamplingEventsProcessor( + Isolate* isolate, ProfileGenerator* generator, + ProfilerCodeObserver* code_observer, base::TimeDelta period, + bool use_precise_sampling) + : ProfilerEventsProcessor(isolate, generator, code_observer), sampler_(new CpuSampler(isolate, this)), period_(period), use_precise_sampling_(use_precise_sampling) { @@ -70,7 +105,10 @@ SamplingEventsProcessor::SamplingEventsProcessor(Isolate* isolate, SamplingEventsProcessor::~SamplingEventsProcessor() { sampler_->Stop(); } -ProfilerEventsProcessor::~ProfilerEventsProcessor() = default; +ProfilerEventsProcessor::~ProfilerEventsProcessor() { + DCHECK_EQ(code_observer_->processor(), this); + code_observer_->clear_processor(); +} void ProfilerEventsProcessor::Enqueue(const CodeEventsContainer& event) { event.generic.order = ++last_code_event_id_; @@ -123,16 +161,13 @@ void ProfilerEventsProcessor::StopSynchronously() { bool ProfilerEventsProcessor::ProcessCodeEvent() { CodeEventsContainer record; if (events_buffer_.Dequeue(&record)) { - switch (record.generic.type) { -#define PROFILER_TYPE_CASE(type, clss) \ - case CodeEventRecord::type: \ - record.clss##_.UpdateCodeMap(generator_->code_map()); \ - break; - - CODE_EVENTS_TYPE_LIST(PROFILER_TYPE_CASE) - -#undef PROFILER_TYPE_CASE - default: return true; // Skip record. + if (record.generic.type == CodeEventRecord::NATIVE_CONTEXT_MOVE) { + NativeContextMoveEventRecord& nc_record = + record.NativeContextMoveEventRecord_; + generator_->UpdateNativeContextAddress(nc_record.from_address, + nc_record.to_address); + } else { + code_observer_->CodeEventHandlerInternal(record); } last_processed_code_event_id_ = record.generic.order; return true; @@ -146,6 +181,7 @@ void ProfilerEventsProcessor::CodeEventHandler( case CodeEventRecord::CODE_CREATION: case CodeEventRecord::CODE_MOVE: case CodeEventRecord::CODE_DISABLE_OPT: + case CodeEventRecord::NATIVE_CONTEXT_MOVE: Enqueue(evt_rec); break; case CodeEventRecord::CODE_DEOPT: { @@ -262,6 +298,62 @@ void* SamplingEventsProcessor::operator new(size_t size) { void SamplingEventsProcessor::operator delete(void* ptr) { AlignedFree(ptr); } +ProfilerCodeObserver::ProfilerCodeObserver(Isolate* isolate) + : isolate_(isolate), processor_(nullptr) { + CreateEntriesForRuntimeCallStats(); + LogBuiltins(); +} + +void ProfilerCodeObserver::CodeEventHandler( + const CodeEventsContainer& evt_rec) { + if (processor_) { + processor_->CodeEventHandler(evt_rec); + return; + } + CodeEventHandlerInternal(evt_rec); +} + +void ProfilerCodeObserver::CodeEventHandlerInternal( + const CodeEventsContainer& evt_rec) { + CodeEventsContainer record = evt_rec; + switch (evt_rec.generic.type) { +#define PROFILER_TYPE_CASE(type, clss) \ + case CodeEventRecord::type: \ + record.clss##_.UpdateCodeMap(&code_map_); \ + break; + + CODE_EVENTS_TYPE_LIST(PROFILER_TYPE_CASE) + +#undef PROFILER_TYPE_CASE + default: + break; + } +} + +void ProfilerCodeObserver::CreateEntriesForRuntimeCallStats() { + RuntimeCallStats* rcs = isolate_->counters()->runtime_call_stats(); + for (int i = 0; i < RuntimeCallStats::kNumberOfCounters; ++i) { + RuntimeCallCounter* counter = rcs->GetCounter(i); + DCHECK(counter->name()); + auto entry = new CodeEntry(CodeEventListener::FUNCTION_TAG, counter->name(), + "native V8Runtime"); + code_map_.AddCode(reinterpret_cast<Address>(counter), entry, 1); + } +} + +void ProfilerCodeObserver::LogBuiltins() { + Builtins* builtins = isolate_->builtins(); + DCHECK(builtins->is_initialized()); + for (int i = 0; i < Builtins::builtin_count; i++) { + CodeEventsContainer evt_rec(CodeEventRecord::REPORT_BUILTIN); + ReportBuiltinEventRecord* rec = &evt_rec.ReportBuiltinEventRecord_; + Builtins::Name id = static_cast<Builtins::Name>(i); + rec->instruction_start = builtins->builtin(id).InstructionStart(); + rec->builtin_id = id; + CodeEventHandlerInternal(evt_rec); + } +} + int CpuProfiler::GetProfilesCount() { // The count of profiles doesn't depend on a security token. return static_cast<int>(profiles_->profiles()->size()); @@ -324,29 +416,37 @@ DEFINE_LAZY_LEAKY_OBJECT_GETTER(CpuProfilersManager, GetProfilersManager) } // namespace -CpuProfiler::CpuProfiler(Isolate* isolate, CpuProfilingNamingMode naming_mode) - : CpuProfiler(isolate, naming_mode, new CpuProfilesCollection(isolate), - nullptr, nullptr) {} +CpuProfiler::CpuProfiler(Isolate* isolate, CpuProfilingNamingMode naming_mode, + CpuProfilingLoggingMode logging_mode) + : CpuProfiler(isolate, naming_mode, logging_mode, + new CpuProfilesCollection(isolate), nullptr, nullptr) {} CpuProfiler::CpuProfiler(Isolate* isolate, CpuProfilingNamingMode naming_mode, + CpuProfilingLoggingMode logging_mode, CpuProfilesCollection* test_profiles, ProfileGenerator* test_generator, ProfilerEventsProcessor* test_processor) : isolate_(isolate), naming_mode_(naming_mode), + logging_mode_(logging_mode), base_sampling_interval_(base::TimeDelta::FromMicroseconds( FLAG_cpu_profiler_sampling_interval)), profiles_(test_profiles), generator_(test_generator), processor_(test_processor), + code_observer_(isolate), is_profiling_(false) { profiles_->set_cpu_profiler(this); GetProfilersManager()->AddProfiler(isolate, this); + + if (logging_mode == kEagerLogging) EnableLogging(); } CpuProfiler::~CpuProfiler() { DCHECK(!is_profiling_); GetProfilersManager()->RemoveProfiler(isolate_, this); + + DisableLogging(); } void CpuProfiler::set_sampling_interval(base::TimeDelta value) { @@ -362,20 +462,26 @@ void CpuProfiler::set_use_precise_sampling(bool value) { void CpuProfiler::ResetProfiles() { profiles_.reset(new CpuProfilesCollection(isolate_)); profiles_->set_cpu_profiler(this); - profiler_listener_.reset(); generator_.reset(); + if (!profiling_scope_) profiler_listener_.reset(); } -void CpuProfiler::CreateEntriesForRuntimeCallStats() { - RuntimeCallStats* rcs = isolate_->counters()->runtime_call_stats(); - CodeMap* code_map = generator_->code_map(); - for (int i = 0; i < RuntimeCallStats::kNumberOfCounters; ++i) { - RuntimeCallCounter* counter = rcs->GetCounter(i); - DCHECK(counter->name()); - auto entry = new CodeEntry(CodeEventListener::FUNCTION_TAG, counter->name(), - "native V8Runtime"); - code_map->AddCode(reinterpret_cast<Address>(counter), entry, 1); +void CpuProfiler::EnableLogging() { + if (profiling_scope_) return; + + if (!profiler_listener_) { + profiler_listener_.reset( + new ProfilerListener(isolate_, &code_observer_, naming_mode_)); } + profiling_scope_.reset( + new ProfilingScope(isolate_, profiler_listener_.get())); +} + +void CpuProfiler::DisableLogging() { + if (!profiling_scope_) return; + + DCHECK(profiler_listener_); + profiling_scope_.reset(); } base::TimeDelta CpuProfiler::ComputeSamplingInterval() const { @@ -418,36 +524,23 @@ void CpuProfiler::StartProcessorIfNotStarted() { processor_->AddCurrentStack(); return; } - isolate_->wasm_engine()->EnableCodeLogging(isolate_); - Logger* logger = isolate_->logger(); - bool codemap_needs_initialization = false; + if (!profiling_scope_) { + DCHECK_EQ(logging_mode_, kLazyLogging); + EnableLogging(); + } + if (!generator_) { - generator_.reset(new ProfileGenerator(profiles_.get())); - codemap_needs_initialization = true; - CreateEntriesForRuntimeCallStats(); + generator_.reset( + new ProfileGenerator(profiles_.get(), code_observer_.code_map())); } + base::TimeDelta sampling_interval = ComputeSamplingInterval(); - processor_.reset(new SamplingEventsProcessor( - isolate_, generator_.get(), sampling_interval, use_precise_sampling_)); - if (profiler_listener_) { - profiler_listener_->set_observer(processor_.get()); - } else { - profiler_listener_.reset( - new ProfilerListener(isolate_, processor_.get(), naming_mode_)); - } - logger->AddCodeEventListener(profiler_listener_.get()); + processor_.reset( + new SamplingEventsProcessor(isolate_, generator_.get(), &code_observer_, + sampling_interval, use_precise_sampling_)); is_profiling_ = true; - // Enumerate stuff we already have in the heap. - DCHECK(isolate_->heap()->HasBeenSetUp()); - if (codemap_needs_initialization) { - if (!FLAG_prof_browser_mode) { - logger->LogCodeObjects(); - } - logger->LogCompiledFunctions(); - logger->LogAccessorCallbacks(); - LogBuiltins(); - } + // Enable stack sampling. processor_->AddCurrentStack(); processor_->StartSynchronously(); @@ -471,26 +564,14 @@ void CpuProfiler::StopProcessorIfLastProfile(const char* title) { } void CpuProfiler::StopProcessor() { - Logger* logger = isolate_->logger(); is_profiling_ = false; - logger->RemoveCodeEventListener(profiler_listener_.get()); processor_->StopSynchronously(); processor_.reset(); -} - -void CpuProfiler::LogBuiltins() { - Builtins* builtins = isolate_->builtins(); - DCHECK(builtins->is_initialized()); - for (int i = 0; i < Builtins::builtin_count; i++) { - CodeEventsContainer evt_rec(CodeEventRecord::REPORT_BUILTIN); - ReportBuiltinEventRecord* rec = &evt_rec.ReportBuiltinEventRecord_; - Builtins::Name id = static_cast<Builtins::Name>(i); - rec->instruction_start = builtins->builtin(id).InstructionStart(); - rec->builtin_id = id; - processor_->Enqueue(evt_rec); + DCHECK(profiling_scope_); + if (logging_mode_ == kLazyLogging) { + DisableLogging(); } } - } // namespace internal } // namespace v8 diff --git a/chromium/v8/src/profiler/cpu-profiler.h b/chromium/v8/src/profiler/cpu-profiler.h index 96d53356e67..093f28aba34 100644 --- a/chromium/v8/src/profiler/cpu-profiler.h +++ b/chromium/v8/src/profiler/cpu-profiler.h @@ -13,6 +13,7 @@ #include "src/base/platform/mutex.h" #include "src/base/platform/time.h" #include "src/execution/isolate.h" +#include "src/handles/maybe-handles.h" #include "src/libsampler/sampler.h" #include "src/profiler/circular-queue.h" #include "src/profiler/profiler-listener.h" @@ -30,21 +31,21 @@ class CpuProfile; class CpuProfilesCollection; class ProfileGenerator; -#define CODE_EVENTS_TYPE_LIST(V) \ - V(CODE_CREATION, CodeCreateEventRecord) \ - V(CODE_MOVE, CodeMoveEventRecord) \ - V(CODE_DISABLE_OPT, CodeDisableOptEventRecord) \ - V(CODE_DEOPT, CodeDeoptEventRecord) \ +#define CODE_EVENTS_TYPE_LIST(V) \ + V(CODE_CREATION, CodeCreateEventRecord) \ + V(CODE_MOVE, CodeMoveEventRecord) \ + V(CODE_DISABLE_OPT, CodeDisableOptEventRecord) \ + V(CODE_DEOPT, CodeDeoptEventRecord) \ V(REPORT_BUILTIN, ReportBuiltinEventRecord) +#define VM_EVENTS_TYPE_LIST(V) \ + CODE_EVENTS_TYPE_LIST(V) \ + V(NATIVE_CONTEXT_MOVE, NativeContextMoveEventRecord) class CodeEventRecord { public: #define DECLARE_TYPE(type, ignore) type, - enum Type { - NONE = 0, - CODE_EVENTS_TYPE_LIST(DECLARE_TYPE) - }; + enum Type { NONE = 0, VM_EVENTS_TYPE_LIST(DECLARE_TYPE) }; #undef DECLARE_TYPE Type type; @@ -102,6 +103,12 @@ class ReportBuiltinEventRecord : public CodeEventRecord { V8_INLINE void UpdateCodeMap(CodeMap* code_map); }; +// Signals that a native context's address has changed. +class NativeContextMoveEventRecord : public CodeEventRecord { + public: + Address from_address; + Address to_address; +}; class TickSampleEventRecord { public: @@ -124,33 +131,25 @@ class CodeEventsContainer { union { CodeEventRecord generic; #define DECLARE_CLASS(ignore, type) type type##_; - CODE_EVENTS_TYPE_LIST(DECLARE_CLASS) + VM_EVENTS_TYPE_LIST(DECLARE_CLASS) #undef DECLARE_CLASS }; }; -// Maintains the number of active CPU profilers in an isolate. +// Maintains the number of active CPU profilers in an isolate, and routes +// logging to a given ProfilerListener. class ProfilingScope { public: - explicit ProfilingScope(Isolate* isolate) : isolate_(isolate) { - size_t profiler_count = isolate_->num_cpu_profilers(); - profiler_count++; - isolate_->set_num_cpu_profilers(profiler_count); - isolate_->set_is_profiling(true); - } - - ~ProfilingScope() { - size_t profiler_count = isolate_->num_cpu_profilers(); - DCHECK_GT(profiler_count, 0); - profiler_count--; - isolate_->set_num_cpu_profilers(profiler_count); - if (profiler_count == 0) isolate_->set_is_profiling(false); - } + ProfilingScope(Isolate* isolate, ProfilerListener* listener); + ~ProfilingScope(); private: Isolate* const isolate_; + ProfilerListener* const listener_; }; +class ProfilerCodeObserver; + // This class implements both the profile events processor thread and // methods called by event producers: VM and stack sampler threads. class V8_EXPORT_PRIVATE ProfilerEventsProcessor : public base::Thread, @@ -175,7 +174,8 @@ class V8_EXPORT_PRIVATE ProfilerEventsProcessor : public base::Thread, virtual void SetSamplingInterval(base::TimeDelta) {} protected: - ProfilerEventsProcessor(Isolate* isolate, ProfileGenerator* generator); + ProfilerEventsProcessor(Isolate* isolate, ProfileGenerator* generator, + ProfilerCodeObserver* code_observer); // Called from events processing thread (Run() method.) bool ProcessCodeEvent(); @@ -188,6 +188,7 @@ class V8_EXPORT_PRIVATE ProfilerEventsProcessor : public base::Thread, virtual SampleProcessingResult ProcessOneSample() = 0; ProfileGenerator* generator_; + ProfilerCodeObserver* code_observer_; base::Atomic32 running_; base::ConditionVariable running_cond_; base::Mutex running_mutex_; @@ -196,13 +197,13 @@ class V8_EXPORT_PRIVATE ProfilerEventsProcessor : public base::Thread, std::atomic<unsigned> last_code_event_id_; unsigned last_processed_code_event_id_; Isolate* isolate_; - ProfilingScope profiling_scope_; }; class V8_EXPORT_PRIVATE SamplingEventsProcessor : public ProfilerEventsProcessor { public: SamplingEventsProcessor(Isolate* isolate, ProfileGenerator* generator, + ProfilerCodeObserver* code_observer, base::TimeDelta period, bool use_precise_sampling); ~SamplingEventsProcessor() override; @@ -241,11 +242,47 @@ class V8_EXPORT_PRIVATE SamplingEventsProcessor // low sampling intervals on Windows. }; +// Builds and maintains a CodeMap tracking code objects on the VM heap. While +// alive, logs generated code, callbacks, and builtins from the isolate. +// Redirects events to the profiler events processor when present. +class V8_EXPORT_PRIVATE ProfilerCodeObserver : public CodeEventObserver { + public: + explicit ProfilerCodeObserver(Isolate*); + + void CodeEventHandler(const CodeEventsContainer& evt_rec) override; + + CodeMap* code_map() { return &code_map_; } + + private: + friend class ProfilerEventsProcessor; + + void CodeEventHandlerInternal(const CodeEventsContainer& evt_rec); + + void CreateEntriesForRuntimeCallStats(); + void LogBuiltins(); + + ProfilerEventsProcessor* processor() { return processor_; } + + // Redirects code events to be enqueued on the given events processor. + void set_processor(ProfilerEventsProcessor* processor) { + processor_ = processor; + } + + // Stops redirection of code events onto an events processor. + void clear_processor() { processor_ = nullptr; } + + Isolate* const isolate_; + CodeMap code_map_; + ProfilerEventsProcessor* processor_; +}; + class V8_EXPORT_PRIVATE CpuProfiler { public: - explicit CpuProfiler(Isolate* isolate, CpuProfilingNamingMode = kDebugNaming); + explicit CpuProfiler(Isolate* isolate, CpuProfilingNamingMode = kDebugNaming, + CpuProfilingLoggingMode = kLazyLogging); CpuProfiler(Isolate* isolate, CpuProfilingNamingMode naming_mode, + CpuProfilingLoggingMode logging_mode, CpuProfilesCollection* profiles, ProfileGenerator* test_generator, ProfilerEventsProcessor* test_processor); @@ -255,6 +292,7 @@ class V8_EXPORT_PRIVATE CpuProfiler { using ProfilingMode = v8::CpuProfilingMode; using NamingMode = v8::CpuProfilingNamingMode; + using LoggingMode = v8::CpuProfilingLoggingMode; base::TimeDelta sampling_interval() const { return base_sampling_interval_; } void set_sampling_interval(base::TimeDelta value); @@ -262,6 +300,7 @@ class V8_EXPORT_PRIVATE CpuProfiler { void CollectSample(); void StartProfiling(const char* title, CpuProfilingOptions options = {}); void StartProfiling(String title, CpuProfilingOptions options = {}); + CpuProfile* StopProfiling(const char* title); CpuProfile* StopProfiling(String title); int GetProfilesCount(); @@ -284,8 +323,9 @@ class V8_EXPORT_PRIVATE CpuProfiler { void StopProcessorIfLastProfile(const char* title); void StopProcessor(); void ResetProfiles(); - void LogBuiltins(); - void CreateEntriesForRuntimeCallStats(); + + void EnableLogging(); + void DisableLogging(); // Computes a sampling interval sufficient to accomodate attached profiles. base::TimeDelta ComputeSamplingInterval() const; @@ -295,6 +335,7 @@ class V8_EXPORT_PRIVATE CpuProfiler { Isolate* const isolate_; const NamingMode naming_mode_; + const LoggingMode logging_mode_; bool use_precise_sampling_ = true; // Sampling interval to which per-profile sampling intervals will be clamped // to a multiple of, or used as the default if unspecified. @@ -303,6 +344,8 @@ class V8_EXPORT_PRIVATE CpuProfiler { std::unique_ptr<ProfileGenerator> generator_; std::unique_ptr<ProfilerEventsProcessor> processor_; std::unique_ptr<ProfilerListener> profiler_listener_; + std::unique_ptr<ProfilingScope> profiling_scope_; + ProfilerCodeObserver code_observer_; bool is_profiling_; DISALLOW_COPY_AND_ASSIGN(CpuProfiler); diff --git a/chromium/v8/src/profiler/heap-profiler.cc b/chromium/v8/src/profiler/heap-profiler.cc index 472dbdbb10b..a498e8e2143 100644 --- a/chromium/v8/src/profiler/heap-profiler.cc +++ b/chromium/v8/src/profiler/heap-profiler.cc @@ -151,6 +151,17 @@ SnapshotObjectId HeapProfiler::GetSnapshotObjectId(Handle<Object> obj) { return ids_->FindEntry(HeapObject::cast(*obj).address()); } +SnapshotObjectId HeapProfiler::GetSnapshotObjectId(NativeObject obj) { + // Try to find id of regular native node first. + SnapshotObjectId id = ids_->FindEntry(reinterpret_cast<Address>(obj)); + // In case no id has been found, check whether there exists an entry where the + // native objects has been merged into a V8 entry. + if (id == v8::HeapProfiler::kUnknownObjectId) { + id = ids_->FindMergedNativeEntry(obj); + } + return id; +} + void HeapProfiler::ObjectMoveEvent(Address from, Address to, int size) { base::MutexGuard guard(&profiler_mutex_); bool known_object = ids_->MoveObject(from, to, size); diff --git a/chromium/v8/src/profiler/heap-profiler.h b/chromium/v8/src/profiler/heap-profiler.h index 940574282ef..f7336eb6be5 100644 --- a/chromium/v8/src/profiler/heap-profiler.h +++ b/chromium/v8/src/profiler/heap-profiler.h @@ -52,6 +52,7 @@ class HeapProfiler : public HeapObjectAllocationTracker { int GetSnapshotsCount(); HeapSnapshot* GetSnapshot(int index); SnapshotObjectId GetSnapshotObjectId(Handle<Object> obj); + SnapshotObjectId GetSnapshotObjectId(NativeObject obj); void DeleteAllSnapshots(); void RemoveSnapshot(HeapSnapshot* snapshot); diff --git a/chromium/v8/src/profiler/heap-snapshot-generator.cc b/chromium/v8/src/profiler/heap-snapshot-generator.cc index df941eda96a..75b6aa7b77e 100644 --- a/chromium/v8/src/profiler/heap-snapshot-generator.cc +++ b/chromium/v8/src/profiler/heap-snapshot-generator.cc @@ -352,7 +352,7 @@ void HeapObjectsMap::UpdateObjectSize(Address addr, int size) { SnapshotObjectId HeapObjectsMap::FindEntry(Address addr) { base::HashMap::Entry* entry = entries_map_.Lookup( reinterpret_cast<void*>(addr), ComputeAddressHash(addr)); - if (entry == nullptr) return 0; + if (entry == nullptr) return v8::HeapProfiler::kUnknownObjectId; int entry_index = static_cast<int>(reinterpret_cast<intptr_t>(entry->value)); EntryInfo& entry_info = entries_.at(entry_index); DCHECK(static_cast<uint32_t>(entries_.size()) > entries_map_.occupancy()); @@ -386,6 +386,25 @@ SnapshotObjectId HeapObjectsMap::FindOrAddEntry(Address addr, return id; } +SnapshotObjectId HeapObjectsMap::FindMergedNativeEntry(NativeObject addr) { + auto it = merged_native_entries_map_.find(addr); + if (it == merged_native_entries_map_.end()) + return v8::HeapProfiler::kUnknownObjectId; + return entries_[it->second].id; +} + +void HeapObjectsMap::AddMergedNativeEntry(NativeObject addr, + Address canonical_addr) { + base::HashMap::Entry* entry = + entries_map_.Lookup(reinterpret_cast<void*>(canonical_addr), + ComputeAddressHash(canonical_addr)); + auto result = merged_native_entries_map_.insert( + {addr, reinterpret_cast<size_t>(entry->value)}); + if (!result.second) { + result.first->second = reinterpret_cast<size_t>(entry->value); + } +} + void HeapObjectsMap::StopHeapObjectsTracking() { time_intervals_.clear(); } void HeapObjectsMap::UpdateHeapObjectsMap() { @@ -465,9 +484,20 @@ SnapshotObjectId HeapObjectsMap::PushHeapObjectsStats(OutputStream* stream, void HeapObjectsMap::RemoveDeadEntries() { DCHECK(entries_.size() > 0 && entries_.at(0).id == 0 && entries_.at(0).addr == kNullAddress); + + // Build up temporary reverse map. + std::unordered_map<size_t, NativeObject> reverse_merged_native_entries_map; + for (const auto& it : merged_native_entries_map_) { + auto result = + reverse_merged_native_entries_map.emplace(it.second, it.first); + DCHECK(result.second); + USE(result); + } + size_t first_free_entry = 1; for (size_t i = 1; i < entries_.size(); ++i) { EntryInfo& entry_info = entries_.at(i); + auto merged_reverse_it = reverse_merged_native_entries_map.find(i); if (entry_info.accessed) { if (first_free_entry != i) { entries_.at(first_free_entry) = entry_info; @@ -478,11 +508,19 @@ void HeapObjectsMap::RemoveDeadEntries() { ComputeAddressHash(entry_info.addr)); DCHECK(entry); entry->value = reinterpret_cast<void*>(first_free_entry); + if (merged_reverse_it != reverse_merged_native_entries_map.end()) { + auto it = merged_native_entries_map_.find(merged_reverse_it->second); + DCHECK_NE(merged_native_entries_map_.end(), it); + it->second = first_free_entry; + } ++first_free_entry; } else { if (entry_info.addr) { entries_map_.Remove(reinterpret_cast<void*>(entry_info.addr), ComputeAddressHash(entry_info.addr)); + if (merged_reverse_it != reverse_merged_native_entries_map.end()) { + merged_native_entries_map_.erase(merged_reverse_it->second); + } } } } @@ -1853,10 +1891,14 @@ HeapEntry* EmbedderGraphEntriesAllocator::AllocateEntry(HeapThing ptr) { reinterpret_cast<EmbedderGraphImpl::Node*>(ptr); DCHECK(node->IsEmbedderNode()); size_t size = node->SizeInBytes(); - return snapshot_->AddEntry( - EmbedderGraphNodeType(node), EmbedderGraphNodeName(names_, node), - static_cast<SnapshotObjectId>(reinterpret_cast<uintptr_t>(node) << 1), - static_cast<int>(size), 0); + Address lookup_address = reinterpret_cast<Address>(node->GetNativeObject()); + SnapshotObjectId id = + (lookup_address) ? heap_object_map_->FindOrAddEntry(lookup_address, 0) + : static_cast<SnapshotObjectId>( + reinterpret_cast<uintptr_t>(node) << 1); + return snapshot_->AddEntry(EmbedderGraphNodeType(node), + EmbedderGraphNodeName(names_, node), id, + static_cast<int>(size), 0); } NativeObjectsExplorer::NativeObjectsExplorer( @@ -1865,12 +1907,14 @@ NativeObjectsExplorer::NativeObjectsExplorer( Isolate::FromHeap(snapshot->profiler()->heap_object_map()->heap())), snapshot_(snapshot), names_(snapshot_->profiler()->names()), + heap_object_map_(snapshot_->profiler()->heap_object_map()), embedder_graph_entries_allocator_( new EmbedderGraphEntriesAllocator(snapshot)) {} HeapEntry* NativeObjectsExplorer::EntryForEmbedderGraphNode( EmbedderGraphImpl::Node* node) { EmbedderGraphImpl::Node* wrapper = node->WrapperNode(); + NativeObject native_object = node->GetNativeObject(); if (wrapper) { node = wrapper; } @@ -1882,8 +1926,16 @@ HeapEntry* NativeObjectsExplorer::EntryForEmbedderGraphNode( static_cast<EmbedderGraphImpl::V8NodeImpl*>(node); Object object = v8_node->GetObject(); if (object.IsSmi()) return nullptr; - return generator_->FindEntry( + HeapEntry* entry = generator_->FindEntry( reinterpret_cast<void*>(Object::cast(object).ptr())); + if (native_object) { + HeapObject heap_object = HeapObject::cast(object); + heap_object_map_->AddMergedNativeEntry(native_object, + heap_object.address()); + DCHECK_EQ(entry->id(), + heap_object_map_->FindMergedNativeEntry(native_object)); + } + return entry; } } @@ -1945,13 +1997,13 @@ HeapSnapshotGenerator::HeapSnapshotGenerator( } namespace { -class NullContextScope { +class NullContextForSnapshotScope { public: - explicit NullContextScope(Isolate* isolate) + explicit NullContextForSnapshotScope(Isolate* isolate) : isolate_(isolate), prev_(isolate->context()) { isolate_->set_context(Context()); } - ~NullContextScope() { isolate_->set_context(prev_); } + ~NullContextForSnapshotScope() { isolate_->set_context(prev_); } private: Isolate* isolate_; @@ -1971,7 +2023,7 @@ bool HeapSnapshotGenerator::GenerateSnapshot() { heap_->PreciseCollectAllGarbage(Heap::kNoGCFlags, GarbageCollectionReason::kHeapProfiler); - NullContextScope null_context_scope(Isolate::FromHeap(heap_)); + NullContextForSnapshotScope null_context_scope(Isolate::FromHeap(heap_)); #ifdef VERIFY_HEAP Heap* debug_heap = heap_; diff --git a/chromium/v8/src/profiler/heap-snapshot-generator.h b/chromium/v8/src/profiler/heap-snapshot-generator.h index d3d3330e27b..360ed1f0092 100644 --- a/chromium/v8/src/profiler/heap-snapshot-generator.h +++ b/chromium/v8/src/profiler/heap-snapshot-generator.h @@ -82,8 +82,8 @@ class HeapGraphEdge { V8_INLINE HeapSnapshot* snapshot() const; int from_index() const { return FromIndexField::decode(bit_field_); } - class TypeField : public BitField<Type, 0, 3> {}; - class FromIndexField : public BitField<int, 3, 29> {}; + using TypeField = BitField<Type, 0, 3>; + using FromIndexField = BitField<int, 3, 29>; uint32_t bit_field_; HeapEntry* to_entry_; union { @@ -249,6 +249,8 @@ class HeapObjectsMap { SnapshotObjectId FindOrAddEntry(Address addr, unsigned int size, bool accessed = true); + SnapshotObjectId FindMergedNativeEntry(NativeObject addr); + void AddMergedNativeEntry(NativeObject addr, Address canonical_addr); bool MoveObject(Address from, Address to, int size); void UpdateObjectSize(Address addr, int size); SnapshotObjectId last_assigned_id() const { @@ -285,6 +287,8 @@ class HeapObjectsMap { base::HashMap entries_map_; std::vector<EntryInfo> entries_; std::vector<TimeInterval> time_intervals_; + // Map from NativeObject to EntryInfo index in entries_. + std::unordered_map<NativeObject, size_t> merged_native_entries_map_; Heap* heap_; DISALLOW_COPY_AND_ASSIGN(HeapObjectsMap); @@ -453,6 +457,7 @@ class NativeObjectsExplorer { Isolate* isolate_; HeapSnapshot* snapshot_; StringsStorage* names_; + HeapObjectsMap* heap_object_map_; std::unique_ptr<HeapEntriesAllocator> embedder_graph_entries_allocator_; // Used during references extraction. HeapSnapshotGenerator* generator_ = nullptr; diff --git a/chromium/v8/src/profiler/profile-generator-inl.h b/chromium/v8/src/profiler/profile-generator-inl.h index 2d73c20a37f..bb5ef0da5b7 100644 --- a/chromium/v8/src/profiler/profile-generator-inl.h +++ b/chromium/v8/src/profiler/profile-generator-inl.h @@ -28,7 +28,7 @@ CodeEntry::CodeEntry(CodeEventListener::LogEventsAndTags tag, const char* name, instruction_start_(instruction_start) {} inline CodeEntry* ProfileGenerator::FindEntry(Address address) { - CodeEntry* entry = code_map_.FindEntry(address); + CodeEntry* entry = code_map_->FindEntry(address); if (entry) entry->mark_used(); return entry; } diff --git a/chromium/v8/src/profiler/profile-generator.cc b/chromium/v8/src/profiler/profile-generator.cc index e869f657627..f5f71846136 100644 --- a/chromium/v8/src/profiler/profile-generator.cc +++ b/chromium/v8/src/profiler/profile-generator.cc @@ -19,6 +19,14 @@ void SourcePositionTable::SetPosition(int pc_offset, int line, int inlining_id) { DCHECK_GE(pc_offset, 0); DCHECK_GT(line, 0); // The 1-based number of the source line. + // It's possible that we map multiple source positions to a pc_offset in + // optimized code. Usually these map to the same line, so there is no + // difference here as we only store line number and not line/col in the form + // of a script offset. Ignore any subsequent sets to the same offset. + if (!pc_offsets_to_lines_.empty() && + pc_offsets_to_lines_.back().pc_offset == pc_offset) { + return; + } // Check that we are inserting in ascending order, so that the vector remains // sorted. DCHECK(pc_offsets_to_lines_.empty() || @@ -404,16 +412,18 @@ ProfileNode* ProfileTree::AddPathFromEnd(const std::vector<CodeEntry*>& path, ProfileNode* ProfileTree::AddPathFromEnd(const ProfileStackTrace& path, int src_line, bool update_stats, - ProfilingMode mode) { + ProfilingMode mode, + ContextFilter* context_filter) { ProfileNode* node = root_; CodeEntry* last_entry = nullptr; int parent_line_number = v8::CpuProfileNode::kNoLineNumberInfo; for (auto it = path.rbegin(); it != path.rend(); ++it) { - if ((*it).code_entry == nullptr) continue; - last_entry = (*it).code_entry; - node = node->FindOrAddChild((*it).code_entry, parent_line_number); + if (it->entry.code_entry == nullptr) continue; + if (context_filter && !context_filter->Accept(*it)) continue; + last_entry = (*it).entry.code_entry; + node = node->FindOrAddChild((*it).entry.code_entry, parent_line_number); parent_line_number = mode == ProfilingMode::kCallerLineNumbers - ? (*it).line_number + ? (*it).entry.line_number : v8::CpuProfileNode::kNoLineNumberInfo; } if (last_entry && last_entry->has_deopt_info()) { @@ -428,7 +438,6 @@ ProfileNode* ProfileTree::AddPathFromEnd(const ProfileStackTrace& path, return node; } - class Position { public: explicit Position(ProfileNode* node) @@ -470,6 +479,21 @@ void ProfileTree::TraverseDepthFirst(Callback* callback) { } } +bool ContextFilter::Accept(const ProfileStackFrame& frame) { + // If a frame should always be included in profiles (e.g. metadata frames), + // skip the context check. + if (!frame.filterable) return true; + + // Strip heap object tag from frame. + return (frame.native_context & ~kHeapObjectTag) == native_context_address_; +} + +void ContextFilter::OnMoveEvent(Address from_address, Address to_address) { + if (native_context_address() != from_address) return; + + set_native_context_address(to_address); +} + using v8::tracing::TracedValue; std::atomic<uint32_t> CpuProfile::last_id_; @@ -488,6 +512,13 @@ CpuProfile::CpuProfile(CpuProfiler* profiler, const char* title, (start_time_ - base::TimeTicks()).InMicroseconds()); TRACE_EVENT_SAMPLE_WITH_ID1(TRACE_DISABLED_BY_DEFAULT("v8.cpu_profiler"), "Profile", id_, "data", std::move(value)); + + if (options_.has_filter_context()) { + DisallowHeapAllocation no_gc; + i::Address raw_filter_context = + reinterpret_cast<i::Address>(options_.raw_filter_context()); + context_filter_ = base::make_unique<ContextFilter>(raw_filter_context); + } } bool CpuProfile::CheckSubsample(base::TimeDelta source_sampling_interval) { @@ -512,11 +543,11 @@ void CpuProfile::AddPath(base::TimeTicks timestamp, bool update_stats, base::TimeDelta sampling_interval) { if (!CheckSubsample(sampling_interval)) return; - ProfileNode* top_frame_node = - top_down_.AddPathFromEnd(path, src_line, update_stats, options_.mode()); + ProfileNode* top_frame_node = top_down_.AddPathFromEnd( + path, src_line, update_stats, options_.mode(), context_filter_.get()); bool should_record_sample = - !timestamp.IsNull() && + !timestamp.IsNull() && timestamp >= start_time_ && (options_.max_samples() == CpuProfilingOptions::kNoSampleLimit || samples_.size() < options_.max_samples()); @@ -615,6 +646,8 @@ void CpuProfile::StreamPendingTraceEvents() { void CpuProfile::FinishProfile() { end_time_ = base::TimeTicks::HighResolutionNow(); + // Stop tracking context movements after profiling stops. + context_filter_ = nullptr; StreamPendingTraceEvents(); auto value = TracedValue::Create(); value->SetDouble("endTime", (end_time_ - base::TimeTicks()).InMicroseconds()); @@ -825,8 +858,20 @@ void CpuProfilesCollection::AddPathToCurrentProfiles( current_profiles_semaphore_.Signal(); } -ProfileGenerator::ProfileGenerator(CpuProfilesCollection* profiles) - : profiles_(profiles) {} +void CpuProfilesCollection::UpdateNativeContextAddressForCurrentProfiles( + Address from, Address to) { + current_profiles_semaphore_.Wait(); + for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) { + if (auto* context_filter = profile->context_filter()) { + context_filter->OnMoveEvent(from, to); + } + } + current_profiles_semaphore_.Signal(); +} + +ProfileGenerator::ProfileGenerator(CpuProfilesCollection* profiles, + CodeMap* code_map) + : profiles_(profiles), code_map_(code_map) {} void ProfileGenerator::RecordTickSample(const TickSample& sample) { ProfileStackTrace stack_trace; @@ -848,9 +893,11 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { // Don't use PC when in external callback code, as it can point // inside a callback's code, and we will erroneously report // that a callback calls itself. - stack_trace.push_back( - {FindEntry(reinterpret_cast<Address>(sample.external_callback_entry)), - no_line_info}); + stack_trace.push_back({{FindEntry(reinterpret_cast<Address>( + sample.external_callback_entry)), + no_line_info}, + reinterpret_cast<Address>(sample.top_context), + true}); } else { Address attributed_pc = reinterpret_cast<Address>(sample.pc); CodeEntry* pc_entry = FindEntry(attributed_pc); @@ -874,7 +921,9 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { src_line = pc_entry->line_number(); } src_line_not_found = false; - stack_trace.push_back({pc_entry, src_line}); + stack_trace.push_back({{pc_entry, src_line}, + reinterpret_cast<Address>(sample.top_context), + true}); if (pc_entry->builtin_id() == Builtins::kFunctionPrototypeApply || pc_entry->builtin_id() == Builtins::kFunctionPrototypeCall) { @@ -886,7 +935,9 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { // 'unresolved' entry. if (!sample.has_external_callback) { stack_trace.push_back( - {CodeEntry::unresolved_entry(), no_line_info}); + {{CodeEntry::unresolved_entry(), no_line_info}, + kNullAddress, + true}); } } } @@ -894,6 +945,7 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { for (unsigned i = 0; i < sample.frames_count; ++i) { Address stack_pos = reinterpret_cast<Address>(sample.stack[i]); + Address native_context = reinterpret_cast<Address>(sample.contexts[i]); CodeEntry* entry = FindEntry(stack_pos); int line_number = no_line_info; if (entry) { @@ -905,8 +957,13 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { entry->GetInlineStack(pc_offset); if (inline_stack) { int most_inlined_frame_line_number = entry->GetSourceLine(pc_offset); - stack_trace.insert(stack_trace.end(), inline_stack->begin(), - inline_stack->end()); + for (auto entry : *inline_stack) { + // Set the native context of inlined frames to be equal to that of + // their parent. This is safe, as functions cannot inline themselves + // into a parent from another native context. + stack_trace.push_back({entry, native_context, true}); + } + // This is a bit of a messy hack. The line number for the most-inlined // frame (the function at the end of the chain of function calls) has // the wrong line number in inline_stack. The actual line number in @@ -916,7 +973,7 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { // inlining_id. DCHECK(!inline_stack->empty()); size_t index = stack_trace.size() - inline_stack->size(); - stack_trace[index].line_number = most_inlined_frame_line_number; + stack_trace[index].entry.line_number = most_inlined_frame_line_number; } // Skip unresolved frames (e.g. internal frame) and get source line of // the first JS caller. @@ -935,21 +992,22 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { // so we use it instead of pushing entry to stack_trace. if (inline_stack) continue; } - stack_trace.push_back({entry, line_number}); + stack_trace.push_back({{entry, line_number}, native_context, true}); } } if (FLAG_prof_browser_mode) { bool no_symbolized_entries = true; for (auto e : stack_trace) { - if (e.code_entry != nullptr) { + if (e.entry.code_entry != nullptr) { no_symbolized_entries = false; break; } } // If no frames were symbolized, put the VM state entry in. if (no_symbolized_entries) { - stack_trace.push_back({EntryForVMState(sample.state), no_line_info}); + stack_trace.push_back( + {{EntryForVMState(sample.state), no_line_info}, kNullAddress, false}); } } @@ -958,6 +1016,10 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) { sample.sampling_interval); } +void ProfileGenerator::UpdateNativeContextAddress(Address from, Address to) { + profiles_->UpdateNativeContextAddressForCurrentProfiles(from, to); +} + CodeEntry* ProfileGenerator::EntryForVMState(StateTag tag) { switch (tag) { case GC: diff --git a/chromium/v8/src/profiler/profile-generator.h b/chromium/v8/src/profiler/profile-generator.h index b0543c9d794..2f7273a0862 100644 --- a/chromium/v8/src/profiler/profile-generator.h +++ b/chromium/v8/src/profiler/profile-generator.h @@ -234,7 +234,36 @@ struct CodeEntryAndLineNumber { int line_number; }; -using ProfileStackTrace = std::vector<CodeEntryAndLineNumber>; +struct ProfileStackFrame { + CodeEntryAndLineNumber entry; + Address native_context; + bool filterable; // If true, the frame should be filtered by context (if a + // filter is present). +}; + +typedef std::vector<ProfileStackFrame> ProfileStackTrace; + +// Filters stack frames from sources other than a target native context. +class ContextFilter { + public: + explicit ContextFilter(Address native_context_address) + : native_context_address_(native_context_address) {} + + // Returns true if the stack frame passes a context check. + bool Accept(const ProfileStackFrame&); + + // Invoked when a native context has changed address. + void OnMoveEvent(Address from_address, Address to_address); + + // Update the context's tracked address based on VM-thread events. + void set_native_context_address(Address address) { + native_context_address_ = address; + } + Address native_context_address() const { return native_context_address_; } + + private: + Address native_context_address_; +}; class ProfileTree; @@ -321,7 +350,8 @@ class V8_EXPORT_PRIVATE ProfileTree { const ProfileStackTrace& path, int src_line = v8::CpuProfileNode::kNoLineNumberInfo, bool update_stats = true, - ProfilingMode mode = ProfilingMode::kLeafNodeLineNumbers); + ProfilingMode mode = ProfilingMode::kLeafNodeLineNumbers, + ContextFilter* context_filter = nullptr); ProfileNode* root() const { return root_; } unsigned next_node_id() { return next_node_id_++; } unsigned GetFunctionId(const ProfileNode* node); @@ -389,6 +419,7 @@ class CpuProfile { base::TimeTicks start_time() const { return start_time_; } base::TimeTicks end_time() const { return end_time_; } CpuProfiler* cpu_profiler() const { return profiler_; } + ContextFilter* context_filter() const { return context_filter_.get(); } void UpdateTicksScale(); @@ -399,6 +430,7 @@ class CpuProfile { const char* title_; const CpuProfilingOptions options_; + std::unique_ptr<ContextFilter> context_filter_; base::TimeTicks start_time_; base::TimeTicks end_time_; std::deque<SampleInfo> samples_; @@ -477,6 +509,9 @@ class V8_EXPORT_PRIVATE CpuProfilesCollection { bool update_stats, base::TimeDelta sampling_interval); + // Called from profile generator thread. + void UpdateNativeContextAddressForCurrentProfiles(Address from, Address to); + // Limits the number of profiles that can be simultaneously collected. static const int kMaxSimultaneousProfiles = 100; @@ -494,18 +529,20 @@ class V8_EXPORT_PRIVATE CpuProfilesCollection { class V8_EXPORT_PRIVATE ProfileGenerator { public: - explicit ProfileGenerator(CpuProfilesCollection* profiles); + explicit ProfileGenerator(CpuProfilesCollection* profiles, CodeMap* code_map); void RecordTickSample(const TickSample& sample); - CodeMap* code_map() { return &code_map_; } + void UpdateNativeContextAddress(Address from, Address to); + + CodeMap* code_map() { return code_map_; } private: CodeEntry* FindEntry(Address address); CodeEntry* EntryForVMState(StateTag tag); CpuProfilesCollection* profiles_; - CodeMap code_map_; + CodeMap* const code_map_; DISALLOW_COPY_AND_ASSIGN(ProfileGenerator); }; diff --git a/chromium/v8/src/profiler/profiler-listener.cc b/chromium/v8/src/profiler/profiler-listener.cc index 156c1b8bb01..b00c1f5cfd7 100644 --- a/chromium/v8/src/profiler/profiler-listener.cc +++ b/chromium/v8/src/profiler/profiler-listener.cc @@ -177,8 +177,7 @@ void ProfilerListener::CodeCreateEvent(CodeEventListener::LogEventsAndTags tag, CodeEntry* cached_entry = GetOrInsertCachedEntry( &cached_inline_entries, std::move(inline_entry)); - inline_stack.push_back( - CodeEntryAndLineNumber{cached_entry, line_number}); + inline_stack.push_back({cached_entry, line_number}); } DCHECK(!inline_stack.empty()); inline_stacks.emplace(inlining_id, std::move(inline_stack)); @@ -280,6 +279,13 @@ void ProfilerListener::SetterCallbackEvent(Name name, Address entry_point) { DispatchCodeEvent(evt_rec); } +void ProfilerListener::NativeContextMoveEvent(Address from, Address to) { + CodeEventsContainer evt_rec(CodeEventRecord::NATIVE_CONTEXT_MOVE); + evt_rec.NativeContextMoveEventRecord_.from_address = from; + evt_rec.NativeContextMoveEventRecord_.to_address = to; + DispatchCodeEvent(evt_rec); +} + Name ProfilerListener::InferScriptName(Name name, SharedFunctionInfo info) { if (name.IsString() && String::cast(name).length()) return name; if (!info.script().IsScript()) return name; diff --git a/chromium/v8/src/profiler/profiler-listener.h b/chromium/v8/src/profiler/profiler-listener.h index 6ca4225e540..85070d65aaf 100644 --- a/chromium/v8/src/profiler/profiler-listener.h +++ b/chromium/v8/src/profiler/profiler-listener.h @@ -55,6 +55,7 @@ class V8_EXPORT_PRIVATE ProfilerListener : public CodeEventListener { void RegExpCodeCreateEvent(AbstractCode code, String source) override; void SetterCallbackEvent(Name name, Address entry_point) override; void SharedFunctionInfoMoveEvent(Address from, Address to) override {} + void NativeContextMoveEvent(Address from, Address to) override; const char* GetName(Name name) { return function_and_resource_names_.GetName(name); diff --git a/chromium/v8/src/profiler/tick-sample.cc b/chromium/v8/src/profiler/tick-sample.cc index b3ea07db34f..5c2f2d63ce3 100644 --- a/chromium/v8/src/profiler/tick-sample.cc +++ b/chromium/v8/src/profiler/tick-sample.cc @@ -16,6 +16,7 @@ #include "src/sanitizer/msan.h" namespace v8 { +namespace internal { namespace { bool IsSamePage(i::Address ptr1, i::Address ptr2) { @@ -78,11 +79,6 @@ bool IsNoFrameRegion(i::Address address) { return false; } -} // namespace - -namespace internal { -namespace { - #if defined(USE_SIMULATOR) class SimulatorHelper { public: @@ -147,22 +143,56 @@ bool SimulatorHelper::FillRegisters(Isolate* isolate, } #endif // USE_SIMULATOR +// Attempts to safely dereference the address of a native context at a given +// context's address. Returns kNullAddress on failure, in the event that the +// context is in an inconsistent state. +Address ScrapeNativeContextAddress(Heap* heap, Address context_address) { + DCHECK_EQ(heap->gc_state(), Heap::NOT_IN_GC); + + if (!HAS_STRONG_HEAP_OBJECT_TAG(context_address)) return kNullAddress; + + if (heap->memory_allocator()->IsOutsideAllocatedSpace(context_address)) + return kNullAddress; + + // Note that once a native context has been assigned to a context, the slot + // is no longer mutated except during pointer updates / evictions. Since + // pointer updates exclusively occur on the main thread, and we don't record + // TickSamples when the main thread's VM state is GC, the only other + // situation where the address here would be invalid is if it's being + // reassigned -- which isn't possible. + int native_context_offset = + i::Context::SlotOffset(i::Context::NATIVE_CONTEXT_INDEX); + i::Address native_context_slot_address = + context_address + native_context_offset; + + // By the prior hypothesis, the indirect native context address should always + // be valid. + if (heap->memory_allocator()->IsOutsideAllocatedSpace( + native_context_slot_address)) { + DCHECK(false); + return kNullAddress; + } + + i::ObjectSlot native_context_slot(native_context_slot_address); + i::Object native_context = native_context_slot.Relaxed_Load(); + + return native_context.ptr(); +} + } // namespace -} // namespace internal -// -// StackTracer implementation -// DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate, const RegisterState& reg_state, RecordCEntryFrame record_c_entry_frame, bool update_stats, - bool use_simulator_reg_state) { + bool use_simulator_reg_state, + base::TimeDelta sampling_interval) { this->update_stats = update_stats; SampleInfo info; RegisterState regs = reg_state; if (!GetStackSample(v8_isolate, ®s, record_c_entry_frame, stack, - kMaxFramesCount, &info, use_simulator_reg_state)) { + kMaxFramesCount, &info, use_simulator_reg_state, + contexts)) { // It is executing JS but failed to collect a stack trace. // Mark the sample as spoiled. pc = nullptr; @@ -173,6 +203,7 @@ DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate, pc = regs.pc; frames_count = static_cast<unsigned>(info.frames_count); has_external_callback = info.external_callback_entry != nullptr; + top_context = info.top_context; if (has_external_callback) { external_callback_entry = info.external_callback_entry; } else if (frames_count) { @@ -191,17 +222,20 @@ DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate, } else { tos = nullptr; } + this->sampling_interval = sampling_interval; + timestamp = base::TimeTicks::HighResolutionNow(); } bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs, RecordCEntryFrame record_c_entry_frame, void** frames, size_t frames_limit, v8::SampleInfo* sample_info, - bool use_simulator_reg_state) { + bool use_simulator_reg_state, void** contexts) { i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate); sample_info->frames_count = 0; sample_info->vm_state = isolate->current_vm_state(); sample_info->external_callback_entry = nullptr; + sample_info->top_context = nullptr; if (sample_info->vm_state == GC) return true; i::Address js_entry_sp = isolate->js_entry_sp(); @@ -229,7 +263,7 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs, i::ExternalCallbackScope* scope = isolate->external_callback_scope(); i::Address handler = i::Isolate::handler(isolate->thread_local_top()); // If there is a handler on top of the external callback scope then - // we have already entrered JavaScript again and the external callback + // we have already entered JavaScript again and the external callback // is not the top function. if (scope && scope->scope_address() < handler) { i::Address* external_callback_entry_ptr = @@ -245,23 +279,62 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs, reinterpret_cast<i::Address>(regs->sp), reinterpret_cast<i::Address>(regs->lr), js_entry_sp); + + i::Address top_context_address = it.top_context_address(); + if (top_context_address != i::kNullAddress) { + sample_info->top_context = reinterpret_cast<void*>( + i::ScrapeNativeContextAddress(isolate->heap(), top_context_address)); + } else { + sample_info->top_context = nullptr; + } + if (it.done()) return true; size_t i = 0; if (record_c_entry_frame == kIncludeCEntryFrame && (it.top_frame_type() == internal::StackFrame::EXIT || it.top_frame_type() == internal::StackFrame::BUILTIN_EXIT)) { - frames[i++] = reinterpret_cast<void*>(isolate->c_function()); + frames[i] = reinterpret_cast<void*>(isolate->c_function()); + if (contexts) contexts[i] = sample_info->top_context; + i++; } + + // If we couldn't get a context address from the top frame due to execution + // being in a callback, borrow it from the next context on the stack. + bool borrows_top_context = it.top_frame_type() == i::StackFrame::EXIT || + it.top_frame_type() == i::StackFrame::BUILTIN_EXIT; + i::RuntimeCallTimer* timer = isolate->counters()->runtime_call_stats()->current_timer(); for (; !it.done() && i < frames_limit; it.Advance()) { while (timer && reinterpret_cast<i::Address>(timer) < it.frame()->fp() && i < frames_limit) { + if (contexts) contexts[i] = nullptr; frames[i++] = reinterpret_cast<void*>(timer->counter()); timer = timer->parent(); } if (i == frames_limit) break; + + // Attempt to read the native context associated with the frame from the + // heap for standard frames. + if (it.frame()->is_standard() && (contexts || borrows_top_context)) { + i::Address context_address = base::Memory<i::Address>( + it.frame()->fp() + i::StandardFrameConstants::kContextOffset); + i::Address native_context_address = + i::ScrapeNativeContextAddress(isolate->heap(), context_address); + if (contexts) + contexts[i] = reinterpret_cast<void*>(native_context_address); + + if (borrows_top_context) { + DCHECK(!sample_info->top_context); + sample_info->top_context = + reinterpret_cast<void*>(native_context_address); + } + } else if (contexts) { + contexts[i] = nullptr; + } + borrows_top_context = false; + if (it.frame()->is_interpreted()) { // For interpreted frames use the bytecode array pointer as the pc. i::InterpretedFrame* frame = @@ -290,20 +363,6 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs, return true; } -namespace internal { - -void TickSample::Init(Isolate* isolate, const v8::RegisterState& state, - RecordCEntryFrame record_c_entry_frame, bool update_stats, - bool use_simulator_reg_state, - base::TimeDelta sampling_interval) { - v8::TickSample::Init(reinterpret_cast<v8::Isolate*>(isolate), state, - record_c_entry_frame, update_stats, - use_simulator_reg_state); - this->sampling_interval = sampling_interval; - if (pc == nullptr) return; - timestamp = base::TimeTicks::HighResolutionNow(); -} - void TickSample::print() const { PrintF("TickSample: at %p\n", this); PrintF(" - state: %s\n", StateToString(state)); diff --git a/chromium/v8/src/profiler/tick-sample.h b/chromium/v8/src/profiler/tick-sample.h index ba78c923c4c..37ae1e9d06e 100644 --- a/chromium/v8/src/profiler/tick-sample.h +++ b/chromium/v8/src/profiler/tick-sample.h @@ -5,7 +5,7 @@ #ifndef V8_PROFILER_TICK_SAMPLE_H_ #define V8_PROFILER_TICK_SAMPLE_H_ -#include "include/v8-profiler.h" +#include "include/v8.h" #include "src/base/platform/time.h" #include "src/common/globals.h" @@ -14,15 +14,83 @@ namespace internal { class Isolate; -struct TickSample : public v8::TickSample { +// TickSample captures the information collected for each sample. +struct V8_EXPORT TickSample { + // Internal profiling (with --prof + tools/$OS-tick-processor) wants to + // include the runtime function we're calling. Externally exposed tick + // samples don't care. + enum RecordCEntryFrame { kIncludeCEntryFrame, kSkipCEntryFrame }; + + TickSample() + : state(OTHER), + pc(nullptr), + external_callback_entry(nullptr), + frames_count(0), + has_external_callback(false), + update_stats(true) {} + + /** + * Initialize a tick sample from the isolate. + * \param isolate The isolate. + * \param state Execution state. + * \param record_c_entry_frame Include or skip the runtime function. + * \param update_stats Whether update the sample to the aggregated stats. + * \param use_simulator_reg_state When set to true and V8 is running under a + * simulator, the method will use the simulator + * register state rather than the one provided + * with |state| argument. Otherwise the method + * will use provided register |state| as is. + */ void Init(Isolate* isolate, const v8::RegisterState& state, RecordCEntryFrame record_c_entry_frame, bool update_stats, bool use_simulator_reg_state = true, base::TimeDelta sampling_interval = base::TimeDelta()); - base::TimeTicks timestamp; - base::TimeDelta sampling_interval; // Sampling interval used to capture. + /** + * Get a call stack sample from the isolate. + * \param isolate The isolate. + * \param state Register state. + * \param record_c_entry_frame Include or skip the runtime function. + * \param frames Caller allocated buffer to store stack frames. + * \param frames_limit Maximum number of frames to capture. The buffer must + * be large enough to hold the number of frames. + * \param sample_info The sample info is filled up by the function + * provides number of actual captured stack frames and + * the current VM state. + * \param use_simulator_reg_state When set to true and V8 is running under a + * simulator, the method will use the simulator + * register state rather than the one provided + * with |state| argument. Otherwise the method + * will use provided register |state| as is. + * \note GetStackSample is thread and signal safe and should only be called + * when the JS thread is paused or interrupted. + * Otherwise the behavior is undefined. + */ + static bool GetStackSample(Isolate* isolate, v8::RegisterState* state, + RecordCEntryFrame record_c_entry_frame, + void** frames, size_t frames_limit, + v8::SampleInfo* sample_info, + bool use_simulator_reg_state = true, + void** contexts = nullptr); void print() const; + + StateTag state; // The state of the VM. + void* pc; // Instruction pointer. + union { + void* tos; // Top stack value (*sp). + void* external_callback_entry; + }; + static const unsigned kMaxFramesCountLog2 = 8; + static const unsigned kMaxFramesCount = (1 << kMaxFramesCountLog2) - 1; + void* stack[kMaxFramesCount]; // Call stack. + void* contexts[kMaxFramesCount]; // Stack of associated native contexts. + void* top_context = nullptr; // Address of the incumbent native context. + unsigned frames_count : kMaxFramesCountLog2; // Number of captured frames. + bool has_external_callback : 1; + bool update_stats : 1; // Whether the sample should update aggregated stats. + + base::TimeTicks timestamp; + base::TimeDelta sampling_interval; // Sampling interval used to capture. }; } // namespace internal |