| // 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.symmetricdifference |
| @incrementUseCounter('v8::Isolate::kSetMethods') |
| transitioning javascript builtin SetPrototypeSymmetricDifference( |
| js-implicit context: NativeContext, receiver: JSAny)(other: JSAny): JSSet { |
| const methodName: constexpr string = 'Set.prototype.symmetricDifference'; |
| 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); |
| |
| // 4. Let keysIter be ? GetKeysIterator(otherRec). |
| let keysIter = |
| GetKeysIterator(otherRec.object, UnsafeCast<Callable>(otherRec.keys)); |
| |
| // 5. Let resultSetData be a copy of O.[[SetData]]. |
| let table = NewStableBackingTableWitness(o); |
| const resultSetData = Cast<OrderedHashSet>( |
| CloneFixedArray(table.GetTable(), ExtractFixedArrayFlag::kFixedArrays)) |
| otherwise unreachable; |
| let resultAndNumberOfElements = OrderedHashSetAndNumberOfElements{ |
| setData: resultSetData, |
| numberOfElements: UnsafeCast<Smi>( |
| resultSetData.objects[kOrderedHashSetNumberOfElementsIndex]) |
| }; |
| |
| try { |
| typeswitch (other) { |
| case (otherSet: JSSetWithNoCustomIteration): { |
| CheckSetRecordHasJSSetMethods(otherRec) otherwise SlowPath; |
| |
| const otherTable = NewStableBackingTableWitness(otherSet); |
| |
| let otherIterator = collections::NewUnmodifiedOrderedHashSetIterator( |
| otherTable.GetTable()); |
| |
| while (true) { |
| const nextValue = otherIterator.Next() otherwise Done; |
| |
| resultAndNumberOfElements = FastSymmetricDifference( |
| nextValue, table, resultAndNumberOfElements, methodName); |
| } |
| } |
| case (otherMap: JSMapWithNoCustomIteration): { |
| CheckSetRecordHasJSMapMethods(otherRec) otherwise SlowPath; |
| |
| const otherTable = NewStableBackingTableWitness(otherMap); |
| |
| let otherIterator = collections::NewUnmodifiedOrderedHashMapIterator( |
| otherTable.GetTable()); |
| |
| while (true) { |
| const nextValue = otherIterator.Next() otherwise Done; |
| |
| resultAndNumberOfElements = FastSymmetricDifference( |
| nextValue.key, table, resultAndNumberOfElements, methodName); |
| } |
| } |
| case (JSAny): { |
| goto SlowPath; |
| } |
| } |
| } label SlowPath { |
| // 6. Let next be true. |
| let nextRecord: JSReceiver; |
| // 7. Repeat, while next is not false, |
| while (true) { |
| // a. Set next to ? IteratorStep(keysIter). |
| nextRecord = iterator::IteratorStep(keysIter, fastIteratorResultMap) |
| otherwise Done; |
| |
| // b. If next is not false, then |
| // i. Let nextValue be ? IteratorValue(next). |
| let nextValue = |
| iterator::IteratorValue(nextRecord, fastIteratorResultMap); |
| |
| // ii. If nextValue is -0𝔽, set nextValue to +0𝔽. |
| nextValue = collections::NormalizeNumberKey(nextValue); |
| |
| // iii. Let inResult be SetDataHas(resultSetData, nextValue). |
| const inResult = |
| TableHasKey(resultAndNumberOfElements.setData, nextValue); |
| |
| // iv. If SetDataHas(O.[[SetData]], nextValue) is true, then |
| table.ReloadTable(); |
| if (table.HasKey(nextValue)) { |
| // 1. If inResult is true, remove nextValue from resultSetData. |
| if (inResult) { |
| resultAndNumberOfElements.numberOfElements = |
| DeleteFromSetTable(resultAndNumberOfElements.setData, nextValue) |
| otherwise unreachable; |
| } |
| } else { |
| // v. Else, |
| // 1. If inResult is false, append nextValue to resultSetData. |
| if (!inResult) { |
| resultAndNumberOfElements.setData = AddToSetTable( |
| resultAndNumberOfElements.setData, nextValue, methodName); |
| resultAndNumberOfElements.numberOfElements++; |
| } |
| } |
| } |
| } label Done { |
| const shrunk = ShrinkOrderedHashSetIfNeeded( |
| resultAndNumberOfElements.numberOfElements, |
| resultAndNumberOfElements.setData); |
| return new JSSet{ |
| map: *NativeContextSlot(ContextSlot::JS_SET_MAP_INDEX), |
| properties_or_hash: kEmptyFixedArray, |
| elements: kEmptyFixedArray, |
| table: shrunk |
| }; |
| } |
| unreachable; |
| } |
| |
| // This macro gets the nextValue in other table and normalize it. If the |
| // nextValue exists in the receiver table, it will be removed. Otherwise |
| // it will be added to the resultSetData. |
| struct OrderedHashSetAndNumberOfElements { |
| setData: OrderedHashSet; |
| numberOfElements: Smi; |
| } |
| |
| macro FastSymmetricDifference( |
| implicit context: Context)(nextValue: JSAny, |
| table: StableJSSetBackingTableWitness, |
| resultSetDataAndNumberOfElements: OrderedHashSetAndNumberOfElements, |
| methodName: constexpr string): OrderedHashSetAndNumberOfElements { |
| let key = nextValue; |
| let resultSetData = resultSetDataAndNumberOfElements.setData; |
| let numberOfElements = resultSetDataAndNumberOfElements.numberOfElements; |
| |
| // ii. If nextValue is -0𝔽, set nextValue to +0𝔽. |
| key = collections::NormalizeNumberKey(key); |
| |
| // iii. Let inResult be SetDataHas(resultSetData, nextValue). |
| const inResult = TableHasKey(resultSetData, key); |
| |
| // iv. If SetDataHas(O.[[SetData]], nextValue) is true, then |
| dcheck(inResult == table.HasKey(key)); |
| // 1. If inResult is true, remove nextValue from resultSetData. |
| if (inResult) { |
| numberOfElements = DeleteFromSetTable(resultSetData, key) |
| otherwise unreachable; |
| } else { |
| // v. Else, |
| // 1. If inResult is false, append nextValue to resultSetData. |
| resultSetData = AddToSetTable(resultSetData, key, methodName); |
| numberOfElements++; |
| } |
| return OrderedHashSetAndNumberOfElements{ |
| setData: resultSetData, |
| numberOfElements: numberOfElements |
| }; |
| } |
| } |