Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,42 @@
// Copyright 2022 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 <stdlib.h>
#include "src/heap/factory.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
using ArrayListTest = TestWithContext;
TEST_F(ArrayListTest, ArrayList) {
HandleScope scope(i_isolate());
DirectHandle<ArrayList> array = i_isolate()->factory()->empty_array_list();
EXPECT_EQ(0, array->length());
array = ArrayList::Add(i_isolate(), array,
direct_handle(Smi::FromInt(100), i_isolate()));
EXPECT_EQ(1, array->length());
EXPECT_EQ(100, Smi::ToInt(array->get(0)));
array = ArrayList::Add(i_isolate(), array,
direct_handle(Smi::FromInt(200), i_isolate()),
direct_handle(Smi::FromInt(300), i_isolate()));
EXPECT_EQ(3, array->length());
EXPECT_EQ(100, Smi::ToInt(array->get(0)));
EXPECT_EQ(200, Smi::ToInt(array->get(1)));
EXPECT_EQ(300, Smi::ToInt(array->get(2)));
array->set(2, Smi::FromInt(400));
EXPECT_EQ(400, Smi::ToInt(array->get(2)));
array->set(2, ReadOnlyRoots(i_isolate()).undefined_value());
array->set_length(2);
EXPECT_EQ(2, array->length());
EXPECT_EQ(100, Smi::ToInt(array->get(0)));
EXPECT_EQ(200, Smi::ToInt(array->get(1)));
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,185 @@
// Copyright 2022 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/local-handles-inl.h"
#include "src/handles/persistent-handles.h"
#include "src/heap/heap.h"
#include "src/heap/local-heap-inl.h"
#include "src/heap/local-heap.h"
#include "src/heap/parked-scope.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
using ConcurrentDescriptorArrayTest = TestWithContext;
namespace internal {
static constexpr int kNumHandles = kHandleBlockSize * 2 + kHandleBlockSize / 2;
namespace {
class ConcurrentSearchThread final : public v8::base::Thread {
public:
ConcurrentSearchThread(Heap* heap,
std::vector<IndirectHandle<JSObject>> handles,
std::unique_ptr<PersistentHandles> ph,
Handle<Name> name, base::Semaphore* sema_started)
: v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")),
heap_(heap),
handles_(std::move(handles)),
ph_(std::move(ph)),
name_(name),
sema_started_(sema_started) {}
void Run() override {
LocalHeap local_heap(heap_, ThreadKind::kBackground, std::move(ph_));
UnparkedScope unparked_scope(&local_heap);
LocalHandleScope scope(&local_heap);
for (int i = 0; i < kNumHandles; i++) {
handles_.push_back(local_heap.NewPersistentHandle(handles_[0]));
}
sema_started_->Signal();
for (DirectHandle<JSObject> handle : handles_) {
// Lookup the named property on the {map}.
EXPECT_TRUE(IsUniqueName(*name_));
DirectHandle<Map> map(handle->map(), &local_heap);
DirectHandle<DescriptorArray> descriptors(
map->instance_descriptors(kAcquireLoad), &local_heap);
bool is_background_thread = true;
InternalIndex const number =
descriptors->Search(*name_, *map, is_background_thread);
EXPECT_TRUE(number.is_found());
}
EXPECT_EQ(static_cast<int>(handles_.size()), kNumHandles * 2);
}
private:
Heap* heap_;
std::vector<IndirectHandle<JSObject>> handles_;
std::unique_ptr<PersistentHandles> ph_;
Handle<Name> name_;
base::Semaphore* sema_started_;
};
// Uses linear search on a flat object, with up to 8 elements.
TEST_F(ConcurrentDescriptorArrayTest, LinearSearchFlatObject) {
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
std::vector<IndirectHandle<JSObject>> handles;
auto factory = i_isolate()->factory();
HandleScope handle_scope(i_isolate());
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(factory->empty_string());
Handle<JSObject> js_object = factory->NewJSObject(function);
Handle<String> name = MakeString("property");
DirectHandle<Object> value = MakeString("dummy_value");
// For the default constructor function no in-object properties are reserved
// hence adding a single property will initialize the property-array.
JSObject::DefinePropertyOrElementIgnoreAttributes(js_object, name, value,
NONE)
.Check();
for (int i = 0; i < kNumHandles; i++) {
handles.push_back(ph->NewHandle(js_object));
}
Handle<Name> persistent_name = ph->NewHandle(name);
base::Semaphore sema_started(0);
// Pass persistent handles to background thread.
std::unique_ptr<ConcurrentSearchThread> thread(new ConcurrentSearchThread(
i_isolate()->heap(), std::move(handles), std::move(ph), persistent_name,
&sema_started));
EXPECT_TRUE(thread->Start());
sema_started.Wait();
// Exercise descriptor in main thread too.
for (int i = 0; i < 7; ++i) {
DirectHandle<String> filler_name = MakeName("filler_property_", i);
DirectHandle<Object> filler_value = MakeString("dummy_value");
JSObject::DefinePropertyOrElementIgnoreAttributes(js_object, filler_name,
filler_value, NONE)
.Check();
}
EXPECT_EQ(js_object->map()->NumberOfOwnDescriptors(), 8);
thread->Join();
}
// Uses linear search on a flat object, which has more than 8 elements.
TEST_F(ConcurrentDescriptorArrayTest, LinearSearchFlatObject_ManyElements) {
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
std::vector<Handle<JSObject>> handles;
auto factory = i_isolate()->factory();
HandleScope handle_scope(i_isolate());
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(factory->empty_string());
Handle<JSObject> js_object = factory->NewJSObject(function);
Handle<String> name = MakeString("property");
DirectHandle<Object> value = MakeString("dummy_value");
// For the default constructor function no in-object properties are reserved
// hence adding a single property will initialize the property-array.
JSObject::DefinePropertyOrElementIgnoreAttributes(js_object, name, value,
NONE)
.Check();
// If we have more than 8 properties we would do a binary search. However,
// since we are going search in a background thread, we force a linear search
// that is safe to do in the background.
for (int i = 0; i < 10; ++i) {
DirectHandle<String> filler_name = MakeName("filler_property_", i);
DirectHandle<Object> filler_value = MakeString("dummy_value");
JSObject::DefinePropertyOrElementIgnoreAttributes(js_object, filler_name,
filler_value, NONE)
.Check();
}
EXPECT_GT(js_object->map()->NumberOfOwnDescriptors(), 8);
for (int i = 0; i < kNumHandles; i++) {
handles.push_back(ph->NewHandle(js_object));
}
Handle<Name> persistent_name = ph->NewHandle(name);
base::Semaphore sema_started(0);
// Pass persistent handles to background thread.
std::unique_ptr<ConcurrentSearchThread> thread(new ConcurrentSearchThread(
i_isolate()->heap(), std::move(handles), std::move(ph), persistent_name,
&sema_started));
EXPECT_TRUE(thread->Start());
sema_started.Wait();
// Exercise descriptor in main thread too.
for (int i = 10; i < 20; ++i) {
DirectHandle<String> filler_name = MakeName("filler_property_", i);
DirectHandle<Object> filler_value = MakeString("dummy_value");
JSObject::DefinePropertyOrElementIgnoreAttributes(js_object, filler_name,
filler_value, NONE)
.Check();
}
thread->Join();
}
} // anonymous namespace
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,273 @@
// Copyright 2022 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 "src/heap/parked-scope.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
using ConcurrentFeedbackVectorTest = TestWithContext;
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>;
bool AllRequiredStatesSeen(const InlineCacheSet& found) {
auto end = found.end();
return (found.find(InlineCacheState::UNINITIALIZED) != end &&
found.find(InlineCacheState::MONOMORPHIC) != end &&
found.find(InlineCacheState::POLYMORPHIC) != end &&
found.find(InlineCacheState::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 == InlineCacheState::MONOMORPHIC ||
state == InlineCacheState::POLYMORPHIC) {
MapHandles maps(isolate);
nexus.ExtractMaps(&maps);
for (unsigned int j = 0; j < maps.size(); j++) {
EXPECT_TRUE(IsMap(*maps[j]));
}
}
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();
EXPECT_EQ(state, InlineCacheState::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();
EXPECT_EQ(state, InlineCacheState::MONOMORPHIC);
MapHandles maps(isolate);
nexus.ExtractMaps(&maps);
EXPECT_TRUE(IsMap(*maps[0]));
}
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();
EXPECT_EQ(state, InlineCacheState::POLYMORPHIC);
MapHandles maps(isolate);
nexus.ExtractMaps(&maps);
for (unsigned int i = 0; i < maps.size(); i++) {
EXPECT_TRUE(IsMap(*maps[i]));
}
}
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();
EXPECT_EQ(state, InlineCacheState::MEGAMORPHIC);
}
}
all_states_seen.store(true, std::memory_order_release);
vector_consumed_->Signal();
EXPECT_TRUE(!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_F(ConcurrentFeedbackVectorTest, CheckLoadICStates) {
v8_flags.lazy_feedback_allocation = false;
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
HandleScope handle_scope(i_isolate());
DirectHandle<HeapObject> o1 =
Cast<HeapObject>(Utils::OpenDirectHandle(*RunJS("o1 = { bar: {} };")));
DirectHandle<HeapObject> o2 = Cast<HeapObject>(
Utils::OpenDirectHandle(*RunJS("o2 = { baz: 3, bar: 3 };")));
DirectHandle<HeapObject> o3 = Cast<HeapObject>(
Utils::OpenDirectHandle(*RunJS("o3 = { blu: 3, baz: 3, bar: 3 };")));
DirectHandle<HeapObject> o4 = Cast<HeapObject>(Utils::OpenDirectHandle(
*RunJS("o4 = { ble: 3, blu: 3, baz: 3, bar: 3 };")));
auto result = RunJS(
"function foo(o) {"
" let a = o.bar;"
" return a;"
"}"
"foo(o1);"
"foo;");
DirectHandle<JSFunction> function =
Cast<JSFunction>(Utils::OpenDirectHandle(*result));
Handle<FeedbackVector> vector(function->feedback_vector(), i_isolate());
FeedbackSlot slot(0);
FeedbackNexus nexus(i_isolate(), vector, slot);
EXPECT_TRUE(IsLoadICKind(nexus.kind()));
EXPECT_EQ(InlineCacheState::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 =
Cast<FeedbackVector>(ph->NewHandle(vector));
std::unique_ptr<FeedbackVectorExplorationThread> thread(
new FeedbackVectorExplorationThread(i_isolate()->heap(), &sema_started,
&vector_ready, &vector_consumed,
std::move(ph), persistent_vector));
EXPECT_TRUE(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.
MaybeObjectDirectHandle dummy_handler(Tagged<Object>(Smi::FromInt(10)),
i_isolate());
for (int i = 0; i < kCycles; i++) {
if (all_states_seen.load(std::memory_order_acquire)) break;
EXPECT_EQ(InlineCacheState::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(DirectHandle<Name>(),
DirectHandle<Map>(o1->map(), i_isolate()),
dummy_handler);
EXPECT_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
if (i == (kCycles - 1)) {
vector_ready.Signal();
CheckedWait(vector_consumed);
fprintf(stderr, "Main thread configuring polymorphic\n");
}
// Go polymorphic.
MapsAndHandlers map_and_handlers(i_isolate());
map_and_handlers.emplace_back(direct_handle(o1->map(), i_isolate()),
dummy_handler);
map_and_handlers.emplace_back(direct_handle(o2->map(), i_isolate()),
dummy_handler);
map_and_handlers.emplace_back(direct_handle(o3->map(), i_isolate()),
dummy_handler);
map_and_handlers.emplace_back(direct_handle(o4->map(), i_isolate()),
dummy_handler);
nexus.ConfigurePolymorphic(DirectHandle<Name>(), map_and_handlers);
EXPECT_EQ(InlineCacheState::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();
EXPECT_EQ(InlineCacheState::MEGAMORPHIC, nexus.ic_state());
if (i == (kCycles - 1)) {
vector_ready.Signal();
CheckedWait(vector_consumed);
fprintf(stderr, "Main thread finishing\n");
}
nexus.ConfigureUninitialized();
}
EXPECT_TRUE(all_states_seen.load(std::memory_order_acquire));
thread->Join();
}
} // anonymous namespace
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,140 @@
// Copyright 2022 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 <optional>
#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-inl.h"
#include "src/heap/local-heap.h"
#include "src/heap/parked-scope.h"
#include "src/objects/js-array-inl.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
using ConcurrentJsArrayTest = TestWithContext;
namespace internal {
static constexpr int kNumArrays = 1024;
namespace {
class BackgroundThread final : public v8::base::Thread {
public:
BackgroundThread(Heap* heap, std::vector<Handle<JSArray>> handles,
std::unique_ptr<PersistentHandles> ph,
base::Semaphore* sema_started)
: v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")),
heap_(heap),
handles_(std::move(handles)),
ph_(std::move(ph)),
sema_started_(sema_started) {}
void Run() override {
LocalHeap local_heap(heap_, ThreadKind::kBackground, std::move(ph_));
UnparkedScope unparked_scope(&local_heap);
LocalHandleScope scope(&local_heap);
Isolate* isolate = heap_->isolate();
for (int i = 0; i < kNumArrays; i++) {
handles_[i] = local_heap.NewPersistentHandle(handles_[i]);
}
sema_started_->Signal();
// Iterate in the opposite directions as the main thread to make a race at
// some point more likely.
static constexpr int kIndex = 1;
for (int i = 0; i < kNumArrays; i++) {
DirectHandle<JSArray> x = handles_[i];
DirectHandle<FixedArrayBase> elements =
local_heap.NewPersistentHandle(x->elements(isolate, kRelaxedLoad));
ElementsKind elements_kind = x->map(isolate)->elements_kind();
// Mirroring the conditions in JSArrayRef::GetOwnCowElement.
if (!IsSmiOrObjectElementsKind(elements_kind)) continue;
if (elements->map() != ReadOnlyRoots(isolate).fixed_cow_array_map()) {
continue;
}
std::optional<Tagged<Object>> result =
ConcurrentLookupIterator::TryGetOwnCowElement(
isolate, Cast<FixedArray>(*elements), elements_kind,
Smi::ToInt(x->length(isolate, kRelaxedLoad)), kIndex);
if (result.has_value()) {
// On any success, the elements at index 1 must be the original value
// Tagged<Smi>(1).
EXPECT_TRUE(IsSmi(result.value()));
CHECK_EQ(Smi::ToInt(result.value()), 1);
}
}
}
private:
Heap* heap_;
std::vector<Handle<JSArray>> handles_;
std::unique_ptr<PersistentHandles> ph_;
base::Semaphore* sema_started_;
};
TEST_F(ConcurrentJsArrayTest, ArrayWithCowElements) {
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
std::vector<Handle<JSArray>> handles;
std::vector<Handle<JSArray>> persistent_handles;
HandleScope handle_scope(i_isolate());
// Create kNumArrays arrays with COW backing stores.
RunJS(
"function f() { return [0,1,2,3,4]; }\n"
"const xs = [];\n"
"let i = 0;\n");
for (int i = 0; i < kNumArrays; i++) {
Handle<JSArray> x =
Cast<JSArray>(Utils::OpenHandle(*RunJS("xs[i++] = f();")));
EXPECT_EQ(x->elements()->map(),
ReadOnlyRoots(i_isolate()).fixed_cow_array_map());
handles.push_back(x);
persistent_handles.push_back(ph->NewHandle(x));
}
base::Semaphore sema_started(0);
// Pass persistent handles to background thread.
std::unique_ptr<BackgroundThread> thread(new BackgroundThread(
i_isolate()->heap(), persistent_handles, std::move(ph), &sema_started));
EXPECT_TRUE(thread->Start());
sema_started.Wait();
// On the main thread, mutate the arrays, converting to a non-COW backing
// store.
static const char* const kMutators[] = {
"xs[--i].length--;", "xs[--i].length++;", "xs[--i].length = 0;",
"xs[--i][1] = 42;", "delete xs[--i][1];", "xs[--i].push(42);",
"xs[--i].pop();", "xs[--i][1] = 1.5;", "xs[--i][1] = {};",
};
static const int kNumMutators = arraysize(kMutators);
for (int i = kNumArrays - 1; i >= 0; i--) {
RunJS(kMutators[i % kNumMutators]);
EXPECT_NE(handles[i]->elements()->map(),
ReadOnlyRoots(i_isolate()).fixed_cow_array_map());
}
thread->Join();
}
} // anonymous namespace
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,197 @@
// Copyright 2022 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/local-handles-inl.h"
#include "src/handles/persistent-handles.h"
#include "src/heap/heap.h"
#include "src/heap/local-heap-inl.h"
#include "src/heap/local-heap.h"
#include "src/heap/parked-scope.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
using ConcurrentPrototypeTest = TestWithContext;
namespace internal {
static constexpr int kNumHandles = kHandleBlockSize * 2 + kHandleBlockSize / 2;
namespace {
class ConcurrentSearchThread final : public v8::base::Thread {
public:
ConcurrentSearchThread(Heap* heap,
std::vector<IndirectHandle<JSObject>> handles,
std::unique_ptr<PersistentHandles> ph,
base::Semaphore* sema_started)
: v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")),
heap_(heap),
handles_(std::move(handles)),
ph_(std::move(ph)),
sema_started_(sema_started) {}
void Run() override {
LocalHeap local_heap(heap_, ThreadKind::kBackground, std::move(ph_));
UnparkedScope unparked_scope(&local_heap);
LocalHandleScope scope(&local_heap);
for (int i = 0; i < kNumHandles; i++) {
handles_.push_back(local_heap.NewPersistentHandle(handles_[0]));
}
sema_started_->Signal();
for (DirectHandle<JSObject> js_obj : handles_) {
// Walk up the prototype chain all the way to the top.
DirectHandle<Map> map(js_obj->map(kAcquireLoad), &local_heap);
while (!IsNull(map->prototype())) {
DirectHandle<Map> map_prototype_map(map->prototype()->map(kAcquireLoad),
&local_heap);
if (!IsJSObjectMap(*map_prototype_map)) {
break;
}
map = map_prototype_map;
}
}
CHECK_EQ(static_cast<int>(handles_.size()), kNumHandles * 2);
}
private:
Heap* heap_;
std::vector<IndirectHandle<JSObject>> handles_;
std::unique_ptr<PersistentHandles> ph_;
base::Semaphore* sema_started_;
};
// Test to search on a background thread, while the main thread is idle.
TEST_F(ConcurrentPrototypeTest, ProtoWalkBackground) {
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
std::vector<IndirectHandle<JSObject>> handles;
auto factory = i_isolate()->factory();
HandleScope handle_scope(i_isolate());
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(factory->empty_string());
Handle<JSObject> js_object = factory->NewJSObject(function);
DirectHandle<String> name = MakeString("property");
DirectHandle<Object> value = MakeString("dummy_value");
// For the default constructor function no in-object properties are reserved
// hence adding a single property will initialize the property-array.
JSObject::DefinePropertyOrElementIgnoreAttributes(js_object, name, value,
NONE)
.Check();
for (int i = 0; i < kNumHandles; i++) {
handles.push_back(ph->NewHandle(js_object));
}
base::Semaphore sema_started(0);
// Pass persistent handles to background thread.
std::unique_ptr<ConcurrentSearchThread> thread(new ConcurrentSearchThread(
i_isolate()->heap(), std::move(handles), std::move(ph), &sema_started));
CHECK(thread->Start());
sema_started.Wait();
thread->Join();
}
// Test to search on a background thread, while the main thread modifies the
// descriptor array.
TEST_F(ConcurrentPrototypeTest, ProtoWalkBackground_DescriptorArrayWrite) {
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
std::vector<IndirectHandle<JSObject>> handles;
auto factory = i_isolate()->factory();
HandleScope handle_scope(i_isolate());
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(factory->empty_string());
Handle<JSObject> js_object = factory->NewJSObject(function);
DirectHandle<String> name = MakeString("property");
DirectHandle<Object> value = MakeString("dummy_value");
// For the default constructor function no in-object properties are reserved
// hence adding a single property will initialize the property-array.
JSObject::DefinePropertyOrElementIgnoreAttributes(js_object, name, value,
NONE)
.Check();
for (int i = 0; i < kNumHandles; i++) {
handles.push_back(ph->NewHandle(js_object));
}
base::Semaphore sema_started(0);
// Pass persistent handles to background thread.
std::unique_ptr<ConcurrentSearchThread> thread(new ConcurrentSearchThread(
i_isolate()->heap(), std::move(handles), std::move(ph), &sema_started));
CHECK(thread->Start());
sema_started.Wait();
// Exercise descriptor array.
for (int i = 0; i < 20; ++i) {
DirectHandle<String> filler_name = MakeName("filler_property_", i);
DirectHandle<Object> filler_value = MakeString("dummy_value");
JSObject::DefinePropertyOrElementIgnoreAttributes(js_object, filler_name,
filler_value, NONE)
.Check();
}
thread->Join();
}
TEST_F(ConcurrentPrototypeTest, ProtoWalkBackground_PrototypeChainWrite) {
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
std::vector<IndirectHandle<JSObject>> handles;
auto factory = i_isolate()->factory();
HandleScope handle_scope(i_isolate());
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(factory->empty_string());
Handle<JSObject> js_object = factory->NewJSObject(function);
for (int i = 0; i < kNumHandles; i++) {
handles.push_back(ph->NewHandle(js_object));
}
base::Semaphore sema_started(0);
// Pass persistent handles to background thread.
std::unique_ptr<ConcurrentSearchThread> thread(new ConcurrentSearchThread(
i_isolate()->heap(), std::move(handles), std::move(ph), &sema_started));
CHECK(thread->Start());
// The prototype chain looks like this JSObject -> Object -> null. Change the
// prototype of the js_object to be JSObject -> null, and then back a bunch of
// times.
DirectHandle<Map> map(js_object->map(), i_isolate());
Handle<HeapObject> old_proto(map->prototype(), i_isolate());
DCHECK(!IsNull(*old_proto));
Handle<HeapObject> new_proto(old_proto->map()->prototype(), i_isolate());
sema_started.Wait();
for (int i = 0; i < 20; ++i) {
CHECK(JSReceiver::SetPrototype(i_isolate(), js_object,
i % 2 == 0 ? new_proto : old_proto, false,
kDontThrow)
.FromJust());
}
thread->Join();
}
} // anonymous namespace
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,206 @@
// Copyright 2022 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/local-handles-inl.h"
#include "src/handles/persistent-handles.h"
#include "src/heap/heap.h"
#include "src/heap/local-heap.h"
#include "src/heap/parked-scope.h"
#include "src/objects/contexts.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
using ConcurrentScriptContextTableTest = TestWithContext;
namespace internal {
namespace {
std::atomic<int> g_initialized_entries;
class ScriptContextTableAccessUsedThread final : public v8::base::Thread {
public:
ScriptContextTableAccessUsedThread(
Isolate* isolate, Heap* heap, base::Semaphore* sema_started,
std::unique_ptr<PersistentHandles> ph,
Handle<ScriptContextTable> script_context_table)
: v8::base::Thread(
base::Thread::Options("ScriptContextTableAccessUsedThread")),
heap_(heap),
sema_started_(sema_started),
ph_(std::move(ph)),
script_context_table_(script_context_table) {}
void Run() override {
LocalHeap local_heap(heap_, ThreadKind::kBackground, std::move(ph_));
UnparkedScope unparked_scope(&local_heap);
LocalHandleScope scope(&local_heap);
sema_started_->Signal();
for (int i = 0; i < script_context_table_->length(kAcquireLoad); ++i) {
Tagged<Context> context = script_context_table_->get(i);
EXPECT_TRUE(context->IsScriptContext());
}
}
private:
Heap* heap_;
base::Semaphore* sema_started_;
std::unique_ptr<PersistentHandles> ph_;
Handle<ScriptContextTable> script_context_table_;
};
class AccessScriptContextTableThread final : public v8::base::Thread {
public:
AccessScriptContextTableThread(Isolate* isolate, Heap* heap,
base::Semaphore* sema_started,
std::unique_ptr<PersistentHandles> ph,
Handle<NativeContext> native_context)
: v8::base::Thread(
base::Thread::Options("AccessScriptContextTableThread")),
heap_(heap),
sema_started_(sema_started),
ph_(std::move(ph)),
native_context_(native_context) {}
void Run() override {
LocalHeap local_heap(heap_, ThreadKind::kBackground, std::move(ph_));
UnparkedScope unparked_scope(&local_heap);
LocalHandleScope scope(&local_heap);
sema_started_->Signal();
for (int i = 0; i < 1000; ++i) {
// Read upper bound with relaxed semantics to not add any ordering
// constraints.
while (i >= g_initialized_entries.load(std::memory_order_relaxed)) {
}
auto script_context_table = Handle<ScriptContextTable>(
native_context_->synchronized_script_context_table(), &local_heap);
DirectHandle<Context> context(script_context_table->get(i), &local_heap);
EXPECT_TRUE(!context.is_null());
}
}
private:
Heap* heap_;
base::Semaphore* sema_started_;
std::unique_ptr<PersistentHandles> ph_;
Handle<NativeContext> native_context_;
};
TEST_F(ConcurrentScriptContextTableTest, ScriptContextTable_Extend) {
v8::HandleScope scope(isolate());
const bool kIgnoreDuplicateNames = true;
Factory* factory = i_isolate()->factory();
DirectHandle<NativeContext> native_context = factory->NewNativeContext();
DirectHandle<Map> script_context_map = factory->NewContextfulMap(
native_context, SCRIPT_CONTEXT_TYPE, kVariableSizeSentinel);
script_context_map->set_native_context(*native_context);
native_context->set_script_context_map(*script_context_map);
Handle<ScriptContextTable> script_context_table =
factory->NewScriptContextTable();
DirectHandle<ScopeInfo> scope_info =
i_isolate()->factory()->global_this_binding_scope_info();
for (int i = 0; i < 10; ++i) {
DirectHandle<Context> script_context =
factory->NewScriptContext(native_context, scope_info);
script_context_table =
ScriptContextTable::Add(i_isolate(), script_context_table,
script_context, kIgnoreDuplicateNames);
}
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
Handle<ScriptContextTable> persistent_script_context_table =
ph->NewHandle(script_context_table);
base::Semaphore sema_started(0);
auto thread = std::make_unique<ScriptContextTableAccessUsedThread>(
i_isolate(), i_isolate()->heap(), &sema_started, std::move(ph),
persistent_script_context_table);
EXPECT_TRUE(thread->Start());
sema_started.Wait();
for (int i = 0; i < 100; ++i) {
DirectHandle<Context> context =
factory->NewScriptContext(native_context, scope_info);
script_context_table = ScriptContextTable::Add(
i_isolate(), script_context_table, context, kIgnoreDuplicateNames);
}
thread->Join();
}
TEST_F(ConcurrentScriptContextTableTest,
ScriptContextTable_AccessScriptContextTable) {
v8::HandleScope scope(isolate());
Factory* factory = i_isolate()->factory();
Handle<NativeContext> native_context = factory->NewNativeContext();
DirectHandle<Map> script_context_map = factory->NewContextfulMap(
native_context, SCRIPT_CONTEXT_TYPE, kVariableSizeSentinel);
script_context_map->set_native_context(*native_context);
native_context->set_script_context_map(*script_context_map);
DirectHandle<ScopeInfo> scope_info =
i_isolate()->factory()->global_this_binding_scope_info();
Handle<ScriptContextTable> script_context_table =
factory->NewScriptContextTable();
DirectHandle<Context> context =
factory->NewScriptContext(native_context, scope_info);
script_context_table = ScriptContextTable::Add(
i_isolate(), script_context_table, context, false);
int initialized_entries = 1;
g_initialized_entries.store(initialized_entries, std::memory_order_release);
native_context->set_script_context_table(*script_context_table);
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
Handle<NativeContext> persistent_native_context =
ph->NewHandle(native_context);
base::Semaphore sema_started(0);
auto thread = std::make_unique<AccessScriptContextTableThread>(
i_isolate(), i_isolate()->heap(), &sema_started, std::move(ph),
persistent_native_context);
EXPECT_TRUE(thread->Start());
sema_started.Wait();
const bool kIgnoreDuplicateNames = true;
for (; initialized_entries < 1000; ++initialized_entries) {
DirectHandle<Context> new_context =
factory->NewScriptContext(native_context, scope_info);
script_context_table = ScriptContextTable::Add(
i_isolate(), script_context_table, new_context, kIgnoreDuplicateNames);
native_context->synchronized_set_script_context_table(
*script_context_table);
// Update with relaxed semantics to not introduce ordering constraints.
g_initialized_entries.store(initialized_entries, std::memory_order_relaxed);
}
g_initialized_entries.store(initialized_entries, std::memory_order_relaxed);
thread->Join();
}
} // anonymous namespace
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,317 @@
// Copyright 2022 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/local-handles-inl.h"
#include "src/handles/persistent-handles.h"
#include "src/heap/heap.h"
#include "src/heap/local-heap-inl.h"
#include "src/heap/local-heap.h"
#include "src/heap/parked-scope.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
using ConcurrentStringTest = TestWithContext;
namespace internal {
namespace {
#define DOUBLE_VALUE 28.123456789
#define STRING_VALUE "28.123456789"
#define ARRAY_VALUE \
{ '2', '8', '.', '1', '2', '3', '4', '5', '6', '7', '8', '9' }
// Adapted from cctest/test-api.cc, and
// test/cctest/heap/test-external-string-tracker.cc.
class TestOneByteResource : public v8::String::ExternalOneByteStringResource {
public:
explicit TestOneByteResource(const char* data)
: data_(data), length_(strlen(data)) {}
~TestOneByteResource() override { i::DeleteArray(data_); }
const char* data() const override { return data_; }
size_t length() const override { return length_; }
private:
const char* data_;
size_t length_;
};
// Adapted from cctest/test-api.cc.
class TestTwoByteResource : public v8::String::ExternalStringResource {
public:
explicit TestTwoByteResource(uint16_t* data) : data_(data), length_(0) {
while (data[length_]) ++length_;
}
~TestTwoByteResource() override { i::DeleteArray(data_); }
const uint16_t* data() const override { return data_; }
size_t length() const override { return length_; }
private:
uint16_t* data_;
size_t length_;
};
class ConcurrentStringThread final : public v8::base::Thread {
public:
ConcurrentStringThread(Isolate* isolate, Handle<String> str,
std::unique_ptr<PersistentHandles> ph,
base::Semaphore* sema_started,
std::vector<uint16_t> chars)
: v8::base::Thread(base::Thread::Options("ThreadWithLocalHeap")),
isolate_(isolate),
str_(str),
ph_(std::move(ph)),
sema_started_(sema_started),
length_(chars.size()),
chars_(chars) {}
void Run() override {
LocalIsolate local_isolate(isolate_, ThreadKind::kBackground);
local_isolate.heap()->AttachPersistentHandles(std::move(ph_));
UnparkedScope unparked_scope(local_isolate.heap());
sema_started_->Signal();
// Check the three operations we do from the StringRef concurrently: get the
// string, the nth character, and convert into a double.
EXPECT_EQ(str_->length(kAcquireLoad), static_cast<uint32_t>(length_));
for (unsigned int i = 0; i < length_; ++i) {
EXPECT_EQ(str_->Get(i, &local_isolate), chars_[i]);
}
EXPECT_EQ(TryStringToDouble(&local_isolate, str_).value(), DOUBLE_VALUE);
}
private:
Isolate* isolate_;
Handle<String> str_;
std::unique_ptr<PersistentHandles> ph_;
base::Semaphore* sema_started_;
uint64_t length_;
std::vector<uint16_t> chars_;
};
// Inspect a one byte string, while the main thread externalizes it.
TEST_F(ConcurrentStringTest, InspectOneByteExternalizing) {
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
auto factory = i_isolate()->factory();
HandleScope handle_scope(i_isolate());
// Crate an internalized one-byte string.
const char* raw_string = STRING_VALUE;
Handle<String> one_byte_string = factory->InternalizeString(
factory->NewStringFromAsciiChecked(raw_string));
EXPECT_TRUE(one_byte_string->IsOneByteRepresentation());
EXPECT_TRUE(!IsExternalString(*one_byte_string));
EXPECT_TRUE(IsInternalizedString(*one_byte_string));
Handle<String> persistent_string = ph->NewHandle(one_byte_string);
std::vector<uint16_t> chars;
for (uint32_t i = 0; i < one_byte_string->length(); ++i) {
chars.push_back(one_byte_string->Get(i));
}
base::Semaphore sema_started(0);
std::unique_ptr<ConcurrentStringThread> thread(new ConcurrentStringThread(
i_isolate(), persistent_string, std::move(ph), &sema_started, chars));
EXPECT_TRUE(thread->Start());
sema_started.Wait();
// Externalize it to a one-byte external string.
// We need to use StrDup in this case since the TestOneByteResource will get
// ownership of raw_string otherwise.
EXPECT_TRUE(one_byte_string->MakeExternal(
i_isolate(), new TestOneByteResource(i::StrDup(raw_string))));
EXPECT_TRUE(IsExternalOneByteString(*one_byte_string));
EXPECT_TRUE(IsInternalizedString(*one_byte_string));
thread->Join();
}
// Inspect a two byte string, while the main thread externalizes it.
TEST_F(ConcurrentStringTest, InspectTwoByteExternalizing) {
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
auto factory = i_isolate()->factory();
HandleScope handle_scope(i_isolate());
// Crate an internalized two-byte string.
// TODO(solanes): Can we have only one raw string?
const char* raw_string = STRING_VALUE;
// TODO(solanes): Is this the best way to create a two byte string from chars?
const int kLength = 12;
const uint16_t two_byte_array[kLength] = ARRAY_VALUE;
Handle<String> two_bytes_string;
{
Handle<SeqTwoByteString> raw =
factory->NewRawTwoByteString(kLength).ToHandleChecked();
DisallowGarbageCollection no_gc;
CopyChars(raw->GetChars(no_gc), two_byte_array, kLength);
two_bytes_string = raw;
}
two_bytes_string = factory->InternalizeString(two_bytes_string);
EXPECT_TRUE(two_bytes_string->IsTwoByteRepresentation());
EXPECT_TRUE(!IsExternalString(*two_bytes_string));
EXPECT_TRUE(IsInternalizedString(*two_bytes_string));
Handle<String> persistent_string = ph->NewHandle(two_bytes_string);
std::vector<uint16_t> chars;
for (uint32_t i = 0; i < two_bytes_string->length(); ++i) {
chars.push_back(two_bytes_string->Get(i));
}
base::Semaphore sema_started(0);
std::unique_ptr<ConcurrentStringThread> thread(new ConcurrentStringThread(
i_isolate(), persistent_string, std::move(ph), &sema_started, chars));
EXPECT_TRUE(thread->Start());
sema_started.Wait();
// Externalize it to a two-bytes external string.
EXPECT_TRUE(two_bytes_string->MakeExternal(
i_isolate(), new TestTwoByteResource(AsciiToTwoByteString(raw_string))));
EXPECT_TRUE(IsExternalTwoByteString(*two_bytes_string));
EXPECT_TRUE(IsInternalizedString(*two_bytes_string));
thread->Join();
}
// Inspect a one byte string, while the main thread externalizes it. Same as
// InspectOneByteExternalizing, but using thin strings.
TEST_F(ConcurrentStringTest, InspectOneByteExternalizing_ThinString) {
// We will not create a thin string if single_generation is turned on.
if (v8_flags.single_generation) return;
// We don't create ThinStrings immediately when using the forwarding table.
if (v8_flags.always_use_string_forwarding_table) return;
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
auto factory = i_isolate()->factory();
HandleScope handle_scope(i_isolate());
// Create a string.
const char* raw_string = STRING_VALUE;
Handle<String> thin_string = factory->NewStringFromAsciiChecked(raw_string);
EXPECT_TRUE(!IsExternalString(*thin_string));
EXPECT_TRUE(!IsInternalizedString(*thin_string));
// Crate an internalized one-byte version of that string string.
DirectHandle<String> internalized_string =
factory->InternalizeString(thin_string);
EXPECT_TRUE(internalized_string->IsOneByteRepresentation());
EXPECT_TRUE(!IsExternalString(*internalized_string));
EXPECT_TRUE(IsInternalizedString(*internalized_string));
// We now should have an internalized string, and a thin string pointing to
// it.
EXPECT_TRUE(IsThinString(*thin_string));
EXPECT_NE(*thin_string, *internalized_string);
Handle<String> persistent_string = ph->NewHandle(thin_string);
std::vector<uint16_t> chars;
for (uint32_t i = 0; i < thin_string->length(); ++i) {
chars.push_back(thin_string->Get(i));
}
base::Semaphore sema_started(0);
std::unique_ptr<ConcurrentStringThread> thread(new ConcurrentStringThread(
i_isolate(), persistent_string, std::move(ph), &sema_started, chars));
EXPECT_TRUE(thread->Start());
sema_started.Wait();
// Externalize it to a one-byte external string.
// We need to use StrDup in this case since the TestOneByteResource will get
// ownership of raw_string otherwise.
EXPECT_TRUE(internalized_string->MakeExternal(
i_isolate(), new TestOneByteResource(i::StrDup(raw_string))));
EXPECT_TRUE(IsExternalOneByteString(*internalized_string));
EXPECT_TRUE(IsInternalizedString(*internalized_string));
// Check that the thin string is unmodified.
EXPECT_TRUE(!IsExternalString(*thin_string));
EXPECT_TRUE(!IsInternalizedString(*thin_string));
EXPECT_TRUE(IsThinString(*thin_string));
thread->Join();
}
// Inspect a two byte string, while the main thread externalizes it. Same as
// InspectTwoByteExternalizing, but using thin strings.
TEST_F(ConcurrentStringTest, InspectTwoByteExternalizing_ThinString) {
// We will not create a thin string if single_generation is turned on.
if (v8_flags.single_generation) return;
// We don't create ThinStrings immediately when using the forwarding table.
if (v8_flags.always_use_string_forwarding_table) return;
std::unique_ptr<PersistentHandles> ph = i_isolate()->NewPersistentHandles();
auto factory = i_isolate()->factory();
HandleScope handle_scope(i_isolate());
// Crate an internalized two-byte string.
// TODO(solanes): Can we have only one raw string?
const char* raw_string = STRING_VALUE;
// TODO(solanes): Is this the best way to create a two byte string from chars?
const int kLength = 12;
const uint16_t two_byte_array[kLength] = ARRAY_VALUE;
Handle<String> thin_string;
{
Handle<SeqTwoByteString> raw =
factory->NewRawTwoByteString(kLength).ToHandleChecked();
DisallowGarbageCollection no_gc;
CopyChars(raw->GetChars(no_gc), two_byte_array, kLength);
thin_string = raw;
}
DirectHandle<String> internalized_string =
factory->InternalizeString(thin_string);
EXPECT_TRUE(internalized_string->IsTwoByteRepresentation());
EXPECT_TRUE(!IsExternalString(*internalized_string));
EXPECT_TRUE(IsInternalizedString(*internalized_string));
Handle<String> persistent_string = ph->NewHandle(thin_string);
std::vector<uint16_t> chars;
for (uint32_t i = 0; i < thin_string->length(); ++i) {
chars.push_back(thin_string->Get(i));
}
base::Semaphore sema_started(0);
std::unique_ptr<ConcurrentStringThread> thread(new ConcurrentStringThread(
i_isolate(), persistent_string, std::move(ph), &sema_started, chars));
EXPECT_TRUE(thread->Start());
sema_started.Wait();
// Externalize it to a two-bytes external string.
EXPECT_TRUE(internalized_string->MakeExternal(
i_isolate(), new TestTwoByteResource(AsciiToTwoByteString(raw_string))));
EXPECT_TRUE(IsExternalTwoByteString(*internalized_string));
EXPECT_TRUE(IsInternalizedString(*internalized_string));
// Check that the thin string is unmodified.
EXPECT_TRUE(!IsExternalString(*thin_string));
EXPECT_TRUE(!IsInternalizedString(*thin_string));
EXPECT_TRUE(IsThinString(*thin_string));
thread->Join();
}
} // anonymous namespace
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,457 @@
// Copyright 2022 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 <optional>
#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/heap/parked-scope.h"
#include "src/objects/transitions-inl.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
using ConcurrentTransitionArrayTest = TestWithContext;
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,
std::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(heap_->isolate(), *map_, true)
.SearchTransition(*name_, PropertyKind::kData, NONE),
result_map_ ? **result_map_ : Tagged<Map>());
}
Heap* heap() { return heap_; }
// 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_;
std::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);
background_thread_started_->Signal();
main_thread_finished_->Wait();
CHECK_EQ(TransitionsAccessor(heap()->isolate(), *map_, true)
.SearchTransition(*name_, PropertyKind::kData, NONE),
result_map_ ? **result_map_ : Tagged<Map>());
}
base::Semaphore* main_thread_finished_;
};
// Search on the main thread and in the background thread at the same time.
TEST_F(ConcurrentTransitionArrayTest, FullFieldTransitions_OnlySearch) {
v8::HandleScope scope(isolate());
Handle<String> name = MakeString("name");
const PropertyAttributes attributes = NONE;
const PropertyKind kind = PropertyKind::kData;
// Set map0 to be a full transition array with transition 'name' to map1.
Handle<Map> map0 = Map::Create(i_isolate(), 0);
Handle<Map> map1 =
Map::CopyWithField(i_isolate(), map0, name, FieldType::Any(i_isolate()),
attributes, PropertyConstness::kMutable,
Representation::Tagged(), OMIT_TRANSITION)
.ToHandleChecked();
TransitionsAccessor::Insert(i_isolate(), map0, name, map1,
PROPERTY_TRANSITION);
{
TestTransitionsAccessor transitions(i_isolate(), map0);
CHECK(transitions.IsFullTransitionArrayEncoding());
}
std::unique_ptr<PersistentHandles> ph = i_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(
i_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::SearchTransition(
i_isolate(), map0, *name, kind, attributes)
.ToHandleChecked());
thread->Join();
}
// Search and insert on the main thread, while the background thread searches at
// the same time.
TEST_F(ConcurrentTransitionArrayTest, FullFieldTransitions) {
v8::HandleScope scope(isolate());
Handle<String> name1 = MakeString("name1");
DirectHandle<String> name2 = MakeString("name2");
const PropertyAttributes attributes = NONE;
const PropertyKind kind = PropertyKind::kData;
// Set map0 to be a full transition array with transition 'name1' to map1.
Handle<Map> map0 = Map::Create(i_isolate(), 0);
Handle<Map> map1 =
Map::CopyWithField(i_isolate(), map0, name1, FieldType::Any(i_isolate()),
attributes, PropertyConstness::kMutable,
Representation::Tagged(), OMIT_TRANSITION)
.ToHandleChecked();
DirectHandle<Map> map2 =
Map::CopyWithField(i_isolate(), map0, name2, FieldType::Any(i_isolate()),
attributes, PropertyConstness::kMutable,
Representation::Tagged(), OMIT_TRANSITION)
.ToHandleChecked();
TransitionsAccessor::Insert(i_isolate(), map0, name1, map1,
PROPERTY_TRANSITION);
{
TestTransitionsAccessor transitions(i_isolate(), map0);
CHECK(transitions.IsFullTransitionArrayEncoding());
}
std::unique_ptr<PersistentHandles> ph = i_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(
i_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::SearchTransition(
i_isolate(), map0, *name1, kind, attributes)
.ToHandleChecked());
TransitionsAccessor::Insert(i_isolate(), map0, name2, map2,
PROPERTY_TRANSITION);
CHECK_EQ(*map2, *TransitionsAccessor::SearchTransition(
i_isolate(), map0, *name2, kind, attributes)
.ToHandleChecked());
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_F(ConcurrentTransitionArrayTest, WeakRefToFullFieldTransitions) {
v8::HandleScope scope(isolate());
Handle<String> name1 = MakeString("name1");
DirectHandle<String> name2 = MakeString("name2");
const PropertyAttributes attributes = NONE;
const PropertyKind kind = PropertyKind::kData;
// Set map0 to be a simple transition array with transition 'name1' to map1.
Handle<Map> map0 = Map::Create(i_isolate(), 0);
Handle<Map> map1 =
Map::CopyWithField(i_isolate(), map0, name1, FieldType::Any(i_isolate()),
attributes, PropertyConstness::kMutable,
Representation::Tagged(), OMIT_TRANSITION)
.ToHandleChecked();
DirectHandle<Map> map2 =
Map::CopyWithField(i_isolate(), map0, name2, FieldType::Any(i_isolate()),
attributes, PropertyConstness::kMutable,
Representation::Tagged(), OMIT_TRANSITION)
.ToHandleChecked();
TransitionsAccessor::Insert(i_isolate(), map0, name1, map1,
SIMPLE_PROPERTY_TRANSITION);
{
TestTransitionsAccessor transitions(i_isolate(), map0);
CHECK(transitions.IsWeakRefEncoding());
}
std::unique_ptr<PersistentHandles> ph = i_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(
i_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::SearchTransition(
i_isolate(), map0, *name1, kind, attributes)
.ToHandleChecked());
TransitionsAccessor::Insert(i_isolate(), map0, name2, map2,
SIMPLE_PROPERTY_TRANSITION);
{
TestTransitionsAccessor transitions(i_isolate(), map0);
CHECK(transitions.IsFullTransitionArrayEncoding());
}
CHECK_EQ(*map2, *TransitionsAccessor::SearchTransition(
i_isolate(), map0, *name2, kind, attributes)
.ToHandleChecked());
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_F(ConcurrentTransitionArrayTest, FullFieldTransitions_withSlack) {
v8::HandleScope scope(isolate());
Handle<String> name1 = MakeString("name1");
DirectHandle<String> name2 = MakeString("name2");
DirectHandle<String> name3 = MakeString("name3");
const PropertyAttributes attributes = NONE;
const PropertyKind kind = PropertyKind::kData;
// Set map0 to be a full transition array with transition 'name1' to map1.
Handle<Map> map0 = Map::Create(i_isolate(), 0);
Handle<Map> map1 =
Map::CopyWithField(i_isolate(), map0, name1, FieldType::Any(i_isolate()),
attributes, PropertyConstness::kMutable,
Representation::Tagged(), OMIT_TRANSITION)
.ToHandleChecked();
DirectHandle<Map> map2 =
Map::CopyWithField(i_isolate(), map0, name2, FieldType::Any(i_isolate()),
attributes, PropertyConstness::kMutable,
Representation::Tagged(), OMIT_TRANSITION)
.ToHandleChecked();
DirectHandle<Map> map3 =
Map::CopyWithField(i_isolate(), map0, name3, FieldType::Any(i_isolate()),
attributes, PropertyConstness::kMutable,
Representation::Tagged(), OMIT_TRANSITION)
.ToHandleChecked();
TransitionsAccessor::Insert(i_isolate(), map0, name1, map1,
PROPERTY_TRANSITION);
TransitionsAccessor::Insert(i_isolate(), map0, name2, map2,
PROPERTY_TRANSITION);
{
TestTransitionsAccessor transitions(i_isolate(), map0);
CHECK(transitions.IsFullTransitionArrayEncoding());
}
std::unique_ptr<PersistentHandles> ph = i_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(
i_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::SearchTransition(
i_isolate(), map0, *name1, kind, attributes)
.ToHandleChecked());
CHECK_EQ(*map2, *TransitionsAccessor::SearchTransition(
i_isolate(), map0, *name2, kind, attributes)
.ToHandleChecked());
{
// Check that we have enough slack for the 3rd insertion into the
// TransitionArray.
TestTransitionsAccessor transitions(i_isolate(), map0);
CHECK_GE(transitions.Capacity(), 3);
}
TransitionsAccessor::Insert(i_isolate(), map0, name3, map3,
PROPERTY_TRANSITION);
CHECK_EQ(*map3, *TransitionsAccessor::SearchTransition(
i_isolate(), map0, *name3, kind, attributes)
.ToHandleChecked());
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_F(ConcurrentTransitionArrayTest, UninitializedToFullFieldTransitions) {
v8::HandleScope scope(isolate());
DirectHandle<String> name1 = MakeString("name1");
Handle<String> name2 = MakeString("name2");
const PropertyAttributes attributes = NONE;
const PropertyKind kind = PropertyKind::kData;
// Set map0 to be a full transition array with transition 'name1' to map1.
Handle<Map> map0 = Map::Create(i_isolate(), 0);
DirectHandle<Map> map1 =
Map::CopyWithField(i_isolate(), map0, name1, FieldType::Any(i_isolate()),
attributes, PropertyConstness::kMutable,
Representation::Tagged(), OMIT_TRANSITION)
.ToHandleChecked();
{
TestTransitionsAccessor transitions(i_isolate(), map0);
CHECK(transitions.IsUninitializedEncoding());
}
std::unique_ptr<PersistentHandles> ph = i_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(
i_isolate()->heap(), &background_thread_started, std::move(ph),
persistent_name2, persistent_map0, std::nullopt));
CHECK(thread->Start());
background_thread_started.Wait();
TransitionsAccessor::Insert(i_isolate(), map0, name1, map1,
PROPERTY_TRANSITION);
CHECK_EQ(*map1, *TransitionsAccessor::SearchTransition(
i_isolate(), map0, *name1, kind, attributes)
.ToHandleChecked());
{
TestTransitionsAccessor transitions(i_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_F(ConcurrentTransitionArrayTest,
FullFieldTransitions_BackgroundSearchOldPointer) {
v8::HandleScope scope(isolate());
Handle<String> name1 = MakeString("name1");
DirectHandle<String> name2 = MakeString("name2");
const PropertyAttributes attributes = NONE;
const PropertyKind kind = PropertyKind::kData;
// Set map0 to be a full transition array with transition 'name1' to map1.
Handle<Map> map0 = Map::Create(i_isolate(), 0);
Handle<Map> map1 =
Map::CopyWithField(i_isolate(), map0, name1, FieldType::Any(i_isolate()),
attributes, PropertyConstness::kMutable,
Representation::Tagged(), OMIT_TRANSITION)
.ToHandleChecked();
DirectHandle<Map> map2 =
Map::CopyWithField(i_isolate(), map0, name2, FieldType::Any(i_isolate()),
attributes, PropertyConstness::kMutable,
Representation::Tagged(), OMIT_TRANSITION)
.ToHandleChecked();
TransitionsAccessor::Insert(i_isolate(), map0, name1, map1,
PROPERTY_TRANSITION);
{
TestTransitionsAccessor transitions(i_isolate(), map0);
CHECK(transitions.IsFullTransitionArrayEncoding());
}
std::unique_ptr<PersistentHandles> ph = i_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(
i_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::SearchTransition(
i_isolate(), map0, *name1, kind, attributes)
.ToHandleChecked());
{
// Check that we do not have enough slack for the 2nd insertion into the
// TransitionArray.
TestTransitionsAccessor transitions(i_isolate(), map0);
CHECK_EQ(transitions.Capacity(), 1);
}
TransitionsAccessor::Insert(i_isolate(), map0, name2, map2,
PROPERTY_TRANSITION);
CHECK_EQ(*map2, *TransitionsAccessor::SearchTransition(
i_isolate(), map0, *name2, kind, attributes)
.ToHandleChecked());
main_thread_finished.Signal();
thread->Join();
}
} // anonymous namespace
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,309 @@
// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/builtins/builtins-constructor.h"
#include "src/debug/debug.h"
#include "src/execution/execution.h"
#include "src/handles/global-handles.h"
#include "src/heap/factory.h"
#include "src/heap/spaces.h"
#include "src/init/v8.h"
#include "src/objects/hash-table-inl.h"
#include "src/objects/objects-inl.h"
#include "src/roots/roots.h"
#include "test/unittests/heap/heap-utils.h"
namespace v8 {
namespace internal {
class DictionaryTest : public TestWithHeapInternalsAndContext {
public:
template <typename HashMap>
void TestHashMap(Handle<HashMap> table) {
Factory* factory = isolate()->factory();
DirectHandle<JSObject> a = factory->NewJSArray(7);
DirectHandle<JSObject> b = factory->NewJSArray(11);
table = HashMap::Put(table, a, b);
CHECK_EQ(1, table->NumberOfElements());
CHECK_EQ(table->Lookup(a), *b);
// When the key does not exist in the map, Lookup returns the hole.
ReadOnlyRoots roots(heap());
CHECK_EQ(table->Lookup(b), roots.the_hole_value());
// Keys still have to be valid after objects were moved.
InvokeMinorGC();
CHECK_EQ(1, table->NumberOfElements());
CHECK_EQ(table->Lookup(a), *b);
CHECK_EQ(table->Lookup(b), roots.the_hole_value());
// Keys that are overwritten should not change number of elements.
table = HashMap::Put(table, a, factory->NewJSArray(13));
CHECK_EQ(1, table->NumberOfElements());
CHECK_NE(table->Lookup(a), *b);
// Keys that have been removed are mapped to the hole.
bool was_present = false;
table = HashMap::Remove(isolate(), table, a, &was_present);
CHECK(was_present);
CHECK_EQ(0, table->NumberOfElements());
CHECK_EQ(table->Lookup(a), roots.the_hole_value());
// Keys should map back to their respective values and also should get
// an identity hash code generated.
for (int i = 0; i < 100; i++) {
DirectHandle<JSReceiver> key = factory->NewJSArray(7);
DirectHandle<JSObject> value = factory->NewJSArray(11);
table = HashMap::Put(table, key, value);
CHECK_EQ(table->NumberOfElements(), i + 1);
CHECK(table->FindEntry(isolate(), key).is_found());
CHECK_EQ(table->Lookup(key), *value);
CHECK(IsSmi(key->GetIdentityHash()));
}
// Keys never added to the map which already have an identity hash
// code should not be found.
for (int i = 0; i < 100; i++) {
DirectHandle<JSReceiver> key = factory->NewJSArray(7);
CHECK(IsSmi(key->GetOrCreateIdentityHash(isolate())));
CHECK(table->FindEntry(isolate(), key).is_not_found());
CHECK_EQ(table->Lookup(key), roots.the_hole_value());
CHECK(IsSmi(key->GetIdentityHash()));
}
// Keys that don't have an identity hash should not be found and also
// should not get an identity hash code generated.
for (int i = 0; i < 100; i++) {
DirectHandle<JSReceiver> key = factory->NewJSArray(7);
CHECK_EQ(table->Lookup(key), roots.the_hole_value());
Tagged<Object> identity_hash = key->GetIdentityHash();
CHECK_EQ(roots.undefined_value(), identity_hash);
}
}
template <typename HashSet>
void TestHashSet(Handle<HashSet> table) {
Factory* factory = isolate()->factory();
DirectHandle<JSObject> a = factory->NewJSArray(7);
DirectHandle<JSObject> b = factory->NewJSArray(11);
table = HashSet::Add(isolate(), table, a);
CHECK_EQ(1, table->NumberOfElements());
CHECK(table->Has(isolate(), a));
CHECK(!table->Has(isolate(), b));
// Keys still have to be valid after objects were moved.
InvokeMinorGC();
CHECK_EQ(1, table->NumberOfElements());
CHECK(table->Has(isolate(), a));
CHECK(!table->Has(isolate(), b));
// Keys that are overwritten should not change number of elements.
table = HashSet::Add(isolate(), table, a);
CHECK_EQ(1, table->NumberOfElements());
CHECK(table->Has(isolate(), a));
CHECK(!table->Has(isolate(), b));
// Keys that have been removed are mapped to the hole.
// TODO(cbruni): not implemented yet.
// bool was_present = false;
// table = HashSet::Remove(table, a, &was_present);
// CHECK(was_present);
// CHECK_EQ(0, table->NumberOfElements());
// CHECK(!table->Has(a));
// CHECK(!table->Has(b));
// Keys should map back to their respective values and also should get
// an identity hash code generated.
for (int i = 0; i < 100; i++) {
DirectHandle<JSReceiver> key = factory->NewJSArray(7);
table = HashSet::Add(isolate(), table, key);
CHECK_EQ(table->NumberOfElements(), i + 2);
CHECK(table->Has(isolate(), key));
CHECK(IsSmi(key->GetIdentityHash()));
}
// Keys never added to the map which already have an identity hash
// code should not be found.
for (int i = 0; i < 100; i++) {
DirectHandle<JSReceiver> key = factory->NewJSArray(7);
CHECK(IsSmi(key->GetOrCreateIdentityHash(isolate())));
CHECK(!table->Has(isolate(), key));
CHECK(IsSmi(key->GetIdentityHash()));
}
// Keys that don't have an identity hash should not be found and also
// should not get an identity hash code generated.
for (int i = 0; i < 100; i++) {
DirectHandle<JSReceiver> key = factory->NewJSArray(7);
CHECK(!table->Has(isolate(), key));
Tagged<Object> identity_hash = key->GetIdentityHash();
CHECK_EQ(ReadOnlyRoots(heap()).undefined_value(), identity_hash);
}
}
#ifdef DEBUG
template <class HashSet>
void TestHashSetCausesGC(Handle<HashSet> table) {
Factory* factory = isolate()->factory();
Handle<JSObject> key = factory->NewJSArray(0);
// Simulate a full heap so that generating an identity hash code
// in subsequent calls will request GC.
SimulateFullSpace(heap()->new_space());
SimulateFullSpace(heap()->old_space());
// Calling Contains() should not cause GC ever.
int gc_count = heap()->gc_count();
CHECK(!table->Contains(key));
CHECK(gc_count == heap()->gc_count());
// Calling Remove() will not cause GC in this case.
bool was_present = false;
table = HashSet::Remove(table, key, &was_present);
CHECK(!was_present);
CHECK(gc_count == heap()->gc_count());
// Calling Add() should cause GC.
table = HashSet::Add(table, key);
CHECK(gc_count < heap()->gc_count());
}
#endif
#ifdef DEBUG
template <class HashMap>
void TestHashMapDoesNotCauseGC(Handle<HashMap> table) {
Factory* factory = isolate()->factory();
DirectHandle<JSObject> key = factory->NewJSArray(0);
// Even though we simulate a full heap, generating an identity hash
// code in subsequent calls will not request GC.
if (!v8_flags.single_generation) {
SimulateFullSpace(heap()->new_space());
}
SimulateFullSpace(heap()->old_space());
// Calling Lookup() should not cause GC ever.
CHECK(IsTheHole(table->Lookup(key), isolate()));
// Calling Put() should request GC by returning a failure.
int gc_count = heap()->gc_count();
HashMap::Put(table, key, key);
CHECK(gc_count == heap()->gc_count());
}
#endif
};
TEST_F(DictionaryTest, HashMap) {
TestHashMap(ObjectHashTable::New(isolate(), 23));
}
TEST_F(DictionaryTest, HashSet) {
TestHashSet(ObjectHashSet::New(isolate(), 23));
}
class ObjectHashTableTest {
public:
explicit ObjectHashTableTest(Tagged<ObjectHashTable> o) : table_(o) {}
// For every object, add a `->` operator which returns a pointer to this
// object. This will allow smoother transition between T and Tagged<T>.
ObjectHashTableTest* operator->() { return this; }
const ObjectHashTableTest* operator->() const { return this; }
void insert(InternalIndex entry, int key, int value) {
table_->set(table_->EntryToIndex(entry), Smi::FromInt(key));
table_->set(table_->EntryToIndex(entry) + 1, Smi::FromInt(value));
}
int lookup(int key, Isolate* isolate) {
DirectHandle<Object> key_obj(Smi::FromInt(key), isolate);
return Smi::ToInt(table_->Lookup(key_obj));
}
int capacity() { return table_->Capacity(); }
void Rehash(Isolate* isolate) { table_->Rehash(isolate); }
private:
Tagged<ObjectHashTable> table_;
};
TEST_F(DictionaryTest, HashTableRehash) {
// Test almost filled table.
{
DirectHandle<ObjectHashTable> table = ObjectHashTable::New(isolate(), 100);
ObjectHashTableTest t(*table);
int capacity = t->capacity();
for (int i = 0; i < capacity - 1; i++) {
t->insert(InternalIndex(i), i * i, i);
}
t->Rehash(isolate());
for (int i = 0; i < capacity - 1; i++) {
CHECK_EQ(i, t->lookup(i * i, isolate()));
}
}
// Test half-filled table.
{
DirectHandle<ObjectHashTable> table = ObjectHashTable::New(isolate(), 100);
ObjectHashTableTest t(*table);
int capacity = t->capacity();
for (int i = 0; i < capacity / 2; i++) {
t->insert(InternalIndex(i), i * i, i);
}
t->Rehash(isolate());
for (int i = 0; i < capacity / 2; i++) {
CHECK_EQ(i, t->lookup(i * i, isolate()));
}
}
}
#ifdef DEBUG
TEST_F(DictionaryTest, ObjectHashTableCausesGC) {
i::v8_flags.stress_compaction = false;
// For SimulateFullSpace in TestHashMapDoesNotCauseGC.
i::v8_flags.stress_concurrent_allocation = false;
TestHashMapDoesNotCauseGC(ObjectHashTable::New(isolate(), 1));
}
#endif
TEST_F(DictionaryTest, MaximumClonedShallowObjectProperties) {
// Assert that a NameDictionary with kMaximumClonedShallowObjectProperties is
// not in large-object space.
const int max_capacity = NameDictionary::ComputeCapacity(
ConstructorBuiltins::kMaximumClonedShallowObjectProperties);
const InternalIndex max_literal_entry(max_capacity /
NameDictionary::kEntrySize);
const int max_literal_index = NameDictionary::EntryToIndex(max_literal_entry);
CHECK_LE(NameDictionary::OffsetOfElementAt(max_literal_index),
kMaxRegularHeapObjectSize);
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,524 @@
// Copyright 2015 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 <stdlib.h>
#include <utility>
#include "src/codegen/compilation-cache.h"
#include "src/execution/execution.h"
#include "src/handles/global-handles.h"
#include "src/heap/factory.h"
#include "src/ic/stub-cache.h"
#include "src/init/v8.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
using ElementsKindTest = TestWithContext;
//
// Helper functions.
//
namespace {
template <typename T, typename M, template <typename> typename HandleType1,
template <typename> typename HandleType2>
requires(
std::conjunction_v<std::is_convertible<HandleType1<T>, DirectHandle<T>>,
std::is_convertible<HandleType2<M>, DirectHandle<M>>>)
bool EQUALS(Isolate* isolate, HandleType1<T> left, HandleType2<M> right) {
if (*left == *right) return true;
return Object::Equals(isolate, Cast<Object>(left), Cast<Object>(right))
.FromJust();
}
template <typename T, typename M, template <typename> typename HandleType>
requires(std::is_convertible_v<HandleType<T>, DirectHandle<T>>)
bool EQUALS(Isolate* isolate, HandleType<T> left, M right) {
return EQUALS(isolate, left, direct_handle(right, isolate));
}
template <typename T, typename M, template <typename> typename HandleType>
requires(std::is_convertible_v<HandleType<M>, DirectHandle<M>>)
bool EQUALS(Isolate* isolate, T left, HandleType<M> right) {
return EQUALS(isolate, direct_handle(left, isolate), right);
}
bool ElementsKindIsHoleyElementsKindForRead(ElementsKind kind) {
switch (kind) {
case ElementsKind::HOLEY_SMI_ELEMENTS:
case ElementsKind::HOLEY_ELEMENTS:
case ElementsKind::HOLEY_DOUBLE_ELEMENTS:
case ElementsKind::HOLEY_NONEXTENSIBLE_ELEMENTS:
case ElementsKind::HOLEY_SEALED_ELEMENTS:
case ElementsKind::HOLEY_FROZEN_ELEMENTS:
return true;
default:
return false;
}
}
bool ElementsKindIsHoleyElementsKind(ElementsKind kind) {
switch (kind) {
case ElementsKind::HOLEY_SMI_ELEMENTS:
case ElementsKind::HOLEY_ELEMENTS:
case ElementsKind::HOLEY_DOUBLE_ELEMENTS:
return true;
default:
return false;
}
}
bool ElementsKindIsFastPackedElementsKind(ElementsKind kind) {
switch (kind) {
case ElementsKind::PACKED_SMI_ELEMENTS:
case ElementsKind::PACKED_ELEMENTS:
case ElementsKind::PACKED_DOUBLE_ELEMENTS:
return true;
default:
return false;
}
}
} // namespace
//
// Tests
//
TEST_F(ElementsKindTest, SystemPointerElementsKind) {
CHECK_EQ(ElementsKindToShiftSize(SYSTEM_POINTER_ELEMENTS),
kSystemPointerSizeLog2);
CHECK_EQ(ElementsKindToByteSize(SYSTEM_POINTER_ELEMENTS), kSystemPointerSize);
}
TEST_F(ElementsKindTest, JSObjectAddingProperties) {
Factory* factory = i_isolate()->factory();
v8::HandleScope scope(isolate());
Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array());
Handle<PropertyArray> empty_property_array(factory->empty_property_array());
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(factory->empty_string());
DirectHandle<Object> value(Smi::FromInt(42), i_isolate());
DirectHandle<JSObject> object = factory->NewJSObject(function);
DirectHandle<Map> previous_map(object->map(), i_isolate());
CHECK_EQ(HOLEY_ELEMENTS, previous_map->elements_kind());
CHECK(EQUALS(i_isolate(), object->property_array(), empty_property_array));
CHECK(EQUALS(i_isolate(), object->elements(), empty_fixed_array));
// For the default constructor function no in-object properties are reserved
// hence adding a single property will initialize the property-array.
DirectHandle<String> name = MakeName("property", 0);
JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE)
.Check();
CHECK_NE(object->map(), *previous_map);
CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
CHECK_LE(1, object->property_array()->length());
CHECK(EQUALS(i_isolate(), object->elements(), empty_fixed_array));
}
TEST_F(ElementsKindTest, JSObjectInObjectAddingProperties) {
Factory* factory = i_isolate()->factory();
v8::HandleScope scope(isolate());
Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array());
Handle<PropertyArray> empty_property_array(factory->empty_property_array());
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(factory->empty_string());
int nof_inobject_properties = 10;
// Force in object properties by changing the expected_nof_properties
// (we always reserve 8 inobject properties slack on top).
function->shared()->set_expected_nof_properties(nof_inobject_properties - 8);
DirectHandle<Object> value(Smi::FromInt(42), i_isolate());
DirectHandle<JSObject> object = factory->NewJSObject(function);
DirectHandle<Map> previous_map(object->map(), i_isolate());
CHECK_EQ(HOLEY_ELEMENTS, previous_map->elements_kind());
CHECK(EQUALS(i_isolate(), object->property_array(), empty_property_array));
CHECK(EQUALS(i_isolate(), object->elements(), empty_fixed_array));
// We have reserved space for in-object properties, hence adding up to
// |nof_inobject_properties| will not create a property store.
for (int i = 0; i < nof_inobject_properties; i++) {
DirectHandle<String> name = MakeName("property", i);
JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE)
.Check();
}
CHECK_NE(object->map(), *previous_map);
CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
CHECK(EQUALS(i_isolate(), object->property_array(), empty_property_array));
CHECK(EQUALS(i_isolate(), object->elements(), empty_fixed_array));
// Adding one more property will not fit in the in-object properties, thus
// creating a property store.
int index = nof_inobject_properties + 1;
DirectHandle<String> name = MakeName("property", index);
JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE)
.Check();
CHECK_NE(object->map(), *previous_map);
CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
// There must be at least 1 element in the properties store.
CHECK_LE(1, object->property_array()->length());
CHECK(EQUALS(i_isolate(), object->elements(), empty_fixed_array));
}
TEST_F(ElementsKindTest, JSObjectAddingElements) {
Factory* factory = i_isolate()->factory();
v8::HandleScope scope(isolate());
DirectHandle<String> name;
Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array());
Handle<PropertyArray> empty_property_array(factory->empty_property_array());
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(factory->empty_string());
DirectHandle<Object> value(Smi::FromInt(42), i_isolate());
DirectHandle<JSObject> object = factory->NewJSObject(function);
DirectHandle<Map> previous_map(object->map(), i_isolate());
CHECK_EQ(HOLEY_ELEMENTS, previous_map->elements_kind());
CHECK(EQUALS(i_isolate(), object->property_array(), empty_property_array));
CHECK(EQUALS(i_isolate(), object->elements(), empty_fixed_array));
// Adding an indexed element initializes the elements array.
name = MakeString("0");
JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE)
.Check();
// No change in elements_kind => no map transition.
CHECK_EQ(object->map(), *previous_map);
CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
CHECK(EQUALS(i_isolate(), object->property_array(), empty_property_array));
CHECK_LE(1, object->elements()->length());
// Adding more consecutive elements without a change in the backing store.
int non_dict_backing_store_limit = 100;
for (int i = 1; i < non_dict_backing_store_limit; i++) {
name = MakeName("", i);
JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE)
.Check();
}
// No change in elements_kind => no map transition.
CHECK_EQ(object->map(), *previous_map);
CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
CHECK(EQUALS(i_isolate(), object->property_array(), empty_property_array));
CHECK_LE(non_dict_backing_store_limit, object->elements()->length());
// Adding an element at an very large index causes a change to
// DICTIONARY_ELEMENTS.
name = MakeString("100000000");
JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE)
.Check();
// Change in elements_kind => map transition.
CHECK_NE(object->map(), *previous_map);
CHECK_EQ(DICTIONARY_ELEMENTS, object->map()->elements_kind());
CHECK(EQUALS(i_isolate(), object->property_array(), empty_property_array));
CHECK_LE(non_dict_backing_store_limit, object->elements()->length());
}
TEST_F(ElementsKindTest, JSArrayAddingProperties) {
Factory* factory = i_isolate()->factory();
v8::HandleScope scope(isolate());
Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array());
Handle<PropertyArray> empty_property_array(factory->empty_property_array());
DirectHandle<Object> value(Smi::FromInt(42), i_isolate());
DirectHandle<JSArray> array =
factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
DirectHandle<Map> previous_map(array->map(), i_isolate());
CHECK_EQ(PACKED_SMI_ELEMENTS, previous_map->elements_kind());
CHECK(EQUALS(i_isolate(), array->property_array(), empty_property_array));
CHECK(EQUALS(i_isolate(), array->elements(), empty_fixed_array));
CHECK_EQ(0, Smi::ToInt(array->length()));
// For the default constructor function no in-object properties are reserved
// hence adding a single property will initialize the property-array.
DirectHandle<String> name = MakeName("property", 0);
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE)
.Check();
// No change in elements_kind but added property => new map.
CHECK_NE(array->map(), *previous_map);
CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
CHECK_LE(1, array->property_array()->length());
CHECK(EQUALS(i_isolate(), array->elements(), empty_fixed_array));
CHECK_EQ(0, Smi::ToInt(array->length()));
}
TEST_F(ElementsKindTest, JSArrayAddingElements) {
Factory* factory = i_isolate()->factory();
v8::HandleScope scope(isolate());
DirectHandle<String> name;
Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array());
Handle<PropertyArray> empty_property_array(factory->empty_property_array());
DirectHandle<Object> value(Smi::FromInt(42), i_isolate());
DirectHandle<JSArray> array =
factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
DirectHandle<Map> previous_map(array->map(), i_isolate());
CHECK_EQ(PACKED_SMI_ELEMENTS, previous_map->elements_kind());
CHECK(EQUALS(i_isolate(), array->property_array(), empty_property_array));
CHECK(EQUALS(i_isolate(), array->elements(), empty_fixed_array));
CHECK_EQ(0, Smi::ToInt(array->length()));
// Adding an indexed element initializes the elements array.
name = MakeString("0");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE)
.Check();
// No change in elements_kind => no map transition.
CHECK_EQ(array->map(), *previous_map);
CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
CHECK(EQUALS(i_isolate(), array->property_array(), empty_property_array));
CHECK_LE(1, array->elements()->length());
CHECK_EQ(1, Smi::ToInt(array->length()));
// Adding more consecutive elements without a change in the backing store.
int non_dict_backing_store_limit = 100;
for (int i = 1; i < non_dict_backing_store_limit; i++) {
name = MakeName("", i);
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE)
.Check();
}
// No change in elements_kind => no map transition.
CHECK_EQ(array->map(), *previous_map);
CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
CHECK(EQUALS(i_isolate(), array->property_array(), empty_property_array));
CHECK_LE(non_dict_backing_store_limit, array->elements()->length());
CHECK_EQ(non_dict_backing_store_limit, Smi::ToInt(array->length()));
// Adding an element at an very large index causes a change to
// DICTIONARY_ELEMENTS.
int index = 100000000;
name = MakeName("", index);
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE)
.Check();
// Change in elements_kind => map transition.
CHECK_NE(array->map(), *previous_map);
CHECK_EQ(DICTIONARY_ELEMENTS, array->map()->elements_kind());
CHECK(EQUALS(i_isolate(), array->property_array(), empty_property_array));
CHECK_LE(non_dict_backing_store_limit, array->elements()->length());
CHECK_LE(array->elements()->length(), index);
CHECK_EQ(index + 1, Smi::ToInt(array->length()));
}
TEST_F(ElementsKindTest, JSArrayAddingElementsGeneralizingiFastSmiElements) {
Factory* factory = i_isolate()->factory();
v8::HandleScope scope(isolate());
DirectHandle<String> name;
DirectHandle<Object> value_smi(Smi::FromInt(42), i_isolate());
DirectHandle<Object> value_string(MakeString("value"));
DirectHandle<Object> value_double = factory->NewNumber(3.1415);
DirectHandle<JSArray> array =
factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
DirectHandle<Map> previous_map(array->map(), i_isolate());
CHECK_EQ(PACKED_SMI_ELEMENTS, previous_map->elements_kind());
CHECK_EQ(0, Smi::ToInt(array->length()));
// `array[0] = smi_value` doesn't change the elements_kind.
name = MakeString("0");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
NONE)
.Check();
// no change in elements_kind => no map transition
CHECK_EQ(array->map(), *previous_map);
CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
CHECK_EQ(1, Smi::ToInt(array->length()));
// `delete array[0]` does not alter length, but changes the elements_kind.
name = MakeString("0");
CHECK(JSReceiver::DeletePropertyOrElement(i_isolate(), array, name)
.FromMaybe(false));
CHECK_NE(array->map(), *previous_map);
CHECK_EQ(HOLEY_SMI_ELEMENTS, array->map()->elements_kind());
CHECK_EQ(1, Smi::ToInt(array->length()));
previous_map = direct_handle(array->map(), i_isolate());
// Add a couple of elements again.
name = MakeString("0");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
NONE)
.Check();
name = MakeString("1");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
NONE)
.Check();
CHECK_EQ(array->map(), *previous_map);
CHECK_EQ(HOLEY_SMI_ELEMENTS, array->map()->elements_kind());
CHECK_EQ(2, Smi::ToInt(array->length()));
// Adding a string to the array changes from FAST_HOLEY_SMI to FAST_HOLEY.
name = MakeString("0");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string,
NONE)
.Check();
CHECK_NE(array->map(), *previous_map);
CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
CHECK_EQ(2, Smi::ToInt(array->length()));
previous_map = direct_handle(array->map(), i_isolate());
// We don't transition back to FAST_SMI even if we remove the string.
name = MakeString("0");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
NONE)
.Check();
CHECK_EQ(array->map(), *previous_map);
// Adding a double doesn't change the map either.
name = MakeString("0");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double,
NONE)
.Check();
CHECK_EQ(array->map(), *previous_map);
}
TEST_F(ElementsKindTest, JSArrayAddingElementsGeneralizingFastElements) {
Factory* factory = i_isolate()->factory();
v8::HandleScope scope(isolate());
DirectHandle<String> name;
DirectHandle<Object> value_smi(Smi::FromInt(42), i_isolate());
DirectHandle<Object> value_string(MakeString("value"));
DirectHandle<JSArray> array =
factory->NewJSArray(ElementsKind::PACKED_ELEMENTS, 0, 0);
DirectHandle<Map> previous_map(array->map(), i_isolate());
CHECK_EQ(PACKED_ELEMENTS, previous_map->elements_kind());
CHECK_EQ(0, Smi::ToInt(array->length()));
// `array[0] = smi_value` doesn't change the elements_kind.
name = MakeString("0");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
NONE)
.Check();
// no change in elements_kind => no map transition
CHECK_EQ(array->map(), *previous_map);
CHECK_EQ(PACKED_ELEMENTS, array->map()->elements_kind());
CHECK_EQ(1, Smi::ToInt(array->length()));
// `delete array[0]` does not alter length, but changes the elements_kind.
name = MakeString("0");
CHECK(JSReceiver::DeletePropertyOrElement(i_isolate(), array, name)
.FromMaybe(false));
CHECK_NE(array->map(), *previous_map);
CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
CHECK_EQ(1, Smi::ToInt(array->length()));
previous_map = direct_handle(array->map(), i_isolate());
// Add a couple of elements, elements_kind stays HOLEY.
name = MakeString("0");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string,
NONE)
.Check();
name = MakeString("1");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
NONE)
.Check();
CHECK_EQ(array->map(), *previous_map);
CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
CHECK_EQ(2, Smi::ToInt(array->length()));
}
TEST_F(ElementsKindTest, JSArrayAddingElementsGeneralizingiFastDoubleElements) {
Factory* factory = i_isolate()->factory();
v8::HandleScope scope(isolate());
DirectHandle<String> name;
DirectHandle<Object> value_smi(Smi::FromInt(42), i_isolate());
DirectHandle<Object> value_string(MakeString("value"));
DirectHandle<Object> value_double = factory->NewNumber(3.1415);
DirectHandle<JSArray> array =
factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
DirectHandle<Map> previous_map(array->map(), i_isolate());
// `array[0] = value_double` changes |elements_kind| to
// PACKED_DOUBLE_ELEMENTS.
name = MakeString("0");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double,
NONE)
.Check();
CHECK_NE(array->map(), *previous_map);
CHECK_EQ(PACKED_DOUBLE_ELEMENTS, array->map()->elements_kind());
CHECK_EQ(1, Smi::ToInt(array->length()));
previous_map = direct_handle(array->map(), i_isolate());
// `array[1] = value_smi` doesn't alter the |elements_kind|.
name = MakeString("1");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
NONE)
.Check();
CHECK_EQ(array->map(), *previous_map);
CHECK_EQ(PACKED_DOUBLE_ELEMENTS, array->map()->elements_kind());
CHECK_EQ(2, Smi::ToInt(array->length()));
// `delete array[0]` does not alter length, but changes the elements_kind.
name = MakeString("0");
CHECK(JSReceiver::DeletePropertyOrElement(i_isolate(), array, name)
.FromMaybe(false));
CHECK_NE(array->map(), *previous_map);
CHECK_EQ(HOLEY_DOUBLE_ELEMENTS, array->map()->elements_kind());
CHECK_EQ(2, Smi::ToInt(array->length()));
previous_map = direct_handle(array->map(), i_isolate());
// Filling the hole `array[0] = value_smi` again doesn't transition back.
name = MakeString("0");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double,
NONE)
.Check();
CHECK_EQ(array->map(), *previous_map);
CHECK_EQ(HOLEY_DOUBLE_ELEMENTS, array->map()->elements_kind());
CHECK_EQ(2, Smi::ToInt(array->length()));
// Adding a string to the array changes to elements_kind PACKED_ELEMENTS.
name = MakeString("1");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string,
NONE)
.Check();
CHECK_NE(array->map(), *previous_map);
CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
CHECK_EQ(2, Smi::ToInt(array->length()));
previous_map = direct_handle(array->map(), i_isolate());
// Adding a double doesn't change the map.
name = MakeString("0");
JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double,
NONE)
.Check();
CHECK_EQ(array->map(), *previous_map);
}
TEST_F(ElementsKindTest, IsHoleyElementsKindForRead) {
for (int i = 0; i <= ElementsKind::LAST_ELEMENTS_KIND; i++) {
ElementsKind kind = static_cast<ElementsKind>(i);
CHECK_EQ(ElementsKindIsHoleyElementsKindForRead(kind),
IsHoleyElementsKindForRead(kind));
}
}
TEST_F(ElementsKindTest, IsHoleyElementsKind) {
for (int i = 0; i <= ElementsKind::LAST_ELEMENTS_KIND; i++) {
ElementsKind kind = static_cast<ElementsKind>(i);
CHECK_EQ(ElementsKindIsHoleyElementsKind(kind), IsHoleyElementsKind(kind));
}
}
TEST_F(ElementsKindTest, IsFastPackedElementsKind) {
for (int i = 0; i <= ElementsKind::LAST_ELEMENTS_KIND; i++) {
ElementsKind kind = static_cast<ElementsKind>(i);
CHECK_EQ(ElementsKindIsFastPackedElementsKind(kind),
IsFastPackedElementsKind(kind));
}
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,788 @@
// Copyright 2022 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-inl.h"
#include "src/execution/execution.h"
#include "src/heap/factory.h"
#include "src/objects/feedback-cell-inl.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/test-utils.h"
namespace v8 {
namespace internal {
class FeedbackVectorTest : public TestWithContext {
protected:
DirectHandle<JSFunction> GetFunction(const char* name) {
v8::MaybeLocal<v8::Value> v8_f =
v8_context()->Global()->Get(v8_context(), NewString(name));
DirectHandle<JSFunction> f =
Cast<JSFunction>(v8::Utils::OpenHandle(*v8_f.ToLocalChecked()));
return f;
}
};
#define CHECK_SLOT_KIND(helper, index, expected_kind) \
CHECK_EQ(expected_kind, helper.vector()->GetKind(helper.slot(index)));
TEST_F(FeedbackVectorTest, VectorStructure) {
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
Factory* factory = isolate->factory();
Zone zone(isolate->allocator(), ZONE_NAME);
Handle<FeedbackVector> vector;
{
FeedbackVectorSpec one_slot(&zone);
one_slot.AddForInSlot();
vector = NewFeedbackVector(isolate, &one_slot);
FeedbackVectorHelper helper(vector);
CHECK_EQ(1, helper.slot_count());
}
{
FeedbackVectorSpec one_icslot(&zone);
one_icslot.AddCallICSlot();
vector = NewFeedbackVector(isolate, &one_icslot);
FeedbackVectorHelper helper(vector);
CHECK_EQ(1, helper.slot_count());
}
{
FeedbackVectorSpec spec(&zone);
for (int i = 0; i < 3; i++) {
spec.AddForInSlot();
}
for (int i = 0; i < 5; i++) {
spec.AddCallICSlot();
}
vector = NewFeedbackVector(isolate, &spec);
FeedbackVectorHelper helper(vector);
CHECK_EQ(8, helper.slot_count());
int index = vector->GetIndex(helper.slot(0));
CHECK_EQ(helper.slot(0), vector->ToSlot(index));
index = vector->GetIndex(helper.slot(3));
CHECK_EQ(helper.slot(3), vector->ToSlot(index));
index = vector->GetIndex(helper.slot(7));
CHECK_EQ(3 + 4 * FeedbackMetadata::GetSlotSize(FeedbackSlotKind::kCall),
index);
CHECK_EQ(helper.slot(7), vector->ToSlot(index));
CHECK_EQ(3 + 5 * FeedbackMetadata::GetSlotSize(FeedbackSlotKind::kCall),
vector->length());
}
{
FeedbackVectorSpec spec(&zone);
spec.AddForInSlot();
spec.AddCreateClosureParameterCount(0);
spec.AddForInSlot();
vector = NewFeedbackVector(isolate, &spec);
FeedbackVectorHelper helper(vector);
Tagged<FeedbackCell> cell = vector->closure_feedback_cell(0);
CHECK_EQ(cell->value(), *factory->undefined_value());
}
}
// IC slots need an encoding to recognize what is in there.
TEST_F(FeedbackVectorTest, VectorICMetadata) {
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
Zone zone(isolate->allocator(), ZONE_NAME);
FeedbackVectorSpec spec(&zone);
// Set metadata.
for (int i = 0; i < 40; i++) {
switch (i % 4) {
case 0:
spec.AddForInSlot();
break;
case 1:
spec.AddCallICSlot();
break;
case 2:
spec.AddLoadICSlot();
break;
case 3:
spec.AddKeyedLoadICSlot();
break;
}
}
Handle<FeedbackVector> vector = NewFeedbackVector(isolate, &spec);
FeedbackVectorHelper helper(vector);
CHECK_EQ(40, helper.slot_count());
// Meanwhile set some feedback values and type feedback values to
// verify the data structure remains intact.
vector->SynchronizedSet(FeedbackSlot(0), *vector);
// Verify the metadata is correctly set up from the spec.
for (int i = 0; i < 40; i++) {
FeedbackSlotKind kind = vector->GetKind(helper.slot(i));
switch (i % 4) {
case 0:
CHECK_EQ(FeedbackSlotKind::kForIn, kind);
break;
case 1:
CHECK_EQ(FeedbackSlotKind::kCall, kind);
break;
case 2:
CHECK_EQ(FeedbackSlotKind::kLoadProperty, kind);
break;
case 3:
CHECK_EQ(FeedbackSlotKind::kLoadKeyed, kind);
break;
}
}
}
TEST_F(FeedbackVectorTest, VectorCallICStates) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
// Make sure function f has a call that uses a type feedback slot.
TryRunJS(
"function foo() { return 17; };"
"%EnsureFeedbackVectorForFunction(f);"
"function f(a) { a(); } f(foo);");
DirectHandle<JSFunction> f = GetFunction("f");
// There should be one IC.
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(f->feedback_vector(), isolate);
FeedbackSlot slot(0);
FeedbackNexus nexus(isolate, feedback_vector, slot);
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
TryRunJS("f(function() { return 16; })");
CHECK_EQ(InlineCacheState::GENERIC, nexus.ic_state());
// After a collection, state should remain GENERIC.
InvokeMajorGC();
CHECK_EQ(InlineCacheState::GENERIC, nexus.ic_state());
}
// Test the Call IC states transfer with Function.prototype.apply
TEST_F(FeedbackVectorTest, VectorCallICStateApply) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
// Make sure function f has a call that uses a type feedback slot.
TryRunJS(
"var F;"
"%EnsureFeedbackVectorForFunction(foo);"
"function foo() { return F.apply(null, arguments); }"
"F = Math.min;"
"foo();");
DirectHandle<JSFunction> foo = GetFunction("foo");
DirectHandle<JSFunction> F = GetFunction("F");
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(foo->feedback_vector(), isolate);
FeedbackSlot slot(4);
FeedbackNexus nexus(i_isolate(), feedback_vector, slot);
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
CHECK_EQ(CallFeedbackContent::kReceiver, nexus.GetCallFeedbackContent());
Tagged<HeapObject> heap_object;
CHECK(nexus.GetFeedback().GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(*F, heap_object);
TryRunJS(
"F = Math.max;"
"foo();");
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
CHECK_EQ(CallFeedbackContent::kTarget, nexus.GetCallFeedbackContent());
CHECK(nexus.GetFeedback().GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(*isolate->function_prototype_apply(), heap_object);
TryRunJS(
"F.apply = (function () { return; });"
"foo();");
CHECK_EQ(InlineCacheState::GENERIC, nexus.ic_state());
}
TEST_F(FeedbackVectorTest, VectorCallFeedback) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
// Make sure function f has a call that uses a type feedback slot.
TryRunJS(
"function foo() { return 17; }"
"%EnsureFeedbackVectorForFunction(f);"
"function f(a) { a(); } f(foo);");
DirectHandle<JSFunction> f = GetFunction("f");
DirectHandle<JSFunction> foo = GetFunction("foo");
// There should be one IC.
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(f->feedback_vector(), isolate);
FeedbackSlot slot(0);
FeedbackNexus nexus(i_isolate(), feedback_vector, slot);
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
Tagged<HeapObject> heap_object;
CHECK(nexus.GetFeedback().GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(*foo, heap_object);
InvokeMajorGC();
// It should stay monomorphic even after a GC.
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
}
TEST_F(FeedbackVectorTest, VectorPolymorphicCallFeedback) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8_flags.lazy_feedback_allocation = false;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
// Make sure the call feedback of a() in f() becomes polymorphic.
TryRunJS(
"function foo_maker() { return () => { return 17; } }"
"a_foo = foo_maker();"
"function f(a) { a(); } f(foo_maker());"
"f(foo_maker());");
DirectHandle<JSFunction> f = GetFunction("f");
DirectHandle<JSFunction> a_foo = GetFunction("a_foo");
// There should be one IC.
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(f->feedback_vector(), isolate);
FeedbackSlot slot(0);
FeedbackNexus nexus(i_isolate(), feedback_vector, slot);
CHECK_EQ(InlineCacheState::POLYMORPHIC, nexus.ic_state());
Tagged<HeapObject> heap_object;
CHECK(nexus.GetFeedback().GetHeapObjectIfWeak(&heap_object));
CHECK(IsFeedbackCell(heap_object, isolate));
// Ensure this is the feedback cell for the closure returned by
// foo_maker.
CHECK_EQ(heap_object, a_foo->raw_feedback_cell());
}
TEST_F(FeedbackVectorTest, VectorCallFeedbackForArray) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
// Make sure function f has a call that uses a type feedback slot.
TryRunJS(
"function f(a) { a(); };"
"%EnsureFeedbackVectorForFunction(f);"
"f(Array);");
DirectHandle<JSFunction> f = GetFunction("f");
// There should be one IC.
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(f->feedback_vector(), isolate);
FeedbackSlot slot(0);
FeedbackNexus nexus(i_isolate(), feedback_vector, slot);
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
Tagged<HeapObject> heap_object;
CHECK(nexus.GetFeedback().GetHeapObjectIfWeak(&heap_object));
CHECK_EQ(*isolate->array_function(), heap_object);
InvokeMajorGC();
// It should stay monomorphic even after a GC.
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
}
TEST_F(FeedbackVectorTest, VectorCallCounts) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
// Make sure function f has a call that uses a type feedback slot.
TryRunJS(
"function foo() { return 17; }"
"%EnsureFeedbackVectorForFunction(f);"
"function f(a) { a(); } f(foo);");
DirectHandle<JSFunction> f = GetFunction("f");
// There should be one IC.
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(f->feedback_vector(), isolate);
FeedbackSlot slot(0);
FeedbackNexus nexus(i_isolate(), feedback_vector, slot);
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
TryRunJS("f(foo); f(foo);");
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
CHECK_EQ(3, nexus.GetCallCount());
// Send the IC megamorphic, but we should still have incrementing counts.
TryRunJS("f(function() { return 12; });");
CHECK_EQ(InlineCacheState::GENERIC, nexus.ic_state());
CHECK_EQ(4, nexus.GetCallCount());
}
TEST_F(FeedbackVectorTest, VectorConstructCounts) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
// Make sure function f has a call that uses a type feedback slot.
TryRunJS(
"function Foo() {}"
"%EnsureFeedbackVectorForFunction(f);"
"function f(a) { new a(); } f(Foo);");
DirectHandle<JSFunction> f = GetFunction("f");
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(f->feedback_vector(), isolate);
FeedbackSlot slot(0);
FeedbackNexus nexus(i_isolate(), feedback_vector, slot);
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
CHECK(feedback_vector->Get(slot).IsWeak());
TryRunJS("f(Foo); f(Foo);");
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
CHECK_EQ(3, nexus.GetCallCount());
// Send the IC megamorphic, but we should still have incrementing counts.
TryRunJS("f(function() {});");
CHECK_EQ(InlineCacheState::GENERIC, nexus.ic_state());
CHECK_EQ(4, nexus.GetCallCount());
}
TEST_F(FeedbackVectorTest, VectorSpeculationMode) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
// Make sure function f has a call that uses a type feedback slot.
TryRunJS(
"function Foo() {}"
"%EnsureFeedbackVectorForFunction(f);"
"function f(a) { new a(); } f(Foo);");
DirectHandle<JSFunction> f = GetFunction("f");
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(f->feedback_vector(), isolate);
FeedbackSlot slot(0);
FeedbackNexus nexus(i_isolate(), feedback_vector, slot);
CHECK_EQ(SpeculationMode::kAllowSpeculation, nexus.GetSpeculationMode());
TryRunJS("f(Foo); f(Foo);");
CHECK_EQ(3, nexus.GetCallCount());
CHECK_EQ(SpeculationMode::kAllowSpeculation, nexus.GetSpeculationMode());
nexus.SetSpeculationMode(SpeculationMode::kDisallowSpeculation);
CHECK_EQ(SpeculationMode::kDisallowSpeculation, nexus.GetSpeculationMode());
CHECK_EQ(3, nexus.GetCallCount());
nexus.SetSpeculationMode(SpeculationMode::kAllowSpeculation);
CHECK_EQ(SpeculationMode::kAllowSpeculation, nexus.GetSpeculationMode());
CHECK_EQ(3, nexus.GetCallCount());
}
TEST_F(FeedbackVectorTest, VectorCallSpeculationModeAndFeedbackContent) {
if (!i::v8_flags.use_ic) return;
if (!i::v8_flags.turbofan) return;
if (i::v8_flags.always_turbofan) return;
if (i::v8_flags.jitless) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
TryRunJS(
"function min() { return Math.min.apply(null, arguments); }"
"function f(x) { return min(x, 0); }"
"%PrepareFunctionForOptimization(min);"
"%PrepareFunctionForOptimization(f);"
"f(1);");
DirectHandle<JSFunction> min = GetFunction("min");
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(min->feedback_vector(), isolate);
FeedbackSlot slot(6);
FeedbackNexus nexus(i_isolate(), feedback_vector, slot);
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
CHECK_EQ(SpeculationMode::kAllowSpeculation, nexus.GetSpeculationMode());
CHECK_EQ(CallFeedbackContent::kReceiver, nexus.GetCallFeedbackContent());
TryRunJS("%OptimizeFunctionOnNextCall(f); f(1);");
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
CHECK_EQ(SpeculationMode::kAllowSpeculation, nexus.GetSpeculationMode());
CHECK_EQ(CallFeedbackContent::kReceiver, nexus.GetCallFeedbackContent());
TryRunJS("f({});"); // Deoptimizes.
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
CHECK_EQ(SpeculationMode::kDisallowSpeculation, nexus.GetSpeculationMode());
CHECK_EQ(CallFeedbackContent::kReceiver, nexus.GetCallFeedbackContent());
}
TEST_F(FeedbackVectorTest, VectorLoadICStates) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
// Make sure function f has a call that uses a type feedback slot.
TryRunJS(
"var o = { foo: 3 };"
"%EnsureFeedbackVectorForFunction(f);"
"function f(a) { return a.foo; } f(o);");
DirectHandle<JSFunction> f = GetFunction("f");
// There should be one IC.
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(f->feedback_vector(), isolate);
FeedbackSlot slot(0);
FeedbackNexus nexus(i_isolate(), feedback_vector, slot);
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
// Verify that the monomorphic map is the one we expect.
v8::MaybeLocal<v8::Value> v8_o =
v8_context()->Global()->Get(v8_context(), NewString("o"));
DirectHandle<JSObject> o =
Cast<JSObject>(v8::Utils::OpenDirectHandle(*v8_o.ToLocalChecked()));
CHECK_EQ(o->map(), nexus.GetFirstMap());
// Now go polymorphic.
TryRunJS("f({ blarg: 3, foo: 2 })");
CHECK_EQ(InlineCacheState::POLYMORPHIC, nexus.ic_state());
TryRunJS(
"delete o.foo;"
"f(o)");
CHECK_EQ(InlineCacheState::POLYMORPHIC, nexus.ic_state());
TryRunJS("f({ blarg: 3, torino: 10, foo: 2 })");
CHECK_EQ(InlineCacheState::POLYMORPHIC, nexus.ic_state());
MapHandles maps(isolate);
nexus.ExtractMaps(&maps);
CHECK_EQ(4, maps.size());
// Finally driven megamorphic.
TryRunJS("f({ blarg: 3, gran: 3, torino: 10, foo: 2 })");
CHECK_EQ(InlineCacheState::MEGAMORPHIC, nexus.ic_state());
CHECK(nexus.GetFirstMap().is_null());
// After a collection, state should not be reset to PREMONOMORPHIC.
InvokeMajorGC();
CHECK_EQ(InlineCacheState::MEGAMORPHIC, nexus.ic_state());
}
TEST_F(FeedbackVectorTest, VectorLoadGlobalICSlotSharing) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
// Function f has 5 LoadGlobalICs: 3 for {o} references outside of "typeof"
// operator and 2 for {o} references inside "typeof" operator.
TryRunJS(
"o = 10;"
"function f() {"
" var x = o || 10;"
" var y = typeof o;"
" return o , typeof o, x , y, o;"
"}"
"%EnsureFeedbackVectorForFunction(f);"
"f();");
DirectHandle<JSFunction> f = GetFunction("f");
// There should be two IC slots for {o} references outside and inside
// typeof operator respectively.
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(f->feedback_vector(), isolate);
FeedbackVectorHelper helper(feedback_vector);
CHECK_EQ(4, helper.slot_count());
CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kLoadGlobalNotInsideTypeof);
CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kLoadGlobalInsideTypeof);
CHECK_SLOT_KIND(helper, 2, FeedbackSlotKind::kTypeOf);
CHECK_SLOT_KIND(helper, 3, FeedbackSlotKind::kTypeOf);
FeedbackSlot slot1 = helper.slot(0);
FeedbackSlot slot2 = helper.slot(1);
FeedbackSlot slot3 = helper.slot(2);
FeedbackSlot slot4 = helper.slot(3);
CHECK_EQ(InlineCacheState::MONOMORPHIC,
FeedbackNexus(i_isolate(), feedback_vector, slot1).ic_state());
CHECK_EQ(InlineCacheState::MONOMORPHIC,
FeedbackNexus(i_isolate(), feedback_vector, slot2).ic_state());
CHECK_EQ(InlineCacheState::MONOMORPHIC,
FeedbackNexus(i_isolate(), feedback_vector, slot3).ic_state());
CHECK_EQ(InlineCacheState::MONOMORPHIC,
FeedbackNexus(i_isolate(), feedback_vector, slot4).ic_state());
}
TEST_F(FeedbackVectorTest, VectorLoadICOnSmi) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
Heap* heap = isolate->heap();
// Make sure function f has a call that uses a type feedback slot.
TryRunJS(
"var o = { foo: 3 };"
"%EnsureFeedbackVectorForFunction(f);"
"function f(a) { return a.foo; } f(34);");
DirectHandle<JSFunction> f = GetFunction("f");
// There should be one IC.
Handle<FeedbackVector> feedback_vector =
Handle<FeedbackVector>(f->feedback_vector(), isolate);
FeedbackSlot slot(0);
FeedbackNexus nexus(i_isolate(), feedback_vector, slot);
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
// Verify that the monomorphic map is the one we expect.
Tagged<Map> number_map = ReadOnlyRoots(heap).heap_number_map();
CHECK_EQ(number_map, nexus.GetFirstMap());
// Now go polymorphic on o.
TryRunJS("f(o)");
CHECK_EQ(InlineCacheState::POLYMORPHIC, nexus.ic_state());
MapHandles maps(isolate);
nexus.ExtractMaps(&maps);
CHECK_EQ(2, maps.size());
// One of the maps should be the o map.
v8::MaybeLocal<v8::Value> v8_o =
v8_context()->Global()->Get(v8_context(), NewString("o"));
DirectHandle<JSObject> o =
Cast<JSObject>(v8::Utils::OpenDirectHandle(*v8_o.ToLocalChecked()));
bool number_map_found = false;
bool o_map_found = false;
for (DirectHandle<Map> current : maps) {
if (*current == number_map)
number_map_found = true;
else if (*current == o->map())
o_map_found = true;
}
CHECK(number_map_found && o_map_found);
// The degree of polymorphism doesn't change.
TryRunJS("f(100)");
CHECK_EQ(InlineCacheState::POLYMORPHIC, nexus.ic_state());
MapHandles maps2(isolate);
nexus.ExtractMaps(&maps2);
CHECK_EQ(2, maps2.size());
}
TEST_F(FeedbackVectorTest, ReferenceContextAllocatesNoSlots) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
{
TryRunJS(
"function testvar(x) {"
" y = x;"
" y = a;"
" return y;"
"}"
"%EnsureFeedbackVectorForFunction(testvar);"
"a = 3;"
"testvar({});");
DirectHandle<JSFunction> f = GetFunction("testvar");
// There should be two LOAD_ICs, one for a and one for y at the end.
Handle<FeedbackVector> feedback_vector =
handle(f->feedback_vector(), isolate);
FeedbackVectorHelper helper(feedback_vector);
CHECK_EQ(3, helper.slot_count());
CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kStoreGlobalSloppy);
CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kLoadGlobalNotInsideTypeof);
CHECK_SLOT_KIND(helper, 2, FeedbackSlotKind::kLoadGlobalNotInsideTypeof);
}
{
TryRunJS(
"function testprop(x) {"
" 'use strict';"
" x.blue = a;"
"}"
"%EnsureFeedbackVectorForFunction(testprop);"
"testprop({ blue: 3 });");
DirectHandle<JSFunction> f = GetFunction("testprop");
// There should be one LOAD_IC, for the load of a.
Handle<FeedbackVector> feedback_vector(f->feedback_vector(), isolate);
FeedbackVectorHelper helper(feedback_vector);
CHECK_EQ(2, helper.slot_count());
CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kLoadGlobalNotInsideTypeof);
CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kSetNamedStrict);
}
{
TryRunJS(
"function testpropfunc(x) {"
" x().blue = a;"
" return x().blue;"
"}"
"%EnsureFeedbackVectorForFunction(testpropfunc);"
"function makeresult() { return { blue: 3 }; }"
"testpropfunc(makeresult);");
DirectHandle<JSFunction> f = GetFunction("testpropfunc");
// There should be 1 LOAD_GLOBAL_IC to load x (in both cases), 2 CALL_ICs
// to call x and a LOAD_IC to load blue.
Handle<FeedbackVector> feedback_vector(f->feedback_vector(), isolate);
FeedbackVectorHelper helper(feedback_vector);
CHECK_EQ(5, helper.slot_count());
CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kCall);
CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kLoadGlobalNotInsideTypeof);
CHECK_SLOT_KIND(helper, 2, FeedbackSlotKind::kSetNamedSloppy);
CHECK_SLOT_KIND(helper, 3, FeedbackSlotKind::kCall);
CHECK_SLOT_KIND(helper, 4, FeedbackSlotKind::kLoadProperty);
}
{
TryRunJS(
"function testkeyedprop(x) {"
" x[0] = a;"
" return x[0];"
"}"
"%EnsureFeedbackVectorForFunction(testkeyedprop);"
"testkeyedprop([0, 1, 2]);");
DirectHandle<JSFunction> f = GetFunction("testkeyedprop");
// There should be 1 LOAD_GLOBAL_ICs for the load of a, and one
// KEYED_LOAD_IC for the load of x[0] in the return statement.
Handle<FeedbackVector> feedback_vector(f->feedback_vector(), isolate);
FeedbackVectorHelper helper(feedback_vector);
CHECK_EQ(3, helper.slot_count());
CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kLoadGlobalNotInsideTypeof);
CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kSetKeyedSloppy);
CHECK_SLOT_KIND(helper, 2, FeedbackSlotKind::kLoadKeyed);
}
{
TryRunJS(
"function testkeyedprop(x) {"
" 'use strict';"
" x[0] = a;"
" return x[0];"
"}"
"%EnsureFeedbackVectorForFunction(testkeyedprop);"
"testkeyedprop([0, 1, 2]);");
DirectHandle<JSFunction> f = GetFunction("testkeyedprop");
// There should be 1 LOAD_GLOBAL_ICs for the load of a, and one
// KEYED_LOAD_IC for the load of x[0] in the return statement.
Handle<FeedbackVector> feedback_vector(f->feedback_vector(), isolate);
FeedbackVectorHelper helper(feedback_vector);
CHECK_EQ(3, helper.slot_count());
CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kLoadGlobalNotInsideTypeof);
CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kSetKeyedStrict);
CHECK_SLOT_KIND(helper, 2, FeedbackSlotKind::kLoadKeyed);
}
{
TryRunJS(
"function testcompound(x) {"
" 'use strict';"
" x.old = x.young = x.in_between = a;"
" return x.old + x.young;"
"}"
"%EnsureFeedbackVectorForFunction(testcompound);"
"testcompound({ old: 3, young: 3, in_between: 3 });");
DirectHandle<JSFunction> f = GetFunction("testcompound");
// There should be 1 LOAD_GLOBAL_IC for load of a and 2 LOAD_ICs, for load
// of x.old and x.young.
Handle<FeedbackVector> feedback_vector(f->feedback_vector(), isolate);
FeedbackVectorHelper helper(feedback_vector);
CHECK_EQ(7, helper.slot_count());
CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kLoadGlobalNotInsideTypeof);
CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kSetNamedStrict);
CHECK_SLOT_KIND(helper, 2, FeedbackSlotKind::kSetNamedStrict);
CHECK_SLOT_KIND(helper, 3, FeedbackSlotKind::kSetNamedStrict);
CHECK_SLOT_KIND(helper, 4, FeedbackSlotKind::kBinaryOp);
CHECK_SLOT_KIND(helper, 5, FeedbackSlotKind::kLoadProperty);
CHECK_SLOT_KIND(helper, 6, FeedbackSlotKind::kLoadProperty);
}
}
TEST_F(FeedbackVectorTest, VectorStoreICBasic) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
TryRunJS(
"function f(a) {"
" a.foo = 5;"
"};"
"%EnsureFeedbackVectorForFunction(f);"
"var a = { foo: 3 };"
"f(a);"
"f(a);"
"f(a);");
DirectHandle<JSFunction> f = GetFunction("f");
// There should be one IC slot.
Handle<FeedbackVector> feedback_vector(f->feedback_vector(), f->GetIsolate());
FeedbackVectorHelper helper(feedback_vector);
CHECK_EQ(1, helper.slot_count());
FeedbackSlot slot(0);
FeedbackNexus nexus(i_isolate(), feedback_vector, slot);
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
}
TEST_F(FeedbackVectorTest, DefineNamedOwnIC) {
if (!i::v8_flags.use_ic) return;
if (i::v8_flags.always_turbofan) return;
v8_flags.allow_natives_syntax = true;
v8::HandleScope scope(v8_isolate());
TryRunJS(
"function f(v) {"
" return {a: 0, b: v, c: 0};"
"}"
"%EnsureFeedbackVectorForFunction(f);"
"f(1);"
"f(2);"
"f(3);");
DirectHandle<JSFunction> f = GetFunction("f");
// There should be one IC slot.
Handle<FeedbackVector> feedback_vector(f->feedback_vector(), f->GetIsolate());
FeedbackVectorHelper helper(feedback_vector);
CHECK_EQ(2, helper.slot_count());
CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kLiteral);
CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kDefineNamedOwn);
FeedbackNexus nexus(i_isolate(), feedback_vector, helper.slot(1));
CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,133 @@
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/init/v8.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock-support.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
using GlobalObjectTest = TestWithContext;
// This test fails if properties on the prototype of the global object appear
// as declared globals.
TEST_F(GlobalObjectTest, StrictUndeclaredGlobalVariable) {
Local<String> var_name = NewString("x");
TryCatch try_catch(isolate());
Local<Object> proto = Object::New(isolate());
Local<Object> global = context()->Global();
proto->Set(context(), var_name, Number::New(isolate(), 100)).FromJust();
global->SetPrototypeV2(context(), proto).FromJust();
CHECK_EQ(global->GetPrototypeV2(), proto);
CHECK(TryRunJS("\"use strict\"; x = 42;").IsEmpty());
CHECK(try_catch.HasCaught());
String::Utf8Value exception(isolate(), try_catch.Exception());
CHECK_EQ(0, strcmp("ReferenceError: x is not defined", *exception));
}
TEST_F(GlobalObjectTest, KeysGlobalObject_Regress2764) {
Local<Context> env1 = context();
// Create second environment.
Local<Context> env2 = Context::New(env1->GetIsolate());
Local<Value> token = NewString("foo");
// Set same security token for env1 and env2.
env1->SetSecurityToken(token);
env2->SetSecurityToken(token);
// Create a reference to env2 global from env1 global.
env1->Global()->Set(env1, NewString("global2"), env2->Global()).FromJust();
// Set some global variables in global2
env2->Global()->Set(env2, NewString("a"), NewString("a")).FromJust();
env2->Global()->Set(env2, NewString("42"), NewString("42")).FromJust();
// List all entries from global2.
Local<Array> result;
result = Local<Array>::Cast(RunJS("Object.keys(global2)"));
CHECK_EQ(2u, result->Length());
CHECK(NewString("42")
->Equals(env1, result->Get(env1, 0).ToLocalChecked())
.FromJust());
CHECK(NewString("a")
->Equals(env1, result->Get(env1, 1).ToLocalChecked())
.FromJust());
result = Local<Array>::Cast(RunJS("Object.getOwnPropertyNames(global2)"));
CHECK_LT(2u, result->Length());
// Check that all elements are in the property names
CHECK(RunJS("-1 < Object.getOwnPropertyNames(global2).indexOf('42')")
->IsTrue());
CHECK(
RunJS("-1 < Object.getOwnPropertyNames(global2).indexOf('a')")->IsTrue());
// Hold on to global from env2 and detach global from env2.
env2->DetachGlobal();
// List again all entries from the detached global2.
result = Local<Array>::Cast(RunJS("Object.keys(global2)"));
CHECK_EQ(0u, result->Length());
result = Local<Array>::Cast(RunJS("Object.getOwnPropertyNames(global2)"));
CHECK_EQ(0u, result->Length());
}
// Allow usages of v8::Object::GetPrototype() for now.
// TODO(https://crbug.com/333672197): remove.
START_ALLOW_USE_DEPRECATED()
TEST_F(GlobalObjectTest, KeysGlobalObject_SetPrototype) {
Local<Context> env1 = context();
// Create second environment.
Local<Context> env2 = Context::New(env1->GetIsolate());
Local<Value> token = NewString("foo");
// Set same security token for env1 and env2.
env1->SetSecurityToken(token);
env2->SetSecurityToken(token);
// Create a reference to env2 global from env1 global.
env1->Global()->SetPrototypeV2(env1, env2->Global()).FromJust();
CHECK_EQ(env1->Global()->GetPrototypeV2(), env2->Global());
CHECK_EQ(env1->Global()->GetPrototype().As<Object>()->GetPrototype(),
env2->Global());
// Set some global variables in global2
env2->Global()->Set(env2, NewString("a"), NewString("a")).FromJust();
env2->Global()->Set(env2, NewString("42"), NewString("42")).FromJust();
// List all entries from global2.
CHECK(RunJS("a == 'a'")->IsTrue());
}
// Allow usages of v8::Object::GetPrototype() for now.
// TODO(https://crbug.com/333672197): remove.
END_ALLOW_USE_DEPRECATED()
} // namespace v8

