From e155881f2458bc0988978cc9e8a983ba4f3eb298 Mon Sep 17 00:00:00 2001 From: Corentin Pescheloche Date: Mon, 6 Dec 2021 23:28:08 -0800 Subject: [PATCH] Reland "[profiler] Surface VM & Embedder State" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a reland of 2d087f237eadd78f5545548675642f013fdfe675 The changes are : * Fix redundant reinterpret_cast in test file for MSVC failure https://crbug.com/v8/12476 * Fix flaky test https://crbug.com/v8/12475 If a sample is captured during a GC, no embedder context is obtained defaulting to EMPTY. This is the expected behavior, made it in clear in implementation and in test. * Synchronized the embedder context filter behavior with existing native context filter. Original change's description: > Add APIs to surface VMState and new EmbedderState to CpuProfile samples. > > EmbedderState: > * An EmbedderState is defined as a value uint8_t and a v8::context used > for filtering. > * EmbedderStates are stack allocated by the embedder, construction and > destruction set/unset the state to the isolate thread local top. > * A v8::context is used to filter states that are added to a CpuProfile, > if the CpuProfile do not have a ContextFilter set or if contexts do not > match, state defaults to Empty. > > * v8:StateTag is already propagated all the way to a Sample, simply add > an API to surface it. > > VMState: > Change-Id: I7eed08907360b99b0ad20ddcff59c95c7076c85e > Bug: chromium:1263871 > Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3188072 > Auto-Submit: Corentin Pescheloche > Reviewed-by: Camillo Bruni > Reviewed-by: Dominik Inführ > Reviewed-by: Igor Sheludko > Commit-Queue: Camillo Bruni > Cr-Commit-Position: refs/heads/main@{#78250} Bug: chromium:1263871 Change-Id: Ief891b05da99c695e9fb70f94ed7ebdecc6c3b7b Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3320037 Auto-Submit: Corentin Pescheloche Reviewed-by: Camillo Bruni Reviewed-by: Igor Sheludko Reviewed-by: Dominik Inführ Commit-Queue: Dominik Inführ Cr-Commit-Position: refs/heads/main@{#78281} --- BUILD.bazel | 3 + BUILD.gn | 3 + include/v8-embedder-state-scope.h | 48 ++++++ include/v8-profiler.h | 12 ++ include/v8-unwinder.h | 17 ++- src/api/api.cc | 17 +++ src/execution/embedder-state.cc | 45 ++++++ src/execution/embedder-state.h | 39 +++++ src/execution/isolate.h | 3 + src/execution/thread-local-top.cc | 1 + src/execution/thread-local-top.h | 6 +- src/heap/heap.cc | 5 + src/profiler/cpu-profiler.cc | 11 +- src/profiler/profile-generator.cc | 22 ++- src/profiler/profile-generator.h | 17 ++- src/profiler/tick-sample.cc | 13 ++ src/profiler/tick-sample.h | 3 + test/cctest/test-cpu-profiler.cc | 212 ++++++++++++++++++++++++++ test/cctest/test-profile-generator.cc | 39 ++--- 19 files changed, 471 insertions(+), 45 deletions(-) create mode 100644 include/v8-embedder-state-scope.h create mode 100644 src/execution/embedder-state.cc create mode 100644 src/execution/embedder-state.h diff --git a/BUILD.bazel b/BUILD.bazel index 0e3e1b03b6..743bcf58f9 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -445,6 +445,7 @@ filegroup( "include/v8-date.h", "include/v8-debug.h", "include/v8-embedder-heap.h", + "include/v8-embedder-state-scope.h, "include/v8-exception.h", "include/v8-extension.h", "include/v8-external.h", @@ -1214,6 +1215,8 @@ filegroup( "src/execution/arguments.h", "src/execution/encoded-c-signature.cc", "src/execution/encoded-c-signature.h", + "src/execution/embedder-state.h", + "src/execution/embedder-state.cc", "src/execution/execution.cc", "src/execution/execution.h", "src/execution/frame-constants.h", diff --git a/BUILD.gn b/BUILD.gn index c2e11071c1..8a898f826f 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -2487,6 +2487,7 @@ v8_header_set("v8_headers") { "include/v8-date.h", "include/v8-debug.h", "include/v8-embedder-heap.h", + "include/v8-embedder-state-scope.h", "include/v8-exception.h", "include/v8-extension.h", "include/v8-external.h", @@ -2864,6 +2865,7 @@ v8_header_set("v8_internal_headers") { "src/diagnostics/unwinder.h", "src/execution/arguments-inl.h", "src/execution/arguments.h", + "src/execution/embedder-state.h", "src/execution/encoded-c-signature.h", "src/execution/execution.h", "src/execution/frame-constants.h", @@ -4079,6 +4081,7 @@ v8_source_set("v8_base_without_compiler") { "src/diagnostics/perf-jit.cc", "src/diagnostics/unwinder.cc", "src/execution/arguments.cc", + "src/execution/embedder-state.cc", "src/execution/encoded-c-signature.cc", "src/execution/execution.cc", "src/execution/frames.cc", diff --git a/include/v8-embedder-state-scope.h b/include/v8-embedder-state-scope.h new file mode 100644 index 0000000000..6ae9b3b477 --- /dev/null +++ b/include/v8-embedder-state-scope.h @@ -0,0 +1,48 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_ +#define INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_ + +#include + +#include "v8-context.h" // NOLINT(build/include_directory) +#include "v8-internal.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) + +namespace v8 { + +namespace internal { +class EmbedderState; +} // namespace internal + +// A StateTag represents a possible state of the embedder. +enum class EmbedderStateTag : uint8_t { + EMPTY = 0, + // embedder can define any state in between + OTHER = UINT8_MAX, +}; + +// A stack-allocated class that manages an embedder state on the isolate. +// After an EmbedderState scope has been created, a new embedder state will be +// pushed on the isolate stack. +class V8_EXPORT EmbedderStateScope { + public: + EmbedderStateScope(Isolate* isolate, Local context, + EmbedderStateTag tag); + + private: + // Declaring operator new and delete as deleted is not spec compliant. + // Therefore declare them private instead to disable dynamic alloc + void* operator new(size_t size); + void* operator new[](size_t size); + void operator delete(void*, size_t); + void operator delete[](void*, size_t); + + std::unique_ptr embedder_state_; +}; + +} // namespace v8 + +#endif // INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_ diff --git a/include/v8-profiler.h b/include/v8-profiler.h index ccf15bab2a..c9a2704f7b 100644 --- a/include/v8-profiler.h +++ b/include/v8-profiler.h @@ -20,9 +20,11 @@ */ namespace v8 { +enum class EmbedderStateTag : uint8_t; class HeapGraphNode; struct HeapStatsUpdate; class Object; +enum StateTag : int; using NativeObject = void*; using SnapshotObjectId = uint32_t; @@ -210,6 +212,16 @@ class V8_EXPORT CpuProfile { */ int64_t GetStartTime() const; + /** + * Returns state of the vm when sample was captured. + */ + StateTag GetSampleState(int index) const; + + /** + * Returns state of the embedder when sample was captured. + */ + EmbedderStateTag GetSampleEmbedderState(int index) const; + /** * Returns time when the profile recording was stopped (in microseconds) * since some unspecified starting point. diff --git a/include/v8-unwinder.h b/include/v8-unwinder.h index 22a5cd713d..8dca52f41c 100644 --- a/include/v8-unwinder.h +++ b/include/v8-unwinder.h @@ -7,7 +7,8 @@ #include -#include "v8config.h" // NOLINT(build/include_directory) +#include "v8-embedder-state-scope.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) namespace v8 { // Holds the callee saved registers needed for the stack unwinder. It is the @@ -32,7 +33,7 @@ struct V8_EXPORT RegisterState { }; // A StateTag represents a possible state of the VM. -enum StateTag { +enum StateTag : int { JS, GC, PARSER, @@ -46,11 +47,13 @@ enum StateTag { // The output structure filled up by GetStackSample API function. struct SampleInfo { - size_t frames_count; // Number of frames collected. - StateTag vm_state; // Current VM state. - void* external_callback_entry; // External callback address if VM is - // executing an external callback. - void* context; // Incumbent native context address. + size_t frames_count; // Number of frames collected. + void* external_callback_entry; // External callback address if VM is + // executing an external callback. + void* context; // Incumbent native context address. + void* embedder_context; // Native context address for embedder state + StateTag vm_state; // Current VM state. + EmbedderStateTag embedder_state; // Current Embedder state }; struct MemoryRange { diff --git a/src/api/api.cc b/src/api/api.cc index b378caf4e2..7250da637c 100644 --- a/src/api/api.cc +++ b/src/api/api.cc @@ -15,6 +15,7 @@ #include "include/v8-callbacks.h" #include "include/v8-cppgc.h" #include "include/v8-date.h" +#include "include/v8-embedder-state-scope.h" #include "include/v8-extension.h" #include "include/v8-fast-api-calls.h" #include "include/v8-function.h" @@ -46,6 +47,7 @@ #include "src/debug/liveedit.h" #include "src/deoptimizer/deoptimizer.h" #include "src/diagnostics/gdb-jit.h" +#include "src/execution/embedder-state.h" #include "src/execution/execution.h" #include "src/execution/frames-inl.h" #include "src/execution/isolate-inl.h" @@ -9864,6 +9866,16 @@ int64_t CpuProfile::GetSampleTimestamp(int index) const { return profile->sample(index).timestamp.since_origin().InMicroseconds(); } +StateTag CpuProfile::GetSampleState(int index) const { + const i::CpuProfile* profile = reinterpret_cast(this); + return profile->sample(index).state_tag; +} + +EmbedderStateTag CpuProfile::GetSampleEmbedderState(int index) const { + const i::CpuProfile* profile = reinterpret_cast(this); + return profile->sample(index).embedder_state_tag; +} + int64_t CpuProfile::GetStartTime() const { const i::CpuProfile* profile = reinterpret_cast(this); return profile->start_time().since_origin().InMicroseconds(); @@ -10332,6 +10344,11 @@ void EmbedderHeapTracer::ResetHandleInNonTracingGC( UNREACHABLE(); } +EmbedderStateScope::EmbedderStateScope(Isolate* isolate, + Local context, + EmbedderStateTag tag) + : embedder_state_(new internal::EmbedderState(isolate, context, tag)) {} + void TracedReferenceBase::CheckValue() const { #ifdef V8_HOST_ARCH_64_BIT if (!val_) return; diff --git a/src/execution/embedder-state.cc b/src/execution/embedder-state.cc new file mode 100644 index 0000000000..1c4fa2ff11 --- /dev/null +++ b/src/execution/embedder-state.cc @@ -0,0 +1,45 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/execution/embedder-state.h" + +#include "src/api/api-inl.h" +#include "src/base/logging.h" + +namespace v8 { + +namespace internal { + +EmbedderState::EmbedderState(v8::Isolate* isolate, Local context, + EmbedderStateTag tag) + : isolate_(reinterpret_cast(isolate)), + tag_(tag), + previous_embedder_state_(isolate_->current_embedder_state()) { + if (!context.IsEmpty()) { + native_context_address_ = + v8::Utils::OpenHandle(*context)->native_context().address(); + } + + DCHECK_NE(this, isolate_->current_embedder_state()); + isolate_->set_current_embedder_state(this); +} + +EmbedderState::~EmbedderState() { + DCHECK_EQ(this, isolate_->current_embedder_state()); + isolate_->set_current_embedder_state(previous_embedder_state_); +} + +void EmbedderState::OnMoveEvent(Address from, Address to) { + EmbedderState* state = this; + do { + if (state->native_context_address_ == from) { + native_context_address_ = to; + } + state = state->previous_embedder_state_; + } while (state != nullptr); +} + +} // namespace internal + +} // namespace v8 diff --git a/src/execution/embedder-state.h b/src/execution/embedder-state.h new file mode 100644 index 0000000000..3bd439c1e6 --- /dev/null +++ b/src/execution/embedder-state.h @@ -0,0 +1,39 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_EXECUTION_EMBEDDER_STATE_H_ +#define V8_EXECUTION_EMBEDDER_STATE_H_ + +#include "include/v8-local-handle.h" +#include "src/execution/isolate.h" + +namespace v8 { + +enum class EmbedderStateTag : uint8_t; + +namespace internal { +class V8_EXPORT_PRIVATE EmbedderState { + public: + EmbedderState(v8::Isolate* isolate, Local context, + EmbedderStateTag tag); + + ~EmbedderState(); + + EmbedderStateTag GetState() const { return tag_; } + + Address native_context_address() const { return native_context_address_; } + + void OnMoveEvent(Address from, Address to); + + private: + Isolate* isolate_; + EmbedderStateTag tag_; + Address native_context_address_ = kNullAddress; + EmbedderState* previous_embedder_state_; +}; +} // namespace internal + +} // namespace v8 + +#endif // V8_EXECUTION_EMBEDDER_STATE_H_ diff --git a/src/execution/isolate.h b/src/execution/isolate.h index f3da866703..17e0d86c4e 100644 --- a/src/execution/isolate.h +++ b/src/execution/isolate.h @@ -65,6 +65,8 @@ class V8Inspector; namespace v8 { +class EmbedderState; + namespace base { class RandomNumberGenerator; } // namespace base @@ -1303,6 +1305,7 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { THREAD_LOCAL_TOP_ACCESSOR(ExternalCallbackScope*, external_callback_scope) THREAD_LOCAL_TOP_ACCESSOR(StateTag, current_vm_state) + THREAD_LOCAL_TOP_ACCESSOR(EmbedderState*, current_embedder_state) void SetData(uint32_t slot, void* data) { DCHECK_LT(slot, Internals::kNumIsolateDataSlots); diff --git a/src/execution/thread-local-top.cc b/src/execution/thread-local-top.cc index c2b09c67b1..3bdeb227a8 100644 --- a/src/execution/thread-local-top.cc +++ b/src/execution/thread-local-top.cc @@ -31,6 +31,7 @@ void ThreadLocalTop::Clear() { js_entry_sp_ = kNullAddress; external_callback_scope_ = nullptr; current_vm_state_ = EXTERNAL; + current_embedder_state_ = nullptr; failed_access_check_callback_ = nullptr; thread_in_wasm_flag_address_ = kNullAddress; #ifdef V8_ENABLE_CONSERVATIVE_STACK_SCANNING diff --git a/src/execution/thread-local-top.h b/src/execution/thread-local-top.h index 236beda8a0..b072005d40 100644 --- a/src/execution/thread-local-top.h +++ b/src/execution/thread-local-top.h @@ -23,6 +23,7 @@ class TryCatch; namespace internal { +class EmbedderState; class ExternalCallbackScope; class Isolate; class PromiseOnStack; @@ -34,9 +35,9 @@ class ThreadLocalTop { // refactor this to really consist of just Addresses and 32-bit // integer fields. #ifdef V8_ENABLE_CONSERVATIVE_STACK_SCANNING - static constexpr uint32_t kSizeInBytes = 25 * kSystemPointerSize; + static constexpr uint32_t kSizeInBytes = 26 * kSystemPointerSize; #else - static constexpr uint32_t kSizeInBytes = 24 * kSystemPointerSize; + static constexpr uint32_t kSizeInBytes = 25 * kSystemPointerSize; #endif // Does early low-level initialization that does not depend on the @@ -151,6 +152,7 @@ class ThreadLocalTop { // The external callback we're currently in. ExternalCallbackScope* external_callback_scope_; StateTag current_vm_state_; + EmbedderState* current_embedder_state_; // Call back function to report unsafe JS accesses. v8::FailedAccessCheckCallback failed_access_check_callback_; diff --git a/src/heap/heap.cc b/src/heap/heap.cc index 32d41397ba..5b2a41a185 100644 --- a/src/heap/heap.cc +++ b/src/heap/heap.cc @@ -27,6 +27,7 @@ #include "src/compiler-dispatcher/optimizing-compile-dispatcher.h" #include "src/debug/debug.h" #include "src/deoptimizer/deoptimizer.h" +#include "src/execution/embedder-state.h" #include "src/execution/isolate-utils-inl.h" #include "src/execution/microtask-queue.h" #include "src/execution/runtime-profiler.h" @@ -3323,6 +3324,10 @@ void Heap::OnMoveEvent(HeapObject target, HeapObject source, LOG_CODE_EVENT(isolate_, SharedFunctionInfoMoveEvent(source.address(), target.address())); } else if (target.IsNativeContext()) { + if (isolate_->current_embedder_state() != nullptr) { + isolate_->current_embedder_state()->OnMoveEvent(source.address(), + target.address()); + } PROFILE(isolate_, NativeContextMoveEvent(source.address(), target.address())); } diff --git a/src/profiler/cpu-profiler.cc b/src/profiler/cpu-profiler.cc index 7540077b69..99dbd9f9c1 100644 --- a/src/profiler/cpu-profiler.cc +++ b/src/profiler/cpu-profiler.cc @@ -230,12 +230,15 @@ void ProfilerEventsProcessor::CodeEventHandler( void SamplingEventsProcessor::SymbolizeAndAddToProfiles( const TickSampleEventRecord* record) { + const TickSample& tick_sample = record->sample; Symbolizer::SymbolizedSample symbolized = - symbolizer_->SymbolizeTickSample(record->sample); + symbolizer_->SymbolizeTickSample(tick_sample); profiles_->AddPathToCurrentProfiles( - record->sample.timestamp, symbolized.stack_trace, symbolized.src_line, - record->sample.update_stats_, record->sample.sampling_interval_, - reinterpret_cast
(record->sample.context)); + tick_sample.timestamp, symbolized.stack_trace, symbolized.src_line, + tick_sample.update_stats_, tick_sample.sampling_interval_, + tick_sample.state, tick_sample.embedder_state, + reinterpret_cast
(tick_sample.context), + reinterpret_cast
(tick_sample.embedder_context)); } ProfilerEventsProcessor::SampleProcessingResult diff --git a/src/profiler/profile-generator.cc b/src/profiler/profile-generator.cc index 749f6001e0..1a4665b874 100644 --- a/src/profiler/profile-generator.cc +++ b/src/profiler/profile-generator.cc @@ -620,7 +620,9 @@ bool CpuProfile::CheckSubsample(base::TimeDelta source_sampling_interval) { void CpuProfile::AddPath(base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line, - bool update_stats, base::TimeDelta sampling_interval) { + bool update_stats, base::TimeDelta sampling_interval, + StateTag state_tag, + EmbedderStateTag embedder_state_tag) { if (!CheckSubsample(sampling_interval)) return; ProfileNode* top_frame_node = @@ -632,7 +634,8 @@ void CpuProfile::AddPath(base::TimeTicks timestamp, samples_.size() < options_.max_samples()); if (should_record_sample) { - samples_.push_back({top_frame_node, timestamp, src_line}); + samples_.push_back( + {top_frame_node, timestamp, src_line, state_tag, embedder_state_tag}); } if (!should_record_sample && delegate_ != nullptr) { @@ -988,19 +991,24 @@ base::TimeDelta CpuProfilesCollection::GetCommonSamplingInterval() const { void CpuProfilesCollection::AddPathToCurrentProfiles( base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line, - bool update_stats, base::TimeDelta sampling_interval, - Address native_context_address) { + bool update_stats, base::TimeDelta sampling_interval, StateTag state, + EmbedderStateTag embedder_state_tag, Address native_context_address, + Address embedder_native_context_address) { // As starting / stopping profiles is rare relatively to this // method, we don't bother minimizing the duration of lock holding, // e.g. copying contents of the list to a local vector. current_profiles_semaphore_.Wait(); const ProfileStackTrace empty_path; for (const std::unique_ptr& profile : current_profiles_) { + ContextFilter& context_filter = profile->context_filter(); // If the context filter check failed, omit the contents of the stack. - bool accepts_context = - profile->context_filter().Accept(native_context_address); + bool accepts_context = context_filter.Accept(native_context_address); + bool accepts_embedder_context = + context_filter.Accept(embedder_native_context_address); profile->AddPath(timestamp, accepts_context ? path : empty_path, src_line, - update_stats, sampling_interval); + update_stats, sampling_interval, state, + accepts_embedder_context ? embedder_state_tag + : EmbedderStateTag::EMPTY); } current_profiles_semaphore_.Signal(); } diff --git a/src/profiler/profile-generator.h b/src/profiler/profile-generator.h index bb0adbfe3b..85402564ff 100644 --- a/src/profiler/profile-generator.h +++ b/src/profiler/profile-generator.h @@ -17,6 +17,7 @@ #include "include/v8-profiler.h" #include "src/base/platform/time.h" #include "src/builtins/builtins.h" +#include "src/execution/vm-state.h" #include "src/logging/code-events.h" #include "src/profiler/strings-storage.h" #include "src/utils/allocation.h" @@ -405,6 +406,8 @@ class CpuProfile { ProfileNode* node; base::TimeTicks timestamp; int line; + StateTag state_tag; + EmbedderStateTag embedder_state_tag; }; V8_EXPORT_PRIVATE CpuProfile( @@ -419,7 +422,8 @@ class CpuProfile { // Add pc -> ... -> main() call path to the profile. void AddPath(base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line, bool update_stats, - base::TimeDelta sampling_interval); + base::TimeDelta sampling_interval, StateTag state, + EmbedderStateTag embedder_state); void FinishProfile(); const char* title() const { return title_; } @@ -554,11 +558,12 @@ class V8_EXPORT_PRIVATE CpuProfilesCollection { base::TimeDelta GetCommonSamplingInterval() const; // Called from profile generator thread. - void AddPathToCurrentProfiles(base::TimeTicks timestamp, - const ProfileStackTrace& path, int src_line, - bool update_stats, - base::TimeDelta sampling_interval, - Address native_context_address = kNullAddress); + void AddPathToCurrentProfiles( + base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line, + bool update_stats, base::TimeDelta sampling_interval, StateTag state, + EmbedderStateTag embedder_state_tag, + Address native_context_address = kNullAddress, + Address native_embedder_context_address = kNullAddress); // Called from profile generator thread. void UpdateNativeContextAddressForCurrentProfiles(Address from, Address to); diff --git a/src/profiler/tick-sample.cc b/src/profiler/tick-sample.cc index 6ea0f2a899..20732bfb76 100644 --- a/src/profiler/tick-sample.cc +++ b/src/profiler/tick-sample.cc @@ -9,6 +9,7 @@ #include "include/v8-profiler.h" #include "src/base/sanitizer/asan.h" #include "src/base/sanitizer/msan.h" +#include "src/execution/embedder-state.h" #include "src/execution/frames-inl.h" #include "src/execution/simulator.h" #include "src/execution/vm-state-inl.h" @@ -178,6 +179,8 @@ DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate, frames_count = static_cast(info.frames_count); has_external_callback = info.external_callback_entry != nullptr; context = info.context; + embedder_context = info.embedder_context; + embedder_state = info.embedder_state; if (has_external_callback) { external_callback_entry = info.external_callback_entry; } else if (frames_count) { @@ -210,9 +213,19 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs, sample_info->frames_count = 0; sample_info->vm_state = isolate->current_vm_state(); sample_info->external_callback_entry = nullptr; + sample_info->embedder_state = EmbedderStateTag::EMPTY; + sample_info->embedder_context = nullptr; sample_info->context = nullptr; + if (sample_info->vm_state == GC) return true; + EmbedderState* embedder_state = isolate->current_embedder_state(); + if (embedder_state != nullptr) { + sample_info->embedder_context = + reinterpret_cast(embedder_state->native_context_address()); + sample_info->embedder_state = embedder_state->GetState(); + } + i::Address js_entry_sp = isolate->js_entry_sp(); if (js_entry_sp == 0) return true; // Not executing JS now. diff --git a/src/profiler/tick-sample.h b/src/profiler/tick-sample.h index b5cf334689..236e4a2f86 100644 --- a/src/profiler/tick-sample.h +++ b/src/profiler/tick-sample.h @@ -23,6 +23,7 @@ struct V8_EXPORT TickSample { TickSample() : state(OTHER), + embedder_state(EmbedderStateTag::EMPTY), pc(nullptr), external_callback_entry(nullptr), frames_count(0), @@ -82,6 +83,7 @@ struct V8_EXPORT TickSample { void print() const; StateTag state; // The state of the VM. + EmbedderStateTag embedder_state; void* pc; // Instruction pointer. union { void* tos; // Top stack value (*sp). @@ -91,6 +93,7 @@ struct V8_EXPORT TickSample { static const unsigned kMaxFramesCount = (1 << kMaxFramesCountLog2) - 1; void* stack[kMaxFramesCount]; // Call stack. void* context = nullptr; // Address of the incumbent native context. + void* embedder_context = nullptr; // Address of the embedder 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. diff --git a/test/cctest/test-cpu-profiler.cc b/test/cctest/test-cpu-profiler.cc index 1b5b56cacf..5d7e45198b 100644 --- a/test/cctest/test-cpu-profiler.cc +++ b/test/cctest/test-cpu-profiler.cc @@ -41,6 +41,7 @@ #include "src/codegen/compilation-cache.h" #include "src/codegen/source-position-table.h" #include "src/deoptimizer/deoptimizer.h" +#include "src/execution/embedder-state.h" #include "src/heap/spaces.h" #include "src/init/v8.h" #include "src/libplatform/default-platform.h" @@ -3922,6 +3923,217 @@ TEST(ContextIsolation) { } } +void ValidateEmbedderState(v8::CpuProfile* profile, + EmbedderStateTag expected_tag) { + for (int i = 0; i < profile->GetSamplesCount(); i++) { + if (profile->GetSampleState(i) == StateTag::GC) { + // Samples captured during a GC do not have an EmbedderState + CHECK_EQ(profile->GetSampleEmbedderState(i), EmbedderStateTag::EMPTY); + } else { + CHECK_EQ(profile->GetSampleEmbedderState(i), expected_tag); + } + } +} + +// Tests that embedder states from other contexts aren't recorded +TEST(EmbedderContextIsolation) { + i::FLAG_allow_natives_syntax = true; + LocalContext execution_env; + i::HandleScope scope(CcTest::i_isolate()); + + v8::Isolate* isolate = execution_env.local()->GetIsolate(); + + // Install CollectSample callback for more deterministic sampling. + v8::Local func_template = + v8::FunctionTemplate::New(isolate, CallCollectSample); + v8::Local func = + func_template->GetFunction(execution_env.local()).ToLocalChecked(); + func->SetName(v8_str("CallCollectSample")); + execution_env->Global() + ->Set(execution_env.local(), v8_str("CallCollectSample"), func) + .FromJust(); + + v8::Local diff_context = v8::Context::New(isolate); + { + CHECK_NULL(CcTest::i_isolate()->current_embedder_state()); + // prepare other embedder state + EmbedderStateScope scope(isolate, diff_context, EmbedderStateTag::OTHER); + CHECK_EQ(CcTest::i_isolate()->current_embedder_state()->GetState(), + EmbedderStateTag::OTHER); + + ProfilerHelper helper(execution_env.local()); + CompileRun(R"( + function optimized() { + CallCollectSample(); + } + + function unoptimized() { + CallCollectSample(); + } + + function start() { + // Test optimized functions + %PrepareFunctionForOptimization(optimized); + optimized(); + optimized(); + %OptimizeFunctionOnNextCall(optimized); + optimized(); + + // Test unoptimized functions + %NeverOptimizeFunction(unoptimized); + unoptimized(); + + // Test callback + CallCollectSample(); + } + )"); + v8::Local function = + GetFunction(execution_env.local(), "start"); + + v8::CpuProfile* profile = helper.Run( + function, nullptr, 0, 0, 0, v8::CpuProfilingMode::kLeafNodeLineNumbers, + v8::CpuProfilingOptions::kNoSampleLimit, execution_env.local()); + ValidateEmbedderState(profile, EmbedderStateTag::EMPTY); + } + CHECK_NULL(CcTest::i_isolate()->current_embedder_state()); +} + +// Tests that embedder states from same context are recorded +TEST(EmbedderStatePropagate) { + i::FLAG_allow_natives_syntax = true; + LocalContext execution_env; + i::HandleScope scope(CcTest::i_isolate()); + + v8::Isolate* isolate = execution_env.local()->GetIsolate(); + + // Install CollectSample callback for more deterministic sampling. + v8::Local func_template = + v8::FunctionTemplate::New(isolate, CallCollectSample); + v8::Local func = + func_template->GetFunction(execution_env.local()).ToLocalChecked(); + func->SetName(v8_str("CallCollectSample")); + execution_env->Global() + ->Set(execution_env.local(), v8_str("CallCollectSample"), func) + .FromJust(); + + { + // prepare embedder state + EmbedderState embedderState(isolate, execution_env.local(), + EmbedderStateTag::OTHER); + CHECK_EQ(CcTest::i_isolate()->current_embedder_state(), &embedderState); + + ProfilerHelper helper(execution_env.local()); + CompileRun(R"( + function optimized() { + CallCollectSample(); + } + + function unoptimized() { + CallCollectSample(); + } + + function start() { + // Test optimized functions + %PrepareFunctionForOptimization(optimized); + optimized(); + optimized(); + %OptimizeFunctionOnNextCall(optimized); + optimized(); + + // Test unoptimized functions + %NeverOptimizeFunction(unoptimized); + unoptimized(); + + // Test callback + CallCollectSample(); + } + )"); + v8::Local function = + GetFunction(execution_env.local(), "start"); + + v8::CpuProfile* profile = helper.Run( + function, nullptr, 0, 0, 0, v8::CpuProfilingMode::kLeafNodeLineNumbers, + v8::CpuProfilingOptions::kNoSampleLimit, execution_env.local()); + ValidateEmbedderState(profile, EmbedderStateTag::OTHER); + } + CHECK_NULL(CcTest::i_isolate()->current_embedder_state()); +} + +// Tests that embedder states from same context are recorded +// even after native context move +TEST(EmbedderStatePropagateNativeContextMove) { + // Reusing context addresses will cause this test to fail. + if (i::FLAG_gc_global || i::FLAG_stress_compaction || + i::FLAG_stress_incremental_marking || i::FLAG_enable_third_party_heap) { + return; + } + i::FLAG_allow_natives_syntax = true; + i::FLAG_manual_evacuation_candidates_selection = true; + LocalContext execution_env; + i::HandleScope scope(CcTest::i_isolate()); + + v8::Isolate* isolate = execution_env.local()->GetIsolate(); + + // Install CollectSample callback for more deterministic sampling. + v8::Local func_template = + v8::FunctionTemplate::New(isolate, CallCollectSample); + v8::Local func = + func_template->GetFunction(execution_env.local()).ToLocalChecked(); + func->SetName(v8_str("CallCollectSample")); + execution_env->Global() + ->Set(execution_env.local(), v8_str("CallCollectSample"), func) + .FromJust(); + + { + // prepare embedder state + EmbedderState embedderState(isolate, execution_env.local(), + EmbedderStateTag::OTHER); + CHECK_EQ(CcTest::i_isolate()->current_embedder_state(), &embedderState); + + i::Address initial_address = + CcTest::i_isolate()->current_embedder_state()->native_context_address(); + + // Install a function that triggers the native context to be moved. + v8::Local move_func_template = + v8::FunctionTemplate::New( + execution_env.local()->GetIsolate(), + [](const v8::FunctionCallbackInfo& info) { + i::Isolate* isolate = + reinterpret_cast(info.GetIsolate()); + i::heap::ForceEvacuationCandidate( + i::Page::FromHeapObject(isolate->raw_native_context())); + CcTest::CollectAllGarbage(); + }); + v8::Local move_func = + move_func_template->GetFunction(execution_env.local()).ToLocalChecked(); + move_func->SetName(v8_str("ForceNativeContextMove")); + execution_env->Global() + ->Set(execution_env.local(), v8_str("ForceNativeContextMove"), + move_func) + .FromJust(); + + ProfilerHelper helper(execution_env.local()); + CompileRun(R"( + function start() { + ForceNativeContextMove(); + CallCollectSample(); + } + )"); + v8::Local function = + GetFunction(execution_env.local(), "start"); + + v8::CpuProfile* profile = helper.Run( + function, nullptr, 0, 0, 0, v8::CpuProfilingMode::kLeafNodeLineNumbers, + v8::CpuProfilingOptions::kNoSampleLimit, execution_env.local()); + ValidateEmbedderState(profile, EmbedderStateTag::OTHER); + + i::Address new_address = + CcTest::i_isolate()->current_embedder_state()->native_context_address(); + CHECK_NE(initial_address, new_address); + } + CHECK_NULL(CcTest::i_isolate()->current_embedder_state()); +} + // Tests that when a native context that's being filtered is moved, we continue // to track its execution. TEST(ContextFilterMovedNativeContext) { diff --git a/test/cctest/test-profile-generator.cc b/test/cctest/test-profile-generator.cc index 4f3dd4fd69..3bc84e4e48 100644 --- a/test/cctest/test-profile-generator.cc +++ b/test/cctest/test-profile-generator.cc @@ -482,9 +482,9 @@ TEST(SampleIds) { sample1.stack[0] = ToPointer(0x1510); sample1.frames_count = 1; auto symbolized = symbolizer.SymbolizeTickSample(sample1); - profiles.AddPathToCurrentProfiles(sample1.timestamp, symbolized.stack_trace, - symbolized.src_line, true, - base::TimeDelta()); + profiles.AddPathToCurrentProfiles( + sample1.timestamp, symbolized.stack_trace, symbolized.src_line, true, + base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY); TickSample sample2; sample2.timestamp = v8::base::TimeTicks::HighResolutionNow(); @@ -494,9 +494,9 @@ TEST(SampleIds) { sample2.stack[2] = ToPointer(0x1620); sample2.frames_count = 3; symbolized = symbolizer.SymbolizeTickSample(sample2); - profiles.AddPathToCurrentProfiles(sample2.timestamp, symbolized.stack_trace, - symbolized.src_line, true, - base::TimeDelta()); + profiles.AddPathToCurrentProfiles( + sample2.timestamp, symbolized.stack_trace, symbolized.src_line, true, + base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY); TickSample sample3; sample3.timestamp = v8::base::TimeTicks::HighResolutionNow(); @@ -505,9 +505,9 @@ TEST(SampleIds) { sample3.stack[1] = ToPointer(0x1610); sample3.frames_count = 2; symbolized = symbolizer.SymbolizeTickSample(sample3); - profiles.AddPathToCurrentProfiles(sample3.timestamp, symbolized.stack_trace, - symbolized.src_line, true, - base::TimeDelta()); + profiles.AddPathToCurrentProfiles( + sample3.timestamp, symbolized.stack_trace, symbolized.src_line, true, + base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY); CpuProfile* profile = profiles.StopProfiling(""); unsigned nodeId = 1; @@ -603,9 +603,9 @@ TEST(MaxSamplesCallback) { sample1.stack[0] = ToPointer(0x1510); sample1.frames_count = 1; auto symbolized = symbolizer.SymbolizeTickSample(sample1); - profiles.AddPathToCurrentProfiles(sample1.timestamp, symbolized.stack_trace, - symbolized.src_line, true, - base::TimeDelta()); + profiles.AddPathToCurrentProfiles( + sample1.timestamp, symbolized.stack_trace, symbolized.src_line, true, + base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY); CHECK_EQ(0, mock_platform->posted_count()); TickSample sample2; sample2.timestamp = v8::base::TimeTicks::HighResolutionNow(); @@ -613,18 +613,18 @@ TEST(MaxSamplesCallback) { sample2.stack[0] = ToPointer(0x1780); sample2.frames_count = 2; symbolized = symbolizer.SymbolizeTickSample(sample2); - profiles.AddPathToCurrentProfiles(sample2.timestamp, symbolized.stack_trace, - symbolized.src_line, true, - base::TimeDelta()); + profiles.AddPathToCurrentProfiles( + sample2.timestamp, symbolized.stack_trace, symbolized.src_line, true, + base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY); CHECK_EQ(1, mock_platform->posted_count()); TickSample sample3; sample3.timestamp = v8::base::TimeTicks::HighResolutionNow(); sample3.pc = ToPointer(0x1510); sample3.frames_count = 3; symbolized = symbolizer.SymbolizeTickSample(sample3); - profiles.AddPathToCurrentProfiles(sample3.timestamp, symbolized.stack_trace, - symbolized.src_line, true, - base::TimeDelta()); + profiles.AddPathToCurrentProfiles( + sample3.timestamp, symbolized.stack_trace, symbolized.src_line, true, + base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY); CHECK_EQ(1, mock_platform->posted_count()); // Teardown @@ -654,7 +654,8 @@ TEST(NoSamples) { auto symbolized = symbolizer.SymbolizeTickSample(sample1); profiles.AddPathToCurrentProfiles(v8::base::TimeTicks::HighResolutionNow(), symbolized.stack_trace, symbolized.src_line, - true, base::TimeDelta()); + true, base::TimeDelta(), StateTag::JS, + EmbedderStateTag::EMPTY); CpuProfile* profile = profiles.StopProfiling(""); unsigned nodeId = 1;