Commit 05f41a55 authored by Frank Emrich's avatar Frank Emrich Committed by Commit Bot

[dict-proto] CSA version of ordered hash table allocation

Previously, CodeStubAssembler::AllocateOrderedHashTable() would
allocate hash tables of the (statically known) minimum capacity in-
dicated by the concrete table type.
This CL adds AllocateOrderedHashTableWithCapacity, which is inspired by
AllocateNameDictionary. It takes a Node<IntPtrT> indicating the desired
capacity.

Bug: v8:7569
Change-Id: I4bf28f69286e52773319a1ae37d33b2f55175a84
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2503950
Commit-Queue: Frank Emrich <emrich@google.com>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarIgor Sheludko <ishell@chromium.org>
Reviewed-by: 's avatarMarja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/master@{#71175}
parent 21b0f751
......@@ -843,9 +843,9 @@ TNode<HeapObject> CollectionsBuiltinsAssembler::AllocateJSCollectionIterator(
TNode<HeapObject> CollectionsBuiltinsAssembler::AllocateTable(
Variant variant, TNode<IntPtrT> at_least_space_for) {
if (variant == kMap || variant == kWeakMap) {
return AllocateOrderedHashTable<OrderedHashMap>();
return AllocateOrderedHashMap();
} else {
return AllocateOrderedHashTable<OrderedHashSet>();
return AllocateOrderedHashSet();
}
}
......
......@@ -3351,61 +3351,148 @@ TNode<NameDictionary> CodeStubAssembler::CopyNameDictionary(
}
template <typename CollectionType>
TNode<CollectionType> CodeStubAssembler::AllocateOrderedHashTable() {
static const int kCapacity = CollectionType::kInitialCapacity;
static const int kBucketCount = kCapacity / CollectionType::kLoadFactor;
static const int kDataTableLength = kCapacity * CollectionType::kEntrySize;
static const int kFixedArrayLength =
CollectionType::HashTableStartIndex() + kBucketCount + kDataTableLength;
static const int kDataTableStartIndex =
CollectionType::HashTableStartIndex() + kBucketCount;
STATIC_ASSERT(base::bits::IsPowerOfTwo(kCapacity));
STATIC_ASSERT(kCapacity <= CollectionType::MaxCapacity());
TNode<CollectionType> CodeStubAssembler::AllocateOrderedHashTable(
TNode<IntPtrT> capacity) {
capacity = IntPtrRoundUpToPowerOfTwo32(capacity);
capacity =
IntPtrMax(capacity, IntPtrConstant(CollectionType::kInitialCapacity));
return AllocateOrderedHashTableWithCapacity<CollectionType>(capacity);
}
template <typename CollectionType>
TNode<CollectionType> CodeStubAssembler::AllocateOrderedHashTableWithCapacity(
TNode<IntPtrT> capacity) {
CSA_ASSERT(this, WordIsPowerOfTwo(capacity));
CSA_ASSERT(this,
IntPtrGreaterThanOrEqual(
capacity, IntPtrConstant(CollectionType::kInitialCapacity)));
CSA_ASSERT(this,
IntPtrLessThanOrEqual(
capacity, IntPtrConstant(CollectionType::MaxCapacity())));
STATIC_ASSERT(CollectionType::kLoadFactor == 2);
TNode<IntPtrT> bucket_count = Signed(WordShr(capacity, IntPtrConstant(1)));
TNode<IntPtrT> data_table_length =
IntPtrMul(capacity, IntPtrConstant(CollectionType::kEntrySize));
TNode<IntPtrT> data_table_start_index = IntPtrAdd(
IntPtrConstant(CollectionType::HashTableStartIndex()), bucket_count);
TNode<IntPtrT> fixed_array_length =
IntPtrAdd(data_table_start_index, data_table_length);
// Allocate the table and add the proper map.
const ElementsKind elements_kind = HOLEY_ELEMENTS;
TNode<IntPtrT> length_intptr = IntPtrConstant(kFixedArrayLength);
TNode<Map> fixed_array_map =
HeapConstant(CollectionType::GetMap(ReadOnlyRoots(isolate())));
TNode<CollectionType> table =
CAST(AllocateFixedArray(elements_kind, length_intptr,
CAST(AllocateFixedArray(elements_kind, fixed_array_length,
kAllowLargeObjectAllocation, fixed_array_map));
// Initialize the OrderedHashTable fields.
Comment("Initialize the OrderedHashTable fields.");
const WriteBarrierMode barrier_mode = SKIP_WRITE_BARRIER;
StoreFixedArrayElement(table, CollectionType::NumberOfElementsIndex(),
SmiConstant(0), barrier_mode);
StoreFixedArrayElement(table, CollectionType::NumberOfDeletedElementsIndex(),
SmiConstant(0), barrier_mode);
StoreFixedArrayElement(table, CollectionType::NumberOfBucketsIndex(),
SmiConstant(kBucketCount), barrier_mode);
// Fill the buckets with kNotFound.
TNode<Smi> not_found = SmiConstant(CollectionType::kNotFound);
UnsafeStoreFixedArrayElement(table, CollectionType::NumberOfElementsIndex(),
SmiConstant(0), barrier_mode);
UnsafeStoreFixedArrayElement(table,
CollectionType::NumberOfDeletedElementsIndex(),
SmiConstant(0), barrier_mode);
UnsafeStoreFixedArrayElement(table, CollectionType::NumberOfBucketsIndex(),
SmiFromIntPtr(bucket_count), barrier_mode);
TNode<IntPtrT> object_address = BitcastTaggedToWord(table);
STATIC_ASSERT(CollectionType::HashTableStartIndex() ==
CollectionType::NumberOfBucketsIndex() + 1);
STATIC_ASSERT((CollectionType::HashTableStartIndex() + kBucketCount) ==
kDataTableStartIndex);
for (int i = 0; i < kBucketCount; i++) {
StoreFixedArrayElement(table, CollectionType::HashTableStartIndex() + i,
not_found, barrier_mode);
}
// Fill the data table with undefined.
STATIC_ASSERT(kDataTableStartIndex + kDataTableLength == kFixedArrayLength);
for (int i = 0; i < kDataTableLength; i++) {
StoreFixedArrayElement(table, kDataTableStartIndex + i, UndefinedConstant(),
barrier_mode);
TNode<Smi> not_found_sentinel = SmiConstant(CollectionType::kNotFound);
intptr_t const_capacity;
if (TryToIntPtrConstant(capacity, &const_capacity) &&
const_capacity == CollectionType::kInitialCapacity) {
int const_bucket_count =
static_cast<int>(const_capacity / CollectionType::kLoadFactor);
int const_data_table_length =
static_cast<int>(const_capacity * CollectionType::kEntrySize);
int const_data_table_start_index = static_cast<int>(
CollectionType::HashTableStartIndex() + const_bucket_count);
Comment("Fill the buckets with kNotFound (constant capacity).");
for (int i = 0; i < const_bucket_count; i++) {
UnsafeStoreFixedArrayElement(table,
CollectionType::HashTableStartIndex() + i,
not_found_sentinel, barrier_mode);
}
Comment("Fill the data table with undefined (constant capacity).");
for (int i = 0; i < const_data_table_length; i++) {
UnsafeStoreFixedArrayElement(table, const_data_table_start_index + i,
UndefinedConstant(), barrier_mode);
}
} else {
Comment("Fill the buckets with kNotFound.");
TNode<IntPtrT> buckets_start_address =
IntPtrAdd(object_address,
IntPtrConstant(FixedArray::OffsetOfElementAt(
CollectionType::HashTableStartIndex()) -
kHeapObjectTag));
TNode<IntPtrT> buckets_end_address =
IntPtrAdd(buckets_start_address, TimesTaggedSize(bucket_count));
StoreFieldsNoWriteBarrier(buckets_start_address, buckets_end_address,
not_found_sentinel);
Comment("Fill the data table with undefined.");
TNode<IntPtrT> data_start_address = buckets_end_address;
TNode<IntPtrT> data_end_address = IntPtrAdd(
object_address,
IntPtrAdd(IntPtrConstant(FixedArray::kHeaderSize - kHeapObjectTag),
TimesTaggedSize(fixed_array_length)));
StoreFieldsNoWriteBarrier(data_start_address, data_end_address,
UndefinedConstant());
#ifdef DEBUG
TNode<IntPtrT> ptr_diff =
IntPtrSub(data_end_address, buckets_start_address);
TNode<IntPtrT> array_length = LoadAndUntagFixedArrayBaseLength(table);
TNode<IntPtrT> array_data_fields = IntPtrSub(
array_length, IntPtrConstant(CollectionType::HashTableStartIndex()));
TNode<IntPtrT> expected_end =
IntPtrAdd(data_start_address,
TimesTaggedSize(IntPtrMul(
capacity, IntPtrConstant(CollectionType::kEntrySize))));
CSA_ASSERT(this, IntPtrEqual(ptr_diff, TimesTaggedSize(array_data_fields)));
CSA_ASSERT(this, IntPtrEqual(expected_end, data_end_address));
#endif
}
return table;
}
template TNode<OrderedHashMap>
CodeStubAssembler::AllocateOrderedHashTable<OrderedHashMap>();
template TNode<OrderedHashSet>
CodeStubAssembler::AllocateOrderedHashTable<OrderedHashSet>();
TNode<OrderedNameDictionary> CodeStubAssembler::AllocateOrderedNameDictionary(
TNode<IntPtrT> capacity) {
TNode<OrderedNameDictionary> table =
AllocateOrderedHashTable<OrderedNameDictionary>(capacity);
StoreFixedArrayElement(table, OrderedNameDictionary::PrefixIndex(),
SmiConstant(PropertyArray::kNoHashSentinel),
SKIP_WRITE_BARRIER);
return table;
}
TNode<OrderedNameDictionary> CodeStubAssembler::AllocateOrderedNameDictionary(
int capacity) {
return AllocateOrderedNameDictionary(IntPtrConstant(capacity));
}
TNode<OrderedHashSet> CodeStubAssembler::AllocateOrderedHashSet() {
return AllocateOrderedHashTableWithCapacity<OrderedHashSet>(
IntPtrConstant(OrderedHashSet::kInitialCapacity));
}
TNode<OrderedHashMap> CodeStubAssembler::AllocateOrderedHashMap() {
return AllocateOrderedHashTableWithCapacity<OrderedHashMap>(
IntPtrConstant(OrderedHashMap::kInitialCapacity));
}
TNode<JSObject> CodeStubAssembler::AllocateJSObjectFromMap(
TNode<Map> map, base::Optional<TNode<HeapObject>> properties,
......
......@@ -1735,8 +1735,15 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<NameDictionary> CopyNameDictionary(TNode<NameDictionary> dictionary,
Label* large_object_fallback);
template <typename CollectionType>
TNode<CollectionType> AllocateOrderedHashTable();
TNode<OrderedHashSet> AllocateOrderedHashSet();
TNode<OrderedHashMap> AllocateOrderedHashMap();
// Allocates an OrderedNameDictionary of the given capacity. This guarantees
// that |capacity| entries can be added without reallocating.
TNode<OrderedNameDictionary> AllocateOrderedNameDictionary(
TNode<IntPtrT> capacity);
TNode<OrderedNameDictionary> AllocateOrderedNameDictionary(int capacity);
TNode<JSObject> AllocateJSObjectFromMap(
TNode<Map> map,
......@@ -3662,6 +3669,15 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
base::Optional<TNode<AllocationSite>> allocation_site,
TNode<IntPtrT> size_in_bytes);
// Increases the provided capacity to the next valid value, if necessary.
template <typename CollectionType>
TNode<CollectionType> AllocateOrderedHashTable(TNode<IntPtrT> capacity);
// Uses the provided capacity (which must be valid) in verbatim.
template <typename CollectionType>
TNode<CollectionType> AllocateOrderedHashTableWithCapacity(
TNode<IntPtrT> capacity);
TNode<IntPtrT> SmiShiftBitsConstant() {
return IntPtrConstant(kSmiShiftSize + kSmiTagSize);
}
......
......@@ -167,11 +167,11 @@ class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
Handle<FrameArray> NewFrameArray(int number_of_frames);
// Allocates a |NameDictionary| with an internal capacity calculated such that
// Allocates a NameDictionary with an internal capacity calculated such that
// |at_least_space_for| entries can be added without reallocating.
Handle<NameDictionary> NewNameDictionary(int at_least_space_for);
// Allocates an |OrderedNameDictionary| of the given capacity. This guarantees
// Allocates an OrderedNameDictionary of the given capacity. This guarantees
// that |capacity| entries can be added without reallocating.
Handle<OrderedNameDictionary> NewOrderedNameDictionary(
int capacity = OrderedNameDictionary::kInitialCapacity);
......
......@@ -1902,7 +1902,20 @@ TEST(AllocateJSObjectFromMap) {
}
}
TEST(AllocateNameDictionary) {
namespace {
template <typename Dictionary>
using CSAAllocator =
std::function<TNode<Dictionary>(CodeStubAssembler&, TNode<IntPtrT>)> const&;
template <typename Dictionary>
using Allocator = std::function<Handle<Dictionary>(Isolate*, int)> const&;
// Tests that allocation code emitted by {csa_alloc} yields ordered hash tables
// identical to those produced by {alloc}.
template <typename Dictionary>
void TestDictionaryAllocation(CSAAllocator<Dictionary> csa_alloc,
Allocator<Dictionary> alloc, int max_capacity) {
Isolate* isolate(CcTest::InitIsolateOnce());
const int kNumParams = 1;
......@@ -1911,18 +1924,17 @@ TEST(AllocateNameDictionary) {
{
auto capacity = m.Parameter<Smi>(1);
TNode<NameDictionary> result =
m.AllocateNameDictionary(m.SmiUntag(capacity));
TNode<Dictionary> result = csa_alloc(m, m.SmiUntag(capacity));
m.Return(result);
}
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
{
for (int i = 0; i < 256; i = i * 1.1 + 1) {
for (int i = 0; i < max_capacity; i = i * 1.1 + 1) {
Handle<HeapObject> result = Handle<HeapObject>::cast(
ft.Call(handle(Smi::FromInt(i), isolate)).ToHandleChecked());
Handle<NameDictionary> dict = NameDictionary::New(isolate, i);
Handle<Dictionary> dict = alloc(isolate, i);
// Both dictionaries should be memory equal.
int size = dict->Size();
CHECK_EQ(0, memcmp(reinterpret_cast<void*>(dict->address()),
......@@ -1931,6 +1943,52 @@ TEST(AllocateNameDictionary) {
}
}
} // namespace
TEST(AllocateNameDictionary) {
auto csa_alloc = [](CodeStubAssembler& m, TNode<IntPtrT> cap) {
return m.AllocateNameDictionary(cap);
};
auto alloc = [](Isolate* isolate, int capacity) {
return NameDictionary::New(isolate, capacity);
};
TestDictionaryAllocation<NameDictionary>(csa_alloc, alloc, 256);
}
TEST(AllocateOrderedNameDictionary) {
auto csa_alloc = [](CodeStubAssembler& m, TNode<IntPtrT> cap) {
return m.AllocateOrderedNameDictionary(cap);
};
auto alloc = [](Isolate* isolate, int capacity) {
return OrderedNameDictionary::Allocate(isolate, capacity).ToHandleChecked();
};
TestDictionaryAllocation<OrderedNameDictionary>(csa_alloc, alloc, 256);
}
TEST(AllocateOrderedHashSet) {
// ignoring capacitites, as the API cannot take them
auto csa_alloc = [](CodeStubAssembler& m, TNode<IntPtrT> cap) {
return m.AllocateOrderedHashSet();
};
auto alloc = [](Isolate* isolate, int capacity) {
return OrderedHashSet::Allocate(isolate, OrderedHashSet::kInitialCapacity)
.ToHandleChecked();
};
TestDictionaryAllocation<OrderedHashSet>(csa_alloc, alloc, 1);
}
TEST(AllocateOrderedHashMap) {
// ignoring capacities, as the API cannot take them
auto csa_alloc = [](CodeStubAssembler& m, TNode<IntPtrT> cap) {
return m.AllocateOrderedHashMap();
};
auto alloc = [](Isolate* isolate, int capacity) {
return OrderedHashMap::Allocate(isolate, OrderedHashMap::kInitialCapacity)
.ToHandleChecked();
};
TestDictionaryAllocation<OrderedHashMap>(csa_alloc, alloc, 1);
}
TEST(PopAndReturnFromJSBuiltinWithStackParameters) {
Isolate* isolate(CcTest::InitIsolateOnce());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment