builtins-collections-gen.cc 118 KB
Newer Older
1 2 3 4
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
#include "src/builtins/builtins-collections-gen.h"

7 8 9
#include "src/builtins/builtins-constructor-gen.h"
#include "src/builtins/builtins-iterator-gen.h"
#include "src/builtins/builtins-utils-gen.h"
10
#include "src/codegen/code-stub-assembler.h"
11
#include "src/execution/protectors.h"
12
#include "src/heap/factory-inl.h"
13
#include "src/heap/heap-inl.h"
14
#include "src/objects/hash-table-inl.h"
15
#include "src/objects/js-collection.h"
16
#include "src/objects/ordered-hash-table.h"
17
#include "src/roots/roots.h"
18 19 20 21

namespace v8 {
namespace internal {

22
template <class T>
23
using TVariable = compiler::TypedCodeAssemblerVariable<T>;
24

25
class BaseCollectionsAssembler : public CodeStubAssembler {
26
 public:
27
  explicit BaseCollectionsAssembler(compiler::CodeAssemblerState* state)
28
      : CodeStubAssembler(state) {}
29

30
  virtual ~BaseCollectionsAssembler() = default;
31

32
 protected:
33
  enum Variant { kMap, kSet, kWeakMap, kWeakSet };
34 35 36

  // Adds an entry to a collection.  For Maps, properly handles extracting the
  // key and value from the entry (see LoadKeyValue()).
37
  void AddConstructorEntry(Variant variant, TNode<Context> context,
38
                           TNode<Object> collection, TNode<Object> add_function,
39 40 41 42
                           TNode<Object> key_value,
                           Label* if_may_have_side_effects = nullptr,
                           Label* if_exception = nullptr,
                           TVariable<Object>* var_exception = nullptr);
43 44 45 46 47

  // Adds constructor entries to a collection.  Choosing a fast path when
  // possible.
  void AddConstructorEntries(Variant variant, TNode<Context> context,
                             TNode<Context> native_context,
48
                             TNode<HeapObject> collection,
49
                             TNode<Object> initial_entries);
50 51 52 53 54

  // Fast path for adding constructor entries.  Assumes the entries are a fast
  // JS array (see CodeStubAssembler::BranchIfFastJSArray()).
  void AddConstructorEntriesFromFastJSArray(Variant variant,
                                            TNode<Context> context,
55
                                            TNode<Context> native_context,
56
                                            TNode<Object> collection,
57 58
                                            TNode<JSArray> fast_jsarray,
                                            Label* if_may_have_side_effects);
59 60 61 62 63 64 65 66 67

  // Adds constructor entries to a collection using the iterator protocol.
  void AddConstructorEntriesFromIterable(Variant variant,
                                         TNode<Context> context,
                                         TNode<Context> native_context,
                                         TNode<Object> collection,
                                         TNode<Object> iterable);

  // Constructs a collection instance. Choosing a fast path when possible.
68 69 70
  TNode<JSObject> AllocateJSCollection(TNode<Context> context,
                                       TNode<JSFunction> constructor,
                                       TNode<JSReceiver> new_target);
71 72 73

  // Fast path for constructing a collection instance if the constructor
  // function has not been modified.
74
  TNode<JSObject> AllocateJSCollectionFast(TNode<JSFunction> constructor);
75 76 77

  // Fallback for constructing a collection instance if the constructor function
  // has been modified.
78 79 80
  TNode<JSObject> AllocateJSCollectionSlow(TNode<Context> context,
                                           TNode<JSFunction> constructor,
                                           TNode<JSReceiver> new_target);
81 82

  // Allocates the backing store for a collection.
83 84
  virtual TNode<HeapObject> AllocateTable(
      Variant variant, TNode<IntPtrT> at_least_space_for) = 0;
85 86 87

  // Main entry point for a collection constructor builtin.
  void GenerateConstructor(Variant variant,
88 89 90
                           Handle<String> constructor_function_name,
                           TNode<Object> new_target, TNode<IntPtrT> argc,
                           TNode<Context> context);
91 92 93

  // Retrieves the collection function that adds an entry. `set` for Maps and
  // `add` for Sets.
94 95
  TNode<Object> GetAddFunction(Variant variant, TNode<Context> context,
                               TNode<Object> collection);
96 97 98 99 100 101 102 103 104 105 106

  // Retrieves the collection constructor function.
  TNode<JSFunction> GetConstructor(Variant variant,
                                   TNode<Context> native_context);

  // Retrieves the initial collection function that adds an entry. Should only
  // be called when it is certain that a collection prototype's map hasn't been
  // changed.
  TNode<JSFunction> GetInitialAddFunction(Variant variant,
                                          TNode<Context> native_context);

107 108
  // Checks whether {collection}'s initial add/set function has been modified
  // (depending on {variant}, loaded from {native_context}).
109
  void GotoIfInitialAddFunctionModified(Variant variant,
110 111
                                        TNode<NativeContext> native_context,
                                        TNode<HeapObject> collection,
112
                                        Label* if_modified);
113 114

  // Gets root index for the name of the add/set function.
115
  RootIndex GetAddFunctionNameIndex(Variant variant);
116

117 118
  // Retrieves the offset to access the backing table from the collection.
  int GetTableOffset(Variant variant);
119 120 121 122 123 124 125

  // Estimates the number of entries the collection will have after adding the
  // entries passed in the constructor. AllocateTable() can use this to avoid
  // the time of growing/rehashing when adding the constructor entries.
  TNode<IntPtrT> EstimatedInitialSize(TNode<Object> initial_entries,
                                      TNode<BoolT> is_fast_jsarray);

126 127
  void GotoIfCannotBeWeakKey(const TNode<Object> obj,
                             Label* if_cannot_be_weak_key);
128

129 130 131 132 133
  // Determines whether the collection's prototype has been modified.
  TNode<BoolT> HasInitialCollectionPrototype(Variant variant,
                                             TNode<Context> native_context,
                                             TNode<Object> collection);

134 135 136 137
  // Gets the initial prototype map for given collection {variant}.
  TNode<Map> GetInitialCollectionPrototype(Variant variant,
                                           TNode<Context> native_context);

138 139
  // Loads an element from a fixed array.  If the element is the hole, returns
  // `undefined`.
140
  TNode<Object> LoadAndNormalizeFixedArrayElement(TNode<FixedArray> elements,
141 142 143 144
                                                  TNode<IntPtrT> index);

  // Loads an element from a fixed double array.  If the element is the hole,
  // returns `undefined`.
145 146
  TNode<Object> LoadAndNormalizeFixedDoubleArrayElement(
      TNode<HeapObject> elements, TNode<IntPtrT> index);
147 148
};

149
void BaseCollectionsAssembler::AddConstructorEntry(
150
    Variant variant, TNode<Context> context, TNode<Object> collection,
151
    TNode<Object> add_function, TNode<Object> key_value,
152 153
    Label* if_may_have_side_effects, Label* if_exception,
    TVariable<Object>* var_exception) {
154
  compiler::ScopedExceptionHandler handler(this, if_exception, var_exception);
155
  CSA_DCHECK(this, Word32BinaryNot(IsTheHole(key_value)));
156
  if (variant == kMap || variant == kWeakMap) {
157
    TorqueStructKeyValuePair pair =
158 159 160 161
        if_may_have_side_effects != nullptr
            ? LoadKeyValuePairNoSideEffects(context, key_value,
                                            if_may_have_side_effects)
            : LoadKeyValuePair(context, key_value);
162 163
    TNode<Object> key_n = pair.key;
    TNode<Object> value_n = pair.value;
164
    Call(context, add_function, collection, key_n, value_n);
165 166
  } else {
    DCHECK(variant == kSet || variant == kWeakSet);
167
    Call(context, add_function, collection, key_value);
168 169 170 171 172
  }
}

void BaseCollectionsAssembler::AddConstructorEntries(
    Variant variant, TNode<Context> context, TNode<Context> native_context,
173
    TNode<HeapObject> collection, TNode<Object> initial_entries) {
174
  TVARIABLE(BoolT, use_fast_loop,
175
            IsFastJSArrayWithNoCustomIteration(context, initial_entries));
176
  TNode<IntPtrT> at_least_space_for =
177
      EstimatedInitialSize(initial_entries, use_fast_loop.value());
178 179 180 181 182
  Label allocate_table(this, &use_fast_loop), exit(this), fast_loop(this),
      slow_loop(this, Label::kDeferred);
  Goto(&allocate_table);
  BIND(&allocate_table);
  {
183
    TNode<HeapObject> table = AllocateTable(variant, at_least_space_for);
184 185
    StoreObjectField(collection, GetTableOffset(variant), table);
    GotoIf(IsNullOrUndefined(initial_entries), &exit);
186 187
    GotoIfInitialAddFunctionModified(variant, CAST(native_context), collection,
                                     &slow_loop);
188
    Branch(use_fast_loop.value(), &fast_loop, &slow_loop);
189 190 191
  }
  BIND(&fast_loop);
  {
192 193
    TNode<JSArray> initial_entries_jsarray =
        UncheckedCast<JSArray>(initial_entries);
194
#if DEBUG
195
    CSA_DCHECK(this, IsFastJSArrayWithNoCustomIteration(
196
                         context, initial_entries_jsarray));
197
    TNode<Map> original_initial_entries_map = LoadMap(initial_entries_jsarray);
198 199 200
#endif

    Label if_may_have_side_effects(this, Label::kDeferred);
201 202 203
    AddConstructorEntriesFromFastJSArray(variant, context, native_context,
                                         collection, initial_entries_jsarray,
                                         &if_may_have_side_effects);
204
    Goto(&exit);
205

206 207 208
    if (variant == kMap || variant == kWeakMap) {
      BIND(&if_may_have_side_effects);
#if DEBUG
209 210 211
      {
        // Check that add/set function has not been modified.
        Label if_not_modified(this), if_modified(this);
212
        GotoIfInitialAddFunctionModified(variant, CAST(native_context),
213
                                         collection, &if_modified);
214 215 216 217 218
        Goto(&if_not_modified);
        BIND(&if_modified);
        Unreachable();
        BIND(&if_not_modified);
      }
219
      CSA_DCHECK(this, TaggedEqual(original_initial_entries_map,
220
                                   LoadMap(initial_entries_jsarray)));
221
#endif
222
      use_fast_loop = Int32FalseConstant();
223
      Goto(&allocate_table);
224
    }
225
  }
226 227 228 229 230 231
  BIND(&slow_loop);
  {
    AddConstructorEntriesFromIterable(variant, context, native_context,
                                      collection, initial_entries);
    Goto(&exit);
  }
232 233 234 235
  BIND(&exit);
}

void BaseCollectionsAssembler::AddConstructorEntriesFromFastJSArray(
236
    Variant variant, TNode<Context> context, TNode<Context> native_context,
237 238
    TNode<Object> collection, TNode<JSArray> fast_jsarray,
    Label* if_may_have_side_effects) {
239
  TNode<FixedArrayBase> elements = LoadElements(fast_jsarray);
240
  TNode<Int32T> elements_kind = LoadElementsKind(fast_jsarray);
241
  TNode<JSFunction> add_func = GetInitialAddFunction(variant, native_context);
242
  CSA_DCHECK(this,
243 244
             TaggedEqual(GetAddFunction(variant, native_context, collection),
                         add_func));
245
  CSA_DCHECK(this, IsFastJSArrayWithNoCustomIteration(context, fast_jsarray));
246
  TNode<IntPtrT> length = SmiUntag(LoadFastJSArrayLength(fast_jsarray));
247 248
  CSA_DCHECK(this, IntPtrGreaterThanOrEqual(length, IntPtrConstant(0)));
  CSA_DCHECK(
249
      this, HasInitialCollectionPrototype(variant, native_context, collection));
250

251 252 253 254
#if DEBUG
  TNode<Map> original_collection_map = LoadMap(CAST(collection));
  TNode<Map> original_fast_js_array_map = LoadMap(fast_jsarray);
#endif
255
  Label exit(this), if_doubles(this), if_smiorobjects(this);
256
  GotoIf(IntPtrEqual(length, IntPtrConstant(0)), &exit);
257 258 259 260
  Branch(IsFastSmiOrTaggedElementsKind(elements_kind), &if_smiorobjects,
         &if_doubles);
  BIND(&if_smiorobjects);
  {
261
    auto set_entry = [&](TNode<IntPtrT> index) {
262
      TNode<Object> element = LoadAndNormalizeFixedArrayElement(
263
          CAST(elements), UncheckedCast<IntPtrT>(index));
264 265
      AddConstructorEntry(variant, context, collection, add_func, element,
                          if_may_have_side_effects);
266
    };
267 268 269 270 271

    // Instead of using the slower iteration protocol to iterate over the
    // elements, a fast loop is used.  This assumes that adding an element
    // to the collection does not call user code that could mutate the elements
    // or collection.
272 273
    BuildFastLoop<IntPtrT>(IntPtrConstant(0), length, set_entry, 1,
                           IndexAdvanceMode::kPost);
274 275 276 277 278 279
    Goto(&exit);
  }
  BIND(&if_doubles);
  {
    // A Map constructor requires entries to be arrays (ex. [key, value]),
    // so a FixedDoubleArray can never succeed.
280
    if (variant == kMap || variant == kWeakMap) {
281
      CSA_DCHECK(this, IntPtrGreaterThan(length, IntPtrConstant(0)));
282 283
      TNode<Object> element =
          LoadAndNormalizeFixedDoubleArrayElement(elements, IntPtrConstant(0));
284
      ThrowTypeError(context, MessageTemplate::kIteratorValueNotAnObject,
285
                     element);
286
    } else {
287
      DCHECK(variant == kSet || variant == kWeakSet);
288
      auto set_entry = [&](TNode<IntPtrT> index) {
289 290
        TNode<Object> entry = LoadAndNormalizeFixedDoubleArrayElement(
            elements, UncheckedCast<IntPtrT>(index));
291
        AddConstructorEntry(variant, context, collection, add_func, entry);
292
      };
293 294
      BuildFastLoop<IntPtrT>(IntPtrConstant(0), length, set_entry, 1,
                             IndexAdvanceMode::kPost);
295 296 297 298
      Goto(&exit);
    }
  }
  BIND(&exit);
299
#if DEBUG
300
  CSA_DCHECK(this,
301
             TaggedEqual(original_collection_map, LoadMap(CAST(collection))));
302
  CSA_DCHECK(this,
303
             TaggedEqual(original_fast_js_array_map, LoadMap(fast_jsarray)));
304
#endif
305 306 307 308 309 310
}

void BaseCollectionsAssembler::AddConstructorEntriesFromIterable(
    Variant variant, TNode<Context> context, TNode<Context> native_context,
    TNode<Object> collection, TNode<Object> iterable) {
  Label exit(this), loop(this), if_exception(this, Label::kDeferred);
311
  CSA_DCHECK(this, Word32BinaryNot(IsNullOrUndefined(iterable)));
312

313
  TNode<Object> add_func = GetAddFunction(variant, context, collection);
314
  IteratorBuiltinsAssembler iterator_assembler(this->state());
315
  TorqueStructIteratorRecord iterator =
316
      iterator_assembler.GetIterator(context, iterable);
317

318
  CSA_DCHECK(this, Word32BinaryNot(IsUndefined(iterator.object)));
319

320 321
  TNode<Map> fast_iterator_result_map = CAST(
      LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX));
322 323 324 325 326
  TVARIABLE(Object, var_exception);

  Goto(&loop);
  BIND(&loop);
  {
327
    TNode<JSReceiver> next = iterator_assembler.IteratorStep(
328
        context, iterator, &exit, fast_iterator_result_map);
329 330
    TNode<Object> next_value = iterator_assembler.IteratorValue(
        context, next, fast_iterator_result_map);
331 332
    AddConstructorEntry(variant, context, collection, add_func, next_value,
                        nullptr, &if_exception, &var_exception);
333 334 335 336
    Goto(&loop);
  }
  BIND(&if_exception);
  {
337 338
    TNode<HeapObject> message = GetPendingMessage();
    SetPendingMessage(TheHoleConstant());
339
    IteratorCloseOnException(context, iterator);
340 341
    CallRuntime(Runtime::kReThrowWithMessage, context, var_exception.value(),
                message);
342
    Unreachable();
343 344 345 346
  }
  BIND(&exit);
}

347
RootIndex BaseCollectionsAssembler::GetAddFunctionNameIndex(Variant variant) {
348 349 350
  switch (variant) {
    case kMap:
    case kWeakMap:
351
      return RootIndex::kset_string;
352 353
    case kSet:
    case kWeakSet:
354
      return RootIndex::kadd_string;
355 356 357 358
  }
  UNREACHABLE();
}

359
void BaseCollectionsAssembler::GotoIfInitialAddFunctionModified(
360 361
    Variant variant, TNode<NativeContext> native_context,
    TNode<HeapObject> collection, Label* if_modified) {
362
  static_assert(JSCollection::kAddFunctionDescriptorIndex ==
363
                JSWeakCollection::kAddFunctionDescriptorIndex);
364 365 366 367 368 369 370

  // TODO(jgruber): Investigate if this should also fall back to full prototype
  // verification.
  static constexpr PrototypeCheckAssembler::Flags flags{
      PrototypeCheckAssembler::kCheckPrototypePropertyConstness};

  static constexpr int kNoContextIndex = -1;
371
  static_assert(
372 373 374 375 376 377
      (flags & PrototypeCheckAssembler::kCheckPrototypePropertyIdentity) == 0);

  using DescriptorIndexNameValue =
      PrototypeCheckAssembler::DescriptorIndexNameValue;

  DescriptorIndexNameValue property_to_check{
378
      JSCollection::kAddFunctionDescriptorIndex,
379 380 381 382 383
      GetAddFunctionNameIndex(variant), kNoContextIndex};

  PrototypeCheckAssembler prototype_check_assembler(
      state(), flags, native_context,
      GetInitialCollectionPrototype(variant, native_context),
384
      base::Vector<DescriptorIndexNameValue>(&property_to_check, 1));
385 386 387 388 389 390 391

  TNode<HeapObject> prototype = LoadMapPrototype(LoadMap(collection));
  Label if_unmodified(this);
  prototype_check_assembler.CheckAndBranch(prototype, &if_unmodified,
                                           if_modified);

  BIND(&if_unmodified);
392 393
}

394
TNode<JSObject> BaseCollectionsAssembler::AllocateJSCollection(
395
    TNode<Context> context, TNode<JSFunction> constructor,
396
    TNode<JSReceiver> new_target) {
397
  TNode<BoolT> is_target_unmodified = TaggedEqual(constructor, new_target);
398

399 400 401 402 403 404
  return Select<JSObject>(
      is_target_unmodified,
      [=] { return AllocateJSCollectionFast(constructor); },
      [=] {
        return AllocateJSCollectionSlow(context, constructor, new_target);
      });
405
}
406

407 408
TNode<JSObject> BaseCollectionsAssembler::AllocateJSCollectionFast(
    TNode<JSFunction> constructor) {
409
  CSA_DCHECK(this, IsConstructorMap(LoadMap(constructor)));
410 411 412
  TNode<Map> initial_map =
      CAST(LoadJSFunctionPrototypeOrInitialMap(constructor));
  return AllocateJSObjectFromMap(initial_map);
413 414
}

415
TNode<JSObject> BaseCollectionsAssembler::AllocateJSCollectionSlow(
416
    TNode<Context> context, TNode<JSFunction> constructor,
417
    TNode<JSReceiver> new_target) {
418
  ConstructorBuiltinsAssembler constructor_assembler(this->state());
419
  return constructor_assembler.FastNewObject(context, constructor, new_target);
420 421 422
}

void BaseCollectionsAssembler::GenerateConstructor(
423 424
    Variant variant, Handle<String> constructor_function_name,
    TNode<Object> new_target, TNode<IntPtrT> argc, TNode<Context> context) {
425
  const int kIterableArg = 0;
426
  CodeStubArguments args(this, argc);
427 428 429 430 431
  TNode<Object> iterable = args.GetOptionalArgumentValue(kIterableArg);

  Label if_undefined(this, Label::kDeferred);
  GotoIf(IsUndefined(new_target), &if_undefined);

432
  TNode<NativeContext> native_context = LoadNativeContext(context);
433
  TNode<JSObject> collection = AllocateJSCollection(
434
      context, GetConstructor(variant, native_context), CAST(new_target));
435

436
  AddConstructorEntries(variant, context, native_context, collection, iterable);
437 438 439 440 441 442 443
  Return(collection);

  BIND(&if_undefined);
  ThrowTypeError(context, MessageTemplate::kConstructorNotFunction,
                 HeapConstant(constructor_function_name));
}

444
TNode<Object> BaseCollectionsAssembler::GetAddFunction(
445
    Variant variant, TNode<Context> context, TNode<Object> collection) {
446
  Handle<String> add_func_name = (variant == kMap || variant == kWeakMap)
447 448
                                     ? isolate()->factory()->set_string()
                                     : isolate()->factory()->add_string();
449
  TNode<Object> add_func = GetProperty(context, collection, add_func_name);
450 451 452

  Label exit(this), if_notcallable(this, Label::kDeferred);
  GotoIf(TaggedIsSmi(add_func), &if_notcallable);
453
  GotoIfNot(IsCallable(CAST(add_func)), &if_notcallable);
454 455 456 457 458 459 460
  Goto(&exit);

  BIND(&if_notcallable);
  ThrowTypeError(context, MessageTemplate::kPropertyNotFunction, add_func,
                 HeapConstant(add_func_name), collection);

  BIND(&exit);
461
  return add_func;
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
}

TNode<JSFunction> BaseCollectionsAssembler::GetConstructor(
    Variant variant, TNode<Context> native_context) {
  int index;
  switch (variant) {
    case kMap:
      index = Context::JS_MAP_FUN_INDEX;
      break;
    case kSet:
      index = Context::JS_SET_FUN_INDEX;
      break;
    case kWeakMap:
      index = Context::JS_WEAK_MAP_FUN_INDEX;
      break;
    case kWeakSet:
      index = Context::JS_WEAK_SET_FUN_INDEX;
      break;
  }
  return CAST(LoadContextElement(native_context, index));
}

TNode<JSFunction> BaseCollectionsAssembler::GetInitialAddFunction(
    Variant variant, TNode<Context> native_context) {
  int index;
  switch (variant) {
    case kMap:
      index = Context::MAP_SET_INDEX;
      break;
    case kSet:
      index = Context::SET_ADD_INDEX;
      break;
    case kWeakMap:
      index = Context::WEAKMAP_SET_INDEX;
      break;
    case kWeakSet:
      index = Context::WEAKSET_ADD_INDEX;
      break;
  }
  return CAST(LoadContextElement(native_context, index));
}

int BaseCollectionsAssembler::GetTableOffset(Variant variant) {
  switch (variant) {
    case kMap:
      return JSMap::kTableOffset;
    case kSet:
      return JSSet::kTableOffset;
    case kWeakMap:
      return JSWeakMap::kTableOffset;
    case kWeakSet:
      return JSWeakSet::kTableOffset;
  }
  UNREACHABLE();
516 517 518 519 520 521 522
}

TNode<IntPtrT> BaseCollectionsAssembler::EstimatedInitialSize(
    TNode<Object> initial_entries, TNode<BoolT> is_fast_jsarray) {
  return Select<IntPtrT>(
      is_fast_jsarray,
      [=] { return SmiUntag(LoadFastJSArrayLength(CAST(initial_entries))); },
523
      [=] { return IntPtrConstant(0); });
524 525
}

526 527 528 529 530
void BaseCollectionsAssembler::GotoIfCannotBeWeakKey(
    const TNode<Object> obj, Label* if_cannot_be_weak_key) {
  GotoIf(TaggedIsSmi(obj), if_cannot_be_weak_key);
  TNode<Uint16T> instance_type = LoadMapInstanceType(LoadMap(CAST(obj)));
  GotoIfNot(IsJSReceiverInstanceType(instance_type), if_cannot_be_weak_key);
531 532 533
  // TODO(v8:12547) Shared structs and arrays should only be able to point
  // to shared values in weak collections. For now, disallow them as weak
  // collection keys.
534
  GotoIf(IsJSSharedStructInstanceType(instance_type), if_cannot_be_weak_key);
535
  GotoIf(IsJSSharedArrayInstanceType(instance_type), if_cannot_be_weak_key);
536 537
}

538 539
TNode<Map> BaseCollectionsAssembler::GetInitialCollectionPrototype(
    Variant variant, TNode<Context> native_context) {
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
  int initial_prototype_index;
  switch (variant) {
    case kMap:
      initial_prototype_index = Context::INITIAL_MAP_PROTOTYPE_MAP_INDEX;
      break;
    case kSet:
      initial_prototype_index = Context::INITIAL_SET_PROTOTYPE_MAP_INDEX;
      break;
    case kWeakMap:
      initial_prototype_index = Context::INITIAL_WEAKMAP_PROTOTYPE_MAP_INDEX;
      break;
    case kWeakSet:
      initial_prototype_index = Context::INITIAL_WEAKSET_PROTOTYPE_MAP_INDEX;
      break;
  }
555 556 557 558 559
  return CAST(LoadContextElement(native_context, initial_prototype_index));
}

TNode<BoolT> BaseCollectionsAssembler::HasInitialCollectionPrototype(
    Variant variant, TNode<Context> native_context, TNode<Object> collection) {
560
  TNode<Map> collection_proto_map =
561
      LoadMap(LoadMapPrototype(LoadMap(CAST(collection))));
562

563 564
  return TaggedEqual(collection_proto_map,
                     GetInitialCollectionPrototype(variant, native_context));
565 566
}

567
TNode<Object> BaseCollectionsAssembler::LoadAndNormalizeFixedArrayElement(
568
    TNode<FixedArray> elements, TNode<IntPtrT> index) {
569
  TNode<Object> element = UnsafeLoadFixedArrayElement(elements, index);
570
  return Select<Object>(IsTheHole(element), [=] { return UndefinedConstant(); },
571
                        [=] { return element; });
572 573 574
}

TNode<Object> BaseCollectionsAssembler::LoadAndNormalizeFixedDoubleArrayElement(
575
    TNode<HeapObject> elements, TNode<IntPtrT> index) {
576 577
  TVARIABLE(Object, entry);
  Label if_hole(this, Label::kDeferred), next(this);
578
  TNode<Float64T> element =
579
      LoadFixedDoubleArrayElement(CAST(elements), index, &if_hole);
580 581 582 583 584 585 586 587 588 589
  {  // not hole
    entry = AllocateHeapNumberWithValue(element);
    Goto(&next);
  }
  BIND(&if_hole);
  {
    entry = UndefinedConstant();
    Goto(&next);
  }
  BIND(&next);
590
  return entry.value();
591 592
}

593 594 595 596 597
class CollectionsBuiltinsAssembler : public BaseCollectionsAssembler {
 public:
  explicit CollectionsBuiltinsAssembler(compiler::CodeAssemblerState* state)
      : BaseCollectionsAssembler(state) {}

598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
  // Check whether |iterable| is a JS_MAP_KEY_ITERATOR_TYPE or
  // JS_MAP_VALUE_ITERATOR_TYPE object that is not partially consumed and still
  // has original iteration behavior.
  void BranchIfIterableWithOriginalKeyOrValueMapIterator(TNode<Object> iterable,
                                                         TNode<Context> context,
                                                         Label* if_true,
                                                         Label* if_false);

  // Check whether |iterable| is a JS_SET_TYPE or JS_SET_VALUE_ITERATOR_TYPE
  // object that still has original iteration behavior. In case of the iterator,
  // the iterator also must not have been partially consumed.
  void BranchIfIterableWithOriginalValueSetIterator(TNode<Object> iterable,
                                                    TNode<Context> context,
                                                    Label* if_true,
                                                    Label* if_false);

614
 protected:
615
  template <typename IteratorType>
616 617 618 619 620 621 622 623
  TNode<HeapObject> AllocateJSCollectionIterator(
      const TNode<Context> context, int map_index,
      const TNode<HeapObject> collection);
  TNode<HeapObject> AllocateTable(Variant variant,
                                  TNode<IntPtrT> at_least_space_for) override;
  TNode<IntPtrT> GetHash(const TNode<HeapObject> key);
  TNode<IntPtrT> CallGetHashRaw(const TNode<HeapObject> key);
  TNode<Smi> CallGetOrCreateHashRaw(const TNode<HeapObject> key);
624

625 626
  // Transitions the iterator to the non obsolete backing store.
  // This is a NOP if the [table] is not obsolete.
627 628 629
  template <typename TableType>
  using UpdateInTransition = std::function<void(const TNode<TableType> table,
                                                const TNode<IntPtrT> index)>;
630
  template <typename TableType>
631
  std::pair<TNode<TableType>, TNode<IntPtrT>> Transition(
632
      const TNode<TableType> table, const TNode<IntPtrT> index,
633
      UpdateInTransition<TableType> const& update_in_transition);
634
  template <typename IteratorType, typename TableType>
635
  std::pair<TNode<TableType>, TNode<IntPtrT>> TransitionAndUpdate(
636
      const TNode<IteratorType> iterator);
637
  template <typename TableType>
638 639
  std::tuple<TNode<Object>, TNode<IntPtrT>, TNode<IntPtrT>> NextSkipHoles(
      TNode<TableType> table, TNode<IntPtrT> index, Label* if_end);
640

641
  // Specialization for Smi.
642 643
  // The {result} variable will contain the entry index if the key was found,
  // or the hash code otherwise.
644
  template <typename CollectionType>
645 646 647 648 649 650
  void FindOrderedHashTableEntryForSmiKey(TNode<CollectionType> table,
                                          TNode<Smi> key_tagged,
                                          TVariable<IntPtrT>* result,
                                          Label* entry_found, Label* not_found);
  void SameValueZeroSmi(TNode<Smi> key_smi, TNode<Object> candidate_key,
                        Label* if_same, Label* if_not_same);
651

652
  // Specialization for heap numbers.
653 654
  // The {result} variable will contain the entry index if the key was found,
  // or the hash code otherwise.
655 656 657
  void SameValueZeroHeapNumber(TNode<Float64T> key_float,
                               TNode<Object> candidate_key, Label* if_same,
                               Label* if_not_same);
658
  template <typename CollectionType>
659
  void FindOrderedHashTableEntryForHeapNumberKey(
660 661
      TNode<CollectionType> table, TNode<HeapNumber> key_heap_number,
      TVariable<IntPtrT>* result, Label* entry_found, Label* not_found);
662

663 664 665
  // Specialization for bigints.
  // The {result} variable will contain the entry index if the key was found,
  // or the hash code otherwise.
666 667
  void SameValueZeroBigInt(TNode<BigInt> key, TNode<Object> candidate_key,
                           Label* if_same, Label* if_not_same);
668
  template <typename CollectionType>
669 670 671
  void FindOrderedHashTableEntryForBigIntKey(TNode<CollectionType> table,
                                             TNode<BigInt> key_big_int,
                                             TVariable<IntPtrT>* result,
672 673 674
                                             Label* entry_found,
                                             Label* not_found);

675
  // Specialization for string.
676 677
  // The {result} variable will contain the entry index if the key was found,
  // or the hash code otherwise.
678
  template <typename CollectionType>
679 680 681 682 683 684 685 686
  void FindOrderedHashTableEntryForStringKey(TNode<CollectionType> table,
                                             TNode<String> key_tagged,
                                             TVariable<IntPtrT>* result,
                                             Label* entry_found,
                                             Label* not_found);
  TNode<IntPtrT> ComputeStringHash(TNode<String> string_key);
  void SameValueZeroString(TNode<String> key_string,
                           TNode<Object> candidate_key, Label* if_same,
687
                           Label* if_not_same);
688 689 690

  // Specialization for non-strings, non-numbers. For those we only need
  // reference equality to compare the keys.
691 692 693
  // The {result} variable will contain the entry index if the key was found,
  // or the hash code otherwise. If the hash-code has not been computed, it
  // should be Smi -1.
694
  template <typename CollectionType>
695 696 697 698 699
  void FindOrderedHashTableEntryForOtherKey(TNode<CollectionType> table,
                                            TNode<HeapObject> key_heap_object,
                                            TVariable<IntPtrT>* result,
                                            Label* entry_found,
                                            Label* not_found);
700 701

  template <typename CollectionType>
702 703 704
  void TryLookupOrderedHashTableIndex(const TNode<CollectionType> table,
                                      const TNode<Object> key,
                                      TVariable<IntPtrT>* result,
705 706
                                      Label* if_entry_found,
                                      Label* if_not_found);
707

708
  const TNode<Object> NormalizeNumberKey(const TNode<Object> key);
709
  void StoreOrderedHashMapNewEntry(const TNode<OrderedHashMap> table,
710 711 712 713 714 715
                                   const TNode<Object> key,
                                   const TNode<Object> value,
                                   const TNode<IntPtrT> hash,
                                   const TNode<IntPtrT> number_of_buckets,
                                   const TNode<IntPtrT> occupancy);

716
  void StoreOrderedHashSetNewEntry(const TNode<OrderedHashSet> table,
717 718 719 720
                                   const TNode<Object> key,
                                   const TNode<IntPtrT> hash,
                                   const TNode<IntPtrT> number_of_buckets,
                                   const TNode<IntPtrT> occupancy);
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736

  // Create a JSArray with PACKED_ELEMENTS kind from a Map.prototype.keys() or
  // Map.prototype.values() iterator. The iterator is assumed to satisfy
  // IterableWithOriginalKeyOrValueMapIterator. This function will skip the
  // iterator and iterate directly on the underlying hash table. In the end it
  // will update the state of the iterator to 'exhausted'.
  TNode<JSArray> MapIteratorToList(TNode<Context> context,
                                   TNode<JSMapIterator> iterator);

  // Create a JSArray with PACKED_ELEMENTS kind from a Set.prototype.keys() or
  // Set.prototype.values() iterator, or a Set. The |iterable| is assumed to
  // satisfy IterableWithOriginalValueSetIterator. This function will skip the
  // iterator and iterate directly on the underlying hash table. In the end, if
  // |iterable| is an iterator, it will update the state of the iterator to
  // 'exhausted'.
  TNode<JSArray> SetOrSetIteratorToList(TNode<Context> context,
737
                                        TNode<HeapObject> iterable);
738 739 740

  void BranchIfMapIteratorProtectorValid(Label* if_true, Label* if_false);
  void BranchIfSetIteratorProtectorValid(Label* if_true, Label* if_false);
741 742 743 744 745 746 747 748 749 750 751 752

  // Builds code that finds OrderedHashTable entry for a key with hash code
  // {hash} with using the comparison code generated by {key_compare}. The code
  // jumps to {entry_found} if the key is found, or to {not_found} if the key
  // was not found. In the {entry_found} branch, the variable
  // entry_start_position will be bound to the index of the entry (relative to
  // OrderedHashTable::kHashTableStartIndex).
  //
  // The {CollectionType} template parameter stands for the particular instance
  // of OrderedHashTable, it should be OrderedHashMap or OrderedHashSet.
  template <typename CollectionType>
  void FindOrderedHashTableEntry(
753
      const TNode<CollectionType> table, const TNode<IntPtrT> hash,
754
      const std::function<void(TNode<Object>, Label*, Label*)>& key_compare,
755 756
      TVariable<IntPtrT>* entry_start_position, Label* entry_found,
      Label* not_found);
757 758

