js/src/builtin/MapObject.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
michael@0 2 * vim: set ts=8 sts=4 et sw=4 tw=99:
michael@0 3 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "builtin/MapObject.h"
michael@0 8
michael@0 9 #include "mozilla/Move.h"
michael@0 10
michael@0 11 #include "jscntxt.h"
michael@0 12 #include "jsiter.h"
michael@0 13 #include "jsobj.h"
michael@0 14
michael@0 15 #include "gc/Marking.h"
michael@0 16 #include "js/Utility.h"
michael@0 17 #include "vm/GlobalObject.h"
michael@0 18 #include "vm/Interpreter.h"
michael@0 19
michael@0 20 #include "jsobjinlines.h"
michael@0 21
michael@0 22 using namespace js;
michael@0 23
michael@0 24 using mozilla::NumberEqualsInt32;
michael@0 25 using mozilla::Forward;
michael@0 26 using mozilla::IsNaN;
michael@0 27 using mozilla::Move;
michael@0 28 using mozilla::ArrayLength;
michael@0 29 using JS::DoubleNaNValue;
michael@0 30 using JS::ForOfIterator;
michael@0 31
michael@0 32
michael@0 33 /*** OrderedHashTable ****************************************************************************/
michael@0 34
michael@0 35 /*
michael@0 36 * Define two collection templates, js::OrderedHashMap and js::OrderedHashSet.
michael@0 37 * They are like js::HashMap and js::HashSet except that:
michael@0 38 *
michael@0 39 * - Iterating over an Ordered hash table visits the entries in the order in
michael@0 40 * which they were inserted. This means that unlike a HashMap, the behavior
michael@0 41 * of an OrderedHashMap is deterministic (as long as the HashPolicy methods
michael@0 42 * are effect-free and consistent); the hashing is a pure performance
michael@0 43 * optimization.
michael@0 44 *
michael@0 45 * - Range objects over Ordered tables remain valid even when entries are
michael@0 46 * added or removed or the table is resized. (However in the case of
michael@0 47 * removing entries, note the warning on class Range below.)
michael@0 48 *
michael@0 49 * - The API is a little different, so it's not a drop-in replacement.
michael@0 50 * In particular, the hash policy is a little different.
michael@0 51 * Also, the Ordered templates lack the Ptr and AddPtr types.
michael@0 52 *
michael@0 53 * Hash policies
michael@0 54 *
michael@0 55 * See the comment about "Hash policy" in HashTable.h for general features that
michael@0 56 * hash policy classes must provide. Hash policies for OrderedHashMaps and Sets
michael@0 57 * must additionally provide a distinguished "empty" key value and the
michael@0 58 * following static member functions:
michael@0 59 * bool isEmpty(const Key &);
michael@0 60 * void makeEmpty(Key *);
michael@0 61 */
michael@0 62
michael@0 63 namespace js {
michael@0 64
michael@0 65 namespace detail {
michael@0 66
michael@0 67 /*
michael@0 68 * detail::OrderedHashTable is the underlying data structure used to implement both
michael@0 69 * OrderedHashMap and OrderedHashSet. Programs should use one of those two
michael@0 70 * templates rather than OrderedHashTable.
michael@0 71 */
michael@0 72 template <class T, class Ops, class AllocPolicy>
michael@0 73 class OrderedHashTable
michael@0 74 {
michael@0 75 public:
michael@0 76 typedef typename Ops::KeyType Key;
michael@0 77 typedef typename Ops::Lookup Lookup;
michael@0 78
michael@0 79 struct Data
michael@0 80 {
michael@0 81 T element;
michael@0 82 Data *chain;
michael@0 83
michael@0 84 Data(const T &e, Data *c) : element(e), chain(c) {}
michael@0 85 Data(T &&e, Data *c) : element(Move(e)), chain(c) {}
michael@0 86 };
michael@0 87
michael@0 88 class Range;
michael@0 89 friend class Range;
michael@0 90
michael@0 91 private:
michael@0 92 Data **hashTable; // hash table (has hashBuckets() elements)
michael@0 93 Data *data; // data vector, an array of Data objects
michael@0 94 // data[0:dataLength] are constructed
michael@0 95 uint32_t dataLength; // number of constructed elements in data
michael@0 96 uint32_t dataCapacity; // size of data, in elements
michael@0 97 uint32_t liveCount; // dataLength less empty (removed) entries
michael@0 98 uint32_t hashShift; // multiplicative hash shift
michael@0 99 Range *ranges; // list of all live Ranges on this table
michael@0 100 AllocPolicy alloc;
michael@0 101
michael@0 102 public:
michael@0 103 OrderedHashTable(AllocPolicy &ap)
michael@0 104 : hashTable(nullptr), data(nullptr), dataLength(0), ranges(nullptr), alloc(ap) {}
michael@0 105
michael@0 106 bool init() {
michael@0 107 MOZ_ASSERT(!hashTable, "init must be called at most once");
michael@0 108
michael@0 109 uint32_t buckets = initialBuckets();
michael@0 110 Data **tableAlloc = static_cast<Data **>(alloc.malloc_(buckets * sizeof(Data *)));
michael@0 111 if (!tableAlloc)
michael@0 112 return false;
michael@0 113 for (uint32_t i = 0; i < buckets; i++)
michael@0 114 tableAlloc[i] = nullptr;
michael@0 115
michael@0 116 uint32_t capacity = uint32_t(buckets * fillFactor());
michael@0 117 Data *dataAlloc = static_cast<Data *>(alloc.malloc_(capacity * sizeof(Data)));
michael@0 118 if (!dataAlloc) {
michael@0 119 alloc.free_(tableAlloc);
michael@0 120 return false;
michael@0 121 }
michael@0 122
michael@0 123 // clear() requires that members are assigned only after all allocation
michael@0 124 // has succeeded, and that this->ranges is left untouched.
michael@0 125 hashTable = tableAlloc;
michael@0 126 data = dataAlloc;
michael@0 127 dataLength = 0;
michael@0 128 dataCapacity = capacity;
michael@0 129 liveCount = 0;
michael@0 130 hashShift = HashNumberSizeBits - initialBucketsLog2();
michael@0 131 MOZ_ASSERT(hashBuckets() == buckets);
michael@0 132 return true;
michael@0 133 }
michael@0 134
michael@0 135 ~OrderedHashTable() {
michael@0 136 for (Range *r = ranges, *next; r; r = next) {
michael@0 137 next = r->next;
michael@0 138 r->onTableDestroyed();
michael@0 139 }
michael@0 140 alloc.free_(hashTable);
michael@0 141 freeData(data, dataLength);
michael@0 142 }
michael@0 143
michael@0 144 /* Return the number of elements in the table. */
michael@0 145 uint32_t count() const { return liveCount; }
michael@0 146
michael@0 147 /* True if any element matches l. */
michael@0 148 bool has(const Lookup &l) const {
michael@0 149 return lookup(l) != nullptr;
michael@0 150 }
michael@0 151
michael@0 152 /* Return a pointer to the element, if any, that matches l, or nullptr. */
michael@0 153 T *get(const Lookup &l) {
michael@0 154 Data *e = lookup(l, prepareHash(l));
michael@0 155 return e ? &e->element : nullptr;
michael@0 156 }
michael@0 157
michael@0 158 /* Return a pointer to the element, if any, that matches l, or nullptr. */
michael@0 159 const T *get(const Lookup &l) const {
michael@0 160 return const_cast<OrderedHashTable *>(this)->get(l);
michael@0 161 }
michael@0 162
michael@0 163 /*
michael@0 164 * If the table already contains an entry that matches |element|,
michael@0 165 * replace that entry with |element|. Otherwise add a new entry.
michael@0 166 *
michael@0 167 * On success, return true, whether there was already a matching element or
michael@0 168 * not. On allocation failure, return false. If this returns false, it
michael@0 169 * means the element was not added to the table.
michael@0 170 */
michael@0 171 template <typename ElementInput>
michael@0 172 bool put(ElementInput &&element) {
michael@0 173 HashNumber h = prepareHash(Ops::getKey(element));
michael@0 174 if (Data *e = lookup(Ops::getKey(element), h)) {
michael@0 175 e->element = Forward<ElementInput>(element);
michael@0 176 return true;
michael@0 177 }
michael@0 178
michael@0 179 if (dataLength == dataCapacity) {
michael@0 180 // If the hashTable is more than 1/4 deleted data, simply rehash in
michael@0 181 // place to free up some space. Otherwise, grow the table.
michael@0 182 uint32_t newHashShift = liveCount >= dataCapacity * 0.75 ? hashShift - 1 : hashShift;
michael@0 183 if (!rehash(newHashShift))
michael@0 184 return false;
michael@0 185 }
michael@0 186
michael@0 187 h >>= hashShift;
michael@0 188 liveCount++;
michael@0 189 Data *e = &data[dataLength++];
michael@0 190 new (e) Data(Forward<ElementInput>(element), hashTable[h]);
michael@0 191 hashTable[h] = e;
michael@0 192 return true;
michael@0 193 }
michael@0 194
michael@0 195 /*
michael@0 196 * If the table contains an element matching l, remove it and set *foundp
michael@0 197 * to true. Otherwise set *foundp to false.
michael@0 198 *
michael@0 199 * Return true on success, false if we tried to shrink the table and hit an
michael@0 200 * allocation failure. Even if this returns false, *foundp is set correctly
michael@0 201 * and the matching element was removed. Shrinking is an optimization and
michael@0 202 * it's OK for it to fail.
michael@0 203 */
michael@0 204 bool remove(const Lookup &l, bool *foundp) {
michael@0 205 // Note: This could be optimized so that removing the last entry,
michael@0 206 // data[dataLength - 1], decrements dataLength. LIFO use cases would
michael@0 207 // benefit.
michael@0 208
michael@0 209 // If a matching entry exists, empty it.
michael@0 210 Data *e = lookup(l, prepareHash(l));
michael@0 211 if (e == nullptr) {
michael@0 212 *foundp = false;
michael@0 213 return true;
michael@0 214 }
michael@0 215
michael@0 216 *foundp = true;
michael@0 217 liveCount--;
michael@0 218 Ops::makeEmpty(&e->element);
michael@0 219
michael@0 220 // Update active Ranges.
michael@0 221 uint32_t pos = e - data;
michael@0 222 for (Range *r = ranges; r; r = r->next)
michael@0 223 r->onRemove(pos);
michael@0 224
michael@0 225 // If many entries have been removed, try to shrink the table.
michael@0 226 if (hashBuckets() > initialBuckets() && liveCount < dataLength * minDataFill()) {
michael@0 227 if (!rehash(hashShift + 1))
michael@0 228 return false;
michael@0 229 }
michael@0 230 return true;
michael@0 231 }
michael@0 232
michael@0 233 /*
michael@0 234 * Remove all entries.
michael@0 235 *
michael@0 236 * Returns false on OOM, leaving the OrderedHashTable and any live Ranges
michael@0 237 * in the old state.
michael@0 238 *
michael@0 239 * The effect on live Ranges is the same as removing all entries; in
michael@0 240 * particular, those Ranges are still live and will see any entries added
michael@0 241 * after a successful clear().
michael@0 242 */
michael@0 243 bool clear() {
michael@0 244 if (dataLength != 0) {
michael@0 245 Data **oldHashTable = hashTable;
michael@0 246 Data *oldData = data;
michael@0 247 uint32_t oldDataLength = dataLength;
michael@0 248
michael@0 249 hashTable = nullptr;
michael@0 250 if (!init()) {
michael@0 251 // init() only mutates members on success; see comment above.
michael@0 252 hashTable = oldHashTable;
michael@0 253 return false;
michael@0 254 }
michael@0 255
michael@0 256 alloc.free_(oldHashTable);
michael@0 257 freeData(oldData, oldDataLength);
michael@0 258 for (Range *r = ranges; r; r = r->next)
michael@0 259 r->onClear();
michael@0 260 }
michael@0 261
michael@0 262 MOZ_ASSERT(hashTable);
michael@0 263 MOZ_ASSERT(data);
michael@0 264 MOZ_ASSERT(dataLength == 0);
michael@0 265 MOZ_ASSERT(liveCount == 0);
michael@0 266 return true;
michael@0 267 }
michael@0 268
michael@0 269 /*
michael@0 270 * Ranges are used to iterate over OrderedHashTables.
michael@0 271 *
michael@0 272 * Suppose 'Map' is some instance of OrderedHashMap, and 'map' is a Map.
michael@0 273 * Then you can walk all the key-value pairs like this:
michael@0 274 *
michael@0 275 * for (Map::Range r = map.all(); !r.empty(); r.popFront()) {
michael@0 276 * Map::Entry &pair = r.front();
michael@0 277 * ... do something with pair ...
michael@0 278 * }
michael@0 279 *
michael@0 280 * Ranges remain valid for the lifetime of the OrderedHashTable, even if
michael@0 281 * entries are added or removed or the table is resized. Don't do anything
michael@0 282 * to a Range, except destroy it, after the OrderedHashTable has been
michael@0 283 * destroyed. (We support destroying the two objects in either order to
michael@0 284 * humor the GC, bless its nondeterministic heart.)
michael@0 285 *
michael@0 286 * Warning: The behavior when the current front() entry is removed from the
michael@0 287 * table is subtly different from js::HashTable<>::Enum::removeFront()!
michael@0 288 * HashTable::Enum doesn't skip any entries when you removeFront() and then
michael@0 289 * popFront(). OrderedHashTable::Range does! (This is useful for using a
michael@0 290 * Range to implement JS Map.prototype.iterator.)
michael@0 291 *
michael@0 292 * The workaround is to call popFront() as soon as possible,
michael@0 293 * before there's any possibility of modifying the table:
michael@0 294 *
michael@0 295 * for (Map::Range r = map.all(); !r.empty(); ) {
michael@0 296 * Key key = r.front().key; // this won't modify map
michael@0 297 * Value val = r.front().value; // this won't modify map
michael@0 298 * r.popFront();
michael@0 299 * // ...do things that might modify map...
michael@0 300 * }
michael@0 301 */
michael@0 302 class Range
michael@0 303 {
michael@0 304 friend class OrderedHashTable;
michael@0 305
michael@0 306 OrderedHashTable &ht;
michael@0 307
michael@0 308 /* The index of front() within ht.data. */
michael@0 309 uint32_t i;
michael@0 310
michael@0 311 /*
michael@0 312 * The number of nonempty entries in ht.data to the left of front().
michael@0 313 * This is used when the table is resized or compacted.
michael@0 314 */
michael@0 315 uint32_t count;
michael@0 316
michael@0 317 /*
michael@0 318 * Links in the doubly-linked list of active Ranges on ht.
michael@0 319 *
michael@0 320 * prevp points to the previous Range's .next field;
michael@0 321 * or to ht.ranges if this is the first Range in the list.
michael@0 322 * next points to the next Range;
michael@0 323 * or nullptr if this is the last Range in the list.
michael@0 324 *
michael@0 325 * Invariant: *prevp == this.
michael@0 326 */
michael@0 327 Range **prevp;
michael@0 328 Range *next;
michael@0 329
michael@0 330 /*
michael@0 331 * Create a Range over all the entries in ht.
michael@0 332 * (This is private on purpose. End users must use ht.all().)
michael@0 333 */
michael@0 334 Range(OrderedHashTable &ht) : ht(ht), i(0), count(0), prevp(&ht.ranges), next(ht.ranges) {
michael@0 335 *prevp = this;
michael@0 336 if (next)
michael@0 337 next->prevp = &next;
michael@0 338 seek();
michael@0 339 }
michael@0 340
michael@0 341 public:
michael@0 342 Range(const Range &other)
michael@0 343 : ht(other.ht), i(other.i), count(other.count), prevp(&ht.ranges), next(ht.ranges)
michael@0 344 {
michael@0 345 *prevp = this;
michael@0 346 if (next)
michael@0 347 next->prevp = &next;
michael@0 348 }
michael@0 349
michael@0 350 ~Range() {
michael@0 351 *prevp = next;
michael@0 352 if (next)
michael@0 353 next->prevp = prevp;
michael@0 354 }
michael@0 355
michael@0 356 private:
michael@0 357 // Prohibit copy assignment.
michael@0 358 Range &operator=(const Range &other) MOZ_DELETE;
michael@0 359
michael@0 360 void seek() {
michael@0 361 while (i < ht.dataLength && Ops::isEmpty(Ops::getKey(ht.data[i].element)))
michael@0 362 i++;
michael@0 363 }
michael@0 364
michael@0 365 /*
michael@0 366 * The hash table calls this when an entry is removed.
michael@0 367 * j is the index of the removed entry.
michael@0 368 */
michael@0 369 void onRemove(uint32_t j) {
michael@0 370 MOZ_ASSERT(valid());
michael@0 371 if (j < i)
michael@0 372 count--;
michael@0 373 if (j == i)
michael@0 374 seek();
michael@0 375 }
michael@0 376
michael@0 377 /*
michael@0 378 * The hash table calls this when the table is resized or compacted.
michael@0 379 * Since |count| is the number of nonempty entries to the left of
michael@0 380 * front(), discarding the empty entries will not affect count, and it
michael@0 381 * will make i and count equal.
michael@0 382 */
michael@0 383 void onCompact() {
michael@0 384 MOZ_ASSERT(valid());
michael@0 385 i = count;
michael@0 386 }
michael@0 387
michael@0 388 /* The hash table calls this when cleared. */
michael@0 389 void onClear() {
michael@0 390 MOZ_ASSERT(valid());
michael@0 391 i = count = 0;
michael@0 392 }
michael@0 393
michael@0 394 bool valid() const {
michael@0 395 return next != this;
michael@0 396 }
michael@0 397
michael@0 398 void onTableDestroyed() {
michael@0 399 MOZ_ASSERT(valid());
michael@0 400 prevp = &next;
michael@0 401 next = this;
michael@0 402 }
michael@0 403
michael@0 404 public:
michael@0 405 bool empty() const {
michael@0 406 MOZ_ASSERT(valid());
michael@0 407 return i >= ht.dataLength;
michael@0 408 }
michael@0 409
michael@0 410 /*
michael@0 411 * Return the first element in the range. This must not be called if
michael@0 412 * this->empty().
michael@0 413 *
michael@0 414 * Warning: Removing an entry from the table also removes it from any
michael@0 415 * live Ranges, and a Range can become empty that way, rendering
michael@0 416 * front() invalid. If in doubt, check empty() before calling front().
michael@0 417 */
michael@0 418 T &front() {
michael@0 419 MOZ_ASSERT(valid());
michael@0 420 MOZ_ASSERT(!empty());
michael@0 421 return ht.data[i].element;
michael@0 422 }
michael@0 423
michael@0 424 /*
michael@0 425 * Remove the first element from this range.
michael@0 426 * This must not be called if this->empty().
michael@0 427 *
michael@0 428 * Warning: Removing an entry from the table also removes it from any
michael@0 429 * live Ranges, and a Range can become empty that way, rendering
michael@0 430 * popFront() invalid. If in doubt, check empty() before calling
michael@0 431 * popFront().
michael@0 432 */
michael@0 433 void popFront() {
michael@0 434 MOZ_ASSERT(valid());
michael@0 435 MOZ_ASSERT(!empty());
michael@0 436 MOZ_ASSERT(!Ops::isEmpty(Ops::getKey(ht.data[i].element)));
michael@0 437 count++;
michael@0 438 i++;
michael@0 439 seek();
michael@0 440 }
michael@0 441
michael@0 442 /*
michael@0 443 * Change the key of the front entry.
michael@0 444 *
michael@0 445 * This calls Ops::hash on both the current key and the new key.
michael@0 446 * Ops::hash on the current key must return the same hash code as
michael@0 447 * when the entry was added to the table.
michael@0 448 */
michael@0 449 void rekeyFront(const Key &k) {
michael@0 450 MOZ_ASSERT(valid());
michael@0 451 Data &entry = ht.data[i];
michael@0 452 HashNumber oldHash = prepareHash(Ops::getKey(entry.element)) >> ht.hashShift;
michael@0 453 HashNumber newHash = prepareHash(k) >> ht.hashShift;
michael@0 454 Ops::setKey(entry.element, k);
michael@0 455 if (newHash != oldHash) {
michael@0 456 // Remove this entry from its old hash chain. (If this crashes
michael@0 457 // reading nullptr, it would mean we did not find this entry on
michael@0 458 // the hash chain where we expected it. That probably means the
michael@0 459 // key's hash code changed since it was inserted, breaking the
michael@0 460 // hash code invariant.)
michael@0 461 Data **ep = &ht.hashTable[oldHash];
michael@0 462 while (*ep != &entry)
michael@0 463 ep = &(*ep)->chain;
michael@0 464 *ep = entry.chain;
michael@0 465
michael@0 466 // Add it to the new hash chain. We could just insert it at the
michael@0 467 // beginning of the chain. Instead, we do a bit of work to
michael@0 468 // preserve the invariant that hash chains always go in reverse
michael@0 469 // insertion order (descending memory order). No code currently
michael@0 470 // depends on this invariant, so it's fine to kill it if
michael@0 471 // needed.
michael@0 472 ep = &ht.hashTable[newHash];
michael@0 473 while (*ep && *ep > &entry)
michael@0 474 ep = &(*ep)->chain;
michael@0 475 entry.chain = *ep;
michael@0 476 *ep = &entry;
michael@0 477 }
michael@0 478 }
michael@0 479 };
michael@0 480
michael@0 481 Range all() { return Range(*this); }
michael@0 482
michael@0 483 /*
michael@0 484 * Change the value of the given key.
michael@0 485 *
michael@0 486 * This calls Ops::hash on both the current key and the new key.
michael@0 487 * Ops::hash on the current key must return the same hash code as
michael@0 488 * when the entry was added to the table.
michael@0 489 */
michael@0 490 void rekeyOneEntry(const Key &current, const Key &newKey, const T &element) {
michael@0 491 if (current == newKey)
michael@0 492 return;
michael@0 493
michael@0 494 Data *entry = lookup(current, prepareHash(current));
michael@0 495 if (!entry)
michael@0 496 return;
michael@0 497
michael@0 498 HashNumber oldHash = prepareHash(current) >> hashShift;
michael@0 499 HashNumber newHash = prepareHash(newKey) >> hashShift;
michael@0 500
michael@0 501 entry->element = element;
michael@0 502
michael@0 503 // Remove this entry from its old hash chain. (If this crashes
michael@0 504 // reading nullptr, it would mean we did not find this entry on
michael@0 505 // the hash chain where we expected it. That probably means the
michael@0 506 // key's hash code changed since it was inserted, breaking the
michael@0 507 // hash code invariant.)
michael@0 508 Data **ep = &hashTable[oldHash];
michael@0 509 while (*ep != entry)
michael@0 510 ep = &(*ep)->chain;
michael@0 511 *ep = entry->chain;
michael@0 512
michael@0 513 // Add it to the new hash chain. We could just insert it at the
michael@0 514 // beginning of the chain. Instead, we do a bit of work to
michael@0 515 // preserve the invariant that hash chains always go in reverse
michael@0 516 // insertion order (descending memory order). No code currently
michael@0 517 // depends on this invariant, so it's fine to kill it if
michael@0 518 // needed.
michael@0 519 ep = &hashTable[newHash];
michael@0 520 while (*ep && *ep > entry)
michael@0 521 ep = &(*ep)->chain;
michael@0 522 entry->chain = *ep;
michael@0 523 *ep = entry;
michael@0 524 }
michael@0 525
michael@0 526 private:
michael@0 527 /* Logarithm base 2 of the number of buckets in the hash table initially. */
michael@0 528 static uint32_t initialBucketsLog2() { return 1; }
michael@0 529 static uint32_t initialBuckets() { return 1 << initialBucketsLog2(); }
michael@0 530
michael@0 531 /*
michael@0 532 * The maximum load factor (mean number of entries per bucket).
michael@0 533 * It is an invariant that
michael@0 534 * dataCapacity == floor(hashBuckets() * fillFactor()).
michael@0 535 *
michael@0 536 * The fill factor should be between 2 and 4, and it should be chosen so that
michael@0 537 * the fill factor times sizeof(Data) is close to but <= a power of 2.
michael@0 538 * This fixed fill factor was chosen to make the size of the data
michael@0 539 * array, in bytes, close to a power of two when sizeof(T) is 16.
michael@0 540 */
michael@0 541 static double fillFactor() { return 8.0 / 3.0; }
michael@0 542
michael@0 543 /*
michael@0 544 * The minimum permitted value of (liveCount / dataLength).
michael@0 545 * If that ratio drops below this value, we shrink the table.
michael@0 546 */
michael@0 547 static double minDataFill() { return 0.25; }
michael@0 548
michael@0 549 static HashNumber prepareHash(const Lookup &l) {
michael@0 550 return ScrambleHashCode(Ops::hash(l));
michael@0 551 }
michael@0 552
michael@0 553 /* The size of hashTable, in elements. Always a power of two. */
michael@0 554 uint32_t hashBuckets() const {
michael@0 555 return 1 << (HashNumberSizeBits - hashShift);
michael@0 556 }
michael@0 557
michael@0 558 static void destroyData(Data *data, uint32_t length) {
michael@0 559 for (Data *p = data + length; p != data; )
michael@0 560 (--p)->~Data();
michael@0 561 }
michael@0 562
michael@0 563 void freeData(Data *data, uint32_t length) {
michael@0 564 destroyData(data, length);
michael@0 565 alloc.free_(data);
michael@0 566 }
michael@0 567
michael@0 568 Data *lookup(const Lookup &l, HashNumber h) {
michael@0 569 for (Data *e = hashTable[h >> hashShift]; e; e = e->chain) {
michael@0 570 if (Ops::match(Ops::getKey(e->element), l))
michael@0 571 return e;
michael@0 572 }
michael@0 573 return nullptr;
michael@0 574 }
michael@0 575
michael@0 576 const Data *lookup(const Lookup &l) const {
michael@0 577 return const_cast<OrderedHashTable *>(this)->lookup(l, prepareHash(l));
michael@0 578 }
michael@0 579
michael@0 580 /* This is called after rehashing the table. */
michael@0 581 void compacted() {
michael@0 582 // If we had any empty entries, compacting may have moved live entries
michael@0 583 // to the left within |data|. Notify all live Ranges of the change.
michael@0 584 for (Range *r = ranges; r; r = r->next)
michael@0 585 r->onCompact();
michael@0 586 }
michael@0 587
michael@0 588 /* Compact the entries in |data| and rehash them. */
michael@0 589 void rehashInPlace() {
michael@0 590 for (uint32_t i = 0, N = hashBuckets(); i < N; i++)
michael@0 591 hashTable[i] = nullptr;
michael@0 592 Data *wp = data, *end = data + dataLength;
michael@0 593 for (Data *rp = data; rp != end; rp++) {
michael@0 594 if (!Ops::isEmpty(Ops::getKey(rp->element))) {
michael@0 595 HashNumber h = prepareHash(Ops::getKey(rp->element)) >> hashShift;
michael@0 596 if (rp != wp)
michael@0 597 wp->element = Move(rp->element);
michael@0 598 wp->chain = hashTable[h];
michael@0 599 hashTable[h] = wp;
michael@0 600 wp++;
michael@0 601 }
michael@0 602 }
michael@0 603 MOZ_ASSERT(wp == data + liveCount);
michael@0 604
michael@0 605 while (wp != end)
michael@0 606 (--end)->~Data();
michael@0 607 dataLength = liveCount;
michael@0 608 compacted();
michael@0 609 }
michael@0 610
michael@0 611 /*
michael@0 612 * Grow, shrink, or compact both |hashTable| and |data|.
michael@0 613 *
michael@0 614 * On success, this returns true, dataLength == liveCount, and there are no
michael@0 615 * empty elements in data[0:dataLength]. On allocation failure, this
michael@0 616 * leaves everything as it was and returns false.
michael@0 617 */
michael@0 618 bool rehash(uint32_t newHashShift) {
michael@0 619 // If the size of the table is not changing, rehash in place to avoid
michael@0 620 // allocating memory.
michael@0 621 if (newHashShift == hashShift) {
michael@0 622 rehashInPlace();
michael@0 623 return true;
michael@0 624 }
michael@0 625
michael@0 626 size_t newHashBuckets = 1 << (HashNumberSizeBits - newHashShift);
michael@0 627 Data **newHashTable = static_cast<Data **>(alloc.malloc_(newHashBuckets * sizeof(Data *)));
michael@0 628 if (!newHashTable)
michael@0 629 return false;
michael@0 630 for (uint32_t i = 0; i < newHashBuckets; i++)
michael@0 631 newHashTable[i] = nullptr;
michael@0 632
michael@0 633 uint32_t newCapacity = uint32_t(newHashBuckets * fillFactor());
michael@0 634 Data *newData = static_cast<Data *>(alloc.malloc_(newCapacity * sizeof(Data)));
michael@0 635 if (!newData) {
michael@0 636 alloc.free_(newHashTable);
michael@0 637 return false;
michael@0 638 }
michael@0 639
michael@0 640 Data *wp = newData;
michael@0 641 for (Data *p = data, *end = data + dataLength; p != end; p++) {
michael@0 642 if (!Ops::isEmpty(Ops::getKey(p->element))) {
michael@0 643 HashNumber h = prepareHash(Ops::getKey(p->element)) >> newHashShift;
michael@0 644 new (wp) Data(Move(p->element), newHashTable[h]);
michael@0 645 newHashTable[h] = wp;
michael@0 646 wp++;
michael@0 647 }
michael@0 648 }
michael@0 649 MOZ_ASSERT(wp == newData + liveCount);
michael@0 650
michael@0 651 alloc.free_(hashTable);
michael@0 652 freeData(data, dataLength);
michael@0 653
michael@0 654 hashTable = newHashTable;
michael@0 655 data = newData;
michael@0 656 dataLength = liveCount;
michael@0 657 dataCapacity = newCapacity;
michael@0 658 hashShift = newHashShift;
michael@0 659 MOZ_ASSERT(hashBuckets() == newHashBuckets);
michael@0 660
michael@0 661 compacted();
michael@0 662 return true;
michael@0 663 }
michael@0 664
michael@0 665 // Not copyable.
michael@0 666 OrderedHashTable &operator=(const OrderedHashTable &) MOZ_DELETE;
michael@0 667 OrderedHashTable(const OrderedHashTable &) MOZ_DELETE;
michael@0 668 };
michael@0 669
michael@0 670 } // namespace detail
michael@0 671
michael@0 672 template <class Key, class Value, class OrderedHashPolicy, class AllocPolicy>
michael@0 673 class OrderedHashMap
michael@0 674 {
michael@0 675 public:
michael@0 676 class Entry
michael@0 677 {
michael@0 678 template <class, class, class> friend class detail::OrderedHashTable;
michael@0 679 void operator=(const Entry &rhs) {
michael@0 680 const_cast<Key &>(key) = rhs.key;
michael@0 681 value = rhs.value;
michael@0 682 }
michael@0 683
michael@0 684 void operator=(Entry &&rhs) {
michael@0 685 MOZ_ASSERT(this != &rhs, "self-move assignment is prohibited");
michael@0 686 const_cast<Key &>(key) = Move(rhs.key);
michael@0 687 value = Move(rhs.value);
michael@0 688 }
michael@0 689
michael@0 690 public:
michael@0 691 Entry() : key(), value() {}
michael@0 692 Entry(const Key &k, const Value &v) : key(k), value(v) {}
michael@0 693 Entry(Entry &&rhs) : key(Move(rhs.key)), value(Move(rhs.value)) {}
michael@0 694
michael@0 695 const Key key;
michael@0 696 Value value;
michael@0 697 };
michael@0 698
michael@0 699 private:
michael@0 700 struct MapOps : OrderedHashPolicy
michael@0 701 {
michael@0 702 typedef Key KeyType;
michael@0 703 static void makeEmpty(Entry *e) {
michael@0 704 OrderedHashPolicy::makeEmpty(const_cast<Key *>(&e->key));
michael@0 705
michael@0 706 // Clear the value. Destroying it is another possibility, but that
michael@0 707 // would complicate class Entry considerably.
michael@0 708 e->value = Value();
michael@0 709 }
michael@0 710 static const Key &getKey(const Entry &e) { return e.key; }
michael@0 711 static void setKey(Entry &e, const Key &k) { const_cast<Key &>(e.key) = k; }
michael@0 712 };
michael@0 713
michael@0 714 typedef detail::OrderedHashTable<Entry, MapOps, AllocPolicy> Impl;
michael@0 715 Impl impl;
michael@0 716
michael@0 717 public:
michael@0 718 typedef typename Impl::Range Range;
michael@0 719
michael@0 720 OrderedHashMap(AllocPolicy ap = AllocPolicy()) : impl(ap) {}
michael@0 721 bool init() { return impl.init(); }
michael@0 722 uint32_t count() const { return impl.count(); }
michael@0 723 bool has(const Key &key) const { return impl.has(key); }
michael@0 724 Range all() { return impl.all(); }
michael@0 725 const Entry *get(const Key &key) const { return impl.get(key); }
michael@0 726 Entry *get(const Key &key) { return impl.get(key); }
michael@0 727 bool put(const Key &key, const Value &value) { return impl.put(Entry(key, value)); }
michael@0 728 bool remove(const Key &key, bool *foundp) { return impl.remove(key, foundp); }
michael@0 729 bool clear() { return impl.clear(); }
michael@0 730
michael@0 731 void rekeyOneEntry(const Key &current, const Key &newKey) {
michael@0 732 const Entry *e = get(current);
michael@0 733 if (!e)
michael@0 734 return;
michael@0 735 return impl.rekeyOneEntry(current, newKey, Entry(newKey, e->value));
michael@0 736 }
michael@0 737 };
michael@0 738
michael@0 739 template <class T, class OrderedHashPolicy, class AllocPolicy>
michael@0 740 class OrderedHashSet
michael@0 741 {
michael@0 742 private:
michael@0 743 struct SetOps : OrderedHashPolicy
michael@0 744 {
michael@0 745 typedef const T KeyType;
michael@0 746 static const T &getKey(const T &v) { return v; }
michael@0 747 static void setKey(const T &e, const T &v) { const_cast<T &>(e) = v; }
michael@0 748 };
michael@0 749
michael@0 750 typedef detail::OrderedHashTable<T, SetOps, AllocPolicy> Impl;
michael@0 751 Impl impl;
michael@0 752
michael@0 753 public:
michael@0 754 typedef typename Impl::Range Range;
michael@0 755
michael@0 756 OrderedHashSet(AllocPolicy ap = AllocPolicy()) : impl(ap) {}
michael@0 757 bool init() { return impl.init(); }
michael@0 758 uint32_t count() const { return impl.count(); }
michael@0 759 bool has(const T &value) const { return impl.has(value); }
michael@0 760 Range all() { return impl.all(); }
michael@0 761 bool put(const T &value) { return impl.put(value); }
michael@0 762 bool remove(const T &value, bool *foundp) { return impl.remove(value, foundp); }
michael@0 763 bool clear() { return impl.clear(); }
michael@0 764
michael@0 765 void rekeyOneEntry(const T &current, const T &newKey) {
michael@0 766 return impl.rekeyOneEntry(current, newKey, newKey);
michael@0 767 }
michael@0 768 };
michael@0 769
michael@0 770 } // namespace js
michael@0 771
michael@0 772
michael@0 773 /*** HashableValue *******************************************************************************/
michael@0 774
michael@0 775 bool
michael@0 776 HashableValue::setValue(JSContext *cx, HandleValue v)
michael@0 777 {
michael@0 778 if (v.isString()) {
michael@0 779 // Atomize so that hash() and operator==() are fast and infallible.
michael@0 780 JSString *str = AtomizeString(cx, v.toString(), DoNotInternAtom);
michael@0 781 if (!str)
michael@0 782 return false;
michael@0 783 value = StringValue(str);
michael@0 784 } else if (v.isDouble()) {
michael@0 785 double d = v.toDouble();
michael@0 786 int32_t i;
michael@0 787 if (NumberEqualsInt32(d, &i)) {
michael@0 788 // Normalize int32_t-valued doubles to int32_t for faster hashing and testing.
michael@0 789 value = Int32Value(i);
michael@0 790 } else if (IsNaN(d)) {
michael@0 791 // NaNs with different bits must hash and test identically.
michael@0 792 value = DoubleNaNValue();
michael@0 793 } else {
michael@0 794 value = v;
michael@0 795 }
michael@0 796 } else {
michael@0 797 value = v;
michael@0 798 }
michael@0 799
michael@0 800 JS_ASSERT(value.isUndefined() || value.isNull() || value.isBoolean() ||
michael@0 801 value.isNumber() || value.isString() || value.isObject());
michael@0 802 return true;
michael@0 803 }
michael@0 804
michael@0 805 HashNumber
michael@0 806 HashableValue::hash() const
michael@0 807 {
michael@0 808 // HashableValue::setValue normalizes values so that the SameValue relation
michael@0 809 // on HashableValues is the same as the == relationship on
michael@0 810 // value.data.asBits.
michael@0 811 return value.asRawBits();
michael@0 812 }
michael@0 813
michael@0 814 bool
michael@0 815 HashableValue::operator==(const HashableValue &other) const
michael@0 816 {
michael@0 817 // Two HashableValues are equal if they have equal bits.
michael@0 818 bool b = (value.asRawBits() == other.value.asRawBits());
michael@0 819
michael@0 820 #ifdef DEBUG
michael@0 821 bool same;
michael@0 822 JS_ASSERT(SameValue(nullptr, value, other.value, &same));
michael@0 823 JS_ASSERT(same == b);
michael@0 824 #endif
michael@0 825 return b;
michael@0 826 }
michael@0 827
michael@0 828 HashableValue
michael@0 829 HashableValue::mark(JSTracer *trc) const
michael@0 830 {
michael@0 831 HashableValue hv(*this);
michael@0 832 trc->setTracingLocation((void *)this);
michael@0 833 gc::MarkValue(trc, &hv.value, "key");
michael@0 834 return hv;
michael@0 835 }
michael@0 836
michael@0 837
michael@0 838 /*** MapIterator *********************************************************************************/
michael@0 839
michael@0 840 namespace {
michael@0 841
michael@0 842 class MapIteratorObject : public JSObject
michael@0 843 {
michael@0 844 public:
michael@0 845 static const Class class_;
michael@0 846
michael@0 847 enum { TargetSlot, KindSlot, RangeSlot, SlotCount };
michael@0 848 static const JSFunctionSpec methods[];
michael@0 849 static MapIteratorObject *create(JSContext *cx, HandleObject mapobj, ValueMap *data,
michael@0 850 MapObject::IteratorKind kind);
michael@0 851 static void finalize(FreeOp *fop, JSObject *obj);
michael@0 852
michael@0 853 private:
michael@0 854 static inline bool is(HandleValue v);
michael@0 855 inline ValueMap::Range *range();
michael@0 856 inline MapObject::IteratorKind kind() const;
michael@0 857 static bool next_impl(JSContext *cx, CallArgs args);
michael@0 858 static bool next(JSContext *cx, unsigned argc, Value *vp);
michael@0 859 };
michael@0 860
michael@0 861 } /* anonymous namespace */
michael@0 862
michael@0 863 const Class MapIteratorObject::class_ = {
michael@0 864 "Map Iterator",
michael@0 865 JSCLASS_IMPLEMENTS_BARRIERS |
michael@0 866 JSCLASS_HAS_RESERVED_SLOTS(MapIteratorObject::SlotCount),
michael@0 867 JS_PropertyStub, /* addProperty */
michael@0 868 JS_DeletePropertyStub, /* delProperty */
michael@0 869 JS_PropertyStub, /* getProperty */
michael@0 870 JS_StrictPropertyStub, /* setProperty */
michael@0 871 JS_EnumerateStub,
michael@0 872 JS_ResolveStub,
michael@0 873 JS_ConvertStub,
michael@0 874 MapIteratorObject::finalize
michael@0 875 };
michael@0 876
michael@0 877 const JSFunctionSpec MapIteratorObject::methods[] = {
michael@0 878 JS_SELF_HOSTED_FN("@@iterator", "IteratorIdentity", 0, 0),
michael@0 879 JS_FN("next", next, 0, 0),
michael@0 880 JS_FS_END
michael@0 881 };
michael@0 882
michael@0 883 inline ValueMap::Range *
michael@0 884 MapIteratorObject::range()
michael@0 885 {
michael@0 886 return static_cast<ValueMap::Range *>(getSlot(RangeSlot).toPrivate());
michael@0 887 }
michael@0 888
michael@0 889 inline MapObject::IteratorKind
michael@0 890 MapIteratorObject::kind() const
michael@0 891 {
michael@0 892 int32_t i = getSlot(KindSlot).toInt32();
michael@0 893 JS_ASSERT(i == MapObject::Keys || i == MapObject::Values || i == MapObject::Entries);
michael@0 894 return MapObject::IteratorKind(i);
michael@0 895 }
michael@0 896
michael@0 897 bool
michael@0 898 GlobalObject::initMapIteratorProto(JSContext *cx, Handle<GlobalObject *> global)
michael@0 899 {
michael@0 900 JSObject *base = GlobalObject::getOrCreateIteratorPrototype(cx, global);
michael@0 901 if (!base)
michael@0 902 return false;
michael@0 903 Rooted<JSObject*> proto(cx,
michael@0 904 NewObjectWithGivenProto(cx, &MapIteratorObject::class_, base, global));
michael@0 905 if (!proto)
michael@0 906 return false;
michael@0 907 proto->setSlot(MapIteratorObject::RangeSlot, PrivateValue(nullptr));
michael@0 908 if (!JS_DefineFunctions(cx, proto, MapIteratorObject::methods))
michael@0 909 return false;
michael@0 910 global->setReservedSlot(MAP_ITERATOR_PROTO, ObjectValue(*proto));
michael@0 911 return true;
michael@0 912 }
michael@0 913
michael@0 914 MapIteratorObject *
michael@0 915 MapIteratorObject::create(JSContext *cx, HandleObject mapobj, ValueMap *data,
michael@0 916 MapObject::IteratorKind kind)
michael@0 917 {
michael@0 918 Rooted<GlobalObject *> global(cx, &mapobj->global());
michael@0 919 Rooted<JSObject*> proto(cx, GlobalObject::getOrCreateMapIteratorPrototype(cx, global));
michael@0 920 if (!proto)
michael@0 921 return nullptr;
michael@0 922
michael@0 923 ValueMap::Range *range = cx->new_<ValueMap::Range>(data->all());
michael@0 924 if (!range)
michael@0 925 return nullptr;
michael@0 926
michael@0 927 JSObject *iterobj = NewObjectWithGivenProto(cx, &class_, proto, global);
michael@0 928 if (!iterobj) {
michael@0 929 js_delete(range);
michael@0 930 return nullptr;
michael@0 931 }
michael@0 932 iterobj->setSlot(TargetSlot, ObjectValue(*mapobj));
michael@0 933 iterobj->setSlot(KindSlot, Int32Value(int32_t(kind)));
michael@0 934 iterobj->setSlot(RangeSlot, PrivateValue(range));
michael@0 935 return static_cast<MapIteratorObject *>(iterobj);
michael@0 936 }
michael@0 937
michael@0 938 void
michael@0 939 MapIteratorObject::finalize(FreeOp *fop, JSObject *obj)
michael@0 940 {
michael@0 941 fop->delete_(obj->as<MapIteratorObject>().range());
michael@0 942 }
michael@0 943
michael@0 944 bool
michael@0 945 MapIteratorObject::is(HandleValue v)
michael@0 946 {
michael@0 947 return v.isObject() && v.toObject().hasClass(&class_);
michael@0 948 }
michael@0 949
michael@0 950 bool
michael@0 951 MapIteratorObject::next_impl(JSContext *cx, CallArgs args)
michael@0 952 {
michael@0 953 MapIteratorObject &thisobj = args.thisv().toObject().as<MapIteratorObject>();
michael@0 954 ValueMap::Range *range = thisobj.range();
michael@0 955 RootedValue value(cx);
michael@0 956 bool done;
michael@0 957
michael@0 958 if (!range || range->empty()) {
michael@0 959 js_delete(range);
michael@0 960 thisobj.setReservedSlot(RangeSlot, PrivateValue(nullptr));
michael@0 961 value.setUndefined();
michael@0 962 done = true;
michael@0 963 } else {
michael@0 964 switch (thisobj.kind()) {
michael@0 965 case MapObject::Keys:
michael@0 966 value = range->front().key.get();
michael@0 967 break;
michael@0 968
michael@0 969 case MapObject::Values:
michael@0 970 value = range->front().value;
michael@0 971 break;
michael@0 972
michael@0 973 case MapObject::Entries: {
michael@0 974 JS::AutoValueArray<2> pair(cx);
michael@0 975 pair[0].set(range->front().key.get());
michael@0 976 pair[1].set(range->front().value);
michael@0 977
michael@0 978 JSObject *pairobj = NewDenseCopiedArray(cx, pair.length(), pair.begin());
michael@0 979 if (!pairobj)
michael@0 980 return false;
michael@0 981 value.setObject(*pairobj);
michael@0 982 break;
michael@0 983 }
michael@0 984 }
michael@0 985 range->popFront();
michael@0 986 done = false;
michael@0 987 }
michael@0 988
michael@0 989 RootedObject result(cx, CreateItrResultObject(cx, value, done));
michael@0 990 if (!result)
michael@0 991 return false;
michael@0 992 args.rval().setObject(*result);
michael@0 993
michael@0 994 return true;
michael@0 995 }
michael@0 996
michael@0 997 bool
michael@0 998 MapIteratorObject::next(JSContext *cx, unsigned argc, Value *vp)
michael@0 999 {
michael@0 1000 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1001 return CallNonGenericMethod(cx, is, next_impl, args);
michael@0 1002 }
michael@0 1003
michael@0 1004
michael@0 1005 /*** Map *****************************************************************************************/
michael@0 1006
michael@0 1007 const Class MapObject::class_ = {
michael@0 1008 "Map",
michael@0 1009 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
michael@0 1010 JSCLASS_HAS_CACHED_PROTO(JSProto_Map),
michael@0 1011 JS_PropertyStub, // addProperty
michael@0 1012 JS_DeletePropertyStub, // delProperty
michael@0 1013 JS_PropertyStub, // getProperty
michael@0 1014 JS_StrictPropertyStub, // setProperty
michael@0 1015 JS_EnumerateStub,
michael@0 1016 JS_ResolveStub,
michael@0 1017 JS_ConvertStub,
michael@0 1018 finalize,
michael@0 1019 nullptr, // call
michael@0 1020 nullptr, // hasInstance
michael@0 1021 nullptr, // construct
michael@0 1022 mark
michael@0 1023 };
michael@0 1024
michael@0 1025 const JSPropertySpec MapObject::properties[] = {
michael@0 1026 JS_PSG("size", size, 0),
michael@0 1027 JS_PS_END
michael@0 1028 };
michael@0 1029
michael@0 1030 const JSFunctionSpec MapObject::methods[] = {
michael@0 1031 JS_FN("get", get, 1, 0),
michael@0 1032 JS_FN("has", has, 1, 0),
michael@0 1033 JS_FN("set", set, 2, 0),
michael@0 1034 JS_FN("delete", delete_, 1, 0),
michael@0 1035 JS_FN("keys", keys, 0, 0),
michael@0 1036 JS_FN("values", values, 0, 0),
michael@0 1037 JS_FN("clear", clear, 0, 0),
michael@0 1038 JS_SELF_HOSTED_FN("forEach", "MapForEach", 2, 0),
michael@0 1039 JS_FS_END
michael@0 1040 };
michael@0 1041
michael@0 1042 static JSObject *
michael@0 1043 InitClass(JSContext *cx, Handle<GlobalObject*> global, const Class *clasp, JSProtoKey key, Native construct,
michael@0 1044 const JSPropertySpec *properties, const JSFunctionSpec *methods)
michael@0 1045 {
michael@0 1046 Rooted<JSObject*> proto(cx, global->createBlankPrototype(cx, clasp));
michael@0 1047 if (!proto)
michael@0 1048 return nullptr;
michael@0 1049 proto->setPrivate(nullptr);
michael@0 1050
michael@0 1051 Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, construct, ClassName(key, cx), 0));
michael@0 1052 if (!ctor ||
michael@0 1053 !LinkConstructorAndPrototype(cx, ctor, proto) ||
michael@0 1054 !DefinePropertiesAndBrand(cx, proto, properties, methods) ||
michael@0 1055 !GlobalObject::initBuiltinConstructor(cx, global, key, ctor, proto))
michael@0 1056 {
michael@0 1057 return nullptr;
michael@0 1058 }
michael@0 1059 return proto;
michael@0 1060 }
michael@0 1061
michael@0 1062 JSObject *
michael@0 1063 MapObject::initClass(JSContext *cx, JSObject *obj)
michael@0 1064 {
michael@0 1065 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
michael@0 1066 RootedObject proto(cx,
michael@0 1067 InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods));
michael@0 1068 if (proto) {
michael@0 1069 // Define the "entries" method.
michael@0 1070 JSFunction *fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0);
michael@0 1071 if (!fun)
michael@0 1072 return nullptr;
michael@0 1073
michael@0 1074 // Define its alias.
michael@0 1075 RootedValue funval(cx, ObjectValue(*fun));
michael@0 1076 if (!JS_DefineProperty(cx, proto, js_std_iterator_str, funval, 0))
michael@0 1077 return nullptr;
michael@0 1078 }
michael@0 1079 return proto;
michael@0 1080 }
michael@0 1081
michael@0 1082 template <class Range>
michael@0 1083 static void
michael@0 1084 MarkKey(Range &r, const HashableValue &key, JSTracer *trc)
michael@0 1085 {
michael@0 1086 HashableValue newKey = key.mark(trc);
michael@0 1087
michael@0 1088 if (newKey.get() != key.get()) {
michael@0 1089 // The hash function only uses the bits of the Value, so it is safe to
michael@0 1090 // rekey even when the object or string has been modified by the GC.
michael@0 1091 r.rekeyFront(newKey);
michael@0 1092 }
michael@0 1093 }
michael@0 1094
michael@0 1095 void
michael@0 1096 MapObject::mark(JSTracer *trc, JSObject *obj)
michael@0 1097 {
michael@0 1098 if (ValueMap *map = obj->as<MapObject>().getData()) {
michael@0 1099 for (ValueMap::Range r = map->all(); !r.empty(); r.popFront()) {
michael@0 1100 MarkKey(r, r.front().key, trc);
michael@0 1101 gc::MarkValue(trc, &r.front().value, "value");
michael@0 1102 }
michael@0 1103 }
michael@0 1104 }
michael@0 1105
michael@0 1106 #ifdef JSGC_GENERATIONAL
michael@0 1107 struct UnbarrieredHashPolicy {
michael@0 1108 typedef Value Lookup;
michael@0 1109 static HashNumber hash(const Lookup &v) { return v.asRawBits(); }
michael@0 1110 static bool match(const Value &k, const Lookup &l) { return k == l; }
michael@0 1111 static bool isEmpty(const Value &v) { return v.isMagic(JS_HASH_KEY_EMPTY); }
michael@0 1112 static void makeEmpty(Value *vp) { vp->setMagic(JS_HASH_KEY_EMPTY); }
michael@0 1113 };
michael@0 1114
michael@0 1115 template <typename TableType>
michael@0 1116 class OrderedHashTableRef : public gc::BufferableRef
michael@0 1117 {
michael@0 1118 TableType *table;
michael@0 1119 Value key;
michael@0 1120
michael@0 1121 public:
michael@0 1122 explicit OrderedHashTableRef(TableType *t, const Value &k) : table(t), key(k) {}
michael@0 1123
michael@0 1124 void mark(JSTracer *trc) {
michael@0 1125 JS_ASSERT(UnbarrieredHashPolicy::hash(key) ==
michael@0 1126 HashableValue::Hasher::hash(*reinterpret_cast<HashableValue*>(&key)));
michael@0 1127 Value prior = key;
michael@0 1128 gc::MarkValueUnbarriered(trc, &key, "ordered hash table key");
michael@0 1129 table->rekeyOneEntry(prior, key);
michael@0 1130 }
michael@0 1131 };
michael@0 1132 #endif
michael@0 1133
michael@0 1134 static void
michael@0 1135 WriteBarrierPost(JSRuntime *rt, ValueMap *map, const HashableValue &key)
michael@0 1136 {
michael@0 1137 #ifdef JSGC_GENERATIONAL
michael@0 1138 typedef OrderedHashMap<Value, Value, UnbarrieredHashPolicy, RuntimeAllocPolicy> UnbarrieredMap;
michael@0 1139 rt->gcStoreBuffer.putGeneric(OrderedHashTableRef<UnbarrieredMap>(
michael@0 1140 reinterpret_cast<UnbarrieredMap *>(map), key.get()));
michael@0 1141 #endif
michael@0 1142 }
michael@0 1143
michael@0 1144 static void
michael@0 1145 WriteBarrierPost(JSRuntime *rt, ValueSet *set, const HashableValue &key)
michael@0 1146 {
michael@0 1147 #ifdef JSGC_GENERATIONAL
michael@0 1148 typedef OrderedHashSet<Value, UnbarrieredHashPolicy, RuntimeAllocPolicy> UnbarrieredSet;
michael@0 1149 rt->gcStoreBuffer.putGeneric(OrderedHashTableRef<UnbarrieredSet>(
michael@0 1150 reinterpret_cast<UnbarrieredSet *>(set), key.get()));
michael@0 1151 #endif
michael@0 1152 }
michael@0 1153
michael@0 1154 void
michael@0 1155 MapObject::finalize(FreeOp *fop, JSObject *obj)
michael@0 1156 {
michael@0 1157 if (ValueMap *map = obj->as<MapObject>().getData())
michael@0 1158 fop->delete_(map);
michael@0 1159 }
michael@0 1160
michael@0 1161 bool
michael@0 1162 MapObject::construct(JSContext *cx, unsigned argc, Value *vp)
michael@0 1163 {
michael@0 1164 Rooted<JSObject*> obj(cx, NewBuiltinClassInstance(cx, &class_));
michael@0 1165 if (!obj)
michael@0 1166 return false;
michael@0 1167
michael@0 1168 ValueMap *map = cx->new_<ValueMap>(cx->runtime());
michael@0 1169 if (!map)
michael@0 1170 return false;
michael@0 1171 if (!map->init()) {
michael@0 1172 js_ReportOutOfMemory(cx);
michael@0 1173 return false;
michael@0 1174 }
michael@0 1175 obj->setPrivate(map);
michael@0 1176
michael@0 1177 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1178 if (args.hasDefined(0)) {
michael@0 1179 ForOfIterator iter(cx);
michael@0 1180 if (!iter.init(args[0]))
michael@0 1181 return false;
michael@0 1182 RootedValue pairVal(cx);
michael@0 1183 RootedObject pairObj(cx);
michael@0 1184 while (true) {
michael@0 1185 bool done;
michael@0 1186 if (!iter.next(&pairVal, &done))
michael@0 1187 return false;
michael@0 1188 if (done)
michael@0 1189 break;
michael@0 1190 if (!pairVal.isObject()) {
michael@0 1191 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INVALID_MAP_ITERABLE);
michael@0 1192 return false;
michael@0 1193 }
michael@0 1194
michael@0 1195 pairObj = &pairVal.toObject();
michael@0 1196 if (!pairObj)
michael@0 1197 return false;
michael@0 1198
michael@0 1199 RootedValue key(cx);
michael@0 1200 if (!JSObject::getElement(cx, pairObj, pairObj, 0, &key))
michael@0 1201 return false;
michael@0 1202
michael@0 1203 AutoHashableValueRooter hkey(cx);
michael@0 1204 if (!hkey.setValue(cx, key))
michael@0 1205 return false;
michael@0 1206
michael@0 1207 RootedValue val(cx);
michael@0 1208 if (!JSObject::getElement(cx, pairObj, pairObj, 1, &val))
michael@0 1209 return false;
michael@0 1210
michael@0 1211 RelocatableValue rval(val);
michael@0 1212 if (!map->put(hkey, rval)) {
michael@0 1213 js_ReportOutOfMemory(cx);
michael@0 1214 return false;
michael@0 1215 }
michael@0 1216 WriteBarrierPost(cx->runtime(), map, hkey);
michael@0 1217 }
michael@0 1218 }
michael@0 1219
michael@0 1220 args.rval().setObject(*obj);
michael@0 1221 return true;
michael@0 1222 }
michael@0 1223
michael@0 1224 bool
michael@0 1225 MapObject::is(HandleValue v)
michael@0 1226 {
michael@0 1227 return v.isObject() && v.toObject().hasClass(&class_) && v.toObject().getPrivate();
michael@0 1228 }
michael@0 1229
michael@0 1230 #define ARG0_KEY(cx, args, key) \
michael@0 1231 AutoHashableValueRooter key(cx); \
michael@0 1232 if (args.length() > 0 && !key.setValue(cx, args[0])) \
michael@0 1233 return false
michael@0 1234
michael@0 1235 ValueMap &
michael@0 1236 MapObject::extract(CallReceiver call)
michael@0 1237 {
michael@0 1238 JS_ASSERT(call.thisv().isObject());
michael@0 1239 JS_ASSERT(call.thisv().toObject().hasClass(&MapObject::class_));
michael@0 1240 return *call.thisv().toObject().as<MapObject>().getData();
michael@0 1241 }
michael@0 1242
michael@0 1243 bool
michael@0 1244 MapObject::size_impl(JSContext *cx, CallArgs args)
michael@0 1245 {
michael@0 1246 JS_ASSERT(MapObject::is(args.thisv()));
michael@0 1247
michael@0 1248 ValueMap &map = extract(args);
michael@0 1249 JS_STATIC_ASSERT(sizeof map.count() <= sizeof(uint32_t));
michael@0 1250 args.rval().setNumber(map.count());
michael@0 1251 return true;
michael@0 1252 }
michael@0 1253
michael@0 1254 bool
michael@0 1255 MapObject::size(JSContext *cx, unsigned argc, Value *vp)
michael@0 1256 {
michael@0 1257 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1258 return CallNonGenericMethod<MapObject::is, MapObject::size_impl>(cx, args);
michael@0 1259 }
michael@0 1260
michael@0 1261 bool
michael@0 1262 MapObject::get_impl(JSContext *cx, CallArgs args)
michael@0 1263 {
michael@0 1264 JS_ASSERT(MapObject::is(args.thisv()));
michael@0 1265
michael@0 1266 ValueMap &map = extract(args);
michael@0 1267 ARG0_KEY(cx, args, key);
michael@0 1268
michael@0 1269 if (ValueMap::Entry *p = map.get(key))
michael@0 1270 args.rval().set(p->value);
michael@0 1271 else
michael@0 1272 args.rval().setUndefined();
michael@0 1273 return true;
michael@0 1274 }
michael@0 1275
michael@0 1276 bool
michael@0 1277 MapObject::get(JSContext *cx, unsigned argc, Value *vp)
michael@0 1278 {
michael@0 1279 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1280 return CallNonGenericMethod<MapObject::is, MapObject::get_impl>(cx, args);
michael@0 1281 }
michael@0 1282
michael@0 1283 bool
michael@0 1284 MapObject::has_impl(JSContext *cx, CallArgs args)
michael@0 1285 {
michael@0 1286 JS_ASSERT(MapObject::is(args.thisv()));
michael@0 1287
michael@0 1288 ValueMap &map = extract(args);
michael@0 1289 ARG0_KEY(cx, args, key);
michael@0 1290 args.rval().setBoolean(map.has(key));
michael@0 1291 return true;
michael@0 1292 }
michael@0 1293
michael@0 1294 bool
michael@0 1295 MapObject::has(JSContext *cx, unsigned argc, Value *vp)
michael@0 1296 {
michael@0 1297 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1298 return CallNonGenericMethod<MapObject::is, MapObject::has_impl>(cx, args);
michael@0 1299 }
michael@0 1300
michael@0 1301 bool
michael@0 1302 MapObject::set_impl(JSContext *cx, CallArgs args)
michael@0 1303 {
michael@0 1304 JS_ASSERT(MapObject::is(args.thisv()));
michael@0 1305
michael@0 1306 ValueMap &map = extract(args);
michael@0 1307 ARG0_KEY(cx, args, key);
michael@0 1308 RelocatableValue rval(args.get(1));
michael@0 1309 if (!map.put(key, rval)) {
michael@0 1310 js_ReportOutOfMemory(cx);
michael@0 1311 return false;
michael@0 1312 }
michael@0 1313 WriteBarrierPost(cx->runtime(), &map, key);
michael@0 1314 args.rval().setUndefined();
michael@0 1315 return true;
michael@0 1316 }
michael@0 1317
michael@0 1318 bool
michael@0 1319 MapObject::set(JSContext *cx, unsigned argc, Value *vp)
michael@0 1320 {
michael@0 1321 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1322 return CallNonGenericMethod<MapObject::is, MapObject::set_impl>(cx, args);
michael@0 1323 }
michael@0 1324
michael@0 1325 bool
michael@0 1326 MapObject::delete_impl(JSContext *cx, CallArgs args)
michael@0 1327 {
michael@0 1328 // MapObject::mark does not mark deleted entries. Incremental GC therefore
michael@0 1329 // requires that no RelocatableValue objects pointing to heap values be
michael@0 1330 // left alive in the ValueMap.
michael@0 1331 //
michael@0 1332 // OrderedHashMap::remove() doesn't destroy the removed entry. It merely
michael@0 1333 // calls OrderedHashMap::MapOps::makeEmpty. But that is sufficient, because
michael@0 1334 // makeEmpty clears the value by doing e->value = Value(), and in the case
michael@0 1335 // of a ValueMap, Value() means RelocatableValue(), which is the same as
michael@0 1336 // RelocatableValue(UndefinedValue()).
michael@0 1337 JS_ASSERT(MapObject::is(args.thisv()));
michael@0 1338
michael@0 1339 ValueMap &map = extract(args);
michael@0 1340 ARG0_KEY(cx, args, key);
michael@0 1341 bool found;
michael@0 1342 if (!map.remove(key, &found)) {
michael@0 1343 js_ReportOutOfMemory(cx);
michael@0 1344 return false;
michael@0 1345 }
michael@0 1346 args.rval().setBoolean(found);
michael@0 1347 return true;
michael@0 1348 }
michael@0 1349
michael@0 1350 bool
michael@0 1351 MapObject::delete_(JSContext *cx, unsigned argc, Value *vp)
michael@0 1352 {
michael@0 1353 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1354 return CallNonGenericMethod<MapObject::is, MapObject::delete_impl>(cx, args);
michael@0 1355 }
michael@0 1356
michael@0 1357 bool
michael@0 1358 MapObject::iterator_impl(JSContext *cx, CallArgs args, IteratorKind kind)
michael@0 1359 {
michael@0 1360 Rooted<MapObject*> mapobj(cx, &args.thisv().toObject().as<MapObject>());
michael@0 1361 ValueMap &map = *mapobj->getData();
michael@0 1362 Rooted<JSObject*> iterobj(cx, MapIteratorObject::create(cx, mapobj, &map, kind));
michael@0 1363 if (!iterobj)
michael@0 1364 return false;
michael@0 1365 args.rval().setObject(*iterobj);
michael@0 1366 return true;
michael@0 1367 }
michael@0 1368
michael@0 1369 bool
michael@0 1370 MapObject::keys_impl(JSContext *cx, CallArgs args)
michael@0 1371 {
michael@0 1372 return iterator_impl(cx, args, Keys);
michael@0 1373 }
michael@0 1374
michael@0 1375 bool
michael@0 1376 MapObject::keys(JSContext *cx, unsigned argc, Value *vp)
michael@0 1377 {
michael@0 1378 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1379 return CallNonGenericMethod(cx, is, keys_impl, args);
michael@0 1380 }
michael@0 1381
michael@0 1382 bool
michael@0 1383 MapObject::values_impl(JSContext *cx, CallArgs args)
michael@0 1384 {
michael@0 1385 return iterator_impl(cx, args, Values);
michael@0 1386 }
michael@0 1387
michael@0 1388 bool
michael@0 1389 MapObject::values(JSContext *cx, unsigned argc, Value *vp)
michael@0 1390 {
michael@0 1391 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1392 return CallNonGenericMethod(cx, is, values_impl, args);
michael@0 1393 }
michael@0 1394
michael@0 1395 bool
michael@0 1396 MapObject::entries_impl(JSContext *cx, CallArgs args)
michael@0 1397 {
michael@0 1398 return iterator_impl(cx, args, Entries);
michael@0 1399 }
michael@0 1400
michael@0 1401 bool
michael@0 1402 MapObject::entries(JSContext *cx, unsigned argc, Value *vp)
michael@0 1403 {
michael@0 1404 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1405 return CallNonGenericMethod(cx, is, entries_impl, args);
michael@0 1406 }
michael@0 1407
michael@0 1408 bool
michael@0 1409 MapObject::clear_impl(JSContext *cx, CallArgs args)
michael@0 1410 {
michael@0 1411 Rooted<MapObject*> mapobj(cx, &args.thisv().toObject().as<MapObject>());
michael@0 1412 if (!mapobj->getData()->clear()) {
michael@0 1413 js_ReportOutOfMemory(cx);
michael@0 1414 return false;
michael@0 1415 }
michael@0 1416 args.rval().setUndefined();
michael@0 1417 return true;
michael@0 1418 }
michael@0 1419
michael@0 1420 bool
michael@0 1421 MapObject::clear(JSContext *cx, unsigned argc, Value *vp)
michael@0 1422 {
michael@0 1423 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1424 return CallNonGenericMethod(cx, is, clear_impl, args);
michael@0 1425 }
michael@0 1426
michael@0 1427 JSObject *
michael@0 1428 js_InitMapClass(JSContext *cx, HandleObject obj)
michael@0 1429 {
michael@0 1430 return MapObject::initClass(cx, obj);
michael@0 1431 }
michael@0 1432
michael@0 1433
michael@0 1434 /*** SetIterator *********************************************************************************/
michael@0 1435
michael@0 1436 namespace {
michael@0 1437
michael@0 1438 class SetIteratorObject : public JSObject
michael@0 1439 {
michael@0 1440 public:
michael@0 1441 static const Class class_;
michael@0 1442
michael@0 1443 enum { TargetSlot, KindSlot, RangeSlot, SlotCount };
michael@0 1444 static const JSFunctionSpec methods[];
michael@0 1445 static SetIteratorObject *create(JSContext *cx, HandleObject setobj, ValueSet *data,
michael@0 1446 SetObject::IteratorKind kind);
michael@0 1447 static void finalize(FreeOp *fop, JSObject *obj);
michael@0 1448
michael@0 1449 private:
michael@0 1450 static inline bool is(HandleValue v);
michael@0 1451 inline ValueSet::Range *range();
michael@0 1452 inline SetObject::IteratorKind kind() const;
michael@0 1453 static bool next_impl(JSContext *cx, CallArgs args);
michael@0 1454 static bool next(JSContext *cx, unsigned argc, Value *vp);
michael@0 1455 };
michael@0 1456
michael@0 1457 } /* anonymous namespace */
michael@0 1458
michael@0 1459 const Class SetIteratorObject::class_ = {
michael@0 1460 "Set Iterator",
michael@0 1461 JSCLASS_IMPLEMENTS_BARRIERS |
michael@0 1462 JSCLASS_HAS_RESERVED_SLOTS(SetIteratorObject::SlotCount),
michael@0 1463 JS_PropertyStub, /* addProperty */
michael@0 1464 JS_DeletePropertyStub, /* delProperty */
michael@0 1465 JS_PropertyStub, /* getProperty */
michael@0 1466 JS_StrictPropertyStub, /* setProperty */
michael@0 1467 JS_EnumerateStub,
michael@0 1468 JS_ResolveStub,
michael@0 1469 JS_ConvertStub,
michael@0 1470 SetIteratorObject::finalize
michael@0 1471 };
michael@0 1472
michael@0 1473 const JSFunctionSpec SetIteratorObject::methods[] = {
michael@0 1474 JS_SELF_HOSTED_FN("@@iterator", "IteratorIdentity", 0, 0),
michael@0 1475 JS_FN("next", next, 0, 0),
michael@0 1476 JS_FS_END
michael@0 1477 };
michael@0 1478
michael@0 1479 inline ValueSet::Range *
michael@0 1480 SetIteratorObject::range()
michael@0 1481 {
michael@0 1482 return static_cast<ValueSet::Range *>(getSlot(RangeSlot).toPrivate());
michael@0 1483 }
michael@0 1484
michael@0 1485 inline SetObject::IteratorKind
michael@0 1486 SetIteratorObject::kind() const
michael@0 1487 {
michael@0 1488 int32_t i = getSlot(KindSlot).toInt32();
michael@0 1489 JS_ASSERT(i == SetObject::Values || i == SetObject::Entries);
michael@0 1490 return SetObject::IteratorKind(i);
michael@0 1491 }
michael@0 1492
michael@0 1493 bool
michael@0 1494 GlobalObject::initSetIteratorProto(JSContext *cx, Handle<GlobalObject*> global)
michael@0 1495 {
michael@0 1496 JSObject *base = GlobalObject::getOrCreateIteratorPrototype(cx, global);
michael@0 1497 if (!base)
michael@0 1498 return false;
michael@0 1499 RootedObject proto(cx, NewObjectWithGivenProto(cx, &SetIteratorObject::class_, base, global));
michael@0 1500 if (!proto)
michael@0 1501 return false;
michael@0 1502 proto->setSlot(SetIteratorObject::RangeSlot, PrivateValue(nullptr));
michael@0 1503 if (!JS_DefineFunctions(cx, proto, SetIteratorObject::methods))
michael@0 1504 return false;
michael@0 1505 global->setReservedSlot(SET_ITERATOR_PROTO, ObjectValue(*proto));
michael@0 1506 return true;
michael@0 1507 }
michael@0 1508
michael@0 1509 SetIteratorObject *
michael@0 1510 SetIteratorObject::create(JSContext *cx, HandleObject setobj, ValueSet *data,
michael@0 1511 SetObject::IteratorKind kind)
michael@0 1512 {
michael@0 1513 Rooted<GlobalObject *> global(cx, &setobj->global());
michael@0 1514 Rooted<JSObject*> proto(cx, GlobalObject::getOrCreateSetIteratorPrototype(cx, global));
michael@0 1515 if (!proto)
michael@0 1516 return nullptr;
michael@0 1517
michael@0 1518 ValueSet::Range *range = cx->new_<ValueSet::Range>(data->all());
michael@0 1519 if (!range)
michael@0 1520 return nullptr;
michael@0 1521
michael@0 1522 JSObject *iterobj = NewObjectWithGivenProto(cx, &class_, proto, global);
michael@0 1523 if (!iterobj) {
michael@0 1524 js_delete(range);
michael@0 1525 return nullptr;
michael@0 1526 }
michael@0 1527 iterobj->setSlot(TargetSlot, ObjectValue(*setobj));
michael@0 1528 iterobj->setSlot(KindSlot, Int32Value(int32_t(kind)));
michael@0 1529 iterobj->setSlot(RangeSlot, PrivateValue(range));
michael@0 1530 return static_cast<SetIteratorObject *>(iterobj);
michael@0 1531 }
michael@0 1532
michael@0 1533 void
michael@0 1534 SetIteratorObject::finalize(FreeOp *fop, JSObject *obj)
michael@0 1535 {
michael@0 1536 fop->delete_(obj->as<SetIteratorObject>().range());
michael@0 1537 }
michael@0 1538
michael@0 1539 bool
michael@0 1540 SetIteratorObject::is(HandleValue v)
michael@0 1541 {
michael@0 1542 return v.isObject() && v.toObject().is<SetIteratorObject>();
michael@0 1543 }
michael@0 1544
michael@0 1545 bool
michael@0 1546 SetIteratorObject::next_impl(JSContext *cx, CallArgs args)
michael@0 1547 {
michael@0 1548 SetIteratorObject &thisobj = args.thisv().toObject().as<SetIteratorObject>();
michael@0 1549 ValueSet::Range *range = thisobj.range();
michael@0 1550 RootedValue value(cx);
michael@0 1551 bool done;
michael@0 1552
michael@0 1553 if (!range || range->empty()) {
michael@0 1554 js_delete(range);
michael@0 1555 thisobj.setReservedSlot(RangeSlot, PrivateValue(nullptr));
michael@0 1556 value.setUndefined();
michael@0 1557 done = true;
michael@0 1558 } else {
michael@0 1559 switch (thisobj.kind()) {
michael@0 1560 case SetObject::Values:
michael@0 1561 value = range->front().get();
michael@0 1562 break;
michael@0 1563
michael@0 1564 case SetObject::Entries: {
michael@0 1565 JS::AutoValueArray<2> pair(cx);
michael@0 1566 pair[0].set(range->front().get());
michael@0 1567 pair[1].set(range->front().get());
michael@0 1568
michael@0 1569 JSObject *pairObj = NewDenseCopiedArray(cx, 2, pair.begin());
michael@0 1570 if (!pairObj)
michael@0 1571 return false;
michael@0 1572 value.setObject(*pairObj);
michael@0 1573 break;
michael@0 1574 }
michael@0 1575 }
michael@0 1576 range->popFront();
michael@0 1577 done = false;
michael@0 1578 }
michael@0 1579
michael@0 1580 RootedObject result(cx, CreateItrResultObject(cx, value, done));
michael@0 1581 if (!result)
michael@0 1582 return false;
michael@0 1583 args.rval().setObject(*result);
michael@0 1584
michael@0 1585 return true;
michael@0 1586 }
michael@0 1587
michael@0 1588 bool
michael@0 1589 SetIteratorObject::next(JSContext *cx, unsigned argc, Value *vp)
michael@0 1590 {
michael@0 1591 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1592 return CallNonGenericMethod(cx, is, next_impl, args);
michael@0 1593 }
michael@0 1594
michael@0 1595
michael@0 1596 /*** Set *****************************************************************************************/
michael@0 1597
michael@0 1598 const Class SetObject::class_ = {
michael@0 1599 "Set",
michael@0 1600 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
michael@0 1601 JSCLASS_HAS_CACHED_PROTO(JSProto_Set),
michael@0 1602 JS_PropertyStub, // addProperty
michael@0 1603 JS_DeletePropertyStub, // delProperty
michael@0 1604 JS_PropertyStub, // getProperty
michael@0 1605 JS_StrictPropertyStub, // setProperty
michael@0 1606 JS_EnumerateStub,
michael@0 1607 JS_ResolveStub,
michael@0 1608 JS_ConvertStub,
michael@0 1609 finalize,
michael@0 1610 nullptr, // call
michael@0 1611 nullptr, // hasInstance
michael@0 1612 nullptr, // construct
michael@0 1613 mark
michael@0 1614 };
michael@0 1615
michael@0 1616 const JSPropertySpec SetObject::properties[] = {
michael@0 1617 JS_PSG("size", size, 0),
michael@0 1618 JS_PS_END
michael@0 1619 };
michael@0 1620
michael@0 1621 const JSFunctionSpec SetObject::methods[] = {
michael@0 1622 JS_FN("has", has, 1, 0),
michael@0 1623 JS_FN("add", add, 1, 0),
michael@0 1624 JS_FN("delete", delete_, 1, 0),
michael@0 1625 JS_FN("entries", entries, 0, 0),
michael@0 1626 JS_FN("clear", clear, 0, 0),
michael@0 1627 JS_SELF_HOSTED_FN("forEach", "SetForEach", 2, 0),
michael@0 1628 JS_FS_END
michael@0 1629 };
michael@0 1630
michael@0 1631 JSObject *
michael@0 1632 SetObject::initClass(JSContext *cx, JSObject *obj)
michael@0 1633 {
michael@0 1634 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
michael@0 1635 RootedObject proto(cx,
michael@0 1636 InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods));
michael@0 1637 if (proto) {
michael@0 1638 // Define the "values" method.
michael@0 1639 JSFunction *fun = JS_DefineFunction(cx, proto, "values", values, 0, 0);
michael@0 1640 if (!fun)
michael@0 1641 return nullptr;
michael@0 1642
michael@0 1643 // Define its aliases.
michael@0 1644 RootedValue funval(cx, ObjectValue(*fun));
michael@0 1645 if (!JS_DefineProperty(cx, proto, "keys", funval, 0))
michael@0 1646 return nullptr;
michael@0 1647 if (!JS_DefineProperty(cx, proto, js_std_iterator_str, funval, 0))
michael@0 1648 return nullptr;
michael@0 1649 }
michael@0 1650 return proto;
michael@0 1651 }
michael@0 1652
michael@0 1653 void
michael@0 1654 SetObject::mark(JSTracer *trc, JSObject *obj)
michael@0 1655 {
michael@0 1656 SetObject *setobj = static_cast<SetObject *>(obj);
michael@0 1657 if (ValueSet *set = setobj->getData()) {
michael@0 1658 for (ValueSet::Range r = set->all(); !r.empty(); r.popFront())
michael@0 1659 MarkKey(r, r.front(), trc);
michael@0 1660 }
michael@0 1661 }
michael@0 1662
michael@0 1663 void
michael@0 1664 SetObject::finalize(FreeOp *fop, JSObject *obj)
michael@0 1665 {
michael@0 1666 SetObject *setobj = static_cast<SetObject *>(obj);
michael@0 1667 if (ValueSet *set = setobj->getData())
michael@0 1668 fop->delete_(set);
michael@0 1669 }
michael@0 1670
michael@0 1671 bool
michael@0 1672 SetObject::construct(JSContext *cx, unsigned argc, Value *vp)
michael@0 1673 {
michael@0 1674 Rooted<JSObject*> obj(cx, NewBuiltinClassInstance(cx, &class_));
michael@0 1675 if (!obj)
michael@0 1676 return false;
michael@0 1677
michael@0 1678 ValueSet *set = cx->new_<ValueSet>(cx->runtime());
michael@0 1679 if (!set)
michael@0 1680 return false;
michael@0 1681 if (!set->init()) {
michael@0 1682 js_ReportOutOfMemory(cx);
michael@0 1683 return false;
michael@0 1684 }
michael@0 1685 obj->setPrivate(set);
michael@0 1686
michael@0 1687 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1688 if (args.hasDefined(0)) {
michael@0 1689 RootedValue keyVal(cx);
michael@0 1690 ForOfIterator iter(cx);
michael@0 1691 if (!iter.init(args[0]))
michael@0 1692 return false;
michael@0 1693 AutoHashableValueRooter key(cx);
michael@0 1694 while (true) {
michael@0 1695 bool done;
michael@0 1696 if (!iter.next(&keyVal, &done))
michael@0 1697 return false;
michael@0 1698 if (done)
michael@0 1699 break;
michael@0 1700 if (!key.setValue(cx, keyVal))
michael@0 1701 return false;
michael@0 1702 if (!set->put(key)) {
michael@0 1703 js_ReportOutOfMemory(cx);
michael@0 1704 return false;
michael@0 1705 }
michael@0 1706 WriteBarrierPost(cx->runtime(), set, key);
michael@0 1707 }
michael@0 1708 }
michael@0 1709
michael@0 1710 args.rval().setObject(*obj);
michael@0 1711 return true;
michael@0 1712 }
michael@0 1713
michael@0 1714 bool
michael@0 1715 SetObject::is(HandleValue v)
michael@0 1716 {
michael@0 1717 return v.isObject() && v.toObject().hasClass(&class_) && v.toObject().getPrivate();
michael@0 1718 }
michael@0 1719
michael@0 1720 ValueSet &
michael@0 1721 SetObject::extract(CallReceiver call)
michael@0 1722 {
michael@0 1723 JS_ASSERT(call.thisv().isObject());
michael@0 1724 JS_ASSERT(call.thisv().toObject().hasClass(&SetObject::class_));
michael@0 1725 return *static_cast<SetObject&>(call.thisv().toObject()).getData();
michael@0 1726 }
michael@0 1727
michael@0 1728 bool
michael@0 1729 SetObject::size_impl(JSContext *cx, CallArgs args)
michael@0 1730 {
michael@0 1731 JS_ASSERT(is(args.thisv()));
michael@0 1732
michael@0 1733 ValueSet &set = extract(args);
michael@0 1734 JS_STATIC_ASSERT(sizeof set.count() <= sizeof(uint32_t));
michael@0 1735 args.rval().setNumber(set.count());
michael@0 1736 return true;
michael@0 1737 }
michael@0 1738
michael@0 1739 bool
michael@0 1740 SetObject::size(JSContext *cx, unsigned argc, Value *vp)
michael@0 1741 {
michael@0 1742 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1743 return CallNonGenericMethod<SetObject::is, SetObject::size_impl>(cx, args);
michael@0 1744 }
michael@0 1745
michael@0 1746 bool
michael@0 1747 SetObject::has_impl(JSContext *cx, CallArgs args)
michael@0 1748 {
michael@0 1749 JS_ASSERT(is(args.thisv()));
michael@0 1750
michael@0 1751 ValueSet &set = extract(args);
michael@0 1752 ARG0_KEY(cx, args, key);
michael@0 1753 args.rval().setBoolean(set.has(key));
michael@0 1754 return true;
michael@0 1755 }
michael@0 1756
michael@0 1757 bool
michael@0 1758 SetObject::has(JSContext *cx, unsigned argc, Value *vp)
michael@0 1759 {
michael@0 1760 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1761 return CallNonGenericMethod<SetObject::is, SetObject::has_impl>(cx, args);
michael@0 1762 }
michael@0 1763
michael@0 1764 bool
michael@0 1765 SetObject::add_impl(JSContext *cx, CallArgs args)
michael@0 1766 {
michael@0 1767 JS_ASSERT(is(args.thisv()));
michael@0 1768
michael@0 1769 ValueSet &set = extract(args);
michael@0 1770 ARG0_KEY(cx, args, key);
michael@0 1771 if (!set.put(key)) {
michael@0 1772 js_ReportOutOfMemory(cx);
michael@0 1773 return false;
michael@0 1774 }
michael@0 1775 WriteBarrierPost(cx->runtime(), &set, key);
michael@0 1776 args.rval().setUndefined();
michael@0 1777 return true;
michael@0 1778 }
michael@0 1779
michael@0 1780 bool
michael@0 1781 SetObject::add(JSContext *cx, unsigned argc, Value *vp)
michael@0 1782 {
michael@0 1783 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1784 return CallNonGenericMethod<SetObject::is, SetObject::add_impl>(cx, args);
michael@0 1785 }
michael@0 1786
michael@0 1787 bool
michael@0 1788 SetObject::delete_impl(JSContext *cx, CallArgs args)
michael@0 1789 {
michael@0 1790 JS_ASSERT(is(args.thisv()));
michael@0 1791
michael@0 1792 ValueSet &set = extract(args);
michael@0 1793 ARG0_KEY(cx, args, key);
michael@0 1794 bool found;
michael@0 1795 if (!set.remove(key, &found)) {
michael@0 1796 js_ReportOutOfMemory(cx);
michael@0 1797 return false;
michael@0 1798 }
michael@0 1799 args.rval().setBoolean(found);
michael@0 1800 return true;
michael@0 1801 }
michael@0 1802
michael@0 1803 bool
michael@0 1804 SetObject::delete_(JSContext *cx, unsigned argc, Value *vp)
michael@0 1805 {
michael@0 1806 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1807 return CallNonGenericMethod<SetObject::is, SetObject::delete_impl>(cx, args);
michael@0 1808 }
michael@0 1809
michael@0 1810 bool
michael@0 1811 SetObject::iterator_impl(JSContext *cx, CallArgs args, IteratorKind kind)
michael@0 1812 {
michael@0 1813 Rooted<SetObject*> setobj(cx, &args.thisv().toObject().as<SetObject>());
michael@0 1814 ValueSet &set = *setobj->getData();
michael@0 1815 Rooted<JSObject*> iterobj(cx, SetIteratorObject::create(cx, setobj, &set, kind));
michael@0 1816 if (!iterobj)
michael@0 1817 return false;
michael@0 1818 args.rval().setObject(*iterobj);
michael@0 1819 return true;
michael@0 1820 }
michael@0 1821
michael@0 1822 bool
michael@0 1823 SetObject::values_impl(JSContext *cx, CallArgs args)
michael@0 1824 {
michael@0 1825 return iterator_impl(cx, args, Values);
michael@0 1826 }
michael@0 1827
michael@0 1828 bool
michael@0 1829 SetObject::values(JSContext *cx, unsigned argc, Value *vp)
michael@0 1830 {
michael@0 1831 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1832 return CallNonGenericMethod(cx, is, values_impl, args);
michael@0 1833 }
michael@0 1834
michael@0 1835 bool
michael@0 1836 SetObject::entries_impl(JSContext *cx, CallArgs args)
michael@0 1837 {
michael@0 1838 return iterator_impl(cx, args, Entries);
michael@0 1839 }
michael@0 1840
michael@0 1841 bool
michael@0 1842 SetObject::entries(JSContext *cx, unsigned argc, Value *vp)
michael@0 1843 {
michael@0 1844 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1845 return CallNonGenericMethod(cx, is, entries_impl, args);
michael@0 1846 }
michael@0 1847
michael@0 1848 bool
michael@0 1849 SetObject::clear_impl(JSContext *cx, CallArgs args)
michael@0 1850 {
michael@0 1851 Rooted<SetObject*> setobj(cx, &args.thisv().toObject().as<SetObject>());
michael@0 1852 if (!setobj->getData()->clear()) {
michael@0 1853 js_ReportOutOfMemory(cx);
michael@0 1854 return false;
michael@0 1855 }
michael@0 1856 args.rval().setUndefined();
michael@0 1857 return true;
michael@0 1858 }
michael@0 1859
michael@0 1860 bool
michael@0 1861 SetObject::clear(JSContext *cx, unsigned argc, Value *vp)
michael@0 1862 {
michael@0 1863 CallArgs args = CallArgsFromVp(argc, vp);
michael@0 1864 return CallNonGenericMethod(cx, is, clear_impl, args);
michael@0 1865 }
michael@0 1866
michael@0 1867 JSObject *
michael@0 1868 js_InitSetClass(JSContext *cx, HandleObject obj)
michael@0 1869 {
michael@0 1870 return SetObject::initClass(cx, obj);
michael@0 1871 }

mercurial