[es6] Spread in array literals
authorarv <arv@chromium.org>
Thu, 21 May 2015 08:09:06 +0000 (01:09 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 21 May 2015 08:08:55 +0000 (08:08 +0000)
This allows you to put iterables into your array literals
and the will get spread into the array.

  let x = [0, ...range(1, 3)];  // [0, 1, 2]

This is done by treating the array literal up to the first
spread element as usual, including using a boiler plate
array, and then appending the remaining expressions and rest
expressions.

BUG=v8:3018
LOG=N

Review URL: https://codereview.chromium.org/1125183008

Cr-Commit-Position: refs/heads/master@{#28534}

23 files changed:
src/arm/full-codegen-arm.cc
src/arm64/full-codegen-arm64.cc
src/ast-numbering.cc
src/ast.cc
src/bailout-reason.h
src/bootstrapper.cc
src/builtins.h
src/compiler/ast-graph-builder.cc
src/flag-definitions.h
src/full-codegen.cc
src/hydrogen.cc
src/ia32/full-codegen-ia32.cc
src/mips/full-codegen-mips.cc
src/mips64/full-codegen-mips64.cc
src/parser.cc
src/preparser.h
src/runtime.js
src/runtime/runtime-object.cc
src/runtime/runtime.h
src/typing.cc
src/x64/full-codegen-x64.cc
test/cctest/test-parsing.cc
test/mjsunit/harmony/spread-array.js [new file with mode: 0644]

index e6a19a4..9d626a6 100644 (file)
@@ -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) {
index 174658a..632ec5f 100644 (file)
@@ -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) {
index d1b64ba..13d8bf8 100644 (file)
@@ -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) {
index 5038716..a73c613 100644 (file)
@@ -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<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());
   }
index cd73674..ed42124 100644 (file)
@@ -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")                                        \
index 08627e8..30c6755 100644 (file)
@@ -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<JSObject> 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++) {
index df72091..90ed7ad 100644 (file)
@@ -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;
index 2d92ca5..1c98099 100644 (file)
@@ -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());
 }
 
index d74200a..fad1139 100644 (file)
@@ -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)                               \
index 66c790b..4ea455e 100644 (file)
@@ -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) {
index 5bf9f7b..84de23d 100644 (file)
@@ -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;
index c9c0163..7d575d8 100644 (file)
@@ -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);
index df1d093..67ec571 100644 (file)
@@ -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();
index c968d4a..b253ed8 100644 (file)
@@ -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();
index d4607d1..c2e7c6d 100644 (file)
@@ -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(
index c14fb9b..d38aaaa 100644 (file)
@@ -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<Traits>::ExpressionT ParserBase<Traits>::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 {
index 3322e75..d89715a 100644 (file)
@@ -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);
 }
index 5a5ae33..b470c96 100644 (file)
@@ -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<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);
index df616be..192e342 100644 (file)
@@ -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)                               \
index ac3dd91..c0d3ddd 100644 (file)
@@ -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) {
index 463fbdc..6e26e76 100644 (file)
@@ -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);
index d99b2b8..659680d 100644 (file)
@@ -1366,6 +1366,7 @@ enum ParserFlag {
   kAllowHarmonyComputedPropertyNames,
   kAllowHarmonySpreadCalls,
   kAllowHarmonyDestructuring,
+  kAllowHarmonySpreadArrays,
   kAllowStrongMode
 };
 
@@ -1397,6 +1398,8 @@ void SetParserFlags(i::ParserBase<Traits>* 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 (file)
index 0000000..18b72a2
--- /dev/null
@@ -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());
+});