| Index: src/builtins/builtins-array.cc
|
| diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
|
| index 85a49284b72953e4cace97be637c92fb54e98058..3537b4243572f1805de8d7c5197d88ac6c2ff084 100644
|
| --- a/src/builtins/builtins-array.cc
|
| +++ b/src/builtins/builtins-array.cc
|
| @@ -415,67 +415,162 @@ BUILTIN(ArrayUnshift) {
|
| return Smi::FromInt(new_length);
|
| }
|
|
|
| -class ForEachCodeStubAssembler : public CodeStubAssembler {
|
| +class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler {
|
| public:
|
| - explicit ForEachCodeStubAssembler(compiler::CodeAssemblerState* state)
|
| + explicit ArrayBuiltinCodeStubAssembler(compiler::CodeAssemblerState* state)
|
| : CodeStubAssembler(state) {}
|
|
|
| - void VisitOneElement(Node* context, Node* this_arg, Node* o, Node* k,
|
| - Node* callbackfn) {
|
| - Comment("begin VisitOneElement");
|
| + typedef std::function<Node*(Node* o, Node* len)> BuiltinResultGenerator;
|
| + typedef std::function<void(Node* a, Node* pK, Node* value)>
|
| + CallResultProcessor;
|
| +
|
| + void GenerateArrayIteratingBuiltinBody(
|
| + const char* name, Node* receiver, Node* callbackfn, Node* this_arg,
|
| + Node* context, const BuiltinResultGenerator& generator,
|
| + const CallResultProcessor& processor) {
|
| + Variable k(this, MachineRepresentation::kTagged, SmiConstant(0));
|
| + Label non_array(this), slow(this, &k), array_changes(this, &k);
|
| +
|
| + // TODO(danno): Seriously? Do we really need to throw the exact error
|
| + // message on null and undefined so that the webkit tests pass?
|
| + Label throw_null_undefined_exception(this, Label::kDeferred);
|
| + GotoIf(WordEqual(receiver, NullConstant()),
|
| + &throw_null_undefined_exception);
|
| + GotoIf(WordEqual(receiver, UndefinedConstant()),
|
| + &throw_null_undefined_exception);
|
| +
|
| + // By the book: taken directly from the ECMAScript 2015 specification
|
| +
|
| + // 1. Let O be ToObject(this value).
|
| + // 2. ReturnIfAbrupt(O)
|
| + Node* o = CallStub(CodeFactory::ToObject(isolate()), context, receiver);
|
| +
|
| + // 3. Let len be ToLength(Get(O, "length")).
|
| + // 4. ReturnIfAbrupt(len).
|
| + Variable merged_length(this, MachineRepresentation::kTagged);
|
| + Label has_length(this, &merged_length), not_js_array(this);
|
| + GotoIf(DoesntHaveInstanceType(o, JS_ARRAY_TYPE), ¬_js_array);
|
| + merged_length.Bind(LoadJSArrayLength(o));
|
| + Goto(&has_length);
|
| + Bind(¬_js_array);
|
| + Node* len_property =
|
| + CallStub(CodeFactory::GetProperty(isolate()), context, o,
|
| + HeapConstant(isolate()->factory()->length_string()));
|
| + merged_length.Bind(
|
| + CallStub(CodeFactory::ToLength(isolate()), context, len_property));
|
| + Goto(&has_length);
|
| + Bind(&has_length);
|
| + Node* len = merged_length.value();
|
| +
|
| + // 5. If IsCallable(callbackfn) is false, throw a TypeError exception.
|
| + Label type_exception(this, Label::kDeferred);
|
| + Label done(this);
|
| + GotoIf(TaggedIsSmi(callbackfn), &type_exception);
|
| + Branch(IsCallableMap(LoadMap(callbackfn)), &done, &type_exception);
|
| +
|
| + Bind(&throw_null_undefined_exception);
|
| + {
|
| + CallRuntime(
|
| + Runtime::kThrowTypeError, context,
|
| + SmiConstant(MessageTemplate::kCalledOnNullOrUndefined),
|
| + HeapConstant(isolate()->factory()->NewStringFromAsciiChecked(name)));
|
| + Unreachable();
|
| + }
|
| +
|
| + Bind(&type_exception);
|
| + {
|
| + CallRuntime(Runtime::kThrowTypeError, context,
|
| + SmiConstant(MessageTemplate::kCalledNonCallable), callbackfn);
|
| + Unreachable();
|
| + }
|
| +
|
| + Bind(&done);
|
| +
|
| + Node* a = generator(o, len);
|
| +
|
| + // 6. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
| + // [Already done by the arguments adapter]
|
| +
|
| + HandleFastElements(context, this_arg, o, len, callbackfn, processor, a, k,
|
| + &slow);
|
| +
|
| + // 7. Let k be 0.
|
| + // Already done above in initialization of the Variable k
|
| +
|
| + Bind(&slow);
|
| + {
|
| + // 8. Repeat, while k < len
|
| + Label loop(this, &k);
|
| + Label after_loop(this);
|
| + Goto(&loop);
|
| + Bind(&loop);
|
| + {
|
| + GotoUnlessNumberLessThan(k.value(), len, &after_loop);
|
| +
|
| + Label done_element(this);
|
| + // a. Let Pk be ToString(k).
|
| + Node* p_k = ToString(context, k.value());
|
|
|
| - // a. Let Pk be ToString(k).
|
| - Node* p_k = ToString(context, k);
|
| + // b. Let kPresent be HasProperty(O, Pk).
|
| + // c. ReturnIfAbrupt(kPresent).
|
| + Node* k_present =
|
| + CallStub(CodeFactory::HasProperty(isolate()), context, p_k, o);
|
|
|
| - // b. Let kPresent be HasProperty(O, Pk).
|
| - // c. ReturnIfAbrupt(kPresent).
|
| - Node* k_present =
|
| - CallStub(CodeFactory::HasProperty(isolate()), context, p_k, o);
|
| + // d. If kPresent is true, then
|
| + GotoIf(WordNotEqual(k_present, TrueConstant()), &done_element);
|
|
|
| - // d. If kPresent is true, then
|
| - Label not_present(this);
|
| - GotoIf(WordNotEqual(k_present, TrueConstant()), ¬_present);
|
| + // i. Let kValue be Get(O, Pk).
|
| + // ii. ReturnIfAbrupt(kValue).
|
| + Node* k_value = CallStub(CodeFactory::GetProperty(isolate()), context,
|
| + o, k.value());
|
|
|
| - // i. Let kValue be Get(O, Pk).
|
| - // ii. ReturnIfAbrupt(kValue).
|
| - Node* k_value =
|
| - CallStub(CodeFactory::GetProperty(isolate()), context, o, k);
|
| + // iii. Let funcResult be Call(callbackfn, T, «kValue, k, O»).
|
| + // iv. ReturnIfAbrupt(funcResult).
|
| + Node* result = CallJS(CodeFactory::Call(isolate()), context, callbackfn,
|
| + this_arg, k_value, k.value(), o);
|
|
|
| - // iii. Let funcResult be Call(callbackfn, T, «kValue, k, O»).
|
| - // iv. ReturnIfAbrupt(funcResult).
|
| - CallJS(CodeFactory::Call(isolate()), context, callbackfn, this_arg, k_value,
|
| - k, o);
|
| + processor(a, p_k, result);
|
| + Goto(&done_element);
|
| + Bind(&done_element);
|
|
|
| - Goto(¬_present);
|
| - Bind(¬_present);
|
| - Comment("end VisitOneElement");
|
| + // e. Increase k by 1.
|
| + k.Bind(NumberInc(k.value()));
|
| + Goto(&loop);
|
| + }
|
| + Bind(&after_loop);
|
| + Return(a);
|
| + }
|
| }
|
|
|
| - void VisitAllFastElements(Node* context, ElementsKind kind, Node* this_arg,
|
| - Node* o, Node* len, Node* callbackfn,
|
| - ParameterMode mode) {
|
| - Comment("begin VisitAllFastElements");
|
| + private:
|
| + Node* VisitAllFastElementsOneKind(Node* context, ElementsKind kind,
|
| + Node* this_arg, Node* o, Node* len,
|
| + Node* callbackfn,
|
| + const CallResultProcessor& processor,
|
| + Node* a, Label* array_changed,
|
| + ParameterMode mode) {
|
| + Comment("begin VisitAllFastElementsOneKind");
|
| Variable original_map(this, MachineRepresentation::kTagged);
|
| original_map.Bind(LoadMap(o));
|
| VariableList list({&original_map}, zone());
|
| + Node* last_index = nullptr;
|
| BuildFastLoop(
|
| list, IntPtrOrSmiConstant(0, mode), TaggedToParameter(len, mode),
|
| - [context, kind, this, o, &original_map, callbackfn, this_arg,
|
| - mode](Node* index) {
|
| - Label one_element_done(this), array_changed(this, Label::kDeferred),
|
| - hole_element(this);
|
| + [=, &original_map, &last_index](Node* index) {
|
| + last_index = index;
|
| + Label one_element_done(this), hole_element(this);
|
|
|
| // Check if o's map has changed during the callback. If so, we have to
|
| // fall back to the slower spec implementation for the rest of the
|
| // iteration.
|
| Node* o_map = LoadMap(o);
|
| - GotoIf(WordNotEqual(o_map, original_map.value()), &array_changed);
|
| + GotoIf(WordNotEqual(o_map, original_map.value()), array_changed);
|
|
|
| // Check if o's length has changed during the callback and if the
|
| // index is now out of range of the new length.
|
| Node* tagged_index = ParameterToTagged(index, mode);
|
| GotoIf(SmiGreaterThanOrEqual(tagged_index, LoadJSArrayLength(o)),
|
| - &array_changed);
|
| + array_changed);
|
|
|
| // Re-load the elements array. If may have been resized.
|
| Node* elements = LoadElements(o);
|
| @@ -496,159 +591,129 @@ class ForEachCodeStubAssembler : public CodeStubAssembler {
|
| LoadDoubleWithHoleCheck(elements, offset, &hole_element);
|
| value = AllocateHeapNumberWithValue(double_value);
|
| }
|
| - CallJS(CodeFactory::Call(isolate()), context, callbackfn, this_arg,
|
| - value, tagged_index, o);
|
| + Node* result = CallJS(CodeFactory::Call(isolate()), context,
|
| + callbackfn, this_arg, value, tagged_index, o);
|
| + processor(a, tagged_index, result);
|
| Goto(&one_element_done);
|
|
|
| Bind(&hole_element);
|
| + // Check if o's prototype change unexpectedly has elements after the
|
| + // callback in the case of a hole.
|
| BranchIfPrototypesHaveNoElements(o_map, &one_element_done,
|
| - &array_changed);
|
| -
|
| - // O's changed during the forEach. Use the implementation precisely
|
| - // specified in the spec for the rest of the iteration, also making
|
| - // the failed original_map sticky in case of a subseuent change that
|
| - // goes back to the original map.
|
| - Bind(&array_changed);
|
| - VisitOneElement(context, this_arg, o, ParameterToTagged(index, mode),
|
| - callbackfn);
|
| - original_map.Bind(UndefinedConstant());
|
| - Goto(&one_element_done);
|
| + array_changed);
|
|
|
| Bind(&one_element_done);
|
| },
|
| 1, mode, IndexAdvanceMode::kPost);
|
| - Comment("end VisitAllFastElements");
|
| + Comment("end VisitAllFastElementsOneKind");
|
| + return last_index;
|
| }
|
| -};
|
|
|
| -TF_BUILTIN(ArrayForEach, ForEachCodeStubAssembler) {
|
| - Label non_array(this), examine_elements(this), fast_elements(this),
|
| - slow(this), maybe_double_elements(this), fast_double_elements(this);
|
| + void HandleFastElements(Node* context, Node* this_arg, Node* o, Node* len,
|
| + Node* callbackfn, CallResultProcessor processor,
|
| + Node* a, Variable& k, Label* slow) {
|
| + Label switch_on_elements_kind(this), fast_elements(this),
|
| + maybe_double_elements(this), fast_double_elements(this);
|
|
|
| - Node* receiver = Parameter(ForEachDescriptor::kReceiver);
|
| - Node* callbackfn = Parameter(ForEachDescriptor::kCallback);
|
| - Node* this_arg = Parameter(ForEachDescriptor::kThisArg);
|
| - Node* context = Parameter(ForEachDescriptor::kContext);
|
| + Comment("begin HandleFastElements");
|
| + // Non-smi lengths must use the slow path.
|
| + GotoIf(TaggedIsNotSmi(len), slow);
|
|
|
| - // TODO(danno): Seriously? Do we really need to throw the exact error message
|
| - // on null and undefined so that the webkit tests pass?
|
| - Label throw_null_undefined_exception(this, Label::kDeferred);
|
| - GotoIf(WordEqual(receiver, NullConstant()), &throw_null_undefined_exception);
|
| - GotoIf(WordEqual(receiver, UndefinedConstant()),
|
| - &throw_null_undefined_exception);
|
| -
|
| - // By the book: taken directly from the ECMAScript 2015 specification
|
| -
|
| - // 1. Let O be ToObject(this value).
|
| - // 2. ReturnIfAbrupt(O)
|
| - Node* o = CallStub(CodeFactory::ToObject(isolate()), context, receiver);
|
| -
|
| - // 3. Let len be ToLength(Get(O, "length")).
|
| - // 4. ReturnIfAbrupt(len).
|
| - Variable merged_length(this, MachineRepresentation::kTagged);
|
| - Label has_length(this, &merged_length), not_js_array(this);
|
| - GotoIf(DoesntHaveInstanceType(o, JS_ARRAY_TYPE), ¬_js_array);
|
| - merged_length.Bind(LoadJSArrayLength(o));
|
| - Goto(&has_length);
|
| - Bind(¬_js_array);
|
| - Node* len_property =
|
| - CallStub(CodeFactory::GetProperty(isolate()), context, o,
|
| - HeapConstant(isolate()->factory()->length_string()));
|
| - merged_length.Bind(
|
| - CallStub(CodeFactory::ToLength(isolate()), context, len_property));
|
| - Goto(&has_length);
|
| - Bind(&has_length);
|
| - Node* len = merged_length.value();
|
| -
|
| - // 5. If IsCallable(callbackfn) is false, throw a TypeError exception.
|
| - Label type_exception(this, Label::kDeferred);
|
| - GotoIf(TaggedIsSmi(callbackfn), &type_exception);
|
| - GotoIfNot(IsCallableMap(LoadMap(callbackfn)), &type_exception);
|
| -
|
| - // 6. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
| - // [Already done by the arguments adapter]
|
| -
|
| - // Non-smi lengths must use the slow path.
|
| - GotoIf(TaggedIsNotSmi(len), &slow);
|
| -
|
| - BranchIfFastJSArray(o, context,
|
| - CodeStubAssembler::FastJSArrayAccessMode::INBOUNDS_READ,
|
| - &examine_elements, &slow);
|
| -
|
| - Bind(&examine_elements);
|
| -
|
| - ParameterMode mode = OptimalParameterMode();
|
| -
|
| - // Select by ElementsKind
|
| - Node* o_map = LoadMap(o);
|
| - Node* bit_field2 = LoadMapBitField2(o_map);
|
| - Node* kind = DecodeWord32<Map::ElementsKindBits>(bit_field2);
|
| - Branch(Int32GreaterThan(kind, Int32Constant(FAST_HOLEY_ELEMENTS)),
|
| - &maybe_double_elements, &fast_elements);
|
| -
|
| - Bind(&fast_elements);
|
| - {
|
| - VisitAllFastElements(context, FAST_ELEMENTS, this_arg, o, len, callbackfn,
|
| - mode);
|
| + BranchIfFastJSArray(o, context,
|
| + CodeStubAssembler::FastJSArrayAccessMode::INBOUNDS_READ,
|
| + &switch_on_elements_kind, slow);
|
|
|
| - // No exception, return success
|
| - Return(UndefinedConstant());
|
| - }
|
| + Bind(&switch_on_elements_kind);
|
| + // Select by ElementsKind
|
| + Node* o_map = LoadMap(o);
|
| + Node* bit_field2 = LoadMapBitField2(o_map);
|
| + Node* kind = DecodeWord32<Map::ElementsKindBits>(bit_field2);
|
| + Branch(Int32GreaterThan(kind, Int32Constant(FAST_HOLEY_ELEMENTS)),
|
| + &maybe_double_elements, &fast_elements);
|
|
|
| - Bind(&maybe_double_elements);
|
| - Branch(Int32GreaterThan(kind, Int32Constant(FAST_HOLEY_DOUBLE_ELEMENTS)),
|
| - &slow, &fast_double_elements);
|
| + ParameterMode mode = OptimalParameterMode();
|
| + Bind(&fast_elements);
|
| + {
|
| + Label array_changed(this, Label::kDeferred);
|
| + Node* last_index = VisitAllFastElementsOneKind(
|
| + context, FAST_ELEMENTS, this_arg, o, len, callbackfn, processor, a,
|
| + &array_changed, mode);
|
|
|
| - Bind(&fast_double_elements);
|
| - {
|
| - VisitAllFastElements(context, FAST_DOUBLE_ELEMENTS, this_arg, o, len,
|
| - callbackfn, mode);
|
| + // No exception, return success
|
| + Return(a);
|
|
|
| - // No exception, return success
|
| - Return(UndefinedConstant());
|
| - }
|
| + Bind(&array_changed);
|
| + k.Bind(ParameterToTagged(last_index, mode));
|
| + Goto(slow);
|
| + }
|
|
|
| - Bind(&slow);
|
| - {
|
| - // By the book: taken from the ECMAScript 2015 specification (cont.)
|
| + Bind(&maybe_double_elements);
|
| + Branch(Int32GreaterThan(kind, Int32Constant(FAST_HOLEY_DOUBLE_ELEMENTS)),
|
| + slow, &fast_double_elements);
|
|
|
| - // 7. Let k be 0.
|
| - Variable k(this, MachineRepresentation::kTagged);
|
| - k.Bind(SmiConstant(0));
|
| -
|
| - // 8. Repeat, while k < len
|
| - Label loop(this, &k);
|
| - Label after_loop(this);
|
| - Goto(&loop);
|
| - Bind(&loop);
|
| + Bind(&fast_double_elements);
|
| {
|
| - GotoUnlessNumberLessThan(k.value(), len, &after_loop);
|
| + Label array_changed(this, Label::kDeferred);
|
| + Node* last_index = VisitAllFastElementsOneKind(
|
| + context, FAST_DOUBLE_ELEMENTS, this_arg, o, len, callbackfn,
|
| + processor, a, &array_changed, mode);
|
|
|
| - VisitOneElement(context, this_arg, o, k.value(), callbackfn);
|
| + // No exception, return success
|
| + Return(a);
|
|
|
| - // e. Increase k by 1.
|
| - k.Bind(NumberInc(k.value()));
|
| - Goto(&loop);
|
| + Bind(&array_changed);
|
| + k.Bind(ParameterToTagged(last_index, mode));
|
| + Goto(slow);
|
| }
|
| - Bind(&after_loop);
|
| - Return(UndefinedConstant());
|
| }
|
| +};
|
|
|
| - Bind(&throw_null_undefined_exception);
|
| - {
|
| - CallRuntime(Runtime::kThrowTypeError, context,
|
| - SmiConstant(MessageTemplate::kCalledOnNullOrUndefined),
|
| - HeapConstant(isolate()->factory()->NewStringFromAsciiChecked(
|
| - "Array.prototype.forEach")));
|
| - Unreachable();
|
| - }
|
| +TF_BUILTIN(ArrayForEach, ArrayBuiltinCodeStubAssembler) {
|
| + Node* receiver = Parameter(ForEachDescriptor::kReceiver);
|
| + Node* callbackfn = Parameter(ForEachDescriptor::kCallback);
|
| + Node* this_arg = Parameter(ForEachDescriptor::kThisArg);
|
| + Node* context = Parameter(ForEachDescriptor::kContext);
|
|
|
| - Bind(&type_exception);
|
| - {
|
| - CallRuntime(Runtime::kThrowTypeError, context,
|
| - SmiConstant(MessageTemplate::kCalledNonCallable), callbackfn);
|
| - Unreachable();
|
| - }
|
| + GenerateArrayIteratingBuiltinBody(
|
| + "Array.prototype.forEach", receiver, callbackfn, this_arg, context,
|
| + [=](Node*, Node*) { return UndefinedConstant(); },
|
| + [](Node* a, Node* p_k, Node* value) {});
|
| +}
|
| +
|
| +TF_BUILTIN(ArrayEvery, ArrayBuiltinCodeStubAssembler) {
|
| + Node* receiver = Parameter(ForEachDescriptor::kReceiver);
|
| + Node* callbackfn = Parameter(ForEachDescriptor::kCallback);
|
| + Node* this_arg = Parameter(ForEachDescriptor::kThisArg);
|
| + Node* context = Parameter(ForEachDescriptor::kContext);
|
| +
|
| + GenerateArrayIteratingBuiltinBody(
|
| + "Array.prototype.every", receiver, callbackfn, this_arg, context,
|
| + [=](Node*, Node*) { return TrueConstant(); },
|
| + [=](Node* a, Node* p_k, Node* value) {
|
| + Label true_continue(this), return_false(this);
|
| + BranchIfToBooleanIsTrue(value, &true_continue, &return_false);
|
| + Bind(&return_false);
|
| + Return(FalseConstant());
|
| + Bind(&true_continue);
|
| + });
|
| +}
|
| +
|
| +TF_BUILTIN(ArraySome, ArrayBuiltinCodeStubAssembler) {
|
| + Node* receiver = Parameter(ForEachDescriptor::kReceiver);
|
| + Node* callbackfn = Parameter(ForEachDescriptor::kCallback);
|
| + Node* this_arg = Parameter(ForEachDescriptor::kThisArg);
|
| + Node* context = Parameter(ForEachDescriptor::kContext);
|
| +
|
| + GenerateArrayIteratingBuiltinBody(
|
| + "Array.prototype.some", receiver, callbackfn, this_arg, context,
|
| + [=](Node*, Node*) { return FalseConstant(); },
|
| + [=](Node* a, Node* p_k, Node* value) {
|
| + Label false_continue(this), return_true(this);
|
| + BranchIfToBooleanIsTrue(value, &return_true, &false_continue);
|
| + Bind(&return_true);
|
| + Return(TrueConstant());
|
| + Bind(&false_continue);
|
| + });
|
| }
|
|
|
| BUILTIN(ArraySlice) {
|
|
|