#ifndef ds_OrderedHashTable_h
#define ds_OrderedHashTable_h
#include "mozilla/HashFunctions.h"
#include "mozilla/Move.h"
#include "js/HashTable.h"
namespace js {
namespace detail {
template <class T, class Ops, class AllocPolicy>
class OrderedHashTable {
public:
typedef typename Ops::KeyType Key;
typedef typename Ops::Lookup Lookup;
struct Data {
T element;
Data* chain;
Data(const T& e, Data* c) : element(e), chain(c) {}
Data(T&& e, Data* c) : element(std::move(e)), chain(c) {}
};
class Range;
friend class Range;
private:
Data** hashTable; Data* data; uint32_t dataLength; uint32_t dataCapacity; uint32_t liveCount; uint32_t hashShift; Range* ranges; Range*
nurseryRanges; AllocPolicy alloc;
mozilla::HashCodeScrambler hcs;
template <void (*f)(Range* range, uint32_t arg)>
void forEachRange(uint32_t arg = 0) {
Range* next;
for (Range* r = ranges; r; r = next) {
next = r->next;
f(r, arg);
}
for (Range* r = nurseryRanges; r; r = next) {
next = r->next;
f(r, arg);
}
}
public:
OrderedHashTable(AllocPolicy& ap, mozilla::HashCodeScrambler hcs)
: hashTable(nullptr),
data(nullptr),
dataLength(0),
dataCapacity(0),
liveCount(0),
hashShift(0),
ranges(nullptr),
nurseryRanges(nullptr),
alloc(ap),
hcs(hcs) {}
MOZ_MUST_USE bool init() {
MOZ_ASSERT(!hashTable, "init must be called at most once");
uint32_t buckets = initialBuckets();
Data** tableAlloc = alloc.template pod_malloc<Data*>(buckets);
if (!tableAlloc) {
return false;
}
for (uint32_t i = 0; i < buckets; i++) {
tableAlloc[i] = nullptr;
}
uint32_t capacity = uint32_t(buckets * fillFactor());
Data* dataAlloc = alloc.template pod_malloc<Data>(capacity);
if (!dataAlloc) {
alloc.free_(tableAlloc, buckets);
return false;
}
hashTable = tableAlloc;
data = dataAlloc;
dataLength = 0;
dataCapacity = capacity;
liveCount = 0;
hashShift = js::kHashNumberBits - initialBucketsLog2();
MOZ_ASSERT(hashBuckets() == buckets);
return true;
}
~OrderedHashTable() {
forEachRange<Range::onTableDestroyed>();
alloc.free_(hashTable, hashBuckets());
freeData(data, dataLength, dataCapacity);
}
uint32_t count() const { return liveCount; }
bool has(const Lookup& l) const { return lookup(l) != nullptr; }
T* get(const Lookup& l) {
Data* e = lookup(l, prepareHash(l));
return e ? &e->element : nullptr;
}
const T* get(const Lookup& l) const {
return const_cast<OrderedHashTable*>(this)->get(l);
}
template <typename ElementInput>
MOZ_MUST_USE bool put(ElementInput&& element) {
HashNumber h = prepareHash(Ops::getKey(element));
if (Data* e = lookup(Ops::getKey(element), h)) {
e->element = std::forward<ElementInput>(element);
return true;
}
if (dataLength == dataCapacity) {
uint32_t newHashShift =
liveCount >= dataCapacity * 0.75 ? hashShift - 1 : hashShift;
if (!rehash(newHashShift)) {
return false;
}
}
h >>= hashShift;
liveCount++;
Data* e = &data[dataLength++];
new (e) Data(std::forward<ElementInput>(element), hashTable[h]);
hashTable[h] = e;
return true;
}
bool remove(const Lookup& l, bool* foundp) {
Data* e = lookup(l, prepareHash(l));
if (e == nullptr) {
*foundp = false;
return true;
}
*foundp = true;
liveCount--;
Ops::makeEmpty(&e->element);
uint32_t pos = e - data;
forEachRange<&Range::onRemove>(pos);
if (hashBuckets() > initialBuckets() &&
liveCount < dataLength * minDataFill()) {
if (!rehash(hashShift + 1)) {
return false;
}
}
return true;
}
MOZ_MUST_USE bool clear() {
if (dataLength != 0) {
Data** oldHashTable = hashTable;
Data* oldData = data;
uint32_t oldHashBuckets = hashBuckets();
uint32_t oldDataLength = dataLength;
uint32_t oldDataCapacity = dataCapacity;
hashTable = nullptr;
if (!init()) {
hashTable = oldHashTable;
return false;
}
alloc.free_(oldHashTable, oldHashBuckets);
freeData(oldData, oldDataLength, oldDataCapacity);
forEachRange<&Range::onClear>();
}
MOZ_ASSERT(hashTable);
MOZ_ASSERT(data);
MOZ_ASSERT(dataLength == 0);
MOZ_ASSERT(liveCount == 0);
return true;
}
class Range {
friend class OrderedHashTable;
OrderedHashTable* ht;
uint32_t i;
uint32_t count;
Range** prevp;
Range* next;
Range(OrderedHashTable* ht, Range** listp)
: ht(ht), i(0), count(0), prevp(listp), next(*listp) {
*prevp = this;
if (next) {
next->prevp = &next;
}
seek();
}
public:
Range(const Range& other)
: ht(other.ht),
i(other.i),
count(other.count),
prevp(&ht->ranges),
next(ht->ranges) {
*prevp = this;
if (next) {
next->prevp = &next;
}
}
~Range() {
*prevp = next;
if (next) {
next->prevp = prevp;
}
}
private:
Range& operator=(const Range& other) = delete;
void seek() {
while (i < ht->dataLength &&
Ops::isEmpty(Ops::getKey(ht->data[i].element))) {
i++;
}
}
void onRemove(uint32_t j) {
MOZ_ASSERT(valid());
if (j < i) {
count--;
}
if (j == i) {
seek();
}
}
void onCompact() {
MOZ_ASSERT(valid());
i = count;
}
void onClear() {
MOZ_ASSERT(valid());
i = count = 0;
}
bool valid() const { return next != this; }
void onTableDestroyed() {
MOZ_ASSERT(valid());
prevp = &next;
next = this;
}
public:
bool empty() const {
MOZ_ASSERT(valid());
return i >= ht->dataLength;
}
T& front() {
MOZ_ASSERT(valid());
MOZ_ASSERT(!empty());
return ht->data[i].element;
}
void popFront() {
MOZ_ASSERT(valid());
MOZ_ASSERT(!empty());
MOZ_ASSERT(!Ops::isEmpty(Ops::getKey(ht->data[i].element)));
count++;
i++;
seek();
}
void rekeyFront(const Key& k) {
MOZ_ASSERT(valid());
Data& entry = ht->data[i];
HashNumber oldHash =
ht->prepareHash(Ops::getKey(entry.element)) >> ht->hashShift;
HashNumber newHash = ht->prepareHash(k) >> ht->hashShift;
Ops::setKey(entry.element, k);
if (newHash != oldHash) {
Data** ep = &ht->hashTable[oldHash];
while (*ep != &entry) {
ep = &(*ep)->chain;
}
*ep = entry.chain;
ep = &ht->hashTable[newHash];
while (*ep && *ep > &entry) {
ep = &(*ep)->chain;
}
entry.chain = *ep;
*ep = &entry;
}
}
static size_t offsetOfHashTable() { return offsetof(Range, ht); }
static size_t offsetOfI() { return offsetof(Range, i); }
static size_t offsetOfCount() { return offsetof(Range, count); }
static size_t offsetOfPrevP() { return offsetof(Range, prevp); }
static size_t offsetOfNext() { return offsetof(Range, next); }
static void onTableDestroyed(Range* range, uint32_t arg) {
range->onTableDestroyed();
}
static void onRemove(Range* range, uint32_t arg) { range->onRemove(arg); }
static void onClear(Range* range, uint32_t arg) { range->onClear(); }
static void onCompact(Range* range, uint32_t arg) { range->onCompact(); }
};
Range all() { return Range(this, &ranges); }
Range* createRange(void* buffer, bool inNursery) {
auto range = static_cast<Range*>(buffer);
new (range) Range(this, inNursery ? &nurseryRanges : &ranges);
return range;
}
void destroyNurseryRanges() { nurseryRanges = nullptr; }
void rekeyOneEntry(const Key& current, const Key& newKey, const T& element) {
if (current == newKey) {
return;
}
Data* entry = lookup(current, prepareHash(current));
if (!entry) {
return;
}
HashNumber oldHash = prepareHash(current) >> hashShift;
HashNumber newHash = prepareHash(newKey) >> hashShift;
entry->element = element;
Data** ep = &hashTable[oldHash];
while (*ep != entry) {
ep = &(*ep)->chain;
}
*ep = entry->chain;
ep = &hashTable[newHash];
while (*ep && *ep > entry) {
ep = &(*ep)->chain;
}
entry->chain = *ep;
*ep = entry;
}
static size_t offsetOfDataLength() {
return offsetof(OrderedHashTable, dataLength);
}
static size_t offsetOfData() { return offsetof(OrderedHashTable, data); }
static constexpr size_t offsetOfDataElement() {
static_assert(offsetof(Data, element) == 0,
"RangeFront and RangePopFront depend on offsetof(Data, "
"element) being 0");
return offsetof(Data, element);
}
static constexpr size_t sizeofData() { return sizeof(Data); }
private:
static uint32_t initialBucketsLog2() { return 1; }
static uint32_t initialBuckets() { return 1 << initialBucketsLog2(); }
static double fillFactor() { return 8.0 / 3.0; }
static double minDataFill() { return 0.25; }
public:
HashNumber prepareHash(const Lookup& l) const {
return mozilla::ScrambleHashCode(Ops::hash(l, hcs));
}
private:
uint32_t hashBuckets() const {
return 1 << (js::kHashNumberBits - hashShift);
}
static void destroyData(Data* data, uint32_t length) {
for (Data* p = data + length; p != data;) {
(--p)->~Data();
}
}
void freeData(Data* data, uint32_t length, uint32_t capacity) {
destroyData(data, length);
alloc.free_(data, capacity);
}
Data* lookup(const Lookup& l, HashNumber h) {
for (Data* e = hashTable[h >> hashShift]; e; e = e->chain) {
if (Ops::match(Ops::getKey(e->element), l)) {
return e;
}
}
return nullptr;
}
const Data* lookup(const Lookup& l) const {
return const_cast<OrderedHashTable*>(this)->lookup(l, prepareHash(l));
}
void compacted() {
forEachRange<&Range::onCompact>();
}
void rehashInPlace() {
for (uint32_t i = 0, N = hashBuckets(); i < N; i++) {
hashTable[i] = nullptr;
}
Data* wp = data;
Data* end = data + dataLength;
for (Data* rp = data; rp != end; rp++) {
if (!Ops::isEmpty(Ops::getKey(rp->element))) {
HashNumber h = prepareHash(Ops::getKey(rp->element)) >> hashShift;
if (rp != wp) {
wp->element = std::move(rp->element);
}
wp->chain = hashTable[h];
hashTable[h] = wp;
wp++;
}
}
MOZ_ASSERT(wp == data + liveCount);
while (wp != end) {
(--end)->~Data();
}
dataLength = liveCount;
compacted();
}
MOZ_MUST_USE bool rehash(uint32_t newHashShift) {
if (newHashShift == hashShift) {
rehashInPlace();
return true;
}
size_t newHashBuckets = size_t(1) << (js::kHashNumberBits - newHashShift);
Data** newHashTable = alloc.template pod_malloc<Data*>(newHashBuckets);
if (!newHashTable) {
return false;
}
for (uint32_t i = 0; i < newHashBuckets; i++) {
newHashTable[i] = nullptr;
}
uint32_t newCapacity = uint32_t(newHashBuckets * fillFactor());
Data* newData = alloc.template pod_malloc<Data>(newCapacity);
if (!newData) {
alloc.free_(newHashTable, newHashBuckets);
return false;
}
Data* wp = newData;
Data* end = data + dataLength;
for (Data* p = data; p != end; p++) {
if (!Ops::isEmpty(Ops::getKey(p->element))) {
HashNumber h = prepareHash(Ops::getKey(p->element)) >> newHashShift;
new (wp) Data(std::move(p->element), newHashTable[h]);
newHashTable[h] = wp;
wp++;
}
}
MOZ_ASSERT(wp == newData + liveCount);
alloc.free_(hashTable, hashBuckets());
freeData(data, dataLength, dataCapacity);
hashTable = newHashTable;
data = newData;
dataLength = liveCount;
dataCapacity = newCapacity;
hashShift = newHashShift;
MOZ_ASSERT(hashBuckets() == newHashBuckets);
compacted();
return true;
}
OrderedHashTable& operator=(const OrderedHashTable&) = delete;
OrderedHashTable(const OrderedHashTable&) = delete;
};
}
template <class Key, class Value, class OrderedHashPolicy, class AllocPolicy>
class OrderedHashMap {
public:
class Entry {
template <class, class, class>
friend class detail::OrderedHashTable;
void operator=(const Entry& rhs) {
const_cast<Key&>(key) = rhs.key;
value = rhs.value;
}
void operator=(Entry&& rhs) {
MOZ_ASSERT(this != &rhs, "self-move assignment is prohibited");
const_cast<Key&>(key) = std::move(rhs.key);
value = std::move(rhs.value);
}
public:
Entry() : key(), value() {}
template <typename V>
Entry(const Key& k, V&& v) : key(k), value(std::forward<V>(v)) {}
Entry(Entry&& rhs) : key(std::move(rhs.key)), value(std::move(rhs.value)) {}
const Key key;
Value value;
static size_t offsetOfKey() { return offsetof(Entry, key); }
static size_t offsetOfValue() { return offsetof(Entry, value); }
};
private:
struct MapOps : OrderedHashPolicy {
typedef Key KeyType;
static void makeEmpty(Entry* e) {
OrderedHashPolicy::makeEmpty(const_cast<Key*>(&e->key));
e->value = Value();
}
static const Key& getKey(const Entry& e) { return e.key; }
static void setKey(Entry& e, const Key& k) { const_cast<Key&>(e.key) = k; }
};
typedef detail::OrderedHashTable<Entry, MapOps, AllocPolicy> Impl;
Impl impl;
public:
typedef typename Impl::Range Range;
OrderedHashMap(AllocPolicy ap, mozilla::HashCodeScrambler hcs)
: impl(ap, hcs) {}
MOZ_MUST_USE bool init() { return impl.init(); }
uint32_t count() const { return impl.count(); }
bool has(const Key& key) const { return impl.has(key); }
Range all() { return impl.all(); }
const Entry* get(const Key& key) const { return impl.get(key); }
Entry* get(const Key& key) { return impl.get(key); }
bool remove(const Key& key, bool* foundp) { return impl.remove(key, foundp); }
MOZ_MUST_USE bool clear() { return impl.clear(); }
template <typename V>
MOZ_MUST_USE bool put(const Key& key, V&& value) {
return impl.put(Entry(key, std::forward<V>(value)));
}
HashNumber hash(const Key& key) const { return impl.prepareHash(key); }
void rekeyOneEntry(const Key& current, const Key& newKey) {
const Entry* e = get(current);
if (!e) {
return;
}
return impl.rekeyOneEntry(current, newKey, Entry(newKey, e->value));
}
Range* createRange(void* buffer, bool inNursery) {
return impl.createRange(buffer, inNursery);
}
void destroyNurseryRanges() { impl.destroyNurseryRanges(); }
static size_t offsetOfEntryKey() { return Entry::offsetOfKey(); }
static size_t offsetOfImplDataLength() { return Impl::offsetOfDataLength(); }
static size_t offsetOfImplData() { return Impl::offsetOfData(); }
static constexpr size_t offsetOfImplDataElement() {
return Impl::offsetOfDataElement();
}
static constexpr size_t sizeofImplData() { return Impl::sizeofData(); }
};
template <class T, class OrderedHashPolicy, class AllocPolicy>
class OrderedHashSet {
private:
struct SetOps : OrderedHashPolicy {
typedef const T KeyType;
static const T& getKey(const T& v) { return v; }
static void setKey(const T& e, const T& v) { const_cast<T&>(e) = v; }
};
typedef detail::OrderedHashTable<T, SetOps, AllocPolicy> Impl;
Impl impl;
public:
typedef typename Impl::Range Range;
explicit OrderedHashSet(AllocPolicy ap, mozilla::HashCodeScrambler hcs)
: impl(ap, hcs) {}
MOZ_MUST_USE bool init() { return impl.init(); }
uint32_t count() const { return impl.count(); }
bool has(const T& value) const { return impl.has(value); }
Range all() { return impl.all(); }
MOZ_MUST_USE bool put(const T& value) { return impl.put(value); }
bool remove(const T& value, bool* foundp) {
return impl.remove(value, foundp);
}
MOZ_MUST_USE bool clear() { return impl.clear(); }
HashNumber hash(const T& value) const { return impl.prepareHash(value); }
void rekeyOneEntry(const T& current, const T& newKey) {
return impl.rekeyOneEntry(current, newKey, newKey);
}
Range* createRange(void* buffer, bool inNursery) {
return impl.createRange(buffer, inNursery);
}
void destroyNurseryRanges() { impl.destroyNurseryRanges(); }
static size_t offsetOfEntryKey() { return 0; }
static size_t offsetOfImplDataLength() { return Impl::offsetOfDataLength(); }
static size_t offsetOfImplData() { return Impl::offsetOfData(); }
static constexpr size_t offsetOfImplDataElement() {
return Impl::offsetOfDataElement();
}
static constexpr size_t sizeofImplData() { return Impl::sizeofData(); }
};
}
#endif