Commit f4664d02 authored by Sathya Gunasekaran's avatar Sathya Gunasekaran Committed by Commit Bot

[runtime] Implement SmallOrderedHashTable

Implements the Allocate, Add, and HasKey operations. Also, adds GC
support for this new instance type.

Bug: v8:6443
Change-Id: I1cc7ba2faead2a11f7b0381a57858629e123aee6
Reviewed-on: https://chromium-review.googlesource.com/500447
Commit-Queue: Sathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarHannes Payer <hpayer@chromium.org>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#45551}
parent eef603fe
......@@ -8800,8 +8800,8 @@ class Internals {
static const int kNodeIsIndependentShift = 3;
static const int kNodeIsActiveShift = 4;
static const int kJSApiObjectType = 0xbb;
static const int kJSObjectType = 0xbc;
static const int kJSApiObjectType = 0xbc;
static const int kJSObjectType = 0xbd;
static const int kFirstNonstringType = 0x80;
static const int kOddballType = 0x82;
static const int kForeignType = 0x86;
......
......@@ -310,6 +310,7 @@ AstType::bitset AstBitsetType::Lub(i::Map* map) {
case CELL_TYPE:
case WEAK_CELL_TYPE:
case PROTOTYPE_INFO_TYPE:
case SMALL_ORDERED_HASH_SET_TYPE:
case TUPLE2_TYPE:
case TUPLE3_TYPE:
case CONTEXT_EXTENSION_TYPE:
......
......@@ -320,6 +320,7 @@ Type::bitset BitsetType::Lub(i::Map* map) {
case DEBUG_INFO_TYPE:
case STACK_FRAME_INFO_TYPE:
case WEAK_CELL_TYPE:
case SMALL_ORDERED_HASH_SET_TYPE:
case PROTOTYPE_INFO_TYPE:
case TUPLE2_TYPE:
case TUPLE3_TYPE:
......
......@@ -4212,6 +4212,7 @@ Handle<Object> TranslatedState::MaterializeCapturedObjectAt(
case STACK_FRAME_INFO_TYPE:
case CELL_TYPE:
case WEAK_CELL_TYPE:
case SMALL_ORDERED_HASH_SET_TYPE:
case PROTOTYPE_INFO_TYPE:
case TUPLE2_TYPE:
case TUPLE3_TYPE:
......
......@@ -240,6 +240,15 @@ Handle<FrameArray> Factory::NewFrameArray(int number_of_frames,
return Handle<FrameArray>::cast(result);
}
Handle<SmallOrderedHashSet> Factory::NewSmallOrderedHashSet(
int size, PretenureFlag pretenure) {
DCHECK_LE(0, size);
CALL_HEAP_FUNCTION(
isolate(),
isolate()->heap()->AllocateSmallOrderedHashSet(size, pretenure),
SmallOrderedHashSet);
}
Handle<OrderedHashSet> Factory::NewOrderedHashSet() {
return OrderedHashSet::Allocate(isolate(), OrderedHashSet::kMinCapacity);
}
......
......@@ -82,6 +82,10 @@ class V8_EXPORT_PRIVATE Factory final {
Handle<OrderedHashSet> NewOrderedHashSet();
Handle<OrderedHashMap> NewOrderedHashMap();
Handle<SmallOrderedHashSet> NewSmallOrderedHashSet(
int size = SmallOrderedHashSet::kMinCapacity,
PretenureFlag pretenure = NOT_TENURED);
// Create a new PrototypeInfo struct.
Handle<PrototypeInfo> NewPrototypeInfo();
......
......@@ -2391,6 +2391,7 @@ bool Heap::CreateInitialMaps() {
ALLOCATE_VARSIZE_MAP(BYTE_ARRAY_TYPE, byte_array)
ALLOCATE_VARSIZE_MAP(BYTECODE_ARRAY_TYPE, bytecode_array)
ALLOCATE_VARSIZE_MAP(FREE_SPACE_TYPE, free_space)
ALLOCATE_VARSIZE_MAP(SMALL_ORDERED_HASH_SET_TYPE, small_ordered_hash_set)
#define ALLOCATE_FIXED_TYPED_ARRAY_MAP(Type, type, TYPE, ctype, size) \
ALLOCATE_VARSIZE_MAP(FIXED_##TYPE##_ARRAY_TYPE, fixed_##type##_array)
......@@ -3037,6 +3038,25 @@ AllocationResult Heap::AllocateForeign(Address address,
return result;
}
AllocationResult Heap::AllocateSmallOrderedHashSet(int capacity,
PretenureFlag pretenure) {
DCHECK_EQ(0, capacity % SmallOrderedHashSet::kLoadFactor);
CHECK_GE(SmallOrderedHashSet::kMaxCapacity, capacity);
int size = SmallOrderedHashSet::Size(capacity);
AllocationSpace space = SelectSpace(pretenure);
HeapObject* result = nullptr;
{
AllocationResult allocation = AllocateRaw(size, space);
if (!allocation.To(&result)) return allocation;
}
result->set_map_after_allocation(small_ordered_hash_set_map(),
SKIP_WRITE_BARRIER);
Handle<SmallOrderedHashSet> table(SmallOrderedHashSet::cast(result));
table->Initialize(isolate(), capacity);
return result;
}
AllocationResult Heap::AllocateByteArray(int length, PretenureFlag pretenure) {
if (length < 0 || length > ByteArray::kMaxLength) {
......
......@@ -91,6 +91,7 @@ using v8::MemoryPressureLevel;
V(Map, ordered_hash_table_map, OrderedHashTableMap) \
V(Map, unseeded_number_dictionary_map, UnseededNumberDictionaryMap) \
V(Map, sloppy_arguments_elements_map, SloppyArgumentsElementsMap) \
V(Map, small_ordered_hash_set_map, SmallOrderedHashSetMap) \
V(Map, message_object_map, JSMessageObjectMap) \
V(Map, external_map, ExternalMap) \
V(Map, bytecode_array_map, BytecodeArrayMap) \
......@@ -315,6 +316,7 @@ using v8::MemoryPressureLevel;
V(OnePointerFillerMap) \
V(OptimizedOut) \
V(OrderedHashTableMap) \
V(SmallOrderedHashSetMap) \
V(ScopeInfoMap) \
V(ScriptContextMap) \
V(SharedFunctionInfoMap) \
......@@ -2011,6 +2013,9 @@ class Heap {
MUST_USE_RESULT AllocationResult
AllocateFixedArray(int length, PretenureFlag pretenure = NOT_TENURED);
MUST_USE_RESULT AllocationResult AllocateSmallOrderedHashSet(
int length, PretenureFlag pretenure = NOT_TENURED);
// Allocate an uninitialized object. The memory is non-executable if the
// hardware and OS allow. This is the single choke-point for allocations
// performed by the runtime and should not be bypassed (to extend this to
......
......@@ -93,6 +93,11 @@ void StaticNewSpaceVisitor<StaticVisitor>::Initialize() {
&FlexibleBodyVisitor<StaticVisitor, JSWeakCollection::BodyDescriptor,
int>::Visit);
table_.Register(
kVisitSmallOrderedHashSet,
&FlexibleBodyVisitor<StaticVisitor, SmallOrderedHashSet::BodyDescriptor,
int>::Visit);
table_.Register(kVisitJSRegExp, &JSObjectVisitor::Visit);
table_.Register(kVisitDataObject, &DataObjectVisitor::Visit);
......@@ -191,6 +196,11 @@ void StaticMarkingVisitor<StaticVisitor>::Initialize() {
&FixedBodyVisitor<StaticVisitor, PropertyCell::BodyDescriptor,
void>::Visit);
table_.Register(
kVisitSmallOrderedHashSet,
&FlexibleBodyVisitor<StaticVisitor, SmallOrderedHashSet::BodyDescriptor,
void>::Visit);
table_.Register(kVisitWeakCell, &VisitWeakCell);
table_.Register(kVisitTransitionArray, &VisitTransitionArray);
......
......@@ -102,6 +102,9 @@ VisitorId StaticVisitorBase::GetVisitorId(int instance_type, int instance_size,
case JS_ARRAY_BUFFER_TYPE:
return kVisitJSArrayBuffer;
case SMALL_ORDERED_HASH_SET_TYPE:
return kVisitSmallOrderedHashSet;
case JS_OBJECT_TYPE:
case JS_ERROR_TYPE:
case JS_ARGUMENTS_TYPE:
......
......@@ -25,39 +25,40 @@ namespace v8 {
namespace internal {
#define VISITOR_ID_LIST(V) \
V(SeqOneByteString) \
V(SeqTwoByteString) \
V(ShortcutCandidate) \
V(AllocationSite) \
V(ByteArray) \
V(BytecodeArray) \
V(FreeSpace) \
V(Cell) \
V(Code) \
V(ConsString) \
V(DataObject) \
V(FixedArray) \
V(FixedDoubleArray) \
V(FixedTypedArrayBase) \
V(FixedFloat64Array) \
V(NativeContext) \
V(AllocationSite) \
V(DataObject) \
V(JSObjectFast) \
V(JSObject) \
V(FixedTypedArrayBase) \
V(FreeSpace) \
V(JSApiObject) \
V(Struct) \
V(ConsString) \
V(SlicedString) \
V(ThinString) \
V(Symbol) \
V(Oddball) \
V(Code) \
V(JSArrayBuffer) \
V(JSFunction) \
V(JSObject) \
V(JSObjectFast) \
V(JSRegExp) \
V(JSWeakCollection) \
V(Map) \
V(Cell) \
V(NativeContext) \
V(Oddball) \
V(PropertyCell) \
V(WeakCell) \
V(TransitionArray) \
V(SeqOneByteString) \
V(SeqTwoByteString) \
V(SharedFunctionInfo) \
V(JSFunction) \
V(JSWeakCollection) \
V(JSArrayBuffer) \
V(JSRegExp)
V(ShortcutCandidate) \
V(SlicedString) \
V(SmallOrderedHashSet) \
V(Struct) \
V(Symbol) \
V(ThinString) \
V(TransitionArray) \
V(WeakCell)
// For data objects, JS objects and structs along with generic visitor which
// can visit object of any size we provide visitors specialized by
......@@ -359,9 +360,10 @@ VisitorDispatchTable<typename StaticMarkingVisitor<StaticVisitor>::Callback>
V(SeqTwoByteString) \
V(SharedFunctionInfo) \
V(SlicedString) \
V(SmallOrderedHashSet) \
V(Symbol) \
V(TransitionArray) \
V(ThinString) \
V(TransitionArray) \
V(WeakCell)
// The base class for visitors that need to dispatch on object type.
......
......@@ -7,6 +7,7 @@
#include "src/assembler-inl.h"
#include "src/objects-body-descriptors.h"
#include "src/objects/hash-table.h"
#include "src/transitions.h"
namespace v8 {
......@@ -245,6 +246,40 @@ class JSArrayBuffer::BodyDescriptor final : public BodyDescriptorBase {
}
};
class SmallOrderedHashSet::BodyDescriptor final : public BodyDescriptorBase {
public:
static bool IsValidSlot(HeapObject* obj, int offset) {
SmallOrderedHashSet* table = reinterpret_cast<SmallOrderedHashSet*>(obj);
if (offset < table->GetDataTableStartOffset()) return false;
return IsValidSlotImpl(obj, offset);
}
template <typename ObjectVisitor>
static inline void IterateBody(HeapObject* obj, int object_size,
ObjectVisitor* v) {
SmallOrderedHashSet* table = reinterpret_cast<SmallOrderedHashSet*>(obj);
int start = table->GetDataTableStartOffset();
for (int i = 0; i < table->Capacity(); i++) {
IteratePointer(obj, start + (i * kPointerSize), v);
}
}
template <typename StaticVisitor>
static inline void IterateBody(HeapObject* obj, int object_size) {
Heap* heap = obj->GetHeap();
SmallOrderedHashSet* table = reinterpret_cast<SmallOrderedHashSet*>(obj);
int start = table->GetDataTableStartOffset();
for (int i = 0; i < table->Capacity(); i++) {
IteratePointer<StaticVisitor>(heap, obj, start + (i * kPointerSize));
}
}
static inline int SizeOf(Map* map, HeapObject* obj) {
SmallOrderedHashSet* table = reinterpret_cast<SmallOrderedHashSet*>(obj);
return table->Size();
}
};
class ByteArray::BodyDescriptor final : public BodyDescriptorBase {
public:
static bool IsValidSlot(HeapObject* obj, int offset) { return false; }
......@@ -656,7 +691,9 @@ ReturnType BodyDescriptorApply(InstanceType type, T1 p1, T2 p2, T3 p3) {
return Op::template apply<Symbol::BodyDescriptor>(p1, p2, p3);
case BYTECODE_ARRAY_TYPE:
return Op::template apply<BytecodeArray::BodyDescriptor>(p1, p2, p3);
case SMALL_ORDERED_HASH_SET_TYPE:
return Op::template apply<SmallOrderedHashSet::BodyDescriptor>(p1, p2,
p3);
case HEAP_NUMBER_TYPE:
case MUTABLE_HEAP_NUMBER_TYPE:
case FILLER_TYPE:
......
......@@ -249,6 +249,9 @@ void HeapObject::HeapObjectVerify() {
case JS_DATA_VIEW_TYPE:
JSDataView::cast(this)->JSDataViewVerify();
break;
case SMALL_ORDERED_HASH_SET_TYPE:
SmallOrderedHashSet::cast(this)->SmallOrderedHashSetVerify();
break;
#define MAKE_STRUCT_CASE(NAME, Name, name) \
case NAME##_TYPE: \
......@@ -1013,6 +1016,35 @@ void JSPromise::JSPromiseVerify() {
reject_reactions()->IsFixedArray());
}
void SmallOrderedHashSet::SmallOrderedHashSetVerify() {
CHECK(IsSmallOrderedHashSet());
Isolate* isolate = GetIsolate();
for (int entry = 0; entry < NumberOfBuckets(); entry++) {
int bucket = GetFirstEntry(entry);
if (bucket == kNotFound) continue;
Object* val = GetDataEntry(bucket);
CHECK(!val->IsTheHole(isolate));
}
for (int entry = 0; entry < NumberOfElements(); entry++) {
int chain = GetNextEntry(entry);
if (chain == kNotFound) continue;
Object* val = GetDataEntry(chain);
CHECK(!val->IsTheHole(isolate));
}
for (int entry = 0; entry < NumberOfElements(); entry++) {
Object* val = GetDataEntry(entry);
VerifyPointer(val);
}
for (int entry = NumberOfElements(); entry < Capacity(); entry++) {
Object* val = GetDataEntry(entry);
CHECK(val->IsTheHole(isolate));
}
}
void JSRegExp::JSRegExpVerify() {
JSObjectVerify();
Isolate* isolate = GetIsolate();
......
......@@ -31,6 +31,7 @@
#include "src/lookup-cache-inl.h"
#include "src/lookup.h"
#include "src/objects.h"
#include "src/objects/hash-table.h"
#include "src/objects/literal-objects.h"
#include "src/objects/module-info.h"
#include "src/objects/regexp-match-info.h"
......@@ -115,6 +116,7 @@ TYPE_CHECKER(TransitionArray, TRANSITION_ARRAY_TYPE)
TYPE_CHECKER(TypeFeedbackInfo, TUPLE3_TYPE)
TYPE_CHECKER(WeakCell, WEAK_CELL_TYPE)
TYPE_CHECKER(WeakFixedArray, FIXED_ARRAY_TYPE)
TYPE_CHECKER(SmallOrderedHashSet, SMALL_ORDERED_HASH_SET_TYPE)
#define TYPED_ARRAY_TYPE_CHECKER(Type, type, TYPE, ctype, size) \
TYPE_CHECKER(Fixed##Type##Array, FIXED_##TYPE##_ARRAY_TYPE)
......@@ -640,6 +642,7 @@ CAST_ACCESSOR(Tuple3)
CAST_ACCESSOR(TypeFeedbackInfo)
CAST_ACCESSOR(UnseededNumberDictionary)
CAST_ACCESSOR(WeakCell)
CAST_ACCESSOR(SmallOrderedHashSet)
CAST_ACCESSOR(WeakFixedArray)
CAST_ACCESSOR(WeakHashTable)
......@@ -4209,6 +4212,9 @@ int HeapObject::SizeFromMap(Map* map) {
return reinterpret_cast<FixedTypedArrayBase*>(
this)->TypedArraySize(instance_type);
}
if (instance_type == SMALL_ORDERED_HASH_SET_TYPE) {
return reinterpret_cast<SmallOrderedHashSet*>(this)->Size();
}
DCHECK(instance_type == CODE_TYPE);
return reinterpret_cast<Code*>(this)->CodeSize();
}
......@@ -6417,6 +6423,12 @@ void Foreign::set_foreign_address(Address value) {
WRITE_INTPTR_FIELD(this, kForeignAddressOffset, OffsetFrom(value));
}
template <class Derived>
void SmallOrderedHashTable<Derived>::SetDataEntry(int entry, Object* value) {
int offset = GetDataEntryOffset(entry);
NOBARRIER_WRITE_FIELD(this, offset, value);
WRITE_BARRIER(GetHeap(), this, offset, value);
}
ACCESSORS(JSGeneratorObject, function, JSFunction, kFunctionOffset)
ACCESSORS(JSGeneratorObject, context, Context, kContextOffset)
......
......@@ -59,6 +59,7 @@
#include "src/objects/compilation-cache-inl.h"
#include "src/objects/debug-objects-inl.h"
#include "src/objects/frame-array-inl.h"
#include "src/objects/hash-table.h"
#include "src/objects/map.h"
#include "src/property-descriptor.h"
#include "src/prototype.h"
......@@ -3159,6 +3160,8 @@ void HeapNumber::HeapNumberPrint(std::ostream& os) { // NOLINT
os << value();
}
#define FIELD_ADDR(p, offset) \
(reinterpret_cast<byte*>(p) + offset - kHeapObjectTag)
#define FIELD_ADDR_CONST(p, offset) \
(reinterpret_cast<const byte*>(p) + offset - kHeapObjectTag)
......@@ -18883,6 +18886,173 @@ template Handle<OrderedHashMap> OrderedHashTable<OrderedHashMap, 2>::Clear(
template bool OrderedHashTable<OrderedHashMap, 2>::HasKey(
Handle<OrderedHashMap> table, Handle<Object> key);
template <>
Handle<SmallOrderedHashSet>
SmallOrderedHashTable<SmallOrderedHashSet>::Allocate(Isolate* isolate,
int capacity,
PretenureFlag pretenure) {
return isolate->factory()->NewSmallOrderedHashSet(capacity, pretenure);
}
template <class Derived>
void SmallOrderedHashTable<Derived>::Initialize(Isolate* isolate,
int capacity) {
int num_buckets = capacity / kLoadFactor;
int num_chains = capacity;
SetNumberOfBuckets(num_buckets);
SetNumberOfElements(0);
SetNumberOfDeletedElements(0);
byte* hashtable_start =
FIELD_ADDR(this, kHeaderSize + (kBucketsStartOffset * kOneByteSize));
memset(hashtable_start, kNotFound, num_buckets + num_chains);
if (isolate->heap()->InNewSpace(this)) {
MemsetPointer(RawField(this, GetDataTableStartOffset()),
isolate->heap()->the_hole_value(), capacity);
} else {
for (int i = 0; i < capacity; i++) {
SetDataEntry(i, isolate->heap()->the_hole_value());
}
}
#ifdef DEBUG
for (int i = 0; i < num_buckets; ++i) {
DCHECK_EQ(kNotFound, GetFirstEntry(i));
}
for (int i = 0; i < num_chains; ++i) {
DCHECK_EQ(kNotFound, GetNextEntry(i));
}
#endif // DEBUG
}
template <class Derived>
Handle<Derived> SmallOrderedHashTable<Derived>::Add(Handle<Derived> table,
Handle<Object> key) {
Isolate* isolate = table->GetIsolate();
if (table->HasKey(isolate, key)) return table;
if (table->UsedCapacity() >= table->Capacity()) {
table = Derived::Grow(table);
}
int hash = Object::GetOrCreateHash(table->GetIsolate(), key)->value();
int nof = table->NumberOfElements();
// Read the existing bucket values.
int bucket = table->HashToBucket(hash);
int previous_entry = table->HashToFirstEntry(hash);
// Insert a new entry at the end,
int new_entry = nof + table->NumberOfDeletedElements();
// TODO(gsathya): Add support for entrysize > 1.
table->SetDataEntry(new_entry, *key);
table->SetFirstEntry(bucket, new_entry);
table->SetNextEntry(new_entry, previous_entry);
// and update book keeping.
table->SetNumberOfElements(nof + 1);
return table;
}
template <class Derived>
bool SmallOrderedHashTable<Derived>::HasKey(Isolate* isolate,
Handle<Object> key) {
DisallowHeapAllocation no_gc;
Object* raw_key = *key;
Object* hash = key->GetHash();
if (hash->IsUndefined(isolate)) return false;
int entry = HashToFirstEntry(Smi::cast(hash)->value());
// Walk the chain in the bucket to find the key.
while (entry != kNotFound) {
Object* candidate_key = KeyAt(entry);
if (candidate_key->SameValueZero(raw_key)) return true;
entry = GetNextEntry(entry);
}
return false;
}
template <class Derived>
Handle<Derived> SmallOrderedHashTable<Derived>::Rehash(Handle<Derived> table,
int new_capacity) {
DCHECK_GE(kMaxCapacity, new_capacity);
Isolate* isolate = table->GetIsolate();
Handle<Derived> new_table = SmallOrderedHashTable<Derived>::Allocate(
isolate, new_capacity,
isolate->heap()->InNewSpace(*table) ? NOT_TENURED : TENURED);
int nof = table->NumberOfElements();
int nod = table->NumberOfDeletedElements();
int new_entry = 0;
{
DisallowHeapAllocation no_gc;
for (int old_entry = 0; old_entry < (nof + nod); ++old_entry) {
Object* key = table->KeyAt(old_entry);
if (key->IsTheHole(isolate)) continue;
int hash = Smi::cast(key->GetHash())->value();
int bucket = new_table->HashToBucket(hash);
int chain = new_table->GetFirstEntry(bucket);
new_table->SetFirstEntry(bucket, new_entry);
new_table->SetNextEntry(new_entry, chain);
for (int i = 0; i < Derived::kEntrySize; ++i) {
Object* value = table->GetDataEntry(old_entry + i);
new_table->SetDataEntry(new_entry + i, value);
}
++new_entry;
}
new_table->SetNumberOfElements(nof);
}
return new_table;
}
template <class Derived>
Handle<Derived> SmallOrderedHashTable<Derived>::Grow(Handle<Derived> table) {
int capacity = table->Capacity();
int new_capacity = capacity;
// Don't need to grow if we can simply clear out deleted entries instead.
// TODO(gsathya): Compact in place, instead of allocating a new table.
if (table->NumberOfDeletedElements() < (capacity >> 1)) {
new_capacity = capacity << 1;
// The max capacity of our table is 254. We special case for 256 to
// account for our growth strategy, otherwise we would only fill up
// to 128 entries in our table.
if (new_capacity == kGrowthHack) {
new_capacity = kMaxCapacity;
}
// TODO(gsathya): Transition to OrderedHashTable for size > kMaxCapacity.
}
return Rehash(table, new_capacity);
}
template bool SmallOrderedHashTable<SmallOrderedHashSet>::HasKey(
Isolate* isolate, Handle<Object> key);
template Handle<SmallOrderedHashSet>
SmallOrderedHashTable<SmallOrderedHashSet>::Add(
Handle<SmallOrderedHashSet> table, Handle<Object> key);
template Handle<SmallOrderedHashSet>
SmallOrderedHashTable<SmallOrderedHashSet>::Rehash(
Handle<SmallOrderedHashSet> table, int new_capacity);
template Handle<SmallOrderedHashSet> SmallOrderedHashTable<
SmallOrderedHashSet>::Grow(Handle<SmallOrderedHashSet> table);
template void SmallOrderedHashTable<SmallOrderedHashSet>::Initialize(
Isolate* isolate, int capacity);
template<class Derived, class TableType>
void OrderedHashTableIterator<Derived, TableType>::Transition() {
DisallowHeapAllocation no_allocation;
......
......@@ -151,6 +151,7 @@
// - Module
// - ModuleInfoEntry
// - WeakCell
// - SmallOrderedHashSet
//
// Formats of Object*:
// Smi: [31 bit signed int] 0
......@@ -370,6 +371,7 @@ const int kStubMinorKeyBits = kSmiValueSize - kStubMajorKeyBits - 1;
V(CELL_TYPE) \
V(WEAK_CELL_TYPE) \
V(PROPERTY_CELL_TYPE) \
V(SMALL_ORDERED_HASH_SET_TYPE) \
/* TODO(yangguo): these padding types are for ABI stability. Remove after*/ \
/* version 6.0 branch, or replace them when there is demand for new types.*/ \
V(PADDING_TYPE_1) \
......@@ -712,6 +714,7 @@ enum InstanceType {
CELL_TYPE,
WEAK_CELL_TYPE,
PROPERTY_CELL_TYPE,
SMALL_ORDERED_HASH_SET_TYPE,
// TODO(yangguo): these padding types are for ABI stability. Remove after
// version 6.0 branch, or replace them when there is demand for new types.
......@@ -1089,6 +1092,7 @@ template <class C> inline bool Is(Object* obj);
V(SharedFunctionInfo) \
V(SlicedString) \
V(SloppyArgumentsElements) \
V(SmallOrderedHashSet) \
V(SourcePositionTableWithFrameCache) \
V(String) \
V(StringSet) \
......
......@@ -584,6 +584,201 @@ class WeakHashTable
}
};
// This is similar to the OrderedHashTable, except for the memory
// layout where we use byte instead of Smi. The max capacity of this
// is only 254, we transition to an OrderedHashTable beyond that
// limit.
//
// Each bucket and chain value is a byte long. The padding exists so
// that the DataTable entries start aligned. A bucket or chain value
// of 255 is used to denote an unknown entry.
//
// Memory layout: [ Header ] [ HashTable ] [ Chains ] [ Padding ] [ DataTable ]
//
// On a 64 bit machine with capacity = 4 and 2 entries,
//
// [ Header ] :
// [0 .. 7] : Number of elements
// [8 .. 15] : Number of deleted elements
// [16 .. 23] : Number of buckets
//
// [ HashTable ] :
// [24 .. 31] : First chain-link for bucket 1
// [32 .. 40] : First chain-link for bucket 2
//
// [ Chains ] :
// [40 .. 47] : Next chain link for entry 1
// [48 .. 55] : Next chain link for entry 2
// [56 .. 63] : Next chain link for entry 3
// [64 .. 71] : Next chain link for entry 4
//
// [ Padding ] :
// [72 .. 127] : Padding
//
// [ DataTable ] :
// [128 .. 128 + kEntrySize - 1] : Entry 1
// [128 + kEntrySize .. 128 + kEntrySize + kEntrySize - 1] : Entry 2
//
template <class Derived>
class SmallOrderedHashTable : public HeapObject {
public:
void Initialize(Isolate* isolate, int capacity);
static Handle<Derived> Allocate(Isolate* isolate, int capacity,
PretenureFlag pretenure = NOT_TENURED);
// Adds |value| to |table|, if the capacity isn't enough, a new
// table is created. The original |table| is returned if there is
// capacity to store |value| otherwise the new table is returned.
static Handle<Derived> Add(Handle<Derived> table, Handle<Object> value);
// Returns a true if the OrderedHashTable contains the key
bool HasKey(Isolate* isolate, Handle<Object> key);
// Iterates only fields in the DataTable.
class BodyDescriptor;
// Returns an SmallOrderedHashTable (possibly |table|) with enough
// space to add at least one new element.
static Handle<Derived> Grow(Handle<Derived> table);
static Handle<Derived> Rehash(Handle<Derived> table, int new_capacity);
void SetDataEntry(int entry, Object* value);
// TODO(gsathya): There should be a better way to do this.
static int GetDataTableStartOffset(int capacity) {
int nof_buckets = capacity / kLoadFactor;
int nof_chain_entries = capacity;
int padding_index = kBucketsStartOffset + nof_buckets + nof_chain_entries;
int padding_offset = padding_index * kBitsPerByte;
return ((padding_offset + kPointerSize - 1) / kPointerSize) * kPointerSize;
}
int GetDataTableStartOffset() { return GetDataTableStartOffset(Capacity()); }
static int Size(int capacity) {
int data_table_start = GetDataTableStartOffset(capacity);
int data_table_size = capacity * Derived::kEntrySize * kBitsPerPointer;
return data_table_start + data_table_size;
}
int Size() { return Size(Capacity()); }
void SetFirstEntry(int bucket, byte value) {
set(kBucketsStartOffset + bucket, value);
}
int GetFirstEntry(int bucket) { return get(kBucketsStartOffset + bucket); }
void SetNextEntry(int entry, int next_entry) {
set(GetChainTableOffset() + entry, next_entry);
}
int GetNextEntry(int entry) { return get(GetChainTableOffset() + entry); }
Object* GetDataEntry(int entry) {
int offset = GetDataEntryOffset(entry);
return READ_FIELD(this, offset);
}
// TODO(gsathya): This will be specialized once we support entrysize > 1.
Object* KeyAt(int entry) {
int offset = GetDataEntryOffset(entry);
return READ_FIELD(this, offset);
}
int HashToBucket(int hash) { return hash & (NumberOfBuckets() - 1); }
int HashToFirstEntry(int hash) {
int bucket = HashToBucket(hash);
int entry = GetFirstEntry(bucket);
return entry;
}
int GetChainTableOffset() { return kBucketsStartOffset + NumberOfBuckets(); }
void SetNumberOfBuckets(int num) { set(kNumberOfBucketsOffset, num); }
void SetNumberOfElements(int num) { set(kNumberOfElementsOffset, num); }
void SetNumberOfDeletedElements(int num) {
set(kNumberOfDeletedElementsOffset, num);
}
int NumberOfElements() { return get(kNumberOfElementsOffset); }
int NumberOfDeletedElements() { return get(kNumberOfDeletedElementsOffset); }
int NumberOfBuckets() { return get(kNumberOfBucketsOffset); }
static const byte kNotFound = 0xFF;
static const int kMinCapacity = 4;
// We use the value 255 to indicate kNotFound for chain and bucket
// values, which means that this value can't be used a valid
// index.
static const int kMaxCapacity = 254;
STATIC_ASSERT(kMaxCapacity < kNotFound);
static const int kNumberOfElementsOffset = 0;
static const int kNumberOfDeletedElementsOffset = 1;
static const int kNumberOfBucketsOffset = 2;
static const int kBucketsStartOffset = 3;
// The load factor is used to derive the number of buckets from
// capacity during Allocation. We also depend on this to calaculate
// the capacity from number of buckets after allocation. If we
// decide to change kLoadFactor to something other than 2, capacity
// should be stored as another field of this object.
static const int kLoadFactor = 2;
static const int kBitsPerPointer = kPointerSize * kBitsPerByte;
// Our growth strategy involves doubling the capacity until we reach
// kMaxCapacity, but since the kMaxCapacity is always less than 256,
// we will never fully utilize this table. We special case for 256,
// by changing the new capacity to be kMaxCapacity in
// SmallOrderedHashTable::Grow.
static const int kGrowthHack = 256;
protected:
// This is used for accessing the non |DataTable| part of the
// structure.
byte get(int index) {
return READ_BYTE_FIELD(this, kHeaderSize + (index * kOneByteSize));
}
void set(int index, byte value) {
WRITE_BYTE_FIELD(this, kHeaderSize + (index * kOneByteSize), value);
}
int GetDataEntryOffset(int entry) {
int datatable_start = GetDataTableStartOffset();
int offset_in_datatable = entry * Derived::kEntrySize * kPointerSize;
return datatable_start + offset_in_datatable;
}
// Returns the number elements that can fit into the allocated buffer.
int Capacity() { return NumberOfBuckets() * kLoadFactor; }
int UsedCapacity() { return NumberOfElements() + NumberOfDeletedElements(); }
};
class SmallOrderedHashSet : public SmallOrderedHashTable<SmallOrderedHashSet> {
public:
DECLARE_CAST(SmallOrderedHashSet)
DECLARE_PRINTER(SmallOrderedHashSet)
DECLARE_VERIFIER(SmallOrderedHashSet)
static const int kEntrySize = 1;
// Iterates only fields in the DataTable.
class BodyDescriptor;
};
// OrderedHashTableIterator is an iterator that iterates over the keys and
// values of an OrderedHashTable.
//
......
......@@ -158,6 +158,7 @@ v8_executable("cctest") {
"test-mementos.cc",
"test-modules.cc",
"test-object.cc",
"test-orderedhashtable.cc",
"test-parsing.cc",
"test-platform.cc",
"test-profile-generator.cc",
......
......@@ -176,6 +176,7 @@
'test-mementos.cc',
'test-modules.cc',
'test-object.cc',
'test-orderedhashtable.cc',
'test-parsing.cc',
'test-platform.cc',
'test-profile-generator.cc',
......
// 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 <utility>
#include "src/v8.h"
#include "src/objects-inl.h"
#include "test/cctest/cctest.h"
using namespace v8::internal;
static Isolate* GetIsolateFrom(LocalContext* context) {
return reinterpret_cast<Isolate*>((*context)->GetIsolate());
}
void Verify(Handle<SmallOrderedHashSet> set) {
#if VERIFY_HEAP
set->ObjectVerify();
#endif
}
TEST(Insertion) {
LocalContext context;
Isolate* isolate = GetIsolateFrom(&context);
Factory* factory = isolate->factory();
HandleScope scope(isolate);
Handle<SmallOrderedHashSet> set = factory->NewSmallOrderedHashSet();
Verify(set);
CHECK_EQ(2, set->NumberOfBuckets());
CHECK_EQ(0, set->NumberOfElements());
// Add a new key.
Handle<Smi> key1(Smi::FromInt(1), isolate);
CHECK(!set->HasKey(isolate, key1));
set = SmallOrderedHashSet::Add(set, key1);
Verify(set);
CHECK_EQ(2, set->NumberOfBuckets());
CHECK_EQ(1, set->NumberOfElements());
CHECK(set->HasKey(isolate, key1));
// Add existing key.
set = SmallOrderedHashSet::Add(set, key1);
Verify(set);
CHECK_EQ(2, set->NumberOfBuckets());
CHECK_EQ(1, set->NumberOfElements());
CHECK(set->HasKey(isolate, key1));
Handle<String> key2 = factory->NewStringFromAsciiChecked("foo");
CHECK(!set->HasKey(isolate, key2));
set = SmallOrderedHashSet::Add(set, key2);
Verify(set);
CHECK_EQ(2, set->NumberOfBuckets());
CHECK_EQ(2, set->NumberOfElements());
CHECK(set->HasKey(isolate, key1));
CHECK(set->HasKey(isolate, key2));
set = SmallOrderedHashSet::Add(set, key2);
Verify(set);
CHECK_EQ(2, set->NumberOfBuckets());
CHECK_EQ(2, set->NumberOfElements());
CHECK(set->HasKey(isolate, key1));
CHECK(set->HasKey(isolate, key2));
Handle<Symbol> key3 = factory->NewSymbol();
CHECK(!set->HasKey(isolate, key3));
set = SmallOrderedHashSet::Add(set, key3);
Verify(set);
CHECK_EQ(2, set->NumberOfBuckets());
CHECK_EQ(3, set->NumberOfElements());
CHECK(set->HasKey(isolate, key1));
CHECK(set->HasKey(isolate, key2));
CHECK(set->HasKey(isolate, key3));
set = SmallOrderedHashSet::Add(set, key3);
Verify(set);
CHECK_EQ(2, set->NumberOfBuckets());
CHECK_EQ(3, set->NumberOfElements());
CHECK(set->HasKey(isolate, key1));
CHECK(set->HasKey(isolate, key2));
CHECK(set->HasKey(isolate, key3));
Handle<Object> key4 = factory->NewHeapNumber(42.0);
CHECK(!set->HasKey(isolate, key4));
set = SmallOrderedHashSet::Add(set, key4);
Verify(set);
CHECK_EQ(2, set->NumberOfBuckets());
CHECK_EQ(4, set->NumberOfElements());
CHECK(set->HasKey(isolate, key1));
CHECK(set->HasKey(isolate, key2));
CHECK(set->HasKey(isolate, key3));
CHECK(set->HasKey(isolate, key4));
set = SmallOrderedHashSet::Add(set, key4);
Verify(set);
CHECK_EQ(2, set->NumberOfBuckets());
CHECK_EQ(4, set->NumberOfElements());
CHECK(set->HasKey(isolate, key1));
CHECK(set->HasKey(isolate, key2));
CHECK(set->HasKey(isolate, key3));
CHECK(set->HasKey(isolate, key4));
}
TEST(DuplicateHashCode) {
LocalContext context;
Isolate* isolate = GetIsolateFrom(&context);
Factory* factory = isolate->factory();
HandleScope scope(isolate);
Handle<SmallOrderedHashSet> set = factory->NewSmallOrderedHashSet();
Handle<JSObject> key1 = factory->NewJSObjectWithNullProto();
set = SmallOrderedHashSet::Add(set, key1);
Verify(set);
CHECK_EQ(2, set->NumberOfBuckets());
CHECK_EQ(1, set->NumberOfElements());
CHECK(set->HasKey(isolate, key1));
Handle<Name> hash_code_symbol = isolate->factory()->hash_code_symbol();
Handle<Smi> hash =
Handle<Smi>::cast(JSObject::GetDataProperty(key1, hash_code_symbol));
Handle<JSObject> key2 = factory->NewJSObjectWithNullProto();
LookupIterator it(key2, hash_code_symbol, key2, LookupIterator::OWN);
CHECK(key2->AddDataProperty(
&it, hash, NONE, v8::internal::AccessCheckInfo::THROW_ON_ERROR,
v8::internal::AccessCheckInfo::CERTAINLY_NOT_STORE_FROM_KEYED)
.IsJust());
set = SmallOrderedHashSet::Add(set, key2);
Verify(set);
CHECK_EQ(2, set->NumberOfBuckets());
CHECK_EQ(2, set->NumberOfElements());
CHECK(set->HasKey(isolate, key1));
CHECK(set->HasKey(isolate, key2));
}
TEST(Grow) {
LocalContext context;
Isolate* isolate = GetIsolateFrom(&context);
Factory* factory = isolate->factory();
HandleScope scope(isolate);
Handle<SmallOrderedHashSet> set = factory->NewSmallOrderedHashSet();
std::vector<Handle<Object>> keys;
for (size_t i = 0; i < 4; i++) {
Handle<Smi> key(Smi::FromInt(static_cast<int>(i)), isolate);
keys.push_back(key);
}
for (size_t i = 0; i < 4; i++) {
set = SmallOrderedHashSet::Add(set, keys[i]);
Verify(set);
}
for (size_t i = 0; i < 4; i++) {
CHECK(set->HasKey(isolate, keys[i]));
Verify(set);
}
CHECK_EQ(4, set->NumberOfElements());
CHECK_EQ(2, set->NumberOfBuckets());
CHECK_EQ(0, set->NumberOfDeletedElements());
Verify(set);
for (int i = 4; i < 8; i++) {
Handle<Smi> key(Smi::FromInt(i), isolate);
keys.push_back(key);
}
for (size_t i = 4; i < 8; i++) {
set = SmallOrderedHashSet::Add(set, keys[i]);
Verify(set);
}
for (size_t i = 0; i < 8; i++) {
CHECK(set->HasKey(isolate, keys[i]));
Verify(set);
}
CHECK_EQ(8, set->NumberOfElements());
CHECK_EQ(4, set->NumberOfBuckets());
CHECK_EQ(0, set->NumberOfDeletedElements());
Verify(set);
for (int i = 8; i < 16; i++) {
Handle<Smi> key(Smi::FromInt(i), isolate);
keys.push_back(key);
}
for (size_t i = 8; i < 16; i++) {
set = SmallOrderedHashSet::Add(set, keys[i]);
Verify(set);
}
for (size_t i = 0; i < 16; i++) {
CHECK(set->HasKey(isolate, keys[i]));
Verify(set);
}
CHECK_EQ(16, set->NumberOfElements());
CHECK_EQ(8, set->NumberOfBuckets());
CHECK_EQ(0, set->NumberOfDeletedElements());
Verify(set);
for (int i = 16; i < 32; i++) {
Handle<Smi> key(Smi::FromInt(i), isolate);
keys.push_back(key);
}
for (size_t i = 16; i < 32; i++) {
set = SmallOrderedHashSet::Add(set, keys[i]);
Verify(set);
}
for (size_t i = 0; i < 32; i++) {
CHECK(set->HasKey(isolate, keys[i]));
Verify(set);
}
CHECK_EQ(32, set->NumberOfElements());
CHECK_EQ(16, set->NumberOfBuckets());
CHECK_EQ(0, set->NumberOfDeletedElements());
Verify(set);
for (int i = 32; i < 64; i++) {
Handle<Smi> key(Smi::FromInt(i), isolate);
keys.push_back(key);
}
for (size_t i = 32; i < 64; i++) {
set = SmallOrderedHashSet::Add(set, keys[i]);
Verify(set);
}
for (size_t i = 0; i < 64; i++) {
CHECK(set->HasKey(isolate, keys[i]));
Verify(set);
}
CHECK_EQ(64, set->NumberOfElements());
CHECK_EQ(32, set->NumberOfBuckets());
CHECK_EQ(0, set->NumberOfDeletedElements());
Verify(set);
for (int i = 64; i < 128; i++) {
Handle<Smi> key(Smi::FromInt(i), isolate);
keys.push_back(key);
}
for (size_t i = 64; i < 128; i++) {
set = SmallOrderedHashSet::Add(set, keys[i]);
Verify(set);
}
for (size_t i = 0; i < 128; i++) {
CHECK(set->HasKey(isolate, keys[i]));
Verify(set);
}
CHECK_EQ(128, set->NumberOfElements());
CHECK_EQ(64, set->NumberOfBuckets());
CHECK_EQ(0, set->NumberOfDeletedElements());
Verify(set);
for (int i = 128; i < 254; i++) {
Handle<Smi> key(Smi::FromInt(i), isolate);
keys.push_back(key);
}
for (size_t i = 128; i < 254; i++) {
set = SmallOrderedHashSet::Add(set, keys[i]);
Verify(set);
}
for (size_t i = 0; i < 254; i++) {
CHECK(set->HasKey(isolate, keys[i]));
Verify(set);
}
CHECK_EQ(254, set->NumberOfElements());
CHECK_EQ(127, set->NumberOfBuckets());
CHECK_EQ(0, set->NumberOfDeletedElements());
Verify(set);
}
This diff is collapsed.
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