1ef2936adf
Timeouts occurred in test-concurrent-feedback-vector/CheckLoadICStates because the main thread could enter "handshaking" mode precisely at the moment when the worker thread successfully saw all states. The main thread would miss this, and end up waiting forever on a signal from the worker thread. Bug: v8:11082 Change-Id: I0441785d908c5e27562a3620bb2195483727f118 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2519553 Commit-Queue: Michael Stanton <mvstanton@chromium.org> Commit-Queue: Nico Hartmann <nicohartmann@chromium.org> Reviewed-by: Nico Hartmann <nicohartmann@chromium.org> Cr-Commit-Position: refs/heads/master@{#70962}
269 lines
9.4 KiB
C++
269 lines
9.4 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();
|
|
|
|
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
|