Skip to content

Commit 697d39a

Browse files
mathiasbynensCommit Bot
authored andcommitted
[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}
1 parent f8fb4a5 commit 697d39a

17 files changed

Lines changed: 469 additions & 27 deletions

‎src/bootstrapper.cc‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4251,6 +4251,17 @@ void Genesis::InitializeGlobal_harmony_array_prototype_values() {
42514251
NONE);
42524252
}
42534253

4254+
void Genesis::InitializeGlobal_harmony_array_flatten() {
4255+
if (!FLAG_harmony_array_flatten) return;
4256+
Handle<JSFunction> array_constructor(native_context()->array_function());
4257+
Handle<JSObject> array_prototype(
4258+
JSObject::cast(array_constructor->instance_prototype()));
4259+
SimpleInstallFunction(array_prototype, "flatten",
4260+
Builtins::kArrayPrototypeFlatten, 0, false, DONT_ENUM);
4261+
SimpleInstallFunction(array_prototype, "flatMap",
4262+
Builtins::kArrayPrototypeFlatMap, 1, false, DONT_ENUM);
4263+
}
4264+
42544265
void Genesis::InitializeGlobal_harmony_promise_finally() {
42554266
if (!FLAG_harmony_promise_finally) return;
42564267

‎src/builtins/builtins-array-gen.cc‎

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3845,5 +3845,260 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) {
38453845
}
38463846
}
38473847

3848+
namespace {
3849+
3850+
class ArrayFlattenAssembler : public CodeStubAssembler {
3851+
public:
3852+
explicit ArrayFlattenAssembler(compiler::CodeAssemblerState* state)
3853+
: CodeStubAssembler(state) {}
3854+
3855+
// https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray
3856+
Node* FlattenIntoArray(Node* context, Node* target, Node* source,
3857+
Node* source_length, Node* start, Node* depth,
3858+
Node* mapper_function = nullptr,
3859+
Node* this_arg = nullptr) {
3860+
CSA_ASSERT(this, IsJSReceiver(target));
3861+
CSA_ASSERT(this, IsJSReceiver(source));
3862+
CSA_ASSERT(this, IsNumberPositive(source_length));
3863+
CSA_ASSERT(this, IsNumberPositive(start));
3864+
CSA_ASSERT(this, IsNumber(depth));
3865+
3866+
// 1. Let targetIndex be start.
3867+
VARIABLE(var_target_index, MachineRepresentation::kTagged, start);
3868+
3869+
// 2. Let sourceIndex be 0.
3870+
VARIABLE(var_source_index, MachineRepresentation::kTagged, SmiConstant(0));
3871+
3872+
// 3. Repeat...
3873+
Label loop(this, {&var_target_index, &var_source_index}), done_loop(this);
3874+
Goto(&loop);
3875+
BIND(&loop);
3876+
{
3877+
Node* const source_index = var_source_index.value();
3878+
Node* const target_index = var_target_index.value();
3879+
3880+
// ...while sourceIndex < sourceLen
3881+
GotoIfNumberGreaterThanOrEqual(source_index, source_length, &done_loop);
3882+
3883+
// a. Let P be ! ToString(sourceIndex).
3884+
// b. Let exists be ? HasProperty(source, P).
3885+
CSA_ASSERT(this, SmiGreaterThanOrEqual(source_index, SmiConstant(0)));
3886+
Node* const exists =
3887+
HasProperty(source, source_index, context, kHasProperty);
3888+
3889+
// c. If exists is true, then
3890+
Label next(this);
3891+
GotoIfNot(IsTrue(exists), &next);
3892+
{
3893+
// i. Let element be ? Get(source, P).
3894+
Node* element = GetProperty(context, source, source_index);
3895+
3896+
// ii. If mapperFunction is present, then
3897+
if (mapper_function != nullptr) {
3898+
CSA_ASSERT(this, Word32Or(IsUndefined(mapper_function),
3899+
IsCallable(mapper_function)));
3900+
DCHECK_NOT_NULL(this_arg);
3901+
3902+
// 1. Set element to ? Call(mapperFunction, thisArg , « element,
3903+
// sourceIndex, source »).
3904+
element =
3905+
CallJS(CodeFactory::Call(isolate()), context, mapper_function,
3906+
this_arg, element, source_index, source);
3907+
}
3908+
3909+
// iii. Let shouldFlatten be false.
3910+
Label if_flatten_array(this), if_flatten_proxy(this, Label::kDeferred),
3911+
if_noflatten(this);
3912+
// iv. If depth > 0, then
3913+
GotoIfNumberGreaterThanOrEqual(SmiConstant(0), depth, &if_noflatten);
3914+
// 1. Set shouldFlatten to ? IsArray(element).
3915+
GotoIf(TaggedIsSmi(element), &if_noflatten);
3916+
GotoIf(IsJSArray(element), &if_flatten_array);
3917+
GotoIfNot(IsJSProxy(element), &if_noflatten);
3918+
Branch(IsTrue(CallRuntime(Runtime::kArrayIsArray, context, element)),
3919+
&if_flatten_proxy, &if_noflatten);
3920+
3921+
BIND(&if_flatten_array);
3922+
{
3923+
CSA_ASSERT(this, IsJSArray(element));
3924+
3925+
// 1. Let elementLen be ? ToLength(? Get(element, "length")).
3926+
Node* const element_length =
3927+
LoadObjectField(element, JSArray::kLengthOffset);
3928+
3929+
// 2. Set targetIndex to ? FlattenIntoArray(target, element,
3930+
// elementLen, targetIndex,
3931+
// depth - 1).
3932+
var_target_index.Bind(
3933+
CallBuiltin(Builtins::kFlattenIntoArray, context, target, element,
3934+
element_length, target_index, NumberDec(depth)));
3935+
Goto(&next);
3936+
}
3937+
3938+
BIND(&if_flatten_proxy);
3939+
{
3940+
CSA_ASSERT(this, IsJSProxy(element));
3941+
3942+
// 1. Let elementLen be ? ToLength(? Get(element, "length")).
3943+
Node* const element_length = ToLength_Inline(
3944+
context, GetProperty(context, element, LengthStringConstant()));
3945+
3946+
// 2. Set targetIndex to ? FlattenIntoArray(target, element,
3947+
// elementLen, targetIndex,
3948+
// depth - 1).
3949+
var_target_index.Bind(
3950+
CallBuiltin(Builtins::kFlattenIntoArray, context, target, element,
3951+
element_length, target_index, NumberDec(depth)));
3952+
Goto(&next);
3953+
}
3954+
3955+
BIND(&if_noflatten);
3956+
{
3957+
// 1. If targetIndex >= 2^53-1, throw a TypeError exception.
3958+
Label throw_error(this, Label::kDeferred);
3959+
GotoIfNumberGreaterThanOrEqual(
3960+
target_index, NumberConstant(kMaxSafeInteger), &throw_error);
3961+
3962+
// 2. Perform ? CreateDataPropertyOrThrow(target,
3963+
// ! ToString(targetIndex),
3964+
// element).
3965+
CallRuntime(Runtime::kCreateDataProperty, context, target,
3966+
target_index, element);
3967+
3968+
// 3. Increase targetIndex by 1.
3969+
var_target_index.Bind(NumberInc(target_index));
3970+
Goto(&next);
3971+
3972+
BIND(&throw_error);
3973+
ThrowTypeError(context, MessageTemplate::kFlattenPastSafeLength,
3974+
source_length, target_index);
3975+
}
3976+
}
3977+
BIND(&next);
3978+
3979+
// d. Increase sourceIndex by 1.
3980+
var_source_index.Bind(NumberInc(source_index));
3981+
Goto(&loop);
3982+
}
3983+
3984+
BIND(&done_loop);
3985+
return var_target_index.value();
3986+
}
3987+
};
3988+
3989+
} // namespace
3990+
3991+
// https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray
3992+
TF_BUILTIN(FlattenIntoArray, ArrayFlattenAssembler) {
3993+
Node* const context = Parameter(Descriptor::kContext);
3994+
Node* const target = Parameter(Descriptor::kTarget);
3995+
Node* const source = Parameter(Descriptor::kSource);
3996+
Node* const source_length = Parameter(Descriptor::kSourceLength);
3997+
Node* const start = Parameter(Descriptor::kStart);
3998+
Node* const depth = Parameter(Descriptor::kDepth);
3999+
4000+
Return(
4001+
FlattenIntoArray(context, target, source, source_length, start, depth));
4002+
}
4003+
4004+
// https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray
4005+
TF_BUILTIN(FlatMapIntoArray, ArrayFlattenAssembler) {
4006+
Node* const context = Parameter(Descriptor::kContext);
4007+
Node* const target = Parameter(Descriptor::kTarget);
4008+
Node* const source = Parameter(Descriptor::kSource);
4009+
Node* const source_length = Parameter(Descriptor::kSourceLength);
4010+
Node* const start = Parameter(Descriptor::kStart);
4011+
Node* const depth = Parameter(Descriptor::kDepth);
4012+
Node* const mapper_function = Parameter(Descriptor::kMapperFunction);
4013+
Node* const this_arg = Parameter(Descriptor::kThisArg);
4014+
4015+
Return(FlattenIntoArray(context, target, source, source_length, start, depth,
4016+
mapper_function, this_arg));
4017+
}
4018+
4019+
// https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatten
4020+
TF_BUILTIN(ArrayPrototypeFlatten, CodeStubAssembler) {
4021+
Node* const argc =
4022+
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
4023+
CodeStubArguments args(this, argc);
4024+
Node* const context = Parameter(BuiltinDescriptor::kContext);
4025+
Node* const receiver = args.GetReceiver();
4026+
Node* const depth = args.GetOptionalArgumentValue(0);
4027+
4028+
// 1. Let O be ? ToObject(this value).
4029+
Node* const o = ToObject(context, receiver);
4030+
4031+
// 2. Let sourceLen be ? ToLength(? Get(O, "length")).
4032+
Node* const source_length =
4033+
ToLength_Inline(context, GetProperty(context, o, LengthStringConstant()));
4034+
4035+
// 3. Let depthNum be 1.
4036+
VARIABLE(var_depth_num, MachineRepresentation::kTagged, SmiConstant(1));
4037+
4038+
// 4. If depth is not undefined, then
4039+
Label done(this);
4040+
GotoIf(IsUndefined(depth), &done);
4041+
{
4042+
// a. Set depthNum to ? ToInteger(depth).
4043+
var_depth_num.Bind(ToInteger_Inline(context, depth));
4044+
Goto(&done);
4045+
}
4046+
BIND(&done);
4047+
4048+
// 5. Let A be ? ArraySpeciesCreate(O, 0).
4049+
Node* const constructor =
4050+
CallRuntime(Runtime::kArraySpeciesConstructor, context, o);
4051+
Node* const a = ConstructJS(CodeFactory::Construct(isolate()), context,
4052+
constructor, SmiConstant(0));
4053+
4054+
// 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum).
4055+
CallBuiltin(Builtins::kFlattenIntoArray, context, a, o, source_length,
4056+
SmiConstant(0), var_depth_num.value());
4057+
4058+
// 7. Return A.
4059+
args.PopAndReturn(a);
4060+
}
4061+
4062+
// https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap
4063+
TF_BUILTIN(ArrayPrototypeFlatMap, CodeStubAssembler) {
4064+
Node* const argc =
4065+
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
4066+
CodeStubArguments args(this, argc);
4067+
Node* const context = Parameter(BuiltinDescriptor::kContext);
4068+
Node* const receiver = args.GetReceiver();
4069+
Node* const mapper_function = args.GetOptionalArgumentValue(0);
4070+
4071+
// 1. Let O be ? ToObject(this value).
4072+
Node* const o = ToObject(context, receiver);
4073+
4074+
// 2. Let sourceLen be ? ToLength(? Get(O, "length")).
4075+
Node* const source_length =
4076+
ToLength_Inline(context, GetProperty(context, o, LengthStringConstant()));
4077+
4078+
// 3. If IsCallable(mapperFunction) is false, throw a TypeError exception.
4079+
Label if_not_callable(this, Label::kDeferred);
4080+
GotoIf(TaggedIsSmi(mapper_function), &if_not_callable);
4081+
GotoIfNot(IsCallable(mapper_function), &if_not_callable);
4082+
4083+
// 4. If thisArg is present, let T be thisArg; else let T be undefined.
4084+
Node* const t = args.GetOptionalArgumentValue(1);
4085+
4086+
// 5. Let A be ? ArraySpeciesCreate(O, 0).
4087+
Node* const constructor =
4088+
CallRuntime(Runtime::kArraySpeciesConstructor, context, o);
4089+
Node* const a = ConstructJS(CodeFactory::Construct(isolate()), context,
4090+
constructor, SmiConstant(0));
4091+
4092+
// 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, T).
4093+
CallBuiltin(Builtins::kFlatMapIntoArray, context, a, o, source_length,
4094+
SmiConstant(0), SmiConstant(1), mapper_function, t);
4095+
4096+
// 7. Return A.
4097+
args.PopAndReturn(a);
4098+
4099+
BIND(&if_not_callable);
4100+
{ ThrowTypeError(context, MessageTemplate::kMapperFunctionNonCallable); }
4101+
}
4102+
38484103
} // namespace internal
38494104
} // namespace v8

