b5bf34bce7
LocalHeap can be used on main thread, however allocation might cause a GC which works differently on the main thread than on a background thread. Support collection on main thread by directly performing the GC instead of requesting the GC as done on background threads. To allow for differentiation between main and background threads, LocalHeap/LocalIsolate now require an additional argument. Change-Id: I08094ea633e303e149913f21dff395da9e046534 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2463238 Reviewed-by: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Georg Neis <neis@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Commit-Queue: Dominik Inführ <dinfuehr@chromium.org> Cr-Commit-Position: refs/heads/master@{#70590}
442 lines
17 KiB
C++
442 lines
17 KiB
C++
// Copyright 2020 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/api/api.h"
|
|
#include "src/base/platform/semaphore.h"
|
|
#include "src/handles/handles-inl.h"
|
|
#include "src/handles/persistent-handles.h"
|
|
#include "src/heap/heap.h"
|
|
#include "src/heap/local-heap.h"
|
|
#include "src/objects/transitions-inl.h"
|
|
#include "test/cctest/cctest.h"
|
|
#include "test/cctest/heap/heap-utils.h"
|
|
#include "test/cctest/test-transitions.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
|
|
namespace {
|
|
|
|
// Background search thread class
|
|
class ConcurrentSearchThread : public v8::base::Thread {
|
|
public:
|
|
ConcurrentSearchThread(Heap* heap, base::Semaphore* background_thread_started,
|
|
std::unique_ptr<PersistentHandles> ph,
|
|
Handle<Name> name, Handle<Map> map,
|
|
base::Optional<Handle<Map>> result_map)
|
|
: v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")),
|
|
heap_(heap),
|
|
background_thread_started_(background_thread_started),
|
|
ph_(std::move(ph)),
|
|
name_(name),
|
|
map_(map),
|
|
result_map_(result_map) {}
|
|
|
|
void Run() override {
|
|
LocalHeap local_heap(heap_, ThreadKind::kBackground, std::move(ph_));
|
|
UnparkedScope scope(&local_heap);
|
|
|
|
background_thread_started_->Signal();
|
|
|
|
CHECK_EQ(TransitionsAccessor(CcTest::i_isolate(), map_, true)
|
|
.SearchTransition(*name_, kData, NONE),
|
|
result_map_ ? **result_map_ : Map());
|
|
}
|
|
|
|
// protected instead of private due to having a subclass.
|
|
protected:
|
|
Heap* heap_;
|
|
base::Semaphore* background_thread_started_;
|
|
std::unique_ptr<PersistentHandles> ph_;
|
|
Handle<Name> name_;
|
|
Handle<Map> map_;
|
|
base::Optional<Handle<Map>> result_map_;
|
|
};
|
|
|
|
// Background search thread class that creates the transitions accessor before
|
|
// the main thread modifies the TransitionArray, and searches the transition
|
|
// only after the main thread finished.
|
|
class ConcurrentSearchOnOutdatedAccessorThread final
|
|
: public ConcurrentSearchThread {
|
|
public:
|
|
ConcurrentSearchOnOutdatedAccessorThread(
|
|
Heap* heap, base::Semaphore* background_thread_started,
|
|
base::Semaphore* main_thread_finished,
|
|
std::unique_ptr<PersistentHandles> ph, Handle<Name> name, Handle<Map> map,
|
|
Handle<Map> result_map)
|
|
: ConcurrentSearchThread(heap, background_thread_started, std::move(ph),
|
|
name, map, result_map),
|
|
main_thread_finished_(main_thread_finished) {}
|
|
|
|
void Run() override {
|
|
LocalHeap local_heap(heap_, ThreadKind::kBackground, std::move(ph_));
|
|
UnparkedScope scope(&local_heap);
|
|
|
|
TransitionsAccessor accessor(CcTest::i_isolate(), map_, true);
|
|
background_thread_started_->Signal();
|
|
main_thread_finished_->Wait();
|
|
|
|
CHECK_EQ(accessor.SearchTransition(*name_, kData, NONE),
|
|
result_map_ ? **result_map_ : Map());
|
|
}
|
|
|
|
base::Semaphore* main_thread_finished_;
|
|
};
|
|
|
|
// Search on the main thread and in the background thread at the same time.
|
|
TEST(FullFieldTransitions_OnlySearch) {
|
|
CcTest::InitializeVM();
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
|
|
Handle<String> name = CcTest::MakeString("name");
|
|
const PropertyAttributes attributes = NONE;
|
|
const PropertyKind kind = kData;
|
|
|
|
// Set map0 to be a full transition array with transition 'name' to map1.
|
|
Handle<Map> map0 = Map::Create(isolate, 0);
|
|
Handle<Map> map1 =
|
|
Map::CopyWithField(isolate, map0, name, FieldType::Any(isolate),
|
|
attributes, PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
TransitionsAccessor(isolate, map0).Insert(name, map1, PROPERTY_TRANSITION);
|
|
{
|
|
TestTransitionsAccessor transitions(isolate, map0);
|
|
CHECK(transitions.IsFullTransitionArrayEncoding());
|
|
}
|
|
|
|
std::unique_ptr<PersistentHandles> ph = isolate->NewPersistentHandles();
|
|
|
|
Handle<Name> persistent_name = ph->NewHandle(name);
|
|
Handle<Map> persistent_map0 = ph->NewHandle(map0);
|
|
Handle<Map> persistent_result_map1 = ph->NewHandle(map1);
|
|
|
|
base::Semaphore background_thread_started(0);
|
|
|
|
// Pass persistent handles to background thread.
|
|
std::unique_ptr<ConcurrentSearchThread> thread(new ConcurrentSearchThread(
|
|
isolate->heap(), &background_thread_started, std::move(ph),
|
|
persistent_name, persistent_map0, persistent_result_map1));
|
|
CHECK(thread->Start());
|
|
|
|
background_thread_started.Wait();
|
|
|
|
CHECK_EQ(*map1, TransitionsAccessor(isolate, map0)
|
|
.SearchTransition(*name, kind, attributes));
|
|
|
|
thread->Join();
|
|
}
|
|
|
|
// Search and insert on the main thread, while the background thread searches at
|
|
// the same time.
|
|
TEST(FullFieldTransitions) {
|
|
CcTest::InitializeVM();
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
|
|
Handle<String> name1 = CcTest::MakeString("name1");
|
|
Handle<String> name2 = CcTest::MakeString("name2");
|
|
const PropertyAttributes attributes = NONE;
|
|
const PropertyKind kind = kData;
|
|
|
|
// Set map0 to be a full transition array with transition 'name1' to map1.
|
|
Handle<Map> map0 = Map::Create(isolate, 0);
|
|
Handle<Map> map1 =
|
|
Map::CopyWithField(isolate, map0, name1, FieldType::Any(isolate),
|
|
attributes, PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
Handle<Map> map2 =
|
|
Map::CopyWithField(isolate, map0, name2, FieldType::Any(isolate),
|
|
attributes, PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
TransitionsAccessor(isolate, map0).Insert(name1, map1, PROPERTY_TRANSITION);
|
|
{
|
|
TestTransitionsAccessor transitions(isolate, map0);
|
|
CHECK(transitions.IsFullTransitionArrayEncoding());
|
|
}
|
|
|
|
std::unique_ptr<PersistentHandles> ph = isolate->NewPersistentHandles();
|
|
|
|
Handle<Name> persistent_name1 = ph->NewHandle(name1);
|
|
Handle<Map> persistent_map0 = ph->NewHandle(map0);
|
|
Handle<Map> persistent_result_map1 = ph->NewHandle(map1);
|
|
|
|
base::Semaphore background_thread_started(0);
|
|
|
|
// Pass persistent handles to background thread.
|
|
std::unique_ptr<ConcurrentSearchThread> thread(new ConcurrentSearchThread(
|
|
isolate->heap(), &background_thread_started, std::move(ph),
|
|
persistent_name1, persistent_map0, persistent_result_map1));
|
|
CHECK(thread->Start());
|
|
|
|
background_thread_started.Wait();
|
|
|
|
CHECK_EQ(*map1, TransitionsAccessor(isolate, map0)
|
|
.SearchTransition(*name1, kind, attributes));
|
|
TransitionsAccessor(isolate, map0).Insert(name2, map2, PROPERTY_TRANSITION);
|
|
CHECK_EQ(*map2, TransitionsAccessor(isolate, map0)
|
|
.SearchTransition(*name2, kind, attributes));
|
|
|
|
thread->Join();
|
|
}
|
|
|
|
// Search and insert on the main thread which changes the encoding from kWeakRef
|
|
// to kFullTransitionArray, while the background thread searches at the same
|
|
// time.
|
|
TEST(WeakRefToFullFieldTransitions) {
|
|
CcTest::InitializeVM();
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
|
|
Handle<String> name1 = CcTest::MakeString("name1");
|
|
Handle<String> name2 = CcTest::MakeString("name2");
|
|
const PropertyAttributes attributes = NONE;
|
|
const PropertyKind kind = kData;
|
|
|
|
// Set map0 to be a simple transition array with transition 'name1' to map1.
|
|
Handle<Map> map0 = Map::Create(isolate, 0);
|
|
Handle<Map> map1 =
|
|
Map::CopyWithField(isolate, map0, name1, FieldType::Any(isolate),
|
|
attributes, PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
Handle<Map> map2 =
|
|
Map::CopyWithField(isolate, map0, name2, FieldType::Any(isolate),
|
|
attributes, PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
TransitionsAccessor(isolate, map0)
|
|
.Insert(name1, map1, SIMPLE_PROPERTY_TRANSITION);
|
|
{
|
|
TestTransitionsAccessor transitions(isolate, map0);
|
|
CHECK(transitions.IsWeakRefEncoding());
|
|
}
|
|
|
|
std::unique_ptr<PersistentHandles> ph = isolate->NewPersistentHandles();
|
|
|
|
Handle<Name> persistent_name1 = ph->NewHandle(name1);
|
|
Handle<Map> persistent_map0 = ph->NewHandle(map0);
|
|
Handle<Map> persistent_result_map1 = ph->NewHandle(map1);
|
|
|
|
base::Semaphore background_thread_started(0);
|
|
|
|
// Pass persistent handles to background thread.
|
|
std::unique_ptr<ConcurrentSearchThread> thread(new ConcurrentSearchThread(
|
|
isolate->heap(), &background_thread_started, std::move(ph),
|
|
persistent_name1, persistent_map0, persistent_result_map1));
|
|
CHECK(thread->Start());
|
|
|
|
background_thread_started.Wait();
|
|
|
|
CHECK_EQ(*map1, TransitionsAccessor(isolate, map0)
|
|
.SearchTransition(*name1, kind, attributes));
|
|
TransitionsAccessor(isolate, map0)
|
|
.Insert(name2, map2, SIMPLE_PROPERTY_TRANSITION);
|
|
{
|
|
TestTransitionsAccessor transitions(isolate, map0);
|
|
CHECK(transitions.IsFullTransitionArrayEncoding());
|
|
}
|
|
CHECK_EQ(*map2, TransitionsAccessor(isolate, map0)
|
|
.SearchTransition(*name2, kind, attributes));
|
|
|
|
thread->Join();
|
|
}
|
|
|
|
// Search and insert on the main thread, while the background thread searches at
|
|
// the same time. In this case, we have a kFullTransitionArray with enough slack
|
|
// when we are concurrently writing.
|
|
TEST(FullFieldTransitions_withSlack) {
|
|
CcTest::InitializeVM();
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
|
|
Handle<String> name1 = CcTest::MakeString("name1");
|
|
Handle<String> name2 = CcTest::MakeString("name2");
|
|
Handle<String> name3 = CcTest::MakeString("name3");
|
|
const PropertyAttributes attributes = NONE;
|
|
const PropertyKind kind = kData;
|
|
|
|
// Set map0 to be a full transition array with transition 'name1' to map1.
|
|
Handle<Map> map0 = Map::Create(isolate, 0);
|
|
Handle<Map> map1 =
|
|
Map::CopyWithField(isolate, map0, name1, FieldType::Any(isolate),
|
|
attributes, PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
Handle<Map> map2 =
|
|
Map::CopyWithField(isolate, map0, name2, FieldType::Any(isolate),
|
|
attributes, PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
Handle<Map> map3 =
|
|
Map::CopyWithField(isolate, map0, name3, FieldType::Any(isolate),
|
|
attributes, PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
TransitionsAccessor(isolate, map0).Insert(name1, map1, PROPERTY_TRANSITION);
|
|
TransitionsAccessor(isolate, map0).Insert(name2, map2, PROPERTY_TRANSITION);
|
|
{
|
|
TestTransitionsAccessor transitions(isolate, map0);
|
|
CHECK(transitions.IsFullTransitionArrayEncoding());
|
|
}
|
|
|
|
std::unique_ptr<PersistentHandles> ph = isolate->NewPersistentHandles();
|
|
|
|
Handle<Name> persistent_name1 = ph->NewHandle(name1);
|
|
Handle<Map> persistent_map0 = ph->NewHandle(map0);
|
|
Handle<Map> persistent_result_map1 = ph->NewHandle(map1);
|
|
|
|
base::Semaphore background_thread_started(0);
|
|
|
|
// Pass persistent handles to background thread.
|
|
std::unique_ptr<ConcurrentSearchThread> thread(new ConcurrentSearchThread(
|
|
isolate->heap(), &background_thread_started, std::move(ph),
|
|
persistent_name1, persistent_map0, persistent_result_map1));
|
|
CHECK(thread->Start());
|
|
|
|
background_thread_started.Wait();
|
|
|
|
CHECK_EQ(*map1, TransitionsAccessor(isolate, map0)
|
|
.SearchTransition(*name1, kind, attributes));
|
|
CHECK_EQ(*map2, TransitionsAccessor(isolate, map0)
|
|
.SearchTransition(*name2, kind, attributes));
|
|
{
|
|
// Check that we have enough slack for the 3rd insertion into the
|
|
// TransitionArray.
|
|
TestTransitionsAccessor transitions(isolate, map0);
|
|
CHECK_GE(transitions.Capacity(), 3);
|
|
}
|
|
TransitionsAccessor(isolate, map0).Insert(name3, map3, PROPERTY_TRANSITION);
|
|
CHECK_EQ(*map3, TransitionsAccessor(isolate, map0)
|
|
.SearchTransition(*name3, kind, attributes));
|
|
|
|
thread->Join();
|
|
}
|
|
|
|
// Search and insert on the main thread which changes the encoding from
|
|
// kUninitialized to kFullTransitionArray, while the background thread searches
|
|
// at the same time.
|
|
TEST(UninitializedToFullFieldTransitions) {
|
|
CcTest::InitializeVM();
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
|
|
Handle<String> name1 = CcTest::MakeString("name1");
|
|
Handle<String> name2 = CcTest::MakeString("name2");
|
|
const PropertyAttributes attributes = NONE;
|
|
const PropertyKind kind = kData;
|
|
|
|
// Set map0 to be a full transition array with transition 'name1' to map1.
|
|
Handle<Map> map0 = Map::Create(isolate, 0);
|
|
Handle<Map> map1 =
|
|
Map::CopyWithField(isolate, map0, name1, FieldType::Any(isolate),
|
|
attributes, PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
{
|
|
TestTransitionsAccessor transitions(isolate, map0);
|
|
CHECK(transitions.IsUninitializedEncoding());
|
|
}
|
|
|
|
std::unique_ptr<PersistentHandles> ph = isolate->NewPersistentHandles();
|
|
|
|
Handle<Name> persistent_name2 = ph->NewHandle(name2);
|
|
Handle<Map> persistent_map0 = ph->NewHandle(map0);
|
|
|
|
base::Semaphore background_thread_started(0);
|
|
|
|
// Pass persistent handles to background thread.
|
|
// Background thread will search for name2, guaranteed to *not* be on the map.
|
|
std::unique_ptr<ConcurrentSearchThread> thread(new ConcurrentSearchThread(
|
|
isolate->heap(), &background_thread_started, std::move(ph),
|
|
persistent_name2, persistent_map0, base::nullopt));
|
|
CHECK(thread->Start());
|
|
|
|
background_thread_started.Wait();
|
|
|
|
TransitionsAccessor(isolate, map0).Insert(name1, map1, PROPERTY_TRANSITION);
|
|
CHECK_EQ(*map1, TransitionsAccessor(isolate, map0)
|
|
.SearchTransition(*name1, kind, attributes));
|
|
{
|
|
TestTransitionsAccessor transitions(isolate, map0);
|
|
CHECK(transitions.IsFullTransitionArrayEncoding());
|
|
}
|
|
thread->Join();
|
|
}
|
|
|
|
// In this test the background search will hold a pointer to an old transition
|
|
// array with no slack, while the main thread will try to insert a value into
|
|
// it. This makes it so that the main thread will create a new array, and the
|
|
// background thread will have a pointer to the old one.
|
|
TEST(FullFieldTransitions_BackgroundSearchOldPointer) {
|
|
CcTest::InitializeVM();
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
|
|
Handle<String> name1 = CcTest::MakeString("name1");
|
|
Handle<String> name2 = CcTest::MakeString("name2");
|
|
const PropertyAttributes attributes = NONE;
|
|
const PropertyKind kind = kData;
|
|
|
|
// Set map0 to be a full transition array with transition 'name1' to map1.
|
|
Handle<Map> map0 = Map::Create(isolate, 0);
|
|
Handle<Map> map1 =
|
|
Map::CopyWithField(isolate, map0, name1, FieldType::Any(isolate),
|
|
attributes, PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
Handle<Map> map2 =
|
|
Map::CopyWithField(isolate, map0, name2, FieldType::Any(isolate),
|
|
attributes, PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
TransitionsAccessor(isolate, map0).Insert(name1, map1, PROPERTY_TRANSITION);
|
|
{
|
|
TestTransitionsAccessor transitions(isolate, map0);
|
|
CHECK(transitions.IsFullTransitionArrayEncoding());
|
|
}
|
|
|
|
std::unique_ptr<PersistentHandles> ph = isolate->NewPersistentHandles();
|
|
|
|
Handle<Name> persistent_name1 = ph->NewHandle(name1);
|
|
Handle<Map> persistent_map0 = ph->NewHandle(map0);
|
|
Handle<Map> persistent_result_map1 = ph->NewHandle(map1);
|
|
|
|
base::Semaphore background_thread_started(0);
|
|
base::Semaphore main_thread_finished(0);
|
|
|
|
// Pass persistent handles to background thread.
|
|
std::unique_ptr<ConcurrentSearchThread> thread(
|
|
new ConcurrentSearchOnOutdatedAccessorThread(
|
|
isolate->heap(), &background_thread_started, &main_thread_finished,
|
|
std::move(ph), persistent_name1, persistent_map0,
|
|
persistent_result_map1));
|
|
CHECK(thread->Start());
|
|
|
|
background_thread_started.Wait();
|
|
|
|
CHECK_EQ(*map1, TransitionsAccessor(isolate, map0)
|
|
.SearchTransition(*name1, kind, attributes));
|
|
{
|
|
// Check that we do not have enough slack for the 2nd insertion into the
|
|
// TransitionArray.
|
|
TestTransitionsAccessor transitions(isolate, map0);
|
|
CHECK_EQ(transitions.Capacity(), 1);
|
|
}
|
|
TransitionsAccessor(isolate, map0).Insert(name2, map2, PROPERTY_TRANSITION);
|
|
CHECK_EQ(*map2, TransitionsAccessor(isolate, map0)
|
|
.SearchTransition(*name2, kind, attributes));
|
|
main_thread_finished.Signal();
|
|
|
|
thread->Join();
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
} // namespace internal
|
|
} // namespace v8
|