  TNode<Word32T> ComputeUnseededHash(TNode<IntPtrT> key);
759 760
};

761 762
template <typename CollectionType>
void CollectionsBuiltinsAssembler::FindOrderedHashTableEntry(
763
    const TNode<CollectionType> table, const TNode<IntPtrT> hash,
764
    const std::function<void(TNode<Object>, Label*, Label*)>& key_compare,
765 766
    TVariable<IntPtrT>* entry_start_position, Label* entry_found,
    Label* not_found) {
767
  // Get the index of the bucket.
768
  const TNode<IntPtrT> number_of_buckets =
769
      SmiUntag(CAST(UnsafeLoadFixedArrayElement(
770
          table, CollectionType::NumberOfBucketsIndex())));
771
  const TNode<IntPtrT> bucket =
772
      WordAnd(hash, IntPtrSub(number_of_buckets, IntPtrConstant(1)));
773
  const TNode<IntPtrT> first_entry = SmiUntag(CAST(UnsafeLoadFixedArrayElement(
774
      table, bucket, CollectionType::HashTableStartIndex() * kTaggedSize)));
775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791

  // Walk the bucket chain.
  TNode<IntPtrT> entry_start;
  Label if_key_found(this);
  {
    TVARIABLE(IntPtrT, var_entry, first_entry);
    Label loop(this, {&var_entry, entry_start_position}),
        continue_next_entry(this);
    Goto(&loop);
    BIND(&loop);

    // If the entry index is the not-found sentinel, we are done.
    GotoIf(IntPtrEqual(var_entry.value(),
                       IntPtrConstant(CollectionType::kNotFound)),
           not_found);

    // Make sure the entry index is within range.
792
    CSA_DCHECK(
793 794 795 796 797
        this,
        UintPtrLessThan(
            var_entry.value(),
            SmiUntag(SmiAdd(
                CAST(UnsafeLoadFixedArrayElement(
798
                    table, CollectionType::NumberOfElementsIndex())),
799
                CAST(UnsafeLoadFixedArrayElement(
800
                    table, CollectionType::NumberOfDeletedElementsIndex()))))));
801 802 803 804 805 806 807 808

    // Compute the index of the entry relative to kHashTableStartIndex.
    entry_start =
        IntPtrAdd(IntPtrMul(var_entry.value(),
                            IntPtrConstant(CollectionType::kEntrySize)),
                  number_of_buckets);

    // Load the key from the entry.
809
    const TNode<Object> candidate_key = UnsafeLoadFixedArrayElement(
810
        table, entry_start,
811 812 813 814 815 816 817
        CollectionType::HashTableStartIndex() * kTaggedSize);

    key_compare(candidate_key, &if_key_found, &continue_next_entry);

    BIND(&continue_next_entry);
    // Load the index of the next entry in the bucket chain.
    var_entry = SmiUntag(CAST(UnsafeLoadFixedArrayElement(
818
        table, entry_start,
819 820 821 822 823 824 825
        (CollectionType::HashTableStartIndex() + CollectionType::kChainOffset) *
            kTaggedSize)));

    Goto(&loop);
  }

  BIND(&if_key_found);
826
  *entry_start_position = entry_start;
827 828 829
  Goto(entry_found);
}

830
template <typename IteratorType>
831
TNode<HeapObject> CollectionsBuiltinsAssembler::AllocateJSCollectionIterator(
832 833 834
    const TNode<Context> context, int map_index,
    const TNode<HeapObject> collection) {
  const TNode<Object> table =
835
      LoadObjectField(collection, JSCollection::kTableOffset);
836
  const TNode<NativeContext> native_context = LoadNativeContext(context);
837 838
  const TNode<Map> iterator_map =
      CAST(LoadContextElement(native_context, map_index));
839 840
  const TNode<HeapObject> iterator =
      AllocateInNewSpace(IteratorType::kHeaderSize);
841
  StoreMapNoWriteBarrier(iterator, iterator_map);
842
  StoreObjectFieldRoot(iterator, IteratorType::kPropertiesOrHashOffset,
843
                       RootIndex::kEmptyFixedArray);
844
  StoreObjectFieldRoot(iterator, IteratorType::kElementsOffset,
845
                       RootIndex::kEmptyFixedArray);
846 847 848 849 850 851
  StoreObjectFieldNoWriteBarrier(iterator, IteratorType::kTableOffset, table);
  StoreObjectFieldNoWriteBarrier(iterator, IteratorType::kIndexOffset,
                                 SmiConstant(0));
  return iterator;
}

852 853
TNode<HeapObject> CollectionsBuiltinsAssembler::AllocateTable(
    Variant variant, TNode<IntPtrT> at_least_space_for) {
854
  if (variant == kMap || variant == kWeakMap) {
855
    return AllocateOrderedHashMap();
856
  } else {
857
    return AllocateOrderedHashSet();
858
  }
859
}
860

861
TF_BUILTIN(MapConstructor, CollectionsBuiltinsAssembler) {
862
  auto new_target = Parameter<Object>(Descriptor::kJSNewTarget);
863
  TNode<IntPtrT> argc = ChangeInt32ToIntPtr(
864 865
      UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount));
  auto context = Parameter<Context>(Descriptor::kContext);
866 867 868

  GenerateConstructor(kMap, isolate()->factory()->Map_string(), new_target,
                      argc, context);
869 870 871
}

TF_BUILTIN(SetConstructor, CollectionsBuiltinsAssembler) {
872
  auto new_target = Parameter<Object>(Descriptor::kJSNewTarget);
873
  TNode<IntPtrT> argc = ChangeInt32ToIntPtr(
874 875
      UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount));
  auto context = Parameter<Context>(Descriptor::kContext);
876 877 878

  GenerateConstructor(kSet, isolate()->factory()->Set_string(), new_target,
                      argc, context);
879 880
}

881
TNode<Smi> CollectionsBuiltinsAssembler::CallGetOrCreateHashRaw(
882
    const TNode<HeapObject> key) {
883
  const TNode<ExternalReference> function_addr =
884
      ExternalConstant(ExternalReference::get_or_create_hash_raw());
885
  const TNode<ExternalReference> isolate_ptr =
886 887 888 889 890
      ExternalConstant(ExternalReference::isolate_address(isolate()));

  MachineType type_ptr = MachineType::Pointer();
  MachineType type_tagged = MachineType::AnyTagged();

891 892 893
  TNode<Smi> result = CAST(CallCFunction(function_addr, type_tagged,
                                         std::make_pair(type_ptr, isolate_ptr),
                                         std::make_pair(type_tagged, key)));
894

895
  return result;
896 897
}

898
TNode<IntPtrT> CollectionsBuiltinsAssembler::CallGetHashRaw(
899
    const TNode<HeapObject> key) {
900
  const TNode<ExternalReference> function_addr =
901
      ExternalConstant(ExternalReference::orderedhashmap_gethash_raw());
902
  const TNode<ExternalReference> isolate_ptr =
903 904 905 906 907
      ExternalConstant(ExternalReference::isolate_address(isolate()));

  MachineType type_ptr = MachineType::Pointer();
  MachineType type_tagged = MachineType::AnyTagged();

908 909 910
  TNode<Smi> result = CAST(CallCFunction(function_addr, type_tagged,
                                         std::make_pair(type_ptr, isolate_ptr),
                                         std::make_pair(type_tagged, key)));
911

912 913 914
  return SmiUntag(result);
}

915
TNode<IntPtrT> CollectionsBuiltinsAssembler::GetHash(
916
    const TNode<HeapObject> key) {
917
  TVARIABLE(IntPtrT, var_hash);
918 919
  Label if_receiver(this), if_other(this), done(this);
  Branch(IsJSReceiver(key), &if_receiver, &if_other);
920

921
  BIND(&if_receiver);
922
  {
923
    var_hash = LoadJSReceiverIdentityHash(CAST(key));
924 925
    Goto(&done);
  }
926

927
  BIND(&if_other);
928
  {
929
    var_hash = CallGetHashRaw(key);
930 931 932 933
    Goto(&done);
  }

  BIND(&done);
934
  return var_hash.value();
935 936
}

937 938 939 940
void CollectionsBuiltinsAssembler::SameValueZeroSmi(TNode<Smi> key_smi,
                                                    TNode<Object> candidate_key,
                                                    Label* if_same,
                                                    Label* if_not_same) {
941
  // If the key is the same, we are done.
942
  GotoIf(TaggedEqual(candidate_key, key_smi), if_same);
943 944 945 946 947 948 949

  // If the candidate key is smi, then it must be different (because
  // we already checked for equality above).
  GotoIf(TaggedIsSmi(candidate_key), if_not_same);

  // If the candidate key is not smi, we still have to check if it is a
  // heap number with the same value.
950
  GotoIfNot(IsHeapNumber(CAST(candidate_key)), if_not_same);
951

952
  const TNode<Float64T> candidate_key_number =
953
      LoadHeapNumberValue(CAST(candidate_key));
954
  const TNode<Float64T> key_number = SmiToFloat64(key_smi);
955 956 957 958 959 960

  GotoIf(Float64Equal(candidate_key_number, key_number), if_same);

  Goto(if_not_same);
}

961 962
void CollectionsBuiltinsAssembler::BranchIfMapIteratorProtectorValid(
    Label* if_true, Label* if_false) {
963
  TNode<PropertyCell> protector_cell = MapIteratorProtectorConstant();
964
  DCHECK(isolate()->heap()->map_iterator_protector().IsPropertyCell());
965 966
  Branch(
      TaggedEqual(LoadObjectField(protector_cell, PropertyCell::kValueOffset),
967
                  SmiConstant(Protectors::kProtectorValid)),
968
      if_true, if_false);
969 970 971 972 973 974 975 976 977 978 979 980
}

void CollectionsBuiltinsAssembler::
    BranchIfIterableWithOriginalKeyOrValueMapIterator(TNode<Object> iterator,
                                                      TNode<Context> context,
                                                      Label* if_true,
                                                      Label* if_false) {
  Label if_key_or_value_iterator(this), extra_checks(this);

  // Check if iterator is a keys or values JSMapIterator.
  GotoIf(TaggedIsSmi(iterator), if_false);
  TNode<Map> iter_map = LoadMap(CAST(iterator));
981
  const TNode<Uint16T> instance_type = LoadMapInstanceType(iter_map);
982 983 984 985 986 987 988
  GotoIf(InstanceTypeEqual(instance_type, JS_MAP_KEY_ITERATOR_TYPE),
         &if_key_or_value_iterator);
  Branch(InstanceTypeEqual(instance_type, JS_MAP_VALUE_ITERATOR_TYPE),
         &if_key_or_value_iterator, if_false);

  BIND(&if_key_or_value_iterator);
  // Check that the iterator is not partially consumed.
989
  const TNode<Object> index =
990
      LoadObjectField(CAST(iterator), JSMapIterator::kIndexOffset);
991
  GotoIfNot(TaggedEqual(index, SmiConstant(0)), if_false);
992 993 994 995
  BranchIfMapIteratorProtectorValid(&extra_checks, if_false);

  BIND(&extra_checks);
  // Check if the iterator object has the original %MapIteratorPrototype%.
996 997
  const TNode<NativeContext> native_context = LoadNativeContext(context);
  const TNode<Object> initial_map_iter_proto = LoadContextElement(
998
      native_context, Context::INITIAL_MAP_ITERATOR_PROTOTYPE_INDEX);
999
  const TNode<HeapObject> map_iter_proto = LoadMapPrototype(iter_map);
1000
  GotoIfNot(TaggedEqual(map_iter_proto, initial_map_iter_proto), if_false);
1001 1002 1003

  // Check if the original MapIterator prototype has the original
  // %IteratorPrototype%.
1004
  const TNode<Object> initial_iter_proto = LoadContextElement(
1005
      native_context, Context::INITIAL_ITERATOR_PROTOTYPE_INDEX);
1006
  const TNode<HeapObject> iter_proto =
1007 1008
      LoadMapPrototype(LoadMap(map_iter_proto));
  Branch(TaggedEqual(iter_proto, initial_iter_proto), if_true, if_false);
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
}

void BranchIfIterableWithOriginalKeyOrValueMapIterator(
    compiler::CodeAssemblerState* state, TNode<Object> iterable,
    TNode<Context> context, compiler::CodeAssemblerLabel* if_true,
    compiler::CodeAssemblerLabel* if_false) {
  CollectionsBuiltinsAssembler assembler(state);
  assembler.BranchIfIterableWithOriginalKeyOrValueMapIterator(
      iterable, context, if_true, if_false);
}

void CollectionsBuiltinsAssembler::BranchIfSetIteratorProtectorValid(
    Label* if_true, Label* if_false) {
1022
  const TNode<PropertyCell> protector_cell = SetIteratorProtectorConstant();
1023
  DCHECK(isolate()->heap()->set_iterator_protector().IsPropertyCell());
1024 1025
  Branch(
      TaggedEqual(LoadObjectField(protector_cell, PropertyCell::kValueOffset),
1026
                  SmiConstant(Protectors::kProtectorValid)),
1027
      if_true, if_false);
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
}

void CollectionsBuiltinsAssembler::BranchIfIterableWithOriginalValueSetIterator(
    TNode<Object> iterable, TNode<Context> context, Label* if_true,
    Label* if_false) {
  Label if_set(this), if_value_iterator(this), check_protector(this);
  TVARIABLE(BoolT, var_result);

  GotoIf(TaggedIsSmi(iterable), if_false);
  TNode<Map> iterable_map = LoadMap(CAST(iterable));
1038
  const TNode<Uint16T> instance_type = LoadMapInstanceType(iterable_map);
1039 1040 1041 1042 1043 1044 1045

  GotoIf(InstanceTypeEqual(instance_type, JS_SET_TYPE), &if_set);
  Branch(InstanceTypeEqual(instance_type, JS_SET_VALUE_ITERATOR_TYPE),
         &if_value_iterator, if_false);

  BIND(&if_set);
  // Check if the set object has the original Set prototype.
1046
  const TNode<Object> initial_set_proto = LoadContextElement(
1047
      LoadNativeContext(context), Context::INITIAL_SET_PROTOTYPE_INDEX);
1048
  const TNode<HeapObject> set_proto = LoadMapPrototype(iterable_map);
1049
  GotoIfNot(TaggedEqual(set_proto, initial_set_proto), if_false);
1050 1051 1052 1053
  Goto(&check_protector);

  BIND(&if_value_iterator);
  // Check that the iterator is not partially consumed.
1054
  const TNode<Object> index =
1055
      LoadObjectField(CAST(iterable), JSSetIterator::kIndexOffset);
1056
  GotoIfNot(TaggedEqual(index, SmiConstant(0)), if_false);
1057 1058

  // Check if the iterator object has the original SetIterator prototype.
1059 1060
  const TNode<NativeContext> native_context = LoadNativeContext(context);
  const TNode<Object> initial_set_iter_proto = LoadContextElement(
1061
      native_context, Context::INITIAL_SET_ITERATOR_PROTOTYPE_INDEX);
1062
  const TNode<HeapObject> set_iter_proto = LoadMapPrototype(iterable_map);
1063
  GotoIfNot(TaggedEqual(set_iter_proto, initial_set_iter_proto), if_false);
1064 1065 1066

  // Check if the original SetIterator prototype has the original
  // %IteratorPrototype%.
1067
  const TNode<Object> initial_iter_proto = LoadContextElement(
1068
      native_context, Context::INITIAL_ITERATOR_PROTOTYPE_INDEX);
1069
  const TNode<HeapObject> iter_proto =
1070 1071
      LoadMapPrototype(LoadMap(set_iter_proto));
  GotoIfNot(TaggedEqual(iter_proto, initial_iter_proto), if_false);
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
  Goto(&check_protector);

  BIND(&check_protector);
  BranchIfSetIteratorProtectorValid(if_true, if_false);
}

void BranchIfIterableWithOriginalValueSetIterator(
    compiler::CodeAssemblerState* state, TNode<Object> iterable,
    TNode<Context> context, compiler::CodeAssemblerLabel* if_true,
    compiler::CodeAssemblerLabel* if_false) {
  CollectionsBuiltinsAssembler assembler(state);
  assembler.BranchIfIterableWithOriginalValueSetIterator(iterable, context,
                                                         if_true, if_false);
}

TNode<JSArray> CollectionsBuiltinsAssembler::MapIteratorToList(
    TNode<Context> context, TNode<JSMapIterator> iterator) {
  // Transition the {iterator} table if necessary.
  TNode<OrderedHashMap> table;
  TNode<IntPtrT> index;
  std::tie(table, index) =
      TransitionAndUpdate<JSMapIterator, OrderedHashMap>(iterator);
1094
  CSA_DCHECK(this, IntPtrEqual(index, IntPtrConstant(0)));
1095 1096

  TNode<IntPtrT> size =
1097
      LoadAndUntagObjectField(table, OrderedHashMap::NumberOfElementsOffset());
1098 1099

  const ElementsKind kind = PACKED_ELEMENTS;
1100
  TNode<Map> array_map =
1101
      LoadJSArrayElementsMap(kind, LoadNativeContext(context));
1102 1103 1104
  TNode<JSArray> array =
      AllocateJSArray(kind, array_map, size, SmiTag(size),
                      AllocationFlag::kAllowLargeObjectAllocation);
1105 1106 1107 1108
  TNode<FixedArray> elements = CAST(LoadElements(array));

  const int first_element_offset = FixedArray::kHeaderSize - kHeapObjectTag;
  TNode<IntPtrT> first_to_element_offset =
1109
      ElementOffsetFromIndex(IntPtrConstant(0), kind, 0);
1110 1111
  TVARIABLE(
      IntPtrT, var_offset,
1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
      IntPtrAdd(first_to_element_offset, IntPtrConstant(first_element_offset)));
  TVARIABLE(IntPtrT, var_index, index);
  VariableList vars({&var_index, &var_offset}, zone());
  Label done(this, {&var_index}), loop(this, vars), continue_loop(this, vars),
      write_key(this, vars), write_value(this, vars);

  Goto(&loop);

  BIND(&loop);
  {
    // Read the next entry from the {table}, skipping holes.
    TNode<Object> entry_key;
    TNode<IntPtrT> entry_start_position;
    TNode<IntPtrT> cur_index;
    std::tie(entry_key, entry_start_position, cur_index) =
        NextSkipHoles<OrderedHashMap>(table, var_index.value(), &done);

    // Decide to write key or value.
    Branch(
        InstanceTypeEqual(LoadInstanceType(iterator), JS_MAP_KEY_ITERATOR_TYPE),
        &write_key, &write_value);

    BIND(&write_key);
    {
      Store(elements, var_offset.value(), entry_key);
      Goto(&continue_loop);
    }

    BIND(&write_value);
    {
1142
      CSA_DCHECK(this, InstanceTypeEqual(LoadInstanceType(iterator),
1143 1144
                                         JS_MAP_VALUE_ITERATOR_TYPE));
      TNode<Object> entry_value =
1145 1146 1147 1148
          UnsafeLoadFixedArrayElement(table, entry_start_position,
                                      (OrderedHashMap::HashTableStartIndex() +
                                       OrderedHashMap::kValueOffset) *
                                          kTaggedSize);
1149 1150 1151 1152 1153 1154 1155 1156 1157

      Store(elements, var_offset.value(), entry_value);
      Goto(&continue_loop);
    }

    BIND(&continue_loop);
    {
      // Increment the array offset and continue the loop to the next entry.
      var_index = cur_index;
1158
      var_offset = IntPtrAdd(var_offset.value(), IntPtrConstant(kTaggedSize));
1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172
      Goto(&loop);
    }
  }

  BIND(&done);
  // Set the {iterator} to exhausted.
  StoreObjectFieldRoot(iterator, JSMapIterator::kTableOffset,
                       RootIndex::kEmptyOrderedHashMap);
  StoreObjectFieldNoWriteBarrier(iterator, JSMapIterator::kIndexOffset,
                                 SmiTag(var_index.value()));
  return UncheckedCast<JSArray>(array);
}

