Commit a25e7688 authored by petermarshall's avatar petermarshall Committed by Commit bot

[Ignition/turbo] Add a NewWithSpread bytecode.

This just calls into a runtime function for implementation currently.

Intermediate step in speeding up constructor calls containing a spread.

The NewWithSpread bytecode will probably end up having different arguments with future CLs - the constructor and the new.target should have their own regs. For now we are calling into the runtime function, so we need the regs together.

BUG=v8:5659

Review-Url: https://codereview.chromium.org/2541113004
Cr-Commit-Position: refs/heads/master@{#41542}
parent da2529ad
......@@ -1332,6 +1332,17 @@ void BytecodeGraphBuilder::VisitCallRuntimeForPair() {
Environment::kAttachFrameState);
}
void BytecodeGraphBuilder::VisitNewWithSpread() {
PrepareEagerCheckpoint();
interpreter::Register first_arg = bytecode_iterator().GetRegisterOperand(0);
size_t arg_count = bytecode_iterator().GetRegisterCountOperand(1);
const Operator* call =
javascript()->CallRuntime(Runtime::kNewWithSpread, arg_count);
Node* value = ProcessCallRuntimeArguments(call, first_arg, arg_count);
environment()->BindAccumulator(value, Environment::kAttachFrameState);
}
void BytecodeGraphBuilder::VisitInvokeIntrinsic() {
PrepareEagerCheckpoint();
Runtime::FunctionId functionId = bytecode_iterator().GetIntrinsicIdOperand(0);
......
......@@ -926,6 +926,11 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CallJSRuntime(int context_index,
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::NewWithSpread(RegisterList args) {
OutputNewWithSpread(args, args.register_count());
return *this;
}
BytecodeArrayBuilder& BytecodeArrayBuilder::Delete(Register object,
LanguageMode language_mode) {
if (language_mode == SLOPPY) {
......
......@@ -240,6 +240,11 @@ class V8_EXPORT_PRIVATE BytecodeArrayBuilder final
// Call the JS runtime function with |context_index| and arguments |args|.
BytecodeArrayBuilder& CallJSRuntime(int context_index, RegisterList args);
// Call the constructor in |args[0]| with new_target in |args[1]| and the
// arguments starting at |args[2]| onwards. The final argument must be a
// spread.
BytecodeArrayBuilder& NewWithSpread(RegisterList args);
// Operators (register holds the lhs value, accumulator holds the rhs value).
// Type feedback will be recorded in the |feedback_slot|
BytecodeArrayBuilder& BinaryOperation(Token::Value binop, Register reg,
......
......@@ -2501,25 +2501,12 @@ void BytecodeGenerator::VisitCallSuper(Call* expr) {
// When a super call contains a spread, a CallSuper AST node is only created
// if there is exactly one spread, and it is the last argument.
if (!args->is_empty() && args->last()->IsSpread()) {
// Prepare the spread arguments.
RegisterList spread_prepare_args =
register_allocator()->NewRegisterList(args->length());
VisitArguments(args, spread_prepare_args);
builder()->CallRuntime(Runtime::kSpreadIterablePrepareVarargs,
spread_prepare_args);
// Call ReflectConstruct to do the actual super constructor call.
RegisterList reflect_construct_args =
register_allocator()->NewRegisterList(4);
builder()
->StoreAccumulatorInRegister(reflect_construct_args[2])
.LoadUndefined()
.StoreAccumulatorInRegister(reflect_construct_args[0])
.MoveRegister(constructor, reflect_construct_args[1]);
VisitForRegisterValue(super->new_target_var(), reflect_construct_args[3]);
builder()->CallJSRuntime(Context::REFLECT_CONSTRUCT_INDEX,
reflect_construct_args);
RegisterList args_regs =
register_allocator()->NewRegisterList(args->length() + 2);
builder()->MoveRegister(constructor, args_regs[0]);
VisitForRegisterValue(super->new_target_var(), args_regs[1]);
VisitArguments(args, args_regs, 2);
builder()->NewWithSpread(args_regs);
} else {
RegisterList args_regs =
register_allocator()->NewRegisterList(args->length());
......
......@@ -161,9 +161,11 @@ namespace interpreter {
V(InvokeIntrinsic, AccumulatorUse::kWrite, OperandType::kIntrinsicId, \
OperandType::kRegList, OperandType::kRegCount) \
\
/* New operator */ \
/* New operators */ \
V(New, AccumulatorUse::kReadWrite, OperandType::kReg, OperandType::kRegList, \
OperandType::kRegCount, OperandType::kIdx) \
V(NewWithSpread, AccumulatorUse::kWrite, OperandType::kRegList, \
OperandType::kRegCount) \
\
/* Test Operators */ \
V(TestEqual, AccumulatorUse::kReadWrite, OperandType::kReg, \
......
......@@ -1828,6 +1828,26 @@ void Interpreter::DoCallJSRuntime(InterpreterAssembler* assembler) {
__ Dispatch();
}
// NewWithSpread <first_arg> <arg_count>
//
// Call the constructor in |first_arg| with the new.target in |first_arg + 1|
// for the |arg_count - 2| following arguments. The final argument is always a
// spread.
//
void Interpreter::DoNewWithSpread(InterpreterAssembler* assembler) {
Node* first_arg_reg = __ BytecodeOperandReg(0);
Node* first_arg = __ RegisterLocation(first_arg_reg);
Node* args_count = __ BytecodeOperandCount(1);
Node* context = __ GetContext();
// Call into Runtime function NewWithSpread which does everything.
Node* runtime_function = __ Int32Constant(Runtime::kNewWithSpread);
Node* result =
__ CallRuntimeN(runtime_function, context, first_arg, args_count);
__ SetAccumulator(result);
__ Dispatch();
}
// New <constructor> <first_arg> <arg_count>
//
// Call operator new with |constructor| and the first argument in
......
......@@ -2175,6 +2175,35 @@ MaybeHandle<Object> Object::ArraySpeciesConstructor(
}
}
bool Object::IterationHasObservableEffects() {
if (IsJSArray()) {
// Check that the spread arg has fast elements
JSArray* spread_array = JSArray::cast(this);
ElementsKind array_kind = spread_array->GetElementsKind();
Isolate* isolate = spread_array->GetIsolate();
// And that it has the orignal ArrayPrototype
JSObject* array_proto = JSObject::cast(spread_array->map()->prototype());
Map* iterator_map = isolate->initial_array_iterator_prototype()->map();
// Check that the iterator acts as expected.
// If IsArrayIteratorLookupChainIntact(), then we know that the initial
// ArrayIterator is being used. If the map of the prototype has changed,
// then take the slow path.
if (isolate->is_initial_array_prototype(array_proto) &&
isolate->IsArrayIteratorLookupChainIntact() &&
isolate->is_initial_array_iterator_prototype_map(iterator_map)) {
if (IsFastPackedElementsKind(array_kind)) {
return false;
}
if (IsFastHoleyElementsKind(array_kind) &&
isolate->IsFastArrayConstructorPrototypeChainIntact()) {
return false;
}
}
}
return true;
}
void Object::ShortPrint(FILE* out) {
OFStream os(out);
......
......@@ -1508,6 +1508,11 @@ class Object {
// allow kMaxUInt32.
inline bool ToArrayIndex(uint32_t* index);
// Returns true if the result of iterating over the object is the same
// (including observable effects) as simply accessing the properties between 0
// and length.
bool IterationHasObservableEffects();
DECLARE_VERIFIER(Object)
#ifdef VERIFY_HEAP
// Verify a pointer is a valid object pointer.
......
......@@ -630,38 +630,6 @@ RUNTIME_FUNCTION(Runtime_ArrayIndexOf) {
return Smi::FromInt(-1);
}
namespace {
bool MustIterate(Isolate* isolate, Handle<Object> spread) {
if (spread->IsJSArray()) {
// Check that the spread arg has fast elements
Handle<JSArray> spread_array = Handle<JSArray>::cast(spread);
ElementsKind array_kind = spread_array->GetElementsKind();
// And that it has the orignal ArrayPrototype
JSObject* array_proto = JSObject::cast(spread_array->map()->prototype());
Map* iterator_map = isolate->initial_array_iterator_prototype()->map();
// Check that the iterator acts as expected.
// If IsArrayIteratorLookupChainIntact(), then we know that the initial
// ArrayIterator is being used. If the map of the prototype has changed,
// then take the slow path.
if (isolate->is_initial_array_prototype(array_proto) &&
isolate->IsArrayIteratorLookupChainIntact() &&
isolate->is_initial_array_iterator_prototype_map(iterator_map)) {
if (IsFastPackedElementsKind(array_kind)) {
return false;
}
if (IsFastHoleyElementsKind(array_kind) &&
isolate->IsFastArrayConstructorPrototypeChainIntact()) {
return false;
}
}
}
return true;
}
} // namespace
RUNTIME_FUNCTION(Runtime_SpreadIterablePrepare) {
HandleScope scope(isolate);
......@@ -669,7 +637,7 @@ RUNTIME_FUNCTION(Runtime_SpreadIterablePrepare) {
CONVERT_ARG_HANDLE_CHECKED(Object, spread, 0);
// Iterate over the spread if we need to.
if (MustIterate(isolate, spread)) {
if (spread->IterationHasObservableEffects()) {
Handle<JSFunction> spread_iterable_function = isolate->spread_iterable();
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, spread,
......@@ -680,44 +648,5 @@ RUNTIME_FUNCTION(Runtime_SpreadIterablePrepare) {
return *spread;
}
RUNTIME_FUNCTION(Runtime_SpreadIterablePrepareVarargs) {
HandleScope scope(isolate);
DCHECK_LE(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, spread, args.length() - 1);
// Iterate over the spread if we need to.
if (MustIterate(isolate, spread)) {
Handle<JSFunction> spread_iterable_function = isolate->spread_iterable();
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, spread,
Execution::Call(isolate, spread_iterable_function,
isolate->factory()->undefined_value(), 1, &spread));
}
if (args.length() == 1) return *spread;
JSArray* spread_array = JSArray::cast(*spread);
uint32_t spread_length;
CHECK(spread_array->length()->ToArrayIndex(&spread_length));
// Append each of the individual args to the result.
int result_length = args.length() - 1 + spread_length;
Handle<FixedArray> result = isolate->factory()->NewFixedArray(result_length);
for (int i = 0; i < args.length() - 1; i++) {
result->set(i, *args.at<Object>(i));
}
// Append element of the spread to the result.
for (uint32_t i = 0; i < spread_length; i++) {
LookupIterator it(isolate, spread, i);
Handle<Object> element = spread_array->GetDataProperty(&it);
result->set(args.length() - 1 + i, *element);
}
Handle<JSArray> r = isolate->factory()->NewJSArrayWithElements(
result, FAST_ELEMENTS, result_length);
return *r;
}
} // namespace internal
} // namespace v8
......@@ -388,5 +388,48 @@ RUNTIME_FUNCTION(Runtime_GetSuperConstructor) {
return active_function->map()->prototype();
}
RUNTIME_FUNCTION(Runtime_NewWithSpread) {
HandleScope scope(isolate);
DCHECK_LE(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, constructor, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, new_target, 1);
int constructor_argc = args.length() - 2;
CONVERT_ARG_HANDLE_CHECKED(Object, spread, args.length() - 1);
// Iterate over the spread if we need to.
if (spread->IterationHasObservableEffects()) {
Handle<JSFunction> spread_iterable_function = isolate->spread_iterable();
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, spread,
Execution::Call(isolate, spread_iterable_function,
isolate->factory()->undefined_value(), 1, &spread));
}
uint32_t spread_length;
Handle<JSArray> spread_array = Handle<JSArray>::cast(spread);
CHECK(spread_array->length()->ToArrayIndex(&spread_length));
int result_length = constructor_argc - 1 + spread_length;
ScopedVector<Handle<Object>> construct_args(result_length);
// Append each of the individual args to the result.
for (int i = 0; i < constructor_argc - 1; i++) {
construct_args[i] = args.at<Object>(2 + i);
}
// Append element of the spread to the result.
for (uint32_t i = 0; i < spread_length; i++) {
// TODO(petermarshall): Use ElementAccessors here.
LookupIterator it(isolate, spread, i);
Handle<Object> element = spread_array->GetDataProperty(&it);
construct_args[constructor_argc - 1 + i] = element;
}
// Call the constructor.
RETURN_RESULT_OR_FAILURE(
isolate, Execution::New(isolate, constructor, new_target, result_length,
construct_args.start()));
}
} // namespace internal
} // namespace v8
......@@ -56,8 +56,7 @@ namespace internal {
F(ArraySpeciesConstructor, 1, 1) \
F(ArrayIncludes_Slow, 3, 1) \
F(ArrayIndexOf, 3, 1) \
F(SpreadIterablePrepare, 1, 1) \
F(SpreadIterablePrepareVarargs, -1, 1)
F(SpreadIterablePrepare, 1, 1)
#define FOR_EACH_INTRINSIC_ATOMICS(F) \
F(ThrowNotIntegerSharedTypedArrayError, 1, 1) \
......@@ -89,7 +88,8 @@ namespace internal {
F(StoreToSuper_Sloppy, 4, 1) \
F(StoreKeyedToSuper_Strict, 4, 1) \
F(StoreKeyedToSuper_Sloppy, 4, 1) \
F(GetSuperConstructor, 1, 1)
F(GetSuperConstructor, 1, 1) \
F(NewWithSpread, -1, 1)
#define FOR_EACH_INTRINSIC_COLLECTIONS(F) \
F(StringGetRawHashField, 1, 1) \
......
......@@ -17,9 +17,9 @@ snippet: "
test = new B(1, 2, 3).constructor;
})();
"
frame size: 9
frame size: 7
parameter count: 1
bytecode array length: 40
bytecode array length: 32
bytecodes: [
B(CreateRestParameter),
B(Star), R(2),
......@@ -29,13 +29,10 @@ bytecodes: [
/* 93 E> */ B(StackCheck),
/* 93 S> */ B(CallRuntime), U16(Runtime::k_GetSuperConstructor), R(1), U8(1),
B(Star), R(3),
B(CallRuntime), U16(Runtime::kSpreadIterablePrepareVarargs), R(2), U8(1),
B(Star), R(7),
B(LdaUndefined),
B(Star), R(5),
B(Mov), R(3), R(6),
B(Mov), R(0), R(8),
/* 93 E> */ B(CallJSRuntime), U8(150), R(5), U8(4),
B(Mov), R(3), R(4),
B(Mov), R(0), R(5),
B(Mov), R(2), R(6),
/* 93 E> */ B(NewWithSpread), R(4), U8(3),
/* 93 S> */ B(Return),
]
constant pool: [
......@@ -56,9 +53,9 @@ snippet: "
test = new B(1, 2, 3).constructor;
})();
"
frame size: 10
frame size: 8
parameter count: 1
bytecode array length: 80
bytecode array length: 69
bytecodes: [
B(CreateRestParameter),
B(Star), R(2),
......@@ -69,15 +66,11 @@ bytecodes: [
/* 140 S> */ B(CallRuntime), U16(Runtime::k_GetSuperConstructor), R(1), U8(1),
B(Star), R(3),
B(LdaSmi), U8(1),
B(Star), R(4),
B(Mov), R(2), R(5),
/* 152 E> */ B(CallRuntime), U16(Runtime::kSpreadIterablePrepareVarargs), R(4), U8(2),
B(Star), R(8),
B(LdaUndefined),
B(Star), R(6),
B(Mov), R(3), R(7),
B(Mov), R(0), R(9),
/* 140 E> */ B(CallJSRuntime), U8(150), R(6), U8(4),
B(Mov), R(3), R(4),
B(Mov), R(0), R(5),
B(Mov), R(2), R(7),
/* 152 E> */ B(NewWithSpread), R(4), U8(4),
B(Star), R(3),
B(Ldar), R(this),
B(JumpIfNotHole), U8(4),
......
......@@ -135,7 +135,8 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) {
.Call(reg, reg_list, 1, Call::GLOBAL_CALL, TailCallMode::kAllow)
.CallRuntime(Runtime::kIsArray, reg)
.CallRuntimeForPair(Runtime::kLoadLookupSlotForCall, reg_list, pair)
.CallJSRuntime(Context::SPREAD_ITERABLE_INDEX, reg_list);
.CallJSRuntime(Context::SPREAD_ITERABLE_INDEX, reg_list)
.NewWithSpread(reg_list);
// Emit binary operator invocations.
builder.BinaryOperation(Token::Value::ADD, reg, 1)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment