Skip to content
Permalink
Browse files

[esnext] Implement Array.prototype.{flatten,flatMap} πŸ₯™

Proposal repo: https://tc39.github.io/proposal-flatMap/

Bug: v8:7220
Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng
Change-Id: I61661fc6d5c39d084ce5c96a9e150e5c26799e2d
Also-By: bmeurer@chromium.org
Reviewed-on: https://chromium-review.googlesource.com/957043
Commit-Queue: Mathias Bynens <mathias@chromium.org>
Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51967}
  • Loading branch information
mathiasbynens authored and Commit Bot committed Mar 15, 2018
1 parent f8fb4a5 commit 697d39abff90510523f297bb8577d5c64322229f
@@ -4251,6 +4251,17 @@ void Genesis::InitializeGlobal_harmony_array_prototype_values() {
NONE);
}

void Genesis::InitializeGlobal_harmony_array_flatten() {
if (!FLAG_harmony_array_flatten) return;
Handle<JSFunction> array_constructor(native_context()->array_function());
Handle<JSObject> array_prototype(
JSObject::cast(array_constructor->instance_prototype()));
SimpleInstallFunction(array_prototype, "flatten",
Builtins::kArrayPrototypeFlatten, 0, false, DONT_ENUM);
SimpleInstallFunction(array_prototype, "flatMap",
Builtins::kArrayPrototypeFlatMap, 1, false, DONT_ENUM);
}

void Genesis::InitializeGlobal_harmony_promise_finally() {
if (!FLAG_harmony_promise_finally) return;

@@ -3845,5 +3845,260 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) {
}
}

