[profiler] Surface VM & Embedder State

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 <cpescheloche@fb.com>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: Dominik Inführ <dinfuehr@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78250}
This commit is contained in:
Corentin Pescheloche 2021-12-03 12:48:09 -08:00 committed by V8 LUCI CQ
parent e947712e2c
commit 2d087f237e
19 changed files with 470 additions and 45 deletions

View File

@ -445,6 +445,7 @@ filegroup(
"include/v8-date.h", "include/v8-date.h",
"include/v8-debug.h", "include/v8-debug.h",
"include/v8-embedder-heap.h", "include/v8-embedder-heap.h",
"include/v8-embedder-state-scope.h,
"include/v8-exception.h", "include/v8-exception.h",
"include/v8-extension.h", "include/v8-extension.h",
"include/v8-external.h", "include/v8-external.h",
@ -1214,6 +1215,8 @@ filegroup(
"src/execution/arguments.h", "src/execution/arguments.h",
"src/execution/encoded-c-signature.cc", "src/execution/encoded-c-signature.cc",
"src/execution/encoded-c-signature.h", "src/execution/encoded-c-signature.h",
"src/execution/embedder-state.h",
"src/execution/embedder-state.cc",
"src/execution/execution.cc", "src/execution/execution.cc",
"src/execution/execution.h", "src/execution/execution.h",
"src/execution/frame-constants.h", "src/execution/frame-constants.h",

View File

@ -2487,6 +2487,7 @@ v8_header_set("v8_headers") {
"include/v8-date.h", "include/v8-date.h",
"include/v8-debug.h", "include/v8-debug.h",
"include/v8-embedder-heap.h", "include/v8-embedder-heap.h",
"include/v8-embedder-state-scope.h",
"include/v8-exception.h", "include/v8-exception.h",
"include/v8-extension.h", "include/v8-extension.h",
"include/v8-external.h", "include/v8-external.h",
@ -2864,6 +2865,7 @@ v8_header_set("v8_internal_headers") {
"src/diagnostics/unwinder.h", "src/diagnostics/unwinder.h",
"src/execution/arguments-inl.h", "src/execution/arguments-inl.h",
"src/execution/arguments.h", "src/execution/arguments.h",
"src/execution/embedder-state.h",
"src/execution/encoded-c-signature.h", "src/execution/encoded-c-signature.h",
"src/execution/execution.h", "src/execution/execution.h",
"src/execution/frame-constants.h", "src/execution/frame-constants.h",
@ -4079,6 +4081,7 @@ v8_source_set("v8_base_without_compiler") {
"src/diagnostics/perf-jit.cc", "src/diagnostics/perf-jit.cc",
"src/diagnostics/unwinder.cc", "src/diagnostics/unwinder.cc",
"src/execution/arguments.cc", "src/execution/arguments.cc",
"src/execution/embedder-state.cc",
"src/execution/encoded-c-signature.cc", "src/execution/encoded-c-signature.cc",
"src/execution/execution.cc", "src/execution/execution.cc",
"src/execution/frames.cc", "src/execution/frames.cc",

View File

@ -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 <memory>
#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<v8::Context> 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<internal::EmbedderState> embedder_state_;
};
} // namespace v8
#endif // INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_

View File

@ -20,9 +20,11 @@
*/ */
namespace v8 { namespace v8 {
enum class EmbedderStateTag : uint8_t;
class HeapGraphNode; class HeapGraphNode;
struct HeapStatsUpdate; struct HeapStatsUpdate;
class Object; class Object;
enum StateTag : int;
using NativeObject = void*; using NativeObject = void*;
using SnapshotObjectId = uint32_t; using SnapshotObjectId = uint32_t;
@ -210,6 +212,16 @@ class V8_EXPORT CpuProfile {
*/ */
int64_t GetStartTime() const; 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) * Returns time when the profile recording was stopped (in microseconds)
* since some unspecified starting point. * since some unspecified starting point.

View File

@ -7,6 +7,7 @@
#include <memory> #include <memory>
#include "v8-embedder-state-scope.h" // NOLINT(build/include_directory)
#include "v8config.h" // NOLINT(build/include_directory) #include "v8config.h" // NOLINT(build/include_directory)
namespace v8 { namespace v8 {
@ -32,7 +33,7 @@ struct V8_EXPORT RegisterState {
}; };
// A StateTag represents a possible state of the VM. // A StateTag represents a possible state of the VM.
enum StateTag { enum StateTag : int {
JS, JS,
GC, GC,
PARSER, PARSER,
@ -47,10 +48,12 @@ enum StateTag {
// The output structure filled up by GetStackSample API function. // The output structure filled up by GetStackSample API function.
struct SampleInfo { struct SampleInfo {
size_t frames_count; // Number of frames collected. size_t frames_count; // Number of frames collected.
StateTag vm_state; // Current VM state.
void* external_callback_entry; // External callback address if VM is void* external_callback_entry; // External callback address if VM is
// executing an external callback. // executing an external callback.
void* context; // Incumbent native context address. 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 { struct MemoryRange {

View File

@ -15,6 +15,7 @@
#include "include/v8-callbacks.h" #include "include/v8-callbacks.h"
#include "include/v8-cppgc.h" #include "include/v8-cppgc.h"
#include "include/v8-date.h" #include "include/v8-date.h"
#include "include/v8-embedder-state-scope.h"
#include "include/v8-extension.h" #include "include/v8-extension.h"
#include "include/v8-fast-api-calls.h" #include "include/v8-fast-api-calls.h"
#include "include/v8-function.h" #include "include/v8-function.h"
@ -46,6 +47,7 @@
#include "src/debug/liveedit.h" #include "src/debug/liveedit.h"
#include "src/deoptimizer/deoptimizer.h" #include "src/deoptimizer/deoptimizer.h"
#include "src/diagnostics/gdb-jit.h" #include "src/diagnostics/gdb-jit.h"
#include "src/execution/embedder-state.h"
#include "src/execution/execution.h" #include "src/execution/execution.h"
#include "src/execution/frames-inl.h" #include "src/execution/frames-inl.h"
#include "src/execution/isolate-inl.h" #include "src/execution/isolate-inl.h"
@ -9863,6 +9865,16 @@ int64_t CpuProfile::GetSampleTimestamp(int index) const {
return profile->sample(index).timestamp.since_origin().InMicroseconds(); return profile->sample(index).timestamp.since_origin().InMicroseconds();
} }
StateTag CpuProfile::GetSampleState(int index) const {
const i::CpuProfile* profile = reinterpret_cast<const i::CpuProfile*>(this);
return profile->sample(index).state_tag;
}
EmbedderStateTag CpuProfile::GetSampleEmbedderState(int index) const {
const i::CpuProfile* profile = reinterpret_cast<const i::CpuProfile*>(this);
return profile->sample(index).embedder_state_tag;
}
int64_t CpuProfile::GetStartTime() const { int64_t CpuProfile::GetStartTime() const {
const i::CpuProfile* profile = reinterpret_cast<const i::CpuProfile*>(this); const i::CpuProfile* profile = reinterpret_cast<const i::CpuProfile*>(this);
return profile->start_time().since_origin().InMicroseconds(); return profile->start_time().since_origin().InMicroseconds();
@ -10331,6 +10343,11 @@ void EmbedderHeapTracer::ResetHandleInNonTracingGC(
UNREACHABLE(); UNREACHABLE();
} }
EmbedderStateScope::EmbedderStateScope(Isolate* isolate,
Local<v8::Context> context,
EmbedderStateTag tag)
: embedder_state_(new internal::EmbedderState(isolate, context, tag)) {}
void TracedReferenceBase::CheckValue() const { void TracedReferenceBase::CheckValue() const {
#ifdef V8_HOST_ARCH_64_BIT #ifdef V8_HOST_ARCH_64_BIT
if (!val_) return; if (!val_) return;

View File

@ -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<v8::Context> context,
EmbedderStateTag tag)
: isolate_(reinterpret_cast<i::Isolate*>(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

View File

@ -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<v8::Context> 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_

View File

@ -65,6 +65,8 @@ class V8Inspector;
namespace v8 { namespace v8 {
class EmbedderState;
namespace base { namespace base {
class RandomNumberGenerator; class RandomNumberGenerator;
} // namespace base } // namespace base
@ -1300,6 +1302,7 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
THREAD_LOCAL_TOP_ACCESSOR(ExternalCallbackScope*, external_callback_scope) THREAD_LOCAL_TOP_ACCESSOR(ExternalCallbackScope*, external_callback_scope)
THREAD_LOCAL_TOP_ACCESSOR(StateTag, current_vm_state) THREAD_LOCAL_TOP_ACCESSOR(StateTag, current_vm_state)
THREAD_LOCAL_TOP_ACCESSOR(EmbedderState*, current_embedder_state)
void SetData(uint32_t slot, void* data) { void SetData(uint32_t slot, void* data) {
DCHECK_LT(slot, Internals::kNumIsolateDataSlots); DCHECK_LT(slot, Internals::kNumIsolateDataSlots);

View File

@ -31,6 +31,7 @@ void ThreadLocalTop::Clear() {
js_entry_sp_ = kNullAddress; js_entry_sp_ = kNullAddress;
external_callback_scope_ = nullptr; external_callback_scope_ = nullptr;
current_vm_state_ = EXTERNAL; current_vm_state_ = EXTERNAL;
current_embedder_state_ = nullptr;
failed_access_check_callback_ = nullptr; failed_access_check_callback_ = nullptr;
thread_in_wasm_flag_address_ = kNullAddress; thread_in_wasm_flag_address_ = kNullAddress;
#ifdef V8_ENABLE_CONSERVATIVE_STACK_SCANNING #ifdef V8_ENABLE_CONSERVATIVE_STACK_SCANNING

View File

@ -23,6 +23,7 @@ class TryCatch;
namespace internal { namespace internal {
class EmbedderState;
class ExternalCallbackScope; class ExternalCallbackScope;
class Isolate; class Isolate;
class PromiseOnStack; class PromiseOnStack;
@ -34,9 +35,9 @@ class ThreadLocalTop {
// refactor this to really consist of just Addresses and 32-bit // refactor this to really consist of just Addresses and 32-bit
// integer fields. // integer fields.
#ifdef V8_ENABLE_CONSERVATIVE_STACK_SCANNING #ifdef V8_ENABLE_CONSERVATIVE_STACK_SCANNING
static constexpr uint32_t kSizeInBytes = 25 * kSystemPointerSize; static constexpr uint32_t kSizeInBytes = 26 * kSystemPointerSize;
#else #else
static constexpr uint32_t kSizeInBytes = 24 * kSystemPointerSize; static constexpr uint32_t kSizeInBytes = 25 * kSystemPointerSize;
#endif #endif
// Does early low-level initialization that does not depend on the // Does early low-level initialization that does not depend on the
@ -151,6 +152,7 @@ class ThreadLocalTop {
// The external callback we're currently in. // The external callback we're currently in.
ExternalCallbackScope* external_callback_scope_; ExternalCallbackScope* external_callback_scope_;
StateTag current_vm_state_; StateTag current_vm_state_;
EmbedderState* current_embedder_state_;
// Call back function to report unsafe JS accesses. // Call back function to report unsafe JS accesses.
v8::FailedAccessCheckCallback failed_access_check_callback_; v8::FailedAccessCheckCallback failed_access_check_callback_;

View File

@ -27,6 +27,7 @@
#include "src/compiler-dispatcher/optimizing-compile-dispatcher.h" #include "src/compiler-dispatcher/optimizing-compile-dispatcher.h"
#include "src/debug/debug.h" #include "src/debug/debug.h"
#include "src/deoptimizer/deoptimizer.h" #include "src/deoptimizer/deoptimizer.h"
#include "src/execution/embedder-state.h"
#include "src/execution/isolate-utils-inl.h" #include "src/execution/isolate-utils-inl.h"
#include "src/execution/microtask-queue.h" #include "src/execution/microtask-queue.h"
#include "src/execution/runtime-profiler.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(), LOG_CODE_EVENT(isolate_, SharedFunctionInfoMoveEvent(source.address(),
target.address())); target.address()));
} else if (target.IsNativeContext()) { } else if (target.IsNativeContext()) {
if (isolate_->current_embedder_state() != nullptr) {
isolate_->current_embedder_state()->OnMoveEvent(source.address(),
target.address());
}
PROFILE(isolate_, PROFILE(isolate_,
NativeContextMoveEvent(source.address(), target.address())); NativeContextMoveEvent(source.address(), target.address()));
} }

View File

@ -230,12 +230,15 @@ void ProfilerEventsProcessor::CodeEventHandler(
void SamplingEventsProcessor::SymbolizeAndAddToProfiles( void SamplingEventsProcessor::SymbolizeAndAddToProfiles(
const TickSampleEventRecord* record) { const TickSampleEventRecord* record) {
const TickSample& tick_sample = record->sample;
Symbolizer::SymbolizedSample symbolized = Symbolizer::SymbolizedSample symbolized =
symbolizer_->SymbolizeTickSample(record->sample); symbolizer_->SymbolizeTickSample(tick_sample);
profiles_->AddPathToCurrentProfiles( profiles_->AddPathToCurrentProfiles(
record->sample.timestamp, symbolized.stack_trace, symbolized.src_line, tick_sample.timestamp, symbolized.stack_trace, symbolized.src_line,
record->sample.update_stats_, record->sample.sampling_interval_, tick_sample.update_stats_, tick_sample.sampling_interval_,
reinterpret_cast<Address>(record->sample.context)); tick_sample.state, tick_sample.embedder_state,
reinterpret_cast<Address>(tick_sample.context),
reinterpret_cast<Address>(tick_sample.embedder_context));
} }
ProfilerEventsProcessor::SampleProcessingResult ProfilerEventsProcessor::SampleProcessingResult

View File

@ -620,7 +620,9 @@ bool CpuProfile::CheckSubsample(base::TimeDelta source_sampling_interval) {
void CpuProfile::AddPath(base::TimeTicks timestamp, void CpuProfile::AddPath(base::TimeTicks timestamp,
const ProfileStackTrace& path, int src_line, 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; if (!CheckSubsample(sampling_interval)) return;
ProfileNode* top_frame_node = ProfileNode* top_frame_node =
@ -632,7 +634,8 @@ void CpuProfile::AddPath(base::TimeTicks timestamp,
samples_.size() < options_.max_samples()); samples_.size() < options_.max_samples());
if (should_record_sample) { 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) { if (!should_record_sample && delegate_ != nullptr) {
@ -988,19 +991,26 @@ base::TimeDelta CpuProfilesCollection::GetCommonSamplingInterval() const {
void CpuProfilesCollection::AddPathToCurrentProfiles( void CpuProfilesCollection::AddPathToCurrentProfiles(
base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line, 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,
Address native_context_address) { EmbedderStateTag embedder_state_tag, Address native_context_address,
Address embedder_native_context_address) {
// As starting / stopping profiles is rare relatively to this // As starting / stopping profiles is rare relatively to this
// method, we don't bother minimizing the duration of lock holding, // method, we don't bother minimizing the duration of lock holding,
// e.g. copying contents of the list to a local vector. // e.g. copying contents of the list to a local vector.
current_profiles_semaphore_.Wait(); current_profiles_semaphore_.Wait();
const ProfileStackTrace empty_path; const ProfileStackTrace empty_path;
for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) { for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) {
ContextFilter& context_filter = profile->context_filter();
// If the context filter check failed, omit the contents of the stack. // If the context filter check failed, omit the contents of the stack.
bool accepts_context = bool accepts_context = context_filter.Accept(native_context_address);
profile->context_filter().Accept(native_context_address); bool accepts_embedder_context =
context_filter.native_context_address() != kNullAddress &&
context_filter.native_context_address() ==
embedder_native_context_address;
profile->AddPath(timestamp, accepts_context ? path : empty_path, src_line, 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(); current_profiles_semaphore_.Signal();
} }

View File

@ -17,6 +17,7 @@
#include "include/v8-profiler.h" #include "include/v8-profiler.h"
#include "src/base/platform/time.h" #include "src/base/platform/time.h"
#include "src/builtins/builtins.h" #include "src/builtins/builtins.h"
#include "src/execution/vm-state.h"
#include "src/logging/code-events.h" #include "src/logging/code-events.h"
#include "src/profiler/strings-storage.h" #include "src/profiler/strings-storage.h"
#include "src/utils/allocation.h" #include "src/utils/allocation.h"
@ -405,6 +406,8 @@ class CpuProfile {
ProfileNode* node; ProfileNode* node;
base::TimeTicks timestamp; base::TimeTicks timestamp;
int line; int line;
StateTag state_tag;
EmbedderStateTag embedder_state_tag;
}; };
V8_EXPORT_PRIVATE CpuProfile( V8_EXPORT_PRIVATE CpuProfile(
@ -419,7 +422,8 @@ class CpuProfile {
// Add pc -> ... -> main() call path to the profile. // Add pc -> ... -> main() call path to the profile.
void AddPath(base::TimeTicks timestamp, const ProfileStackTrace& path, void AddPath(base::TimeTicks timestamp, const ProfileStackTrace& path,
int src_line, bool update_stats, int src_line, bool update_stats,
base::TimeDelta sampling_interval); base::TimeDelta sampling_interval, StateTag state,
EmbedderStateTag embedder_state);
void FinishProfile(); void FinishProfile();
const char* title() const { return title_; } const char* title() const { return title_; }
@ -554,11 +558,12 @@ class V8_EXPORT_PRIVATE CpuProfilesCollection {
base::TimeDelta GetCommonSamplingInterval() const; base::TimeDelta GetCommonSamplingInterval() const;
// Called from profile generator thread. // Called from profile generator thread.
void AddPathToCurrentProfiles(base::TimeTicks timestamp, void AddPathToCurrentProfiles(
const ProfileStackTrace& path, int src_line, base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line,
bool update_stats, bool update_stats, base::TimeDelta sampling_interval, StateTag state,
base::TimeDelta sampling_interval, EmbedderStateTag embedder_state_tag,
Address native_context_address = kNullAddress); Address native_context_address = kNullAddress,
Address native_embedder_context_address = kNullAddress);
// Called from profile generator thread. // Called from profile generator thread.
void UpdateNativeContextAddressForCurrentProfiles(Address from, Address to); void UpdateNativeContextAddressForCurrentProfiles(Address from, Address to);

View File

@ -9,6 +9,7 @@
#include "include/v8-profiler.h" #include "include/v8-profiler.h"
#include "src/base/sanitizer/asan.h" #include "src/base/sanitizer/asan.h"
#include "src/base/sanitizer/msan.h" #include "src/base/sanitizer/msan.h"
#include "src/execution/embedder-state.h"
#include "src/execution/frames-inl.h" #include "src/execution/frames-inl.h"
#include "src/execution/simulator.h" #include "src/execution/simulator.h"
#include "src/execution/vm-state-inl.h" #include "src/execution/vm-state-inl.h"
@ -178,6 +179,8 @@ DISABLE_ASAN void TickSample::Init(Isolate* v8_isolate,
frames_count = static_cast<unsigned>(info.frames_count); frames_count = static_cast<unsigned>(info.frames_count);
has_external_callback = info.external_callback_entry != nullptr; has_external_callback = info.external_callback_entry != nullptr;
context = info.context; context = info.context;
embedder_context = info.embedder_context;
embedder_state = info.embedder_state;
if (has_external_callback) { if (has_external_callback) {
external_callback_entry = info.external_callback_entry; external_callback_entry = info.external_callback_entry;
} else if (frames_count) { } else if (frames_count) {
@ -210,6 +213,14 @@ bool TickSample::GetStackSample(Isolate* v8_isolate, RegisterState* regs,
sample_info->frames_count = 0; sample_info->frames_count = 0;
sample_info->vm_state = isolate->current_vm_state(); sample_info->vm_state = isolate->current_vm_state();
sample_info->external_callback_entry = nullptr; sample_info->external_callback_entry = nullptr;
sample_info->embedder_state = EmbedderStateTag::EMPTY;
EmbedderState* embedder_state = isolate->current_embedder_state();
if (embedder_state != nullptr) {
sample_info->embedder_context =
reinterpret_cast<void*>(embedder_state->native_context_address());
sample_info->embedder_state = embedder_state->GetState();
}
sample_info->context = nullptr; sample_info->context = nullptr;
if (sample_info->vm_state == GC) return true; if (sample_info->vm_state == GC) return true;

View File

@ -23,6 +23,7 @@ struct V8_EXPORT TickSample {
TickSample() TickSample()
: state(OTHER), : state(OTHER),
embedder_state(EmbedderStateTag::EMPTY),
pc(nullptr), pc(nullptr),
external_callback_entry(nullptr), external_callback_entry(nullptr),
frames_count(0), frames_count(0),
@ -82,6 +83,7 @@ struct V8_EXPORT TickSample {
void print() const; void print() const;
StateTag state; // The state of the VM. StateTag state; // The state of the VM.
EmbedderStateTag embedder_state;
void* pc; // Instruction pointer. void* pc; // Instruction pointer.
union { union {
void* tos; // Top stack value (*sp). void* tos; // Top stack value (*sp).
@ -91,6 +93,7 @@ struct V8_EXPORT TickSample {
static const unsigned kMaxFramesCount = (1 << kMaxFramesCountLog2) - 1; static const unsigned kMaxFramesCount = (1 << kMaxFramesCountLog2) - 1;
void* stack[kMaxFramesCount]; // Call stack. void* stack[kMaxFramesCount]; // Call stack.
void* context = nullptr; // Address of the incumbent native context. 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. unsigned frames_count : kMaxFramesCountLog2; // Number of captured frames.
bool has_external_callback : 1; bool has_external_callback : 1;
bool update_stats_ : 1; // Whether the sample should update aggregated stats. bool update_stats_ : 1; // Whether the sample should update aggregated stats.

View File

@ -41,6 +41,7 @@
#include "src/codegen/compilation-cache.h" #include "src/codegen/compilation-cache.h"
#include "src/codegen/source-position-table.h" #include "src/codegen/source-position-table.h"
#include "src/deoptimizer/deoptimizer.h" #include "src/deoptimizer/deoptimizer.h"
#include "src/execution/embedder-state.h"
#include "src/heap/spaces.h" #include "src/heap/spaces.h"
#include "src/init/v8.h" #include "src/init/v8.h"
#include "src/libplatform/default-platform.h" #include "src/libplatform/default-platform.h"
@ -3922,6 +3923,216 @@ TEST(ContextIsolation) {
} }
} }
// 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<v8::FunctionTemplate> func_template =
v8::FunctionTemplate::New(isolate, CallCollectSample);
v8::Local<v8::Function> 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<v8::Context> 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<v8::Function> 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());
for (int i = 0; i < profile->GetSamplesCount(); i++) {
CHECK_EQ(profile->GetSampleEmbedderState(i), 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<v8::FunctionTemplate> func_template =
v8::FunctionTemplate::New(isolate, CallCollectSample);
v8::Local<v8::Function> 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<v8::Function> 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());
for (int i = 0; i < profile->GetSamplesCount(); i++) {
CHECK_EQ(profile->GetSampleEmbedderState(i), 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<v8::FunctionTemplate> func_template =
v8::FunctionTemplate::New(isolate, CallCollectSample);
v8::Local<v8::Function> 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 =
reinterpret_cast<i::Address>(CcTest::i_isolate()
->current_embedder_state()
->native_context_address());
// Install a function that triggers the native context to be moved.
v8::Local<v8::FunctionTemplate> move_func_template =
v8::FunctionTemplate::New(
execution_env.local()->GetIsolate(),
[](const v8::FunctionCallbackInfo<v8::Value>& info) {
i::Isolate* isolate =
reinterpret_cast<i::Isolate*>(info.GetIsolate());
i::heap::ForceEvacuationCandidate(
i::Page::FromHeapObject(isolate->raw_native_context()));
CcTest::CollectAllGarbage();
});
v8::Local<v8::Function> 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<v8::Function> 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());
for (int i = 0; i < profile->GetSamplesCount(); i++) {
CHECK_EQ(profile->GetSampleEmbedderState(i), EmbedderStateTag::OTHER);
}
i::Address new_address =
reinterpret_cast<i::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 // Tests that when a native context that's being filtered is moved, we continue
// to track its execution. // to track its execution.
TEST(ContextFilterMovedNativeContext) { TEST(ContextFilterMovedNativeContext) {

View File

@ -482,9 +482,9 @@ TEST(SampleIds) {
sample1.stack[0] = ToPointer(0x1510); sample1.stack[0] = ToPointer(0x1510);
sample1.frames_count = 1; sample1.frames_count = 1;
auto symbolized = symbolizer.SymbolizeTickSample(sample1); auto symbolized = symbolizer.SymbolizeTickSample(sample1);
profiles.AddPathToCurrentProfiles(sample1.timestamp, symbolized.stack_trace, profiles.AddPathToCurrentProfiles(
symbolized.src_line, true, sample1.timestamp, symbolized.stack_trace, symbolized.src_line, true,
base::TimeDelta()); base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY);
TickSample sample2; TickSample sample2;
sample2.timestamp = v8::base::TimeTicks::HighResolutionNow(); sample2.timestamp = v8::base::TimeTicks::HighResolutionNow();
@ -494,9 +494,9 @@ TEST(SampleIds) {
sample2.stack[2] = ToPointer(0x1620); sample2.stack[2] = ToPointer(0x1620);
sample2.frames_count = 3; sample2.frames_count = 3;
symbolized = symbolizer.SymbolizeTickSample(sample2); symbolized = symbolizer.SymbolizeTickSample(sample2);
profiles.AddPathToCurrentProfiles(sample2.timestamp, symbolized.stack_trace, profiles.AddPathToCurrentProfiles(
symbolized.src_line, true, sample2.timestamp, symbolized.stack_trace, symbolized.src_line, true,
base::TimeDelta()); base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY);
TickSample sample3; TickSample sample3;
sample3.timestamp = v8::base::TimeTicks::HighResolutionNow(); sample3.timestamp = v8::base::TimeTicks::HighResolutionNow();
@ -505,9 +505,9 @@ TEST(SampleIds) {
sample3.stack[1] = ToPointer(0x1610); sample3.stack[1] = ToPointer(0x1610);
sample3.frames_count = 2; sample3.frames_count = 2;
symbolized = symbolizer.SymbolizeTickSample(sample3); symbolized = symbolizer.SymbolizeTickSample(sample3);
profiles.AddPathToCurrentProfiles(sample3.timestamp, symbolized.stack_trace, profiles.AddPathToCurrentProfiles(
symbolized.src_line, true, sample3.timestamp, symbolized.stack_trace, symbolized.src_line, true,
base::TimeDelta()); base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY);
CpuProfile* profile = profiles.StopProfiling(""); CpuProfile* profile = profiles.StopProfiling("");
unsigned nodeId = 1; unsigned nodeId = 1;
@ -603,9 +603,9 @@ TEST(MaxSamplesCallback) {
sample1.stack[0] = ToPointer(0x1510); sample1.stack[0] = ToPointer(0x1510);
sample1.frames_count = 1; sample1.frames_count = 1;
auto symbolized = symbolizer.SymbolizeTickSample(sample1); auto symbolized = symbolizer.SymbolizeTickSample(sample1);
profiles.AddPathToCurrentProfiles(sample1.timestamp, symbolized.stack_trace, profiles.AddPathToCurrentProfiles(
symbolized.src_line, true, sample1.timestamp, symbolized.stack_trace, symbolized.src_line, true,
base::TimeDelta()); base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY);
CHECK_EQ(0, mock_platform->posted_count()); CHECK_EQ(0, mock_platform->posted_count());
TickSample sample2; TickSample sample2;
sample2.timestamp = v8::base::TimeTicks::HighResolutionNow(); sample2.timestamp = v8::base::TimeTicks::HighResolutionNow();
@ -613,18 +613,18 @@ TEST(MaxSamplesCallback) {
sample2.stack[0] = ToPointer(0x1780); sample2.stack[0] = ToPointer(0x1780);
sample2.frames_count = 2; sample2.frames_count = 2;
symbolized = symbolizer.SymbolizeTickSample(sample2); symbolized = symbolizer.SymbolizeTickSample(sample2);
profiles.AddPathToCurrentProfiles(sample2.timestamp, symbolized.stack_trace, profiles.AddPathToCurrentProfiles(
symbolized.src_line, true, sample2.timestamp, symbolized.stack_trace, symbolized.src_line, true,
base::TimeDelta()); base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY);
CHECK_EQ(1, mock_platform->posted_count()); CHECK_EQ(1, mock_platform->posted_count());
TickSample sample3; TickSample sample3;
sample3.timestamp = v8::base::TimeTicks::HighResolutionNow(); sample3.timestamp = v8::base::TimeTicks::HighResolutionNow();
sample3.pc = ToPointer(0x1510); sample3.pc = ToPointer(0x1510);
sample3.frames_count = 3; sample3.frames_count = 3;
symbolized = symbolizer.SymbolizeTickSample(sample3); symbolized = symbolizer.SymbolizeTickSample(sample3);
profiles.AddPathToCurrentProfiles(sample3.timestamp, symbolized.stack_trace, profiles.AddPathToCurrentProfiles(
symbolized.src_line, true, sample3.timestamp, symbolized.stack_trace, symbolized.src_line, true,
base::TimeDelta()); base::TimeDelta(), StateTag::JS, EmbedderStateTag::EMPTY);
CHECK_EQ(1, mock_platform->posted_count()); CHECK_EQ(1, mock_platform->posted_count());
// Teardown // Teardown
@ -654,7 +654,8 @@ TEST(NoSamples) {
auto symbolized = symbolizer.SymbolizeTickSample(sample1); auto symbolized = symbolizer.SymbolizeTickSample(sample1);
profiles.AddPathToCurrentProfiles(v8::base::TimeTicks::HighResolutionNow(), profiles.AddPathToCurrentProfiles(v8::base::TimeTicks::HighResolutionNow(),
symbolized.stack_trace, symbolized.src_line, symbolized.stack_trace, symbolized.src_line,
true, base::TimeDelta()); true, base::TimeDelta(), StateTag::JS,
EmbedderStateTag::EMPTY);
CpuProfile* profile = profiles.StopProfiling(""); CpuProfile* profile = profiles.StopProfiling("");
unsigned nodeId = 1; unsigned nodeId = 1;