TF_BUILTIN(MapIteratorToList, CollectionsBuiltinsAssembler) {
1173 1174
  auto context = Parameter<Context>(Descriptor::kContext);
  auto iterator = Parameter<JSMapIterator>(Descriptor::kSource);
1175 1176 1177 1178
  Return(MapIteratorToList(context, iterator));
}

TNode<JSArray> CollectionsBuiltinsAssembler::SetOrSetIteratorToList(
1179
    TNode<Context> context, TNode<HeapObject> iterable) {
1180 1181 1182
  TVARIABLE(OrderedHashSet, var_table);
  Label if_set(this), if_iterator(this), copy(this);

1183
  const TNode<Uint16T> instance_type = LoadInstanceType(iterable);
1184 1185 1186 1187 1188
  Branch(InstanceTypeEqual(instance_type, JS_SET_TYPE), &if_set, &if_iterator);

  BIND(&if_set);
  {
    // {iterable} is a JSSet.
1189
    var_table = CAST(LoadObjectField(iterable, JSSet::kTableOffset));
1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200
    Goto(&copy);
  }

  BIND(&if_iterator);
  {
    // {iterable} is a JSSetIterator.
    // Transition the {iterable} table if necessary.
    TNode<OrderedHashSet> iter_table;
    TNode<IntPtrT> iter_index;
    std::tie(iter_table, iter_index) =
        TransitionAndUpdate<JSSetIterator, OrderedHashSet>(CAST(iterable));
1201
    CSA_DCHECK(this, IntPtrEqual(iter_index, IntPtrConstant(0)));
1202 1203 1204 1205 1206 1207 1208
    var_table = iter_table;
    Goto(&copy);
  }

  BIND(&copy);
  TNode<OrderedHashSet> table = var_table.value();
  TNode<IntPtrT> size =
1209
      LoadAndUntagObjectField(table, OrderedHashMap::NumberOfElementsOffset());
1210 1211

  const ElementsKind kind = PACKED_ELEMENTS;
1212
  TNode<Map> array_map =
1213
      LoadJSArrayElementsMap(kind, LoadNativeContext(context));
1214 1215 1216
  TNode<JSArray> array =
      AllocateJSArray(kind, array_map, size, SmiTag(size),
                      AllocationFlag::kAllowLargeObjectAllocation);
1217 1218 1219 1220
  TNode<FixedArray> elements = CAST(LoadElements(array));

  const int first_element_offset = FixedArray::kHeaderSize - kHeapObjectTag;
  TNode<IntPtrT> first_to_element_offset =
1221
      ElementOffsetFromIndex(IntPtrConstant(0), kind, 0);
1222 1223
  TVARIABLE(
      IntPtrT, var_offset,
1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242
      IntPtrAdd(first_to_element_offset, IntPtrConstant(first_element_offset)));
  TVARIABLE(IntPtrT, var_index, IntPtrConstant(0));
  Label done(this), finalize(this, {&var_index}),
      loop(this, {&var_index, &var_offset});

  Goto(&loop);

  BIND(&loop);
  {
    // Read the next entry from the {table}, skipping holes.
    TNode<Object> entry_key;
    TNode<IntPtrT> entry_start_position;
    TNode<IntPtrT> cur_index;
    std::tie(entry_key, entry_start_position, cur_index) =
        NextSkipHoles<OrderedHashSet>(table, var_index.value(), &finalize);

    Store(elements, var_offset.value(), entry_key);

    var_index = cur_index;
1243
    var_offset = IntPtrAdd(var_offset.value(), IntPtrConstant(kTaggedSize));
1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260
    Goto(&loop);
  }

  BIND(&finalize);
  GotoIf(InstanceTypeEqual(instance_type, JS_SET_TYPE), &done);
  // Set the {iterable} to exhausted if it's an iterator.
  StoreObjectFieldRoot(iterable, JSSetIterator::kTableOffset,
                       RootIndex::kEmptyOrderedHashSet);
  StoreObjectFieldNoWriteBarrier(iterable, JSSetIterator::kIndexOffset,
                                 SmiTag(var_index.value()));
  Goto(&done);

  BIND(&done);
  return UncheckedCast<JSArray>(array);
}

TF_BUILTIN(SetOrSetIteratorToList, CollectionsBuiltinsAssembler) {
1261 1262
  auto context = Parameter<Context>(Descriptor::kContext);
  auto object = Parameter<HeapObject>(Descriptor::kSource);
1263 1264 1265
  Return(SetOrSetIteratorToList(context, object));
}

1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279
TNode<Word32T> CollectionsBuiltinsAssembler::ComputeUnseededHash(
    TNode<IntPtrT> key) {
  // See v8::internal::ComputeUnseededHash()
  TNode<Word32T> hash = TruncateIntPtrToInt32(key);
  hash = Int32Add(Word32Xor(hash, Int32Constant(0xFFFFFFFF)),
                  Word32Shl(hash, Int32Constant(15)));
  hash = Word32Xor(hash, Word32Shr(hash, Int32Constant(12)));
  hash = Int32Add(hash, Word32Shl(hash, Int32Constant(2)));
  hash = Word32Xor(hash, Word32Shr(hash, Int32Constant(4)));
  hash = Int32Mul(hash, Int32Constant(2057));
  hash = Word32Xor(hash, Word32Shr(hash, Int32Constant(16)));
  return Word32And(hash, Int32Constant(0x3FFFFFFF));
}

1280 1281
template <typename CollectionType>
void CollectionsBuiltinsAssembler::FindOrderedHashTableEntryForSmiKey(
1282 1283
    TNode<CollectionType> table, TNode<Smi> smi_key, TVariable<IntPtrT>* result,
    Label* entry_found, Label* not_found) {
1284 1285
  const TNode<IntPtrT> key_untagged = SmiUntag(smi_key);
  const TNode<IntPtrT> hash =
1286
      ChangeInt32ToIntPtr(ComputeUnseededHash(key_untagged));
1287
  CSA_DCHECK(this, IntPtrGreaterThanOrEqual(hash, IntPtrConstant(0)));
1288
  *result = hash;
1289
  FindOrderedHashTableEntry<CollectionType>(
1290
      table, hash,
1291
      [&](TNode<Object> other_key, Label* if_same, Label* if_not_same) {
1292 1293
        SameValueZeroSmi(smi_key, other_key, if_same, if_not_same);
      },
1294
      result, entry_found, not_found);
1295 1296
}

1297 1298
template <typename CollectionType>
void CollectionsBuiltinsAssembler::FindOrderedHashTableEntryForStringKey(
1299 1300
    TNode<CollectionType> table, TNode<String> key_tagged,
    TVariable<IntPtrT>* result, Label* entry_found, Label* not_found) {
1301
  const TNode<IntPtrT> hash = ComputeStringHash(key_tagged);
1302
  CSA_DCHECK(this, IntPtrGreaterThanOrEqual(hash, IntPtrConstant(0)));
1303
  *result = hash;
1304
  FindOrderedHashTableEntry<CollectionType>(
1305
      table, hash,
1306
      [&](TNode<Object> other_key, Label* if_same, Label* if_not_same) {
1307
        SameValueZeroString(key_tagged, other_key, if_same, if_not_same);
1308
      },
1309
      result, entry_found, not_found);
1310 1311
}

1312 1313
template <typename CollectionType>
void CollectionsBuiltinsAssembler::FindOrderedHashTableEntryForHeapNumberKey(
1314 1315
    TNode<CollectionType> table, TNode<HeapNumber> key_heap_number,
    TVariable<IntPtrT>* result, Label* entry_found, Label* not_found) {
1316
  const TNode<IntPtrT> hash = CallGetHashRaw(key_heap_number);
1317
  CSA_DCHECK(this, IntPtrGreaterThanOrEqual(hash, IntPtrConstant(0)));
1318
  *result = hash;
1319
  const TNode<Float64T> key_float = LoadHeapNumberValue(key_heap_number);
1320
  FindOrderedHashTableEntry<CollectionType>(
1321
      table, hash,
1322
      [&](TNode<Object> other_key, Label* if_same, Label* if_not_same) {
1323 1324
        SameValueZeroHeapNumber(key_float, other_key, if_same, if_not_same);
      },
1325
      result, entry_found, not_found);
1326 1327
}

1328 1329
template <typename CollectionType>
void CollectionsBuiltinsAssembler::FindOrderedHashTableEntryForBigIntKey(
1330 1331
    TNode<CollectionType> table, TNode<BigInt> key_big_int,
    TVariable<IntPtrT>* result, Label* entry_found, Label* not_found) {
1332
  const TNode<IntPtrT> hash = CallGetHashRaw(key_big_int);
1333
  CSA_DCHECK(this, IntPtrGreaterThanOrEqual(hash, IntPtrConstant(0)));
1334
  *result = hash;
1335 1336
  FindOrderedHashTableEntry<CollectionType>(
      table, hash,
1337
      [&](TNode<Object> other_key, Label* if_same, Label* if_not_same) {
1338
        SameValueZeroBigInt(key_big_int, other_key, if_same, if_not_same);
1339 1340 1341 1342
      },
      result, entry_found, not_found);
}

1343 1344
template <typename CollectionType>
void CollectionsBuiltinsAssembler::FindOrderedHashTableEntryForOtherKey(
1345 1346
    TNode<CollectionType> table, TNode<HeapObject> key_heap_object,
    TVariable<IntPtrT>* result, Label* entry_found, Label* not_found) {
1347
  const TNode<IntPtrT> hash = GetHash(key_heap_object);
1348
  CSA_DCHECK(this, IntPtrGreaterThanOrEqual(hash, IntPtrConstant(0)));
1349
  *result = hash;
1350
  FindOrderedHashTableEntry<CollectionType>(
1351
      table, hash,
1352
      [&](TNode<Object> other_key, Label* if_same, Label* if_not_same) {
1353
        Branch(TaggedEqual(key_heap_object, other_key), if_same, if_not_same);
1354
      },
1355
      result, entry_found, not_found);
1356 1357
}

1358
TNode<IntPtrT> CollectionsBuiltinsAssembler::ComputeStringHash(
1359
    TNode<String> string_key) {
1360
  TVARIABLE(IntPtrT, var_result);
1361 1362

  Label hash_not_computed(this), done(this, &var_result);
1363
  const TNode<IntPtrT> hash =
1364
      ChangeInt32ToIntPtr(LoadNameHash(string_key, &hash_not_computed));
1365
  var_result = hash;
1366 1367 1368
  Goto(&done);

  BIND(&hash_not_computed);
1369
  var_result = CallGetHashRaw(string_key);
1370
  Goto(&done);
1371

1372 1373 1374 1375
  BIND(&done);
  return var_result.value();
}

1376
void CollectionsBuiltinsAssembler::SameValueZeroString(
1377 1378
    TNode<String> key_string, TNode<Object> candidate_key, Label* if_same,
    Label* if_not_same) {
1379 1380
  // If the candidate is not a string, the keys are not equal.
  GotoIf(TaggedIsSmi(candidate_key), if_not_same);
1381
  GotoIfNot(IsString(CAST(candidate_key)), if_not_same);
1382

1383
  Branch(TaggedEqual(CallBuiltin(Builtin::kStringEqual, NoContextConstant(),
1384
                                 key_string, candidate_key),
1385
                     TrueConstant()),
1386 1387 1388
         if_same, if_not_same);
}

1389 1390 1391
void CollectionsBuiltinsAssembler::SameValueZeroBigInt(
    TNode<BigInt> key, TNode<Object> candidate_key, Label* if_same,
    Label* if_not_same) {
1392
  GotoIf(TaggedIsSmi(candidate_key), if_not_same);
1393
  GotoIfNot(IsBigInt(CAST(candidate_key)), if_not_same);
1394

1395 1396 1397
  Branch(TaggedEqual(CallRuntime(Runtime::kBigIntEqualToBigInt,
                                 NoContextConstant(), key, candidate_key),
                     TrueConstant()),
1398 1399 1400
         if_same, if_not_same);
}

1401
void CollectionsBuiltinsAssembler::SameValueZeroHeapNumber(
1402 1403
    TNode<Float64T> key_float, TNode<Object> candidate_key, Label* if_same,
    Label* if_not_same) {
1404 1405 1406
  Label if_smi(this), if_keyisnan(this);

  GotoIf(TaggedIsSmi(candidate_key), &if_smi);
1407
  GotoIfNot(IsHeapNumber(CAST(candidate_key)), if_not_same);
1408 1409 1410

  {
    // {candidate_key} is a heap number.
1411
    const TNode<Float64T> candidate_float =
1412
        LoadHeapNumberValue(CAST(candidate_key));
1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428
    GotoIf(Float64Equal(key_float, candidate_float), if_same);

    // SameValueZero needs to treat NaNs as equal. First check if {key_float}
    // is NaN.
    BranchIfFloat64IsNaN(key_float, &if_keyisnan, if_not_same);

    BIND(&if_keyisnan);
    {
      // Return true iff {candidate_key} is NaN.
      Branch(Float64Equal(candidate_float, candidate_float), if_not_same,
             if_same);
    }
  }

  BIND(&if_smi);
  {
1429
    const TNode<Float64T> candidate_float = SmiToFloat64(CAST(candidate_key));
1430 1431 1432 1433
    Branch(Float64Equal(key_float, candidate_float), if_same, if_not_same);
  }
}

1434
TF_BUILTIN(OrderedHashTableHealIndex, CollectionsBuiltinsAssembler) {
1435 1436
  auto table = Parameter<HeapObject>(Descriptor::kTable);
  auto index = Parameter<Smi>(Descriptor::kIndex);
1437 1438 1439
  Label return_index(this), return_zero(this);

  // Check if we need to update the {index}.
1440
  GotoIfNot(SmiLessThan(SmiConstant(0), index), &return_zero);
1441 1442

  // Check if the {table} was cleared.
1443
  static_assert(OrderedHashMap::NumberOfDeletedElementsOffset() ==
1444
                OrderedHashSet::NumberOfDeletedElementsOffset());
1445
  TNode<IntPtrT> number_of_deleted_elements = LoadAndUntagObjectField(
1446
      table, OrderedHashMap::NumberOfDeletedElementsOffset());
1447
  static_assert(OrderedHashMap::kClearedTableSentinel ==
1448
                OrderedHashSet::kClearedTableSentinel);
1449 1450
  GotoIf(IntPtrEqual(number_of_deleted_elements,
                     IntPtrConstant(OrderedHashMap::kClearedTableSentinel)),
1451 1452
         &return_zero);

1453 1454
  TVARIABLE(IntPtrT, var_i, IntPtrConstant(0));
  TVARIABLE(Smi, var_index, index);
1455 1456 1457 1458
  Label loop(this, {&var_i, &var_index});
  Goto(&loop);
  BIND(&loop);
  {
1459
    TNode<IntPtrT> i = var_i.value();
1460
    GotoIfNot(IntPtrLessThan(i, number_of_deleted_elements), &return_index);
1461
    static_assert(OrderedHashMap::RemovedHolesIndex() ==
1462
                  OrderedHashSet::RemovedHolesIndex());
1463
    TNode<Smi> removed_index = CAST(LoadFixedArrayElement(
1464
        CAST(table), i, OrderedHashMap::RemovedHolesIndex() * kTaggedSize));
1465
    GotoIf(SmiGreaterThanOrEqual(removed_index, index), &return_index);
1466
    Decrement(&var_index);
1467
    Increment(&var_i);
1468 1469 1470 1471 1472 1473 1474
    Goto(&loop);
  }

  BIND(&return_index);
  Return(var_index.value());

  BIND(&return_zero);
1475
  Return(SmiConstant(0));
1476 1477
}

1478
template <typename TableType>
1479 1480
std::pair<TNode<TableType>, TNode<IntPtrT>>
CollectionsBuiltinsAssembler::Transition(
1481
    const TNode<TableType> table, const TNode<IntPtrT> index,
1482
    UpdateInTransition<TableType> const& update_in_transition) {
1483 1484
  TVARIABLE(IntPtrT, var_index, index);
  TVARIABLE(TableType, var_table, table);
1485 1486
  Label if_done(this), if_transition(this, Label::kDeferred);
  Branch(TaggedIsSmi(
1487
             LoadObjectField(var_table.value(), TableType::NextTableOffset())),
1488 1489 1490 1491 1492 1493 1494 1495
         &if_done, &if_transition);

  BIND(&if_transition);
  {
    Label loop(this, {&var_table, &var_index}), done_loop(this);
    Goto(&loop);
    BIND(&loop);
    {
1496 1497
      TNode<TableType> current_table = var_table.value();
      TNode<IntPtrT> current_index = var_index.value();
1498

1499
      TNode<Object> next_table =
1500
          LoadObjectField(current_table, TableType::NextTableOffset());
1501 1502
      GotoIf(TaggedIsSmi(next_table), &done_loop);

1503
      var_table = CAST(next_table);
1504 1505 1506
      var_index = SmiUntag(CAST(CallBuiltin(Builtin::kOrderedHashTableHealIndex,
                                            NoContextConstant(), current_table,
                                            SmiTag(current_index))));
1507
      Goto(&loop);
1508 1509 1510
    }
    BIND(&done_loop);

1511 1512
    // Update with the new {table} and {index}.
    update_in_transition(var_table.value(), var_index.value());
1513 1514 1515 1516
    Goto(&if_done);
  }

  BIND(&if_done);
1517
  return {var_table.value(), var_index.value()};
1518 1519
}

1520
template <typename IteratorType, typename TableType>
1521 1522
std::pair<TNode<TableType>, TNode<IntPtrT>>
CollectionsBuiltinsAssembler::TransitionAndUpdate(
1523
    const TNode<IteratorType> iterator) {
1524
  return Transition<TableType>(
1525
      CAST(LoadObjectField(iterator, IteratorType::kTableOffset)),
1526
      LoadAndUntagObjectField(iterator, IteratorType::kIndexOffset),
1527 1528
      [this, iterator](const TNode<TableType> table,
                       const TNode<IntPtrT> index) {
1529 1530 1531 1532 1533 1534 1535
        // Update the {iterator} with the new state.
        StoreObjectField(iterator, IteratorType::kTableOffset, table);
        StoreObjectFieldNoWriteBarrier(iterator, IteratorType::kIndexOffset,
                                       SmiTag(index));
      });
}