namespace {

class ArrayFlattenAssembler : public CodeStubAssembler {
public:
explicit ArrayFlattenAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}

// https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray
Node* FlattenIntoArray(Node* context, Node* target, Node* source,
Node* source_length, Node* start, Node* depth,
Node* mapper_function = nullptr,
Node* this_arg = nullptr) {
CSA_ASSERT(this, IsJSReceiver(target));
CSA_ASSERT(this, IsJSReceiver(source));
CSA_ASSERT(this, IsNumberPositive(source_length));
CSA_ASSERT(this, IsNumberPositive(start));
CSA_ASSERT(this, IsNumber(depth));

// 1. Let targetIndex be start.
VARIABLE(var_target_index, MachineRepresentation::kTagged, start);

// 2. Let sourceIndex be 0.
VARIABLE(var_source_index, MachineRepresentation::kTagged, SmiConstant(0));

// 3. Repeat...
Label loop(this, {&var_target_index, &var_source_index}), done_loop(this);
Goto(&loop);
BIND(&loop);
{
Node* const source_index = var_source_index.value();
Node* const target_index = var_target_index.value();

// ...while sourceIndex < sourceLen
GotoIfNumberGreaterThanOrEqual(source_index, source_length, &done_loop);

// a. Let P be ! ToString(sourceIndex).
// b. Let exists be ? HasProperty(source, P).
CSA_ASSERT(this, SmiGreaterThanOrEqual(source_index, SmiConstant(0)));
Node* const exists =
HasProperty(source, source_index, context, kHasProperty);

// c. If exists is true, then
Label next(this);
GotoIfNot(IsTrue(exists), &next);
{
// i. Let element be ? Get(source, P).
Node* element = GetProperty(context, source, source_index);

// ii. If mapperFunction is present, then
if (mapper_function != nullptr) {
CSA_ASSERT(this, Word32Or(IsUndefined(mapper_function),
IsCallable(mapper_function)));
DCHECK_NOT_NULL(this_arg);

// 1. Set element to ? Call(mapperFunction, thisArg , Β« element,
// sourceIndex, source Β»).
element =
CallJS(CodeFactory::Call(isolate()), context, mapper_function,
this_arg, element, source_index, source);
}

// iii. Let shouldFlatten be false.
Label if_flatten_array(this), if_flatten_proxy(this, Label::kDeferred),
if_noflatten(this);
// iv. If depth > 0, then
GotoIfNumberGreaterThanOrEqual(SmiConstant(0), depth, &if_noflatten);
// 1. Set shouldFlatten to ? IsArray(element).
GotoIf(TaggedIsSmi(element), &if_noflatten);
GotoIf(IsJSArray(element), &if_flatten_array);
GotoIfNot(IsJSProxy(element), &if_noflatten);
Branch(IsTrue(CallRuntime(Runtime::kArrayIsArray, context, element)),
&if_flatten_proxy, &if_noflatten);

BIND(&if_flatten_array);
{
CSA_ASSERT(this, IsJSArray(element));

// 1. Let elementLen be ? ToLength(? Get(element, "length")).
Node* const element_length =
LoadObjectField(element, JSArray::kLengthOffset);

// 2. Set targetIndex to ? FlattenIntoArray(target, element,
// elementLen, targetIndex,
// depth - 1).
var_target_index.Bind(
CallBuiltin(Builtins::kFlattenIntoArray, context, target, element,
element_length, target_index, NumberDec(depth)));
Goto(&next);
}

BIND(&if_flatten_proxy);
{
CSA_ASSERT(this, IsJSProxy(element));

// 1. Let elementLen be ? ToLength(? Get(element, "length")).
Node* const element_length = ToLength_Inline(
context, GetProperty(context, element, LengthStringConstant()));

// 2. Set targetIndex to ? FlattenIntoArray(target, element,
// elementLen, targetIndex,
// depth - 1).
var_target_index.Bind(
CallBuiltin(Builtins::kFlattenIntoArray, context, target, element,
element_length, target_index, NumberDec(depth)));
Goto(&next);
}

BIND(&if_noflatten);
{
// 1. If targetIndex >= 2^53-1, throw a TypeError exception.
Label throw_error(this, Label::kDeferred);
GotoIfNumberGreaterThanOrEqual(
target_index, NumberConstant(kMaxSafeInteger), &throw_error);

// 2. Perform ? CreateDataPropertyOrThrow(target,
// ! ToString(targetIndex),
// element).
CallRuntime(Runtime::kCreateDataProperty, context, target,
target_index, element);

// 3. Increase targetIndex by 1.
var_target_index.Bind(NumberInc(target_index));
Goto(&next);

BIND(&throw_error);
ThrowTypeError(context, MessageTemplate::kFlattenPastSafeLength,
source_length, target_index);
}
}
BIND(&next);

// d. Increase sourceIndex by 1.
var_source_index.Bind(NumberInc(source_index));
Goto(&loop);
}

BIND(&done_loop);
return var_target_index.value();
}
};

} // namespace

// https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray
TF_BUILTIN(FlattenIntoArray, ArrayFlattenAssembler) {
Node* const context = Parameter(Descriptor::kContext);
Node* const target = Parameter(Descriptor::kTarget);
Node* const source = Parameter(Descriptor::kSource);
Node* const source_length = Parameter(Descriptor::kSourceLength);
Node* const start = Parameter(Descriptor::kStart);
Node* const depth = Parameter(Descriptor::kDepth);

Return(
FlattenIntoArray(context, target, source, source_length, start, depth));
}

// https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray
TF_BUILTIN(FlatMapIntoArray, ArrayFlattenAssembler) {
Node* const context = Parameter(Descriptor::kContext);
Node* const target = Parameter(Descriptor::kTarget);
Node* const source = Parameter(Descriptor::kSource);
Node* const source_length = Parameter(Descriptor::kSourceLength);
Node* const start = Parameter(Descriptor::kStart);
Node* const depth = Parameter(Descriptor::kDepth);
Node* const mapper_function = Parameter(Descriptor::kMapperFunction);
Node* const this_arg = Parameter(Descriptor::kThisArg);

Return(FlattenIntoArray(context, target, source, source_length, start, depth,
mapper_function, this_arg));
}