View File

@ -0,0 +1,307 @@
// Copyright 2017 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 <stdlib.h>
#include <sstream>
#include <utility>
#include "src/init/v8.h"
#include "src/objects/objects-inl.h"
#include "src/objects/objects.h"
#include "src/objects/ordered-hash-table.h"
#include "src/utils/utils.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/siphash/halfsiphash.h"
namespace v8 {
namespace internal {
class HashcodeTest : public TestWithContext {
public:
template <typename T>
inline DirectHandle<T> GetGlobal(const char* name) {
DirectHandle<String> str_name =
i_isolate()->factory()->InternalizeUtf8String(name);
DirectHandle<Object> value =
Object::GetProperty(i_isolate(), i_isolate()->global_object(), str_name)
.ToHandleChecked();
return Cast<T>(value);
}
int AddToSetAndGetHash(DirectHandle<JSObject> obj, bool has_fast_properties) {
CHECK_EQ(has_fast_properties, obj->HasFastProperties());
CHECK_EQ(ReadOnlyRoots(i_isolate()).undefined_value(),
Object::GetHash(*obj));
Handle<OrderedHashSet> set = i_isolate()->factory()->NewOrderedHashSet();
OrderedHashSet::Add(i_isolate(), set, obj);
CHECK_EQ(has_fast_properties, obj->HasFastProperties());
return Smi::ToInt(Object::GetHash(*obj));
}
int GetPropertyDictionaryHash(DirectHandle<JSObject> obj) {
if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
return obj->property_dictionary_swiss()->Hash();
} else {
return obj->property_dictionary()->Hash();
}
}
int GetPropertyDictionaryLength(DirectHandle<JSObject> obj) {
if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
return obj->property_dictionary_swiss()->Capacity();
} else {
return obj->property_dictionary()->length();
}
}
void CheckIsDictionaryModeObject(DirectHandle<JSObject> obj) {
if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
CHECK(IsSwissNameDictionary(obj->raw_properties_or_hash()));
} else {
CHECK(IsNameDictionary(obj->raw_properties_or_hash()));
}
}
void CheckFastObject(DirectHandle<JSObject> obj, int hash) {
CHECK(obj->HasFastProperties());
CHECK(IsPropertyArray(obj->raw_properties_or_hash()));
CHECK_EQ(Smi::FromInt(hash), Object::GetHash(*obj));
CHECK_EQ(hash, obj->property_array()->Hash());
}
void CheckDictionaryObject(DirectHandle<JSObject> obj, int hash) {
CHECK(!obj->HasFastProperties());
CheckIsDictionaryModeObject(obj);
CHECK_EQ(Smi::FromInt(hash), Object::GetHash(*obj));
CHECK_EQ(hash, GetPropertyDictionaryHash(obj));
}
};
TEST_F(HashcodeTest, AddHashCodeToFastObjectWithoutProperties) {
DirectHandle<JSObject> obj =
i_isolate()->factory()->NewJSObject(i_isolate()->object_function());
CHECK(obj->HasFastProperties());
int hash = AddToSetAndGetHash(obj, true);
CHECK_EQ(Smi::FromInt(hash), obj->raw_properties_or_hash());
}
TEST_F(HashcodeTest, AddHashCodeToFastObjectWithInObjectProperties) {
const char* source = " var x = { a: 1};";
RunJS(source);
DirectHandle<JSObject> obj = GetGlobal<JSObject>("x");
CHECK_EQ(ReadOnlyRoots(i_isolate()).empty_fixed_array(),
obj->raw_properties_or_hash());
int hash = AddToSetAndGetHash(obj, true);
CHECK_EQ(Smi::FromInt(hash), obj->raw_properties_or_hash());
}
TEST_F(HashcodeTest, AddHashCodeToFastObjectWithPropertiesArray) {
const char* source =
" var x = {}; "
" x.a = 1; x.b = 2; x.c = 3; x.d = 4; x.e = 5; ";
RunJS(source);
DirectHandle<JSObject> obj = GetGlobal<JSObject>("x");
CHECK(obj->HasFastProperties());
int hash = AddToSetAndGetHash(obj, true);
CheckFastObject(obj, hash);
}
TEST_F(HashcodeTest, AddHashCodeToSlowObject) {
DirectHandle<JSObject> obj =
i_isolate()->factory()->NewJSObject(i_isolate()->object_function());
CHECK(obj->HasFastProperties());
JSObject::NormalizeProperties(i_isolate(), obj, CLEAR_INOBJECT_PROPERTIES, 0,
"cctest/test-hashcode");
CheckIsDictionaryModeObject(obj);
int hash = AddToSetAndGetHash(obj, false);
CheckDictionaryObject(obj, hash);
}
TEST_F(HashcodeTest, TransitionFastWithInObjectToFastWithPropertyArray) {
const char* source =
" var x = { };"
" x.a = 1; x.b = 2; x.c = 3; x.d = 4;";
RunJS(source);
DirectHandle<JSObject> obj = GetGlobal<JSObject>("x");
CHECK(obj->HasFastProperties());
int hash = AddToSetAndGetHash(obj, true);
CHECK_EQ(Smi::FromInt(hash), obj->raw_properties_or_hash());
int length = obj->property_array()->length();
RunJS("x.e = 5;");
CHECK(obj->property_array()->length() > length);
CheckFastObject(obj, hash);
}
TEST_F(HashcodeTest, TransitionFastWithPropertyArray) {
const char* source =
" var x = { };"
" x.a = 1; x.b = 2; x.c = 3; x.d = 4; x.e = 5; ";
RunJS(source);
DirectHandle<JSObject> obj = GetGlobal<JSObject>("x");
CHECK(IsPropertyArray(obj->raw_properties_or_hash()));
int hash = AddToSetAndGetHash(obj, true);
CHECK_EQ(hash, obj->property_array()->Hash());
int length = obj->property_array()->length();
RunJS("x.f = 2; x.g = 5; x.h = 2");
CHECK(obj->property_array()->length() > length);
CheckFastObject(obj, hash);
}
TEST_F(HashcodeTest, TransitionFastWithPropertyArrayToSlow) {
const char* source =
" var x = { };"
" x.a = 1; x.b = 2; x.c = 3; x.d = 4; x.e = 5; ";
RunJS(source);
DirectHandle<JSObject> obj = GetGlobal<JSObject>("x");
CHECK(IsPropertyArray(obj->raw_properties_or_hash()));
int hash = AddToSetAndGetHash(obj, true);
CHECK(IsPropertyArray(obj->raw_properties_or_hash()));
CHECK_EQ(hash, obj->property_array()->Hash());
JSObject::NormalizeProperties(i_isolate(), obj, KEEP_INOBJECT_PROPERTIES, 0,
"cctest/test-hashcode");
CheckDictionaryObject(obj, hash);
}
TEST_F(HashcodeTest, TransitionSlowToSlow) {
const char* source = " var x = {}; ";
RunJS(source);
DirectHandle<JSObject> obj = GetGlobal<JSObject>("x");
JSObject::NormalizeProperties(i_isolate(), obj, CLEAR_INOBJECT_PROPERTIES, 0,
"cctest/test-hashcode");
CheckIsDictionaryModeObject(obj);
int hash = AddToSetAndGetHash(obj, false);
CHECK_EQ(hash, GetPropertyDictionaryHash(obj));
int length = GetPropertyDictionaryLength(obj);
RunJS("for(var i = 0; i < 10; i++) { x['f'+i] = i };");
CHECK(GetPropertyDictionaryLength(obj) > length);
CheckDictionaryObject(obj, hash);
}
TEST_F(HashcodeTest, TransitionSlowToFastWithoutProperties) {
DirectHandle<JSObject> obj =
i_isolate()->factory()->NewJSObject(i_isolate()->object_function());
JSObject::NormalizeProperties(i_isolate(), obj, CLEAR_INOBJECT_PROPERTIES, 0,
"cctest/test-hashcode");
CheckIsDictionaryModeObject(obj);
int hash = AddToSetAndGetHash(obj, false);
CHECK_EQ(hash, GetPropertyDictionaryHash(obj));
JSObject::MigrateSlowToFast(obj, 0, "cctest/test-hashcode");
CHECK_EQ(Smi::FromInt(hash), Object::GetHash(*obj));
}
TEST_F(HashcodeTest, TransitionSlowToFastWithPropertyArray) {
const char* source =
" var x = Object.create(null); "
" for(var i = 0; i < 10; i++) { x['f'+i] = i }; ";
RunJS(source);
DirectHandle<JSObject> obj = GetGlobal<JSObject>("x");
CheckIsDictionaryModeObject(obj);
int hash = AddToSetAndGetHash(obj, false);
CHECK_EQ(hash, GetPropertyDictionaryHash(obj));
JSObject::MigrateSlowToFast(obj, 0, "cctest/test-hashcode");
CheckFastObject(obj, hash);
}
namespace {
using HashFunction = uint32_t (*)(uint32_t key, uint64_t seed);
void TestIntegerHashQuality(const int samples_log2, int num_buckets_log2,
uint64_t seed, double max_var,
HashFunction hash_function) {
int samples = 1 << samples_log2;
int num_buckets = 1 << num_buckets_log2;
int mean = samples / num_buckets;
int* buckets = new int[num_buckets];
for (int i = 0; i < num_buckets; i++) buckets[i] = 0;
for (int i = 0; i < samples; i++) {
uint32_t hash = hash_function(i, seed);
buckets[hash % num_buckets]++;
}
int sum_deviation = 0;
for (int i = 0; i < num_buckets; i++) {
int deviation = abs(buckets[i] - mean);
sum_deviation += deviation * deviation;
}
delete[] buckets;
double variation_coefficient = sqrt(sum_deviation * 1.0 / num_buckets) / mean;
printf("samples: 1 << %2d, buckets: 1 << %2d, var_coeff: %0.3f\n",
samples_log2, num_buckets_log2, variation_coefficient);
CHECK_LT(variation_coefficient, max_var);
}
uint32_t HalfSipHash(uint32_t key, uint64_t seed) {
return halfsiphash(key, seed);
}
uint32_t JenkinsHash(uint32_t key, uint64_t seed) {
return ComputeLongHash(static_cast<uint64_t>(key) ^ seed);
}
uint32_t DefaultHash(uint32_t key, uint64_t seed) {
return ComputeSeededHash(key, seed);
}
} // anonymous namespace
void TestIntegerHashQuality(HashFunction hash_function) {
TestIntegerHashQuality(17, 13, 0x123456789ABCDEFU, 0.4, hash_function);
TestIntegerHashQuality(16, 12, 0x123456789ABCDEFU, 0.4, hash_function);
TestIntegerHashQuality(15, 11, 0xFEDCBA987654321U, 0.4, hash_function);
TestIntegerHashQuality(14, 10, 0xFEDCBA987654321U, 0.4, hash_function);
TestIntegerHashQuality(13, 9, 1, 0.4, hash_function);
TestIntegerHashQuality(12, 8, 1, 0.4, hash_function);
TestIntegerHashQuality(17, 10, 0x123456789ABCDEFU, 0.2, hash_function);
TestIntegerHashQuality(16, 9, 0x123456789ABCDEFU, 0.2, hash_function);
TestIntegerHashQuality(15, 8, 0xFEDCBA987654321U, 0.2, hash_function);
TestIntegerHashQuality(14, 7, 0xFEDCBA987654321U, 0.2, hash_function);
TestIntegerHashQuality(13, 6, 1, 0.2, hash_function);
TestIntegerHashQuality(12, 5, 1, 0.2, hash_function);
}
TEST_F(HashcodeTest, HalfSipHashQuality) {
TestIntegerHashQuality(HalfSipHash);
}
TEST_F(HashcodeTest, JenkinsHashQuality) {
TestIntegerHashQuality(JenkinsHash);
}
TEST_F(HashcodeTest, DefaultHashQuality) {
TestIntegerHashQuality(DefaultHash);
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,454 @@
// Copyright 2017 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.
#ifdef V8_INTL_SUPPORT
#include "src/objects/intl-objects.h"
#include "src/objects/js-break-iterator.h"
#include "src/objects/js-collator-inl.h"
#include "src/objects/js-date-time-format.h"
#include "src/objects/js-list-format.h"
#include "src/objects/js-number-format.h"
#include "src/objects/js-plural-rules.h"
#include "src/objects/js-relative-time-format.h"
#include "src/objects/js-segmenter.h"
#include "src/objects/lookup.h"
#include "src/objects/objects-inl.h"
#include "src/objects/option-utils.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "unicode/coll.h"
namespace v8 {
namespace internal {
using IntlTest = TestWithContext;
// This operator overloading enables CHECK_EQ to be used with
// std::vector<NumberFormatSpan>
bool operator==(const NumberFormatSpan& lhs, const NumberFormatSpan& rhs) {
return memcmp(&lhs, &rhs, sizeof(lhs)) == 0;
}
template <typename _CharT, typename _Traits>
std::basic_ostream<_CharT, _Traits>& operator<<(
std::basic_ostream<_CharT, _Traits>& self, const NumberFormatSpan& part) {
return self << "{" << part.field_id << "," << part.begin_pos << ","
<< part.end_pos << "}";
}
void test_flatten_regions_to_parts(
const std::vector<NumberFormatSpan>& regions,
const std::vector<NumberFormatSpan>& expected_parts) {
std::vector<NumberFormatSpan> mutable_regions = regions;
std::vector<NumberFormatSpan> parts = FlattenRegionsToParts(&mutable_regions);
CHECK_EQ(expected_parts, parts);
}
TEST_F(IntlTest, FlattenRegionsToParts) {
test_flatten_regions_to_parts(
std::vector<NumberFormatSpan>{
NumberFormatSpan(-1, 0, 10),
NumberFormatSpan(1, 2, 8),
NumberFormatSpan(2, 2, 4),
NumberFormatSpan(3, 6, 8),
},
std::vector<NumberFormatSpan>{
NumberFormatSpan(-1, 0, 2),
NumberFormatSpan(2, 2, 4),
NumberFormatSpan(1, 4, 6),
NumberFormatSpan(3, 6, 8),
NumberFormatSpan(-1, 8, 10),
});
test_flatten_regions_to_parts(
std::vector<NumberFormatSpan>{
NumberFormatSpan(0, 0, 1),
},
std::vector<NumberFormatSpan>{
NumberFormatSpan(0, 0, 1),
});
test_flatten_regions_to_parts(
std::vector<NumberFormatSpan>{
NumberFormatSpan(-1, 0, 1),
NumberFormatSpan(0, 0, 1),
},
std::vector<NumberFormatSpan>{
NumberFormatSpan(0, 0, 1),
});
test_flatten_regions_to_parts(
std::vector<NumberFormatSpan>{
NumberFormatSpan(0, 0, 1),
NumberFormatSpan(-1, 0, 1),
},
std::vector<NumberFormatSpan>{
NumberFormatSpan(0, 0, 1),
});
test_flatten_regions_to_parts(
std::vector<NumberFormatSpan>{
NumberFormatSpan(-1, 0, 10),
NumberFormatSpan(1, 0, 1),
NumberFormatSpan(2, 0, 2),
NumberFormatSpan(3, 0, 3),
NumberFormatSpan(4, 0, 4),
NumberFormatSpan(5, 0, 5),
NumberFormatSpan(15, 5, 10),
NumberFormatSpan(16, 6, 10),
NumberFormatSpan(17, 7, 10),
NumberFormatSpan(18, 8, 10),
NumberFormatSpan(19, 9, 10),
},
std::vector<NumberFormatSpan>{
NumberFormatSpan(1, 0, 1),
NumberFormatSpan(2, 1, 2),
NumberFormatSpan(3, 2, 3),
NumberFormatSpan(4, 3, 4),
NumberFormatSpan(5, 4, 5),
NumberFormatSpan(15, 5, 6),
NumberFormatSpan(16, 6, 7),
NumberFormatSpan(17, 7, 8),
NumberFormatSpan(18, 8, 9),
NumberFormatSpan(19, 9, 10),
});
// : 4
// : 22 33 3
// : 11111 22
// input regions: 0000000 111
// : ------------
// output parts: 0221340--231
test_flatten_regions_to_parts(
std::vector<NumberFormatSpan>{
NumberFormatSpan(-1, 0, 12),
NumberFormatSpan(0, 0, 7),
NumberFormatSpan(1, 9, 12),
NumberFormatSpan(1, 1, 6),
NumberFormatSpan(2, 9, 11),
NumberFormatSpan(2, 1, 3),
NumberFormatSpan(3, 10, 11),
NumberFormatSpan(3, 4, 6),
NumberFormatSpan(4, 5, 6),
},
std::vector<NumberFormatSpan>{
NumberFormatSpan(0, 0, 1),
NumberFormatSpan(2, 1, 3),
NumberFormatSpan(1, 3, 4),
NumberFormatSpan(3, 4, 5),
NumberFormatSpan(4, 5, 6),
NumberFormatSpan(0, 6, 7),
NumberFormatSpan(-1, 7, 9),
NumberFormatSpan(2, 9, 10),
NumberFormatSpan(3, 10, 11),
NumberFormatSpan(1, 11, 12),
});
}
TEST_F(IntlTest, GetStringOption) {
DirectHandle<JSObject> options =
i_isolate()->factory()->NewJSObjectWithNullProto();
{
// No value found
std::unique_ptr<char[]> result = nullptr;
Maybe<bool> found =
GetStringOption(i_isolate(), options, "foo", std::vector<const char*>{},
"service", &result);
CHECK(!found.FromJust());
CHECK_NULL(result);
}
DirectHandle<String> key =
i_isolate()->factory()->NewStringFromAsciiChecked("foo");
LookupIterator it(i_isolate(), options, key);
CHECK(Object::SetProperty(
&it, DirectHandle<Smi>(Smi::FromInt(42), i_isolate()),
StoreOrigin::kMaybeKeyed, Just(ShouldThrow::kThrowOnError))
.FromJust());
{
// Value found
std::unique_ptr<char[]> result = nullptr;
Maybe<bool> found =
GetStringOption(i_isolate(), options, "foo", std::vector<const char*>{},
"service", &result);
CHECK(found.FromJust());
CHECK_NOT_NULL(result);
CHECK_EQ(0, strcmp("42", result.get()));
}
{
// No expected value in values array
std::unique_ptr<char[]> result = nullptr;
Maybe<bool> found =
GetStringOption(i_isolate(), options, "foo",
std::vector<const char*>{"bar"}, "service", &result);
CHECK(i_isolate()->has_exception());
CHECK(found.IsNothing());
CHECK_NULL(result);
i_isolate()->clear_exception();
}
{
// Expected value in values array
std::unique_ptr<char[]> result = nullptr;
Maybe<bool> found =
GetStringOption(i_isolate(), options, "foo",
std::vector<const char*>{"42"}, "service", &result);
CHECK(found.FromJust());
CHECK_NOT_NULL(result);
CHECK_EQ(0, strcmp("42", result.get()));
}
}
TEST_F(IntlTest, GetBoolOption) {
DirectHandle<JSObject> options =
i_isolate()->factory()->NewJSObjectWithNullProto();
{
bool result = false;
Maybe<bool> found =
GetBoolOption(i_isolate(), options, "foo", "service", &result);
CHECK(!found.FromJust());
CHECK(!result);
}
DirectHandle<String> key =
i_isolate()->factory()->NewStringFromAsciiChecked("foo");
{
LookupIterator it(i_isolate(), options, key);
DirectHandle<Object> false_value(
i::ReadOnlyRoots(i_isolate()).false_value(), i_isolate());
Object::SetProperty(i_isolate(), options, key, false_value,
StoreOrigin::kMaybeKeyed,
Just(ShouldThrow::kThrowOnError))
.Assert();
bool result = false;
Maybe<bool> found =
GetBoolOption(i_isolate(), options, "foo", "service", &result);
CHECK(found.FromJust());
CHECK(!result);
}
{
LookupIterator it(i_isolate(), options, key);
DirectHandle<Object> true_value(i::ReadOnlyRoots(i_isolate()).true_value(),
i_isolate());
Object::SetProperty(i_isolate(), options, key, true_value,
StoreOrigin::kMaybeKeyed,
Just(ShouldThrow::kThrowOnError))
.Assert();
bool result = false;
Maybe<bool> found =
GetBoolOption(i_isolate(), options, "foo", "service", &result);
CHECK(found.FromJust());
CHECK(result);
}
}
TEST_F(IntlTest, GetAvailableLocales) {
std::set<std::string> locales;
locales = JSV8BreakIterator::GetAvailableLocales();
CHECK(locales.count("en-US"));
CHECK(!locales.count("abcdefg"));
locales = JSCollator::GetAvailableLocales();
CHECK(locales.count("en-US"));
locales = JSDateTimeFormat::GetAvailableLocales();
CHECK(locales.count("en-US"));
locales = JSListFormat::GetAvailableLocales();
CHECK(locales.count("en-US"));
locales = JSNumberFormat::GetAvailableLocales();
CHECK(locales.count("en-US"));
locales = JSPluralRules::GetAvailableLocales();
CHECK(locales.count("en"));
locales = JSRelativeTimeFormat::GetAvailableLocales();
CHECK(locales.count("en-US"));
locales = JSSegmenter::GetAvailableLocales();
CHECK(locales.count("en-US"));
CHECK(!locales.count("abcdefg"));
}
// Tests that the LocaleCompare fast path and generic path return the same
// comparison results for all ASCII strings.
TEST_F(IntlTest, StringLocaleCompareFastPath) {
// We compare all single-char strings of printable ASCII characters.
std::vector<Handle<String>> ascii_strings;
for (int c = 0; c <= 0x7F; c++) {
if (!std::isprint(c)) continue;
ascii_strings.push_back(
i_isolate()->factory()->LookupSingleCharacterStringFromCode(c));
}
DirectHandle<JSFunction> collator_constructor(
Cast<JSFunction>(
i_isolate()->context()->native_context()->intl_collator_function()),
i_isolate());
DirectHandle<Map> constructor_map =
JSFunction::GetDerivedMap(i_isolate(), collator_constructor,
collator_constructor)
.ToHandleChecked();
DirectHandle<Object> options(ReadOnlyRoots(i_isolate()).undefined_value(),
i_isolate());
static const char* const kMethodName = "StringLocaleCompareFastPath";
// For all fast locales, exhaustively compare within the printable ASCII
// range.
const std::set<std::string>& locales = JSCollator::GetAvailableLocales();
for (const std::string& locale : locales) {
DirectHandle<String> locale_string =
i_isolate()->factory()->NewStringFromAsciiChecked(locale.c_str());
if (Intl::CompareStringsOptionsFor(i_isolate()->AsLocalIsolate(),
locale_string, options) !=
Intl::CompareStringsOptions::kTryFastPath) {
continue;
}
DirectHandle<JSCollator> collator =
JSCollator::New(i_isolate(), constructor_map, locale_string, options,
kMethodName)
.ToHandleChecked();
for (size_t i = 0; i < ascii_strings.size(); i++) {
DirectHandle<String> lhs = ascii_strings[i];
for (size_t j = i + 1; j < ascii_strings.size(); j++) {
DirectHandle<String> rhs = ascii_strings[j];
CHECK_EQ(
Intl::CompareStrings(i_isolate(), *collator->icu_collator()->raw(),
lhs, rhs, Intl::CompareStringsOptions::kNone),
Intl::CompareStrings(i_isolate(), *collator->icu_collator()->raw(),
lhs, rhs,
Intl::CompareStringsOptions::kTryFastPath));
}
}
}
}
TEST_F(IntlTest, IntlMathematicalValueFromString) {
struct TestCase {
bool is_nan;
bool is_minus_zero;
bool is_negative;
bool is_negative_infinity;
bool is_positive_infinity;
bool is_mathematical_value;
const char* string;
} cases[] = {
{false, false, false, false, false, true, "+1"},
{false, false, false, false, false, true,
"+1234567890123456789012345678901234567890"},
{false, false, false, false, true, false,
"+1234567890123456789012345678901234567890e987654321"},
{false, false, false, false, true, false,
" +1234567890123456789012345678901234567890e987654321 "},
{true, false, false, false, false, false,
" +12 345 67 "}, // space between digit is invalid
{true, false, false, false, false, false,
" -12 345 67 "}, // space between digit is invalid
{false, false, false, false, false, true,
"1234567890123456789012345678901234567890"},
{false, false, false, false, false, true,
"+.1234567890123456789012345678901234567890"},
{false, false, false, false, false, true,
".1234567890123456789012345678901234567890"},
{false, false, false, false, false, true, ".1234567890123456789e123"},
{false, false, false, false, false, true, ".1234567890123456789E123"},
{false, false, false, false, false, true, ".1234567890123456789e+123"},
{false, false, false, false, false, true, ".1234567890123456789E+123"},
{false, false, false, false, false, true, ".1234567890123456789e-0123"},
{false, false, false, false, false, true, ".1234567890123456789E-0123"},
{false, false, false, false, false, true,
"1234567890123456789012345678901234567.890"},
{false, false, false, false, false, true,
"1234567890123456789012345678901234567890."},
{true, false, false, false, false, false,
"1234567.90123456789012345678901234567.890"}, // two '.'
{true, false, false, false, false, false,
".1234567890123456789e12.3"}, // two '.'
{false, false, true, false, false, true, "-1"},
{false, false, true, false, false, true, "-1e33 "},
{false, false, true, false, false, true, " -0.21e33"},
{false, false, false, false, false, true, " 0.21e33"},
{false, true, false, false, false, false, "-0"},
{false, false, false, false, false, true, "1"},
{false, false, true, false, false, true, " -1234.567e-20 "},
{false, true, false, false, false, false, " -1234.567e-9876 "},
{false, false, false, false, true, false, " Infinity "},
{false, false, true, true, false, false, " -Infinity "},
{true, false, false, false, false, false, "yz"}, // not digits
{false, false, true, false, false, true,
" -12345678901234567890122345.6778901234567890e234 "},
{false, false, false, false, false, true,
" 12345678901234567890122345.6778901234567890e-234 "},
{false, false, false, false, false, true, " 0b01010001 "},
{false, false, false, false, false, true, " 0B01010001 "},
{true, false, false, false, false, false,
" -0b01010001 "}, // invalid binary becaues of -
{true, false, false, false, false, false,
" -0B01010001 "}, // invalid binary becaues of -
{true, false, false, false, false, false,
" 0b01010002 "}, // invalid binary becaues of 2
{true, false, false, false, false, false,
" 0B01010003 "}, // invalid binary becaues of 3
{false, false, false, false, false, true, " 0o01234567 "},
{false, false, false, false, false, true, " 0O76543210 "},
{true, false, false, false, false, false,
" -0o01234567 "}, // invalid oct becaues of -
{true, false, false, false, false, false,
" -0O76543210 "}, // invalid oct becaues of -
{true, false, false, false, false, false,
" 0o012345678 "}, // invalid oct becaues of 8
{true, false, false, false, false, false,
" 0O765432108 "}, // invalid oct becaues of 8
{false, false, false, false, false, true, " 0x123456789aBcDeF "},
{false, false, false, false, false, true, " 0X123456789AbCdEf "},
{true, false, false, false, false, false,
" -0x123456789aBcDeF "}, // invalid hex because of -
{true, false, false, false, false, false,
" -0X123456789AbCdEf "}, // invalid hex because of -
{true, false, false, false, false, false,
" 0x012345678xyz "}, // invalid hex because xyz
{true, false, false, false, false, false,
" 0X765432108xyz "}, // invalid hex because xyz
};
for (auto& cas : cases) {
IntlMathematicalValue x =
IntlMathematicalValue::From(
i_isolate(),
i_isolate()->factory()->NewStringFromAsciiChecked(cas.string))
.ToChecked();
CHECK_EQ(x.IsNaN(), cas.is_nan);
}
}
TEST_F(IntlTest, IntlMathematicalValueFromBigInt) {
struct TestCase {
bool is_negative;
const char* bigint_string;
} cases[] = {
{false, "12"},
{false, "12345678901234567890123456789012345678901234567890"},
{true, "-12345678901234567890123456789012345678901234567890"},
{false, "0"},
{true, "-20"},
};
for (auto& cas : cases) {
printf("%s\n", cas.bigint_string);
Handle<Object> str =
i_isolate()->factory()->NewStringFromAsciiChecked(cas.bigint_string);
IntlMathematicalValue x =
IntlMathematicalValue::From(
i_isolate(), BigInt::FromObject(i_isolate(), str).ToHandleChecked())
.ToChecked();
CHECK_EQ(x.IsNaN(), false);
}
}
} // namespace internal
} // namespace v8
#endif // V8_INTL_SUPPORT

View File

@ -0,0 +1,191 @@
// Copyright 2016 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "src/objects/managed-inl.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/heap/heap-utils.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8::internal {
using ManagedTest = TestWithIsolate;
class DeleteCounter {
public:
static constexpr ExternalPointerTag kManagedTag = kGenericManagedTag;
explicit DeleteCounter(int* deleted) : deleted_(deleted) { *deleted_ = 0; }
~DeleteCounter() { (*deleted_)++; }
static void Deleter(void* arg) {
delete reinterpret_cast<DeleteCounter*>(arg);
}
private:
int* deleted_;
};
TEST_F(ManagedTest, GCCausesDestruction) {
int deleted1 = 0;
int deleted2 = 0;
auto d2 = std::make_unique<DeleteCounter>(&deleted2);
{
HandleScope scope(isolate());
USE(Managed<DeleteCounter>::From(
isolate(), 0, std::make_shared<DeleteCounter>(&deleted1)));
}
// We need to invoke GC without stack, otherwise the objects may survive.
DisableConservativeStackScanningScopeForTesting scope(isolate()->heap());
InvokeMemoryReducingMajorGCs(isolate());
CHECK_EQ(1, deleted1);
CHECK_EQ(0, deleted2);
d2.reset();
CHECK_EQ(1, deleted2);
}
TEST_F(ManagedTest, DisposeCausesDestruction1) {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = isolate()->array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
isolate->Enter();
int deleted1 = 0;
{
HandleScope scope(i_isolate);
USE(Managed<DeleteCounter>::From(
i_isolate, 0, std::make_shared<DeleteCounter>(&deleted1)));
}
isolate->Exit();
isolate->Dispose();
CHECK_EQ(1, deleted1);
}
TEST_F(ManagedTest, DisposeCausesDestruction2) {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = isolate()->array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
isolate->Enter();
int deleted1 = 0;
int deleted2 = 0;
{
HandleScope scope(i_isolate);
USE(Managed<DeleteCounter>::From(
i_isolate, 0, std::make_shared<DeleteCounter>(&deleted1)));
}
DeleteCounter* d2 = new DeleteCounter(&deleted2);
ManagedPtrDestructor* destructor =
new ManagedPtrDestructor(0, d2, DeleteCounter::Deleter);
i_isolate->RegisterManagedPtrDestructor(destructor);
isolate->Exit();
isolate->Dispose();
CHECK_EQ(1, deleted1);
CHECK_EQ(1, deleted2);
}
TEST_F(ManagedTest, DisposeWithAnotherSharedPtr) {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = isolate()->array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
isolate->Enter();
int deleted1 = 0;
{
auto shared = std::make_shared<DeleteCounter>(&deleted1);
{
HandleScope scope(i_isolate);
USE(Managed<DeleteCounter>::From(i_isolate, 0, shared));
}
isolate->Exit();
isolate->Dispose();
CHECK_EQ(0, deleted1);
}
// Should be deleted after the second shared pointer is destroyed.
CHECK_EQ(1, deleted1);
}
TEST_F(ManagedTest, DisposeAcrossIsolates) {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = isolate()->array_buffer_allocator();
int deleted = 0;
v8::Isolate* isolate1 = v8::Isolate::New(create_params);
Isolate* i_isolate1 = reinterpret_cast<i::Isolate*>(isolate1);
isolate1->Enter();
{
HandleScope scope1(i_isolate1);
auto shared = std::make_shared<DeleteCounter>(&deleted);
USE(Managed<DeleteCounter>::From(i_isolate1, 0, shared));
v8::Isolate* isolate2 = v8::Isolate::New(create_params);
Isolate* i_isolate2 = reinterpret_cast<i::Isolate*>(isolate2);
isolate2->Enter();
{
HandleScope scope(i_isolate2);
USE(Managed<DeleteCounter>::From(i_isolate2, 0, shared));
shared.reset();
}
isolate2->Exit();
isolate2->Dispose();
// The DeleteCounter is kept alive by the Managed in the first isolate.
CHECK_EQ(0, deleted);
}
// Should be deleted after the first isolate is destroyed.
isolate1->Exit();
isolate1->Dispose();
CHECK_EQ(1, deleted);
}
TEST_F(ManagedTest, CollectAcrossIsolates) {
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = isolate()->array_buffer_allocator();
int deleted = 0;
v8::Isolate* isolate1 = v8::Isolate::New(create_params);
Isolate* i_isolate1 = reinterpret_cast<i::Isolate*>(isolate1);
isolate1->Enter();
{
HandleScope scope1(i_isolate1);
auto shared = std::make_shared<DeleteCounter>(&deleted);
USE(Managed<DeleteCounter>::From(i_isolate1, 0, shared));
v8::Isolate* isolate2 = v8::Isolate::New(create_params);
Isolate* i_isolate2 = reinterpret_cast<i::Isolate*>(isolate2);
isolate2->Enter();
{
HandleScope scope(i_isolate2);
USE(Managed<DeleteCounter>::From(i_isolate2, 0, shared));
shared.reset();
}
InvokeMemoryReducingMajorGCs(i_isolate2);
CHECK_EQ(0, deleted);
isolate2->Exit();
isolate2->Dispose();
CHECK_EQ(0, deleted);
}
// Should be deleted after the first isolate is destroyed.
// We need to invoke GC without stack, otherwise the object may survive.
{
DisableConservativeStackScanningScopeForTesting scope(i_isolate1->heap());
InvokeMemoryReducingMajorGCs(i_isolate1);
}
CHECK_EQ(1, deleted);
isolate1->Exit();
isolate1->Dispose();
CHECK_EQ(1, deleted);
}
} // namespace v8::internal

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,730 @@
// Copyright 2016 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 <cmath>
#include <iostream>
#include <limits>
#include "src/api/api-inl.h"
#include "src/codegen/compiler.h"
#include "src/objects/hash-table-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/objects.h"
#include "src/objects/string-set.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
namespace {
bool IsInStringInstanceTypeList(InstanceType instance_type) {
switch (instance_type) {
#define ASSERT_INSTANCE_TYPE(type, ...) \
static_assert(InstanceType::type < InstanceType::FIRST_NONSTRING_TYPE);
STRING_TYPE_LIST(ASSERT_INSTANCE_TYPE)
#undef ASSERT_INSTANCE_TYPE
#define TEST_INSTANCE_TYPE(type, ...) case InstanceType::type:
STRING_TYPE_LIST(TEST_INSTANCE_TYPE)
#undef TEST_INSTANCE_TYPE
return true;
default:
EXPECT_LE(InstanceType::FIRST_NONSTRING_TYPE, instance_type);
return false;
}
}
void CheckOneInstanceType(InstanceType instance_type) {
if (IsInStringInstanceTypeList(instance_type)) {
EXPECT_TRUE((instance_type & kIsNotStringMask) == kStringTag)
<< "Failing IsString mask check for " << instance_type;
} else {
EXPECT_FALSE((instance_type & kIsNotStringMask) == kStringTag)
<< "Failing !IsString mask check for " << instance_type;
}
}
} // namespace
TEST(Object, InstanceTypeList) {
#define TEST_INSTANCE_TYPE(type) CheckOneInstanceType(InstanceType::type);
INSTANCE_TYPE_LIST(TEST_INSTANCE_TYPE)
#undef TEST_INSTANCE_TYPE
}
TEST(Object, InstanceTypeListOrder) {
int current = 0;
int prev = -1;
InstanceType current_type = static_cast<InstanceType>(current);
EXPECT_EQ(current_type, InstanceType::FIRST_TYPE);
EXPECT_EQ(current_type, InstanceType::INTERNALIZED_TWO_BYTE_STRING_TYPE);
#define TEST_INSTANCE_TYPE(type) \
current_type = InstanceType::type; \
current = static_cast<int>(current_type); \
if (current > static_cast<int>(LAST_NAME_TYPE)) { \
EXPECT_LE(prev + 1, current); \
} \
EXPECT_LT(prev, current) << " INSTANCE_TYPE_LIST is not ordered: " \
<< "last = " << static_cast<InstanceType>(prev) \
<< " vs. current = " << current_type; \
prev = current;
// Only test hand-written portion of instance type list. The generated portion
// doesn't run the same risk of getting out of order, and it does emit type
// names out of numerical order in one case: JS_OBJECT_TYPE is emitted before
// its subclass types, because types are emitted in depth-first pre-order
// traversal order, and some of its subclass types are numerically earlier.
INSTANCE_TYPE_LIST_BASE(TEST_INSTANCE_TYPE)
#undef TEST_INSTANCE_TYPE
}
TEST(Object, StructListOrder) {
int current = static_cast<int>(InstanceType::FIRST_STRUCT_TYPE);
int prev = current - 1;
ASSERT_LT(0, prev);
InstanceType current_type = static_cast<InstanceType>(current);
#define TEST_STRUCT(TYPE, class, name) \
current_type = InstanceType::TYPE; \
current = static_cast<int>(current_type); \
EXPECT_LE(prev + 1, current) \
<< " STRUCT_LIST is not ordered: " \
<< " last = " << static_cast<InstanceType>(prev) \
<< " vs. current = " << current_type; \
prev = current;
STRUCT_LIST_GENERATOR(STRUCT_LIST_ADAPTER, TEST_STRUCT)
#undef TEST_STRUCT
}
using ObjectWithIsolate = TestWithIsolate;
TEST_F(ObjectWithIsolate, DictionaryGrowth) {
Handle<NumberDictionary> dict = NumberDictionary::New(isolate(), 1);
DirectHandle<Object> value = isolate()->factory()->null_value();
PropertyDetails details = PropertyDetails::Empty();
// This test documents the expected growth behavior of a dictionary getting
// elements added to it one by one.
static_assert(HashTableBase::kMinCapacity == 4);
uint32_t i = 1;
// 3 elements fit into the initial capacity.
for (; i <= 3; i++) {
dict = NumberDictionary::Add(isolate(), dict, i, value, details);
CHECK_EQ(4, dict->Capacity());
}
// 4th element triggers growth.
DCHECK_EQ(4, i);
for (; i <= 5; i++) {
dict = NumberDictionary::Add(isolate(), dict, i, value, details);
CHECK_EQ(8, dict->Capacity());
}
// 6th element triggers growth.
DCHECK_EQ(6, i);
for (; i <= 11; i++) {
dict = NumberDictionary::Add(isolate(), dict, i, value, details);
CHECK_EQ(16, dict->Capacity());
}
// 12th element triggers growth.
DCHECK_EQ(12, i);
for (; i <= 21; i++) {
dict = NumberDictionary::Add(isolate(), dict, i, value, details);
CHECK_EQ(32, dict->Capacity());
}
// 22nd element triggers growth.
DCHECK_EQ(22, i);
for (; i <= 43; i++) {
dict = NumberDictionary::Add(isolate(), dict, i, value, details);
CHECK_EQ(64, dict->Capacity());
}
// 44th element triggers growth.
DCHECK_EQ(44, i);
for (; i <= 50; i++) {
dict = NumberDictionary::Add(isolate(), dict, i, value, details);
CHECK_EQ(128, dict->Capacity());
}
// If we grow by larger chunks, the next (sufficiently big) power of 2 is
// chosen as the capacity.
dict = NumberDictionary::New(isolate(), 1);
dict = NumberDictionary::EnsureCapacity(isolate(), dict, 65);
CHECK_EQ(128, dict->Capacity());
dict = NumberDictionary::New(isolate(), 1);
dict = NumberDictionary::EnsureCapacity(isolate(), dict, 30);
CHECK_EQ(64, dict->Capacity());
}
TEST_F(TestWithNativeContext, EmptyFunctionScopeInfo) {
// Check that the empty_function has a properly set up ScopeInfo.
DirectHandle<JSFunction> function = RunJS<JSFunction>("(function(){})");
DirectHandle<ScopeInfo> scope_info(function->shared()->scope_info(),
function->GetIsolate());
DirectHandle<ScopeInfo> empty_function_scope_info(
isolate()->empty_function()->shared()->scope_info(),
function->GetIsolate());
EXPECT_EQ(scope_info->Flags(), empty_function_scope_info->Flags());
EXPECT_EQ(scope_info->ParameterCount(),
empty_function_scope_info->ParameterCount());
EXPECT_EQ(scope_info->ContextLocalCount(),
empty_function_scope_info->ContextLocalCount());
}
using ObjectTest = TestWithContext;
static void CheckObject(Isolate* isolate, DirectHandle<Object> obj,
const char* string) {
DirectHandle<String> print_string = String::Flatten(
isolate,
indirect_handle(Object::NoSideEffectsToString(isolate, obj), isolate));
CHECK(print_string->IsOneByteEqualTo(base::CStrVector(string)));
}
static void CheckSmi(Isolate* isolate, int value, const char* string) {
DirectHandle<Object> handle(Smi::FromInt(value), isolate);
CheckObject(isolate, handle, string);
}
static void CheckString(Isolate* isolate, const char* value,
const char* string) {
DirectHandle<String> handle(
isolate->factory()->NewStringFromAsciiChecked(value));
CheckObject(isolate, handle, string);
}
static void CheckNumber(Isolate* isolate, double value, const char* string) {
DirectHandle<Object> number = isolate->factory()->NewNumber(value);
CHECK(IsNumber(*number));
CheckObject(isolate, number, string);
}
static void CheckBoolean(Isolate* isolate, bool value, const char* string) {
CheckObject(isolate, isolate->factory()->ToBoolean(value), string);
}
TEST_F(ObjectTest, NoSideEffectsToString) {
Factory* factory = i_isolate()->factory();
HandleScope scope(i_isolate());
CheckString(i_isolate(), "fisk hest", "fisk hest");
CheckNumber(i_isolate(), 42.3, "42.3");
CheckSmi(i_isolate(), 42, "42");
CheckBoolean(i_isolate(), true, "true");
CheckBoolean(i_isolate(), false, "false");
CheckBoolean(i_isolate(), false, "false");
DirectHandle<Object> smi_42(Smi::FromInt(42), i_isolate());
CheckObject(i_isolate(),
BigInt::FromNumber(i_isolate(), smi_42).ToHandleChecked(), "42");
CheckObject(i_isolate(), factory->undefined_value(), "undefined");
CheckObject(i_isolate(), factory->null_value(), "null");
CheckObject(i_isolate(), factory->error_to_string(), "[object Error]");
CheckObject(i_isolate(), factory->unscopables_symbol(),
"Symbol(Symbol.unscopables)");
CheckObject(
i_isolate(),
factory->NewError(i_isolate()->error_function(), factory->empty_string()),
"Error");
CheckObject(
i_isolate(),
factory->NewError(i_isolate()->error_function(),
factory->NewStringFromAsciiChecked("fisk hest")),
"Error: fisk hest");
CheckObject(i_isolate(), factory->NewJSObject(i_isolate()->object_function()),
"#<Object>");
CheckObject(
i_isolate(),
factory->NewJSProxy(factory->NewJSObject(i_isolate()->object_function()),
factory->NewJSObject(i_isolate()->object_function())),
"#<Object>");
}
TEST_F(ObjectTest, EnumCache) {
i::Factory* factory = i_isolate()->factory();
v8::HandleScope scope(isolate());
// Create a nice transition tree:
// (a) --> (b) --> (c) shared DescriptorArray 1
// |
// +---> (cc) shared DescriptorArray 2
RunJS(
"function O(a) { this.a = 1 };"
"a = new O();"
"b = new O();"
"b.b = 2;"
"c = new O();"
"c.b = 2;"
"c.c = 3;"
"cc = new O();"
"cc.b = 2;"
"cc.cc = 4;");
DirectHandle<JSObject> a = Cast<JSObject>(v8::Utils::OpenDirectHandle(
*context()->Global()->Get(context(), NewString("a")).ToLocalChecked()));
DirectHandle<JSObject> b = Cast<JSObject>(v8::Utils::OpenDirectHandle(
*context()->Global()->Get(context(), NewString("b")).ToLocalChecked()));
DirectHandle<JSObject> c = Cast<JSObject>(v8::Utils::OpenDirectHandle(
*context()->Global()->Get(context(), NewString("c")).ToLocalChecked()));
DirectHandle<JSObject> cc = Cast<JSObject>(v8::Utils::OpenDirectHandle(
*context()->Global()->Get(context(), NewString("cc")).ToLocalChecked()));
// Check the transition tree.
CHECK_EQ(a->map()->instance_descriptors(), b->map()->instance_descriptors());
CHECK_EQ(b->map()->instance_descriptors(), c->map()->instance_descriptors());
CHECK_NE(c->map()->instance_descriptors(), cc->map()->instance_descriptors());
CHECK_NE(b->map()->instance_descriptors(), cc->map()->instance_descriptors());
// Check that the EnumLength is unset.
CHECK_EQ(a->map()->EnumLength(), kInvalidEnumCacheSentinel);
CHECK_EQ(b->map()->EnumLength(), kInvalidEnumCacheSentinel);
CHECK_EQ(c->map()->EnumLength(), kInvalidEnumCacheSentinel);
CHECK_EQ(cc->map()->EnumLength(), kInvalidEnumCacheSentinel);
// Check that the EnumCache is empty.
CHECK_EQ(a->map()->instance_descriptors()->enum_cache(),
*factory->empty_enum_cache());
CHECK_EQ(b->map()->instance_descriptors()->enum_cache(),
*factory->empty_enum_cache());
CHECK_EQ(c->map()->instance_descriptors()->enum_cache(),
*factory->empty_enum_cache());
CHECK_EQ(cc->map()->instance_descriptors()->enum_cache(),
*factory->empty_enum_cache());
// The EnumCache is shared on the DescriptorArray, creating it on {cc} has no
// effect on the other maps.
RunJS("var s = 0; for (let key in cc) { s += cc[key] };");
{
CHECK_EQ(a->map()->EnumLength(), kInvalidEnumCacheSentinel);
CHECK_EQ(b->map()->EnumLength(), kInvalidEnumCacheSentinel);
CHECK_EQ(c->map()->EnumLength(), kInvalidEnumCacheSentinel);
CHECK_EQ(cc->map()->EnumLength(), 3);
CHECK_EQ(a->map()->instance_descriptors()->enum_cache(),
*factory->empty_enum_cache());
CHECK_EQ(b->map()->instance_descriptors()->enum_cache(),
*factory->empty_enum_cache());
CHECK_EQ(c->map()->instance_descriptors()->enum_cache(),
*factory->empty_enum_cache());
Tagged<EnumCache> enum_cache =
cc->map()->instance_descriptors()->enum_cache();
CHECK_NE(enum_cache, *factory->empty_enum_cache());
CHECK_EQ(enum_cache->keys()->length(), 3);
CHECK_EQ(enum_cache->indices()->length(), 3);
}
// Initializing the EnumCache for the the topmost map {a} will not create the
// cache for the other maps.
RunJS("var s = 0; for (let key in a) { s += a[key] };");
{
CHECK_EQ(a->map()->EnumLength(), 1);
CHECK_EQ(b->map()->EnumLength(), kInvalidEnumCacheSentinel);
CHECK_EQ(c->map()->EnumLength(), kInvalidEnumCacheSentinel);
CHECK_EQ(cc->map()->EnumLength(), 3);
// The enum cache is shared on the descriptor array of maps {a}, {b} and
// {c} only.
Tagged<EnumCache> enum_cache =
a->map()->instance_descriptors()->enum_cache();
CHECK_NE(enum_cache, *factory->empty_enum_cache());
CHECK_NE(cc->map()->instance_descriptors()->enum_cache(),
*factory->empty_enum_cache());
CHECK_NE(cc->map()->instance_descriptors()->enum_cache(), enum_cache);
CHECK_EQ(a->map()->instance_descriptors()->enum_cache(), enum_cache);
CHECK_EQ(b->map()->instance_descriptors()->enum_cache(), enum_cache);
CHECK_EQ(c->map()->instance_descriptors()->enum_cache(), enum_cache);
CHECK_EQ(enum_cache->keys()->length(), 1);
CHECK_EQ(enum_cache->indices()->length(), 1);
}
// Creating the EnumCache for {c} will create a new EnumCache on the shared
// DescriptorArray.
DirectHandle<EnumCache> previous_enum_cache(
a->map()->instance_descriptors()->enum_cache(), a->GetIsolate());
DirectHandle<FixedArray> previous_keys(previous_enum_cache->keys(),
a->GetIsolate());
DirectHandle<FixedArray> previous_indices(previous_enum_cache->indices(),
a->GetIsolate());
RunJS("var s = 0; for (let key in c) { s += c[key] };");
{
CHECK_EQ(a->map()->EnumLength(), 1);
CHECK_EQ(b->map()->EnumLength(), kInvalidEnumCacheSentinel);
CHECK_EQ(c->map()->EnumLength(), 3);
CHECK_EQ(cc->map()->EnumLength(), 3);
Tagged<EnumCache> enum_cache =
c->map()->instance_descriptors()->enum_cache();
CHECK_NE(enum_cache, *factory->empty_enum_cache());
// The keys and indices caches are updated.
CHECK_EQ(enum_cache, *previous_enum_cache);
CHECK_NE(enum_cache->keys(), *previous_keys);
CHECK_NE(enum_cache->indices(), *previous_indices);
CHECK_EQ(previous_keys->length(), 1);
CHECK_EQ(previous_indices->length(), 1);
CHECK_EQ(enum_cache->keys()->length(), 3);
CHECK_EQ(enum_cache->indices()->length(), 3);
// The enum cache is shared on the descriptor array of maps {a}, {b} and
// {c} only.
CHECK_NE(cc->map()->instance_descriptors()->enum_cache(),
*factory->empty_enum_cache());
CHECK_NE(cc->map()->instance_descriptors()->enum_cache(), enum_cache);
CHECK_NE(cc->map()->instance_descriptors()->enum_cache(),
*previous_enum_cache);
CHECK_EQ(a->map()->instance_descriptors()->enum_cache(), enum_cache);
CHECK_EQ(b->map()->instance_descriptors()->enum_cache(), enum_cache);
CHECK_EQ(c->map()->instance_descriptors()->enum_cache(), enum_cache);
}
// {b} can reuse the existing EnumCache, hence we only need to set the correct
// EnumLength on the map without modifying the cache itself.
previous_enum_cache = direct_handle(
a->map()->instance_descriptors()->enum_cache(), a->GetIsolate());
previous_keys = direct_handle(previous_enum_cache->keys(), a->GetIsolate());
previous_indices =
direct_handle(previous_enum_cache->indices(), a->GetIsolate());
RunJS("var s = 0; for (let key in b) { s += b[key] };");
{
CHECK_EQ(a->map()->EnumLength(), 1);
CHECK_EQ(b->map()->EnumLength(), 2);
CHECK_EQ(c->map()->EnumLength(), 3);
CHECK_EQ(cc->map()->EnumLength(), 3);
Tagged<EnumCache> enum_cache =
c->map()->instance_descriptors()->enum_cache();
CHECK_NE(enum_cache, *factory->empty_enum_cache());
// The keys and indices caches are not updated.
CHECK_EQ(enum_cache, *previous_enum_cache);
CHECK_EQ(enum_cache->keys(), *previous_keys);
CHECK_EQ(enum_cache->indices(), *previous_indices);
CHECK_EQ(enum_cache->keys()->length(), 3);
CHECK_EQ(enum_cache->indices()->length(), 3);
// The enum cache is shared on the descriptor array of maps {a}, {b} and
// {c} only.
CHECK_NE(cc->map()->instance_descriptors()->enum_cache(),
*factory->empty_enum_cache());
CHECK_NE(cc->map()->instance_descriptors()->enum_cache(), enum_cache);
CHECK_NE(cc->map()->instance_descriptors()->enum_cache(),
*previous_enum_cache);
CHECK_EQ(a->map()->instance_descriptors()->enum_cache(), enum_cache);
CHECK_EQ(b->map()->instance_descriptors()->enum_cache(), enum_cache);
CHECK_EQ(c->map()->instance_descriptors()->enum_cache(), enum_cache);
}
}
TEST_F(ObjectTest, ObjectMethodsThatTruncateMinusZero) {
Factory* factory = i_isolate()->factory();
Handle<Object> minus_zero = factory->NewNumber(-1.0 * 0.0);
CHECK(IsMinusZero(*minus_zero));
DirectHandle<Object> result =
Object::ToInteger(i_isolate(), minus_zero).ToHandleChecked();
CHECK(IsZero(*result));
result = Object::ToLength(i_isolate(), minus_zero).ToHandleChecked();
CHECK(IsZero(*result));
// Choose an error message template, doesn't matter which.
result = Object::ToIndex(i_isolate(), minus_zero,
MessageTemplate::kInvalidAtomicAccessIndex)
.ToHandleChecked();
CHECK(IsZero(*result));
}
#define TEST_FUNCTION_KIND(Name) \
TEST_F(ObjectTest, Name) { \
for (uint32_t i = 0; \
i < static_cast<uint32_t>(FunctionKind::kLastFunctionKind); i++) { \
FunctionKind kind = static_cast<FunctionKind>(i); \
CHECK_EQ(FunctionKind##Name(kind), Name(kind)); \
} \
}
bool FunctionKindIsArrowFunction(FunctionKind kind) {
switch (kind) {
case FunctionKind::kArrowFunction:
case FunctionKind::kAsyncArrowFunction:
return true;
default:
return false;
}
}
TEST_FUNCTION_KIND(IsArrowFunction)
bool FunctionKindIsAsyncGeneratorFunction(FunctionKind kind) {
switch (kind) {
case FunctionKind::kAsyncConciseGeneratorMethod:
case FunctionKind::kStaticAsyncConciseGeneratorMethod:
case FunctionKind::kAsyncGeneratorFunction:
return true;
default:
return false;
}
}
TEST_FUNCTION_KIND(IsAsyncGeneratorFunction)
bool FunctionKindIsGeneratorFunction(FunctionKind kind) {
switch (kind) {
case FunctionKind::kConciseGeneratorMethod:
case FunctionKind::kStaticConciseGeneratorMethod:
case FunctionKind::kAsyncConciseGeneratorMethod:
case FunctionKind::kStaticAsyncConciseGeneratorMethod:
case FunctionKind::kGeneratorFunction:
case FunctionKind::kAsyncGeneratorFunction:
return true;
default:
return false;
}
}
TEST_FUNCTION_KIND(IsGeneratorFunction)
bool FunctionKindIsAsyncFunction(FunctionKind kind) {
switch (kind) {
case FunctionKind::kAsyncFunction:
case FunctionKind::kAsyncArrowFunction:
case FunctionKind::kAsyncConciseMethod:
case FunctionKind::kStaticAsyncConciseMethod:
case FunctionKind::kAsyncConciseGeneratorMethod:
case FunctionKind::kStaticAsyncConciseGeneratorMethod:
case FunctionKind::kAsyncGeneratorFunction:
return true;
default:
return false;
}
}
TEST_FUNCTION_KIND(IsAsyncFunction)
bool FunctionKindIsConciseMethod(FunctionKind kind) {
switch (kind) {
case FunctionKind::kConciseMethod:
case FunctionKind::kStaticConciseMethod:
case FunctionKind::kConciseGeneratorMethod:
case FunctionKind::kStaticConciseGeneratorMethod:
case FunctionKind::kAsyncConciseMethod:
case FunctionKind::kStaticAsyncConciseMethod:
case FunctionKind::kAsyncConciseGeneratorMethod:
case FunctionKind::kStaticAsyncConciseGeneratorMethod:
case FunctionKind::kClassMembersInitializerFunction:
return true;
default:
return false;
}
}
TEST_FUNCTION_KIND(IsConciseMethod)
bool FunctionKindIsAccessorFunction(FunctionKind kind) {
switch (kind) {
case FunctionKind::kGetterFunction:
case FunctionKind::kStaticGetterFunction:
case FunctionKind::kSetterFunction:
case FunctionKind::kStaticSetterFunction:
return true;
default:
return false;
}
}
TEST_FUNCTION_KIND(IsAccessorFunction)
bool FunctionKindIsDefaultConstructor(FunctionKind kind) {
switch (kind) {
case FunctionKind::kDefaultBaseConstructor:
case FunctionKind::kDefaultDerivedConstructor:
return true;
default:
return false;
}
}
TEST_FUNCTION_KIND(IsDefaultConstructor)
bool FunctionKindIsBaseConstructor(FunctionKind kind) {
switch (kind) {
case FunctionKind::kBaseConstructor:
case FunctionKind::kDefaultBaseConstructor:
return true;
default:
return false;
}
}
TEST_FUNCTION_KIND(IsBaseConstructor)
bool FunctionKindIsDerivedConstructor(FunctionKind kind) {
switch (kind) {
case FunctionKind::kDefaultDerivedConstructor:
case FunctionKind::kDerivedConstructor:
return true;
default:
return false;
}
}
TEST_FUNCTION_KIND(IsDerivedConstructor)
bool FunctionKindIsClassConstructor(FunctionKind kind) {
switch (kind) {
case FunctionKind::kBaseConstructor:
case FunctionKind::kDefaultBaseConstructor:
case FunctionKind::kDefaultDerivedConstructor:
case FunctionKind::kDerivedConstructor:
return true;
default:
return false;
}
}
TEST_FUNCTION_KIND(IsClassConstructor)
bool FunctionKindIsConstructable(FunctionKind kind) {
switch (kind) {
case FunctionKind::kGetterFunction:
case FunctionKind::kStaticGetterFunction:
case FunctionKind::kSetterFunction:
case FunctionKind::kStaticSetterFunction:
case FunctionKind::kArrowFunction:
case FunctionKind::kAsyncArrowFunction:
case FunctionKind::kAsyncFunction:
case FunctionKind::kAsyncConciseMethod:
case FunctionKind::kStaticAsyncConciseMethod:
case FunctionKind::kAsyncConciseGeneratorMethod:
case FunctionKind::kStaticAsyncConciseGeneratorMethod:
case FunctionKind::kAsyncGeneratorFunction:
case FunctionKind::kGeneratorFunction:
case FunctionKind::kConciseGeneratorMethod:
case FunctionKind::kStaticConciseGeneratorMethod:
case FunctionKind::kConciseMethod:
case FunctionKind::kStaticConciseMethod:
case FunctionKind::kClassMembersInitializerFunction:
return false;
default:
return true;
}
}
TEST_FUNCTION_KIND(IsConstructable)
bool FunctionKindIsStrictFunctionWithoutPrototype(FunctionKind kind) {
return IsArrowFunction(kind) || IsConciseMethod(kind) ||
IsAccessorFunction(kind);
}
TEST_FUNCTION_KIND(IsStrictFunctionWithoutPrototype)
#undef TEST_FUNCTION_KIND
TEST_F(ObjectTest, ConstructorInstanceTypes) {
bool flag_was_enabled = i::v8_flags.js_float16array;
i::v8_flags.js_float16array = true;
v8::HandleScope scope(isolate());
DirectHandle<NativeContext> context = i_isolate()->native_context();
DisallowGarbageCollection no_gc;
for (int i = 0; i < Context::NATIVE_CONTEXT_SLOTS; i++) {
Tagged<Object> value = context->get(i);
if (!IsJSFunction(value)) continue;
InstanceType instance_type =
Cast<JSFunction>(value)->map()->instance_type();
switch (i) {
case Context::ARRAY_FUNCTION_INDEX:
CHECK_EQ(instance_type, JS_ARRAY_CONSTRUCTOR_TYPE);
break;
case Context::REGEXP_FUNCTION_INDEX:
CHECK_EQ(instance_type, JS_REG_EXP_CONSTRUCTOR_TYPE);
break;
case Context::PROMISE_FUNCTION_INDEX:
CHECK_EQ(instance_type, JS_PROMISE_CONSTRUCTOR_TYPE);
break;
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \
case Context::TYPE##_ARRAY_FUN_INDEX: \
CHECK_EQ(instance_type, TYPE##_TYPED_ARRAY_CONSTRUCTOR_TYPE); \
break;
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
default:
// All the other functions must have the default instance type.
CHECK_EQ(instance_type, JS_FUNCTION_TYPE);
break;
}
}
i::v8_flags.js_float16array = flag_was_enabled;
}
TEST_F(ObjectTest, AddDataPropertyNameCollision) {
v8::HandleScope scope(isolate());
Factory* factory = i_isolate()->factory();
DirectHandle<JSObject> object =
factory->NewJSObject(i_isolate()->object_function());
DirectHandle<String> key = factory->NewStringFromStaticChars("key_string");
DirectHandle<Object> value1(Smi::FromInt(0), i_isolate());
DirectHandle<Object> value2 = factory->NewStringFromAsciiChecked("corrupt");
LookupIterator outer_it(i_isolate(), object, key, object,
LookupIterator::OWN_SKIP_INTERCEPTOR);
{
LookupIterator inner_it(i_isolate(), object, key, object,
LookupIterator::OWN_SKIP_INTERCEPTOR);
CHECK(Object::AddDataProperty(&inner_it, value1, NONE,
Just(ShouldThrow::kThrowOnError),
StoreOrigin::kNamed)
.IsJust());
}
EXPECT_DEATH_IF_SUPPORTED(
Object::AddDataProperty(&outer_it, value2, NONE,
Just(ShouldThrow::kThrowOnError),
StoreOrigin::kNamed)
.IsJust(),
"");
}
TEST_F(ObjectTest, AddDataPropertyNameCollisionDeprecatedMap) {
v8::HandleScope scope(isolate());
Factory* factory = i_isolate()->factory();
// Create two identical maps
RunJS(
"a = {'regular_prop':5};"
"b = {'regular_prop':5};");
DirectHandle<JSObject> a = Cast<JSObject>(v8::Utils::OpenHandle(
*context()->Global()->Get(context(), NewString("a")).ToLocalChecked()));
DirectHandle<JSObject> b = Cast<JSObject>(v8::Utils::OpenHandle(
*context()->Global()->Get(context(), NewString("b")).ToLocalChecked()));
CHECK(a->map() == b->map());
DirectHandle<String> key =
factory->NewStringFromStaticChars("corrupted_prop");
DirectHandle<Object> value = factory->NewStringFromAsciiChecked("corrupt");
LookupIterator it(i_isolate(), a, key, a,
LookupIterator::OWN_SKIP_INTERCEPTOR);
// Transition `a`'s map to deprecated
RunJS(
"a.corrupted_prop = 1;"
"b.regular_prop = 5.5;");
CHECK(a->map()->is_deprecated());
EXPECT_DEATH_IF_SUPPORTED(
Object::AddDataProperty(&it, value, NONE,
Just(ShouldThrow::kThrowOnError),
StoreOrigin::kNamed)
.IsJust(),
"");
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,60 @@
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/objects/property-details.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
using RepresentationTest = ::testing::Test;
void TestPairPositive(Representation more_general,
Representation less_general) {
CHECK(more_general.is_more_general_than(less_general));
}
void TestPairNegative(Representation more_general,
Representation less_general) {
CHECK(!more_general.is_more_general_than(less_general));
}
TEST_F(RepresentationTest, RepresentationMoreGeneralThan) {
TestPairNegative(Representation::None(), Representation::None());
TestPairPositive(Representation::Smi(), Representation::None());
TestPairPositive(Representation::HeapObject(), Representation::None());
TestPairPositive(Representation::Double(), Representation::None());
TestPairPositive(Representation::Tagged(), Representation::None());
TestPairNegative(Representation::None(), Representation::Smi());
TestPairNegative(Representation::Smi(), Representation::Smi());
TestPairNegative(Representation::HeapObject(), Representation::Smi());
TestPairPositive(Representation::Double(), Representation::Smi());
TestPairPositive(Representation::Tagged(), Representation::Smi());
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,125 @@
// Copyright 2018 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/common/globals.h"
#include "src/heap/heap-inl.h"
#include "src/heap/memory-chunk-metadata.h"
#include "src/objects/cell.h"
#include "src/objects/feedback-cell.h"
#include "src/objects/script.h"
#include "src/roots/roots-inl.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
using RootsTest = TestWithIsolate;
namespace {
AllocationSpace GetSpaceFromObject(Tagged<Object> object) {
DCHECK(IsHeapObject(object));
MemoryChunk* chunk = MemoryChunk::FromHeapObject(Cast<HeapObject>(object));
if (chunk->InReadOnlySpace()) return RO_SPACE;
return chunk->Metadata()->owner()->identity();
}
} // namespace
#define CHECK_IN_RO_SPACE(type, name, CamelName) \
Tagged<HeapObject> name = roots.name(); \
CHECK_EQ(RO_SPACE, GetSpaceFromObject(name));
// The following tests check that all the roots accessible via ReadOnlyRoots are
// in RO_SPACE.
TEST_F(RootsTest, TestReadOnlyRoots) {
ReadOnlyRoots roots(i_isolate());
READ_ONLY_ROOT_LIST(CHECK_IN_RO_SPACE)
}
#undef CHECK_IN_RO_SPACE
namespace {
// Applies to objects in mutable root slots; specific slots may point into RO
// space (e.g. because the slot value may change and only the initial value is
// in RO space; or, because RO promotion dynamically decides whether to promote
// the slot value to RO space).
bool CanBeInReadOnlySpace(Factory* factory, Handle<Object> object) {
// Entries in this list are in STRONG_MUTABLE_MOVABLE_ROOT_LIST, but may
// initially point to objects that are in RO_SPACE.
#define INITIALLY_READ_ONLY_ROOT_LIST(V) \
V(api_private_symbol_table) \
V(api_symbol_table) \
V(basic_block_profiling_data) \
V(builtins_constants_table) \
V(current_microtask) \
V(detached_contexts) \
V(feedback_vectors_for_profiling_tools) \
V(shared_wasm_memories) \
V(materialized_objects) \
V(public_symbol_table) \
V(serialized_global_proxy_sizes) \
V(serialized_objects) \
IF_WASM(V, js_to_wasm_wrappers) \
IF_WASM(V, wasm_canonical_rtts) \
V(weak_refs_keep_during_job)
#define TEST_CAN_BE_READ_ONLY(name) \
if (factory->name().address() == object.address()) return true;
INITIALLY_READ_ONLY_ROOT_LIST(TEST_CAN_BE_READ_ONLY)
#undef TEST_CAN_BE_READ_ONLY
#undef INITIALLY_READ_ONLY_ROOT_LIST
// May be promoted to RO space, see read-only-promotion.h.
if (IsAccessorInfo(*object)) return true;
if (IsFunctionTemplateInfo(*object)) return true;
if (IsFunctionTemplateRareData(*object)) return true;
if (IsSharedFunctionInfo(*object)) return true;
return false;
}
// Some mutable roots may initially point to undefined until they are properly
// initialized.
bool IsUninitialized(DirectHandle<Object> object) {
return !IsTrustedObject(*object) && IsUndefined(*object);
}
} // namespace
// The CHECK_EQ line is there just to ensure that the root is publicly
// accessible from Heap, but ultimately the factory is used as it provides
// handles that have the address in the root table.
#define CHECK_NOT_IN_RO_SPACE(type, name, CamelName) \
Handle<Object> name = factory->name(); \
CHECK_EQ(*name, heap->name()); \
if (IsHeapObject(*name) && !CanBeInReadOnlySpace(factory, name) && \
!IsUninitialized(name)) { \
CHECK_NE(RO_SPACE, GetSpaceFromObject(Cast<HeapObject>(*name))); \
}
// The following tests check that all the roots accessible via public Heap
// accessors are not in RO_SPACE (with some exceptions, see
// CanBeInReadOnlySpace).
TEST_F(RootsTest, TestHeapRootsNotReadOnly) {
Factory* factory = i_isolate()->factory();
Heap* heap = i_isolate()->heap();
MUTABLE_ROOT_LIST(CHECK_NOT_IN_RO_SPACE)
}
TEST_F(RootsTest, TestHeapNumberList) {
ReadOnlyRoots roots(isolate());
for (auto pos = RootIndex::kFirstReadOnlyRoot;
pos <= RootIndex::kLastReadOnlyRoot; ++pos) {
auto obj = roots.object_at(pos);
bool in_nr_range = pos >= RootIndex::kFirstHeapNumberRoot &&
pos <= RootIndex::kLastHeapNumberRoot;
CHECK_EQ(IsHeapNumber(obj), in_nr_range);
}
}
#undef CHECK_NOT_IN_RO_SPACE
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,73 @@
// Copyright 2022 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/objects/swiss-hash-table-helpers.h"
#include <vector>
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::ElementsAre;
namespace v8 {
namespace internal {
namespace swiss_table {
template <typename T>
class SwissTableGroupTest : public testing::Test {};
using GroupTypes = testing::Types<
#if V8_SWISS_TABLE_HAVE_SSE2_HOST
GroupSse2Impl,
#endif
GroupSse2Polyfill, GroupPortableImpl>;
TYPED_TEST_SUITE(SwissTableGroupTest, GroupTypes);
// Tests imported from Abseil's raw_hash_set_test.cc, modified to be
// parameterized.
TYPED_TEST(SwissTableGroupTest, EmptyGroup) {
const ctrl_t kEmptyGroup[16] = {
kSentinel, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty,
kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty,
};
for (h2_t h = 0; h != 128; ++h) EXPECT_FALSE(TypeParam{kEmptyGroup}.Match(h));
}
TYPED_TEST(SwissTableGroupTest, Match) {
if (TypeParam::kWidth == 16) {
ctrl_t group[] = {kEmpty, 1, kDeleted, 3, kEmpty, 5, kSentinel, 7,
7, 5, 3, 1, 1, 1, 1, 1};
EXPECT_THAT(TypeParam{group}.Match(0), ElementsAre());
EXPECT_THAT(TypeParam{group}.Match(1), ElementsAre(1, 11, 12, 13, 14, 15));
EXPECT_THAT(TypeParam{group}.Match(3), ElementsAre(3, 10));
EXPECT_THAT(TypeParam{group}.Match(5), ElementsAre(5, 9));
EXPECT_THAT(TypeParam{group}.Match(7), ElementsAre(7, 8));
} else if (TypeParam::kWidth == 8) {
ctrl_t group[] = {kEmpty, 1, 2, kDeleted, 2, 1, kSentinel, 1};
EXPECT_THAT(TypeParam{group}.Match(0), ElementsAre());
EXPECT_THAT(TypeParam{group}.Match(1), ElementsAre(1, 5, 7));
EXPECT_THAT(TypeParam{group}.Match(2), ElementsAre(2, 4));
} else {
FAIL() << "No test coverage for kWidth==" << TypeParam::kWidth;
}
}
TYPED_TEST(SwissTableGroupTest, MatchEmpty) {
if (TypeParam::kWidth == 16) {
ctrl_t group[] = {kEmpty, 1, kDeleted, 3, kEmpty, 5, kSentinel, 7,
7, 5, 3, 1, 1, 1, 1, 1};
EXPECT_THAT(TypeParam{group}.MatchEmpty(), ElementsAre(0, 4));
} else if (TypeParam::kWidth == 8) {
ctrl_t group[] = {kEmpty, 1, 2, kDeleted, 2, 1, kSentinel, 1};
EXPECT_THAT(TypeParam{group}.MatchEmpty(), ElementsAre(0));
} else {
FAIL() << "No test coverage for kWidth==" << TypeParam::kWidth;
}
}
} // namespace swiss_table
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,80 @@
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Check that we can traverse very deep stacks of ConsStrings using
// StringCharacterStram. Check that Get(int) works on very deep stacks
// of ConsStrings. These operations may not be very fast, but they
// should be possible without getting errors due to too deep recursion.
#include "src/execution/isolate.h"
#include "src/heap/factory.h"
#include "src/objects/name-inl.h"
#include "src/objects/objects.h"
#include "src/utils/ostreams.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
using SymbolsTest = TestWithIsolate;
TEST_F(SymbolsTest, Create) {
const int kNumSymbols = 30;
Handle<Symbol> symbols[kNumSymbols];
StdoutStream os;
for (int i = 0; i < kNumSymbols; ++i) {
symbols[i] = isolate()->factory()->NewSymbol();
CHECK(IsName(*symbols[i]));
CHECK(IsSymbol(*symbols[i]));
CHECK(symbols[i]->HasHashCode());
CHECK(IsUniqueName(*symbols[i]));
CHECK_GT(symbols[i]->hash(), 0u);
os << Brief(*symbols[i]) << "\n";
#if OBJECT_PRINT
Print(*symbols[i], os);
#endif
#if VERIFY_HEAP
Object::ObjectVerify(*symbols[i], isolate());
#endif
}
InvokeMinorGC();
InvokeMajorGC();
// All symbols should be distinct.
for (int i = 0; i < kNumSymbols; ++i) {
CHECK(Object::SameValue(*symbols[i], *symbols[i]));
for (int j = i + 1; j < kNumSymbols; ++j) {
CHECK(!Object::SameValue(*symbols[i], *symbols[j]));
}
}
}
} // namespace internal
} // namespace v8

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,137 @@
// Copyright 2019 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 <optional>
#include "src/base/platform/platform.h"
#include "src/objects/backing-store.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
class BackingStoreTest : public TestWithIsolate {};
TEST_F(BackingStoreTest, GrowWasmMemoryInPlace) {
auto backing_store = BackingStore::AllocateWasmMemory(
isolate(), 1, 2, WasmMemoryFlag::kWasmMemory32, SharedFlag::kNotShared);
CHECK(backing_store);
EXPECT_TRUE(backing_store->is_wasm_memory());
EXPECT_EQ(1 * wasm::kWasmPageSize, backing_store->byte_length());
EXPECT_EQ(2 * wasm::kWasmPageSize, backing_store->byte_capacity());
std::optional<size_t> result =
backing_store->GrowWasmMemoryInPlace(isolate(), 1, 2);
EXPECT_TRUE(result.has_value());
EXPECT_EQ(result.value(), 1u);
EXPECT_EQ(2 * wasm::kWasmPageSize, backing_store->byte_length());
}
TEST_F(BackingStoreTest, GrowWasmMemoryInPlace_neg) {
auto backing_store = BackingStore::AllocateWasmMemory(
isolate(), 1, 2, WasmMemoryFlag::kWasmMemory32, SharedFlag::kNotShared);
CHECK(backing_store);
EXPECT_TRUE(backing_store->is_wasm_memory());
EXPECT_EQ(1 * wasm::kWasmPageSize, backing_store->byte_length());
EXPECT_EQ(2 * wasm::kWasmPageSize, backing_store->byte_capacity());
std::optional<size_t> result =
backing_store->GrowWasmMemoryInPlace(isolate(), 2, 2);
EXPECT_FALSE(result.has_value());
EXPECT_EQ(1 * wasm::kWasmPageSize, backing_store->byte_length());
}
TEST_F(BackingStoreTest, GrowSharedWasmMemoryInPlace) {
auto backing_store = BackingStore::AllocateWasmMemory(
isolate(), 2, 3, WasmMemoryFlag::kWasmMemory32, SharedFlag::kShared);
CHECK(backing_store);
EXPECT_TRUE(backing_store->is_wasm_memory());
EXPECT_EQ(2 * wasm::kWasmPageSize, backing_store->byte_length());
EXPECT_EQ(3 * wasm::kWasmPageSize, backing_store->byte_capacity());
std::optional<size_t> result =
backing_store->GrowWasmMemoryInPlace(isolate(), 1, 3);
EXPECT_TRUE(result.has_value());
EXPECT_EQ(result.value(), 2u);
EXPECT_EQ(3 * wasm::kWasmPageSize, backing_store->byte_length());
}
TEST_F(BackingStoreTest, CopyWasmMemory) {
auto bs1 = BackingStore::AllocateWasmMemory(
isolate(), 1, 2, WasmMemoryFlag::kWasmMemory32, SharedFlag::kNotShared);
CHECK(bs1);
EXPECT_TRUE(bs1->is_wasm_memory());
EXPECT_EQ(1 * wasm::kWasmPageSize, bs1->byte_length());
EXPECT_EQ(2 * wasm::kWasmPageSize, bs1->byte_capacity());
auto bs2 =
bs1->CopyWasmMemory(isolate(), 3, 3, WasmMemoryFlag::kWasmMemory32);
EXPECT_TRUE(bs2->is_wasm_memory());
EXPECT_EQ(3 * wasm::kWasmPageSize, bs2->byte_length());
EXPECT_EQ(3 * wasm::kWasmPageSize, bs2->byte_capacity());
}
class GrowerThread : public base::Thread {
public:
GrowerThread(Isolate* isolate, uint32_t increment, uint32_t max,
std::shared_ptr<BackingStore> backing_store)
: base::Thread(base::Thread::Options("GrowerThread")),
isolate_(isolate),
increment_(increment),
max_(max),
backing_store_(backing_store) {}
void Run() override {
size_t max_length = max_ * wasm::kWasmPageSize;
while (true) {
size_t current_length = backing_store_->byte_length();
if (current_length >= max_length) break;
std::optional<size_t> result =
backing_store_->GrowWasmMemoryInPlace(isolate_, increment_, max_);
size_t new_length = backing_store_->byte_length();
if (result.has_value()) {
CHECK_LE(current_length / wasm::kWasmPageSize, result.value());
CHECK_GE(new_length, current_length + increment_);
} else {
CHECK_EQ(max_length, new_length);
}
}
}
private:
Isolate* isolate_;
uint32_t increment_;
uint32_t max_;
std::shared_ptr<BackingStore> backing_store_;
};
TEST_F(BackingStoreTest, RacyGrowWasmMemoryInPlace) {
constexpr int kNumThreads = 10;
constexpr int kMaxPages = 1024;
GrowerThread* threads[kNumThreads];
std::shared_ptr<BackingStore> backing_store =
BackingStore::AllocateWasmMemory(isolate(), 0, kMaxPages,
WasmMemoryFlag::kWasmMemory32,
SharedFlag::kShared);
for (int i = 0; i < kNumThreads; i++) {
threads[i] = new GrowerThread(isolate(), 1, kMaxPages, backing_store);
CHECK(threads[i]->Start());
}
for (int i = 0; i < kNumThreads; i++) {
threads[i]->Join();
}
EXPECT_EQ(kMaxPages * wasm::kWasmPageSize, backing_store->byte_length());
for (int i = 0; i < kNumThreads; i++) {
delete threads[i];
}
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,57 @@
// Copyright 2019 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/heap/factory.h"
#include "test/unittests/test-utils.h"
namespace v8 {
namespace internal {
using WeakArrayListTest = TestWithIsolate;
TEST_F(WeakArrayListTest, Compact) {
DirectHandle<WeakArrayList> list = isolate()->factory()->NewWeakArrayList(10);
EXPECT_EQ(list->length(), 0);
EXPECT_EQ(list->capacity(), 10);
Tagged<MaybeObject> some_object = *isolate()->factory()->empty_fixed_array();
Tagged<MaybeObject> weak_ref = MakeWeak(some_object);
Tagged<MaybeObject> smi = Smi::FromInt(0);
Tagged<MaybeObject> cleared_ref = ClearedValue(isolate());
list->Set(0, weak_ref);
list->Set(1, smi);
list->Set(2, cleared_ref);
list->Set(3, cleared_ref);
list->set_length(5);
list->Compact(isolate());
EXPECT_EQ(list->length(), 3);
EXPECT_EQ(list->capacity(), 10);
}
TEST_F(WeakArrayListTest, OutOfPlaceCompact) {
DirectHandle<WeakArrayList> list = isolate()->factory()->NewWeakArrayList(20);
EXPECT_EQ(list->length(), 0);
EXPECT_EQ(list->capacity(), 20);
Tagged<MaybeObject> some_object = *isolate()->factory()->empty_fixed_array();
Tagged<MaybeObject> weak_ref = MakeWeak(some_object);
Tagged<MaybeObject> smi = Smi::FromInt(0);
Tagged<MaybeObject> cleared_ref = ClearedValue(isolate());
list->Set(0, weak_ref);
list->Set(1, smi);
list->Set(2, cleared_ref);
list->Set(3, smi);
list->Set(4, cleared_ref);
list->set_length(6);
DirectHandle<WeakArrayList> compacted =
isolate()->factory()->CompactWeakArrayList(list, 4);
EXPECT_EQ(list->length(), 6);
EXPECT_EQ(compacted->length(), 4);
EXPECT_EQ(compacted->capacity(), 4);
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,391 @@
// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <utility>
#include "src/execution/isolate.h"
#include "src/handles/global-handles-inl.h"
#include "src/heap/factory.h"
#include "src/heap/heap-inl.h"
#include "src/heap/heap-layout-inl.h"
#include "src/objects/hash-table-inl.h"
#include "src/objects/js-collection-inl.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/heap/heap-utils.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
namespace test_weakmaps {
using WeakMapsTest = TestWithHeapInternalsAndContext;
static int NumberOfWeakCalls = 0;
static void WeakPointerCallback(const v8::WeakCallbackInfo<void>& data) {
std::pair<v8::Persistent<v8::Value>*, int>* p =
reinterpret_cast<std::pair<v8::Persistent<v8::Value>*, int>*>(
data.GetParameter());
CHECK_EQ(1234, p->second);
NumberOfWeakCalls++;
p->first->Reset();
}
TEST_F(WeakMapsTest, Weakness) {
v8_flags.incremental_marking = false;
Isolate* isolate = i_isolate();
Factory* factory = isolate->factory();
HandleScope scope(isolate);
IndirectHandle<JSWeakMap> weakmap = isolate->factory()->NewJSWeakMap();
GlobalHandles* global_handles = isolate->global_handles();
// Keep global reference to the key.
IndirectHandle<Object> key;
{
HandleScope inner_scope(isolate);
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
DirectHandle<JSObject> object = factory->NewJSObjectFromMap(map);
key = global_handles->Create(*object);
}
CHECK(!global_handles->IsWeak(key.location()));
// Put two chained entries into weak map.
{
HandleScope inner_scope(isolate);
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
DirectHandle<JSObject> object = factory->NewJSObjectFromMap(map);
DirectHandle<Smi> smi(Smi::FromInt(23), isolate);
int32_t hash = Object::GetOrCreateHash(*key, isolate).value();
JSWeakCollection::Set(weakmap, key, object, hash);
int32_t object_hash = Object::GetOrCreateHash(*object, isolate).value();
JSWeakCollection::Set(weakmap, object, smi, object_hash);
}
// Put a symbol key into weak map.
{
HandleScope inner_scope(isolate);
DirectHandle<Symbol> symbol = factory->NewSymbol();
DirectHandle<Smi> smi(Smi::FromInt(23), isolate);
JSWeakCollection::Set(weakmap, symbol, smi, symbol->hash());
}
CHECK_EQ(3, Cast<EphemeronHashTable>(weakmap->table())->NumberOfElements());
// Force a full GC.
{
// We need to invoke GC without stack, otherwise some objects may not be
// reclaimed because of conservative stack scanning.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
isolate->heap());
InvokeAtomicMajorGC();
}
CHECK_EQ(0, NumberOfWeakCalls);
// Symbol key should be deleted.
CHECK_EQ(2, Cast<EphemeronHashTable>(weakmap->table())->NumberOfElements());
CHECK_EQ(
1, Cast<EphemeronHashTable>(weakmap->table())->NumberOfDeletedElements());
// Make the global reference to the key weak.
std::pair<IndirectHandle<Object>*, int> handle_and_id(&key, 1234);
GlobalHandles::MakeWeak(
key.location(), reinterpret_cast<void*>(&handle_and_id),
&WeakPointerCallback, v8::WeakCallbackType::kParameter);
CHECK(global_handles->IsWeak(key.location()));
{
// We need to invoke GC without stack, otherwise some objects may not be
// reclaimed because of conservative stack scanning.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
isolate->heap());
InvokeAtomicMajorGC();
}
CHECK_EQ(1, NumberOfWeakCalls);
CHECK_EQ(0, Cast<EphemeronHashTable>(weakmap->table())->NumberOfElements());
CHECK_EQ(
3, Cast<EphemeronHashTable>(weakmap->table())->NumberOfDeletedElements());
}
TEST_F(WeakMapsTest, Shrinking) {
Isolate* isolate = i_isolate();
Factory* factory = isolate->factory();
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
isolate->heap());
HandleScope scope(isolate);
DirectHandle<JSWeakMap> weakmap = isolate->factory()->NewJSWeakMap();
// Check initial capacity.
CHECK_EQ(32, Cast<EphemeronHashTable>(weakmap->table())->Capacity());
// Fill up weak map to trigger capacity change.
{
HandleScope inner_scope(isolate);
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
for (int i = 0; i < 32; i++) {
DirectHandle<JSObject> object = factory->NewJSObjectFromMap(map);
DirectHandle<Smi> smi(Smi::FromInt(i), isolate);
int32_t object_hash = Object::GetOrCreateHash(*object, isolate).value();
JSWeakCollection::Set(weakmap, object, smi, object_hash);
}
}
// Check increased capacity.
CHECK_EQ(128, Cast<EphemeronHashTable>(weakmap->table())->Capacity());
// Force a full GC.
CHECK_EQ(32, Cast<EphemeronHashTable>(weakmap->table())->NumberOfElements());
CHECK_EQ(
0, Cast<EphemeronHashTable>(weakmap->table())->NumberOfDeletedElements());
InvokeAtomicMajorGC();
CHECK_EQ(0, Cast<EphemeronHashTable>(weakmap->table())->NumberOfElements());
CHECK_EQ(
32,
Cast<EphemeronHashTable>(weakmap->table())->NumberOfDeletedElements());
// Check shrunk capacity.
CHECK_EQ(32, Cast<EphemeronHashTable>(weakmap->table())->Capacity());
}
namespace {
bool EphemeronHashTableContainsKey(Tagged<EphemeronHashTable> table,
Tagged<HeapObject> key) {
for (InternalIndex i : table->IterateEntries()) {
if (table->KeyAt(i) == key) return true;
}
return false;
}
} // namespace
TEST_F(WeakMapsTest, WeakMapPromotionMarkCompact) {
Isolate* isolate = i_isolate();
ManualGCScope manual_gc_scope(isolate);
Factory* factory = isolate->factory();
HandleScope scope(isolate);
DirectHandle<JSWeakMap> weakmap = isolate->factory()->NewJSWeakMap();
InvokeMajorGC();
CHECK(!HeapLayout::InYoungGeneration(weakmap->table()));
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
DirectHandle<JSObject> object = factory->NewJSObjectFromMap(map);
DirectHandle<Smi> smi(Smi::FromInt(1), isolate);
int32_t object_hash = Object::GetOrCreateHash(*object, isolate).value();
JSWeakCollection::Set(weakmap, object, smi, object_hash);
CHECK(EphemeronHashTableContainsKey(
Cast<EphemeronHashTable>(weakmap->table()), *object));
InvokeMajorGC();
CHECK(!HeapLayout::InYoungGeneration(*object));
CHECK(!HeapLayout::InYoungGeneration(weakmap->table()));
CHECK(EphemeronHashTableContainsKey(
Cast<EphemeronHashTable>(weakmap->table()), *object));
InvokeMajorGC();
CHECK(!HeapLayout::InYoungGeneration(*object));
CHECK(!HeapLayout::InYoungGeneration(weakmap->table()));
CHECK(EphemeronHashTableContainsKey(
Cast<EphemeronHashTable>(weakmap->table()), *object));
}
TEST_F(WeakMapsTest, WeakMapScavenge) {
if (i::v8_flags.single_generation) return;
if (i::v8_flags.stress_incremental_marking) return;
v8_flags.scavenger_precise_object_pinning = false;
Isolate* isolate = i_isolate();
ManualGCScope manual_gc_scope(isolate);
Factory* factory = isolate->factory();
HandleScope scope(isolate);
IndirectHandle<JSWeakMap> weakmap = isolate->factory()->NewJSWeakMap();
InvokeAtomicMinorGC();
CHECK(HeapLayout::InYoungGeneration(weakmap->table()));
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
IndirectHandle<JSObject> object = factory->NewJSObjectFromMap(map);
DirectHandle<Smi> smi(Smi::FromInt(1), isolate);
int32_t object_hash = Object::GetOrCreateHash(*object, isolate).value();
JSWeakCollection::Set(weakmap, object, smi, object_hash);
CHECK(EphemeronHashTableContainsKey(
Cast<EphemeronHashTable>(weakmap->table()), *object));
if (!v8_flags.minor_ms) {
// CSS prevents promoting objects to the old generation.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
isolate->heap());
InvokeAtomicMinorGC();
CHECK(HeapLayout::InYoungGeneration(*object));
CHECK(!HeapLayout::InYoungGeneration(weakmap->table()));
CHECK(EphemeronHashTableContainsKey(
Cast<EphemeronHashTable>(weakmap->table()), *object));
}
InvokeAtomicMajorGC();
CHECK(!HeapLayout::InYoungGeneration(*object));
CHECK(!HeapLayout::InYoungGeneration(weakmap->table()));
CHECK(EphemeronHashTableContainsKey(
Cast<EphemeronHashTable>(weakmap->table()), *object));
}
// Test that weak map values on an evacuation candidate which are not reachable
// by other paths are correctly recorded in the slots buffer.
TEST_F(WeakMapsTest, Regress2060a) {
if (!i::v8_flags.compact) return;
v8_flags.compact_on_every_full_gc = true;
v8_flags.stress_concurrent_allocation = false; // For SimulateFullSpace.
Isolate* isolate = i_isolate();
ManualGCScope manual_gc_scope(isolate);
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope scope(isolate);
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(factory->function_string());
DirectHandle<JSObject> key = factory->NewJSObject(function);
DirectHandle<JSWeakMap> weakmap = isolate->factory()->NewJSWeakMap();
// Start second old-space page so that values land on evacuation candidate.
PageMetadata* first_page = heap->old_space()->first_page();
SimulateFullSpace(heap->old_space());
// Fill up weak map with values on an evacuation candidate.
{
HandleScope inner_scope(isolate);
for (int i = 0; i < 32; i++) {
DirectHandle<JSObject> object =
factory->NewJSObject(function, AllocationType::kOld);
CHECK(!HeapLayout::InYoungGeneration(*object));
CHECK(!first_page->Contains(object->address()));
int32_t hash = Object::GetOrCreateHash(*key, isolate).value();
JSWeakCollection::Set(weakmap, key, object, hash);
}
}
// Force compacting garbage collection.
CHECK(v8_flags.compact_on_every_full_gc);
InvokeMajorGC();
}
// Test that weak map keys on an evacuation candidate which are reachable by
// other strong paths are correctly recorded in the slots buffer.
TEST_F(WeakMapsTest, Regress2060b) {
if (!i::v8_flags.compact) return;
v8_flags.compact_on_every_full_gc = true;
#ifdef VERIFY_HEAP
v8_flags.verify_heap = true;
#endif
v8_flags.stress_concurrent_allocation = false; // For SimulateFullSpace.
Isolate* isolate = i_isolate();
ManualGCScope manual_gc_scope(isolate);
Factory* factory = isolate->factory();
Heap* heap = isolate->heap();
HandleScope scope(isolate);
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(factory->function_string());
// Start second old-space page so that keys land on evacuation candidate.
PageMetadata* first_page = heap->old_space()->first_page();
SimulateFullSpace(heap->old_space());
// Fill up weak map with keys on an evacuation candidate.
Handle<JSObject> keys[32];
for (int i = 0; i < 32; i++) {
keys[i] = factory->NewJSObject(function, AllocationType::kOld);
CHECK(!HeapLayout::InYoungGeneration(*keys[i]));
CHECK(!first_page->Contains(keys[i]->address()));
}
DirectHandle<JSWeakMap> weakmap = isolate->factory()->NewJSWeakMap();
for (int i = 0; i < 32; i++) {
DirectHandle<Smi> smi(Smi::FromInt(i), isolate);
int32_t hash = Object::GetOrCreateHash(*keys[i], isolate).value();
JSWeakCollection::Set(weakmap, keys[i], smi, hash);
}
// Force compacting garbage collection. The subsequent collections are used
// to verify that key references were actually updated.
CHECK(v8_flags.compact_on_every_full_gc);
InvokeMajorGC();
InvokeMajorGC();
InvokeMajorGC();
}
TEST_F(WeakMapsTest, Regress399527) {
if (!v8_flags.incremental_marking) return;
v8::HandleScope scope(v8_isolate());
Isolate* isolate = i_isolate();
Heap* heap = isolate->heap();
{
HandleScope inner_scope(isolate);
isolate->factory()->NewJSWeakMap();
SimulateIncrementalMarking(heap);
}
// The weak map is marked black here but leaving the handle scope will make
// the object unreachable. Aborting incremental marking will clear all the
// marking bits which makes the weak map garbage.
InvokeMajorGC();
}
TEST_F(WeakMapsTest, WeakMapsWithChainedEntries) {
ManualGCScope manual_gc_scope(i_isolate());
v8::Isolate* isolate = v8_isolate();
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
i_isolate()->heap());
v8::HandleScope scope(isolate);
const int initial_gc_count = i_isolate()->heap()->gc_count();
DirectHandle<JSWeakMap> weakmap1 = i_isolate()->factory()->NewJSWeakMap();
DirectHandle<JSWeakMap> weakmap2 = i_isolate()->factory()->NewJSWeakMap();
v8::Global<v8::Object> g1;
v8::Global<v8::Object> g2;
{
v8::HandleScope inner_scope(isolate);
v8::Local<v8::Object> o1 = v8::Object::New(isolate);
g1.Reset(isolate, o1);
g1.SetWeak();
v8::Local<v8::Object> o2 = v8::Object::New(isolate);
g2.Reset(isolate, o2);
g2.SetWeak();
DirectHandle<Object> i_o1 = v8::Utils::OpenDirectHandle(*o1);
DirectHandle<Object> i_o2 = v8::Utils::OpenDirectHandle(*o2);
int32_t hash1 = Object::GetOrCreateHash(*i_o1, i_isolate()).value();
int32_t hash2 = Object::GetOrCreateHash(*i_o2, i_isolate()).value();
JSWeakCollection::Set(weakmap1, i_o1, i_o2, hash1);
JSWeakCollection::Set(weakmap2, i_o2, i_o1, hash2);
}
InvokeMajorGC();
CHECK(g1.IsEmpty());
CHECK(g2.IsEmpty());
CHECK_EQ(1, i_isolate()->heap()->gc_count() - initial_gc_count);
}
} // namespace test_weakmaps
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,251 @@
// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <utility>
#include "src/execution/isolate.h"
#include "src/handles/global-handles-inl.h"
#include "src/heap/factory.h"
#include "src/heap/heap-inl.h"
#include "src/objects/hash-table-inl.h"
#include "src/objects/js-collection-inl.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/heap/heap-utils.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
namespace test_weaksets {
class WeakSetsTest : public TestWithHeapInternalsAndContext {
public:
Handle<JSWeakSet> AllocateJSWeakSet() {
Factory* factory = i_isolate()->factory();
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_WEAK_SET_TYPE, JSWeakSet::kHeaderSize);
DirectHandle<JSObject> weakset_obj = factory->NewJSObjectFromMap(map);
Handle<JSWeakSet> weakset(Cast<JSWeakSet>(*weakset_obj), i_isolate());
// Do not leak handles for the hash table, it would make entries strong.
{
HandleScope scope(i_isolate());
DirectHandle<EphemeronHashTable> table =
EphemeronHashTable::New(i_isolate(), 1);
weakset->set_table(*table);
}
return weakset;
}
};
namespace {
static int NumberOfWeakCalls = 0;
static void WeakPointerCallback(const v8::WeakCallbackInfo<void>& data) {
std::pair<v8::Persistent<v8::Value>*, int>* p =
reinterpret_cast<std::pair<v8::Persistent<v8::Value>*, int>*>(
data.GetParameter());
CHECK_EQ(1234, p->second);
NumberOfWeakCalls++;
p->first->Reset();
}
} // namespace
TEST_F(WeakSetsTest, WeakSet_Weakness) {
v8_flags.incremental_marking = false;
Factory* factory = i_isolate()->factory();
HandleScope scope(i_isolate());
IndirectHandle<JSWeakSet> weakset = AllocateJSWeakSet();
GlobalHandles* global_handles = i_isolate()->global_handles();
// Keep global reference to the key.
Handle<Object> key;
{
HandleScope inner_scope(i_isolate());
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
DirectHandle<JSObject> object = factory->NewJSObjectFromMap(map);
key = global_handles->Create(*object);
}
CHECK(!global_handles->IsWeak(key.location()));
// Put entry into weak set.
{
HandleScope inner_scope(i_isolate());
DirectHandle<Smi> smi(Smi::FromInt(23), i_isolate());
int32_t hash = Object::GetOrCreateHash(*key, i_isolate()).value();
JSWeakCollection::Set(weakset, key, smi, hash);
}
CHECK_EQ(1, Cast<EphemeronHashTable>(weakset->table())->NumberOfElements());
// Force a full GC.
InvokeAtomicMajorGC();
CHECK_EQ(0, NumberOfWeakCalls);
CHECK_EQ(1, Cast<EphemeronHashTable>(weakset->table())->NumberOfElements());
CHECK_EQ(
0, Cast<EphemeronHashTable>(weakset->table())->NumberOfDeletedElements());
// Make the global reference to the key weak.
std::pair<Handle<Object>*, int> handle_and_id(&key, 1234);
GlobalHandles::MakeWeak(
key.location(), reinterpret_cast<void*>(&handle_and_id),
&WeakPointerCallback, v8::WeakCallbackType::kParameter);
CHECK(global_handles->IsWeak(key.location()));
// We need to invoke GC without stack here, otherwise the object may survive.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(
isolate()->heap());
InvokeAtomicMajorGC();
CHECK_EQ(1, NumberOfWeakCalls);
CHECK_EQ(0, Cast<EphemeronHashTable>(weakset->table())->NumberOfElements());
CHECK_EQ(
1, Cast<EphemeronHashTable>(weakset->table())->NumberOfDeletedElements());
}
TEST_F(WeakSetsTest, WeakSet_Shrinking) {
Factory* factory = i_isolate()->factory();
HandleScope scope(i_isolate());
DirectHandle<JSWeakSet> weakset = AllocateJSWeakSet();
// Check initial capacity.
CHECK_EQ(32, Cast<EphemeronHashTable>(weakset->table())->Capacity());
// Fill up weak set to trigger capacity change.
{
HandleScope inner_scope(i_isolate());
DirectHandle<Map> map = factory->NewContextfulMapForCurrentContext(
JS_OBJECT_TYPE, JSObject::kHeaderSize);
for (int i = 0; i < 32; i++) {
DirectHandle<JSObject> object = factory->NewJSObjectFromMap(map);
DirectHandle<Smi> smi(Smi::FromInt(i), i_isolate());
int32_t hash = Object::GetOrCreateHash(*object, i_isolate()).value();
JSWeakCollection::Set(weakset, object, smi, hash);
}
}
// Check increased capacity.
CHECK_EQ(128, Cast<EphemeronHashTable>(weakset->table())->Capacity());
// Force a full GC.
CHECK_EQ(32, Cast<EphemeronHashTable>(weakset->table())->NumberOfElements());
CHECK_EQ(
0, Cast<EphemeronHashTable>(weakset->table())->NumberOfDeletedElements());
InvokeAtomicMajorGC();
CHECK_EQ(0, Cast<EphemeronHashTable>(weakset->table())->NumberOfElements());
CHECK_EQ(
32,
Cast<EphemeronHashTable>(weakset->table())->NumberOfDeletedElements());
// Check shrunk capacity.
CHECK_EQ(32, Cast<EphemeronHashTable>(weakset->table())->Capacity());
}
// Test that weak set values on an evacuation candidate which are not reachable
// by other paths are correctly recorded in the slots buffer.
TEST_F(WeakSetsTest, WeakSet_Regress2060a) {
if (!i::v8_flags.compact) return;
v8_flags.compact_on_every_full_gc = true;
v8_flags.stress_concurrent_allocation = false; // For SimulateFullSpace.
ManualGCScope manual_gc_scope(i_isolate());
Factory* factory = i_isolate()->factory();
Heap* heap = i_isolate()->heap();
HandleScope scope(i_isolate());
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(factory->function_string());
DirectHandle<JSObject> key = factory->NewJSObject(function);
DirectHandle<JSWeakSet> weakset = AllocateJSWeakSet();
// Start second old-space page so that values land on evacuation candidate.
PageMetadata* first_page = heap->old_space()->first_page();
SimulateFullSpace(heap->old_space());
// Fill up weak set with values on an evacuation candidate.
{
HandleScope inner_scope(i_isolate());
for (int i = 0; i < 32; i++) {
DirectHandle<JSObject> object =
factory->NewJSObject(function, AllocationType::kOld);
CHECK(!HeapLayout::InYoungGeneration(*object));
CHECK(!first_page->Contains(object->address()));
int32_t hash = Object::GetOrCreateHash(*key, i_isolate()).value();
JSWeakCollection::Set(weakset, key, object, hash);
}
}
// Force compacting garbage collection.
CHECK(v8_flags.compact_on_every_full_gc);
// We need to invoke GC without stack, otherwise no compaction is performed.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
InvokeMajorGC();
}
// Test that weak set keys on an evacuation candidate which are reachable by
// other strong paths are correctly recorded in the slots buffer.
TEST_F(WeakSetsTest, WeakSet_Regress2060b) {
if (!i::v8_flags.compact) return;
v8_flags.compact_on_every_full_gc = true;
#ifdef VERIFY_HEAP
v8_flags.verify_heap = true;
#endif
v8_flags.stress_concurrent_allocation = false; // For SimulateFullSpace.
ManualGCScope manual_gc_scope(i_isolate());
Factory* factory = i_isolate()->factory();
Heap* heap = i_isolate()->heap();
HandleScope scope(i_isolate());
DirectHandle<JSFunction> function =
factory->NewFunctionForTesting(factory->function_string());
// Start second old-space page so that keys land on evacuation candidate.
PageMetadata* first_page = heap->old_space()->first_page();
SimulateFullSpace(heap->old_space());
// Fill up weak set with keys on an evacuation candidate.
Handle<JSObject> keys[32];
for (int i = 0; i < 32; i++) {
keys[i] = factory->NewJSObject(function, AllocationType::kOld);
CHECK(!HeapLayout::InYoungGeneration(*keys[i]));
CHECK(!first_page->Contains(keys[i]->address()));
}
DirectHandle<JSWeakSet> weakset = AllocateJSWeakSet();
for (int i = 0; i < 32; i++) {
DirectHandle<Smi> smi(Smi::FromInt(i), i_isolate());
int32_t hash = Object::GetOrCreateHash(*keys[i], i_isolate()).value();
JSWeakCollection::Set(weakset, keys[i], smi, hash);
}
// Force compacting garbage collection. The subsequent collections are used
// to verify that key references were actually updated.
CHECK(v8_flags.compact_on_every_full_gc);
// We need to invoke GC without stack, otherwise no compaction is performed.
DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap);
InvokeMajorGC();
InvokeMajorGC();
InvokeMajorGC();
}
} // namespace test_weaksets
} // namespace internal
} // namespace v8