blob: 9ce6074031e34bb79d4de36c849f06f381f949e3 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef ds_InlineMap_h
#define ds_InlineMap_h
#include "js/HashTable.h"
namespace js {
/*
* A type can only be used as an InlineMap key if zero is an invalid key value
* (and thus may be used as a tombstone value by InlineMap).
*/
template <typename T> struct ZeroIsReserved { static const bool result = false; };
template <typename T> struct ZeroIsReserved<T *> { static const bool result = true; };
template <typename K, typename V, size_t InlineElems>
class InlineMap
{
public:
typedef HashMap<K, V, DefaultHasher<K>, SystemAllocPolicy> WordMap;
struct InlineElem
{
K key;
V value;
};
private:
typedef typename WordMap::Ptr WordMapPtr;
typedef typename WordMap::AddPtr WordMapAddPtr;
typedef typename WordMap::Range WordMapRange;
size_t inlNext;
size_t inlCount;
InlineElem inl[InlineElems];
WordMap map;
void checkStaticInvariants() {
JS_STATIC_ASSERT(ZeroIsReserved<K>::result);
}
bool usingMap() const {
return inlNext > InlineElems;
}
bool switchToMap() {
JS_ASSERT(inlNext == InlineElems);
if (map.initialized()) {
map.clear();
} else {
if (!map.init(count()))
return false;
JS_ASSERT(map.initialized());
}
for (InlineElem *it = inl, *end = inl + inlNext; it != end; ++it) {
if (it->key && !map.putNew(it->key, it->value))
return false;
}
inlNext = InlineElems + 1;
JS_ASSERT(map.count() == inlCount);
JS_ASSERT(usingMap());
return true;
}
JS_NEVER_INLINE
bool switchAndAdd(const K &key, const V &value) {
if (!switchToMap())
return false;
return map.putNew(key, value);
}
public:
explicit InlineMap()
: inlNext(0), inlCount(0) {
checkStaticInvariants(); /* Force the template to instantiate the static invariants. */
}
class Entry
{
friend class InlineMap;
const K &key_;
V &value_;
Entry(const K &key, V &value) : key_(key), value_(value) {}
public:
const K &key() { return key_; }
V &value() { return value_; }
}; /* class Entry */
class Ptr
{
friend class InlineMap;
WordMapPtr mapPtr;
InlineElem *inlPtr;
bool isInlinePtr;
typedef Ptr ******* ConvertibleToBool;
explicit Ptr(WordMapPtr p) : mapPtr(p), isInlinePtr(false) {}
explicit Ptr(InlineElem *ie) : inlPtr(ie), isInlinePtr(true) {}
void operator==(const Ptr &other);
public:
/* Leaves Ptr uninitialized. */
Ptr() {
#ifdef DEBUG
inlPtr = (InlineElem *) 0xbad;
isInlinePtr = true;
#endif
}
/* Default copy constructor works for this structure. */
bool found() const {
return isInlinePtr ? bool(inlPtr) : mapPtr.found();
}
operator ConvertibleToBool() const {
return ConvertibleToBool(found());
}
K &key() {
JS_ASSERT(found());
return isInlinePtr ? inlPtr->key : mapPtr->key;
}
V &value() {
JS_ASSERT(found());
return isInlinePtr ? inlPtr->value : mapPtr->value;
}
}; /* class Ptr */
class AddPtr
{
friend class InlineMap;
WordMapAddPtr mapAddPtr;
InlineElem *inlAddPtr;
bool isInlinePtr;
/* Indicates whether inlAddPtr is a found result or an add pointer. */
bool inlPtrFound;
AddPtr(InlineElem *ptr, bool found)
: inlAddPtr(ptr), isInlinePtr(true), inlPtrFound(found)
{}
AddPtr(const WordMapAddPtr &p) : mapAddPtr(p), isInlinePtr(false) {}
void operator==(const AddPtr &other);
typedef AddPtr ******* ConvertibleToBool;
public:
AddPtr() {}
bool found() const {
return isInlinePtr ? inlPtrFound : mapAddPtr.found();
}
operator ConvertibleToBool() const {
return found() ? ConvertibleToBool(1) : ConvertibleToBool(0);
}
V &value() {
JS_ASSERT(found());
if (isInlinePtr)
return inlAddPtr->value;
return mapAddPtr->value;
}
}; /* class AddPtr */
size_t count() {
return usingMap() ? map.count() : inlCount;
}
bool empty() const {
return usingMap() ? map.empty() : !inlCount;
}
void clear() {
inlNext = 0;
inlCount = 0;
}
bool isMap() const {
return usingMap();
}
const WordMap &asMap() const {
JS_ASSERT(isMap());
return map;
}
const InlineElem *asInline() const {
JS_ASSERT(!isMap());
return inl;
}
const InlineElem *inlineEnd() const {
JS_ASSERT(!isMap());
return inl + inlNext;
}
JS_ALWAYS_INLINE
Ptr lookup(const K &key) {
if (usingMap())
return Ptr(map.lookup(key));
for (InlineElem *it = inl, *end = inl + inlNext; it != end; ++it) {
if (it->key == key)
return Ptr(it);
}
return Ptr(NULL);
}
JS_ALWAYS_INLINE
AddPtr lookupForAdd(const K &key) {
if (usingMap())
return AddPtr(map.lookupForAdd(key));
for (InlineElem *it = inl, *end = inl + inlNext; it != end; ++it) {
if (it->key == key)
return AddPtr(it, true);
}
/*
* The add pointer that's returned here may indicate the limit entry of
* the linear space, in which case the |add| operation will initialize
* the map if necessary and add the entry there.
*/
return AddPtr(inl + inlNext, false);
}
JS_ALWAYS_INLINE
bool add(AddPtr &p, const K &key, const V &value) {
JS_ASSERT(!p);
if (p.isInlinePtr) {
InlineElem *addPtr = p.inlAddPtr;
JS_ASSERT(addPtr == inl + inlNext);
/* Switching to map mode before we add this pointer. */
if (addPtr == inl + InlineElems)
return switchAndAdd(key, value);
JS_ASSERT(!p.found());
JS_ASSERT(uintptr_t(inl + inlNext) == uintptr_t(p.inlAddPtr));
p.inlAddPtr->key = key;
p.inlAddPtr->value = value;
++inlCount;
++inlNext;
return true;
}
return map.add(p.mapAddPtr, key, value);
}
JS_ALWAYS_INLINE
bool put(const K &key, const V &value) {
AddPtr p = lookupForAdd(key);
if (p) {
p.value() = value;
return true;
}
return add(p, key, value);
}
void remove(Ptr p) {
JS_ASSERT(p);
if (p.isInlinePtr) {
JS_ASSERT(inlCount > 0);
JS_ASSERT(p.inlPtr->key != NULL);
p.inlPtr->key = NULL;
--inlCount;
return;
}
JS_ASSERT(map.initialized() && usingMap());
map.remove(p.mapPtr);
}
void remove(const K &key) {
if (Ptr p = lookup(key))
remove(p);
}
class Range
{
friend class InlineMap;
WordMapRange mapRange;
InlineElem *cur;
InlineElem *end;
bool isInline;
explicit Range(WordMapRange r)
: cur(NULL), end(NULL), /* Avoid GCC 4.3.3 over-warning. */
isInline(false) {
mapRange = r;
JS_ASSERT(!isInlineRange());
}
Range(const InlineElem *begin, const InlineElem *end_)
: cur(const_cast<InlineElem *>(begin)),
end(const_cast<InlineElem *>(end_)),
isInline(true) {
advancePastNulls(cur);
JS_ASSERT(isInlineRange());
}
bool checkInlineRangeInvariants() const {
JS_ASSERT(uintptr_t(cur) <= uintptr_t(end));
JS_ASSERT_IF(cur != end, cur->key != NULL);
return true;
}
bool isInlineRange() const {
JS_ASSERT_IF(isInline, checkInlineRangeInvariants());
return isInline;
}
void advancePastNulls(InlineElem *begin) {
InlineElem *newCur = begin;
while (newCur < end && NULL == newCur->key)
++newCur;
JS_ASSERT(uintptr_t(newCur) <= uintptr_t(end));
cur = newCur;
}
void bumpCurPtr() {
JS_ASSERT(isInlineRange());
advancePastNulls(cur + 1);
}
void operator==(const Range &other);
public:
bool empty() const {
return isInlineRange() ? cur == end : mapRange.empty();
}
Entry front() {
JS_ASSERT(!empty());
if (isInlineRange())
return Entry(cur->key, cur->value);
return Entry(mapRange.front().key, mapRange.front().value);
}
void popFront() {
JS_ASSERT(!empty());
if (isInlineRange())
bumpCurPtr();
else
mapRange.popFront();
}
}; /* class Range */
Range all() const {
return usingMap() ? Range(map.all()) : Range(inl, inl + inlNext);
}
}; /* class InlineMap */
} /* namespace js */
#endif /* ds_InlineMap_h */