f47e59e045
Because of LocalHeap safepoints, our existing assert scopes don't necessarily maintain the same guarantees as desired. In particular, DisallowHeapAllocation no longer guarantees that objects don't move. This patch transitions DisallowHeapAllocation to DisallowGarbageCollection, to ensure that code using this scope is also protected against safepoints. Change-Id: I0411425884f6849982611205fb17bb072881c722 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2540547 Commit-Queue: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Peter Marshall <petermarshall@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Reviewed-by: Clemens Backes <clemensb@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Reviewed-by: Tobias Tebbi <tebbi@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Cr-Commit-Position: refs/heads/master@{#71319}
425 lines
14 KiB
C++
425 lines
14 KiB
C++
// Copyright 2020 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "src/base/logging.h"
|
|
#include "src/codegen/code-stub-assembler.h"
|
|
#include "src/common/globals.h"
|
|
#include "src/objects/descriptor-array.h"
|
|
#include "src/objects/property-details.h"
|
|
#include "src/objects/string-inl.h"
|
|
#include "src/objects/transitions-inl.h"
|
|
#include "test/cctest/cctest.h"
|
|
#include "test/cctest/compiler/code-assembler-tester.h"
|
|
#include "test/cctest/compiler/function-tester.h"
|
|
#include "test/cctest/test-transitions.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
|
|
namespace {
|
|
|
|
using Label = compiler::CodeAssemblerLabel;
|
|
template <class T>
|
|
using TVariable = compiler::TypedCodeAssemblerVariable<T>;
|
|
|
|
Handle<Name> NewNameWithHash(Isolate* isolate, const char* str, uint32_t hash,
|
|
bool is_integer) {
|
|
uint32_t hash_field = hash << Name::kHashShift;
|
|
|
|
static_assert(Name::kNofHashBitFields == 2, "This test needs updating");
|
|
static_assert(Name::kHashNotComputedMask == 1, "This test needs updating");
|
|
static_assert(Name::kIsNotIntegerIndexMask == 2, "This test needs updating");
|
|
|
|
if (!is_integer) {
|
|
hash_field |= Name::kIsNotIntegerIndexMask;
|
|
}
|
|
Handle<Name> name = isolate->factory()->NewOneByteInternalizedString(
|
|
OneByteVector(str), hash_field);
|
|
name->set_raw_hash_field(hash_field);
|
|
CHECK(name->IsUniqueName());
|
|
return name;
|
|
}
|
|
|
|
template <typename... Args>
|
|
MaybeHandle<Object> Call(Isolate* isolate, Handle<JSFunction> function,
|
|
Args... args) {
|
|
const int nof_args = sizeof...(Args);
|
|
Handle<Object> call_args[] = {args...};
|
|
Handle<Object> receiver = isolate->factory()->undefined_value();
|
|
return Execution::Call(isolate, function, receiver, nof_args, call_args);
|
|
}
|
|
|
|
void CheckDescriptorArrayLookups(Isolate* isolate, Handle<Map> map,
|
|
std::vector<Handle<Name>>& names,
|
|
Handle<JSFunction> csa_lookup) {
|
|
// Test C++ implementation.
|
|
{
|
|
DisallowGarbageCollection no_gc;
|
|
DescriptorArray descriptors = map->instance_descriptors(kRelaxedLoad);
|
|
DCHECK(descriptors.IsSortedNoDuplicates());
|
|
int nof_descriptors = descriptors.number_of_descriptors();
|
|
|
|
for (size_t i = 0; i < names.size(); ++i) {
|
|
Name name = *names[i];
|
|
InternalIndex index = descriptors.Search(name, nof_descriptors, false);
|
|
CHECK(index.is_found());
|
|
CHECK_EQ(i, index.as_uint32());
|
|
}
|
|
}
|
|
|
|
// Test CSA implementation.
|
|
if (!FLAG_jitless) {
|
|
for (size_t i = 0; i < names.size(); ++i) {
|
|
Handle<Object> name_index =
|
|
Call(isolate, csa_lookup, map, names[i]).ToHandleChecked();
|
|
CHECK(name_index->IsSmi());
|
|
CHECK_EQ(DescriptorArray::ToKeyIndex(static_cast<int>(i)),
|
|
Smi::ToInt(*name_index));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckTransitionArrayLookups(Isolate* isolate,
|
|
Handle<TransitionArray> transitions,
|
|
std::vector<Handle<Map>>& maps,
|
|
Handle<JSFunction> csa_lookup) {
|
|
// Test C++ implementation.
|
|
{
|
|
DisallowGarbageCollection no_gc;
|
|
DCHECK(transitions->IsSortedNoDuplicates());
|
|
|
|
for (size_t i = 0; i < maps.size(); ++i) {
|
|
Map expected_map = *maps[i];
|
|
Name name = expected_map.instance_descriptors(kRelaxedLoad)
|
|
.GetKey(expected_map.LastAdded());
|
|
|
|
Map map = transitions->SearchAndGetTargetForTesting(PropertyKind::kData,
|
|
name, NONE);
|
|
CHECK(!map.is_null());
|
|
CHECK_EQ(expected_map, map);
|
|
}
|
|
}
|
|
|
|
// Test CSA implementation.
|
|
if (!FLAG_jitless) {
|
|
for (size_t i = 0; i < maps.size(); ++i) {
|
|
Handle<Map> expected_map = maps[i];
|
|
Handle<Name> name(expected_map->instance_descriptors(kRelaxedLoad)
|
|
.GetKey(expected_map->LastAdded()),
|
|
isolate);
|
|
|
|
Handle<Object> transition_map =
|
|
Call(isolate, csa_lookup, transitions, name).ToHandleChecked();
|
|
CHECK(transition_map->IsMap());
|
|
CHECK_EQ(*expected_map, *transition_map);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Creates function with (Map, Name) arguments. Returns Smi with the index of
|
|
// the name value of the found descriptor (DescriptorArray::ToKeyIndex())
|
|
// or null otherwise.
|
|
Handle<JSFunction> CreateCsaDescriptorArrayLookup(Isolate* isolate) {
|
|
// We are not allowed to generate code in jitless mode.
|
|
if (FLAG_jitless) return Handle<JSFunction>();
|
|
|
|
// Preallocate handle for the result in the current handle scope.
|
|
Handle<JSFunction> result_function(JSFunction{}, isolate);
|
|
|
|
const int kNumParams = 2;
|
|
|
|
compiler::CodeAssemblerTester asm_tester(
|
|
isolate, kNumParams + 1, // +1 to include receiver.
|
|
CodeKind::FOR_TESTING);
|
|
{
|
|
CodeStubAssembler m(asm_tester.state());
|
|
|
|
auto map = m.Parameter<Map>(1);
|
|
auto unique_name = m.Parameter<Name>(2);
|
|
|
|
Label passed(&m), failed(&m);
|
|
Label if_found(&m), if_not_found(&m);
|
|
TVariable<IntPtrT> var_name_index(&m);
|
|
|
|
TNode<Uint32T> bit_field3 = m.LoadMapBitField3(map);
|
|
TNode<DescriptorArray> descriptors = m.LoadMapDescriptors(map);
|
|
|
|
m.DescriptorLookup(unique_name, descriptors, bit_field3, &if_found,
|
|
&var_name_index, &if_not_found);
|
|
|
|
m.BIND(&if_found);
|
|
m.Return(m.SmiTag(var_name_index.value()));
|
|
|
|
m.BIND(&if_not_found);
|
|
m.Return(m.NullConstant());
|
|
}
|
|
|
|
{
|
|
compiler::FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
|
|
// Copy function value to a handle created in the outer handle scope.
|
|
result_function.PatchValue(*ft.function);
|
|
}
|
|
|
|
return result_function;
|
|
}
|
|
|
|
// Creates function with (TransitionArray, Name) arguments. Returns transition
|
|
// map if transition is found or null otherwise.
|
|
Handle<JSFunction> CreateCsaTransitionArrayLookup(Isolate* isolate) {
|
|
// We are not allowed to generate code in jitless mode.
|
|
if (FLAG_jitless) return Handle<JSFunction>();
|
|
|
|
// Preallocate handle for the result in the current handle scope.
|
|
Handle<JSFunction> result_function(JSFunction{}, isolate);
|
|
|
|
const int kNumParams = 2;
|
|
compiler::CodeAssemblerTester asm_tester(
|
|
isolate, kNumParams + 1, // +1 to include receiver.
|
|
CodeKind::FOR_TESTING);
|
|
{
|
|
CodeStubAssembler m(asm_tester.state());
|
|
|
|
auto transitions = m.Parameter<TransitionArray>(1);
|
|
auto unique_name = m.Parameter<Name>(2);
|
|
|
|
Label passed(&m), failed(&m);
|
|
Label if_found(&m), if_not_found(&m);
|
|
TVariable<IntPtrT> var_name_index(&m);
|
|
|
|
m.TransitionLookup(unique_name, transitions, &if_found, &var_name_index,
|
|
&if_not_found);
|
|
|
|
m.BIND(&if_found);
|
|
{
|
|
STATIC_ASSERT(kData == 0);
|
|
STATIC_ASSERT(NONE == 0);
|
|
const int kKeyToTargetOffset = (TransitionArray::kEntryTargetIndex -
|
|
TransitionArray::kEntryKeyIndex) *
|
|
kTaggedSize;
|
|
TNode<Map> transition_map = m.CAST(m.GetHeapObjectAssumeWeak(
|
|
m.LoadArrayElement(transitions, WeakFixedArray::kHeaderSize,
|
|
var_name_index.value(), kKeyToTargetOffset)));
|
|
m.Return(transition_map);
|
|
}
|
|
|
|
m.BIND(&if_not_found);
|
|
m.Return(m.NullConstant());
|
|
}
|
|
|
|
{
|
|
compiler::FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
|
|
// Copy function value to a handle created in the outer handle scope.
|
|
result_function.PatchValue(*ft.function);
|
|
}
|
|
|
|
return result_function;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(DescriptorArrayHashCollisionMassive) {
|
|
CcTest::InitializeVM();
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
HandleScope handle_scope(isolate);
|
|
|
|
static_assert(Name::kNofHashBitFields == 2, "This test needs updating");
|
|
|
|
std::vector<Handle<Name>> names;
|
|
|
|
// Use the same hash value for all names.
|
|
uint32_t hash =
|
|
static_cast<uint32_t>(isolate->GenerateIdentityHash(Name::kHashBitMask));
|
|
|
|
for (int i = 0; i < kMaxNumberOfDescriptors / 2; ++i) {
|
|
// Add pairs of names having the same base hash value but having different
|
|
// values of is_integer bit.
|
|
bool first_is_integer = (i & 1) != 0;
|
|
bool second_is_integer = (i & 2) != 0;
|
|
|
|
names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer));
|
|
names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer));
|
|
}
|
|
|
|
// Create descriptor array with the created names by appending fields to some
|
|
// map. DescriptorArray marking relies on the fact that it's attached to an
|
|
// owning map.
|
|
Handle<Map> map = Map::Create(isolate, 0);
|
|
|
|
Handle<FieldType> any_type = FieldType::Any(isolate);
|
|
|
|
for (size_t i = 0; i < names.size(); ++i) {
|
|
map = Map::CopyWithField(isolate, map, names[i], any_type, NONE,
|
|
PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
}
|
|
|
|
Handle<JSFunction> csa_lookup = CreateCsaDescriptorArrayLookup(isolate);
|
|
|
|
CheckDescriptorArrayLookups(isolate, map, names, csa_lookup);
|
|
|
|
// Sort descriptor array and check it again.
|
|
map->instance_descriptors(kRelaxedLoad).Sort();
|
|
CheckDescriptorArrayLookups(isolate, map, names, csa_lookup);
|
|
}
|
|
|
|
TEST(DescriptorArrayHashCollision) {
|
|
CcTest::InitializeVM();
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
HandleScope handle_scope(isolate);
|
|
|
|
static_assert(Name::kNofHashBitFields == 2, "This test needs updating");
|
|
|
|
std::vector<Handle<Name>> names;
|
|
uint32_t hash = 0;
|
|
|
|
for (int i = 0; i < kMaxNumberOfDescriptors / 2; ++i) {
|
|
if (i % 2 == 0) {
|
|
// Change hash value for every pair of names.
|
|
hash = static_cast<uint32_t>(
|
|
isolate->GenerateIdentityHash(Name::kHashBitMask));
|
|
}
|
|
|
|
// Add pairs of names having the same base hash value but having different
|
|
// values of is_integer bit.
|
|
bool first_is_integer = (i & 1) != 0;
|
|
bool second_is_integer = (i & 2) != 0;
|
|
|
|
names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer));
|
|
names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer));
|
|
}
|
|
|
|
// Create descriptor array with the created names by appending fields to some
|
|
// map. DescriptorArray marking relies on the fact that it's attached to an
|
|
// owning map.
|
|
Handle<Map> map = Map::Create(isolate, 0);
|
|
|
|
Handle<FieldType> any_type = FieldType::Any(isolate);
|
|
|
|
for (size_t i = 0; i < names.size(); ++i) {
|
|
map = Map::CopyWithField(isolate, map, names[i], any_type, NONE,
|
|
PropertyConstness::kMutable,
|
|
Representation::Tagged(), OMIT_TRANSITION)
|
|
.ToHandleChecked();
|
|
}
|
|
|
|
Handle<JSFunction> csa_lookup = CreateCsaDescriptorArrayLookup(isolate);
|
|
|
|
CheckDescriptorArrayLookups(isolate, map, names, csa_lookup);
|
|
|
|
// Sort descriptor array and check it again.
|
|
map->instance_descriptors(kRelaxedLoad).Sort();
|
|
CheckDescriptorArrayLookups(isolate, map, names, csa_lookup);
|
|
}
|
|
|
|
TEST(TransitionArrayHashCollisionMassive) {
|
|
CcTest::InitializeVM();
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
HandleScope handle_scope(isolate);
|
|
|
|
static_assert(Name::kNofHashBitFields == 2, "This test needs updating");
|
|
|
|
std::vector<Handle<Name>> names;
|
|
|
|
// Use the same hash value for all names.
|
|
uint32_t hash =
|
|
static_cast<uint32_t>(isolate->GenerateIdentityHash(Name::kHashBitMask));
|
|
|
|
for (int i = 0; i < TransitionsAccessor::kMaxNumberOfTransitions / 2; ++i) {
|
|
// Add pairs of names having the same base hash value but having different
|
|
// values of is_integer bit.
|
|
bool first_is_integer = (i & 1) != 0;
|
|
bool second_is_integer = (i & 2) != 0;
|
|
|
|
names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer));
|
|
names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer));
|
|
}
|
|
|
|
// Create transitions for each name.
|
|
Handle<Map> root_map = Map::Create(isolate, 0);
|
|
|
|
std::vector<Handle<Map>> maps;
|
|
|
|
Handle<FieldType> any_type = FieldType::Any(isolate);
|
|
|
|
for (size_t i = 0; i < names.size(); ++i) {
|
|
Handle<Map> map =
|
|
Map::CopyWithField(isolate, root_map, names[i], any_type, NONE,
|
|
PropertyConstness::kMutable,
|
|
Representation::Tagged(), INSERT_TRANSITION)
|
|
.ToHandleChecked();
|
|
maps.push_back(map);
|
|
}
|
|
|
|
Handle<JSFunction> csa_lookup = CreateCsaTransitionArrayLookup(isolate);
|
|
|
|
Handle<TransitionArray> transition_array(
|
|
TestTransitionsAccessor(isolate, root_map).transitions(), isolate);
|
|
|
|
CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup);
|
|
|
|
// Sort transition array and check it again.
|
|
transition_array->Sort();
|
|
CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup);
|
|
}
|
|
|
|
TEST(TransitionArrayHashCollision) {
|
|
CcTest::InitializeVM();
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
HandleScope handle_scope(isolate);
|
|
|
|
static_assert(Name::kNofHashBitFields == 2, "This test needs updating");
|
|
|
|
std::vector<Handle<Name>> names;
|
|
|
|
// Use the same hash value for all names.
|
|
uint32_t hash =
|
|
static_cast<uint32_t>(isolate->GenerateIdentityHash(Name::kHashBitMask));
|
|
|
|
for (int i = 0; i < TransitionsAccessor::kMaxNumberOfTransitions / 2; ++i) {
|
|
if (i % 2 == 0) {
|
|
// Change hash value for every pair of names.
|
|
hash = static_cast<uint32_t>(
|
|
isolate->GenerateIdentityHash(Name::kHashBitMask));
|
|
}
|
|
// Add pairs of names having the same base hash value but having different
|
|
// values of is_integer bit.
|
|
bool first_is_integer = (i & 1) != 0;
|
|
bool second_is_integer = (i & 2) != 0;
|
|
|
|
names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer));
|
|
names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer));
|
|
}
|
|
|
|
// Create transitions for each name.
|
|
Handle<Map> root_map = Map::Create(isolate, 0);
|
|
|
|
std::vector<Handle<Map>> maps;
|
|
|
|
Handle<FieldType> any_type = FieldType::Any(isolate);
|
|
|
|
for (size_t i = 0; i < names.size(); ++i) {
|
|
Handle<Map> map =
|
|
Map::CopyWithField(isolate, root_map, names[i], any_type, NONE,
|
|
PropertyConstness::kMutable,
|
|
Representation::Tagged(), INSERT_TRANSITION)
|
|
.ToHandleChecked();
|
|
maps.push_back(map);
|
|
}
|
|
|
|
Handle<JSFunction> csa_lookup = CreateCsaTransitionArrayLookup(isolate);
|
|
|
|
Handle<TransitionArray> transition_array(
|
|
TestTransitionsAccessor(isolate, root_map).transitions(), isolate);
|
|
|
|
CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup);
|
|
|
|
// Sort transition array and check it again.
|
|
transition_array->Sort();
|
|
CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup);
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace v8
|