1536
template <typename TableType>
1537 1538 1539 1540
std::tuple<TNode<Object>, TNode<IntPtrT>, TNode<IntPtrT>>
CollectionsBuiltinsAssembler::NextSkipHoles(TNode<TableType> table,
                                            TNode<IntPtrT> index,
                                            Label* if_end) {
1541
  // Compute the used capacity for the {table}.
1542
  TNode<IntPtrT> number_of_buckets =
1543
      LoadAndUntagObjectField(table, TableType::NumberOfBucketsOffset());
1544
  TNode<IntPtrT> number_of_elements =
1545 1546 1547
      LoadAndUntagObjectField(table, TableType::NumberOfElementsOffset());
  TNode<IntPtrT> number_of_deleted_elements = LoadAndUntagObjectField(
      table, TableType::NumberOfDeletedElementsOffset());
1548
  TNode<IntPtrT> used_capacity =
1549 1550
      IntPtrAdd(number_of_elements, number_of_deleted_elements);

1551 1552 1553
  TNode<Object> entry_key;
  TNode<IntPtrT> entry_start_position;
  TVARIABLE(IntPtrT, var_index, index);
1554 1555 1556 1557 1558 1559 1560 1561
  Label loop(this, &var_index), done_loop(this);
  Goto(&loop);
  BIND(&loop);
  {
    GotoIfNot(IntPtrLessThan(var_index.value(), used_capacity), if_end);
    entry_start_position = IntPtrAdd(
        IntPtrMul(var_index.value(), IntPtrConstant(TableType::kEntrySize)),
        number_of_buckets);
1562 1563 1564
    entry_key = UnsafeLoadFixedArrayElement(
        table, entry_start_position,
        TableType::HashTableStartIndex() * kTaggedSize);
1565
    Increment(&var_index);
1566 1567 1568 1569
    Branch(IsTheHole(entry_key), &loop, &done_loop);
  }

  BIND(&done_loop);
1570 1571
  return std::tuple<TNode<Object>, TNode<IntPtrT>, TNode<IntPtrT>>{
      entry_key, entry_start_position, var_index.value()};
1572 1573
}

1574
TF_BUILTIN(MapPrototypeGet, CollectionsBuiltinsAssembler) {
1575 1576 1577
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto key = Parameter<Object>(Descriptor::kKey);
  const auto context = Parameter<Context>(Descriptor::kContext);
1578 1579 1580

  ThrowIfNotInstanceType(context, receiver, JS_MAP_TYPE, "Map.prototype.get");

1581
  const TNode<Object> table =
1582
      LoadObjectField<Object>(CAST(receiver), JSMap::kTableOffset);
1583 1584
  TNode<Smi> index =
      CAST(CallBuiltin(Builtin::kFindOrderedHashMapEntry, context, table, key));
1585

1586 1587 1588
  Label if_found(this), if_not_found(this);
  Branch(SmiGreaterThanOrEqual(index, SmiConstant(0)), &if_found,
         &if_not_found);
1589

1590
  BIND(&if_found);
1591
  Return(LoadFixedArrayElement(
1592
      CAST(table), SmiUntag(index),
1593
      (OrderedHashMap::HashTableStartIndex() + OrderedHashMap::kValueOffset) *
1594
          kTaggedSize));
1595

1596
  BIND(&if_not_found);
1597
  Return(UndefinedConstant());
1598
}
1599

1600
TF_BUILTIN(MapPrototypeHas, CollectionsBuiltinsAssembler) {
1601 1602 1603
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto key = Parameter<Object>(Descriptor::kKey);
  const auto context = Parameter<Context>(Descriptor::kContext);
1604 1605 1606

  ThrowIfNotInstanceType(context, receiver, JS_MAP_TYPE, "Map.prototype.has");

1607
  const TNode<Object> table =
1608
      LoadObjectField(CAST(receiver), JSMap::kTableOffset);
1609 1610
  TNode<Smi> index =
      CAST(CallBuiltin(Builtin::kFindOrderedHashMapEntry, context, table, key));
1611

1612 1613 1614
  Label if_found(this), if_not_found(this);
  Branch(SmiGreaterThanOrEqual(index, SmiConstant(0)), &if_found,
         &if_not_found);
1615

1616
  BIND(&if_found);
1617 1618
  Return(TrueConstant());

1619
  BIND(&if_not_found);
1620
  Return(FalseConstant());
1621 1622
}

1623 1624 1625
const TNode<Object> CollectionsBuiltinsAssembler::NormalizeNumberKey(
    const TNode<Object> key) {
  TVARIABLE(Object, result, key);
1626 1627 1628
  Label done(this);

  GotoIf(TaggedIsSmi(key), &done);
1629
  GotoIfNot(IsHeapNumber(CAST(key)), &done);
1630
  const TNode<Float64T> number = LoadHeapNumberValue(CAST(key));
1631 1632 1633
  GotoIfNot(Float64Equal(number, Float64Constant(0.0)), &done);
  // We know the value is zero, so we take the key to be Smi 0.
  // Another option would be to normalize to Smi here.
1634
  result = SmiConstant(0);
1635 1636 1637 1638 1639 1640
  Goto(&done);

  BIND(&done);
  return result.value();
}

1641
TF_BUILTIN(MapPrototypeSet, CollectionsBuiltinsAssembler) {
1642 1643 1644 1645
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  auto key = Parameter<Object>(Descriptor::kKey);
  const auto value = Parameter<Object>(Descriptor::kValue);
  const auto context = Parameter<Context>(Descriptor::kContext);
1646 1647 1648 1649 1650

  ThrowIfNotInstanceType(context, receiver, JS_MAP_TYPE, "Map.prototype.set");

  key = NormalizeNumberKey(key);

1651
  const TNode<OrderedHashMap> table =
1652
      LoadObjectField<OrderedHashMap>(CAST(receiver), JSMap::kTableOffset);
1653

1654
  TVARIABLE(IntPtrT, entry_start_position_or_hash, IntPtrConstant(0));
1655 1656
  Label entry_found(this), not_found(this);

1657 1658
  TryLookupOrderedHashTableIndex<OrderedHashMap>(
      table, key, &entry_start_position_or_hash, &entry_found, &not_found);
1659 1660 1661 1662 1663

  BIND(&entry_found);
  // If we found the entry, we just store the value there.
  StoreFixedArrayElement(table, entry_start_position_or_hash.value(), value,
                         UPDATE_WRITE_BARRIER,
1664 1665
                         kTaggedSize * (OrderedHashMap::HashTableStartIndex() +
                                        OrderedHashMap::kValueOffset));
1666 1667 1668 1669 1670 1671
  Return(receiver);

  Label no_hash(this), add_entry(this), store_new_entry(this);
  BIND(&not_found);
  {
    // If we have a hash code, we can start adding the new entry.
1672 1673
    GotoIf(IntPtrGreaterThan(entry_start_position_or_hash.value(),
                             IntPtrConstant(0)),
1674 1675 1676
           &add_entry);

    // Otherwise, go to runtime to compute the hash code.
1677
    entry_start_position_or_hash = SmiUntag(CallGetOrCreateHashRaw(CAST(key)));
1678 1679 1680 1681
    Goto(&add_entry);
  }

  BIND(&add_entry);
1682 1683
  TVARIABLE(IntPtrT, number_of_buckets);
  TVARIABLE(IntPtrT, occupancy);
1684
  TVARIABLE(OrderedHashMap, table_var, table);
1685 1686
  {
    // Check we have enough space for the entry.
1687 1688
    number_of_buckets = SmiUntag(CAST(UnsafeLoadFixedArrayElement(
        table, OrderedHashMap::NumberOfBucketsIndex())));
1689

1690
    static_assert(OrderedHashMap::kLoadFactor == 2);
1691 1692
    const TNode<WordT> capacity = WordShl(number_of_buckets.value(), 1);
    const TNode<IntPtrT> number_of_elements = SmiUntag(
1693
        CAST(LoadObjectField(table, OrderedHashMap::NumberOfElementsOffset())));
1694
    const TNode<IntPtrT> number_of_deleted = SmiUntag(CAST(LoadObjectField(
1695
        table, OrderedHashMap::NumberOfDeletedElementsOffset())));
1696
    occupancy = IntPtrAdd(number_of_elements, number_of_deleted);
1697 1698 1699 1700 1701
    GotoIf(IntPtrLessThan(occupancy.value(), capacity), &store_new_entry);

    // We do not have enough space, grow the table and reload the relevant
    // fields.
    CallRuntime(Runtime::kMapGrow, context, receiver);
1702 1703
    table_var =
        LoadObjectField<OrderedHashMap>(CAST(receiver), JSMap::kTableOffset);
1704 1705
    number_of_buckets = SmiUntag(CAST(UnsafeLoadFixedArrayElement(
        table_var.value(), OrderedHashMap::NumberOfBucketsIndex())));
1706
    const TNode<IntPtrT> new_number_of_elements = SmiUntag(CAST(LoadObjectField(
1707
        table_var.value(), OrderedHashMap::NumberOfElementsOffset())));
1708
    const TNode<IntPtrT> new_number_of_deleted = SmiUntag(CAST(LoadObjectField(
1709
        table_var.value(), OrderedHashMap::NumberOfDeletedElementsOffset())));
1710
    occupancy = IntPtrAdd(new_number_of_elements, new_number_of_deleted);
1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721
    Goto(&store_new_entry);
  }
  BIND(&store_new_entry);
  // Store the key, value and connect the element to the bucket chain.
  StoreOrderedHashMapNewEntry(table_var.value(), key, value,
                              entry_start_position_or_hash.value(),
                              number_of_buckets.value(), occupancy.value());
  Return(receiver);
}

void CollectionsBuiltinsAssembler::StoreOrderedHashMapNewEntry(
1722
    const TNode<OrderedHashMap> table, const TNode<Object> key,
1723 1724
    const TNode<Object> value, const TNode<IntPtrT> hash,
    const TNode<IntPtrT> number_of_buckets, const TNode<IntPtrT> occupancy) {
1725
  const TNode<IntPtrT> bucket =
1726
      WordAnd(hash, IntPtrSub(number_of_buckets, IntPtrConstant(1)));
1727 1728
  TNode<Smi> bucket_entry = CAST(UnsafeLoadFixedArrayElement(
      table, bucket, OrderedHashMap::HashTableStartIndex() * kTaggedSize));
1729 1730

  // Store the entry elements.
1731
  const TNode<IntPtrT> entry_start = IntPtrAdd(
1732 1733
      IntPtrMul(occupancy, IntPtrConstant(OrderedHashMap::kEntrySize)),
      number_of_buckets);
1734 1735 1736 1737 1738 1739 1740 1741
  UnsafeStoreFixedArrayElement(
      table, entry_start, key, UPDATE_WRITE_BARRIER,
      kTaggedSize * OrderedHashMap::HashTableStartIndex());
  UnsafeStoreFixedArrayElement(
      table, entry_start, value, UPDATE_WRITE_BARRIER,
      kTaggedSize * (OrderedHashMap::HashTableStartIndex() +
                     OrderedHashMap::kValueOffset));
  UnsafeStoreFixedArrayElement(
1742
      table, entry_start, bucket_entry,
1743 1744
      kTaggedSize * (OrderedHashMap::HashTableStartIndex() +
                     OrderedHashMap::kChainOffset));
1745 1746

  // Update the bucket head.
1747
  UnsafeStoreFixedArrayElement(
1748
      table, bucket, SmiTag(occupancy),
1749
      OrderedHashMap::HashTableStartIndex() * kTaggedSize);
1750 1751

  // Bump the elements count.
1752
  const TNode<Smi> number_of_elements =
1753 1754 1755
      CAST(LoadObjectField(table, OrderedHashMap::NumberOfElementsOffset()));
  StoreObjectFieldNoWriteBarrier(table,
                                 OrderedHashMap::NumberOfElementsOffset(),
1756 1757 1758
                                 SmiAdd(number_of_elements, SmiConstant(1)));
}

1759
TF_BUILTIN(MapPrototypeDelete, CollectionsBuiltinsAssembler) {
1760 1761 1762
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto key = Parameter<Object>(Descriptor::kKey);
  const auto context = Parameter<Context>(Descriptor::kContext);
1763 1764 1765 1766

  ThrowIfNotInstanceType(context, receiver, JS_MAP_TYPE,
                         "Map.prototype.delete");

1767 1768 1769
  // This check breaks a known exploitation technique. See crbug.com/1263462
  CSA_CHECK(this, TaggedNotEqual(key, TheHoleConstant()));

1770
  const TNode<OrderedHashMap> table =
1771
      LoadObjectField<OrderedHashMap>(CAST(receiver), JSMap::kTableOffset);
1772

1773
  TVARIABLE(IntPtrT, entry_start_position_or_hash, IntPtrConstant(0));
1774 1775
  Label entry_found(this), not_found(this);

1776 1777
  TryLookupOrderedHashTableIndex<OrderedHashMap>(
      table, key, &entry_start_position_or_hash, &entry_found, &not_found);
1778 1779 1780 1781 1782 1783 1784 1785

  BIND(&not_found);
  Return(FalseConstant());

  BIND(&entry_found);
  // If we found the entry, mark the entry as deleted.
  StoreFixedArrayElement(table, entry_start_position_or_hash.value(),
                         TheHoleConstant(), UPDATE_WRITE_BARRIER,
1786
                         kTaggedSize * OrderedHashMap::HashTableStartIndex());
1787 1788
  StoreFixedArrayElement(table, entry_start_position_or_hash.value(),
                         TheHoleConstant(), UPDATE_WRITE_BARRIER,
1789 1790
                         kTaggedSize * (OrderedHashMap::HashTableStartIndex() +
                                        OrderedHashMap::kValueOffset));
1791 1792

  // Decrement the number of elements, increment the number of deleted elements.
1793
  const TNode<Smi> number_of_elements = SmiSub(
1794
      CAST(LoadObjectField(table, OrderedHashMap::NumberOfElementsOffset())),
1795
      SmiConstant(1));
1796 1797
  StoreObjectFieldNoWriteBarrier(
      table, OrderedHashMap::NumberOfElementsOffset(), number_of_elements);
1798
  const TNode<Smi> number_of_deleted =
1799
      SmiAdd(CAST(LoadObjectField(
1800
                 table, OrderedHashMap::NumberOfDeletedElementsOffset())),
1801
             SmiConstant(1));
1802
  StoreObjectFieldNoWriteBarrier(
1803 1804
      table, OrderedHashMap::NumberOfDeletedElementsOffset(),
      number_of_deleted);
1805

1806
  const TNode<Smi> number_of_buckets = CAST(
1807
      LoadFixedArrayElement(table, OrderedHashMap::NumberOfBucketsIndex()));
1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820

  // If there fewer elements than #buckets / 2, shrink the table.
  Label shrink(this);
  GotoIf(SmiLessThan(SmiAdd(number_of_elements, number_of_elements),
                     number_of_buckets),
         &shrink);
  Return(TrueConstant());

  BIND(&shrink);
  CallRuntime(Runtime::kMapShrink, context, receiver);
  Return(TrueConstant());
}

1821
TF_BUILTIN(SetPrototypeAdd, CollectionsBuiltinsAssembler) {
1822 1823 1824
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  auto key = Parameter<Object>(Descriptor::kKey);
  const auto context = Parameter<Context>(Descriptor::kContext);
1825 1826 1827 1828 1829

  ThrowIfNotInstanceType(context, receiver, JS_SET_TYPE, "Set.prototype.add");

  key = NormalizeNumberKey(key);

1830
  const TNode<OrderedHashSet> table =
1831
      LoadObjectField<OrderedHashSet>(CAST(receiver), JSMap::kTableOffset);
1832

1833
  TVARIABLE(IntPtrT, entry_start_position_or_hash, IntPtrConstant(0));
1834 1835
  Label entry_found(this), not_found(this);

1836 1837
  TryLookupOrderedHashTableIndex<OrderedHashSet>(
      table, key, &entry_start_position_or_hash, &entry_found, &not_found);
1838 1839 1840 1841 1842 1843 1844 1845 1846

  BIND(&entry_found);
  // The entry was found, there is nothing to do.
  Return(receiver);

  Label no_hash(this), add_entry(this), store_new_entry(this);
  BIND(&not_found);
  {
    // If we have a hash code, we can start adding the new entry.
1847 1848
    GotoIf(IntPtrGreaterThan(entry_start_position_or_hash.value(),
                             IntPtrConstant(0)),
1849 1850 1851
           &add_entry);

    // Otherwise, go to runtime to compute the hash code.
1852
    entry_start_position_or_hash = SmiUntag(CallGetOrCreateHashRaw(CAST(key)));
1853 1854 1855 1856
    Goto(&add_entry);
  }

  BIND(&add_entry);
1857 1858
  TVARIABLE(IntPtrT, number_of_buckets);
  TVARIABLE(IntPtrT, occupancy);
1859
  TVARIABLE(OrderedHashSet, table_var, table);
1860 1861
  {
    // Check we have enough space for the entry.
1862 1863
    number_of_buckets = SmiUntag(CAST(UnsafeLoadFixedArrayElement(
        table, OrderedHashSet::NumberOfBucketsIndex())));
1864

1865
    static_assert(OrderedHashSet::kLoadFactor == 2);
1866 1867
    const TNode<WordT> capacity = WordShl(number_of_buckets.value(), 1);
    const TNode<IntPtrT> number_of_elements = SmiUntag(
1868
        CAST(LoadObjectField(table, OrderedHashSet::NumberOfElementsOffset())));
1869
    const TNode<IntPtrT> number_of_deleted = SmiUntag(CAST(LoadObjectField(
1870
        table, OrderedHashSet::NumberOfDeletedElementsOffset())));
1871
    occupancy = IntPtrAdd(number_of_elements, number_of_deleted);
1872 1873 1874 1875 1876
    GotoIf(IntPtrLessThan(occupancy.value(), capacity), &store_new_entry);

    // We do not have enough space, grow the table and reload the relevant
    // fields.
    CallRuntime(Runtime::kSetGrow, context, receiver);
1877 1878
    table_var =
        LoadObjectField<OrderedHashSet>(CAST(receiver), JSMap::kTableOffset);
1879 1880
    number_of_buckets = SmiUntag(CAST(UnsafeLoadFixedArrayElement(
        table_var.value(), OrderedHashSet::NumberOfBucketsIndex())));
1881
    const TNode<IntPtrT> new_number_of_elements = SmiUntag(CAST(LoadObjectField(
1882
        table_var.value(), OrderedHashSet::NumberOfElementsOffset())));
1883
    const TNode<IntPtrT> new_number_of_deleted = SmiUntag(CAST(LoadObjectField(
1884
        table_var.value(), OrderedHashSet::NumberOfDeletedElementsOffset())));
1885
    occupancy = IntPtrAdd(new_number_of_elements, new_number_of_deleted);
1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896
    Goto(&store_new_entry);
  }
  BIND(&store_new_entry);
  // Store the key, value and connect the element to the bucket chain.
  StoreOrderedHashSetNewEntry(table_var.value(), key,
                              entry_start_position_or_hash.value(),
                              number_of_buckets.value(), occupancy.value());
  Return(receiver);
}

