blob: 47e6540f9783b83e4d8fbf16c726975ea332be89 [file] [log] [blame]
// 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 {
extern macro CollectionsBuiltinsAssembler::AddValueToKeyedGroup(
OrderedHashMap, Object, Object, String): OrderedHashMap;
extern macro CollectionsBuiltinsAssembler::NormalizeNumberKey(JSAny): JSAny;
} // namespace collections
// https://tc39.es/proposal-array-grouping/#sec-group-by
transitioning macro CoerceGroupKey(
implicit context: Context)(key: JSAny, coerceToProperty: Boolean): JSAny {
// 6.g. If coercion is property, then
if (coerceToProperty == True) {
// i. Set key to Completion(ToPropertyKey(key)).
return ToName(key);
}
// 6.h. Else,
// i. Assert: coercion is zero.
// ii. If key is -0𝔽, set key to +0𝔽.
return collections::NormalizeNumberKey(key);
}
// https://tc39.es/proposal-array-grouping/#sec-group-by
transitioning builtin GroupByGeneric(
implicit context: Context)(items: JSAny, initialGroups: OrderedHashMap,
callbackfn: Callable, coerceToProperty: Boolean,
methodName: String): OrderedHashMap {
let groups = initialGroups;
// 4. Let iteratorRecord be ? GetIterator(items, sync).
const fastIteratorResultMap = GetIteratorResultMap();
const iteratorRecord = iterator::GetIterator(items);
// 5. Let k be 0.
let k: Number = 0;
// 6. Repeat,
while (true) {
// a. If k ≥ 2^53 - 1, then
// i. Let error be ThrowCompletion(a newly created TypeError object).
// ii. Return ? IteratorClose(iteratorRecord, error).
//
// The spec requires that we throw an exception if index reaches 2^53-1,
// but an empty loop would take >100 days to do this many iterations. To
// actually run for that long would require an iterator that never set
// done to true and a target array which somehow never ran out of
// memory, e.g. a proxy that discarded the values. Ignoring this case
// just means we would call the callback with 2^53.
dcheck(k < kMaxSafeInteger);
// b. Let next be ? IteratorStep(iteratorRecord).
let next: JSReceiver;
try {
next = iterator::IteratorStep(iteratorRecord, fastIteratorResultMap)
otherwise NextIsFalse;
}
// c. If next is false, then
label NextIsFalse {
// i. Return groups.
return groups;
}
// d. Let value be ? IteratorValue(next).
const value = iterator::IteratorValue(next, fastIteratorResultMap);
// e. Let key be Completion(Call(callbackfn, undefined, « value, 𝔽(k) »)).
let key: JSAny;
try {
key = Call(context, callbackfn, Undefined, value, k);
key = CoerceGroupKey(key, coerceToProperty);
} catch (e, message) {
// f. and g.ii.
// IfAbruptCloseIterator(key, iteratorRecord).
iterator::IteratorCloseOnException(iteratorRecord);
ReThrowWithMessage(context, e, message);
}
// i. Perform AddValueToKeyedGroup(groups, key, value).
groups = collections::AddValueToKeyedGroup(groups, key, value, methodName);
// j. Set k to k + 1.
k += 1;
}
unreachable;
}
// https://tc39.es/proposal-array-grouping/#sec-group-by
transitioning macro GroupByImpl(
implicit context: Context)(items: JSAny, callback: JSAny,
coerceToProperty: Boolean, methodName: constexpr string): OrderedHashMap {
// 1. Perform ? RequireObjectCoercible(items).
RequireObjectCoercible(items, methodName);
// 2. If IsCallable(callbackfn) is false, throw a TypeError exception.
const callbackfn = Cast<Callable>(callback)
otherwise ThrowTypeError(MessageTemplate::kCalledNonCallable, callback);
// 3. Let groups be a new empty List.
let groups = AllocateOrderedHashMap();
try {
typeswitch (items) {
case (array: FastJSArrayForReadWithNoCustomIteration): {
// Per spec, the iterator and its next method are cached up front. This
// means that we only need to check for no custom iteration once up
// front. Even though the grouping callback has arbitrary side effects,
// mutations to %ArrayIteratorPrototype% will not be reflected during
// the iteration itself. Therefore we don't need a "no custom iteration"
// witness.
let fastArrayWitness = NewFastJSArrayForReadWitness(array);
const stableArray = fastArrayWitness.stable;
let k: Smi = 0;
try {
while (k < stableArray.length) {
fastArrayWitness.Recheck() otherwise goto SlowArrayContinuation;
let value: JSAny;
try {
value =
fastArrayWitness.LoadElementNoHole(k) otherwise IsUndefined;
} label IsUndefined {
value = Undefined;
}
const key = CoerceGroupKey(
Call(context, callbackfn, Undefined, value, k),
coerceToProperty);
groups = collections::AddValueToKeyedGroup(
groups, key, value, methodName);
++k;
}
} label SlowArrayContinuation deferred {
// The grouping callback can mutate the array such that it is no
// longer fast, but it is still a JSArray. Since the spec caches the
// iterator up front, a fully generic fallback is not needed. Instead
// we encode the array iterator logic here directly for the rest of
// the loop.
while (k < stableArray.length) {
const value = GetProperty(stableArray, k);
const key = CoerceGroupKey(
Call(context, callbackfn, Undefined, value, k),
coerceToProperty);
groups = collections::AddValueToKeyedGroup(
groups, key, value, methodName);
++k;
}
}
return groups;
}
case (JSAny): {
goto SlowGeneric;
}
}
} label SlowGeneric {
return GroupByGeneric(
items, groups, callbackfn, coerceToProperty, methodName);
}
}
@incrementUseCounter('v8::Isolate::kArrayGroup')
transitioning javascript builtin ObjectGroupBy(
js-implicit context: NativeContext, receiver: JSAny)(items: JSAny,
callback: JSAny): JSAny {
// 1. Let groups be ? GroupBy(items, callbackfn, property).
const groups: OrderedHashMap = GroupByImpl(
items, callback, /* coerceToProperty */ True, 'Object.groupBy');
let iter = collections::NewUnmodifiedOrderedHashMapIterator(groups);
// 2. Let obj be OrdinaryObjectCreate(null).
// 3. For each Record { [[Key]], [[Elements]] } g of groups, do
// a. Let elements be CreateArrayFromList(g.[[Elements]]).
// b. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements).
let properties: NameDictionary|SwissNameDictionary;
@if(V8_ENABLE_SWISS_NAME_DICTIONARY) {
properties =
AllocateSwissNameDictionary(Convert<intptr>(iter.usedCapacity));
}
@ifnot(V8_ENABLE_SWISS_NAME_DICTIONARY) {
properties = AllocateNameDictionary(Convert<intptr>(iter.usedCapacity));
}
const nullProtoMap = LoadSlowObjectWithNullPrototypeMap(context);
const obj = AllocateJSObjectFromMap(nullProtoMap, properties);
// TODO(v8:12499): Determine more specific elements map if worth it.
try {
const arrayMap = GetFastPackedElementsJSArrayMap();
while (true) {
const entry = iter.Next() otherwise Done;
const elements = ArrayListElements(UnsafeCast<ArrayList>(entry.value));
const array = NewJSArray(arrayMap, elements);
CreateDataProperty(obj, entry.key, array);
}
} label Done {}
// 4. Return obj.
return obj;
}