diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index e6a19a478c..9d626a6a47 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -1893,8 +1893,11 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* 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; @@ -1907,7 +1910,7 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { 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)); @@ -1916,12 +1919,37 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { 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) { diff --git a/src/arm64/full-codegen-arm64.cc b/src/arm64/full-codegen-arm64.cc index 174658a4d8..632ec5f44e 100644 --- a/src/arm64/full-codegen-arm64.cc +++ b/src/arm64/full-codegen-arm64.cc @@ -1864,8 +1864,11 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* 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; @@ -1878,7 +1881,7 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { 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)); @@ -1887,12 +1890,37 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { 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) { diff --git a/src/ast-numbering.cc b/src/ast-numbering.cc index d1b64bacb6..13d8bf8a34 100644 --- a/src/ast-numbering.cc +++ b/src/ast-numbering.cc @@ -324,7 +324,11 @@ void AstNumberingVisitor::VisitCompareOperation(CompareOperation* node) { } -void AstNumberingVisitor::VisitSpread(Spread* node) { UNREACHABLE(); } +void AstNumberingVisitor::VisitSpread(Spread* node) { + IncrementNodeCount(); + DisableOptimization(kSpread); + Visit(node->expression()); +} void AstNumberingVisitor::VisitForInStatement(ForInStatement* node) { diff --git a/src/ast.cc b/src/ast.cc index 5038716003..a73c613823 100644 --- a/src/ast.cc +++ b/src/ast.cc @@ -386,8 +386,10 @@ void ArrayLiteral::BuildConstantElements(Isolate* isolate) { 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); @@ -400,18 +402,24 @@ void ArrayLiteral::BuildConstantElements(Isolate* 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 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()); } diff --git a/src/bailout-reason.h b/src/bailout-reason.h index cd73674383..ed42124fd5 100644 --- a/src/bailout-reason.h +++ b/src/bailout-reason.h @@ -210,6 +210,7 @@ namespace internal { 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") \ diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 08627e8276..30c6755b42 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1746,6 +1746,7 @@ EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_reflect) 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() { @@ -1776,6 +1777,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_rest_parameters) 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 builtins(native_context()->builtins()); @@ -2401,6 +2403,7 @@ bool Genesis::InstallExperimentalNatives() { 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++) { diff --git a/src/builtins.h b/src/builtins.h index df72091e22..90ed7ad746 100644 --- a/src/builtins.h +++ b/src/builtins.h @@ -211,6 +211,7 @@ enum BuiltinExtraArguments { 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; diff --git a/src/compiler/ast-graph-builder.cc b/src/compiler/ast-graph-builder.cc index 2d92ca5d82..1c9809958c 100644 --- a/src/compiler/ast-graph-builder.cc +++ b/src/compiler/ast-graph-builder.cc @@ -1927,23 +1927,64 @@ void AstGraphBuilder::VisitArrayLiteral(ArrayLiteral* expr) { // 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()); } diff --git a/src/flag-definitions.h b/src/flag-definitions.h index d74200a5d0..fad1139540 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -193,6 +193,7 @@ DEFINE_IMPLICATION(es_staging, harmony) 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) \ diff --git a/src/full-codegen.cc b/src/full-codegen.cc index 66c790b7c9..4ea455edfe 100644 --- a/src/full-codegen.cc +++ b/src/full-codegen.cc @@ -267,7 +267,9 @@ void BreakableStatementChecker::VisitCompareOperation(CompareOperation* expr) { } -void BreakableStatementChecker::VisitSpread(Spread* expr) { UNREACHABLE(); } +void BreakableStatementChecker::VisitSpread(Spread* expr) { + Visit(expr->expression()); +} void BreakableStatementChecker::VisitThisFunction(ThisFunction* expr) { diff --git a/src/hydrogen.cc b/src/hydrogen.cc index 5bf9f7b0d1..84de23dc5c 100644 --- a/src/hydrogen.cc +++ b/src/hydrogen.cc @@ -5861,6 +5861,10 @@ void HOptimizedGraphBuilder::VisitArrayLiteral(ArrayLiteral* 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; diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index c9c016394f..7d575d870e 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -1815,8 +1815,11 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* 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; @@ -1831,7 +1834,7 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { 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. @@ -1843,16 +1846,41 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { 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); diff --git a/src/mips/full-codegen-mips.cc b/src/mips/full-codegen-mips.cc index df1d093087..67ec571296 100644 --- a/src/mips/full-codegen-mips.cc +++ b/src/mips/full-codegen-mips.cc @@ -1877,8 +1877,11 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* 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; @@ -1892,7 +1895,7 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { 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)); @@ -1901,14 +1904,40 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { 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(); diff --git a/src/mips64/full-codegen-mips64.cc b/src/mips64/full-codegen-mips64.cc index c968d4a915..b253ed8b39 100644 --- a/src/mips64/full-codegen-mips64.cc +++ b/src/mips64/full-codegen-mips64.cc @@ -1876,8 +1876,11 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* 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; @@ -1891,7 +1894,7 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { 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)); @@ -1900,14 +1903,40 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { 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(); diff --git a/src/parser.cc b/src/parser.cc index d4607d1007..c2e7c6dbf8 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -878,6 +878,7 @@ Parser::Parser(ParseInfo* info) 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) { @@ -4288,6 +4289,8 @@ PreParser::PreParseResult Parser::ParseLazyFunctionBodyWithPreParser( 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( diff --git a/src/preparser.h b/src/preparser.h index c14fb9b20c..d38aaaaa26 100644 --- a/src/preparser.h +++ b/src/preparser.h @@ -123,6 +123,9 @@ class ParserBase : public Traits { 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_; } @@ -161,7 +164,9 @@ class ParserBase : public Traits { 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 { @@ -1011,6 +1016,7 @@ class ParserBase : public Traits { bool allow_harmony_rest_params_; bool allow_harmony_spreadcalls_; bool allow_harmony_destructuring_; + bool allow_harmony_spread_arrays_; bool allow_strong_mode_; }; @@ -2508,12 +2514,13 @@ typename ParserBase::ExpressionT ParserBase::ParseArrayLiteral( } 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 { diff --git a/src/runtime.js b/src/runtime.js index 3322e7597b..d89715a1b5 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -54,6 +54,7 @@ var CALL_NON_FUNCTION; 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; @@ -726,6 +727,11 @@ REFLECT_CONSTRUCT_PREPARE = function 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); } diff --git a/src/runtime/runtime-object.cc b/src/runtime/runtime-object.cc index 5a5ae335a5..b470c96705 100644 --- a/src/runtime/runtime-object.cc +++ b/src/runtime/runtime-object.cc @@ -721,6 +721,23 @@ RUNTIME_FUNCTION(Runtime_AddElement) { } +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 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); diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index df616be9df..192e342d9b 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -414,6 +414,7 @@ namespace internal { 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) \ diff --git a/src/typing.cc b/src/typing.cc index ac3dd91409..c0d3ddd308 100644 --- a/src/typing.cc +++ b/src/typing.cc @@ -754,7 +754,7 @@ void AstTyper::VisitCompareOperation(CompareOperation* expr) { } -void AstTyper::VisitSpread(Spread* expr) { UNREACHABLE(); } +void AstTyper::VisitSpread(Spread* expr) { RECURSE(Visit(expr->expression())); } void AstTyper::VisitThisFunction(ThisFunction* expr) { diff --git a/src/x64/full-codegen-x64.cc b/src/x64/full-codegen-x64.cc index 463fbdc0dc..6e26e7670c 100644 --- a/src/x64/full-codegen-x64.cc +++ b/src/x64/full-codegen-x64.cc @@ -1847,8 +1847,11 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* 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; @@ -1863,7 +1866,7 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { 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. @@ -1875,16 +1878,41 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { 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); diff --git a/test/cctest/test-parsing.cc b/test/cctest/test-parsing.cc index d99b2b86ae..659680d828 100644 --- a/test/cctest/test-parsing.cc +++ b/test/cctest/test-parsing.cc @@ -1366,6 +1366,7 @@ enum ParserFlag { kAllowHarmonyComputedPropertyNames, kAllowHarmonySpreadCalls, kAllowHarmonyDestructuring, + kAllowHarmonySpreadArrays, kAllowStrongMode }; @@ -1397,6 +1398,8 @@ void SetParserFlags(i::ParserBase* parser, 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)); } @@ -6520,3 +6523,50 @@ TEST(DestructuringNegativeTests) { 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)); +} diff --git a/test/mjsunit/harmony/spread-array.js b/test/mjsunit/harmony/spread-array.js new file mode 100644 index 0000000000..18b72a28c5 --- /dev/null +++ b/test/mjsunit/harmony/spread-array.js @@ -0,0 +1,179 @@ +// 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()); +});