void CollectionsBuiltinsAssembler::StoreOrderedHashSetNewEntry(
1897
    const TNode<OrderedHashSet> table, const TNode<Object> key,
1898 1899
    const TNode<IntPtrT> hash, const TNode<IntPtrT> number_of_buckets,
    const TNode<IntPtrT> occupancy) {
1900
  const TNode<IntPtrT> bucket =
1901
      WordAnd(hash, IntPtrSub(number_of_buckets, IntPtrConstant(1)));
1902 1903
  TNode<Smi> bucket_entry = CAST(UnsafeLoadFixedArrayElement(
      table, bucket, OrderedHashSet::HashTableStartIndex() * kTaggedSize));
1904 1905

  // Store the entry elements.
1906
  const TNode<IntPtrT> entry_start = IntPtrAdd(
1907 1908
      IntPtrMul(occupancy, IntPtrConstant(OrderedHashSet::kEntrySize)),
      number_of_buckets);
1909 1910 1911 1912
  UnsafeStoreFixedArrayElement(
      table, entry_start, key, UPDATE_WRITE_BARRIER,
      kTaggedSize * OrderedHashSet::HashTableStartIndex());
  UnsafeStoreFixedArrayElement(
1913
      table, entry_start, bucket_entry,
1914 1915
      kTaggedSize * (OrderedHashSet::HashTableStartIndex() +
                     OrderedHashSet::kChainOffset));
1916 1917

  // Update the bucket head.
1918
  UnsafeStoreFixedArrayElement(
1919
      table, bucket, SmiTag(occupancy),
1920
      OrderedHashSet::HashTableStartIndex() * kTaggedSize);
1921 1922

  // Bump the elements count.
1923
  const TNode<Smi> number_of_elements =
1924 1925 1926
      CAST(LoadObjectField(table, OrderedHashSet::NumberOfElementsOffset()));
  StoreObjectFieldNoWriteBarrier(table,
                                 OrderedHashSet::NumberOfElementsOffset(),
1927 1928 1929
                                 SmiAdd(number_of_elements, SmiConstant(1)));
}

1930
TF_BUILTIN(SetPrototypeDelete, CollectionsBuiltinsAssembler) {
1931 1932 1933
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto key = Parameter<Object>(Descriptor::kKey);
  const auto context = Parameter<Context>(Descriptor::kContext);
1934 1935 1936 1937

  ThrowIfNotInstanceType(context, receiver, JS_SET_TYPE,
                         "Set.prototype.delete");

1938 1939 1940
  // This check breaks a known exploitation technique. See crbug.com/1263462
  CSA_CHECK(this, TaggedNotEqual(key, TheHoleConstant()));

1941
  const TNode<OrderedHashSet> table =
1942
      LoadObjectField<OrderedHashSet>(CAST(receiver), JSMap::kTableOffset);
1943

1944
  TVARIABLE(IntPtrT, entry_start_position_or_hash, IntPtrConstant(0));
1945 1946
  Label entry_found(this), not_found(this);

1947 1948
  TryLookupOrderedHashTableIndex<OrderedHashSet>(
      table, key, &entry_start_position_or_hash, &entry_found, &not_found);
1949 1950 1951 1952 1953 1954 1955 1956

  BIND(&not_found);
  Return(FalseConstant());

  BIND(&entry_found);
  // If we found the entry, mark the entry as deleted.
  StoreFixedArrayElement(table, entry_start_position_or_hash.value(),
                         TheHoleConstant(), UPDATE_WRITE_BARRIER,
1957
                         kTaggedSize * OrderedHashSet::HashTableStartIndex());
1958 1959

  // Decrement the number of elements, increment the number of deleted elements.
1960
  const TNode<Smi> number_of_elements = SmiSub(
1961
      CAST(LoadObjectField(table, OrderedHashSet::NumberOfElementsOffset())),
1962
      SmiConstant(1));
1963 1964
  StoreObjectFieldNoWriteBarrier(
      table, OrderedHashSet::NumberOfElementsOffset(), number_of_elements);
1965
  const TNode<Smi> number_of_deleted =
1966
      SmiAdd(CAST(LoadObjectField(
1967
                 table, OrderedHashSet::NumberOfDeletedElementsOffset())),
1968
             SmiConstant(1));
1969
  StoreObjectFieldNoWriteBarrier(
1970 1971
      table, OrderedHashSet::NumberOfDeletedElementsOffset(),
      number_of_deleted);
1972

1973
  const TNode<Smi> number_of_buckets = CAST(
1974
      LoadFixedArrayElement(table, OrderedHashSet::NumberOfBucketsIndex()));
1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987

  // If there fewer elements than #buckets / 2, shrink the table.
  Label shrink(this);
  GotoIf(SmiLessThan(SmiAdd(number_of_elements, number_of_elements),
                     number_of_buckets),
         &shrink);
  Return(TrueConstant());

  BIND(&shrink);
  CallRuntime(Runtime::kSetShrink, context, receiver);
  Return(TrueConstant());
}

1988
TF_BUILTIN(MapPrototypeEntries, CollectionsBuiltinsAssembler) {
1989 1990
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto context = Parameter<Context>(Descriptor::kContext);
1991 1992 1993
  ThrowIfNotInstanceType(context, receiver, JS_MAP_TYPE,
                         "Map.prototype.entries");
  Return(AllocateJSCollectionIterator<JSMapIterator>(
1994
      context, Context::MAP_KEY_VALUE_ITERATOR_MAP_INDEX, CAST(receiver)));
1995 1996
}

1997
TF_BUILTIN(MapPrototypeGetSize, CollectionsBuiltinsAssembler) {
1998 1999
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto context = Parameter<Context>(Descriptor::kContext);
2000 2001
  ThrowIfNotInstanceType(context, receiver, JS_MAP_TYPE,
                         "get Map.prototype.size");
2002
  const TNode<OrderedHashMap> table =
2003
      LoadObjectField<OrderedHashMap>(CAST(receiver), JSMap::kTableOffset);
2004
  Return(LoadObjectField(table, OrderedHashMap::NumberOfElementsOffset()));
2005 2006
}

2007 2008
TF_BUILTIN(MapPrototypeForEach, CollectionsBuiltinsAssembler) {
  const char* const kMethodName = "Map.prototype.forEach";
2009 2010
  auto argc = UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount);
  const auto context = Parameter<Context>(Descriptor::kContext);
2011
  CodeStubArguments args(this, argc);
2012 2013 2014
  const TNode<Object> receiver = args.GetReceiver();
  const TNode<Object> callback = args.GetOptionalArgumentValue(0);
  const TNode<Object> this_arg = args.GetOptionalArgumentValue(1);
2015 2016 2017 2018 2019 2020

  ThrowIfNotInstanceType(context, receiver, JS_MAP_TYPE, kMethodName);

  // Ensure that {callback} is actually callable.
  Label callback_not_callable(this, Label::kDeferred);
  GotoIf(TaggedIsSmi(callback), &callback_not_callable);
2021
  GotoIfNot(IsCallable(CAST(callback)), &callback_not_callable);
2022

2023 2024
  TVARIABLE(IntPtrT, var_index, IntPtrConstant(0));
  TVARIABLE(OrderedHashMap, var_table,
2025
            CAST(LoadObjectField(CAST(receiver), JSMap::kTableOffset)));
2026 2027 2028 2029 2030 2031
  Label loop(this, {&var_index, &var_table}), done_loop(this);
  Goto(&loop);
  BIND(&loop);
  {
    // Transition {table} and {index} if there was any modification to
    // the {receiver} while we're iterating.
2032 2033
    TNode<IntPtrT> index = var_index.value();
    TNode<OrderedHashMap> table = var_table.value();
2034 2035
    std::tie(table, index) = Transition<OrderedHashMap>(
        table, index, [](const TNode<OrderedHashMap>, const TNode<IntPtrT>) {});
2036 2037

    // Read the next entry from the {table}, skipping holes.
2038 2039
    TNode<Object> entry_key;
    TNode<IntPtrT> entry_start_position;
2040 2041 2042 2043
    std::tie(entry_key, entry_start_position, index) =
        NextSkipHoles<OrderedHashMap>(table, index, &done_loop);

    // Load the entry value as well.
2044
    TNode<Object> entry_value = LoadFixedArrayElement(
2045
        table, entry_start_position,
2046
        (OrderedHashMap::HashTableStartIndex() + OrderedHashMap::kValueOffset) *
2047
            kTaggedSize);
2048 2049 2050

    // Invoke the {callback} passing the {entry_key}, {entry_value} and the
    // {receiver}.
2051
    Call(context, callback, this_arg, entry_value, entry_key, receiver);
2052 2053

    // Continue with the next entry.
2054 2055
    var_index = index;
    var_table = table;
2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068
    Goto(&loop);
  }

  BIND(&done_loop);
  args.PopAndReturn(UndefinedConstant());

  BIND(&callback_not_callable);
  {
    CallRuntime(Runtime::kThrowCalledNonCallable, context, callback);
    Unreachable();
  }
}

2069
TF_BUILTIN(MapPrototypeKeys, CollectionsBuiltinsAssembler) {
2070 2071
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto context = Parameter<Context>(Descriptor::kContext);
2072 2073
  ThrowIfNotInstanceType(context, receiver, JS_MAP_TYPE, "Map.prototype.keys");
  Return(AllocateJSCollectionIterator<JSMapIterator>(
2074
      context, Context::MAP_KEY_ITERATOR_MAP_INDEX, CAST(receiver)));
2075 2076 2077
}

TF_BUILTIN(MapPrototypeValues, CollectionsBuiltinsAssembler) {
2078 2079
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto context = Parameter<Context>(Descriptor::kContext);
2080 2081 2082
  ThrowIfNotInstanceType(context, receiver, JS_MAP_TYPE,
                         "Map.prototype.values");
  Return(AllocateJSCollectionIterator<JSMapIterator>(
2083
      context, Context::MAP_VALUE_ITERATOR_MAP_INDEX, CAST(receiver)));
2084 2085 2086 2087
}

TF_BUILTIN(MapIteratorPrototypeNext, CollectionsBuiltinsAssembler) {
  const char* const kMethodName = "Map Iterator.prototype.next";
2088 2089
  const auto maybe_receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto context = Parameter<Context>(Descriptor::kContext);
2090

2091
  // Ensure that {maybe_receiver} is actually a JSMapIterator.
2092
  Label if_receiver_valid(this), if_receiver_invalid(this, Label::kDeferred);
2093
  GotoIf(TaggedIsSmi(maybe_receiver), &if_receiver_invalid);
2094
  const TNode<Uint16T> receiver_instance_type =
2095
      LoadInstanceType(CAST(maybe_receiver));
2096 2097 2098 2099 2100 2101 2102 2103
  GotoIf(
      InstanceTypeEqual(receiver_instance_type, JS_MAP_KEY_VALUE_ITERATOR_TYPE),
      &if_receiver_valid);
  GotoIf(InstanceTypeEqual(receiver_instance_type, JS_MAP_KEY_ITERATOR_TYPE),
         &if_receiver_valid);
  Branch(InstanceTypeEqual(receiver_instance_type, JS_MAP_VALUE_ITERATOR_TYPE),
         &if_receiver_valid, &if_receiver_invalid);
  BIND(&if_receiver_invalid);
2104
  ThrowTypeError(context, MessageTemplate::kIncompatibleMethodReceiver,
2105
                 StringConstant(kMethodName), maybe_receiver);
2106
  BIND(&if_receiver_valid);
2107
  TNode<JSMapIterator> receiver = CAST(maybe_receiver);
2108 2109

  // Check if the {receiver} is exhausted.
2110 2111
  TVARIABLE(Oddball, var_done, TrueConstant());
  TVARIABLE(Object, var_value, UndefinedConstant());
2112 2113
  Label return_value(this, {&var_done, &var_value}), return_entry(this),
      return_end(this, Label::kDeferred);
2114 2115

  // Transition the {receiver} table if necessary.
2116 2117
  TNode<OrderedHashMap> table;
  TNode<IntPtrT> index;
2118
  std::tie(table, index) =
2119
      TransitionAndUpdate<JSMapIterator, OrderedHashMap>(receiver);
2120 2121

  // Read the next entry from the {table}, skipping holes.
2122 2123
  TNode<Object> entry_key;
  TNode<IntPtrT> entry_start_position;
2124 2125 2126 2127
  std::tie(entry_key, entry_start_position, index) =
      NextSkipHoles<OrderedHashMap>(table, index, &return_end);
  StoreObjectFieldNoWriteBarrier(receiver, JSMapIterator::kIndexOffset,
                                 SmiTag(index));
2128 2129
  var_value = entry_key;
  var_done = FalseConstant();
2130 2131 2132 2133

  // Check how to return the {key} (depending on {receiver} type).
  GotoIf(InstanceTypeEqual(receiver_instance_type, JS_MAP_KEY_ITERATOR_TYPE),
         &return_value);
2134
  var_value = LoadFixedArrayElement(
2135
      table, entry_start_position,
2136
      (OrderedHashMap::HashTableStartIndex() + OrderedHashMap::kValueOffset) *
2137
          kTaggedSize);
2138 2139 2140 2141 2142
  Branch(InstanceTypeEqual(receiver_instance_type, JS_MAP_VALUE_ITERATOR_TYPE),
         &return_value, &return_entry);

  BIND(&return_entry);
  {
2143
    TNode<JSObject> result =
2144 2145 2146 2147 2148 2149
        AllocateJSIteratorResultForEntry(context, entry_key, var_value.value());
    Return(result);
  }

  BIND(&return_value);
  {
2150
    TNode<JSObject> result =
2151 2152 2153 2154 2155 2156 2157
        AllocateJSIteratorResult(context, var_value.value(), var_done.value());
    Return(result);
  }

  BIND(&return_end);
  {
    StoreObjectFieldRoot(receiver, JSMapIterator::kTableOffset,
2158
                         RootIndex::kEmptyOrderedHashMap);
2159 2160 2161 2162
    Goto(&return_value);
  }
}

2163
TF_BUILTIN(SetPrototypeHas, CollectionsBuiltinsAssembler) {
2164 2165 2166
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto key = Parameter<Object>(Descriptor::kKey);
  const auto context = Parameter<Context>(Descriptor::kContext);
2167 2168 2169

  ThrowIfNotInstanceType(context, receiver, JS_SET_TYPE, "Set.prototype.has");

2170
  const TNode<Object> table =
2171
      LoadObjectField(CAST(receiver), JSMap::kTableOffset);
snek's avatar
snek committed
2172 2173
  TNode<Smi> index =
      CAST(CallBuiltin(Builtin::kFindOrderedHashSetEntry, context, table, key));
2174

snek's avatar
snek committed
2175 2176 2177
  Label if_found(this), if_not_found(this);
  Branch(SmiGreaterThanOrEqual(index, SmiConstant(0)), &if_found,
         &if_not_found);
2178

snek's avatar
snek committed
2179
  BIND(&if_found);
2180 2181
  Return(TrueConstant());

snek's avatar
snek committed
2182
  BIND(&if_not_found);
2183
  Return(FalseConstant());
2184 2185
}

2186
TF_BUILTIN(SetPrototypeEntries, CollectionsBuiltinsAssembler) {
2187 2188
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto context = Parameter<Context>(Descriptor::kContext);
2189 2190 2191
  ThrowIfNotInstanceType(context, receiver, JS_SET_TYPE,
                         "Set.prototype.entries");
  Return(AllocateJSCollectionIterator<JSSetIterator>(
2192
      context, Context::SET_KEY_VALUE_ITERATOR_MAP_INDEX, CAST(receiver)));
2193 2194
}

2195
TF_BUILTIN(SetPrototypeGetSize, CollectionsBuiltinsAssembler) {
2196 2197
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto context = Parameter<Context>(Descriptor::kContext);
2198 2199
  ThrowIfNotInstanceType(context, receiver, JS_SET_TYPE,
                         "get Set.prototype.size");
2200
  const TNode<OrderedHashSet> table =
2201
      LoadObjectField<OrderedHashSet>(CAST(receiver), JSSet::kTableOffset);
2202
  Return(LoadObjectField(table, OrderedHashSet::NumberOfElementsOffset()));
2203 2204
}

2205 2206
TF_BUILTIN(SetPrototypeForEach, CollectionsBuiltinsAssembler) {
  const char* const kMethodName = "Set.prototype.forEach";
2207 2208
  auto argc = UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount);
  const auto context = Parameter<Context>(Descriptor::kContext);
2209
  CodeStubArguments args(this, argc);
2210 2211 2212
  const TNode<Object> receiver = args.GetReceiver();
  const TNode<Object> callback = args.GetOptionalArgumentValue(0);
  const TNode<Object> this_arg = args.GetOptionalArgumentValue(1);
2213 2214 2215 2216 2217 2218

  ThrowIfNotInstanceType(context, receiver, JS_SET_TYPE, kMethodName);

  // Ensure that {callback} is actually callable.
  Label callback_not_callable(this, Label::kDeferred);
  GotoIf(TaggedIsSmi(callback), &callback_not_callable);
2219
  GotoIfNot(IsCallable(CAST(callback)), &callback_not_callable);
2220

2221 2222
  TVARIABLE(IntPtrT, var_index, IntPtrConstant(0));
  TVARIABLE(OrderedHashSet, var_table,
2223
            CAST(LoadObjectField(CAST(receiver), JSSet::kTableOffset)));
