| // Copyright 2023 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. |
| |
| namespace collections { |
| |
| // https://tc39.es/proposal-set-methods/#sec-set.prototype.intersection |
| @incrementUseCounter('v8::Isolate::kSetMethods') |
| transitioning javascript builtin SetPrototypeIntersection( |
| js-implicit context: NativeContext, receiver: JSAny)(other: JSAny): JSSet { |
| const methodName: constexpr string = 'Set.prototype.intersection'; |
| const fastIteratorResultMap = GetIteratorResultMap(); |
| |
| // 1. Let O be the this value. |
| // 2. Perform ? RequireInternalSlot(O, [[SetData]]). |
| const o = Cast<JSSet>(receiver) otherwise |
| ThrowTypeError( |
| MessageTemplate::kIncompatibleMethodReceiver, methodName, receiver); |
| |
| // 3. Let otherRec be ? GetSetRecord(other). |
| let otherRec = GetSetRecord(other, methodName); |
| |
| let table = NewStableBackingTableWitness(o); |
| |
| // 4. Let resultSetData be a new empty List. |
| let resultSetData = AllocateOrderedHashSet(); |
| |
| // 5. Let thisSize be the number of elements in O.[[SetData]]. |
| const thisSize = table.LoadSize(); |
| |
| try { |
| typeswitch (other) { |
| case (otherSet: JSSetWithNoCustomIteration): { |
| CheckSetRecordHasJSSetMethods(otherRec) otherwise SlowPath; |
| |
| const otherTable = NewStableBackingTableWitness(otherSet); |
| |
| const otherSize = otherTable.LoadSize(); |
| |
| if (thisSize <= otherSize) { |
| resultSetData = FastIntersect<StableJSSetBackingTableWitness>( |
| table, otherTable, methodName, resultSetData); |
| goto Done; |
| |
| } else { |
| resultSetData = FastIntersect<StableJSSetBackingTableWitness>( |
| otherTable, table, methodName, resultSetData); |
| goto Done; |
| } |
| } |
| case (otherMap: JSMapWithNoCustomIteration): { |
| CheckSetRecordHasJSMapMethods(otherRec) otherwise SlowPath; |
| |
| const otherTable = NewStableBackingTableWitness(otherMap); |
| |
| const otherSize = otherTable.LoadSize(); |
| |
| if (thisSize <= otherSize) { |
| resultSetData = FastIntersect<StableJSMapBackingTableWitness>( |
| table, otherTable, methodName, resultSetData); |
| goto Done; |
| |
| } else { |
| // TODO(13556): Change `FastIntersect` macro to be able to handle |
| // this case as well. |
| let otherIterator = collections::NewUnmodifiedOrderedHashMapIterator( |
| otherTable.GetTable()); |
| |
| while (true) { |
| const nextValue = otherIterator.Next() otherwise Done; |
| |
| if (table.HasKey(nextValue.key)) { |
| resultSetData = |
| AddToSetTable(resultSetData, nextValue.key, methodName); |
| } |
| } |
| } |
| } |
| case (JSAny): { |
| goto SlowPath; |
| } |
| } |
| } label SlowPath { |
| // 6. If thisSize ≤ otherRec.[[Size]], then |
| if (Convert<Number>(thisSize) <= otherRec.size) { |
| // a. Let index be 0. |
| let thisIter = collections::NewOrderedHashSetIterator(table.GetTable()); |
| |
| // b. Repeat, while index < thisSize, |
| while (true) { |
| // i. Let e be O.[[SetData]][index]. |
| const key = thisIter.Next() otherwise Done; |
| |
| // ii. Set index to index + 1. |
| // iii. If e is not empty, then |
| // 1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], |
| // otherRec.[[Set]], « e »)). |
| const inOther = |
| ToBoolean(Call(context, otherRec.has, otherRec.object, key)); |
| |
| // 2. If inOther is true, then |
| if (inOther) { |
| // a. NOTE: It is possible for earlier calls to otherRec.[[Has]] to |
| // remove and re-add an element of O.[[SetData]], which can cause the |
| // same element to be visited twice during this iteration. |
| // We used `OrderedHashSetIterator` that works when underlying table |
| // is changed. |
| // b. Let alreadyInResult be SetDataHas(resultSetData, e). |
| // c. If alreadyInResult is false, then |
| // i. Append e to resultSetData. |
| resultSetData = AddToSetTable(resultSetData, key, methodName); |
| } |
| |
| // 3. NOTE: The number of elements in O.[[SetData]] may have increased |
| // during execution of otherRec.[[Has]]. |
| // 4. Set thisSize to the number of elements of O.[[SetData]]. |
| // We used iterator so we do not need to update thisSize and index. |
| } |
| } else { |
| // a. Let keysIter be ? GetKeysIterator(otherRec). |
| let keysIter = |
| GetKeysIterator(otherRec.object, UnsafeCast<Callable>(otherRec.keys)); |
| |
| // b. Let next be true. |
| let nextRecord: JSReceiver; |
| |
| // c. Repeat, while next is not false, |
| while (true) { |
| // i. Set next to ? IteratorStep(keysIter). |
| nextRecord = iterator::IteratorStep(keysIter, fastIteratorResultMap) |
| otherwise Done; |
| |
| // ii. If next is not false, then |
| // 1. Let nextValue be ? IteratorValue(next). |
| const nextValue = |
| iterator::IteratorValue(nextRecord, fastIteratorResultMap); |
| |
| // 2. If nextValue is -0𝔽, set nextValue to +0𝔽. |
| // 3. NOTE: Because other is an arbitrary object, it is possible for its |
| // "keys" iterator to produce the same value more than once. |
| // 4. Let alreadyInResult be SetDataHas(resultSetData, nextValue). |
| // 5. Let inThis be SetDataHas(O.[[SetData]], nextValue). |
| |
| table.ReloadTable(); |
| if (table.HasKey(nextValue)) { |
| // 6. If alreadyInResult is false and inThis is true, then |
| // a. Append nextValue to resultSetData. |
| resultSetData = AddToSetTable(resultSetData, nextValue, methodName); |
| } |
| } |
| } |
| } label Done { |
| return new JSSet{ |
| map: *NativeContextSlot(ContextSlot::JS_SET_MAP_INDEX), |
| properties_or_hash: kEmptyFixedArray, |
| elements: kEmptyFixedArray, |
| table: resultSetData |
| }; |
| } |
| unreachable; |
| } |
| |
| // This macro creates an iterator from a collection that need to be iterated |
| // (collectionToIterate), lookup each value of the iterator in a table that |
| // needs to be checked (tableToLookup), and adds the value to the result |
| // (resultSetData) if it exists in the table. |
| macro FastIntersect<T: type>( |
| implicit context: Context)( |
| collectionToIterate: StableJSSetBackingTableWitness, tableToLookup: T, |
| methodName: String, resultSetData: OrderedHashSet): OrderedHashSet { |
| let result = resultSetData; |
| |
| let iter = collections::NewUnmodifiedOrderedHashSetIterator( |
| collectionToIterate.GetTable()); |
| try { |
| while (true) { |
| const nextValue = iter.Next() otherwise Done; |
| |
| if (tableToLookup.HasKey(nextValue)) { |
| result = AddToSetTable(result, nextValue, methodName); |
| } |
| } |
| } label Done { |
| return result; |
| } |
| unreachable; |
| } |
| } |