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