[es6] Spread in array literals
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}
This commit is contained in:
parent
e56585077b
commit
9502e91adb
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
20
src/ast.cc
20
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<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());
|
||||
}
|
||||
|
@ -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") \
|
||||
|
@ -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++) {
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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) \
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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) \
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
|
179
test/mjsunit/harmony/spread-array.js
Normal file
179
test/mjsunit/harmony/spread-array.js
Normal file
@ -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());
|
||||
});
|
Loading…
Reference in New Issue
Block a user