// https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatten
TF_BUILTIN(ArrayPrototypeFlatten, CodeStubAssembler) {
Node* const argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
CodeStubArguments args(this, argc);
Node* const context = Parameter(BuiltinDescriptor::kContext);
Node* const receiver = args.GetReceiver();
Node* const depth = args.GetOptionalArgumentValue(0);

// 1. Let O be ? ToObject(this value).
Node* const o = ToObject(context, receiver);

// 2. Let sourceLen be ? ToLength(? Get(O, "length")).
Node* const source_length =
ToLength_Inline(context, GetProperty(context, o, LengthStringConstant()));

// 3. Let depthNum be 1.
VARIABLE(var_depth_num, MachineRepresentation::kTagged, SmiConstant(1));

// 4. If depth is not undefined, then
Label done(this);
GotoIf(IsUndefined(depth), &done);
{
// a. Set depthNum to ? ToInteger(depth).
var_depth_num.Bind(ToInteger_Inline(context, depth));
Goto(&done);
}
BIND(&done);

// 5. Let A be ? ArraySpeciesCreate(O, 0).
Node* const constructor =
CallRuntime(Runtime::kArraySpeciesConstructor, context, o);
Node* const a = ConstructJS(CodeFactory::Construct(isolate()), context,
constructor, SmiConstant(0));

// 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum).
CallBuiltin(Builtins::kFlattenIntoArray, context, a, o, source_length,
SmiConstant(0), var_depth_num.value());

// 7. Return A.
args.PopAndReturn(a);
}

