Commit 6031f172 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[es2015] Use [[ArrayIteratorNextIndex]] to indicate exhaustion.

Instead of changing the [[IteratedObject]] field to undefined to mark an
array iterator as exhausted, store the appropriate maximum value into
the [[ArrayIteratorNextIndex]] field such that the iterator will never
produce any values again.

Without this change the map check and the "length" access on the
[[IteratedObject]] cannot be eliminated inside the loop, since the
object can either be the array or undefined. Even with this change
it's still not possible immediately due to missing aliasing
information in the LoadElimination, but it paves the way for follow
up improvements. Eventually the goal is to have `for..of` as fast as
a traditional `for` loop even for really tight loops.

This CL also hardens the implementation of the ArrayIterator by using
proper CASTs and CSA_ASSERTs. The readability of the CSA builtin was
improved by utilizing proper helper functions.

Bug: v8:7510, v8:7514, v8:8070
Change-Id: Ib46604fadad1a0f80e77fe71a1f47b0ca31ab841
Reviewed-on: https://chromium-review.googlesource.com/1181902
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#55261}
parent 16fd84f3
This diff is collapsed.
......@@ -2224,6 +2224,47 @@ Node* CodeStubAssembler::LoadFixedTypedArrayElementAsTagged(
}
}
TNode<Numeric> CodeStubAssembler::LoadFixedTypedArrayElementAsTagged(
TNode<WordT> data_pointer, TNode<Smi> index, TNode<Int32T> elements_kind) {
TVARIABLE(Numeric, var_result);
Label done(this), if_unknown_type(this, Label::kDeferred);
int32_t elements_kinds[] = {
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) TYPE##_ELEMENTS,
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
};
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) Label if_##type##array(this);
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
Label* elements_kind_labels[] = {
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) &if_##type##array,
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
};
STATIC_ASSERT(arraysize(elements_kinds) == arraysize(elements_kind_labels));
Switch(elements_kind, &if_unknown_type, elements_kinds, elements_kind_labels,
arraysize(elements_kinds));
BIND(&if_unknown_type);
Unreachable();
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \
BIND(&if_##type##array); \
{ \
var_result = CAST(LoadFixedTypedArrayElementAsTagged( \
data_pointer, index, TYPE##_ELEMENTS, SMI_PARAMETERS)); \
Goto(&done); \
}
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
BIND(&done);
return var_result.value();
}
void CodeStubAssembler::StoreFixedTypedArrayElementFromTagged(
TNode<Context> context, TNode<FixedTypedArrayBase> elements,
TNode<Object> index_node, TNode<Object> value, ElementsKind elements_kind,
......@@ -5787,7 +5828,7 @@ TNode<BoolT> CodeStubAssembler::IsHeapNumberUint32(TNode<HeapNumber> number) {
IsHeapNumberPositive(number),
[=] {
TNode<Float64T> value = LoadHeapNumberValue(number);
TNode<Uint32T> int_value = ChangeFloat64ToUint32(value);
TNode<Uint32T> int_value = Unsigned(TruncateFloat64ToWord32(value));
return Float64Equal(value, ChangeUint32ToFloat64(int_value));
},
[=] { return Int32FalseConstant(); });
......
......@@ -520,6 +520,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
SMI_ARITHMETIC_BINOP(SmiAnd, WordAnd, Word32And)
SMI_ARITHMETIC_BINOP(SmiOr, WordOr, Word32Or)
#undef SMI_ARITHMETIC_BINOP
TNode<Smi> SmiInc(TNode<Smi> value) { return SmiAdd(value, SmiConstant(1)); }
TNode<Smi> TrySmiAdd(TNode<Smi> a, TNode<Smi> b, Label* if_overflow);
TNode<Smi> TrySmiSub(TNode<Smi> a, TNode<Smi> b, Label* if_overflow);
......@@ -1070,6 +1071,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* LoadFixedTypedArrayElementAsTagged(
Node* data_pointer, Node* index_node, ElementsKind elements_kind,
ParameterMode parameter_mode = INTPTR_PARAMETERS);
TNode<Numeric> LoadFixedTypedArrayElementAsTagged(
TNode<WordT> data_pointer, TNode<Smi> index, TNode<Int32T> elements_kind);
// Parts of the above, factored out for readability:
Node* LoadFixedBigInt64ArrayElementAsTagged(Node* data_pointer, Node* offset);
Node* LoadFixedBigUint64ArrayElementAsTagged(Node* data_pointer,
......
......@@ -734,13 +734,11 @@ FieldAccess AccessBuilder::ForJSGlobalProxyNativeContext() {
// static
FieldAccess AccessBuilder::ForJSArrayIteratorIteratedObject() {
FieldAccess access = {kTaggedBase,
JSArrayIterator::kIteratedObjectOffset,
Handle<Name>(),
MaybeHandle<Map>(),
Type::ReceiverOrUndefined(),
MachineType::TaggedPointer(),
kPointerWriteBarrier};
FieldAccess access = {
kTaggedBase, JSArrayIterator::kIteratedObjectOffset,
Handle<Name>(), MaybeHandle<Map>(),
Type::Receiver(), MachineType::TaggedPointer(),
kPointerWriteBarrier};
return access;
}
......
......@@ -4975,17 +4975,13 @@ Reduction JSCallReducer::ReduceArrayIteratorPrototypeNext(Node* node) {
PropertyCellRef(js_heap_broker(), factory()->no_elements_protector()));
}
// Load the (current) {iterated_object} from the {iterator}; this might be
// either undefined or the JSReceiver that was passed to the JSArrayIterator
// creation.
// Load the (current) {iterated_object} from the {iterator}.
Node* iterated_object = effect =
graph()->NewNode(simplified()->LoadField(
AccessBuilder::ForJSArrayIteratorIteratedObject()),
iterator, effect, control);
// Ensure that the {iterated_object} map didn't change. This also rules
// out the undefined that we put as a termination marker into the
// iterator.[[IteratedObject]] field once we reach the end.
// Ensure that the {iterated_object} map didn't change.
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone, iterated_object_maps,
p.feedback()),
......@@ -5143,10 +5139,22 @@ Reduction JSCallReducer::ReduceArrayIteratorPrototypeNext(Node* node) {
// iterator.[[NextIndex]] >= array.length, stop iterating.
done_false = jsgraph()->TrueConstant();
value_false = jsgraph()->UndefinedConstant();
efalse =
graph()->NewNode(simplified()->StoreField(
AccessBuilder::ForJSArrayIteratorIteratedObject()),
iterator, value_false, efalse, if_false);
if (!IsFixedTypedArrayElementsKind(elements_kind)) {
// Mark the {iterator} as exhausted by setting the [[NextIndex]] to a
// value that will never pass the length check again (aka the maximum
// value possible for the specific iterated object). Note that this is
// different from what the specification says, which is changing the
// [[IteratedObject]] field to undefined, but that makes it difficult
// to eliminate the map checks and "length" accesses in for..of loops.
//
// This is not necessary for JSTypedArray's, since the length of those
// cannot change later and so if we were ever out of bounds for them
// we will stay out-of-bounds forever.
Node* end_index = jsgraph()->Constant(index_access.type.Max());
efalse = graph()->NewNode(simplified()->StoreField(index_access),
iterator, end_index, efalse, if_false);
}
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
......
......@@ -1201,8 +1201,7 @@ void JSWeakMap::JSWeakMapVerify(Isolate* isolate) {
void JSArrayIterator::JSArrayIteratorVerify(Isolate* isolate) {
CHECK(IsJSArrayIterator());
JSObjectVerify(isolate);
CHECK(iterated_object()->IsJSReceiver() ||
iterated_object()->IsUndefined(isolate));
CHECK(iterated_object()->IsJSReceiver());
CHECK_GE(next_index()->Number(), 0);
CHECK_LE(next_index()->Number(), kMaxSafeInteger);
......
......@@ -117,6 +117,28 @@ class JSArrayIterator : public JSObject {
DECL_ACCESSORS(iterated_object, Object)
// [next_index]: The [[ArrayIteratorNextIndex]] inobject property.
// The next_index is always a positive integer, and it points to
// the next index that is to be returned by this iterator. It's
// possible range is fixed depending on the [[iterated_object]]:
//
// 1. For JSArray's the next_index is always in Unsigned32
// range, and when the iterator reaches the end it's set
// to kMaxUInt32 to indicate that this iterator should
// never produce values anymore even if the "length"
// property of the JSArray changes at some later point.
// 2. For JSTypedArray's the next_index is always in
// UnsignedSmall range, and when the iterator terminates
// it's set to Smi::kMaxValue.
// 3. For all other JSReceiver's it's always between 0 and
// kMaxSafeInteger, and the latter value is used to mark
// termination.
//
// It's important that for 1. and 2. the value fits into the
// Unsigned32 range (UnsignedSmall is a subset of Unsigned32),
// since we use this knowledge in the fast-path for the array
// iterator next calls in TurboFan (in the JSCallReducer) to
// keep the index in Word32 representation. This invariant is
// checked in JSArrayIterator::JSArrayIteratorVerify().
DECL_ACCESSORS(next_index, Object)
// [kind]: the [[ArrayIterationKind]] inobject property.
......
// Copyright 2018 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.
// Flags: --allow-natives-syntax
function bar(iterator) {
for (const entry of iterator) {}
}
%NeverOptimizeFunction(bar);
function foo(a) {
const iterator = a.values();
bar(iterator);
return iterator.next().done;
}
const a = [1, 2, 3];
assertTrue(foo(a));
assertTrue(foo(a));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(a));
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment