Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1097)

Unified Diff: src/builtins/builtins-array.cc

Issue 2663033003: [builtins] TurboFan version of Array.prototype.forEach including fast path for FAST_ELEMENTS (Closed)
Patch Set: Rebase on ToT Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: src/builtins/builtins-array.cc
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index 0fdab4a8dfece3c39caf5a02e73077449b43362b..c0cc5852b9fccfe429fea4e0b026db1d7eee1a3c 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -426,6 +426,254 @@ BUILTIN(ArrayUnshift) {
return Smi::FromInt(new_length);
}
+class ForEachCodeStubAssembler : public CodeStubAssembler {
+ public:
+ explicit ForEachCodeStubAssembler(compiler::CodeAssemblerState* state)
+ : CodeStubAssembler(state) {}
+
+ void VisitOneElement(Node* context, Node* this_arg, Node* o, Node* k,
+ Node* callbackfn) {
+ Comment("begin VisitOneElement");
+
+ // 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);
+
+ // d. If kPresent is true, then
+ Label not_present(this);
+ GotoIf(WordNotEqual(k_present, TrueConstant()), &not_present);
+
+ // 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).
+ CallJS(CodeFactory::Call(isolate()), context, callbackfn, this_arg, k_value,
+ k, o);
+
+ Goto(&not_present);
+ Bind(&not_present);
+ Comment("end VisitOneElement");
+ }
+
+ void VisitAllFastElements(Node* context, ElementsKind kind, Node* this_arg,
+ Node* o, Node* len, Node* callbackfn,
+ ParameterMode mode) {
+ Comment("begin VisitAllFastElements");
+ Variable original_map(this, MachineRepresentation::kTagged);
+ original_map.Bind(LoadMap(o));
+ VariableList list({&original_map}, zone());
+ 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);
+
+ // 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.
+ GotoIf(WordNotEqual(LoadMap(o), 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);
+
+ // Re-load the elements array. If may have been resized.
+ Node* elements = LoadElements(o);
+
+ // Fast case: load the element directly from the elements FixedArray
+ // and call the callback if the element is not the hole.
+ DCHECK(kind == FAST_ELEMENTS || kind == FAST_DOUBLE_ELEMENTS);
+ int base_size = kind == FAST_ELEMENTS
+ ? FixedArray::kHeaderSize
+ : (FixedArray::kHeaderSize - kHeapObjectTag);
+ Node* offset = ElementOffsetFromIndex(index, kind, mode, base_size);
+ Node* value = nullptr;
+ if (kind == FAST_ELEMENTS) {
+ value = LoadObjectField(elements, offset);
+ GotoIf(WordEqual(value, TheHoleConstant()), &one_element_done);
+ } else {
+ Node* double_value =
+ LoadDoubleWithHoleCheck(elements, offset, &one_element_done);
+ value = AllocateHeapNumberWithValue(double_value);
+ }
Benedikt Meurer 2017/02/02 05:22:38 You need to handle holey arrays here. I.e. upon re
danno 2017/02/02 17:15:48 Done.
+ CallJS(CodeFactory::Call(isolate()), context, callbackfn, this_arg,
+ value, tagged_index, o);
+ Goto(&one_element_done);
+
+ // 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);
+
+ Bind(&one_element_done);
+ },
+ 1, mode, IndexAdvanceMode::kPost);
+ Comment("end VisitAllFastElements");
+ }
+};
+
+TF_BUILTIN(ArrayForEach, ForEachCodeStubAssembler) {
+ Node* argc = Parameter(BuiltinDescriptor::kArgumentsCount);
+ Node* context = Parameter(BuiltinDescriptor::kContext);
+ Label non_array(this), examine_elements(this), fast_elements(this),
+ slow(this), maybe_double_elements(this), fast_double_elements(this);
+
+ Node* argc_intptr = ChangeInt32ToIntPtr(argc);
+ CodeStubArguments args(this, argc_intptr);
+ Node* receiver = args.GetReceiver();
+
+ Variable callbackfn(this, MachineRepresentation::kTagged);
Benedikt Meurer 2017/02/02 05:22:38 Wouldn't it be easier to just use the arguments ad
danno 2017/02/02 17:15:48 Done.
+ Label no_callbackfn(this);
+ callbackfn.Bind(UndefinedConstant());
+ GotoIf(WordEqual(argc_intptr, IntPtrConstant(0)), &no_callbackfn);
+ callbackfn.Bind(args.AtIndex(0));
+ Goto(&no_callbackfn);
+ Bind(&no_callbackfn);
+
+ // TODO(danno): Seriously? Do we really need to throw the exact error message
Benedikt Meurer 2017/02/02 05:22:38 I think it's fine to just let ToObject fail and re
danno 2017/02/02 17:15:48 For consistency with the other Array builtins, I'm
+ // 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), &not_js_array);
+ merged_length.Bind(LoadJSArrayLength(o));
+ Goto(&has_length);
+ Bind(&not_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.value()), &type_exception);
+ GotoUnless(IsCallableMap(LoadMap(callbackfn.value())), &type_exception);
+
+ // 6. If thisArg was supplied, let T be thisArg; else let T be undefined.
+ Variable this_arg(this, MachineRepresentation::kTagged);
+ this_arg.Bind(UndefinedConstant());
+ Label this_arg_done(this);
+ GotoIf(WordEqual(argc_intptr, IntPtrConstant(1)), &this_arg_done);
+ this_arg.Bind(args.AtIndex(1));
+ Goto(&this_arg_done);
+ Bind(&this_arg_done);
+
+ // 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.value(), o, len,
+ callbackfn.value(), mode);
+
+ // No exception, return success
+ args.PopAndReturn(UndefinedConstant());
+ }
+
+ Bind(&maybe_double_elements);
+ Branch(Int32GreaterThan(kind, Int32Constant(FAST_HOLEY_DOUBLE_ELEMENTS)),
+ &slow, &fast_double_elements);
+
+ Bind(&fast_double_elements);
+ {
+ VisitAllFastElements(context, FAST_DOUBLE_ELEMENTS, this_arg.value(), o,
+ len, callbackfn.value(), mode);
+
+ // No exception, return success
+ args.PopAndReturn(UndefinedConstant());
+ }
+
+ Bind(&slow);
+ {
+ // By the book: taken from the ECMAScript 2015 specification (cont.)
+
+ // 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);
+ {
+ GotoUnlessNumberLessThan(k.value(), len, &after_loop);
+
+ VisitOneElement(context, this_arg.value(), o, k.value(),
+ callbackfn.value());
+
+ // e. Increase k by 1.
+ k.Bind(NumberInc(k.value()));
+ Goto(&loop);
+ }
+ Bind(&after_loop);
+ args.PopAndReturn(UndefinedConstant());
+ }
+
+ Bind(&throw_null_undefined_exception);
+ {
+ CallRuntime(Runtime::kThrowTypeError, context,
+ SmiConstant(MessageTemplate::kCalledOnNullOrUndefined),
+ HeapConstant(isolate()->factory()->NewStringFromAsciiChecked(
+ "Array.prototype.forEach")));
+ args.PopAndReturn(UndefinedConstant());
+ }
+
+ Bind(&type_exception);
+ {
+ CallRuntime(Runtime::kThrowTypeError, context,
+ SmiConstant(MessageTemplate::kCalledNonCallable),
+ callbackfn.value());
+ args.PopAndReturn(UndefinedConstant());
+ }
+}
+
BUILTIN(ArraySlice) {
HandleScope scope(isolate);
Handle<Object> receiver = args.receiver();
« no previous file with comments | « src/builtins/builtins.h ('k') | src/flag-definitions.h » ('j') | src/js/array.js » ('J')

Powered by Google App Engine
This is Rietveld 408576698