| // 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 <atomic> |
| #include <unordered_set> |
| |
| #include "src/api/api.h" |
| #include "src/base/platform/semaphore.h" |
| #include "src/handles/handles-inl.h" |
| #include "src/handles/local-handles-inl.h" |
| #include "src/handles/persistent-handles.h" |
| #include "src/heap/heap.h" |
| #include "src/heap/local-heap.h" |
| #include "test/cctest/cctest.h" |
| #include "test/cctest/heap/heap-utils.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| namespace { |
| |
| // kCycles is large enough to ensure we see every state we are interested in. |
| const int kCycles = 1000; |
| static std::atomic<bool> all_states_seen{false}; |
| |
| class FeedbackVectorExplorationThread final : public v8::base::Thread { |
| public: |
| FeedbackVectorExplorationThread(Heap* heap, base::Semaphore* sema_started, |
| base::Semaphore* vector_ready, |
| base::Semaphore* vector_consumed, |
| std::unique_ptr<PersistentHandles> ph, |
| Handle<FeedbackVector> feedback_vector) |
| : v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")), |
| heap_(heap), |
| feedback_vector_(feedback_vector), |
| ph_(std::move(ph)), |
| sema_started_(sema_started), |
| vector_ready_(vector_ready), |
| vector_consumed_(vector_consumed) {} |
| |
| using InlineCacheSet = std::unordered_set<InlineCacheState, std::hash<int>>; |
| bool AllRequiredStatesSeen(const InlineCacheSet& found) { |
| auto end = found.end(); |
| return (found.find(UNINITIALIZED) != end && |
| found.find(MONOMORPHIC) != end && found.find(POLYMORPHIC) != end && |
| found.find(MEGAMORPHIC) != end); |
| } |
| |
| void Run() override { |
| Isolate* isolate = heap_->isolate(); |
| LocalHeap local_heap(heap_, ThreadKind::kBackground, std::move(ph_)); |
| UnparkedScope scope(&local_heap); |
| |
| // Get the feedback vector |
| NexusConfig nexus_config = |
| NexusConfig::FromBackgroundThread(isolate, &local_heap); |
| FeedbackSlot slot(0); |
| |
| // FeedbackVectorExplorationThread signals that it's beginning it's loop. |
| sema_started_->Signal(); |
| |
| InlineCacheSet found_states; |
| for (int i = 0; i < kCycles; i++) { |
| FeedbackNexus nexus(feedback_vector_, slot, nexus_config); |
| auto state = nexus.ic_state(); |
| if (state == MONOMORPHIC || state == POLYMORPHIC) { |
| MapHandles maps; |
| nexus.ExtractMaps(&maps); |
| for (unsigned int i = 0; i < maps.size(); i++) { |
| CHECK(maps[i]->IsMap()); |
| } |
| } |
| |
| if (found_states.find(state) == found_states.end()) { |
| found_states.insert(state); |
| if (AllRequiredStatesSeen(found_states)) { |
| // We are finished. |
| break; |
| } |
| } |
| } |
| |
| if (!AllRequiredStatesSeen(found_states)) { |
| // Repeat the exercise with an explicit handshaking protocol. This ensures |
| // at least coverage of the necessary code paths even though it is |
| // avoiding actual concurrency. I found that in test runs, there is always |
| // one or two bots that have a thread interleaving that doesn't allow all |
| // states to be seen. This is for that situation. |
| vector_ready_->Wait(); |
| fprintf(stderr, "Worker beginning to check for uninitialized\n"); |
| { |
| FeedbackNexus nexus(feedback_vector_, slot, nexus_config); |
| auto state = nexus.ic_state(); |
| CHECK_EQ(state, UNINITIALIZED); |
| } |
| vector_consumed_->Signal(); |
| vector_ready_->Wait(); |
| fprintf(stderr, "Worker beginning to check for monomorphic\n"); |
| { |
| FeedbackNexus nexus(feedback_vector_, slot, nexus_config); |
| auto state = nexus.ic_state(); |
| CHECK_EQ(state, MONOMORPHIC); |
| MapHandles maps; |
| nexus.ExtractMaps(&maps); |
| CHECK(maps[0]->IsMap()); |
| } |
| vector_consumed_->Signal(); |
| vector_ready_->Wait(); |
| fprintf(stderr, "Worker beginning to check for polymorphic\n"); |
| { |
| FeedbackNexus nexus(feedback_vector_, slot, nexus_config); |
| auto state = nexus.ic_state(); |
| CHECK_EQ(state, POLYMORPHIC); |
| MapHandles maps; |
| nexus.ExtractMaps(&maps); |
| for (unsigned int i = 0; i < maps.size(); i++) { |
| CHECK(maps[i]->IsMap()); |
| } |
| } |
| vector_consumed_->Signal(); |
| vector_ready_->Wait(); |
| fprintf(stderr, "Worker beginning to check for megamorphic\n"); |
| { |
| FeedbackNexus nexus(feedback_vector_, slot, nexus_config); |
| auto state = nexus.ic_state(); |
| CHECK_EQ(state, MEGAMORPHIC); |
| } |
| } |
| |
| all_states_seen.store(true, std::memory_order_release); |
| vector_consumed_->Signal(); |
| |
| CHECK(!ph_); |
| ph_ = local_heap.DetachPersistentHandles(); |
| } |
| |
| Heap* heap_; |
| Handle<FeedbackVector> feedback_vector_; |
| std::unique_ptr<PersistentHandles> ph_; |
| base::Semaphore* sema_started_; |
| |
| // These two semaphores control the explicit handshaking mode in case we |
| // didn't see all states within kCycles loops. |
| base::Semaphore* vector_ready_; |
| base::Semaphore* vector_consumed_; |
| }; |
| |
| static void CheckedWait(base::Semaphore& semaphore) { |
| while (!all_states_seen.load(std::memory_order_acquire)) { |
| if (semaphore.WaitFor(base::TimeDelta::FromMilliseconds(1))) break; |
| } |
| } |
| |
| // Verify that a LoadIC can be cycled through different states and safely |
| // read on a background thread. |
| TEST(CheckLoadICStates) { |
| CcTest::InitializeVM(); |
| FLAG_local_heaps = true; |
| FLAG_lazy_feedback_allocation = false; |
| Isolate* isolate = CcTest::i_isolate(); |
| |
| std::unique_ptr<PersistentHandles> ph = isolate->NewPersistentHandles(); |
| HandleScope handle_scope(isolate); |
| |
| Handle<HeapObject> o1 = Handle<HeapObject>::cast( |
| Utils::OpenHandle(*CompileRun("o1 = { bar: {} };"))); |
| Handle<HeapObject> o2 = Handle<HeapObject>::cast( |
| Utils::OpenHandle(*CompileRun("o2 = { baz: 3, bar: 3 };"))); |
| Handle<HeapObject> o3 = Handle<HeapObject>::cast( |
| Utils::OpenHandle(*CompileRun("o3 = { blu: 3, baz: 3, bar: 3 };"))); |
| Handle<HeapObject> o4 = Handle<HeapObject>::cast(Utils::OpenHandle( |
| *CompileRun("o4 = { ble: 3, blu: 3, baz: 3, bar: 3 };"))); |
| auto result = CompileRun( |
| "function foo(o) {" |
| " let a = o.bar;" |
| " return a;" |
| "}" |
| "foo(o1);" |
| "foo;"); |
| Handle<JSFunction> function = |
| Handle<JSFunction>::cast(Utils::OpenHandle(*result)); |
| Handle<FeedbackVector> vector(function->feedback_vector(), isolate); |
| FeedbackSlot slot(0); |
| FeedbackNexus nexus(vector, slot); |
| CHECK(IsLoadICKind(nexus.kind())); |
| CHECK_EQ(MONOMORPHIC, nexus.ic_state()); |
| nexus.ConfigureUninitialized(); |
| |
| // Now the basic environment is set up. Start the worker thread. |
| base::Semaphore sema_started(0); |
| base::Semaphore vector_ready(0); |
| base::Semaphore vector_consumed(0); |
| Handle<FeedbackVector> persistent_vector = |
| Handle<FeedbackVector>::cast(ph->NewHandle(vector)); |
| std::unique_ptr<FeedbackVectorExplorationThread> thread( |
| new FeedbackVectorExplorationThread(isolate->heap(), &sema_started, |
| &vector_ready, &vector_consumed, |
| std::move(ph), persistent_vector)); |
| CHECK(thread->Start()); |
| sema_started.Wait(); |
| |
| // Cycle the IC through all states repeatedly. |
| |
| // {dummy_handler} is just an arbitrary value to associate with a map in order |
| // to fill in the feedback vector slots in a minimally acceptable way. |
| MaybeObjectHandle dummy_handler(Smi::FromInt(10), isolate); |
| for (int i = 0; i < kCycles; i++) { |
| if (all_states_seen.load(std::memory_order_acquire)) break; |
| |
| CHECK_EQ(UNINITIALIZED, nexus.ic_state()); |
| if (i == (kCycles - 1)) { |
| // If we haven't seen all states by the last attempt, enter an explicit |
| // handshaking mode. |
| vector_ready.Signal(); |
| CheckedWait(vector_consumed); |
| fprintf(stderr, "Main thread configuring monomorphic\n"); |
| } |
| nexus.ConfigureMonomorphic(Handle<Name>(), Handle<Map>(o1->map(), isolate), |
| dummy_handler); |
| CHECK_EQ(MONOMORPHIC, nexus.ic_state()); |
| |
| if (i == (kCycles - 1)) { |
| vector_ready.Signal(); |
| CheckedWait(vector_consumed); |
| fprintf(stderr, "Main thread configuring polymorphic\n"); |
| } |
| |
| // Go polymorphic. |
| std::vector<MapAndHandler> map_and_handlers; |
| map_and_handlers.push_back( |
| MapAndHandler(Handle<Map>(o1->map(), isolate), dummy_handler)); |
| map_and_handlers.push_back( |
| MapAndHandler(Handle<Map>(o2->map(), isolate), dummy_handler)); |
| map_and_handlers.push_back( |
| MapAndHandler(Handle<Map>(o3->map(), isolate), dummy_handler)); |
| map_and_handlers.push_back( |
| MapAndHandler(Handle<Map>(o4->map(), isolate), dummy_handler)); |
| nexus.ConfigurePolymorphic(Handle<Name>(), map_and_handlers); |
| CHECK_EQ(POLYMORPHIC, nexus.ic_state()); |
| |
| if (i == (kCycles - 1)) { |
| vector_ready.Signal(); |
| CheckedWait(vector_consumed); |
| fprintf(stderr, "Main thread configuring megamorphic\n"); |
| } |
| |
| // Go Megamorphic |
| nexus.ConfigureMegamorphic(); |
| CHECK_EQ(MEGAMORPHIC, nexus.ic_state()); |
| |
| if (i == (kCycles - 1)) { |
| vector_ready.Signal(); |
| CheckedWait(vector_consumed); |
| fprintf(stderr, "Main thread finishing\n"); |
| } |
| |
| nexus.ConfigureUninitialized(); |
| } |
| |
| CHECK(all_states_seen.load(std::memory_order_acquire)); |
| thread->Join(); |
| } |
| |
| } // anonymous namespace |
| |
| } // namespace internal |
| } // namespace v8 |