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
...@@ -3495,38 +3495,34 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) { ...@@ -3495,38 +3495,34 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) {
TNode<Context> context = CAST(Parameter(Descriptor::kContext)); TNode<Context> context = CAST(Parameter(Descriptor::kContext));
Node* iterator = Parameter(Descriptor::kReceiver); Node* iterator = Parameter(Descriptor::kReceiver);
VARIABLE(var_value, MachineRepresentation::kTagged); VARIABLE(var_done, MachineRepresentation::kTagged, TrueConstant());
VARIABLE(var_done, MachineRepresentation::kTagged); VARIABLE(var_value, MachineRepresentation::kTagged, UndefinedConstant());
// Required, or else `throw_bad_receiver` fails a DCHECK due to these
// variables not being bound along all paths, despite not being used.
var_done.Bind(TrueConstant());
var_value.Bind(UndefinedConstant());
Label throw_bad_receiver(this, Label::kDeferred);
Label set_done(this);
Label allocate_entry_if_needed(this); Label allocate_entry_if_needed(this);
Label allocate_iterator_result(this); Label allocate_iterator_result(this);
Label if_detached(this, Label::kDeferred);
Label if_typedarray(this), if_other(this, Label::kDeferred), if_array(this),
if_generic(this, Label::kDeferred);
Label set_done(this, Label::kDeferred);
// If O does not have all of the internal slots of an Array Iterator Instance // If O does not have all of the internal slots of an Array Iterator Instance
// (22.1.5.3), throw a TypeError exception // (22.1.5.3), throw a TypeError exception
GotoIf(TaggedIsSmi(iterator), &throw_bad_receiver); ThrowIfNotInstanceType(context, iterator, JS_ARRAY_ITERATOR_TYPE,
GotoIfNot(IsJSArrayIterator(CAST(iterator)), &throw_bad_receiver); method_name);
// Let a be O.[[IteratedObject]]. // Let a be O.[[IteratedObject]].
Node* array = TNode<JSReceiver> array =
LoadObjectField(iterator, JSArrayIterator::kIteratedObjectOffset); CAST(LoadObjectField(iterator, JSArrayIterator::kIteratedObjectOffset));
// Let index be O.[[ArrayIteratorNextIndex]]. // Let index be O.[[ArrayIteratorNextIndex]].
Node* index = LoadObjectField(iterator, JSArrayIterator::kNextIndexOffset); TNode<Number> index =
Node* array_map = LoadMap(array); CAST(LoadObjectField(iterator, JSArrayIterator::kNextIndexOffset));
CSA_ASSERT(this, IsNumberNonNegativeSafeInteger(index));
Label if_detached(this, Label::kDeferred); GotoIfNot(TaggedIsSmi(index), &if_other);
Label if_typedarray(this), if_other(this, Label::kDeferred), if_array(this), // Dispatch based on the type of the {array}.
if_generic(this, Label::kDeferred); TNode<Map> array_map = LoadMap(array);
TNode<Int32T> array_type = LoadMapInstanceType(array_map);
Node* array_type = LoadInstanceType(array);
GotoIf(InstanceTypeEqual(array_type, JS_ARRAY_TYPE), &if_array); GotoIf(InstanceTypeEqual(array_type, JS_ARRAY_TYPE), &if_array);
Branch(InstanceTypeEqual(array_type, JS_TYPED_ARRAY_TYPE), &if_typedarray, Branch(InstanceTypeEqual(array_type, JS_TYPED_ARRAY_TYPE), &if_typedarray,
&if_other); &if_other);
...@@ -3534,25 +3530,24 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) { ...@@ -3534,25 +3530,24 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) {
BIND(&if_array); BIND(&if_array);
{ {
// We can only handle fast elements here. // We can only handle fast elements here.
Node* elements_kind = LoadMapElementsKind(array_map); TNode<Int32T> elements_kind = LoadMapElementsKind(array_map);
GotoIfNot(IsFastElementsKind(elements_kind), &if_other); GotoIfNot(IsFastElementsKind(elements_kind), &if_other);
TNode<Smi> length = CAST(LoadJSArrayLength(array)); // Check that the {index} is within range for the {array}.
TNode<Smi> length = CAST(LoadJSArrayLength(CAST(array)));
GotoIfNot(SmiBelow(CAST(index), length), &set_done); GotoIfNot(SmiBelow(CAST(index), length), &set_done);
var_value.Bind(index);
TNode<Smi> one = SmiConstant(1);
StoreObjectFieldNoWriteBarrier(iterator, JSArrayIterator::kNextIndexOffset, StoreObjectFieldNoWriteBarrier(iterator, JSArrayIterator::kNextIndexOffset,
SmiAdd(CAST(index), one)); SmiInc(CAST(index)));
var_done.Bind(FalseConstant()); var_done.Bind(FalseConstant());
var_value.Bind(index);
GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField( GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField(
iterator, JSArrayIterator::kKindOffset), iterator, JSArrayIterator::kKindOffset),
Int32Constant(static_cast<int>(IterationKind::kKeys))), Int32Constant(static_cast<int>(IterationKind::kKeys))),
&allocate_iterator_result); &allocate_iterator_result);
Node* elements = LoadElements(array); Node* elements = LoadElements(CAST(array));
Label if_packed(this), if_holey(this), if_packed_double(this), Label if_packed(this), if_holey(this), if_packed_double(this),
if_holey_double(this), if_unknown_kind(this, Label::kDeferred); if_holey_double(this), if_unknown_kind(this, Label::kDeferred);
int32_t kinds[] = {// Handled by if_packed. int32_t kinds[] = {// Handled by if_packed.
...@@ -3619,25 +3614,59 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) { ...@@ -3619,25 +3614,59 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) {
BIND(&if_other); BIND(&if_other);
{ {
// If a is undefined, return CreateIterResultObject(undefined, true) // If the {array} is actually a JSArray the {index} must be a valid
GotoIf(IsUndefined(array), &allocate_iterator_result); // array index, as the TurboFan fast-path inlining relies on the fact
// that the [[ArrayIteratorNextIndex]] field always contains a valid
Node* length = // Unsigned32 value as long as the [[ArrayIteratorIteratedObject]]
// field contains a JSArray instance. Also rule out JSTypedArray's
// here as they should never reach here (both because the {index}
// in that case must always be a Smi, and second because loading
// the "length" property would be wrong for JSTypedArray's).
CSA_ASSERT(this, Word32BinaryNot(IsJSTypedArray(array)));
CSA_ASSERT(this, Word32Or(Word32BinaryNot(IsJSArray(array)),
IsNumberArrayIndex(index)));
// Check that the {index} is within the bounds of the {array}s "length".
TNode<Number> length = CAST(
CallBuiltin(Builtins::kToLength, context, CallBuiltin(Builtins::kToLength, context,
GetProperty(context, array, factory()->length_string())); GetProperty(context, array, factory()->length_string())));
GotoIfNumberGreaterThanOrEqual(index, length, &set_done); GotoIfNumberGreaterThanOrEqual(index, length, &set_done);
var_value.Bind(index);
StoreObjectField(iterator, JSArrayIterator::kNextIndexOffset, StoreObjectField(iterator, JSArrayIterator::kNextIndexOffset,
NumberInc(index)); NumberInc(index));
var_done.Bind(FalseConstant()); var_done.Bind(FalseConstant());
var_value.Bind(index);
GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField( Branch(Word32Equal(LoadAndUntagToWord32ObjectField(
iterator, JSArrayIterator::kKindOffset), iterator, JSArrayIterator::kKindOffset),
Int32Constant(static_cast<int>(IterationKind::kKeys))), Int32Constant(static_cast<int>(IterationKind::kKeys))),
&allocate_iterator_result); &allocate_iterator_result, &if_generic);
Goto(&if_generic); }
BIND(&set_done);
{
// Change the [[ArrayIteratorNextIndex]] such that the {iterator} will
// never produce values anymore, because it will always fail the bounds
// check. Note that this is different from what the specification does,
// which is changing the [[IteratedObject]] to undefined, because leaving
// [[IteratedObject]] alone helps TurboFan to generate better code with
// the inlining in JSCallReducer::ReduceArrayIteratorPrototypeNext().
//
// The terminal value we chose here depends on the type of the {array},
// for JSArray's we use kMaxUInt32 so that TurboFan can always use
// Word32 representation for fast-path indices (and this is safe since
// the "length" of JSArray's is limited to Unsigned32 range). For other
// JSReceiver's we have to use kMaxSafeInteger, since the "length" can
// be any arbitrary value in the safe integer range.
//
// Note specifically that JSTypedArray's will never take this path, so
// we don't need to worry about their maximum value.
CSA_ASSERT(this, Word32BinaryNot(IsJSTypedArray(array)));
TNode<Number> max_length =
SelectConstant(IsJSArray(array), NumberConstant(kMaxUInt32),
NumberConstant(kMaxSafeInteger));
StoreObjectField(iterator, JSArrayIterator::kNextIndexOffset, max_length);
Goto(&allocate_iterator_result);
} }
BIND(&if_generic); BIND(&if_generic);
...@@ -3648,78 +3677,44 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) { ...@@ -3648,78 +3677,44 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) {
BIND(&if_typedarray); BIND(&if_typedarray);
{ {
Node* buffer = LoadObjectField(array, JSTypedArray::kBufferOffset); // Check that the {array}s buffer wasn't neutered.
TNode<JSArrayBuffer> buffer = LoadArrayBufferViewBuffer(CAST(array));
GotoIf(IsDetachedBuffer(buffer), &if_detached); GotoIf(IsDetachedBuffer(buffer), &if_detached);
// If we go outside of the {length}, we don't need to update the
// [[ArrayIteratorNextIndex]] anymore, since a JSTypedArray's
// length cannot change anymore, so this {iterator} will never
// produce values again anyways.
TNode<Smi> length = LoadTypedArrayLength(CAST(array)); TNode<Smi> length = LoadTypedArrayLength(CAST(array));
GotoIfNot(SmiBelow(CAST(index), length), &allocate_iterator_result);
GotoIfNot(SmiBelow(CAST(index), length), &set_done);
var_value.Bind(index);
TNode<Smi> one = SmiConstant(1);
StoreObjectFieldNoWriteBarrier(iterator, JSArrayIterator::kNextIndexOffset, StoreObjectFieldNoWriteBarrier(iterator, JSArrayIterator::kNextIndexOffset,
SmiAdd(CAST(index), one)); SmiInc(CAST(index)));
var_done.Bind(FalseConstant()); var_done.Bind(FalseConstant());
var_value.Bind(index);
GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField( GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField(
iterator, JSArrayIterator::kKindOffset), iterator, JSArrayIterator::kKindOffset),
Int32Constant(static_cast<int>(IterationKind::kKeys))), Int32Constant(static_cast<int>(IterationKind::kKeys))),
&allocate_iterator_result); &allocate_iterator_result);
Node* elements_kind = LoadMapElementsKind(array_map); TNode<Int32T> elements_kind = LoadMapElementsKind(array_map);
Node* elements = LoadElements(array); Node* elements = LoadElements(CAST(array));
Node* base_ptr = Node* base_ptr =
LoadObjectField(elements, FixedTypedArrayBase::kBasePointerOffset); LoadObjectField(elements, FixedTypedArrayBase::kBasePointerOffset);
Node* external_ptr = Node* external_ptr =
LoadObjectField(elements, FixedTypedArrayBase::kExternalPointerOffset, LoadObjectField(elements, FixedTypedArrayBase::kExternalPointerOffset,
MachineType::Pointer()); MachineType::Pointer());
Node* data_ptr = IntPtrAdd(BitcastTaggedToWord(base_ptr), external_ptr); TNode<WordT> data_ptr =
IntPtrAdd(BitcastTaggedToWord(base_ptr), external_ptr);
Label if_unknown_type(this, Label::kDeferred); var_value.Bind(LoadFixedTypedArrayElementAsTagged(data_ptr, CAST(index),
int32_t elements_kinds[] = { elements_kind));
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) TYPE##_ELEMENTS, Goto(&allocate_entry_if_needed);
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_value.Bind(LoadFixedTypedArrayElementAsTagged( \
data_ptr, index, TYPE##_ELEMENTS, SMI_PARAMETERS)); \
Goto(&allocate_entry_if_needed); \
}
TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
BIND(&if_detached); BIND(&if_detached);
ThrowTypeError(context, MessageTemplate::kDetachedOperation, method_name); ThrowTypeError(context, MessageTemplate::kDetachedOperation, method_name);
} }
BIND(&set_done);
{
StoreObjectFieldNoWriteBarrier(
iterator, JSArrayIterator::kIteratedObjectOffset, UndefinedConstant());
Goto(&allocate_iterator_result);
}
BIND(&allocate_entry_if_needed); BIND(&allocate_entry_if_needed);
{ {
GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField( GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField(
...@@ -3727,49 +3722,17 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) { ...@@ -3727,49 +3722,17 @@ TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) {
Int32Constant(static_cast<int>(IterationKind::kValues))), Int32Constant(static_cast<int>(IterationKind::kValues))),
&allocate_iterator_result); &allocate_iterator_result);
TNode<FixedArray> elements = Node* result =
CAST(AllocateFixedArray(PACKED_ELEMENTS, IntPtrConstant(2))); AllocateJSIteratorResultForEntry(context, index, var_value.value());
StoreFixedArrayElement(elements, 0, index, SKIP_WRITE_BARRIER); Return(result);
StoreFixedArrayElement(elements, 1, var_value.value(), SKIP_WRITE_BARRIER);
Node* entry = Allocate(JSArray::kSize);
Node* map = LoadContextElement(LoadNativeContext(context),
Context::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX);
StoreMapNoWriteBarrier(entry, map);
StoreObjectFieldRoot(entry, JSArray::kPropertiesOrHashOffset,
Heap::kEmptyFixedArrayRootIndex);
StoreObjectFieldNoWriteBarrier(entry, JSArray::kElementsOffset, elements);
StoreObjectFieldNoWriteBarrier(entry, JSArray::kLengthOffset,
SmiConstant(2));
var_value.Bind(entry);
Goto(&allocate_iterator_result);
} }
BIND(&allocate_iterator_result); BIND(&allocate_iterator_result);
{ {
Node* result = Allocate(JSIteratorResult::kSize); Node* result =
Node* map = LoadContextElement(LoadNativeContext(context), AllocateJSIteratorResult(context, var_value.value(), var_done.value());
Context::ITERATOR_RESULT_MAP_INDEX);
StoreMapNoWriteBarrier(result, map);
StoreObjectFieldRoot(result, JSIteratorResult::kPropertiesOrHashOffset,
Heap::kEmptyFixedArrayRootIndex);
StoreObjectFieldRoot(result, JSIteratorResult::kElementsOffset,
Heap::kEmptyFixedArrayRootIndex);
StoreObjectFieldNoWriteBarrier(result, JSIteratorResult::kValueOffset,
var_value.value());
StoreObjectFieldNoWriteBarrier(result, JSIteratorResult::kDoneOffset,
var_done.value());
Return(result); Return(result);
} }
BIND(&throw_bad_receiver);
{
// The {receiver} is not a valid JSArrayIterator.
ThrowTypeError(context, MessageTemplate::kIncompatibleMethodReceiver,
StringConstant(method_name), iterator);
}
} }
namespace { namespace {
......
...@@ -2224,6 +2224,47 @@ Node* CodeStubAssembler::LoadFixedTypedArrayElementAsTagged( ...@@ -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( void CodeStubAssembler::StoreFixedTypedArrayElementFromTagged(
TNode<Context> context, TNode<FixedTypedArrayBase> elements, TNode<Context> context, TNode<FixedTypedArrayBase> elements,
TNode<Object> index_node, TNode<Object> value, ElementsKind elements_kind, TNode<Object> index_node, TNode<Object> value, ElementsKind elements_kind,
...@@ -5787,7 +5828,7 @@ TNode<BoolT> CodeStubAssembler::IsHeapNumberUint32(TNode<HeapNumber> number) { ...@@ -5787,7 +5828,7 @@ TNode<BoolT> CodeStubAssembler::IsHeapNumberUint32(TNode<HeapNumber> number) {
IsHeapNumberPositive(number), IsHeapNumberPositive(number),
[=] { [=] {
TNode<Float64T> value = LoadHeapNumberValue(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 Float64Equal(value, ChangeUint32ToFloat64(int_value));
}, },
[=] { return Int32FalseConstant(); }); [=] { return Int32FalseConstant(); });
......
...@@ -520,6 +520,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler { ...@@ -520,6 +520,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
SMI_ARITHMETIC_BINOP(SmiAnd, WordAnd, Word32And) SMI_ARITHMETIC_BINOP(SmiAnd, WordAnd, Word32And)
SMI_ARITHMETIC_BINOP(SmiOr, WordOr, Word32Or) SMI_ARITHMETIC_BINOP(SmiOr, WordOr, Word32Or)
#undef SMI_ARITHMETIC_BINOP #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> TrySmiAdd(TNode<Smi> a, TNode<Smi> b, Label* if_overflow);
TNode<Smi> TrySmiSub(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 { ...@@ -1070,6 +1071,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* LoadFixedTypedArrayElementAsTagged( Node* LoadFixedTypedArrayElementAsTagged(
Node* data_pointer, Node* index_node, ElementsKind elements_kind, Node* data_pointer, Node* index_node, ElementsKind elements_kind,
ParameterMode parameter_mode = INTPTR_PARAMETERS); 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: // Parts of the above, factored out for readability:
Node* LoadFixedBigInt64ArrayElementAsTagged(Node* data_pointer, Node* offset); Node* LoadFixedBigInt64ArrayElementAsTagged(Node* data_pointer, Node* offset);
Node* LoadFixedBigUint64ArrayElementAsTagged(Node* data_pointer, Node* LoadFixedBigUint64ArrayElementAsTagged(Node* data_pointer,
......
...@@ -734,13 +734,11 @@ FieldAccess AccessBuilder::ForJSGlobalProxyNativeContext() { ...@@ -734,13 +734,11 @@ FieldAccess AccessBuilder::ForJSGlobalProxyNativeContext() {
// static // static
FieldAccess AccessBuilder::ForJSArrayIteratorIteratedObject() { FieldAccess AccessBuilder::ForJSArrayIteratorIteratedObject() {
FieldAccess access = {kTaggedBase, FieldAccess access = {
JSArrayIterator::kIteratedObjectOffset, kTaggedBase, JSArrayIterator::kIteratedObjectOffset,
Handle<Name>(), Handle<Name>(), MaybeHandle<Map>(),
MaybeHandle<Map>(), Type::Receiver(), MachineType::TaggedPointer(),
Type::ReceiverOrUndefined(), kPointerWriteBarrier};
MachineType::TaggedPointer(),
kPointerWriteBarrier};
return access; return access;
} }
......
...@@ -4975,17 +4975,13 @@ Reduction JSCallReducer::ReduceArrayIteratorPrototypeNext(Node* node) { ...@@ -4975,17 +4975,13 @@ Reduction JSCallReducer::ReduceArrayIteratorPrototypeNext(Node* node) {
PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); PropertyCellRef(js_heap_broker(), factory()->no_elements_protector()));
} }
// Load the (current) {iterated_object} from the {iterator}; this might be // Load the (current) {iterated_object} from the {iterator}.
// either undefined or the JSReceiver that was passed to the JSArrayIterator
// creation.
Node* iterated_object = effect = Node* iterated_object = effect =
graph()->NewNode(simplified()->LoadField( graph()->NewNode(simplified()->LoadField(
AccessBuilder::ForJSArrayIteratorIteratedObject()), AccessBuilder::ForJSArrayIteratorIteratedObject()),
iterator, effect, control); iterator, effect, control);
// Ensure that the {iterated_object} map didn't change. This also rules // Ensure that the {iterated_object} map didn't change.
// out the undefined that we put as a termination marker into the
// iterator.[[IteratedObject]] field once we reach the end.
effect = graph()->NewNode( effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone, iterated_object_maps, simplified()->CheckMaps(CheckMapsFlag::kNone, iterated_object_maps,
p.feedback()), p.feedback()),
...@@ -5143,10 +5139,22 @@ Reduction JSCallReducer::ReduceArrayIteratorPrototypeNext(Node* node) { ...@@ -5143,10 +5139,22 @@ Reduction JSCallReducer::ReduceArrayIteratorPrototypeNext(Node* node) {
// iterator.[[NextIndex]] >= array.length, stop iterating. // iterator.[[NextIndex]] >= array.length, stop iterating.
done_false = jsgraph()->TrueConstant(); done_false = jsgraph()->TrueConstant();
value_false = jsgraph()->UndefinedConstant(); value_false = jsgraph()->UndefinedConstant();
efalse =
graph()->NewNode(simplified()->StoreField( if (!IsFixedTypedArrayElementsKind(elements_kind)) {
AccessBuilder::ForJSArrayIteratorIteratedObject()), // Mark the {iterator} as exhausted by setting the [[NextIndex]] to a
iterator, value_false, efalse, if_false); // 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); control = graph()->NewNode(common()->Merge(2), if_true, if_false);
......
...@@ -1201,8 +1201,7 @@ void JSWeakMap::JSWeakMapVerify(Isolate* isolate) { ...@@ -1201,8 +1201,7 @@ void JSWeakMap::JSWeakMapVerify(Isolate* isolate) {
void JSArrayIterator::JSArrayIteratorVerify(Isolate* isolate) { void JSArrayIterator::JSArrayIteratorVerify(Isolate* isolate) {
CHECK(IsJSArrayIterator()); CHECK(IsJSArrayIterator());
JSObjectVerify(isolate); JSObjectVerify(isolate);
CHECK(iterated_object()->IsJSReceiver() || CHECK(iterated_object()->IsJSReceiver());
iterated_object()->IsUndefined(isolate));
CHECK_GE(next_index()->Number(), 0); CHECK_GE(next_index()->Number(), 0);
CHECK_LE(next_index()->Number(), kMaxSafeInteger); CHECK_LE(next_index()->Number(), kMaxSafeInteger);
......
...@@ -117,6 +117,28 @@ class JSArrayIterator : public JSObject { ...@@ -117,6 +117,28 @@ class JSArrayIterator : public JSObject {
DECL_ACCESSORS(iterated_object, Object) DECL_ACCESSORS(iterated_object, Object)
// [next_index]: The [[ArrayIteratorNextIndex]] inobject property. // [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) DECL_ACCESSORS(next_index, Object)
// [kind]: the [[ArrayIterationKind]] inobject property. // [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