[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:
Andrew Comminos 2021-03-19 12:18:18 -07:00 committed by Commit Bot
parent 7a7e48a152
commit 34c37396f0
17 changed files with 322 additions and 148 deletions

View File

@ -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",

View File

@ -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() {

View File

@ -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:

View File

@ -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();

View File

@ -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() {

View File

@ -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()));

View File

@ -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_;
};

View File

@ -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) {

View File

@ -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_;

View File

@ -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);

View File

@ -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_;
};

View 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

View 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_

View File

@ -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; }
};

View File

@ -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

View File

@ -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);
}
}

View File

@ -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