// https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap
TF_BUILTIN(ArrayPrototypeFlatMap, CodeStubAssembler) {
Node* const argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
CodeStubArguments args(this, argc);
Node* const context = Parameter(BuiltinDescriptor::kContext);
Node* const receiver = args.GetReceiver();
Node* const mapper_function = args.GetOptionalArgumentValue(0);

// 1. Let O be ? ToObject(this value).
Node* const o = ToObject(context, receiver);

// 2. Let sourceLen be ? ToLength(? Get(O, "length")).
Node* const source_length =
ToLength_Inline(context, GetProperty(context, o, LengthStringConstant()));

// 3. If IsCallable(mapperFunction) is false, throw a TypeError exception.
Label if_not_callable(this, Label::kDeferred);
GotoIf(TaggedIsSmi(mapper_function), &if_not_callable);
GotoIfNot(IsCallable(mapper_function), &if_not_callable);

// 4. If thisArg is present, let T be thisArg; else let T be undefined.
Node* const t = args.GetOptionalArgumentValue(1);

// 5. Let A be ? ArraySpeciesCreate(O, 0).
Node* const constructor =
CallRuntime(Runtime::kArraySpeciesConstructor, context, o);
Node* const a = ConstructJS(CodeFactory::Construct(isolate()), context,
constructor, SmiConstant(0));

// 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, T).
CallBuiltin(Builtins::kFlatMapIntoArray, context, a, o, source_length,
SmiConstant(0), SmiConstant(1), mapper_function, t);

// 7. Return A.
args.PopAndReturn(a);

BIND(&if_not_callable);
{ ThrowTypeError(context, MessageTemplate::kMapperFunctionNonCallable); }
}

} // namespace internal
} // namespace v8
@@ -367,6 +367,14 @@ namespace internal {
TFJ(ArrayPrototypeValues, 0) \
/* ES6 #sec-%arrayiteratorprototype%.next */ \
TFJ(ArrayIteratorPrototypeNext, 0) \
/* https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray */ \
TFS(FlattenIntoArray, kTarget, kSource, kSourceLength, kStart, kDepth) \
TFS(FlatMapIntoArray, kTarget, kSource, kSourceLength, kStart, kDepth, \
kMapperFunction, kThisArg) \
/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatten */ \
TFJ(ArrayPrototypeFlatten, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \
TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
\
/* ArrayBuffer */ \
/* ES #sec-arraybuffer-constructor */ \
@@ -6174,7 +6174,8 @@ TNode<Number> CodeStubAssembler::ToLength_Inline(SloppyTNode<Context> context,
}

TNode<Number> CodeStubAssembler::ToInteger_Inline(
TNode<Context> context, TNode<Object> input, ToIntegerTruncationMode mode) {
SloppyTNode<Context> context, SloppyTNode<Object> input,
ToIntegerTruncationMode mode) {
Builtins::Name builtin = (mode == kNoTruncation)
? Builtins::kToInteger
: Builtins::kToInteger_TruncateMinusZero;
@@ -10022,7 +10023,7 @@ void CodeStubAssembler::BranchIfSameValue(Node* lhs, Node* rhs, Label* if_true,
}

TNode<Oddball> CodeStubAssembler::HasProperty(SloppyTNode<HeapObject> object,
SloppyTNode<Name> key,
SloppyTNode<Object> key,
SloppyTNode<Context> context,
HasPropertyLookupMode mode) {
Label call_runtime(this, Label::kDeferred), return_true(this),
@@ -1315,7 +1315,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
SloppyTNode<Object> input);

// ES6 7.1.4 ToInteger ( argument )
TNode<Number> ToInteger_Inline(TNode<Context> context, TNode<Object> input,
TNode<Number> ToInteger_Inline(SloppyTNode<Context> context,
SloppyTNode<Object> input,
ToIntegerTruncationMode mode = kNoTruncation);
TNode<Number> ToInteger(SloppyTNode<Context> context,
SloppyTNode<Object> input,
@@ -1910,7 +1911,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
enum HasPropertyLookupMode { kHasProperty, kForInHasProperty };

TNode<Oddball> HasProperty(SloppyTNode<HeapObject> object,
SloppyTNode<Name> key,
SloppyTNode<Object> key,
SloppyTNode<Context> context,
HasPropertyLookupMode mode);

@@ -212,7 +212,8 @@ DEFINE_IMPLICATION(harmony_class_fields, harmony_private_fields)
V(harmony_array_prototype_values, "harmony Array.prototype.values") \
V(harmony_do_expressions, "harmony do-expressions") \
V(harmony_class_fields, "harmony fields in class literals") \
V(harmony_static_fields, "harmony static fields in class literals")
V(harmony_static_fields, "harmony static fields in class literals") \
V(harmony_array_flatten, "harmony Array.prototype.flat{ten,Map}")

// Features that are complete (but still behind --harmony/es-staging flag).
#define HARMONY_STAGED(V) \
@@ -343,6 +343,7 @@ class ErrorUtils : public AllStatic {
T(IteratorSymbolNonCallable, "Found non-callable @@iterator") \
T(IteratorValueNotAnObject, "Iterator value % is not an entry object") \
T(LanguageID, "Language ID should be string or object.") \
T(MapperFunctionNonCallable, "flatMap mapper function is not callable") \
T(MethodCalledOnWrongObject, \
"Method % called on a non-object or on a wrong type of object.") \
T(MethodInvokedOnNullOrUndefined, \
@@ -643,6 +644,9 @@ class ErrorUtils : public AllStatic {
T(NoCatchOrFinally, "Missing catch or finally after try") \
T(NotIsvar, "builtin %%IS_VAR: not a variable") \
T(ParamAfterRest, "Rest parameter must be last formal parameter") \
T(FlattenPastSafeLength, \
"Flattening % elements on an array-like of length % " \
"is disallowed, as the total surpasses 2**53-1") \
T(PushPastSafeLength, \
"Pushing % elements on an array-like of length % " \
"is disallowed, as the total surpasses 2**53-1") \
@@ -367,7 +367,7 @@ bytecodes: [
B(TestTypeOf), U8(6),
B(JumpIfFalse), U8(4),
B(Jump), U8(18),
B(Wide), B(LdaSmi), I16(146),
B(Wide), B(LdaSmi), I16(147),
B(Star), R(18),
B(LdaConstant), U8(15),
B(Star), R(19),

0 comments on commit 697d39a

Please sign in to comment.
You can’t perform that action at this time.