blob: 4cb843c97dcdb2b2dc16ccdb22f12f38e0a64cdb [file] [log] [blame]
// 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