‎src/builtins/builtins-definitions.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,14 @@ namespace internal {
367367
TFJ(ArrayPrototypeValues, 0) \
368368
/* ES6 #sec-%arrayiteratorprototype%.next */ \
369369
TFJ(ArrayIteratorPrototypeNext, 0) \
370+
/* https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray */ \
371+
TFS(FlattenIntoArray, kTarget, kSource, kSourceLength, kStart, kDepth) \
372+
TFS(FlatMapIntoArray, kTarget, kSource, kSourceLength, kStart, kDepth, \
373+
kMapperFunction, kThisArg) \
374+
/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatten */ \
375+
TFJ(ArrayPrototypeFlatten, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
376+
/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \
377+
TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
370378
\
371379
/* ArrayBuffer */ \
372380
/* ES #sec-arraybuffer-constructor */ \

‎src/code-stub-assembler.cc‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6174,7 +6174,8 @@ TNode<Number> CodeStubAssembler::ToLength_Inline(SloppyTNode<Context> context,
61746174
}
61756175

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

1002410025
TNode<Oddball> CodeStubAssembler::HasProperty(SloppyTNode<HeapObject> object,
10025-
SloppyTNode<Name> key,
10026+
SloppyTNode<Object> key,
1002610027
SloppyTNode<Context> context,
1002710028
HasPropertyLookupMode mode) {
1002810029
Label call_runtime(this, Label::kDeferred), return_true(this),

‎src/code-stub-assembler.h‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,7 +1315,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
13151315
SloppyTNode<Object> input);
13161316

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

19121913
TNode<Oddball> HasProperty(SloppyTNode<HeapObject> object,
1913-
SloppyTNode<Name> key,
1914+
SloppyTNode<Object> key,
19141915
SloppyTNode<Context> context,
19151916
HasPropertyLookupMode mode);
19161917

‎src/flag-definitions.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,8 @@ DEFINE_IMPLICATION(harmony_class_fields, harmony_private_fields)
212212
V(harmony_array_prototype_values, "harmony Array.prototype.values") \
213213
V(harmony_do_expressions, "harmony do-expressions") \
214214
V(harmony_class_fields, "harmony fields in class literals") \
215-
V(harmony_static_fields, "harmony static fields in class literals")
215+
V(harmony_static_fields, "harmony static fields in class literals") \
216+
V(harmony_array_flatten, "harmony Array.prototype.flat{ten,Map}")
216217

217218
// Features that are complete (but still behind --harmony/es-staging flag).
218219
#define HARMONY_STAGED(V) \

‎src/messages.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ class ErrorUtils : public AllStatic {
343343
T(IteratorSymbolNonCallable, "Found non-callable @@iterator") \
344344
T(IteratorValueNotAnObject, "Iterator value % is not an entry object") \
345345
T(LanguageID, "Language ID should be string or object.") \
346+
T(MapperFunctionNonCallable, "flatMap mapper function is not callable") \
346347
T(MethodCalledOnWrongObject, \
347348
"Method % called on a non-object or on a wrong type of object.") \
348349
T(MethodInvokedOnNullOrUndefined, \
@@ -643,6 +644,9 @@ class ErrorUtils : public AllStatic {
643644
T(NoCatchOrFinally, "Missing catch or finally after try") \
644645
T(NotIsvar, "builtin %%IS_VAR: not a variable") \
645646
T(ParamAfterRest, "Rest parameter must be last formal parameter") \
647+
T(FlattenPastSafeLength, \
648+
"Flattening % elements on an array-like of length % " \
649+
"is disallowed, as the total surpasses 2**53-1") \
646650
T(PushPastSafeLength, \
647651
"Pushing % elements on an array-like of length % " \
648652
"is disallowed, as the total surpasses 2**53-1") \

‎test/cctest/interpreter/bytecode_expectations/AsyncGenerators.golden‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ bytecodes: [
367367
B(TestTypeOf), U8(6),
368368
B(JumpIfFalse), U8(4),
369369
B(Jump), U8(18),
370-
B(Wide), B(LdaSmi), I16(146),
370+
B(Wide), B(LdaSmi), I16(147),
371371
B(Star), R(18),
372372
B(LdaConstant), U8(15),
373373
B(Star), R(19),

0 commit comments

Comments
 (0)