a6938128f4
This call can be used by embedder to request a GC for testing reasons. The GC also takes the current embedder stack state as an argument that is forwarded to the embedder when entering the atomic pause. This way embedders can request garbage collections for testing and set how the embedder should treat the stack. Bug: chromium:843903 Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng Change-Id: Id10604565b4457dd0fca402afeb5f8e592fa0bae Reviewed-on: https://chromium-review.googlesource.com/1183431 Commit-Queue: Michael Lippautz <mlippautz@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Cr-Commit-Position: refs/heads/master@{#55285}
270 lines
9.3 KiB
C++
270 lines
9.3 KiB
C++
// Copyright 2017 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 "include/v8.h"
|
|
#include "src/api-inl.h"
|
|
#include "src/objects-inl.h"
|
|
#include "src/objects/module.h"
|
|
#include "src/objects/script.h"
|
|
#include "src/objects/shared-function-info.h"
|
|
#include "test/cctest/cctest.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
namespace heap {
|
|
|
|
namespace {
|
|
|
|
v8::Local<v8::Object> ConstructTraceableJSApiObject(
|
|
v8::Local<v8::Context> context, void* first_field, void* second_field) {
|
|
v8::EscapableHandleScope scope(context->GetIsolate());
|
|
v8::Local<v8::FunctionTemplate> function_t =
|
|
v8::FunctionTemplate::New(context->GetIsolate());
|
|
v8::Local<v8::ObjectTemplate> instance_t = function_t->InstanceTemplate();
|
|
instance_t->SetInternalFieldCount(2);
|
|
v8::Local<v8::Function> function =
|
|
function_t->GetFunction(context).ToLocalChecked();
|
|
v8::Local<v8::Object> instance =
|
|
function->NewInstance(context).ToLocalChecked();
|
|
instance->SetAlignedPointerInInternalField(0, first_field);
|
|
instance->SetAlignedPointerInInternalField(1, second_field);
|
|
CHECK(!instance.IsEmpty());
|
|
i::Handle<i::JSReceiver> js_obj = v8::Utils::OpenHandle(*instance);
|
|
CHECK_EQ(i::JS_API_OBJECT_TYPE, js_obj->map()->instance_type());
|
|
return scope.Escape(instance);
|
|
}
|
|
|
|
class TestEmbedderHeapTracer final : public v8::EmbedderHeapTracer {
|
|
public:
|
|
explicit TestEmbedderHeapTracer(v8::Isolate* isolate) : isolate_(isolate) {}
|
|
|
|
void RegisterV8References(
|
|
const std::vector<std::pair<void*, void*>>& embedder_fields) final {
|
|
registered_from_v8_.insert(registered_from_v8_.end(),
|
|
embedder_fields.begin(), embedder_fields.end());
|
|
}
|
|
|
|
void AddReferenceForTracing(v8::Persistent<v8::Object>* persistent) {
|
|
to_register_with_v8_.push_back(persistent);
|
|
}
|
|
|
|
bool AdvanceTracing(double deadline_in_ms,
|
|
AdvanceTracingActions actions) final {
|
|
for (auto persistent : to_register_with_v8_) {
|
|
persistent->RegisterExternalReference(isolate_);
|
|
}
|
|
to_register_with_v8_.clear();
|
|
return false;
|
|
}
|
|
|
|
void TracePrologue() final {}
|
|
void TraceEpilogue() final {}
|
|
void AbortTracing() final {}
|
|
void EnterFinalPause(EmbedderStackState) final {}
|
|
|
|
bool IsRegisteredFromV8(void* first_field) const {
|
|
for (auto pair : registered_from_v8_) {
|
|
if (pair.first == first_field) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
v8::Isolate* const isolate_;
|
|
std::vector<std::pair<void*, void*>> registered_from_v8_;
|
|
std::vector<v8::Persistent<v8::Object>*> to_register_with_v8_;
|
|
};
|
|
|
|
class TemporaryEmbedderHeapTracerScope {
|
|
public:
|
|
TemporaryEmbedderHeapTracerScope(v8::Isolate* isolate,
|
|
EmbedderHeapTracer* tracer)
|
|
: isolate_(isolate) {
|
|
isolate_->SetEmbedderHeapTracer(tracer);
|
|
}
|
|
|
|
~TemporaryEmbedderHeapTracerScope() {
|
|
isolate_->SetEmbedderHeapTracer(nullptr);
|
|
}
|
|
|
|
private:
|
|
v8::Isolate* const isolate_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(V8RegisteringEmbedderReference) {
|
|
// Tests that wrappers are properly registered with the embedder heap
|
|
// tracer.
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
void* first_field = reinterpret_cast<void*>(0x2);
|
|
v8::Local<v8::Object> api_object =
|
|
ConstructTraceableJSApiObject(context, first_field, nullptr);
|
|
CHECK(!api_object.IsEmpty());
|
|
CcTest::CollectGarbage(i::OLD_SPACE);
|
|
CHECK(tracer.IsRegisteredFromV8(first_field));
|
|
}
|
|
|
|
TEST(EmbedderRegisteringV8Reference) {
|
|
// Tests that references that are registered by the embedder heap tracer are
|
|
// considered live by V8.
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
v8::Persistent<v8::Object> g;
|
|
{
|
|
v8::HandleScope inner_scope(isolate);
|
|
v8::Local<v8::Object> o =
|
|
v8::Local<v8::Object>::New(isolate, v8::Object::New(isolate));
|
|
g.Reset(isolate, o);
|
|
g.SetWeak();
|
|
}
|
|
tracer.AddReferenceForTracing(&g);
|
|
CcTest::CollectGarbage(i::OLD_SPACE);
|
|
CHECK(!g.IsEmpty());
|
|
}
|
|
|
|
namespace {
|
|
|
|
void ResurrectingFinalizer(
|
|
const v8::WeakCallbackInfo<v8::Global<v8::Object>>& data) {
|
|
data.GetParameter()->ClearWeak();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(TracingInRevivedSubgraph) {
|
|
// Tests that wrappers are traced when they are contained with in a subgraph
|
|
// that is revived by a finalizer.
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
v8::Global<v8::Object> g;
|
|
void* first_field = reinterpret_cast<void*>(0x4);
|
|
{
|
|
v8::HandleScope inner_scope(isolate);
|
|
v8::Local<v8::Object> api_object =
|
|
ConstructTraceableJSApiObject(context, first_field, nullptr);
|
|
CHECK(!api_object.IsEmpty());
|
|
v8::Local<v8::Object> o =
|
|
v8::Local<v8::Object>::New(isolate, v8::Object::New(isolate));
|
|
o->Set(context, v8_str("link"), api_object).FromJust();
|
|
g.Reset(isolate, o);
|
|
g.SetWeak(&g, ResurrectingFinalizer, v8::WeakCallbackType::kFinalizer);
|
|
}
|
|
CcTest::CollectGarbage(i::OLD_SPACE);
|
|
CHECK(tracer.IsRegisteredFromV8(first_field));
|
|
}
|
|
|
|
TEST(TracingInEphemerons) {
|
|
// Tests that wrappers that are part of ephemerons are traced.
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
|
v8::Context::Scope context_scope(context);
|
|
|
|
v8::Local<v8::Object> key =
|
|
v8::Local<v8::Object>::New(isolate, v8::Object::New(isolate));
|
|
void* first_field = reinterpret_cast<void*>(0x8);
|
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
|
Handle<JSWeakMap> weak_map = i_isolate->factory()->NewJSWeakMap();
|
|
{
|
|
v8::HandleScope inner_scope(isolate);
|
|
v8::Local<v8::Object> api_object =
|
|
ConstructTraceableJSApiObject(context, first_field, nullptr);
|
|
CHECK(!api_object.IsEmpty());
|
|
Handle<JSObject> js_key =
|
|
handle(JSObject::cast(*v8::Utils::OpenHandle(*key)), i_isolate);
|
|
Handle<JSReceiver> js_api_object = v8::Utils::OpenHandle(*api_object);
|
|
int32_t hash = js_key->GetOrCreateHash(i_isolate)->value();
|
|
JSWeakCollection::Set(weak_map, js_key, js_api_object, hash);
|
|
}
|
|
CcTest::CollectGarbage(i::OLD_SPACE);
|
|
CHECK(tracer.IsRegisteredFromV8(first_field));
|
|
}
|
|
|
|
TEST(FinalizeTracingIsNoopWhenNotMarking) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
Isolate* i_isolate = CcTest::i_isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
// Finalize a potentially running garbage collection.
|
|
i_isolate->heap()->CollectGarbage(OLD_SPACE,
|
|
GarbageCollectionReason::kTesting);
|
|
CHECK(i_isolate->heap()->incremental_marking()->IsStopped());
|
|
|
|
int gc_counter = i_isolate->heap()->gc_count();
|
|
tracer.FinalizeTracing();
|
|
CHECK(i_isolate->heap()->incremental_marking()->IsStopped());
|
|
CHECK_EQ(gc_counter, i_isolate->heap()->gc_count());
|
|
}
|
|
|
|
TEST(FinalizeTracingWhenMarking) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
Isolate* i_isolate = CcTest::i_isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
// Finalize a potentially running garbage collection.
|
|
i_isolate->heap()->CollectGarbage(OLD_SPACE,
|
|
GarbageCollectionReason::kTesting);
|
|
if (i_isolate->heap()->mark_compact_collector()->sweeping_in_progress()) {
|
|
i_isolate->heap()->mark_compact_collector()->EnsureSweepingCompleted();
|
|
}
|
|
CHECK(i_isolate->heap()->incremental_marking()->IsStopped());
|
|
|
|
i::IncrementalMarking* marking = i_isolate->heap()->incremental_marking();
|
|
marking->Start(i::GarbageCollectionReason::kTesting);
|
|
// Sweeping is not runing so we should immediately start marking.
|
|
CHECK(marking->IsMarking());
|
|
tracer.FinalizeTracing();
|
|
CHECK(marking->IsStopped());
|
|
}
|
|
|
|
TEST(GarbageCollectionForTesting) {
|
|
ManualGCScope manual_gc;
|
|
CcTest::InitializeVM();
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
Isolate* i_isolate = CcTest::i_isolate();
|
|
TestEmbedderHeapTracer tracer(isolate);
|
|
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
|
|
|
|
int saved_gc_counter = i_isolate->heap()->gc_count();
|
|
tracer.GarbageCollectionForTesting(EmbedderHeapTracer::kUnknown);
|
|
CHECK_GT(i_isolate->heap()->gc_count(), saved_gc_counter);
|
|
}
|
|
|
|
} // namespace heap
|
|
} // namespace internal
|
|
} // namespace v8
|