2224 2225 2226 2227 2228 2229
  Label loop(this, {&var_index, &var_table}), done_loop(this);
  Goto(&loop);
  BIND(&loop);
  {
    // Transition {table} and {index} if there was any modification to
    // the {receiver} while we're iterating.
2230 2231
    TNode<IntPtrT> index = var_index.value();
    TNode<OrderedHashSet> table = var_table.value();
2232 2233
    std::tie(table, index) = Transition<OrderedHashSet>(
        table, index, [](const TNode<OrderedHashSet>, const TNode<IntPtrT>) {});
2234 2235

    // Read the next entry from the {table}, skipping holes.
2236 2237
    TNode<Object> entry_key;
    TNode<IntPtrT> entry_start_position;
2238 2239 2240 2241
    std::tie(entry_key, entry_start_position, index) =
        NextSkipHoles<OrderedHashSet>(table, index, &done_loop);

    // Invoke the {callback} passing the {entry_key} (twice) and the {receiver}.
2242
    Call(context, callback, this_arg, entry_key, entry_key, receiver);
2243 2244

    // Continue with the next entry.
2245 2246
    var_index = index;
    var_table = table;
2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259
    Goto(&loop);
  }

  BIND(&done_loop);
  args.PopAndReturn(UndefinedConstant());

  BIND(&callback_not_callable);
  {
    CallRuntime(Runtime::kThrowCalledNonCallable, context, callback);
    Unreachable();
  }
}

2260
TF_BUILTIN(SetPrototypeValues, CollectionsBuiltinsAssembler) {
2261 2262
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto context = Parameter<Context>(Descriptor::kContext);
2263 2264 2265
  ThrowIfNotInstanceType(context, receiver, JS_SET_TYPE,
                         "Set.prototype.values");
  Return(AllocateJSCollectionIterator<JSSetIterator>(
2266
      context, Context::SET_VALUE_ITERATOR_MAP_INDEX, CAST(receiver)));
2267 2268 2269 2270
}

TF_BUILTIN(SetIteratorPrototypeNext, CollectionsBuiltinsAssembler) {
  const char* const kMethodName = "Set Iterator.prototype.next";
2271 2272
  const auto maybe_receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto context = Parameter<Context>(Descriptor::kContext);
2273

2274
  // Ensure that {maybe_receiver} is actually a JSSetIterator.
2275
  Label if_receiver_valid(this), if_receiver_invalid(this, Label::kDeferred);
2276
  GotoIf(TaggedIsSmi(maybe_receiver), &if_receiver_invalid);
2277
  const TNode<Uint16T> receiver_instance_type =
2278
      LoadInstanceType(CAST(maybe_receiver));
2279 2280 2281 2282 2283 2284
  GotoIf(InstanceTypeEqual(receiver_instance_type, JS_SET_VALUE_ITERATOR_TYPE),
         &if_receiver_valid);
  Branch(
      InstanceTypeEqual(receiver_instance_type, JS_SET_KEY_VALUE_ITERATOR_TYPE),
      &if_receiver_valid, &if_receiver_invalid);
  BIND(&if_receiver_invalid);
2285
  ThrowTypeError(context, MessageTemplate::kIncompatibleMethodReceiver,
2286
                 StringConstant(kMethodName), maybe_receiver);
2287 2288
  BIND(&if_receiver_valid);

2289 2290
  TNode<JSSetIterator> receiver = CAST(maybe_receiver);

2291
  // Check if the {receiver} is exhausted.
2292 2293
  TVARIABLE(Oddball, var_done, TrueConstant());
  TVARIABLE(Object, var_value, UndefinedConstant());
2294 2295
  Label return_value(this, {&var_done, &var_value}), return_entry(this),
      return_end(this, Label::kDeferred);
2296 2297

  // Transition the {receiver} table if necessary.
2298 2299
  TNode<OrderedHashSet> table;
  TNode<IntPtrT> index;
2300
  std::tie(table, index) =
2301
      TransitionAndUpdate<JSSetIterator, OrderedHashSet>(receiver);
2302 2303

  // Read the next entry from the {table}, skipping holes.
2304 2305
  TNode<Object> entry_key;
  TNode<IntPtrT> entry_start_position;
2306 2307 2308 2309
  std::tie(entry_key, entry_start_position, index) =
      NextSkipHoles<OrderedHashSet>(table, index, &return_end);
  StoreObjectFieldNoWriteBarrier(receiver, JSSetIterator::kIndexOffset,
                                 SmiTag(index));
2310 2311
  var_value = entry_key;
  var_done = FalseConstant();
2312 2313 2314 2315 2316 2317 2318

  // Check how to return the {key} (depending on {receiver} type).
  Branch(InstanceTypeEqual(receiver_instance_type, JS_SET_VALUE_ITERATOR_TYPE),
         &return_value, &return_entry);

  BIND(&return_entry);
  {
2319 2320
    TNode<JSObject> result = AllocateJSIteratorResultForEntry(
        context, var_value.value(), var_value.value());
2321 2322 2323 2324 2325
    Return(result);
  }

  BIND(&return_value);
  {
2326
    TNode<JSObject> result =
2327 2328 2329 2330 2331 2332 2333
        AllocateJSIteratorResult(context, var_value.value(), var_done.value());
    Return(result);
  }

  BIND(&return_end);
  {
    StoreObjectFieldRoot(receiver, JSSetIterator::kTableOffset,
2334
                         RootIndex::kEmptyOrderedHashSet);
2335 2336 2337 2338
    Goto(&return_value);
  }
}

2339 2340
template <typename CollectionType>
void CollectionsBuiltinsAssembler::TryLookupOrderedHashTableIndex(
2341 2342
    const TNode<CollectionType> table, const TNode<Object> key,
    TVariable<IntPtrT>* result, Label* if_entry_found, Label* if_not_found) {
2343 2344
  Label if_key_smi(this), if_key_string(this), if_key_heap_number(this),
      if_key_bigint(this);
2345 2346

  GotoIf(TaggedIsSmi(key), &if_key_smi);
2347

2348
  TNode<Map> key_map = LoadMap(CAST(key));
2349
  TNode<Uint16T> key_instance_type = LoadMapInstanceType(key_map);
2350 2351 2352 2353

  GotoIf(IsStringInstanceType(key_instance_type), &if_key_string);
  GotoIf(IsHeapNumberMap(key_map), &if_key_heap_number);
  GotoIf(IsBigIntInstanceType(key_instance_type), &if_key_bigint);
2354

2355
  FindOrderedHashTableEntryForOtherKey<CollectionType>(
2356
      table, CAST(key), result, if_entry_found, if_not_found);
2357 2358 2359

  BIND(&if_key_smi);
  {
2360
    FindOrderedHashTableEntryForSmiKey<CollectionType>(
2361
        table, CAST(key), result, if_entry_found, if_not_found);
2362 2363 2364 2365
  }

  BIND(&if_key_string);
  {
2366
    FindOrderedHashTableEntryForStringKey<CollectionType>(
2367
        table, CAST(key), result, if_entry_found, if_not_found);
2368 2369 2370 2371
  }

  BIND(&if_key_heap_number);
  {
2372
    FindOrderedHashTableEntryForHeapNumberKey<CollectionType>(
2373
        table, CAST(key), result, if_entry_found, if_not_found);
2374
  }
2375 2376 2377 2378

  BIND(&if_key_bigint);
  {
    FindOrderedHashTableEntryForBigIntKey<CollectionType>(
2379
        table, CAST(key), result, if_entry_found, if_not_found);
2380
  }
2381 2382
}

2383
TF_BUILTIN(FindOrderedHashMapEntry, CollectionsBuiltinsAssembler) {
2384 2385
  const auto table = Parameter<OrderedHashMap>(Descriptor::kTable);
  const auto key = Parameter<Object>(Descriptor::kKey);
2386

2387
  TVARIABLE(IntPtrT, entry_start_position, IntPtrConstant(0));
2388 2389
  Label entry_found(this), not_found(this);

2390
  TryLookupOrderedHashTableIndex<OrderedHashMap>(
2391
      table, key, &entry_start_position, &entry_found, &not_found);
2392 2393

  BIND(&entry_found);
2394
  Return(SmiTag(entry_start_position.value()));
2395 2396

  BIND(&not_found);
2397
  Return(SmiConstant(-1));
2398 2399
}

snek's avatar
snek committed
2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416
TF_BUILTIN(FindOrderedHashSetEntry, CollectionsBuiltinsAssembler) {
  const auto table = Parameter<OrderedHashSet>(Descriptor::kTable);
  const auto key = Parameter<Object>(Descriptor::kKey);

  TVARIABLE(IntPtrT, entry_start_position, IntPtrConstant(0));
  Label entry_found(this), not_found(this);

  TryLookupOrderedHashTableIndex<OrderedHashSet>(
      table, key, &entry_start_position, &entry_found, &not_found);

  BIND(&entry_found);
  Return(SmiTag(entry_start_position.value()));

  BIND(&not_found);
  Return(SmiConstant(-1));
}

2417
class WeakCollectionsBuiltinsAssembler : public BaseCollectionsAssembler {
2418 2419
 public:
  explicit WeakCollectionsBuiltinsAssembler(compiler::CodeAssemblerState* state)
2420
      : BaseCollectionsAssembler(state) {}
2421

2422
 protected:
2423
  void AddEntry(TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
2424 2425 2426
                TNode<Object> key, TNode<Object> value,
                TNode<IntPtrT> number_of_elements);

2427 2428
  TNode<HeapObject> AllocateTable(Variant variant,
                                  TNode<IntPtrT> at_least_space_for) override;
2429

2430 2431 2432 2433
  // Generates and sets the identity for a JSRececiver.
  TNode<Smi> CreateIdentityHash(TNode<Object> receiver);
  TNode<IntPtrT> EntryMask(TNode<IntPtrT> capacity);

2434
  // Builds code that finds the EphemeronHashTable entry for a {key} using the
2435 2436
  // comparison code generated by {key_compare}. The key index is returned if
  // the {key} is found.
2437 2438
  using KeyComparator =
      std::function<void(TNode<Object> entry_key, Label* if_same)>;
2439
  TNode<IntPtrT> FindKeyIndex(TNode<HeapObject> table, TNode<IntPtrT> key_hash,
2440 2441 2442
                              TNode<IntPtrT> entry_mask,
                              const KeyComparator& key_compare);

2443 2444
  // Builds code that finds an EphemeronHashTable entry available for a new
  // entry.
2445
  TNode<IntPtrT> FindKeyIndexForInsertion(TNode<HeapObject> table,
2446 2447 2448
                                          TNode<IntPtrT> key_hash,
                                          TNode<IntPtrT> entry_mask);

2449
  // Builds code that finds the EphemeronHashTable entry with key that matches
2450 2451
  // {key} and returns the entry's key index. If {key} cannot be found, jumps to
  // {if_not_found}.
2452
  TNode<IntPtrT> FindKeyIndexForKey(TNode<HeapObject> table, TNode<Object> key,
2453 2454 2455 2456 2457 2458 2459 2460 2461
                                    TNode<IntPtrT> hash,
                                    TNode<IntPtrT> entry_mask,
                                    Label* if_not_found);

  TNode<Word32T> InsufficientCapacityToAdd(TNode<IntPtrT> capacity,
                                           TNode<IntPtrT> number_of_elements,
                                           TNode<IntPtrT> number_of_deleted);
  TNode<IntPtrT> KeyIndexFromEntry(TNode<IntPtrT> entry);

2462 2463 2464 2465 2466 2467
  TNode<IntPtrT> LoadNumberOfElements(TNode<EphemeronHashTable> table,
                                      int offset);
  TNode<IntPtrT> LoadNumberOfDeleted(TNode<EphemeronHashTable> table,
                                     int offset = 0);
  TNode<EphemeronHashTable> LoadTable(TNode<JSWeakCollection> collection);
  TNode<IntPtrT> LoadTableCapacity(TNode<EphemeronHashTable> table);
2468

2469
  void RemoveEntry(TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
2470 2471 2472 2473 2474 2475 2476
                   TNode<IntPtrT> number_of_elements);
  TNode<BoolT> ShouldRehash(TNode<IntPtrT> number_of_elements,
                            TNode<IntPtrT> number_of_deleted);
  TNode<Word32T> ShouldShrink(TNode<IntPtrT> capacity,
                              TNode<IntPtrT> number_of_elements);
  TNode<IntPtrT> ValueIndexFromKeyIndex(TNode<IntPtrT> key_index);
};
2477

2478
void WeakCollectionsBuiltinsAssembler::AddEntry(
2479 2480
    TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
    TNode<Object> key, TNode<Object> value, TNode<IntPtrT> number_of_elements) {
2481
  // See EphemeronHashTable::AddEntry().
2482
  TNode<IntPtrT> value_index = ValueIndexFromKeyIndex(key_index);
2483 2484
  UnsafeStoreFixedArrayElement(table, key_index, key,
                               UPDATE_EPHEMERON_KEY_WRITE_BARRIER);
2485
  UnsafeStoreFixedArrayElement(table, value_index, value);
2486 2487

  // See HashTableBase::ElementAdded().
2488 2489 2490
  UnsafeStoreFixedArrayElement(table,
                               EphemeronHashTable::kNumberOfElementsIndex,
                               SmiFromIntPtr(number_of_elements));
2491
}
2492

2493 2494
TNode<HeapObject> WeakCollectionsBuiltinsAssembler::AllocateTable(
    Variant variant, TNode<IntPtrT> at_least_space_for) {
2495
  // See HashTable::New().
2496
  CSA_DCHECK(this,
2497 2498 2499 2500 2501
             IntPtrLessThanOrEqual(IntPtrConstant(0), at_least_space_for));
  TNode<IntPtrT> capacity = HashTableComputeCapacity(at_least_space_for);

  // See HashTable::NewInternal().
  TNode<IntPtrT> length = KeyIndexFromEntry(capacity);
2502 2503
  TNode<FixedArray> table = CAST(AllocateFixedArray(
      HOLEY_ELEMENTS, length, AllocationFlag::kAllowLargeObjectAllocation));
2504

2505
  TNode<Map> map =
2506
      HeapConstant(EphemeronHashTable::GetMap(ReadOnlyRoots(isolate())));
2507
  StoreMapNoWriteBarrier(table, map);
2508
  StoreFixedArrayElement(table, EphemeronHashTable::kNumberOfElementsIndex,
2509
                         SmiConstant(0), SKIP_WRITE_BARRIER);
2510 2511
  StoreFixedArrayElement(table,
                         EphemeronHashTable::kNumberOfDeletedElementsIndex,
2512
                         SmiConstant(0), SKIP_WRITE_BARRIER);
2513
  StoreFixedArrayElement(table, EphemeronHashTable::kCapacityIndex,
2514
                         SmiFromIntPtr(capacity), SKIP_WRITE_BARRIER);
2515 2516 2517

  TNode<IntPtrT> start = KeyIndexFromEntry(IntPtrConstant(0));
  FillFixedArrayWithValue(HOLEY_ELEMENTS, table, start, length,
2518
                          RootIndex::kUndefinedValue);
2519 2520 2521
  return table;
}

2522 2523
TNode<Smi> WeakCollectionsBuiltinsAssembler::CreateIdentityHash(
    TNode<Object> key) {
2524 2525
  TNode<ExternalReference> function_addr =
      ExternalConstant(ExternalReference::jsreceiver_create_identity_hash());
2526 2527
  TNode<ExternalReference> isolate_ptr =
      ExternalConstant(ExternalReference::isolate_address(isolate()));
2528

2529 2530
  MachineType type_ptr = MachineType::Pointer();
  MachineType type_tagged = MachineType::AnyTagged();
2531

2532 2533 2534
  return CAST(CallCFunction(function_addr, type_tagged,
                            std::make_pair(type_ptr, isolate_ptr),
                            std::make_pair(type_tagged, key)));
2535 2536 2537 2538 2539 2540 2541 2542
}

TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::EntryMask(
    TNode<IntPtrT> capacity) {
  return IntPtrSub(capacity, IntPtrConstant(1));
}

TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::FindKeyIndex(
2543
    TNode<HeapObject> table, TNode<IntPtrT> key_hash, TNode<IntPtrT> entry_mask,
2544
    const KeyComparator& key_compare) {
2545
  // See HashTable::FirstProbe().
2546 2547
  TVARIABLE(IntPtrT, var_entry, WordAnd(key_hash, entry_mask));
  TVARIABLE(IntPtrT, var_count, IntPtrConstant(0));
2548

2549
  Label loop(this, {&var_count, &var_entry}), if_found(this);
2550 2551
  Goto(&loop);
  BIND(&loop);
2552
  TNode<IntPtrT> key_index;
2553
  {
2554
    key_index = KeyIndexFromEntry(var_entry.value());
2555 2556
    TNode<Object> entry_key =
        UnsafeLoadFixedArrayElement(CAST(table), key_index);
2557

2558
    key_compare(entry_key, &if_found);
2559 2560

    // See HashTable::NextProbe().
2561
    Increment(&var_count);
2562 2563
    var_entry =
        WordAnd(IntPtrAdd(var_entry.value(), var_count.value()), entry_mask);
2564 2565 2566
    Goto(&loop);
  }

2567 2568 2569 2570 2571
  BIND(&if_found);
  return key_index;
}

TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::FindKeyIndexForInsertion(
2572 2573
    TNode<HeapObject> table, TNode<IntPtrT> key_hash,
    TNode<IntPtrT> entry_mask) {
2574 2575 2576 2577 2578 2579 2580 2581 2582
  // See HashTable::FindInsertionEntry().
  auto is_not_live = [&](TNode<Object> entry_key, Label* if_found) {
    // This is the the negative form BaseShape::IsLive().
    GotoIf(Word32Or(IsTheHole(entry_key), IsUndefined(entry_key)), if_found);
  };
  return FindKeyIndex(table, key_hash, entry_mask, is_not_live);
}

TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::FindKeyIndexForKey(
2583
    TNode<HeapObject> table, TNode<Object> key, TNode<IntPtrT> hash,
2584 2585 2586 2587 2588
    TNode<IntPtrT> entry_mask, Label* if_not_found) {
  // See HashTable::FindEntry().
  auto match_key_or_exit_on_empty = [&](TNode<Object> entry_key,
                                        Label* if_same) {
    GotoIf(IsUndefined(entry_key), if_not_found);
2589
    GotoIf(TaggedEqual(entry_key, key), if_same);
2590 2591 2592 2593 2594 2595 2596 2597 2598
  };
  return FindKeyIndex(table, hash, entry_mask, match_key_or_exit_on_empty);
}

TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::KeyIndexFromEntry(
    TNode<IntPtrT> entry) {
  // See HashTable::KeyAt().
  // (entry * kEntrySize) + kElementsStartIndex + kEntryKeyIndex
  return IntPtrAdd(
2599 2600 2601
      IntPtrMul(entry, IntPtrConstant(EphemeronHashTable::kEntrySize)),
      IntPtrConstant(EphemeronHashTable::kElementsStartIndex +
                     EphemeronHashTable::kEntryKeyIndex));
2602 2603 2604
}

TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::LoadNumberOfElements(
2605
    TNode<EphemeronHashTable> table, int offset) {
2606
  TNode<IntPtrT> number_of_elements = SmiUntag(CAST(UnsafeLoadFixedArrayElement(
2607
      table, EphemeronHashTable::kNumberOfElementsIndex)));
2608 2609 2610 2611
  return IntPtrAdd(number_of_elements, IntPtrConstant(offset));
}

TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::LoadNumberOfDeleted(
2612
    TNode<EphemeronHashTable> table, int offset) {
2613
  TNode<IntPtrT> number_of_deleted = SmiUntag(CAST(UnsafeLoadFixedArrayElement(
2614
      table, EphemeronHashTable::kNumberOfDeletedElementsIndex)));
2615 2616 2617
  return IntPtrAdd(number_of_deleted, IntPtrConstant(offset));
}

2618 2619
TNode<EphemeronHashTable> WeakCollectionsBuiltinsAssembler::LoadTable(
    TNode<JSWeakCollection> collection) {
2620
  return CAST(LoadObjectField(collection, JSWeakCollection::kTableOffset));
2621 2622 2623
}

TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::LoadTableCapacity(
2624
    TNode<EphemeronHashTable> table) {
2625 2626
  return SmiUntag(CAST(
      UnsafeLoadFixedArrayElement(table, EphemeronHashTable::kCapacityIndex)));
2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646
}

TNode<Word32T> WeakCollectionsBuiltinsAssembler::InsufficientCapacityToAdd(
    TNode<IntPtrT> capacity, TNode<IntPtrT> number_of_elements,
    TNode<IntPtrT> number_of_deleted) {
  // This is the negative form of HashTable::HasSufficientCapacityToAdd().
  // Return true if:
  //   - more than 50% of the available space are deleted elements
  //   - less than 50% will be available
  TNode<IntPtrT> available = IntPtrSub(capacity, number_of_elements);
  TNode<IntPtrT> half_available = WordShr(available, 1);
  TNode<IntPtrT> needed_available = WordShr(number_of_elements, 1);
  return Word32Or(
      // deleted > half
      IntPtrGreaterThan(number_of_deleted, half_available),
      // elements + needed available > capacity
      IntPtrGreaterThan(IntPtrAdd(number_of_elements, needed_available),
                        capacity));
}

2647
void WeakCollectionsBuiltinsAssembler::RemoveEntry(
2648
    TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
2649
    TNode<IntPtrT> number_of_elements) {
2650
  // See EphemeronHashTable::RemoveEntry().
2651 2652 2653 2654 2655 2656
  TNode<IntPtrT> value_index = ValueIndexFromKeyIndex(key_index);
  StoreFixedArrayElement(table, key_index, TheHoleConstant());
  StoreFixedArrayElement(table, value_index, TheHoleConstant());

  // See HashTableBase::ElementRemoved().
  TNode<IntPtrT> number_of_deleted = LoadNumberOfDeleted(table, 1);
2657
  StoreFixedArrayElement(table, EphemeronHashTable::kNumberOfElementsIndex,
2658
                         SmiFromIntPtr(number_of_elements), SKIP_WRITE_BARRIER);
2659 2660
  StoreFixedArrayElement(table,
                         EphemeronHashTable::kNumberOfDeletedElementsIndex,
2661
                         SmiFromIntPtr(number_of_deleted), SKIP_WRITE_BARRIER);
2662 2663
}

2664 2665 2666 2667 2668 2669 2670
TNode<BoolT> WeakCollectionsBuiltinsAssembler::ShouldRehash(
    TNode<IntPtrT> number_of_elements, TNode<IntPtrT> number_of_deleted) {
  // Rehash if more than 33% of the entries are deleted.
  return IntPtrGreaterThanOrEqual(WordShl(number_of_deleted, 1),
                                  number_of_elements);
}

2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686
TNode<Word32T> WeakCollectionsBuiltinsAssembler::ShouldShrink(
    TNode<IntPtrT> capacity, TNode<IntPtrT> number_of_elements) {
  // See HashTable::Shrink().
  TNode<IntPtrT> quarter_capacity = WordShr(capacity, 2);
  return Word32And(
      // Shrink to fit the number of elements if only a quarter of the
      // capacity is filled with elements.
      IntPtrLessThanOrEqual(number_of_elements, quarter_capacity),

      // Allocate a new dictionary with room for at least the current
      // number of elements. The allocation method will make sure that
      // there is extra room in the dictionary for additions. Don't go
      // lower than room for 16 elements.
      IntPtrGreaterThanOrEqual(number_of_elements, IntPtrConstant(16)));
}

2687 2688 2689
TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::ValueIndexFromKeyIndex(
    TNode<IntPtrT> key_index) {
  return IntPtrAdd(key_index,
2690
                   IntPtrConstant(EphemeronHashTable::ShapeT::kEntryValueIndex -
2691
                                  EphemeronHashTable::kEntryKeyIndex));
2692 2693
}

2694
TF_BUILTIN(WeakMapConstructor, WeakCollectionsBuiltinsAssembler) {
2695
  auto new_target = Parameter<Object>(Descriptor::kJSNewTarget);
2696
  TNode<IntPtrT> argc = ChangeInt32ToIntPtr(
2697 2698
      UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount));
  auto context = Parameter<Context>(Descriptor::kContext);
2699 2700 2701

  GenerateConstructor(kWeakMap, isolate()->factory()->WeakMap_string(),
                      new_target, argc, context);
2702 2703 2704
}

TF_BUILTIN(WeakSetConstructor, WeakCollectionsBuiltinsAssembler) {
2705
  auto new_target = Parameter<Object>(Descriptor::kJSNewTarget);
2706
  TNode<IntPtrT> argc = ChangeInt32ToIntPtr(
2707 2708
      UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount));
  auto context = Parameter<Context>(Descriptor::kContext);
2709 2710 2711

  GenerateConstructor(kWeakSet, isolate()->factory()->WeakSet_string(),
                      new_target, argc, context);
2712 2713
}

2714
TF_BUILTIN(WeakMapLookupHashIndex, WeakCollectionsBuiltinsAssembler) {
2715 2716
  auto table = Parameter<EphemeronHashTable>(Descriptor::kTable);
  auto key = Parameter<Object>(Descriptor::kKey);
2717

2718
  Label if_cannot_be_weak_key(this);
2719

2720
  GotoIfCannotBeWeakKey(key, &if_cannot_be_weak_key);
2721

2722 2723
  TNode<IntPtrT> hash =
      LoadJSReceiverIdentityHash(CAST(key), &if_cannot_be_weak_key);
2724
  TNode<IntPtrT> capacity = LoadTableCapacity(table);
2725 2726
  TNode<IntPtrT> key_index = FindKeyIndexForKey(
      table, key, hash, EntryMask(capacity), &if_cannot_be_weak_key);
2727 2728
  Return(SmiTag(ValueIndexFromKeyIndex(key_index)));

2729
  BIND(&if_cannot_be_weak_key);
2730 2731 2732
  Return(SmiConstant(-1));
}

2733
TF_BUILTIN(WeakMapGet, WeakCollectionsBuiltinsAssembler) {
2734 2735 2736
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto key = Parameter<Object>(Descriptor::kKey);
  const auto context = Parameter<Context>(Descriptor::kContext);
2737 2738 2739 2740 2741 2742

  Label return_undefined(this);

  ThrowIfNotInstanceType(context, receiver, JS_WEAK_MAP_TYPE,
                         "WeakMap.prototype.get");

2743 2744
  const TNode<EphemeronHashTable> table = LoadTable(CAST(receiver));
  const TNode<Smi> index =
2745
      CAST(CallBuiltin(Builtin::kWeakMapLookupHashIndex, context, table, key));
2746

2747
  GotoIf(TaggedEqual(index, SmiConstant(-1)), &return_undefined);
2748 2749 2750 2751 2752 2753 2754

  Return(LoadFixedArrayElement(table, SmiUntag(index)));

  BIND(&return_undefined);
  Return(UndefinedConstant());
}

2755
TF_BUILTIN(WeakMapPrototypeHas, WeakCollectionsBuiltinsAssembler) {
2756 2757 2758
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto key = Parameter<Object>(Descriptor::kKey);
  const auto context = Parameter<Context>(Descriptor::kContext);
2759 2760 2761 2762

  Label return_false(this);

  ThrowIfNotInstanceType(context, receiver, JS_WEAK_MAP_TYPE,
2763
                         "WeakMap.prototype.has");
2764

2765 2766
  const TNode<EphemeronHashTable> table = LoadTable(CAST(receiver));
  const TNode<Object> index =
2767
      CallBuiltin(Builtin::kWeakMapLookupHashIndex, context, table, key);
2768

2769
  GotoIf(TaggedEqual(index, SmiConstant(-1)), &return_false);
2770 2771 2772 2773 2774 2775 2776

  Return(TrueConstant());

  BIND(&return_false);
  Return(FalseConstant());
}

2777
// Helper that removes the entry with a given key from the backing store
2778
// (EphemeronHashTable) of a WeakMap or WeakSet.
2779
TF_BUILTIN(WeakCollectionDelete, WeakCollectionsBuiltinsAssembler) {
2780 2781 2782
  auto context = Parameter<Context>(Descriptor::kContext);
  auto collection = Parameter<JSWeakCollection>(Descriptor::kCollection);
  auto key = Parameter<Object>(Descriptor::kKey);
2783

2784
  Label call_runtime(this), if_cannot_be_weak_key(this);
2785

2786
  GotoIfCannotBeWeakKey(key, &if_cannot_be_weak_key);
2787

2788 2789
  TNode<IntPtrT> hash =
      LoadJSReceiverIdentityHash(CAST(key), &if_cannot_be_weak_key);
2790
  TNode<EphemeronHashTable> table = LoadTable(collection);
2791
  TNode<IntPtrT> capacity = LoadTableCapacity(table);
2792 2793
  TNode<IntPtrT> key_index = FindKeyIndexForKey(
      table, key, hash, EntryMask(capacity), &if_cannot_be_weak_key);
2794 2795 2796 2797 2798 2799
  TNode<IntPtrT> number_of_elements = LoadNumberOfElements(table, -1);
  GotoIf(ShouldShrink(capacity, number_of_elements), &call_runtime);

  RemoveEntry(table, key_index, number_of_elements);
  Return(TrueConstant());

2800
  BIND(&if_cannot_be_weak_key);
2801 2802 2803 2804 2805 2806 2807
  Return(FalseConstant());

  BIND(&call_runtime);
  Return(CallRuntime(Runtime::kWeakCollectionDelete, context, collection, key,
                     SmiTag(hash)));
}

2808 2809
// Helper that sets the key and value to the backing store (EphemeronHashTable)
// of a WeakMap or WeakSet.
2810
TF_BUILTIN(WeakCollectionSet, WeakCollectionsBuiltinsAssembler) {
2811 2812 2813 2814
  auto context = Parameter<Context>(Descriptor::kContext);
  auto collection = Parameter<JSWeakCollection>(Descriptor::kCollection);
  auto key = Parameter<JSReceiver>(Descriptor::kKey);
  auto value = Parameter<Object>(Descriptor::kValue);
2815

2816
  CSA_DCHECK(this, IsJSReceiver(key));
2817 2818 2819

  Label call_runtime(this), if_no_hash(this), if_not_found(this);

2820
  TNode<EphemeronHashTable> table = LoadTable(collection);
2821 2822 2823 2824
  TNode<IntPtrT> capacity = LoadTableCapacity(table);
  TNode<IntPtrT> entry_mask = EntryMask(capacity);

  TVARIABLE(IntPtrT, var_hash, LoadJSReceiverIdentityHash(key, &if_no_hash));
2825 2826
  TNode<IntPtrT> key_index = FindKeyIndexForKey(table, key, var_hash.value(),
                                                entry_mask, &if_not_found);
2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847

  StoreFixedArrayElement(table, ValueIndexFromKeyIndex(key_index), value);
  Return(collection);

  BIND(&if_no_hash);
  {
    var_hash = SmiUntag(CreateIdentityHash(key));
    Goto(&if_not_found);
  }
  BIND(&if_not_found);
  {
    TNode<IntPtrT> number_of_deleted = LoadNumberOfDeleted(table);
    TNode<IntPtrT> number_of_elements = LoadNumberOfElements(table, 1);

    // TODO(pwong): Port HashTable's Rehash() and EnsureCapacity() to CSA.
    GotoIf(Word32Or(ShouldRehash(number_of_elements, number_of_deleted),
                    InsufficientCapacityToAdd(capacity, number_of_elements,
                                              number_of_deleted)),
           &call_runtime);

    TNode<IntPtrT> insertion_key_index =
2848
        FindKeyIndexForInsertion(table, var_hash.value(), entry_mask);
2849 2850 2851 2852 2853 2854
    AddEntry(table, insertion_key_index, key, value, number_of_elements);
    Return(collection);
  }
  BIND(&call_runtime);
  {
    CallRuntime(Runtime::kWeakCollectionSet, context, collection, key, value,
2855
                SmiTag(var_hash.value()));
2856 2857 2858 2859
    Return(collection);
  }
}

2860
TF_BUILTIN(WeakMapPrototypeDelete, CodeStubAssembler) {
2861 2862 2863
  auto context = Parameter<Context>(Descriptor::kContext);
  auto receiver = Parameter<Object>(Descriptor::kReceiver);
  auto key = Parameter<Object>(Descriptor::kKey);
2864 2865 2866 2867

  ThrowIfNotInstanceType(context, receiver, JS_WEAK_MAP_TYPE,
                         "WeakMap.prototype.delete");

2868 2869 2870
  // This check breaks a known exploitation technique. See crbug.com/1263462
  CSA_CHECK(this, TaggedNotEqual(key, TheHoleConstant()));

2871
  Return(CallBuiltin(Builtin::kWeakCollectionDelete, context, receiver, key));
2872 2873
}

2874
TF_BUILTIN(WeakMapPrototypeSet, WeakCollectionsBuiltinsAssembler) {
2875 2876 2877 2878
  auto context = Parameter<Context>(Descriptor::kContext);
  auto receiver = Parameter<Object>(Descriptor::kReceiver);
  auto key = Parameter<Object>(Descriptor::kKey);
  auto value = Parameter<Object>(Descriptor::kValue);
2879 2880 2881 2882 2883

  ThrowIfNotInstanceType(context, receiver, JS_WEAK_MAP_TYPE,
                         "WeakMap.prototype.set");

  Label throw_invalid_key(this);
2884
  GotoIfCannotBeWeakKey(key, &throw_invalid_key);
2885 2886

  Return(
2887
      CallBuiltin(Builtin::kWeakCollectionSet, context, receiver, key, value));
2888 2889 2890 2891 2892

  BIND(&throw_invalid_key);
  ThrowTypeError(context, MessageTemplate::kInvalidWeakMapKey, key);
}

2893
TF_BUILTIN(WeakSetPrototypeAdd, WeakCollectionsBuiltinsAssembler) {
2894 2895 2896
  auto context = Parameter<Context>(Descriptor::kContext);
  auto receiver = Parameter<Object>(Descriptor::kReceiver);
  auto value = Parameter<Object>(Descriptor::kValue);
2897 2898 2899 2900 2901

  ThrowIfNotInstanceType(context, receiver, JS_WEAK_SET_TYPE,
                         "WeakSet.prototype.add");

  Label throw_invalid_value(this);
2902
  GotoIfCannotBeWeakKey(value, &throw_invalid_value);
2903

2904
  Return(CallBuiltin(Builtin::kWeakCollectionSet, context, receiver, value,
2905 2906 2907 2908 2909 2910
                     TrueConstant()));

  BIND(&throw_invalid_value);
  ThrowTypeError(context, MessageTemplate::kInvalidWeakSetValue, value);
}

2911
TF_BUILTIN(WeakSetPrototypeDelete, CodeStubAssembler) {
2912 2913 2914
  auto context = Parameter<Context>(Descriptor::kContext);
  auto receiver = Parameter<Object>(Descriptor::kReceiver);
  auto value = Parameter<Object>(Descriptor::kValue);
2915 2916 2917 2918

  ThrowIfNotInstanceType(context, receiver, JS_WEAK_SET_TYPE,
                         "WeakSet.prototype.delete");

2919 2920 2921
  // This check breaks a known exploitation technique. See crbug.com/1263462
  CSA_CHECK(this, TaggedNotEqual(value, TheHoleConstant()));

2922
  Return(CallBuiltin(Builtin::kWeakCollectionDelete, context, receiver, value));
2923 2924
}

2925
TF_BUILTIN(WeakSetPrototypeHas, WeakCollectionsBuiltinsAssembler) {
2926 2927 2928
  const auto receiver = Parameter<Object>(Descriptor::kReceiver);
  const auto key = Parameter<Object>(Descriptor::kKey);
  const auto context = Parameter<Context>(Descriptor::kContext);
2929 2930 2931 2932

  Label return_false(this);

  ThrowIfNotInstanceType(context, receiver, JS_WEAK_SET_TYPE,
2933
                         "WeakSet.prototype.has");
2934

2935 2936
  const TNode<EphemeronHashTable> table = LoadTable(CAST(receiver));
  const TNode<Object> index =
2937
      CallBuiltin(Builtin::kWeakMapLookupHashIndex, context, table, key);
2938

2939
  GotoIf(TaggedEqual(index, SmiConstant(-1)), &return_false);
2940 2941 2942 2943 2944 2945 2946

  Return(TrueConstant());

  BIND(&return_false);
  Return(FalseConstant());
}

2947 2948
}  // namespace internal
}  // namespace v8