[cpu-profiler] Track code object deletion using WeakCodeRegistry
Propagates CodeDeleteEvents to the CPU profiler based on finalizers registered in a WeakCodeRegistry, which tracks heap objects for weakly owned CodeEntries. Bug: v8:11054 Change-Id: I4c1f7885e982241724ca9f284f864da008ce9d75 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2751606 Reviewed-by: Yang Guo <yangguo@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Commit-Queue: Andrew Comminos <acomminos@fb.com> Cr-Commit-Position: refs/heads/master@{#73585}
This commit is contained in:
parent
7a7e48a152
commit
34c37396f0
2
BUILD.gn
2
BUILD.gn
@ -2903,6 +2903,7 @@ v8_header_set("v8_internal_headers") {
|
||||
"src/profiler/symbolizer.h",
|
||||
"src/profiler/tick-sample.h",
|
||||
"src/profiler/tracing-cpu-profiler.h",
|
||||
"src/profiler/weak-code-registry.h",
|
||||
"src/regexp/experimental/experimental-bytecode.h",
|
||||
"src/regexp/experimental/experimental-compiler.h",
|
||||
"src/regexp/experimental/experimental-interpreter.h",
|
||||
@ -3846,6 +3847,7 @@ v8_source_set("v8_base_without_compiler") {
|
||||
"src/profiler/symbolizer.cc",
|
||||
"src/profiler/tick-sample.cc",
|
||||
"src/profiler/tracing-cpu-profiler.cc",
|
||||
"src/profiler/weak-code-registry.cc",
|
||||
"src/regexp/experimental/experimental-bytecode.cc",
|
||||
"src/regexp/experimental/experimental-compiler.cc",
|
||||
"src/regexp/experimental/experimental-interpreter.cc",
|
||||
|
@ -2020,6 +2020,9 @@ void MarkCompactCollector::MarkLiveObjects() {
|
||||
DCHECK(local_marking_worklists()->IsEmpty());
|
||||
}
|
||||
|
||||
// We depend on IterateWeakRootsForPhantomHandles being called before
|
||||
// ClearOldBytecodeCandidates in order to identify flushed bytecode in the
|
||||
// CPU profiler.
|
||||
{
|
||||
heap()->isolate()->global_handles()->IterateWeakRootsForPhantomHandles(
|
||||
&IsUnmarkedHeapObject);
|
||||
@ -2083,6 +2086,8 @@ void MarkCompactCollector::ClearNonLiveReferences() {
|
||||
ClearJSWeakRefs();
|
||||
}
|
||||
|
||||
PROFILE(heap()->isolate(), WeakCodeClearEvent());
|
||||
|
||||
MarkDependentCodeForDeoptimization();
|
||||
|
||||
DCHECK(weak_objects_.transition_arrays.IsEmpty());
|
||||
@ -2206,8 +2211,6 @@ void MarkCompactCollector::FlushBytecodeFromSFI(
|
||||
// performing the unusual task of decompiling.
|
||||
shared_info.set_function_data(uncompiled_data, kReleaseStore);
|
||||
DCHECK(!shared_info.is_compiled());
|
||||
|
||||
PROFILE(heap()->isolate(), BytecodeFlushEvent(compiled_data_start));
|
||||
}
|
||||
|
||||
void MarkCompactCollector::ClearOldBytecodeCandidates() {
|
||||
|
@ -37,8 +37,7 @@ using WasmName = Vector<const char>;
|
||||
V(CODE_MOVING_GC, code-moving-gc) \
|
||||
V(SHARED_FUNC_MOVE_EVENT, sfi-move) \
|
||||
V(SNAPSHOT_CODE_NAME_EVENT, snapshot-code-name) \
|
||||
V(TICK_EVENT, tick) \
|
||||
V(BYTECODE_FLUSH_EVENT, bytecode-flush)
|
||||
V(TICK_EVENT, tick)
|
||||
// clang-format on
|
||||
|
||||
#define TAGS_LIST(V) \
|
||||
@ -110,8 +109,9 @@ class CodeEventListener {
|
||||
virtual void CodeDependencyChangeEvent(Handle<Code> code,
|
||||
Handle<SharedFunctionInfo> shared,
|
||||
const char* reason) = 0;
|
||||
// Invoked during GC. No allocation allowed.
|
||||
virtual void BytecodeFlushEvent(Address compiled_data_start) = 0;
|
||||
// Called during GC shortly after any weak references to code objects are
|
||||
// cleared.
|
||||
virtual void WeakCodeClearEvent() = 0;
|
||||
|
||||
virtual bool is_listening_to_code_events() { return false; }
|
||||
};
|
||||
@ -240,10 +240,9 @@ class CodeEventDispatcher : public CodeEventListener {
|
||||
listener->CodeDependencyChangeEvent(code, sfi, reason);
|
||||
});
|
||||
}
|
||||
void BytecodeFlushEvent(Address compiled_data_start) override {
|
||||
DispatchEventToListeners([=](CodeEventListener* listener) {
|
||||
listener->BytecodeFlushEvent(compiled_data_start);
|
||||
});
|
||||
void WeakCodeClearEvent() override {
|
||||
DispatchEventToListeners(
|
||||
[=](CodeEventListener* listener) { listener->WeakCodeClearEvent(); });
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -216,7 +216,7 @@ class Logger : public CodeEventListener {
|
||||
void CodeDependencyChangeEvent(Handle<Code> code,
|
||||
Handle<SharedFunctionInfo> sfi,
|
||||
const char* reason) override;
|
||||
void BytecodeFlushEvent(Address compiled_data_start) override {}
|
||||
void WeakCodeClearEvent() override {}
|
||||
|
||||
void ProcessDeoptEvent(Handle<Code> code, SourcePosition position,
|
||||
const char* kind, const char* reason);
|
||||
@ -416,7 +416,7 @@ class V8_EXPORT_PRIVATE CodeEventLogger : public CodeEventListener {
|
||||
void CodeDependencyChangeEvent(Handle<Code> code,
|
||||
Handle<SharedFunctionInfo> sfi,
|
||||
const char* reason) override {}
|
||||
void BytecodeFlushEvent(Address compiled_data_start) override {}
|
||||
void WeakCodeClearEvent() override {}
|
||||
|
||||
protected:
|
||||
Isolate* isolate_;
|
||||
@ -484,7 +484,7 @@ class ExternalCodeEventListener : public CodeEventListener {
|
||||
void CodeDependencyChangeEvent(Handle<Code> code,
|
||||
Handle<SharedFunctionInfo> sfi,
|
||||
const char* reason) override {}
|
||||
void BytecodeFlushEvent(Address compiled_data_start) override {}
|
||||
void WeakCodeClearEvent() override {}
|
||||
|
||||
void StartListening(v8::CodeEventHandler* code_event_handler);
|
||||
void StopListening();
|
||||
|
@ -68,8 +68,9 @@ TickSample* SamplingEventsProcessor::StartTickSample() {
|
||||
return &evt->sample;
|
||||
}
|
||||
|
||||
void BytecodeFlushEventRecord::UpdateCodeMap(CodeMap* code_map) {
|
||||
code_map->ClearCodesInRange(instruction_start, instruction_start + 1);
|
||||
void CodeDeleteEventRecord::UpdateCodeMap(CodeMap* code_map) {
|
||||
bool removed = code_map->RemoveCode(entry);
|
||||
CHECK(removed);
|
||||
}
|
||||
|
||||
void SamplingEventsProcessor::FinishTickSample() {
|
||||
|
@ -201,7 +201,7 @@ void ProfilerEventsProcessor::CodeEventHandler(
|
||||
case CodeEventRecord::CODE_CREATION:
|
||||
case CodeEventRecord::CODE_MOVE:
|
||||
case CodeEventRecord::CODE_DISABLE_OPT:
|
||||
case CodeEventRecord::BYTECODE_FLUSH:
|
||||
case CodeEventRecord::CODE_DELETE:
|
||||
Enqueue(evt_rec);
|
||||
break;
|
||||
case CodeEventRecord::CODE_DEOPT: {
|
||||
@ -328,12 +328,16 @@ void* SamplingEventsProcessor::operator new(size_t size) {
|
||||
void SamplingEventsProcessor::operator delete(void* ptr) { AlignedFree(ptr); }
|
||||
|
||||
ProfilerCodeObserver::ProfilerCodeObserver(Isolate* isolate)
|
||||
: isolate_(isolate), code_map_(strings_), processor_(nullptr) {
|
||||
: isolate_(isolate),
|
||||
code_map_(strings_),
|
||||
weak_code_registry_(isolate),
|
||||
processor_(nullptr) {
|
||||
CreateEntriesForRuntimeCallStats();
|
||||
LogBuiltins();
|
||||
}
|
||||
|
||||
void ProfilerCodeObserver::ClearCodeMap() {
|
||||
weak_code_registry_.Clear();
|
||||
code_map_.Clear();
|
||||
// We don't currently expect any references to refcounted strings to be
|
||||
// maintained with zero profiles after the code map is cleared.
|
||||
@ -513,9 +517,9 @@ void CpuProfiler::EnableLogging() {
|
||||
if (profiling_scope_) return;
|
||||
|
||||
if (!profiler_listener_) {
|
||||
profiler_listener_.reset(
|
||||
new ProfilerListener(isolate_, code_observer_.get(),
|
||||
*code_observer_->strings(), naming_mode_));
|
||||
profiler_listener_.reset(new ProfilerListener(
|
||||
isolate_, code_observer_.get(), *code_observer_->strings(),
|
||||
*code_observer_->weak_code_registry(), naming_mode_));
|
||||
}
|
||||
profiling_scope_.reset(
|
||||
new ProfilingScope(isolate_, profiler_listener_.get()));
|
||||
|
@ -35,7 +35,7 @@ class Symbolizer;
|
||||
V(CODE_DISABLE_OPT, CodeDisableOptEventRecord) \
|
||||
V(CODE_DEOPT, CodeDeoptEventRecord) \
|
||||
V(REPORT_BUILTIN, ReportBuiltinEventRecord) \
|
||||
V(BYTECODE_FLUSH, BytecodeFlushEventRecord)
|
||||
V(CODE_DELETE, CodeDeleteEventRecord)
|
||||
|
||||
class CodeEventRecord {
|
||||
public:
|
||||
@ -112,9 +112,9 @@ class TickSampleEventRecord {
|
||||
TickSample sample;
|
||||
};
|
||||
|
||||
class BytecodeFlushEventRecord : public CodeEventRecord {
|
||||
class CodeDeleteEventRecord : public CodeEventRecord {
|
||||
public:
|
||||
Address instruction_start;
|
||||
CodeEntry* entry;
|
||||
|
||||
V8_INLINE void UpdateCodeMap(CodeMap* code_map);
|
||||
};
|
||||
@ -255,6 +255,7 @@ class V8_EXPORT_PRIVATE ProfilerCodeObserver : public CodeEventObserver {
|
||||
void CodeEventHandler(const CodeEventsContainer& evt_rec) override;
|
||||
CodeMap* code_map() { return &code_map_; }
|
||||
StringsStorage* strings() { return &strings_; }
|
||||
WeakCodeRegistry* weak_code_registry() { return &weak_code_registry_; }
|
||||
|
||||
void ClearCodeMap();
|
||||
|
||||
@ -279,6 +280,7 @@ class V8_EXPORT_PRIVATE ProfilerCodeObserver : public CodeEventObserver {
|
||||
Isolate* const isolate_;
|
||||
StringsStorage strings_;
|
||||
CodeMap code_map_;
|
||||
WeakCodeRegistry weak_code_registry_;
|
||||
ProfilerEventsProcessor* processor_;
|
||||
};
|
||||
|
||||
|
@ -334,7 +334,6 @@ CpuProfileNode::SourceType ProfileNode::source_type() const {
|
||||
case CodeEventListener::SHARED_FUNC_MOVE_EVENT:
|
||||
case CodeEventListener::SNAPSHOT_CODE_NAME_EVENT:
|
||||
case CodeEventListener::TICK_EVENT:
|
||||
case CodeEventListener::BYTECODE_FLUSH_EVENT:
|
||||
case CodeEventListener::NUMBER_OF_LOG_EVENTS:
|
||||
return CpuProfileNode::kInternal;
|
||||
}
|
||||
@ -755,8 +754,24 @@ void CodeMap::Clear() {
|
||||
}
|
||||
|
||||
void CodeMap::AddCode(Address addr, CodeEntry* entry, unsigned size) {
|
||||
ClearCodesInRange(addr, addr + size);
|
||||
code_map_.emplace(addr, CodeEntryMapInfo{entry, size});
|
||||
entry->set_instruction_start(addr);
|
||||
}
|
||||
|
||||
bool CodeMap::RemoveCode(CodeEntry* entry) {
|
||||
auto range = code_map_.equal_range(entry->instruction_start());
|
||||
for (auto i = range.first; i != range.second; ++i) {
|
||||
if (i->second.entry == entry) {
|
||||
if (!entry->used()) {
|
||||
DeleteCodeEntry(entry);
|
||||
} else {
|
||||
used_entries_.push_back(entry);
|
||||
}
|
||||
code_map_.erase(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CodeMap::ClearCodesInRange(Address start, Address end) {
|
||||
@ -777,6 +792,9 @@ void CodeMap::ClearCodesInRange(Address start, Address end) {
|
||||
}
|
||||
|
||||
CodeEntry* CodeMap::FindEntry(Address addr, Address* out_instruction_start) {
|
||||
// Note that an address may correspond to multiple CodeEntry objects. An
|
||||
// arbitrary selection is made (as per multimap spec) in the event of a
|
||||
// collision.
|
||||
auto it = code_map_.upper_bound(addr);
|
||||
if (it == code_map_.begin()) return nullptr;
|
||||
--it;
|
||||
@ -790,13 +808,25 @@ CodeEntry* CodeMap::FindEntry(Address addr, Address* out_instruction_start) {
|
||||
|
||||
void CodeMap::MoveCode(Address from, Address to) {
|
||||
if (from == to) return;
|
||||
auto it = code_map_.find(from);
|
||||
if (it == code_map_.end()) return;
|
||||
CodeEntryMapInfo info = it->second;
|
||||
code_map_.erase(it);
|
||||
DCHECK(from + info.size <= to || to + info.size <= from);
|
||||
ClearCodesInRange(to, to + info.size);
|
||||
code_map_.emplace(to, info);
|
||||
|
||||
auto range = code_map_.equal_range(from);
|
||||
// Instead of iterating until |range.second|, iterate the number of elements.
|
||||
// This is because the |range.second| may no longer be the element past the
|
||||
// end of the equal elements range after insertions.
|
||||
size_t distance = std::distance(range.first, range.second);
|
||||
auto it = range.first;
|
||||
while (distance--) {
|
||||
CodeEntryMapInfo& info = it->second;
|
||||
DCHECK(info.entry);
|
||||
DCHECK_EQ(info.entry->instruction_start(), from);
|
||||
info.entry->set_instruction_start(to);
|
||||
|
||||
DCHECK(from + info.size <= to || to + info.size <= from);
|
||||
code_map_.emplace(to, info);
|
||||
it++;
|
||||
}
|
||||
|
||||
code_map_.erase(range.first, it);
|
||||
}
|
||||
|
||||
void CodeMap::DeleteCodeEntry(CodeEntry* entry) {
|
||||
|
@ -72,6 +72,11 @@ class CodeEntry {
|
||||
CodeType code_type = CodeType::JS);
|
||||
CodeEntry(const CodeEntry&) = delete;
|
||||
CodeEntry& operator=(const CodeEntry&) = delete;
|
||||
~CodeEntry() {
|
||||
// No alive handles should be associated with the CodeEntry at time of
|
||||
// destruction.
|
||||
DCHECK(!heap_object_location_);
|
||||
}
|
||||
|
||||
const char* name() const { return name_; }
|
||||
const char* resource_name() const { return resource_name_; }
|
||||
@ -116,6 +121,13 @@ class CodeEntry {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the start address of the instruction segment represented by this
|
||||
// CodeEntry. Used as a key in the containing CodeMap.
|
||||
Address instruction_start() const { return instruction_start_; }
|
||||
void set_instruction_start(Address address) { instruction_start_ = address; }
|
||||
|
||||
Address** heap_object_location_address() { return &heap_object_location_; }
|
||||
|
||||
void FillFunctionInfo(SharedFunctionInfo shared);
|
||||
|
||||
void SetBuiltinId(Builtins::Name id);
|
||||
@ -214,6 +226,8 @@ class CodeEntry {
|
||||
int position_;
|
||||
std::unique_ptr<SourcePositionTable> line_info_;
|
||||
std::unique_ptr<RareData> rare_data_;
|
||||
Address instruction_start_ = kNullAddress;
|
||||
Address* heap_object_location_ = nullptr;
|
||||
};
|
||||
|
||||
struct CodeEntryAndLineNumber {
|
||||
@ -420,9 +434,13 @@ class V8_EXPORT_PRIVATE CodeMap {
|
||||
|
||||
void AddCode(Address addr, CodeEntry* entry, unsigned size);
|
||||
void MoveCode(Address from, Address to);
|
||||
// Attempts to remove the given CodeEntry from the CodeMap.
|
||||
// Returns true iff the entry was found and removed.
|
||||
bool RemoveCode(CodeEntry*);
|
||||
void ClearCodesInRange(Address start, Address end);
|
||||
CodeEntry* FindEntry(Address addr, Address* out_instruction_start = nullptr);
|
||||
void Print();
|
||||
size_t size() const { return code_map_.size(); }
|
||||
|
||||
void Clear();
|
||||
|
||||
@ -434,7 +452,7 @@ class V8_EXPORT_PRIVATE CodeMap {
|
||||
|
||||
void DeleteCodeEntry(CodeEntry*);
|
||||
|
||||
std::map<Address, CodeEntryMapInfo> code_map_;
|
||||
std::multimap<Address, CodeEntryMapInfo> code_map_;
|
||||
std::deque<CodeEntry*> used_entries_; // Entries that are no longer in the
|
||||
// map, but used by a profile.
|
||||
StringsStorage& function_and_resource_names_;
|
||||
|
@ -30,10 +30,12 @@ namespace internal {
|
||||
ProfilerListener::ProfilerListener(Isolate* isolate,
|
||||
CodeEventObserver* observer,
|
||||
StringsStorage& function_and_resource_names,
|
||||
WeakCodeRegistry& weak_code_registry,
|
||||
CpuProfilingNamingMode naming_mode)
|
||||
: isolate_(isolate),
|
||||
observer_(observer),
|
||||
function_and_resource_names_(function_and_resource_names),
|
||||
weak_code_registry_(weak_code_registry),
|
||||
naming_mode_(naming_mode) {}
|
||||
|
||||
ProfilerListener::~ProfilerListener() = default;
|
||||
@ -48,6 +50,7 @@ void ProfilerListener::CodeCreateEvent(LogEventsAndTags tag,
|
||||
CpuProfileNode::kNoLineNumberInfo,
|
||||
CpuProfileNode::kNoColumnNumberInfo, nullptr);
|
||||
rec->instruction_size = code->InstructionSize();
|
||||
weak_code_registry_.Track(rec->entry, code);
|
||||
DispatchCodeEvent(evt_rec);
|
||||
}
|
||||
|
||||
@ -61,6 +64,7 @@ void ProfilerListener::CodeCreateEvent(LogEventsAndTags tag,
|
||||
CpuProfileNode::kNoLineNumberInfo,
|
||||
CpuProfileNode::kNoColumnNumberInfo, nullptr);
|
||||
rec->instruction_size = code->InstructionSize();
|
||||
weak_code_registry_.Track(rec->entry, code);
|
||||
DispatchCodeEvent(evt_rec);
|
||||
}
|
||||
|
||||
@ -78,6 +82,7 @@ void ProfilerListener::CodeCreateEvent(LogEventsAndTags tag,
|
||||
DCHECK(!code->IsCode());
|
||||
rec->entry->FillFunctionInfo(*shared);
|
||||
rec->instruction_size = code->InstructionSize();
|
||||
weak_code_registry_.Track(rec->entry, code);
|
||||
DispatchCodeEvent(evt_rec);
|
||||
}
|
||||
|
||||
@ -220,6 +225,7 @@ void ProfilerListener::CodeCreateEvent(LogEventsAndTags tag,
|
||||
|
||||
rec->entry->FillFunctionInfo(*shared);
|
||||
rec->instruction_size = abstract_code->InstructionSize();
|
||||
weak_code_registry_.Track(rec->entry, abstract_code);
|
||||
DispatchCodeEvent(evt_rec);
|
||||
}
|
||||
|
||||
@ -283,6 +289,7 @@ void ProfilerListener::RegExpCodeCreateEvent(Handle<AbstractCode> code,
|
||||
CodeEntry::kEmptyResourceName, CpuProfileNode::kNoLineNumberInfo,
|
||||
CpuProfileNode::kNoColumnNumberInfo, nullptr);
|
||||
rec->instruction_size = code->InstructionSize();
|
||||
weak_code_registry_.Track(rec->entry, code);
|
||||
DispatchCodeEvent(evt_rec);
|
||||
}
|
||||
|
||||
@ -324,14 +331,16 @@ void ProfilerListener::CodeDeoptEvent(Handle<Code> code, DeoptimizeKind kind,
|
||||
DispatchCodeEvent(evt_rec);
|
||||
}
|
||||
|
||||
void ProfilerListener::BytecodeFlushEvent(Address compiled_data_start) {
|
||||
CodeEventsContainer evt_rec(CodeEventRecord::BYTECODE_FLUSH);
|
||||
BytecodeFlushEventRecord* rec = &evt_rec.BytecodeFlushEventRecord_;
|
||||
rec->instruction_start = compiled_data_start + BytecodeArray::kHeaderSize;
|
||||
void ProfilerListener::WeakCodeClearEvent() { weak_code_registry_.Sweep(this); }
|
||||
|
||||
void ProfilerListener::OnHeapObjectDeletion(CodeEntry* entry) {
|
||||
CodeEventsContainer evt_rec(CodeEventRecord::CODE_DELETE);
|
||||
evt_rec.CodeDeleteEventRecord_.entry = entry;
|
||||
DispatchCodeEvent(evt_rec);
|
||||
}
|
||||
|
||||
void ProfilerListener::CodeSweepEvent() { weak_code_registry_.Sweep(this); }
|
||||
|
||||
const char* ProfilerListener::GetName(Vector<const char> name) {
|
||||
// TODO(all): Change {StringsStorage} to accept non-null-terminated strings.
|
||||
OwnedVector<char> null_terminated = OwnedVector<char>::New(name.size() + 1);
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "include/v8-profiler.h"
|
||||
#include "src/logging/code-events.h"
|
||||
#include "src/profiler/profile-generator.h"
|
||||
#include "src/profiler/weak-code-registry.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
@ -24,10 +25,12 @@ class CodeEventObserver {
|
||||
virtual ~CodeEventObserver() = default;
|
||||
};
|
||||
|
||||
class V8_EXPORT_PRIVATE ProfilerListener : public CodeEventListener {
|
||||
class V8_EXPORT_PRIVATE ProfilerListener : public CodeEventListener,
|
||||
public WeakCodeRegistry::Listener {
|
||||
public:
|
||||
ProfilerListener(Isolate*, CodeEventObserver*,
|
||||
StringsStorage& function_and_resource_names,
|
||||
WeakCodeRegistry& weak_code_registry,
|
||||
CpuProfilingNamingMode mode = kDebugNaming);
|
||||
~ProfilerListener() override;
|
||||
ProfilerListener(const ProfilerListener&) = delete;
|
||||
@ -64,7 +67,12 @@ class V8_EXPORT_PRIVATE ProfilerListener : public CodeEventListener {
|
||||
void CodeDependencyChangeEvent(Handle<Code> code,
|
||||
Handle<SharedFunctionInfo> sfi,
|
||||
const char* reason) override {}
|
||||
void BytecodeFlushEvent(Address compiled_data_start) override;
|
||||
void WeakCodeClearEvent() override;
|
||||
|
||||
void OnHeapObjectDeletion(CodeEntry*) override;
|
||||
|
||||
// Invoked after a mark-sweep cycle.
|
||||
void CodeSweepEvent();
|
||||
|
||||
const char* GetName(Name name) {
|
||||
return function_and_resource_names_.GetName(name);
|
||||
@ -94,6 +102,7 @@ class V8_EXPORT_PRIVATE ProfilerListener : public CodeEventListener {
|
||||
Isolate* isolate_;
|
||||
CodeEventObserver* observer_;
|
||||
StringsStorage& function_and_resource_names_;
|
||||
WeakCodeRegistry& weak_code_registry_;
|
||||
const CpuProfilingNamingMode naming_mode_;
|
||||
};
|
||||
|
||||
|
62
src/profiler/weak-code-registry.cc
Normal file
62
src/profiler/weak-code-registry.cc
Normal file
@ -0,0 +1,62 @@
|
||||
// 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/profiler/weak-code-registry.h"
|
||||
|
||||
#include "src/handles/global-handles.h"
|
||||
#include "src/objects/instance-type-inl.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
namespace {
|
||||
|
||||
void Untrack(CodeEntry* entry) {
|
||||
if (Address** heap_object_location_address =
|
||||
entry->heap_object_location_address()) {
|
||||
GlobalHandles::Destroy(*heap_object_location_address);
|
||||
*heap_object_location_address = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void WeakCodeRegistry::Track(CodeEntry* entry, Handle<AbstractCode> code) {
|
||||
DCHECK(!*entry->heap_object_location_address());
|
||||
DisallowGarbageCollection no_gc;
|
||||
Handle<AbstractCode> handle = isolate_->global_handles()->Create(*code);
|
||||
|
||||
Address** heap_object_location_address =
|
||||
entry->heap_object_location_address();
|
||||
*heap_object_location_address = handle.location();
|
||||
GlobalHandles::MakeWeak(heap_object_location_address);
|
||||
|
||||
entries_.push_back(entry);
|
||||
}
|
||||
|
||||
void WeakCodeRegistry::Sweep(WeakCodeRegistry::Listener* listener) {
|
||||
std::vector<CodeEntry*> alive_entries;
|
||||
for (CodeEntry* entry : entries_) {
|
||||
// Mark the CodeEntry as being deleted on the heap if the heap object
|
||||
// location was nulled, indicating the object was freed.
|
||||
if (!*entry->heap_object_location_address()) {
|
||||
if (listener) {
|
||||
listener->OnHeapObjectDeletion(entry);
|
||||
}
|
||||
} else {
|
||||
alive_entries.push_back(entry);
|
||||
}
|
||||
}
|
||||
entries_ = std::move(alive_entries);
|
||||
}
|
||||
|
||||
void WeakCodeRegistry::Clear() {
|
||||
for (CodeEntry* entry : entries_) {
|
||||
Untrack(entry);
|
||||
}
|
||||
entries_.clear();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
46
src/profiler/weak-code-registry.h
Normal file
46
src/profiler/weak-code-registry.h
Normal file
@ -0,0 +1,46 @@
|
||||
// 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_PROFILER_WEAK_CODE_REGISTRY_H_
|
||||
#define V8_PROFILER_WEAK_CODE_REGISTRY_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "src/execution/isolate.h"
|
||||
#include "src/objects/objects.h"
|
||||
#include "src/profiler/profile-generator.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
class V8_EXPORT_PRIVATE WeakCodeRegistry {
|
||||
public:
|
||||
struct Listener {
|
||||
virtual void OnHeapObjectDeletion(CodeEntry* entry) = 0;
|
||||
};
|
||||
|
||||
explicit WeakCodeRegistry(Isolate* isolate) : isolate_(isolate) {}
|
||||
~WeakCodeRegistry() { Clear(); }
|
||||
|
||||
void Track(CodeEntry* entry, Handle<AbstractCode> code);
|
||||
|
||||
// Removes all dead code objects from the registry, invoking the provided
|
||||
// listener for each new CodeEntry that is no longer referenced on the heap
|
||||
// (if set).
|
||||
void Sweep(Listener* listener);
|
||||
|
||||
// Removes all heap object tracking from stored CodeEntries.
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
Isolate* const isolate_;
|
||||
// Invariant: Entries will always be removed here before the CodeMap is
|
||||
// destroyed. CodeEntries should not be freed while their heap objects exist.
|
||||
std::vector<CodeEntry*> entries_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_PROFILER_WEAK_CODE_REGISTRY_H_
|
@ -1265,7 +1265,7 @@ RUNTIME_FUNCTION(Runtime_EnableCodeLoggingForTesting) {
|
||||
void CodeDependencyChangeEvent(Handle<Code> code,
|
||||
Handle<SharedFunctionInfo> shared,
|
||||
const char* reason) final {}
|
||||
void BytecodeFlushEvent(Address compiled_data_start) final {}
|
||||
void WeakCodeClearEvent() final {}
|
||||
|
||||
bool is_listening_to_code_events() final { return true; }
|
||||
};
|
||||
|
@ -178,7 +178,8 @@ TEST(CodeEvents) {
|
||||
v8::base::TimeDelta::FromMicroseconds(100), true);
|
||||
CHECK(processor->Start());
|
||||
ProfilerListener profiler_listener(isolate, processor,
|
||||
*code_observer.strings());
|
||||
*code_observer.strings(),
|
||||
*code_observer.weak_code_registry());
|
||||
isolate->logger()->AddCodeEventListener(&profiler_listener);
|
||||
|
||||
// Enqueue code creation events.
|
||||
@ -243,7 +244,8 @@ TEST(TickEvents) {
|
||||
profiles->StartProfiling("");
|
||||
CHECK(processor->Start());
|
||||
ProfilerListener profiler_listener(isolate, processor,
|
||||
*code_observer->strings());
|
||||
*code_observer->strings(),
|
||||
*code_observer->weak_code_registry());
|
||||
isolate->logger()->AddCodeEventListener(&profiler_listener);
|
||||
|
||||
profiler_listener.CodeCreateEvent(i::Logger::BUILTIN_TAG, frame1_code, "bbb");
|
||||
@ -404,7 +406,8 @@ TEST(Issue1398) {
|
||||
profiles->StartProfiling("");
|
||||
CHECK(processor->Start());
|
||||
ProfilerListener profiler_listener(isolate, processor,
|
||||
*code_observer->strings());
|
||||
*code_observer->strings(),
|
||||
*code_observer->weak_code_registry());
|
||||
|
||||
profiler_listener.CodeCreateEvent(i::Logger::BUILTIN_TAG, code, "bbb");
|
||||
|
||||
@ -1272,7 +1275,8 @@ static void TickLines(bool optimize) {
|
||||
isolate->logger()->LogCompiledFunctions();
|
||||
CHECK(processor->Start());
|
||||
ProfilerListener profiler_listener(isolate, processor,
|
||||
*code_observer->strings());
|
||||
*code_observer->strings(),
|
||||
*code_observer->weak_code_registry());
|
||||
|
||||
// Enqueue code creation events.
|
||||
i::Handle<i::String> str = factory->NewStringFromAsciiChecked(func_name);
|
||||
@ -4155,6 +4159,38 @@ TEST(BytecodeFlushEventsEagerLogging) {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that unused code entries are removed after GC with eager logging.
|
||||
TEST(ClearUnusedWithEagerLogging) {
|
||||
ManualGCScope manual_gc;
|
||||
TestSetup test_setup;
|
||||
LocalContext env;
|
||||
i::Isolate* isolate = CcTest::i_isolate();
|
||||
i::HandleScope scope(isolate);
|
||||
|
||||
CpuProfiler profiler(isolate, kDebugNaming, kEagerLogging);
|
||||
|
||||
CodeMap* code_map = profiler.code_map_for_test();
|
||||
size_t initial_size = code_map->size();
|
||||
|
||||
{
|
||||
// Create and run a new script and function, generating 2 code objects.
|
||||
i::HandleScope inner_scope(isolate);
|
||||
CompileRun(
|
||||
"function some_func() {}"
|
||||
"some_func();");
|
||||
CHECK_GT(code_map->size(), initial_size);
|
||||
}
|
||||
|
||||
// Perform a few GCs, ensuring that the executed code's bytecode is flushed.
|
||||
const int kAgingThreshold = 8;
|
||||
for (int i = 0; i < kAgingThreshold; i++) {
|
||||
CcTest::CollectAllGarbage();
|
||||
}
|
||||
|
||||
// Verify that the CodeMap's size is unchanged post-GC.
|
||||
CHECK_EQ(code_map->size(), initial_size);
|
||||
}
|
||||
|
||||
} // namespace test_cpu_profiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -1202,103 +1202,3 @@ UNINITIALIZED_TEST(BuiltinsNotLoggedAsLazyCompile) {
|
||||
}
|
||||
isolate->Dispose();
|
||||
}
|
||||
|
||||
TEST(BytecodeFlushEvents) {
|
||||
SETUP_FLAGS();
|
||||
|
||||
#ifndef V8_LITE_MODE
|
||||
i::FLAG_opt = false;
|
||||
i::FLAG_always_opt = false;
|
||||
i::FLAG_optimize_for_size = false;
|
||||
#endif // V8_LITE_MODE
|
||||
i::FLAG_flush_bytecode = true;
|
||||
i::FLAG_allow_natives_syntax = true;
|
||||
|
||||
ManualGCScope manual_gc_scope;
|
||||
|
||||
v8::Isolate* isolate = CcTest::isolate();
|
||||
i::Isolate* i_isolate = CcTest::i_isolate();
|
||||
i::Factory* factory = i_isolate->factory();
|
||||
|
||||
struct FakeCodeEventLogger : public i::CodeEventLogger {
|
||||
explicit FakeCodeEventLogger(i::Isolate* isolate)
|
||||
: CodeEventLogger(isolate) {}
|
||||
|
||||
void CodeMoveEvent(i::AbstractCode from, i::AbstractCode to) override {}
|
||||
void CodeDisableOptEvent(i::Handle<i::AbstractCode> code,
|
||||
i::Handle<i::SharedFunctionInfo> shared) override {
|
||||
}
|
||||
|
||||
void BytecodeFlushEvent(Address compiled_data_start) override {
|
||||
// We only expect a single flush.
|
||||
CHECK_EQ(flushed_compiled_data_start, i::kNullAddress);
|
||||
flushed_compiled_data_start = compiled_data_start;
|
||||
}
|
||||
|
||||
void LogRecordedBuffer(i::Handle<i::AbstractCode> code,
|
||||
i::MaybeHandle<i::SharedFunctionInfo> maybe_shared,
|
||||
const char* name, int length) override {}
|
||||
#if V8_ENABLE_WEBASSEMBLY
|
||||
void LogRecordedBuffer(const i::wasm::WasmCode* code, const char* name,
|
||||
int length) override {}
|
||||
#endif // V8_ENABLE_WEBASSEMBLY
|
||||
|
||||
i::Address flushed_compiled_data_start = i::kNullAddress;
|
||||
};
|
||||
|
||||
FakeCodeEventLogger code_event_logger(i_isolate);
|
||||
|
||||
{
|
||||
ScopedLoggerInitializer logger(isolate);
|
||||
logger.logger()->AddCodeEventListener(&code_event_logger);
|
||||
|
||||
const char* source =
|
||||
"function foo() {"
|
||||
" var x = 42;"
|
||||
" var y = 42;"
|
||||
" var z = x + y;"
|
||||
"};"
|
||||
"foo()";
|
||||
i::Handle<i::String> foo_name = factory->InternalizeUtf8String("foo");
|
||||
|
||||
// This compile will add the code to the compilation cache.
|
||||
{
|
||||
v8::HandleScope scope(isolate);
|
||||
CompileRun(source);
|
||||
}
|
||||
|
||||
// Check function is compiled.
|
||||
i::Handle<i::Object> func_value =
|
||||
i::Object::GetProperty(i_isolate, i_isolate->global_object(), foo_name)
|
||||
.ToHandleChecked();
|
||||
CHECK(func_value->IsJSFunction());
|
||||
i::Handle<i::JSFunction> function =
|
||||
i::Handle<i::JSFunction>::cast(func_value);
|
||||
CHECK(function->shared().is_compiled());
|
||||
|
||||
// The code will survive at least two GCs.
|
||||
CcTest::CollectAllGarbage();
|
||||
CcTest::CollectAllGarbage();
|
||||
CHECK(function->shared().is_compiled());
|
||||
CHECK_EQ(code_event_logger.flushed_compiled_data_start, i::kNullAddress);
|
||||
|
||||
// Get the start address of the compiled data before flushing.
|
||||
i::HeapObject compiled_data =
|
||||
function->shared().GetBytecodeArray(i_isolate);
|
||||
i::Address compiled_data_start = compiled_data.address();
|
||||
|
||||
// Simulate several GCs that use full marking.
|
||||
const int kAgingThreshold = 6;
|
||||
for (int i = 0; i < kAgingThreshold; i++) {
|
||||
CcTest::CollectAllGarbage();
|
||||
}
|
||||
|
||||
// foo should no longer be in the compilation cache
|
||||
CHECK(!function->shared().is_compiled());
|
||||
CHECK(!function->is_compiled());
|
||||
|
||||
// Verify that foo() was in fact flushed.
|
||||
CHECK_EQ(code_event_logger.flushed_compiled_data_start,
|
||||
compiled_data_start);
|
||||
}
|
||||
}
|
||||
|
@ -352,10 +352,6 @@ TEST(CodeMapMoveAndDeleteCode) {
|
||||
code_map.MoveCode(ToAddress(0x1500), ToAddress(0x1700)); // Deprecate bbb.
|
||||
CHECK(!code_map.FindEntry(ToAddress(0x1500)));
|
||||
CHECK_EQ(entry1, code_map.FindEntry(ToAddress(0x1700)));
|
||||
CodeEntry* entry3 = new CodeEntry(i::CodeEventListener::FUNCTION_TAG, "ccc");
|
||||
code_map.AddCode(ToAddress(0x1750), entry3, 0x100);
|
||||
CHECK(!code_map.FindEntry(ToAddress(0x1700)));
|
||||
CHECK_EQ(entry3, code_map.FindEntry(ToAddress(0x1750)));
|
||||
}
|
||||
|
||||
TEST(CodeMapClear) {
|
||||
@ -962,6 +958,63 @@ TEST(NodeSourceTypes) {
|
||||
CHECK_EQ(unresolved_node->source_type(), v8::CpuProfileNode::kUnresolved);
|
||||
}
|
||||
|
||||
TEST(CodeMapRemoveCode) {
|
||||
StringsStorage strings;
|
||||
CodeMap code_map(strings);
|
||||
|
||||
CodeEntry* entry = new CodeEntry(i::CodeEventListener::FUNCTION_TAG, "aaa");
|
||||
code_map.AddCode(ToAddress(0x1000), entry, 0x100);
|
||||
CHECK(code_map.RemoveCode(entry));
|
||||
CHECK(!code_map.FindEntry(ToAddress(0x1000)));
|
||||
|
||||
// Test that when two entries share the same address, we remove only the
|
||||
// entry that we desired to.
|
||||
CodeEntry* colliding_entry1 =
|
||||
new CodeEntry(i::CodeEventListener::FUNCTION_TAG, "aaa");
|
||||
CodeEntry* colliding_entry2 =
|
||||
new CodeEntry(i::CodeEventListener::FUNCTION_TAG, "aaa");
|
||||
code_map.AddCode(ToAddress(0x1000), colliding_entry1, 0x100);
|
||||
code_map.AddCode(ToAddress(0x1000), colliding_entry2, 0x100);
|
||||
|
||||
CHECK(code_map.RemoveCode(colliding_entry1));
|
||||
CHECK_EQ(code_map.FindEntry(ToAddress(0x1000)), colliding_entry2);
|
||||
|
||||
CHECK(code_map.RemoveCode(colliding_entry2));
|
||||
CHECK(!code_map.FindEntry(ToAddress(0x1000)));
|
||||
}
|
||||
|
||||
TEST(CodeMapMoveOverlappingCode) {
|
||||
StringsStorage strings;
|
||||
CodeMap code_map(strings);
|
||||
CodeEntry* colliding_entry1 =
|
||||
new CodeEntry(i::CodeEventListener::FUNCTION_TAG, "aaa");
|
||||
CodeEntry* colliding_entry2 =
|
||||
new CodeEntry(i::CodeEventListener::FUNCTION_TAG, "bbb");
|
||||
CodeEntry* after_entry =
|
||||
new CodeEntry(i::CodeEventListener::FUNCTION_TAG, "ccc");
|
||||
|
||||
code_map.AddCode(ToAddress(0x1400), colliding_entry1, 0x200);
|
||||
code_map.AddCode(ToAddress(0x1400), colliding_entry2, 0x200);
|
||||
code_map.AddCode(ToAddress(0x1800), after_entry, 0x200);
|
||||
|
||||
CHECK_EQ(colliding_entry1->instruction_start(), ToAddress(0x1400));
|
||||
CHECK_EQ(colliding_entry2->instruction_start(), ToAddress(0x1400));
|
||||
CHECK_EQ(after_entry->instruction_start(), ToAddress(0x1800));
|
||||
|
||||
CHECK(code_map.FindEntry(ToAddress(0x1400)));
|
||||
CHECK_EQ(code_map.FindEntry(ToAddress(0x1800)), after_entry);
|
||||
|
||||
code_map.MoveCode(ToAddress(0x1400), ToAddress(0x1600));
|
||||
|
||||
CHECK(!code_map.FindEntry(ToAddress(0x1400)));
|
||||
CHECK(code_map.FindEntry(ToAddress(0x1600)));
|
||||
CHECK_EQ(code_map.FindEntry(ToAddress(0x1800)), after_entry);
|
||||
|
||||
CHECK_EQ(colliding_entry1->instruction_start(), ToAddress(0x1600));
|
||||
CHECK_EQ(colliding_entry2->instruction_start(), ToAddress(0x1600));
|
||||
CHECK_EQ(after_entry->instruction_start(), ToAddress(0x1800));
|
||||
}
|
||||
|
||||
} // namespace test_profile_generator
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
Loading…
Reference in New Issue
Block a user