v8/test/cctest/test-concurrent-feedback-vector.cc
Mike Stanton 2288b1f638 [TurboFan] Provide concurrent access to feedback vector
This CL provides synchronized get/set to feedback vector slots.
The FeedbackNexus is set up to use order preserving reads when used
on the background thread, and a lock to ensure coherent read
of information for ICKinds with two slots. The main thread takes
the lock on sets.

This test provides patterns to be followed by concurrent TurboFan.

We don't yet access the FeedbackVector on the background thread.
This CL only makes it safe to do so. The next step will come when
the optimizing compiler begins to query the the vector from the
background thread. Currently, with --concurrent-inlining turned on
this is done in bytecode serialization on the main thread. Without
concurrent inlining, it's also done on the main thread, in both
cases using the FeedbackNexus.

Bug: v8:7790
Change-Id: I49d8b8031190f91a0da1c24f375b6b6d8a9fe038
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2276210
Commit-Queue: Michael Stanton <mvstanton@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Reviewed-by: Santiago Aboy Solanes <solanes@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70797}
2020-10-27 13:24:23 +00:00

264 lines
9.2 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 <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();
} else {
all_states_seen.store(true, std::memory_order_release);
}
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_;
};
// 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();
vector_consumed.Wait();
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();
vector_consumed.Wait();
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();
vector_consumed.Wait();
fprintf(stderr, "Main thread configuring megamorphic\n");
}
// Go Megamorphic
nexus.ConfigureMegamorphic();
CHECK_EQ(MEGAMORPHIC, nexus.ic_state());
if (i == (kCycles - 1)) {
vector_ready.Signal();
vector_consumed.Wait();
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