// Emit code to evaluate all the non-constant subexpressions and to store
// them into the newly cloned array.
- for (int i = 0; i < length; i++) {
- Expression* subexpr = subexprs->at(i);
+ int array_index = 0;
+ for (; array_index < length; array_index++) {
+ Expression* subexpr = subexprs->at(array_index);
+ if (subexpr->IsSpread()) break;
+
// If the subexpression is a literal or a simple materialized literal it
// is already set in the cloned array.
if (CompileTimeValue::IsCompileTimeValue(subexpr)) continue;
VisitForAccumulatorValue(subexpr);
if (has_fast_elements) {
- int offset = FixedArray::kHeaderSize + (i * kPointerSize);
+ int offset = FixedArray::kHeaderSize + (array_index * kPointerSize);
__ ldr(r6, MemOperand(sp, kPointerSize)); // Copy of array literal.
__ ldr(r1, FieldMemOperand(r6, JSObject::kElementsOffset));
__ str(result_register(), FieldMemOperand(r1, offset));
kLRHasBeenSaved, kDontSaveFPRegs,
EMIT_REMEMBERED_SET, INLINE_SMI_CHECK);
} else {
- __ mov(r3, Operand(Smi::FromInt(i)));
+ __ mov(r3, Operand(Smi::FromInt(array_index)));
StoreArrayLiteralElementStub stub(isolate());
__ CallStub(&stub);
}
- PrepareForBailoutForId(expr->GetIdForElement(i), NO_REGISTERS);
+ PrepareForBailoutForId(expr->GetIdForElement(array_index), NO_REGISTERS);
+ }
+
+ // In case the array literal contains spread expressions it has two parts. The
+ // first part is the "static" array which has a literal index is handled
+ // above. The second part is the part after the first spread expression
+ // (inclusive) and these elements gets appended to the array. Note that the
+ // number elements an iterable produces is unknown ahead of time.
+ if (array_index < length && result_saved) {
+ __ pop(); // literal index
+ __ Pop(r0);
+ result_saved = false;
+ }
+ for (; array_index < length; array_index++) {
+ Expression* subexpr = subexprs->at(array_index);
+
+ __ Push(r0);
+ if (subexpr->IsSpread()) {
+ VisitForStackValue(subexpr->AsSpread()->expression());
+ __ InvokeBuiltin(Builtins::CONCAT_ITERABLE_TO_ARRAY, CALL_FUNCTION);
+ } else {
+ VisitForStackValue(subexpr);
+ __ CallRuntime(Runtime::kAppendElement, 2);
+ }
+
+ PrepareForBailoutForId(expr->GetIdForElement(array_index), NO_REGISTERS);
}
if (result_saved) {
// Emit code to evaluate all the non-constant subexpressions and to store
// them into the newly cloned array.
- for (int i = 0; i < length; i++) {
- Expression* subexpr = subexprs->at(i);
+ int array_index = 0;
+ for (; array_index < length; array_index++) {
+ Expression* subexpr = subexprs->at(array_index);
+ if (subexpr->IsSpread()) break;
+
// If the subexpression is a literal or a simple materialized literal it
// is already set in the cloned array.
if (CompileTimeValue::IsCompileTimeValue(subexpr)) continue;
VisitForAccumulatorValue(subexpr);
if (has_fast_elements) {
- int offset = FixedArray::kHeaderSize + (i * kPointerSize);
+ int offset = FixedArray::kHeaderSize + (array_index * kPointerSize);
__ Peek(x6, kPointerSize); // Copy of array literal.
__ Ldr(x1, FieldMemOperand(x6, JSObject::kElementsOffset));
__ Str(result_register(), FieldMemOperand(x1, offset));
kLRHasBeenSaved, kDontSaveFPRegs,
EMIT_REMEMBERED_SET, INLINE_SMI_CHECK);
} else {
- __ Mov(x3, Smi::FromInt(i));
+ __ Mov(x3, Smi::FromInt(array_index));
StoreArrayLiteralElementStub stub(isolate());
__ CallStub(&stub);
}
- PrepareForBailoutForId(expr->GetIdForElement(i), NO_REGISTERS);
+ PrepareForBailoutForId(expr->GetIdForElement(array_index), NO_REGISTERS);
+ }
+
+ // In case the array literal contains spread expressions it has two parts. The
+ // first part is the "static" array which has a literal index is handled
+ // above. The second part is the part after the first spread expression
+ // (inclusive) and these elements gets appended to the array. Note that the
+ // number elements an iterable produces is unknown ahead of time.
+ if (array_index < length && result_saved) {
+ __ Drop(1); // literal index
+ __ Pop(x0);
+ result_saved = false;
+ }
+ for (; array_index < length; array_index++) {
+ Expression* subexpr = subexprs->at(array_index);
+
+ __ Push(x0);
+ if (subexpr->IsSpread()) {
+ VisitForStackValue(subexpr->AsSpread()->expression());
+ __ InvokeBuiltin(Builtins::CONCAT_ITERABLE_TO_ARRAY, CALL_FUNCTION);
+ } else {
+ VisitForStackValue(subexpr);
+ __ CallRuntime(Runtime::kAppendElement, 2);
+ }
+
+ PrepareForBailoutForId(expr->GetIdForElement(array_index), NO_REGISTERS);
}
if (result_saved) {
}
-void AstNumberingVisitor::VisitSpread(Spread* node) { UNREACHABLE(); }
+void AstNumberingVisitor::VisitSpread(Spread* node) {
+ IncrementNodeCount();
+ DisableOptimization(kSpread);
+ Visit(node->expression());
+}
void AstNumberingVisitor::VisitForInStatement(ForInStatement* node) {
bool is_simple = true;
int depth_acc = 1;
bool is_holey = false;
- for (int i = 0, n = values()->length(); i < n; i++) {
- Expression* element = values()->at(i);
+ int array_index = 0;
+ for (int n = values()->length(); array_index < n; array_index++) {
+ Expression* element = values()->at(array_index);
+ if (element->IsSpread()) break;
MaterializedLiteral* m_literal = element->AsMaterializedLiteral();
if (m_literal != NULL) {
m_literal->BuildConstants(isolate);
is_holey = true;
} else if (boilerplate_value->IsUninitialized()) {
is_simple = false;
- JSObject::SetOwnElement(
- array, i, handle(Smi::FromInt(0), isolate), SLOPPY).Assert();
+ JSObject::SetOwnElement(array, array_index,
+ handle(Smi::FromInt(0), isolate),
+ SLOPPY).Assert();
} else {
- JSObject::SetOwnElement(array, i, boilerplate_value, SLOPPY).Assert();
+ JSObject::SetOwnElement(array, array_index, boilerplate_value, SLOPPY)
+ .Assert();
}
}
+ if (array_index != values()->length()) {
+ JSArray::SetElementsLength(
+ array, handle(Smi::FromInt(array_index), isolate)).Assert();
+ }
Handle<FixedArrayBase> element_values(array->elements());
// Simple and shallow arrays can be lazily copied, we transform the
// elements array to a copy-on-write array.
- if (is_simple && depth_acc == 1 && values()->length() > 0 &&
+ if (is_simple && depth_acc == 1 && array_index > 0 &&
array->HasFastSmiOrObjectElements()) {
element_values->set_map(isolate->heap()->fixed_cow_array_map());
}
V(kScriptContext, "Allocation of script context") \
V(kSmiAdditionOverflow, "Smi addition overflow") \
V(kSmiSubtractionOverflow, "Smi subtraction overflow") \
+ V(kSpread, "Spread in array literal") \
V(kStackAccessBelowStackPointer, "Stack access below stack pointer") \
V(kStackFrameTypesMustMatch, "Stack frame types must match") \
V(kSuperReference, "Super reference") \
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_spreadcalls)
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_destructuring)
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_object)
+EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_spread_arrays)
void Genesis::InstallNativeFunctions_harmony_proxies() {
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_spreadcalls)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_destructuring)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_object)
+EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_spread_arrays)
void Genesis::InitializeGlobal_harmony_regexps() {
Handle<JSObject> builtins(native_context()->builtins());
static const char* harmony_destructuring_natives[] = {nullptr};
static const char* harmony_object_natives[] = {"native harmony-object.js",
NULL};
+ static const char* harmony_spread_arrays_natives[] = {nullptr};
for (int i = ExperimentalNatives::GetDebuggerCount();
i < ExperimentalNatives::GetBuiltinsCount(); i++) {
V(APPLY_PREPARE, 1) \
V(REFLECT_APPLY_PREPARE, 1) \
V(REFLECT_CONSTRUCT_PREPARE, 2) \
+ V(CONCAT_ITERABLE_TO_ARRAY, 1) \
V(STACK_OVERFLOW, 1)
class BuiltinFunctionTable;
// Create nodes to evaluate all the non-constant subexpressions and to store
// them into the newly cloned array.
- for (int i = 0; i < expr->values()->length(); i++) {
- Expression* subexpr = expr->values()->at(i);
+ int array_index = 0;
+ for (; array_index < expr->values()->length(); array_index++) {
+ Expression* subexpr = expr->values()->at(array_index);
+ if (subexpr->IsSpread()) break;
if (CompileTimeValue::IsCompileTimeValue(subexpr)) continue;
VisitForValue(subexpr);
{
FrameStateBeforeAndAfter states(this, subexpr->id());
Node* value = environment()->Pop();
- Node* index = jsgraph()->Constant(i);
+ Node* index = jsgraph()->Constant(array_index);
Node* store =
BuildKeyedStore(literal, index, value, TypeFeedbackId::None());
- states.AddToNode(store, expr->GetIdForElement(i),
+ states.AddToNode(store, expr->GetIdForElement(array_index),
OutputFrameStateCombine::Ignore());
}
}
- environment()->Pop(); // Array literal index.
+ // In case the array literal contains spread expressions it has two parts. The
+ // first part is the "static" array which has a literal index is handled
+ // above. The second part is the part after the first spread expression
+ // (inclusive) and these elements gets appended to the array. Note that the
+ // number elements an iterable produces is unknown ahead of time.
+ bool has_spread = array_index < expr->values()->length();
+ if (has_spread) {
+ environment()->Pop(); // Array literal index.
+ }
+
+ for (; array_index < expr->values()->length(); array_index++) {
+ Expression* subexpr = expr->values()->at(array_index);
+ Node* array = environment()->Pop();
+ Node* result;
+
+ if (subexpr->IsSpread()) {
+ VisitForValue(subexpr->AsSpread()->expression());
+ Node* iterable = environment()->Pop();
+ Node* builtins = BuildLoadBuiltinsObject();
+ Node* function = BuildLoadObjectField(
+ builtins, JSBuiltinsObject::OffsetOfFunctionWithId(
+ Builtins::CONCAT_ITERABLE_TO_ARRAY));
+ result = NewNode(javascript()->CallFunction(3, NO_CALL_FUNCTION_FLAGS,
+ language_mode()),
+ function, array, iterable);
+ } else {
+ VisitForValue(subexpr);
+ Node* value = environment()->Pop();
+ const Operator* op =
+ javascript()->CallRuntime(Runtime::kAppendElement, 2);
+ result = NewNode(op, array, value);
+ }
+
+ PrepareFrameState(result, expr->GetIdForElement(array_index));
+ environment()->Push(result);
+ }
+
+ if (!has_spread) {
+ environment()->Pop(); // Array literal index.
+ }
ast_context()->ProduceValue(environment()->Pop());
}
V(harmony_unicode_regexps, "harmony unicode regexps") \
V(harmony_reflect, "harmony Reflect API") \
V(harmony_destructuring, "harmony destructuring") \
+ V(harmony_spread_arrays, "harmony spread in array literals")
// Features that are complete (but still behind --harmony/es-staging flag).
#define HARMONY_STAGED(V) \
}
-void BreakableStatementChecker::VisitSpread(Spread* expr) { UNREACHABLE(); }
+void BreakableStatementChecker::VisitSpread(Spread* expr) {
+ Visit(expr->expression());
+}
void BreakableStatementChecker::VisitThisFunction(ThisFunction* expr) {
for (int i = 0; i < length; i++) {
Expression* subexpr = subexprs->at(i);
+ if (subexpr->IsSpread()) {
+ return Bailout(kSpread);
+ }
+
// If the subexpression is a literal or a simple materialized literal it
// is already set in the cloned array.
if (CompileTimeValue::IsCompileTimeValue(subexpr)) continue;
// Emit code to evaluate all the non-constant subexpressions and to store
// them into the newly cloned array.
- for (int i = 0; i < length; i++) {
- Expression* subexpr = subexprs->at(i);
+ int array_index = 0;
+ for (; array_index < length; array_index++) {
+ Expression* subexpr = subexprs->at(array_index);
+ if (subexpr->IsSpread()) break;
+
// If the subexpression is a literal or a simple materialized literal it
// is already set in the cloned array.
if (CompileTimeValue::IsCompileTimeValue(subexpr)) continue;
if (has_constant_fast_elements) {
// Fast-case array literal with ElementsKind of FAST_*_ELEMENTS, they
// cannot transition and don't need to call the runtime stub.
- int offset = FixedArray::kHeaderSize + (i * kPointerSize);
+ int offset = FixedArray::kHeaderSize + (array_index * kPointerSize);
__ mov(ebx, Operand(esp, kPointerSize)); // Copy of array literal.
__ mov(ebx, FieldOperand(ebx, JSObject::kElementsOffset));
// Store the subexpression value in the array's elements.
INLINE_SMI_CHECK);
} else {
// Store the subexpression value in the array's elements.
- __ mov(ecx, Immediate(Smi::FromInt(i)));
+ __ mov(ecx, Immediate(Smi::FromInt(array_index)));
StoreArrayLiteralElementStub stub(isolate());
__ CallStub(&stub);
}
- PrepareForBailoutForId(expr->GetIdForElement(i), NO_REGISTERS);
+ PrepareForBailoutForId(expr->GetIdForElement(array_index), NO_REGISTERS);
+ }
+
+ // In case the array literal contains spread expressions it has two parts. The
+ // first part is the "static" array which has a literal index is handled
+ // above. The second part is the part after the first spread expression
+ // (inclusive) and these elements gets appended to the array. Note that the
+ // number elements an iterable produces is unknown ahead of time.
+ if (array_index < length && result_saved) {
+ __ Drop(1); // literal index
+ __ Pop(eax);
+ result_saved = false;
+ }
+ for (; array_index < length; array_index++) {
+ Expression* subexpr = subexprs->at(array_index);
+
+ __ Push(eax);
+ if (subexpr->IsSpread()) {
+ VisitForStackValue(subexpr->AsSpread()->expression());
+ __ InvokeBuiltin(Builtins::CONCAT_ITERABLE_TO_ARRAY, CALL_FUNCTION);
+ } else {
+ VisitForStackValue(subexpr);
+ __ CallRuntime(Runtime::kAppendElement, 2);
+ }
+
+ PrepareForBailoutForId(expr->GetIdForElement(array_index), NO_REGISTERS);
}
if (result_saved) {
- __ add(esp, Immediate(kPointerSize)); // literal index
+ __ Drop(1); // literal index
context()->PlugTOS();
} else {
context()->Plug(eax);
// Emit code to evaluate all the non-constant subexpressions and to store
// them into the newly cloned array.
- for (int i = 0; i < length; i++) {
- Expression* subexpr = subexprs->at(i);
+ int array_index = 0;
+ for (; array_index < length; array_index++) {
+ Expression* subexpr = subexprs->at(array_index);
+ if (subexpr->IsSpread()) break;
+
// If the subexpression is a literal or a simple materialized literal it
// is already set in the cloned array.
if (CompileTimeValue::IsCompileTimeValue(subexpr)) continue;
VisitForAccumulatorValue(subexpr);
if (has_fast_elements) {
- int offset = FixedArray::kHeaderSize + (i * kPointerSize);
+ int offset = FixedArray::kHeaderSize + (array_index * kPointerSize);
__ lw(t2, MemOperand(sp, kPointerSize)); // Copy of array literal.
__ lw(a1, FieldMemOperand(t2, JSObject::kElementsOffset));
__ sw(result_register(), FieldMemOperand(a1, offset));
kRAHasBeenSaved, kDontSaveFPRegs,
EMIT_REMEMBERED_SET, INLINE_SMI_CHECK);
} else {
- __ li(a3, Operand(Smi::FromInt(i)));
+ __ li(a3, Operand(Smi::FromInt(array_index)));
__ mov(a0, result_register());
StoreArrayLiteralElementStub stub(isolate());
__ CallStub(&stub);
}
- PrepareForBailoutForId(expr->GetIdForElement(i), NO_REGISTERS);
+ PrepareForBailoutForId(expr->GetIdForElement(array_index), NO_REGISTERS);
+ }
+
+ // In case the array literal contains spread expressions it has two parts. The
+ // first part is the "static" array which has a literal index is handled
+ // above. The second part is the part after the first spread expression
+ // (inclusive) and these elements gets appended to the array. Note that the
+ // number elements an iterable produces is unknown ahead of time.
+ if (array_index < length && result_saved) {
+ __ Pop(); // literal index
+ __ Pop(v0);
+ result_saved = false;
+ }
+ for (; array_index < length; array_index++) {
+ Expression* subexpr = subexprs->at(array_index);
+
+ __ Push(v0);
+ if (subexpr->IsSpread()) {
+ VisitForStackValue(subexpr->AsSpread()->expression());
+ __ InvokeBuiltin(Builtins::CONCAT_ITERABLE_TO_ARRAY, CALL_FUNCTION);
+ } else {
+ VisitForStackValue(subexpr);
+ __ CallRuntime(Runtime::kAppendElement, 2);
+ }
+
+ PrepareForBailoutForId(expr->GetIdForElement(array_index), NO_REGISTERS);
}
+
if (result_saved) {
__ Pop(); // literal index
context()->PlugTOS();
// Emit code to evaluate all the non-constant subexpressions and to store
// them into the newly cloned array.
- for (int i = 0; i < length; i++) {
- Expression* subexpr = subexprs->at(i);
+ int array_index = 0;
+ for (; array_index < length; array_index++) {
+ Expression* subexpr = subexprs->at(array_index);
+ if (subexpr->IsSpread()) break;
+
// If the subexpression is a literal or a simple materialized literal it
// is already set in the cloned array.
if (CompileTimeValue::IsCompileTimeValue(subexpr)) continue;
VisitForAccumulatorValue(subexpr);
if (has_fast_elements) {
- int offset = FixedArray::kHeaderSize + (i * kPointerSize);
+ int offset = FixedArray::kHeaderSize + (array_index * kPointerSize);
__ ld(a6, MemOperand(sp, kPointerSize)); // Copy of array literal.
__ ld(a1, FieldMemOperand(a6, JSObject::kElementsOffset));
__ sd(result_register(), FieldMemOperand(a1, offset));
kRAHasBeenSaved, kDontSaveFPRegs,
EMIT_REMEMBERED_SET, INLINE_SMI_CHECK);
} else {
- __ li(a3, Operand(Smi::FromInt(i)));
+ __ li(a3, Operand(Smi::FromInt(array_index)));
__ mov(a0, result_register());
StoreArrayLiteralElementStub stub(isolate());
__ CallStub(&stub);
}
- PrepareForBailoutForId(expr->GetIdForElement(i), NO_REGISTERS);
+ PrepareForBailoutForId(expr->GetIdForElement(array_index), NO_REGISTERS);
+ }
+
+ // In case the array literal contains spread expressions it has two parts. The
+ // first part is the "static" array which has a literal index is handled
+ // above. The second part is the part after the first spread expression
+ // (inclusive) and these elements gets appended to the array. Note that the
+ // number elements an iterable produces is unknown ahead of time.
+ if (array_index < length && result_saved) {
+ __ Pop(); // literal index
+ __ Pop(v0);
+ result_saved = false;
+ }
+ for (; array_index < length; array_index++) {
+ Expression* subexpr = subexprs->at(array_index);
+
+ __ Push(v0);
+ if (subexpr->IsSpread()) {
+ VisitForStackValue(subexpr->AsSpread()->expression());
+ __ InvokeBuiltin(Builtins::CONCAT_ITERABLE_TO_ARRAY, CALL_FUNCTION);
+ } else {
+ VisitForStackValue(subexpr);
+ __ CallRuntime(Runtime::kAppendElement, 2);
+ }
+
+ PrepareForBailoutForId(expr->GetIdForElement(array_index), NO_REGISTERS);
}
+
if (result_saved) {
__ Pop(); // literal index
context()->PlugTOS();
set_allow_harmony_rest_params(FLAG_harmony_rest_parameters);
set_allow_harmony_spreadcalls(FLAG_harmony_spreadcalls);
set_allow_harmony_destructuring(FLAG_harmony_destructuring);
+ set_allow_harmony_spread_arrays(FLAG_harmony_spread_arrays);
set_allow_strong_mode(FLAG_strong_mode);
for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount;
++feature) {
allow_harmony_spreadcalls());
reusable_preparser_->set_allow_harmony_destructuring(
allow_harmony_destructuring());
+ reusable_preparser_->set_allow_harmony_spread_arrays(
+ allow_harmony_spread_arrays());
reusable_preparser_->set_allow_strong_mode(allow_strong_mode());
}
PreParser::PreParseResult result = reusable_preparser_->PreParseLazyFunction(
bool allow_harmony_destructuring() const {
return allow_harmony_destructuring_;
}
+ bool allow_harmony_spread_arrays() const {
+ return allow_harmony_spread_arrays_;
+ }
bool allow_strong_mode() const { return allow_strong_mode_; }
void set_allow_harmony_destructuring(bool allow) {
allow_harmony_destructuring_ = allow;
}
-
+ void set_allow_harmony_spread_arrays(bool allow) {
+ allow_harmony_spread_arrays_ = allow;
+ }
protected:
enum AllowRestrictedIdentifiers {
bool allow_harmony_rest_params_;
bool allow_harmony_spreadcalls_;
bool allow_harmony_destructuring_;
+ bool allow_harmony_spread_arrays_;
bool allow_strong_mode_;
};
}
elem = this->GetLiteralTheHole(peek_position(), factory());
} else if (peek() == Token::ELLIPSIS) {
- ExpressionUnexpectedToken(classifier);
+ if (!allow_harmony_spread_arrays()) {
+ ExpressionUnexpectedToken(classifier);
+ }
int start_pos = peek_position();
Consume(Token::ELLIPSIS);
ExpressionT argument =
this->ParseAssignmentExpression(true, classifier, CHECK_OK);
-
elem = factory()->NewSpread(argument, start_pos);
seen_spread = true;
} else {
var CALL_NON_FUNCTION_AS_CONSTRUCTOR;
var CALL_FUNCTION_PROXY;
var CALL_FUNCTION_PROXY_AS_CONSTRUCTOR;
+var CONCAT_ITERABLE_TO_ARRAY;
var APPLY_PREPARE;
var REFLECT_APPLY_PREPARE;
var REFLECT_CONSTRUCT_PREPARE;
}
+CONCAT_ITERABLE_TO_ARRAY = function CONCAT_ITERABLE_TO_ARRAY(iterable) {
+ return %$concatIterableToArray(this, iterable);
+};
+
+
STACK_OVERFLOW = function STACK_OVERFLOW(length) {
throw %MakeRangeError(kStackOverflow);
}
}
+RUNTIME_FUNCTION(Runtime_AppendElement) {
+ HandleScope scope(isolate);
+ RUNTIME_ASSERT(args.length() == 2);
+
+ CONVERT_ARG_HANDLE_CHECKED(JSArray, array, 0);
+ CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
+
+ int index = Smi::cast(array->length())->value();
+
+ Handle<Object> result;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, result, JSObject::SetElement(array, index, value, NONE, SLOPPY,
+ false, DEFINE_PROPERTY));
+ return *array;
+}
+
+
RUNTIME_FUNCTION(Runtime_DeleteProperty) {
HandleScope scope(isolate);
DCHECK(args.length() == 3);
F(AddNamedProperty, 4, 1) \
F(SetProperty, 4, 1) \
F(AddElement, 4, 1) \
+ F(AppendElement, 2, 1) \
F(DeleteProperty, 3, 1) \
F(HasOwnProperty, 2, 1) \
F(HasProperty, 2, 1) \
}
-void AstTyper::VisitSpread(Spread* expr) { UNREACHABLE(); }
+void AstTyper::VisitSpread(Spread* expr) { RECURSE(Visit(expr->expression())); }
void AstTyper::VisitThisFunction(ThisFunction* expr) {
// Emit code to evaluate all the non-constant subexpressions and to store
// them into the newly cloned array.
- for (int i = 0; i < length; i++) {
- Expression* subexpr = subexprs->at(i);
+ int array_index = 0;
+ for (; array_index < length; array_index++) {
+ Expression* subexpr = subexprs->at(array_index);
+ if (subexpr->IsSpread()) break;
+
// If the subexpression is a literal or a simple materialized literal it
// is already set in the cloned array.
if (CompileTimeValue::IsCompileTimeValue(subexpr)) continue;
if (has_constant_fast_elements) {
// Fast-case array literal with ElementsKind of FAST_*_ELEMENTS, they
// cannot transition and don't need to call the runtime stub.
- int offset = FixedArray::kHeaderSize + (i * kPointerSize);
+ int offset = FixedArray::kHeaderSize + (array_index * kPointerSize);
__ movp(rbx, Operand(rsp, kPointerSize)); // Copy of array literal.
__ movp(rbx, FieldOperand(rbx, JSObject::kElementsOffset));
// Store the subexpression value in the array's elements.
INLINE_SMI_CHECK);
} else {
// Store the subexpression value in the array's elements.
- __ Move(rcx, Smi::FromInt(i));
+ __ Move(rcx, Smi::FromInt(array_index));
StoreArrayLiteralElementStub stub(isolate());
__ CallStub(&stub);
}
- PrepareForBailoutForId(expr->GetIdForElement(i), NO_REGISTERS);
+ PrepareForBailoutForId(expr->GetIdForElement(array_index), NO_REGISTERS);
+ }
+
+ // In case the array literal contains spread expressions it has two parts. The
+ // first part is the "static" array which has a literal index is handled
+ // above. The second part is the part after the first spread expression
+ // (inclusive) and these elements gets appended to the array. Note that the
+ // number elements an iterable produces is unknown ahead of time.
+ if (array_index < length && result_saved) {
+ __ Drop(1); // literal index
+ __ Pop(rax);
+ result_saved = false;
+ }
+ for (; array_index < length; array_index++) {
+ Expression* subexpr = subexprs->at(array_index);
+
+ __ Push(rax);
+ if (subexpr->IsSpread()) {
+ VisitForStackValue(subexpr->AsSpread()->expression());
+ __ InvokeBuiltin(Builtins::CONCAT_ITERABLE_TO_ARRAY, CALL_FUNCTION);
+ } else {
+ VisitForStackValue(subexpr);
+ __ CallRuntime(Runtime::kAppendElement, 2);
+ }
+
+ PrepareForBailoutForId(expr->GetIdForElement(array_index), NO_REGISTERS);
}
if (result_saved) {
- __ addp(rsp, Immediate(kPointerSize)); // literal index
+ __ Drop(1); // literal index
context()->PlugTOS();
} else {
context()->Plug(rax);
kAllowHarmonyComputedPropertyNames,
kAllowHarmonySpreadCalls,
kAllowHarmonyDestructuring,
+ kAllowHarmonySpreadArrays,
kAllowStrongMode
};
flags.Contains(kAllowHarmonyComputedPropertyNames));
parser->set_allow_harmony_destructuring(
flags.Contains(kAllowHarmonyDestructuring));
+ parser->set_allow_harmony_spread_arrays(
+ flags.Contains(kAllowHarmonySpreadArrays));
parser->set_allow_strong_mode(flags.Contains(kAllowStrongMode));
}
arraysize(always_flags));
}
}
+
+
+TEST(SpreadArray) {
+ i::FLAG_harmony_spread_arrays = true;
+
+ const char* context_data[][2] = {
+ {"'use strict';", ""}, {"", ""}, {NULL, NULL}};
+
+ // clang-format off
+ const char* data[] = {
+ "[...a]",
+ "[a, ...b]",
+ "[...a,]",
+ "[...a, ,]",
+ "[, ...a]",
+ "[...a, ...b]",
+ "[...a, , ...b]",
+ "[...[...a]]",
+ "[, ...a]",
+ "[, , ...a]",
+ NULL};
+ // clang-format on
+ static const ParserFlag always_flags[] = {kAllowHarmonySpreadArrays};
+ RunParserSyncTest(context_data, data, kSuccess, NULL, 0, always_flags,
+ arraysize(always_flags));
+}
+
+
+TEST(SpreadArrayError) {
+ i::FLAG_harmony_spread_arrays = true;
+
+ const char* context_data[][2] = {
+ {"'use strict';", ""}, {"", ""}, {NULL, NULL}};
+
+ // clang-format off
+ const char* data[] = {
+ "[...]",
+ "[a, ...]",
+ "[..., ]",
+ "[..., ...]",
+ "[ (...a)]",
+ NULL};
+ // clang-format on
+ static const ParserFlag always_flags[] = {kAllowHarmonySpreadArrays};
+ RunParserSyncTest(context_data, data, kError, NULL, 0, always_flags,
+ arraysize(always_flags));
+}
--- /dev/null
+// Copyright 2015 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: --harmony-spread-arrays --allow-natives-syntax
+
+(function TestBasics() {
+ var a = [1, 2];
+ var b = [...a];
+ assertArrayEquals([1, 2], b)
+
+ assertArrayEquals(['a', 'b', 'c', 'd', 'e', 'f'],
+ ['a', ...'bc', 'd', ...'ef'])
+})();
+
+
+var log = [];
+
+function* gen(n) {
+ log.push(n, 1);
+ yield 1;
+ log.push(n, 2);
+ yield 2;
+ log.push(n, 3);
+ yield 3;
+ log.push(n, 'done');
+}
+
+function id(v) {
+ log.push(v);
+ return v;
+}
+
+
+(function TestGenerator() {
+ assertArrayEquals([1, 2, 3], [...gen('a')]);
+ assertArrayEquals(['x', 1, 2, 3, 'y', 1, 2, 3, 'z'],
+ ['x', ...gen('a'), 'y', ...gen('b'), 'z']);
+})();
+
+
+(function TestOrderOfExecution() {
+ log = [];
+ assertArrayEquals(['x', 1, 2, 3, 'y', 1, 2, 3, 'z'],
+ [id('x'), ...gen('a'), id('y'), ...gen('b'), id('z')]);
+ assertArrayEquals([
+ 'x', 'a', 1, 'a', 2, 'a', 3, 'a', 'done',
+ 'y', 'b', 1, 'b', 2, 'b', 3, 'b', 'done',
+ 'z'
+ ], log);
+})();
+
+
+(function TestNotIterable() {
+ var a;
+ assertThrows(function() {
+ a = [...42];
+ }, TypeError);
+ assertSame(undefined, a);
+
+
+})();
+
+
+(function TestInvalidIterator() {
+ var iter = {
+ [Symbol.iterator]: 42
+ };
+ var a;
+ assertThrows(function() {
+ a = [...iter];
+ }, TypeError);
+ assertSame(undefined, a);
+})();
+
+
+(function TestIteratorNotAnObject() {
+ var iter = {
+ [Symbol.iterator]() {
+ return 42;
+ }
+ };
+ var a;
+ assertThrows(function() {
+ a = [...iter];
+ }, TypeError);
+ assertSame(undefined, a);
+})();
+
+
+(function TestIteratorNoNext() {
+ var iter = {
+ [Symbol.iterator]() {
+ return {};
+ }
+ };
+ var a;
+ assertThrows(function() {
+ a = [...iter];
+ }, TypeError);
+ assertSame(undefined, a);
+})();
+
+
+(function TestIteratorResultDoneThrows() {
+ function MyError() {}
+ var iter = {
+ [Symbol.iterator]() {
+ return {
+ next() {
+ return {
+ get done() {
+ throw new MyError();
+ }
+ }
+ }
+ };
+ }
+ };
+ var a;
+ assertThrows(function() {
+ a = [...iter];
+ }, MyError);
+ assertSame(undefined, a);
+})();
+
+
+(function TestIteratorResultValueThrows() {
+ function MyError() {}
+ var iter = {
+ [Symbol.iterator]() {
+ return {
+ next() {
+ return {
+ done: false,
+ get value() {
+ throw new MyError();
+ }
+ }
+ }
+ };
+ }
+ };
+ var a;
+ assertThrows(function() {
+ a = [...iter];
+ }, MyError);
+ assertSame(undefined, a);
+})();
+
+
+(function TestOptimize() {
+ function f() {
+ return [...'abc'];
+ }
+ assertArrayEquals(['a', 'b', 'c'], f());
+ %OptimizeFunctionOnNextCall(f);
+ assertArrayEquals(['a', 'b', 'c'], f());
+})();
+
+
+(function TestDeoptimize() {
+ var iter = {
+ [Symbol.iterator]() {
+ var i = 0;
+ return {
+ next() {
+ $DeoptimizeFunction(f);
+ return {value: ++i, done: i === 3};
+ }
+ };
+ }
+ };
+ function f() {
+ return [0, ...iter];
+ }
+
+ assertArrayEquals([0, 1, 2], f());
+});