[turbofan] Introduce new JSCallWithArrayLike operator.
Add a new JSCallWithArrayLike operator that is backed by the CallWithArrayLike builtin, and use that operator for both Function.prototype.apply and Reflect.apply inlining. Also unify the handling of JSCallWithArrayLike and JSCallWithSpread in the JSCallReducer to reduce the copy&paste overhead. Drive-by-fix: Add a lot of test coverage for Reflect.apply and Function.prototype.apply in optimized code, especially for some corner cases, which was missing so far. BUG=v8:4587,v8:5269 R=petermarshall@chromium.org Review-Url: https://codereview.chromium.org/2950773002 Cr-Commit-Position: refs/heads/master@{#46041}
This commit is contained in:
parent
d00b37fb19
commit
767ce78871
@ -1930,13 +1930,9 @@ void Builtins::Generate_FunctionPrototypeApply(MacroAssembler* masm) {
|
||||
// -- sp[0] : thisArg
|
||||
// -----------------------------------
|
||||
|
||||
// 2. Make sure the receiver is actually callable.
|
||||
Label receiver_not_callable;
|
||||
__ JumpIfSmi(r1, &receiver_not_callable);
|
||||
__ ldr(r4, FieldMemOperand(r1, HeapObject::kMapOffset));
|
||||
__ ldrb(r4, FieldMemOperand(r4, Map::kBitFieldOffset));
|
||||
__ tst(r4, Operand(1 << Map::kIsCallable));
|
||||
__ b(eq, &receiver_not_callable);
|
||||
// 2. We don't need to check explicitly for callable receiver here,
|
||||
// since that's the first thing the Call/CallWithArrayLike builtins
|
||||
// will do.
|
||||
|
||||
// 3. Tail call with no arguments if argArray is null or undefined.
|
||||
Label no_arguments;
|
||||
@ -1954,13 +1950,6 @@ void Builtins::Generate_FunctionPrototypeApply(MacroAssembler* masm) {
|
||||
__ mov(r0, Operand(0));
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
// 4c. The receiver is not callable, throw an appropriate TypeError.
|
||||
__ bind(&receiver_not_callable);
|
||||
{
|
||||
__ str(r1, MemOperand(sp, 0));
|
||||
__ TailCallRuntime(Runtime::kThrowApplyNonFunction);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
@ -2038,24 +2027,13 @@ void Builtins::Generate_ReflectApply(MacroAssembler* masm) {
|
||||
// -- sp[0] : thisArgument
|
||||
// -----------------------------------
|
||||
|
||||
// 2. Make sure the target is actually callable.
|
||||
Label target_not_callable;
|
||||
__ JumpIfSmi(r1, &target_not_callable);
|
||||
__ ldr(r4, FieldMemOperand(r1, HeapObject::kMapOffset));
|
||||
__ ldrb(r4, FieldMemOperand(r4, Map::kBitFieldOffset));
|
||||
__ tst(r4, Operand(1 << Map::kIsCallable));
|
||||
__ b(eq, &target_not_callable);
|
||||
// 2. We don't need to check explicitly for callable target here,
|
||||
// since that's the first thing the Call/CallWithArrayLike builtins
|
||||
// will do.
|
||||
|
||||
// 3a. Apply the target to the given argumentsList.
|
||||
// 3. Apply the target to the given argumentsList.
|
||||
__ Jump(masm->isolate()->builtins()->CallWithArrayLike(),
|
||||
RelocInfo::CODE_TARGET);
|
||||
|
||||
// 3b. The target is not callable, throw an appropriate TypeError.
|
||||
__ bind(&target_not_callable);
|
||||
{
|
||||
__ str(r1, MemOperand(sp, 0));
|
||||
__ TailCallRuntime(Runtime::kThrowApplyNonFunction);
|
||||
}
|
||||
}
|
||||
|
||||
void Builtins::Generate_ReflectConstruct(MacroAssembler* masm) {
|
||||
|
@ -1994,13 +1994,9 @@ void Builtins::Generate_FunctionPrototypeApply(MacroAssembler* masm) {
|
||||
// -- jssp[0] : thisArg
|
||||
// -----------------------------------
|
||||
|
||||
// 2. Make sure the receiver is actually callable.
|
||||
Label receiver_not_callable;
|
||||
__ JumpIfSmi(receiver, &receiver_not_callable);
|
||||
__ Ldr(x10, FieldMemOperand(receiver, HeapObject::kMapOffset));
|
||||
__ Ldrb(w10, FieldMemOperand(x10, Map::kBitFieldOffset));
|
||||
__ TestAndBranchIfAllClear(x10, 1 << Map::kIsCallable,
|
||||
&receiver_not_callable);
|
||||
// 2. We don't need to check explicitly for callable receiver here,
|
||||
// since that's the first thing the Call/CallWithArrayLike builtins
|
||||
// will do.
|
||||
|
||||
// 3. Tail call with no arguments if argArray is null or undefined.
|
||||
Label no_arguments;
|
||||
@ -2020,13 +2016,6 @@ void Builtins::Generate_FunctionPrototypeApply(MacroAssembler* masm) {
|
||||
DCHECK(receiver.Is(x1));
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
// 4c. The receiver is not callable, throw an appropriate TypeError.
|
||||
__ Bind(&receiver_not_callable);
|
||||
{
|
||||
__ Poke(receiver, 0);
|
||||
__ TailCallRuntime(Runtime::kThrowApplyNonFunction);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
@ -2125,23 +2114,13 @@ void Builtins::Generate_ReflectApply(MacroAssembler* masm) {
|
||||
// -- jssp[0] : thisArgument
|
||||
// -----------------------------------
|
||||
|
||||
// 2. Make sure the target is actually callable.
|
||||
Label target_not_callable;
|
||||
__ JumpIfSmi(target, &target_not_callable);
|
||||
__ Ldr(x10, FieldMemOperand(target, HeapObject::kMapOffset));
|
||||
__ Ldr(x10, FieldMemOperand(x10, Map::kBitFieldOffset));
|
||||
__ TestAndBranchIfAllClear(x10, 1 << Map::kIsCallable, &target_not_callable);
|
||||
// 2. We don't need to check explicitly for callable target here,
|
||||
// since that's the first thing the Call/CallWithArrayLike builtins
|
||||
// will do.
|
||||
|
||||
// 3a. Apply the target to the given argumentsList.
|
||||
// 3. Apply the target to the given argumentsList.
|
||||
__ Jump(masm->isolate()->builtins()->CallWithArrayLike(),
|
||||
RelocInfo::CODE_TARGET);
|
||||
|
||||
// 3b. The target is not callable, throw an appropriate TypeError.
|
||||
__ Bind(&target_not_callable);
|
||||
{
|
||||
__ Poke(target, 0);
|
||||
__ TailCallRuntime(Runtime::kThrowApplyNonFunction);
|
||||
}
|
||||
}
|
||||
|
||||
void Builtins::Generate_ReflectConstruct(MacroAssembler* masm) {
|
||||
|
@ -107,6 +107,21 @@ void CallOrConstructBuiltinsAssembler::CallOrConstructWithArrayLike(
|
||||
if_holey_array(this, Label::kDeferred),
|
||||
if_runtime(this, Label::kDeferred);
|
||||
|
||||
// Perform appropriate checks on {target} (and {new_target} first).
|
||||
if (new_target == nullptr) {
|
||||
// Check that {target} is Callable.
|
||||
Label if_target_callable(this),
|
||||
if_target_not_callable(this, Label::kDeferred);
|
||||
GotoIf(TaggedIsSmi(target), &if_target_not_callable);
|
||||
Branch(IsCallable(target), &if_target_callable, &if_target_not_callable);
|
||||
BIND(&if_target_not_callable);
|
||||
{
|
||||
CallRuntime(Runtime::kThrowApplyNonFunction, context, target);
|
||||
Unreachable();
|
||||
}
|
||||
BIND(&if_target_callable);
|
||||
}
|
||||
|
||||
GotoIf(TaggedIsSmi(arguments_list), &if_runtime);
|
||||
Node* arguments_list_map = LoadMap(arguments_list);
|
||||
Node* native_context = LoadNativeContext(context);
|
||||
|
@ -1697,13 +1697,9 @@ void Builtins::Generate_FunctionPrototypeApply(MacroAssembler* masm) {
|
||||
// -- esp[4] : thisArg
|
||||
// -----------------------------------
|
||||
|
||||
// 2. Make sure the receiver is actually callable.
|
||||
Label receiver_not_callable;
|
||||
__ JumpIfSmi(edi, &receiver_not_callable, Label::kNear);
|
||||
__ mov(ecx, FieldOperand(edi, HeapObject::kMapOffset));
|
||||
__ test_b(FieldOperand(ecx, Map::kBitFieldOffset),
|
||||
Immediate(1 << Map::kIsCallable));
|
||||
__ j(zero, &receiver_not_callable, Label::kNear);
|
||||
// 2. We don't need to check explicitly for callable receiver here,
|
||||
// since that's the first thing the Call/CallWithArrayLike builtins
|
||||
// will do.
|
||||
|
||||
// 3. Tail call with no arguments if argArray is null or undefined.
|
||||
Label no_arguments;
|
||||
@ -1722,13 +1718,6 @@ void Builtins::Generate_FunctionPrototypeApply(MacroAssembler* masm) {
|
||||
__ Set(eax, 0);
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
// 4c. The receiver is not callable, throw an appropriate TypeError.
|
||||
__ bind(&receiver_not_callable);
|
||||
{
|
||||
__ mov(Operand(esp, kPointerSize), edi);
|
||||
__ TailCallRuntime(Runtime::kThrowApplyNonFunction);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
@ -1817,24 +1806,13 @@ void Builtins::Generate_ReflectApply(MacroAssembler* masm) {
|
||||
// -- esp[4] : thisArgument
|
||||
// -----------------------------------
|
||||
|
||||
// 2. Make sure the target is actually callable.
|
||||
Label target_not_callable;
|
||||
__ JumpIfSmi(edi, &target_not_callable, Label::kNear);
|
||||
__ mov(ecx, FieldOperand(edi, HeapObject::kMapOffset));
|
||||
__ test_b(FieldOperand(ecx, Map::kBitFieldOffset),
|
||||
Immediate(1 << Map::kIsCallable));
|
||||
__ j(zero, &target_not_callable, Label::kNear);
|
||||
// 2. We don't need to check explicitly for callable target here,
|
||||
// since that's the first thing the Call/CallWithArrayLike builtins
|
||||
// will do.
|
||||
|
||||
// 3a. Apply the target to the given argumentsList.
|
||||
// 3. Apply the target to the given argumentsList.
|
||||
__ Jump(masm->isolate()->builtins()->CallWithArrayLike(),
|
||||
RelocInfo::CODE_TARGET);
|
||||
|
||||
// 3b. The target is not callable, throw an appropriate TypeError.
|
||||
__ bind(&target_not_callable);
|
||||
{
|
||||
__ mov(Operand(esp, kPointerSize), edi);
|
||||
__ TailCallRuntime(Runtime::kThrowApplyNonFunction);
|
||||
}
|
||||
}
|
||||
|
||||
void Builtins::Generate_ReflectConstruct(MacroAssembler* masm) {
|
||||
|
@ -1927,13 +1927,9 @@ void Builtins::Generate_FunctionPrototypeApply(MacroAssembler* masm) {
|
||||
// -- sp[0] : thisArg
|
||||
// -----------------------------------
|
||||
|
||||
// 2. Make sure the receiver is actually callable.
|
||||
Label receiver_not_callable;
|
||||
__ JumpIfSmi(a1, &receiver_not_callable);
|
||||
__ lw(t0, FieldMemOperand(a1, HeapObject::kMapOffset));
|
||||
__ lbu(t0, FieldMemOperand(t0, Map::kBitFieldOffset));
|
||||
__ And(t0, t0, Operand(1 << Map::kIsCallable));
|
||||
__ Branch(&receiver_not_callable, eq, t0, Operand(zero_reg));
|
||||
// 2. We don't need to check explicitly for callable receiver here,
|
||||
// since that's the first thing the Call/CallWithArrayLike builtins
|
||||
// will do.
|
||||
|
||||
// 3. Tail call with no arguments if argArray is null or undefined.
|
||||
Label no_arguments;
|
||||
@ -1951,13 +1947,6 @@ void Builtins::Generate_FunctionPrototypeApply(MacroAssembler* masm) {
|
||||
__ mov(a0, zero_reg);
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
// 4c. The receiver is not callable, throw an appropriate TypeError.
|
||||
__ bind(&receiver_not_callable);
|
||||
{
|
||||
__ sw(a1, MemOperand(sp));
|
||||
__ TailCallRuntime(Runtime::kThrowApplyNonFunction);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
@ -2044,24 +2033,13 @@ void Builtins::Generate_ReflectApply(MacroAssembler* masm) {
|
||||
// -- sp[0] : thisArgument
|
||||
// -----------------------------------
|
||||
|
||||
// 2. Make sure the target is actually callable.
|
||||
Label target_not_callable;
|
||||
__ JumpIfSmi(a1, &target_not_callable);
|
||||
__ lw(t0, FieldMemOperand(a1, HeapObject::kMapOffset));
|
||||
__ lbu(t0, FieldMemOperand(t0, Map::kBitFieldOffset));
|
||||
__ And(t0, t0, Operand(1 << Map::kIsCallable));
|
||||
__ Branch(&target_not_callable, eq, t0, Operand(zero_reg));
|
||||
// 2. We don't need to check explicitly for callable target here,
|
||||
// since that's the first thing the Call/CallWithArrayLike builtins
|
||||
// will do.
|
||||
|
||||
// 3a. Apply the target to the given argumentsList.
|
||||
// 3. Apply the target to the given argumentsList.
|
||||
__ Jump(masm->isolate()->builtins()->CallWithArrayLike(),
|
||||
RelocInfo::CODE_TARGET);
|
||||
|
||||
// 3b. The target is not callable, throw an appropriate TypeError.
|
||||
__ bind(&target_not_callable);
|
||||
{
|
||||
__ sw(a1, MemOperand(sp));
|
||||
__ TailCallRuntime(Runtime::kThrowApplyNonFunction);
|
||||
}
|
||||
}
|
||||
|
||||
void Builtins::Generate_ReflectConstruct(MacroAssembler* masm) {
|
||||
|
@ -1934,13 +1934,9 @@ void Builtins::Generate_FunctionPrototypeApply(MacroAssembler* masm) {
|
||||
// -- sp[0] : thisArg
|
||||
// -----------------------------------
|
||||
|
||||
// 2. Make sure the receiver is actually callable.
|
||||
Label receiver_not_callable;
|
||||
__ JumpIfSmi(receiver, &receiver_not_callable);
|
||||
__ Ld(a4, FieldMemOperand(receiver, HeapObject::kMapOffset));
|
||||
__ Lbu(a4, FieldMemOperand(a4, Map::kBitFieldOffset));
|
||||
__ And(a4, a4, Operand(1 << Map::kIsCallable));
|
||||
__ Branch(&receiver_not_callable, eq, a4, Operand(zero_reg));
|
||||
// 2. We don't need to check explicitly for callable receiver here,
|
||||
// since that's the first thing the Call/CallWithArrayLike builtins
|
||||
// will do.
|
||||
|
||||
// 3. Tail call with no arguments if argArray is null or undefined.
|
||||
Label no_arguments;
|
||||
@ -1959,13 +1955,6 @@ void Builtins::Generate_FunctionPrototypeApply(MacroAssembler* masm) {
|
||||
DCHECK(receiver.is(a1));
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
// 4c. The receiver is not callable, throw an appropriate TypeError.
|
||||
__ bind(&receiver_not_callable);
|
||||
{
|
||||
__ Sd(receiver, MemOperand(sp));
|
||||
__ TailCallRuntime(Runtime::kThrowApplyNonFunction);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
@ -2057,24 +2046,13 @@ void Builtins::Generate_ReflectApply(MacroAssembler* masm) {
|
||||
// -- sp[0] : thisArgument
|
||||
// -----------------------------------
|
||||
|
||||
// 2. Make sure the target is actually callable.
|
||||
Label target_not_callable;
|
||||
__ JumpIfSmi(target, &target_not_callable);
|
||||
__ Ld(a4, FieldMemOperand(target, HeapObject::kMapOffset));
|
||||
__ Lbu(a4, FieldMemOperand(a4, Map::kBitFieldOffset));
|
||||
__ And(a4, a4, Operand(1 << Map::kIsCallable));
|
||||
__ Branch(&target_not_callable, eq, a4, Operand(zero_reg));
|
||||
// 2. We don't need to check explicitly for callable target here,
|
||||
// since that's the first thing the Call/CallWithArrayLike builtins
|
||||
// will do.
|
||||
|
||||
// 3a. Apply the target to the given argumentsList.
|
||||
// 3. Apply the target to the given argumentsList.
|
||||
__ Jump(masm->isolate()->builtins()->CallWithArrayLike(),
|
||||
RelocInfo::CODE_TARGET);
|
||||
|
||||
// 3b. The target is not callable, throw an appropriate TypeError.
|
||||
__ bind(&target_not_callable);
|
||||
{
|
||||
__ Sd(target, MemOperand(sp));
|
||||
__ TailCallRuntime(Runtime::kThrowApplyNonFunction);
|
||||
}
|
||||
}
|
||||
|
||||
void Builtins::Generate_ReflectConstruct(MacroAssembler* masm) {
|
||||
|
@ -1669,13 +1669,9 @@ void Builtins::Generate_FunctionPrototypeApply(MacroAssembler* masm) {
|
||||
// -- rsp[8] : thisArg
|
||||
// -----------------------------------
|
||||
|
||||
// 2. Make sure the receiver is actually callable.
|
||||
Label receiver_not_callable;
|
||||
__ JumpIfSmi(rdi, &receiver_not_callable, Label::kNear);
|
||||
__ movp(rcx, FieldOperand(rdi, HeapObject::kMapOffset));
|
||||
__ testb(FieldOperand(rcx, Map::kBitFieldOffset),
|
||||
Immediate(1 << Map::kIsCallable));
|
||||
__ j(zero, &receiver_not_callable, Label::kNear);
|
||||
// 2. We don't need to check explicitly for callable receiver here,
|
||||
// since that's the first thing the Call/CallWithArrayLike builtins
|
||||
// will do.
|
||||
|
||||
// 3. Tail call with no arguments if argArray is null or undefined.
|
||||
Label no_arguments;
|
||||
@ -1695,14 +1691,6 @@ void Builtins::Generate_FunctionPrototypeApply(MacroAssembler* masm) {
|
||||
__ Set(rax, 0);
|
||||
__ Jump(masm->isolate()->builtins()->Call(), RelocInfo::CODE_TARGET);
|
||||
}
|
||||
|
||||
// 4c. The receiver is not callable, throw an appropriate TypeError.
|
||||
__ bind(&receiver_not_callable);
|
||||
{
|
||||
StackArgumentsAccessor args(rsp, 0);
|
||||
__ movp(args.GetReceiverOperand(), rdi);
|
||||
__ TailCallRuntime(Runtime::kThrowApplyNonFunction);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
@ -1798,25 +1786,13 @@ void Builtins::Generate_ReflectApply(MacroAssembler* masm) {
|
||||
// -- rsp[8] : thisArgument
|
||||
// -----------------------------------
|
||||
|
||||
// 2. Make sure the target is actually callable.
|
||||
Label target_not_callable;
|
||||
__ JumpIfSmi(rdi, &target_not_callable, Label::kNear);
|
||||
__ movp(rcx, FieldOperand(rdi, HeapObject::kMapOffset));
|
||||
__ testb(FieldOperand(rcx, Map::kBitFieldOffset),
|
||||
Immediate(1 << Map::kIsCallable));
|
||||
__ j(zero, &target_not_callable, Label::kNear);
|
||||
// 2. We don't need to check explicitly for callable target here,
|
||||
// since that's the first thing the Call/CallWithArrayLike builtins
|
||||
// will do.
|
||||
|
||||
// 3a. Apply the target to the given argumentsList.
|
||||
// 3. Apply the target to the given argumentsList.
|
||||
__ Jump(masm->isolate()->builtins()->CallWithArrayLike(),
|
||||
RelocInfo::CODE_TARGET);
|
||||
|
||||
// 3b. The target is not callable, throw an appropriate TypeError.
|
||||
__ bind(&target_not_callable);
|
||||
{
|
||||
StackArgumentsAccessor args(rsp, 0);
|
||||
__ movp(args.GetReceiverOperand(), rdi);
|
||||
__ TailCallRuntime(Runtime::kThrowApplyNonFunction);
|
||||
}
|
||||
}
|
||||
|
||||
void Builtins::Generate_ReflectConstruct(MacroAssembler* masm) {
|
||||
|
@ -286,6 +286,12 @@ Callable CodeFactory::Call(Isolate* isolate, ConvertReceiverMode mode,
|
||||
CallTrampolineDescriptor(isolate));
|
||||
}
|
||||
|
||||
// static
|
||||
Callable CodeFactory::CallWithArrayLike(Isolate* isolate) {
|
||||
return Callable(isolate->builtins()->CallWithArrayLike(),
|
||||
CallWithArrayLikeDescriptor(isolate));
|
||||
}
|
||||
|
||||
// static
|
||||
Callable CodeFactory::CallWithSpread(Isolate* isolate) {
|
||||
return Callable(isolate->builtins()->CallWithSpread(),
|
||||
|
@ -87,6 +87,7 @@ class V8_EXPORT_PRIVATE CodeFactory final {
|
||||
static Callable Call(Isolate* isolate,
|
||||
ConvertReceiverMode mode = ConvertReceiverMode::kAny,
|
||||
TailCallMode tail_call_mode = TailCallMode::kDisallow);
|
||||
static Callable CallWithArrayLike(Isolate* isolate);
|
||||
static Callable CallWithSpread(Isolate* isolate);
|
||||
static Callable CallFunction(
|
||||
Isolate* isolate, ConvertReceiverMode mode = ConvertReceiverMode::kAny,
|
||||
|
@ -28,6 +28,8 @@ Reduction JSCallReducer::Reduce(Node* node) {
|
||||
return ReduceJSConstructWithSpread(node);
|
||||
case IrOpcode::kJSCall:
|
||||
return ReduceJSCall(node);
|
||||
case IrOpcode::kJSCallWithArrayLike:
|
||||
return ReduceJSCallWithArrayLike(node);
|
||||
case IrOpcode::kJSCallWithSpread:
|
||||
return ReduceJSCallWithSpread(node);
|
||||
default:
|
||||
@ -95,18 +97,52 @@ Reduction JSCallReducer::ReduceNumberConstructor(Node* node) {
|
||||
return Changed(node);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool CanBeNullOrUndefined(Node* node) {
|
||||
switch (node->opcode()) {
|
||||
case IrOpcode::kJSCreate:
|
||||
case IrOpcode::kJSCreateArguments:
|
||||
case IrOpcode::kJSCreateArray:
|
||||
case IrOpcode::kJSCreateClosure:
|
||||
case IrOpcode::kJSCreateIterResultObject:
|
||||
case IrOpcode::kJSCreateKeyValueArray:
|
||||
case IrOpcode::kJSCreateLiteralArray:
|
||||
case IrOpcode::kJSCreateLiteralObject:
|
||||
case IrOpcode::kJSCreateLiteralRegExp:
|
||||
case IrOpcode::kJSConstruct:
|
||||
case IrOpcode::kJSConstructForwardVarargs:
|
||||
case IrOpcode::kJSConstructWithSpread:
|
||||
case IrOpcode::kJSConvertReceiver:
|
||||
case IrOpcode::kJSToBoolean:
|
||||
case IrOpcode::kJSToInteger:
|
||||
case IrOpcode::kJSToLength:
|
||||
case IrOpcode::kJSToName:
|
||||
case IrOpcode::kJSToNumber:
|
||||
case IrOpcode::kJSToObject:
|
||||
case IrOpcode::kJSToString:
|
||||
case IrOpcode::kJSToPrimitiveToString:
|
||||
return false;
|
||||
case IrOpcode::kHeapConstant: {
|
||||
Handle<HeapObject> value = HeapObjectMatcher(node).Value();
|
||||
Isolate* const isolate = value->GetIsolate();
|
||||
return value->IsNull(isolate) || value->IsUndefined(isolate);
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ES6 section 19.2.3.1 Function.prototype.apply ( thisArg, argArray )
|
||||
Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
|
||||
Node* target = NodeProperties::GetValueInput(node, 0);
|
||||
CallParameters const& p = CallParametersOf(node->op());
|
||||
// Tail calls to Function.prototype.apply are not properly supported
|
||||
// down the pipeline, so we disable this optimization completely for
|
||||
// tail calls (for now).
|
||||
if (p.tail_call_mode() == TailCallMode::kAllow) return NoChange();
|
||||
Handle<JSFunction> apply =
|
||||
Handle<JSFunction>::cast(HeapObjectMatcher(target).Value());
|
||||
size_t arity = p.arity();
|
||||
DCHECK_LE(2u, arity);
|
||||
ConvertReceiverMode convert_mode = ConvertReceiverMode::kAny;
|
||||
@ -119,97 +155,102 @@ Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
|
||||
// The argArray was not provided, just remove the {target}.
|
||||
node->RemoveInput(0);
|
||||
--arity;
|
||||
} else if (arity == 4) {
|
||||
// Check if argArray is an arguments object, and {node} is the only value
|
||||
// user of argArray (except for value uses in frame states).
|
||||
Node* arg_array = NodeProperties::GetValueInput(node, 3);
|
||||
if (arg_array->opcode() != IrOpcode::kJSCreateArguments) return NoChange();
|
||||
for (Edge edge : arg_array->use_edges()) {
|
||||
Node* const user = edge.from();
|
||||
if (user == node) continue;
|
||||
// Ignore uses as frame state's locals or parameters.
|
||||
if (user->opcode() == IrOpcode::kStateValues) continue;
|
||||
// Ignore uses as frame state's accumulator.
|
||||
if (user->opcode() == IrOpcode::kFrameState &&
|
||||
user->InputAt(2) == arg_array) {
|
||||
continue;
|
||||
}
|
||||
if (!NodeProperties::IsValueEdge(edge)) continue;
|
||||
return NoChange();
|
||||
}
|
||||
// Check if the arguments can be handled in the fast case (i.e. we don't
|
||||
// have aliased sloppy arguments), and compute the {start_index} for
|
||||
// rest parameters.
|
||||
CreateArgumentsType const type = CreateArgumentsTypeOf(arg_array->op());
|
||||
Node* frame_state = NodeProperties::GetFrameStateInput(arg_array);
|
||||
FrameStateInfo state_info = OpParameter<FrameStateInfo>(frame_state);
|
||||
int start_index = 0;
|
||||
// Determine the formal parameter count;
|
||||
Handle<SharedFunctionInfo> shared;
|
||||
if (!state_info.shared_info().ToHandle(&shared)) return NoChange();
|
||||
int formal_parameter_count = shared->internal_formal_parameter_count();
|
||||
if (type == CreateArgumentsType::kMappedArguments) {
|
||||
// Mapped arguments (sloppy mode) that are aliased can only be handled
|
||||
// here if there's no side-effect between the {node} and the {arg_array}.
|
||||
// TODO(turbofan): Further relax this constraint.
|
||||
if (formal_parameter_count != 0) {
|
||||
Node* effect = NodeProperties::GetEffectInput(node);
|
||||
while (effect != arg_array) {
|
||||
if (effect->op()->EffectInputCount() != 1 ||
|
||||
!(effect->op()->properties() & Operator::kNoWrite)) {
|
||||
return NoChange();
|
||||
}
|
||||
effect = NodeProperties::GetEffectInput(effect);
|
||||
}
|
||||
}
|
||||
} else if (type == CreateArgumentsType::kRestParameter) {
|
||||
start_index = formal_parameter_count;
|
||||
}
|
||||
// Check if are applying to inlined arguments or to the arguments of
|
||||
// the outermost function.
|
||||
Node* outer_state = frame_state->InputAt(kFrameStateOuterStateInput);
|
||||
if (outer_state->opcode() != IrOpcode::kFrameState) {
|
||||
// Reduce {node} to a JSCallForwardVarargs operation, which just
|
||||
// re-pushes the incoming arguments and calls the {target}.
|
||||
node->RemoveInput(0); // Function.prototype.apply
|
||||
node->RemoveInput(2); // arguments
|
||||
NodeProperties::ChangeOp(node, javascript()->CallForwardVarargs(
|
||||
2, start_index, p.tail_call_mode()));
|
||||
return Changed(node);
|
||||
}
|
||||
// Get to the actual frame state from which to extract the arguments;
|
||||
// we can only optimize this in case the {node} was already inlined into
|
||||
// some other function (and same for the {arg_array}).
|
||||
FrameStateInfo outer_info = OpParameter<FrameStateInfo>(outer_state);
|
||||
if (outer_info.type() == FrameStateType::kArgumentsAdaptor) {
|
||||
// Need to take the parameters from the arguments adaptor.
|
||||
frame_state = outer_state;
|
||||
}
|
||||
// Remove the argArray input from the {node}.
|
||||
node->RemoveInput(static_cast<int>(--arity));
|
||||
// Add the actual parameters to the {node}, skipping the receiver,
|
||||
// starting from {start_index}.
|
||||
Node* const parameters = frame_state->InputAt(kFrameStateParametersInput);
|
||||
for (int i = start_index + 1; i < parameters->InputCount(); ++i) {
|
||||
node->InsertInput(graph()->zone(), static_cast<int>(arity),
|
||||
parameters->InputAt(i));
|
||||
++arity;
|
||||
}
|
||||
// Drop the {target} from the {node}.
|
||||
node->RemoveInput(0);
|
||||
--arity;
|
||||
} else {
|
||||
return NoChange();
|
||||
Node* target = NodeProperties::GetValueInput(node, 1);
|
||||
Node* this_argument = NodeProperties::GetValueInput(node, 2);
|
||||
Node* arguments_list = NodeProperties::GetValueInput(node, 3);
|
||||
Node* context = NodeProperties::GetContextInput(node);
|
||||
Node* frame_state = NodeProperties::GetFrameStateInput(node);
|
||||
Node* effect = NodeProperties::GetEffectInput(node);
|
||||
Node* control = NodeProperties::GetControlInput(node);
|
||||
|
||||
// If {arguments_list} cannot be null or undefined, we don't need
|
||||
// to expand this {node} to control-flow.
|
||||
if (!CanBeNullOrUndefined(arguments_list)) {
|
||||
// Massage the value inputs appropriately.
|
||||
node->ReplaceInput(0, target);
|
||||
node->ReplaceInput(1, this_argument);
|
||||
node->ReplaceInput(2, arguments_list);
|
||||
while (arity-- > 3) node->RemoveInput(3);
|
||||
|
||||
// Morph the {node} to a {JSCallWithArrayLike}.
|
||||
NodeProperties::ChangeOp(node,
|
||||
javascript()->CallWithArrayLike(p.frequency()));
|
||||
Reduction const reduction = ReduceJSCallWithArrayLike(node);
|
||||
return reduction.Changed() ? reduction : Changed(node);
|
||||
} else {
|
||||
// Check whether {arguments_list} is null.
|
||||
Node* check_null =
|
||||
graph()->NewNode(simplified()->ReferenceEqual(), arguments_list,
|
||||
jsgraph()->NullConstant());
|
||||
control = graph()->NewNode(common()->Branch(BranchHint::kFalse),
|
||||
check_null, control);
|
||||
Node* if_null = graph()->NewNode(common()->IfTrue(), control);
|
||||
control = graph()->NewNode(common()->IfFalse(), control);
|
||||
|
||||
// Check whether {arguments_list} is undefined.
|
||||
Node* check_undefined =
|
||||
graph()->NewNode(simplified()->ReferenceEqual(), arguments_list,
|
||||
jsgraph()->UndefinedConstant());
|
||||
control = graph()->NewNode(common()->Branch(BranchHint::kFalse),
|
||||
check_undefined, control);
|
||||
Node* if_undefined = graph()->NewNode(common()->IfTrue(), control);
|
||||
control = graph()->NewNode(common()->IfFalse(), control);
|
||||
|
||||
// Lower to {JSCallWithArrayLike} if {arguments_list} is neither null
|
||||
// nor undefined.
|
||||
Node* effect0 = effect;
|
||||
Node* control0 = control;
|
||||
Node* value0 = effect0 = control0 = graph()->NewNode(
|
||||
javascript()->CallWithArrayLike(p.frequency()), target, this_argument,
|
||||
arguments_list, context, frame_state, effect0, control0);
|
||||
|
||||
// Lower to {JSCall} if {arguments_list} is either null or undefined.
|
||||
Node* effect1 = effect;
|
||||
Node* control1 =
|
||||
graph()->NewNode(common()->Merge(2), if_null, if_undefined);
|
||||
Node* value1 = effect1 = control1 =
|
||||
graph()->NewNode(javascript()->Call(2), target, this_argument,
|
||||
context, frame_state, effect1, control1);
|
||||
|
||||
// Rewire potential exception edges.
|
||||
Node* if_exception = nullptr;
|
||||
if (NodeProperties::IsExceptionalCall(node, &if_exception)) {
|
||||
// Create appropriate {IfException} and {IfSuccess} nodes.
|
||||
Node* if_exception0 =
|
||||
graph()->NewNode(common()->IfException(), control0, effect0);
|
||||
control0 = graph()->NewNode(common()->IfSuccess(), control0);
|
||||
Node* if_exception1 =
|
||||
graph()->NewNode(common()->IfException(), control1, effect1);
|
||||
control1 = graph()->NewNode(common()->IfSuccess(), control1);
|
||||
|
||||
// Join the exception edges.
|
||||
Node* merge =
|
||||
graph()->NewNode(common()->Merge(2), if_exception0, if_exception1);
|
||||
Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception0,
|
||||
if_exception1, merge);
|
||||
Node* phi =
|
||||
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
|
||||
if_exception0, if_exception1, merge);
|
||||
ReplaceWithValue(if_exception, phi, ephi, merge);
|
||||
}
|
||||
|
||||
// Join control paths.
|
||||
control = graph()->NewNode(common()->Merge(2), control0, control1);
|
||||
effect =
|
||||
graph()->NewNode(common()->EffectPhi(2), effect0, effect1, control);
|
||||
Node* value =
|
||||
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
|
||||
value0, value1, control);
|
||||
ReplaceWithValue(node, value, effect, control);
|
||||
return Replace(value);
|
||||
}
|
||||
}
|
||||
// Change {node} to the new {JSCall} operator.
|
||||
NodeProperties::ChangeOp(
|
||||
node,
|
||||
javascript()->Call(arity, p.frequency(), VectorSlotPair(), convert_mode,
|
||||
p.tail_call_mode()));
|
||||
// Change context of {node} to the Function.prototype.apply context,
|
||||
// to ensure any exception is thrown in the correct context.
|
||||
NodeProperties::ReplaceContextInput(
|
||||
node, jsgraph()->HeapConstant(handle(apply->context(), isolate())));
|
||||
// Try to further reduce the JSCall {node}.
|
||||
Reduction const reduction = ReduceJSCall(node);
|
||||
return reduction.Changed() ? reduction : Changed(node);
|
||||
@ -374,6 +415,27 @@ Reduction JSCallReducer::ReduceObjectPrototypeIsPrototypeOf(Node* node) {
|
||||
return Changed(node);
|
||||
}
|
||||
|
||||
// ES6 section 26.1.1 Reflect.apply ( target, thisArgument, argumentsList )
|
||||
Reduction JSCallReducer::ReduceReflectApply(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
|
||||
CallParameters const& p = CallParametersOf(node->op());
|
||||
int arity = static_cast<int>(p.arity() - 2);
|
||||
DCHECK_LE(0, arity);
|
||||
// Massage value inputs appropriately.
|
||||
node->RemoveInput(0);
|
||||
node->RemoveInput(0);
|
||||
while (arity < 3) {
|
||||
node->InsertInput(graph()->zone(), arity++, jsgraph()->UndefinedConstant());
|
||||
}
|
||||
while (arity-- > 3) {
|
||||
node->RemoveInput(arity);
|
||||
}
|
||||
NodeProperties::ChangeOp(node,
|
||||
javascript()->CallWithArrayLike(p.frequency()));
|
||||
Reduction const reduction = ReduceJSCallWithArrayLike(node);
|
||||
return reduction.Changed() ? reduction : Changed(node);
|
||||
}
|
||||
|
||||
// ES6 section 26.1.7 Reflect.getPrototypeOf ( target )
|
||||
Reduction JSCallReducer::ReduceReflectGetPrototypeOf(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
|
||||
@ -598,27 +660,33 @@ Reduction JSCallReducer::ReduceCallApiFunction(
|
||||
return Changed(node);
|
||||
}
|
||||
|
||||
Reduction JSCallReducer::ReduceSpreadCall(Node* node, int arity) {
|
||||
DCHECK(node->opcode() == IrOpcode::kJSCallWithSpread ||
|
||||
Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
|
||||
Node* node, int arity, CallFrequency const& frequency) {
|
||||
DCHECK(node->opcode() == IrOpcode::kJSCallWithArrayLike ||
|
||||
node->opcode() == IrOpcode::kJSCallWithSpread ||
|
||||
node->opcode() == IrOpcode::kJSConstructWithSpread);
|
||||
|
||||
// Do check to make sure we can actually avoid iteration.
|
||||
if (!isolate()->initial_array_iterator_prototype_map()->is_stable()) {
|
||||
// In case of a call/construct with spread, we need to
|
||||
// ensure that it's safe to avoid the actual iteration.
|
||||
if ((node->opcode() == IrOpcode::kJSCallWithSpread ||
|
||||
node->opcode() == IrOpcode::kJSConstructWithSpread) &&
|
||||
!isolate()->initial_array_iterator_prototype_map()->is_stable()) {
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
Node* spread = NodeProperties::GetValueInput(node, arity);
|
||||
|
||||
// Check if spread is an arguments object, and {node} is the only value user
|
||||
// of spread (except for value uses in frame states).
|
||||
if (spread->opcode() != IrOpcode::kJSCreateArguments) return NoChange();
|
||||
for (Edge edge : spread->use_edges()) {
|
||||
// Check if {arguments_list} is an arguments object, and {node} is the only
|
||||
// value user of {arguments_list} (except for value uses in frame states).
|
||||
Node* arguments_list = NodeProperties::GetValueInput(node, arity);
|
||||
if (arguments_list->opcode() != IrOpcode::kJSCreateArguments)
|
||||
return NoChange();
|
||||
for (Edge edge : arguments_list->use_edges()) {
|
||||
Node* const user = edge.from();
|
||||
if (user == node) continue;
|
||||
// Ignore uses as frame state's locals or parameters.
|
||||
if (user->opcode() == IrOpcode::kStateValues) continue;
|
||||
// Ignore uses as frame state's accumulator.
|
||||
if (user->opcode() == IrOpcode::kFrameState && user->InputAt(2) == spread) {
|
||||
if (user->opcode() == IrOpcode::kFrameState &&
|
||||
user->InputAt(2) == arguments_list) {
|
||||
continue;
|
||||
}
|
||||
if (!NodeProperties::IsValueEdge(edge)) continue;
|
||||
@ -627,9 +695,9 @@ Reduction JSCallReducer::ReduceSpreadCall(Node* node, int arity) {
|
||||
|
||||
// Get to the actual frame state from which to extract the arguments;
|
||||
// we can only optimize this in case the {node} was already inlined into
|
||||
// some other function (and same for the {spread}).
|
||||
CreateArgumentsType const type = CreateArgumentsTypeOf(spread->op());
|
||||
Node* frame_state = NodeProperties::GetFrameStateInput(spread);
|
||||
// some other function (and same for the {arguments_list}).
|
||||
CreateArgumentsType const type = CreateArgumentsTypeOf(arguments_list->op());
|
||||
Node* frame_state = NodeProperties::GetFrameStateInput(arguments_list);
|
||||
FrameStateInfo state_info = OpParameter<FrameStateInfo>(frame_state);
|
||||
int start_index = 0;
|
||||
// Determine the formal parameter count;
|
||||
@ -642,7 +710,7 @@ Reduction JSCallReducer::ReduceSpreadCall(Node* node, int arity) {
|
||||
// TODO(turbofan): Further relax this constraint.
|
||||
if (formal_parameter_count != 0) {
|
||||
Node* effect = NodeProperties::GetEffectInput(node);
|
||||
while (effect != spread) {
|
||||
while (effect != arguments_list) {
|
||||
if (effect->op()->EffectInputCount() != 1 ||
|
||||
!(effect->op()->properties() & Operator::kNoWrite)) {
|
||||
return NoChange();
|
||||
@ -653,24 +721,34 @@ Reduction JSCallReducer::ReduceSpreadCall(Node* node, int arity) {
|
||||
} else if (type == CreateArgumentsType::kRestParameter) {
|
||||
start_index = formal_parameter_count;
|
||||
|
||||
// Only check the array iterator protector when we have a rest object.
|
||||
if (!isolate()->IsArrayIteratorLookupChainIntact()) return NoChange();
|
||||
// For spread calls/constructs with rest parameters we need to ensure that
|
||||
// the array iterator protector is intact, which guards that the rest
|
||||
// parameter iteration is not observable.
|
||||
if (node->opcode() == IrOpcode::kJSCallWithSpread ||
|
||||
node->opcode() == IrOpcode::kJSConstructWithSpread) {
|
||||
if (!isolate()->IsArrayIteratorLookupChainIntact()) return NoChange();
|
||||
dependencies()->AssumePropertyCell(factory()->array_iterator_protector());
|
||||
}
|
||||
}
|
||||
|
||||
// Install appropriate code dependencies.
|
||||
dependencies()->AssumeMapStable(
|
||||
isolate()->initial_array_iterator_prototype_map());
|
||||
if (type == CreateArgumentsType::kRestParameter) {
|
||||
dependencies()->AssumePropertyCell(factory()->array_iterator_protector());
|
||||
// For call/construct with spread, we need to also install a code
|
||||
// dependency on the initial %ArrayIteratorPrototype% map here to
|
||||
// ensure that no one messes with the next method.
|
||||
if (node->opcode() == IrOpcode::kJSCallWithSpread ||
|
||||
node->opcode() == IrOpcode::kJSConstructWithSpread) {
|
||||
dependencies()->AssumeMapStable(
|
||||
isolate()->initial_array_iterator_prototype_map());
|
||||
}
|
||||
// Remove the spread input from the {node}.
|
||||
|
||||
// Remove the {arguments_list} input from the {node}.
|
||||
node->RemoveInput(arity--);
|
||||
// Check if are spreading to inlined arguments or to the arguments of
|
||||
// the outermost function.
|
||||
Node* outer_state = frame_state->InputAt(kFrameStateOuterStateInput);
|
||||
if (outer_state->opcode() != IrOpcode::kFrameState) {
|
||||
Operator const* op =
|
||||
(node->opcode() == IrOpcode::kJSCallWithSpread)
|
||||
(node->opcode() == IrOpcode::kJSCallWithArrayLike ||
|
||||
node->opcode() == IrOpcode::kJSCallWithSpread)
|
||||
? javascript()->CallForwardVarargs(arity + 1, start_index,
|
||||
TailCallMode::kDisallow)
|
||||
: javascript()->ConstructForwardVarargs(arity + 2, start_index);
|
||||
@ -692,16 +770,16 @@ Reduction JSCallReducer::ReduceSpreadCall(Node* node, int arity) {
|
||||
parameters->InputAt(i));
|
||||
}
|
||||
|
||||
// TODO(turbofan): Collect call counts on spread call/construct and thread it
|
||||
// through here.
|
||||
if (node->opcode() == IrOpcode::kJSCallWithSpread) {
|
||||
NodeProperties::ChangeOp(node, javascript()->Call(arity + 1));
|
||||
Reduction const r = ReduceJSCall(node);
|
||||
return r.Changed() ? r : Changed(node);
|
||||
if (node->opcode() == IrOpcode::kJSCallWithArrayLike ||
|
||||
node->opcode() == IrOpcode::kJSCallWithSpread) {
|
||||
NodeProperties::ChangeOp(node, javascript()->Call(arity + 1, frequency));
|
||||
Reduction const reduction = ReduceJSCall(node);
|
||||
return reduction.Changed() ? reduction : Changed(node);
|
||||
} else {
|
||||
NodeProperties::ChangeOp(node, javascript()->Construct(arity + 2));
|
||||
Reduction const r = ReduceJSConstruct(node);
|
||||
return r.Changed() ? r : Changed(node);
|
||||
NodeProperties::ChangeOp(node,
|
||||
javascript()->Construct(arity + 2, frequency));
|
||||
Reduction const reduction = ReduceJSConstruct(node);
|
||||
return reduction.Changed() ? reduction : Changed(node);
|
||||
}
|
||||
}
|
||||
|
||||
@ -775,6 +853,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
|
||||
return ReduceObjectPrototypeGetProto(node);
|
||||
case Builtins::kObjectPrototypeIsPrototypeOf:
|
||||
return ReduceObjectPrototypeIsPrototypeOf(node);
|
||||
case Builtins::kReflectApply:
|
||||
return ReduceReflectApply(node);
|
||||
case Builtins::kReflectGetPrototypeOf:
|
||||
return ReduceReflectGetPrototypeOf(node);
|
||||
case Builtins::kArrayForEach:
|
||||
@ -905,13 +985,22 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
Reduction JSCallReducer::ReduceJSCallWithArrayLike(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSCallWithArrayLike, node->opcode());
|
||||
CallFrequency frequency = CallFrequencyOf(node->op());
|
||||
return ReduceCallOrConstructWithArrayLikeOrSpread(node, 2, frequency);
|
||||
}
|
||||
|
||||
Reduction JSCallReducer::ReduceJSCallWithSpread(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSCallWithSpread, node->opcode());
|
||||
SpreadWithArityParameter const& p = SpreadWithArityParameterOf(node->op());
|
||||
DCHECK_LE(3u, p.arity());
|
||||
int arity = static_cast<int>(p.arity() - 1);
|
||||
|
||||
return ReduceSpreadCall(node, arity);
|
||||
// TODO(turbofan): Collect call counts on spread call/construct and thread it
|
||||
// through here.
|
||||
CallFrequency frequency;
|
||||
return ReduceCallOrConstructWithArrayLikeOrSpread(node, arity, frequency);
|
||||
}
|
||||
|
||||
Reduction JSCallReducer::ReduceJSConstruct(Node* node) {
|
||||
@ -1035,7 +1124,10 @@ Reduction JSCallReducer::ReduceJSConstructWithSpread(Node* node) {
|
||||
DCHECK_LE(3u, p.arity());
|
||||
int arity = static_cast<int>(p.arity() - 2);
|
||||
|
||||
return ReduceSpreadCall(node, arity);
|
||||
// TODO(turbofan): Collect call counts on spread call/construct and thread it
|
||||
// through here.
|
||||
CallFrequency frequency;
|
||||
return ReduceCallOrConstructWithArrayLikeOrSpread(node, arity, frequency);
|
||||
}
|
||||
|
||||
Reduction JSCallReducer::ReduceReturnReceiver(Node* node) {
|
||||
|
@ -18,6 +18,7 @@ class Factory;
|
||||
namespace compiler {
|
||||
|
||||
// Forward declarations.
|
||||
class CallFrequency;
|
||||
class CommonOperatorBuilder;
|
||||
class JSGraph;
|
||||
class JSOperatorBuilder;
|
||||
@ -50,12 +51,15 @@ class JSCallReducer final : public AdvancedReducer {
|
||||
Reduction ReduceObjectGetPrototypeOf(Node* node);
|
||||
Reduction ReduceObjectPrototypeGetProto(Node* node);
|
||||
Reduction ReduceObjectPrototypeIsPrototypeOf(Node* node);
|
||||
Reduction ReduceReflectApply(Node* node);
|
||||
Reduction ReduceReflectGetPrototypeOf(Node* node);
|
||||
Reduction ReduceArrayForEach(Handle<JSFunction> function, Node* node);
|
||||
Reduction ReduceSpreadCall(Node* node, int arity);
|
||||
Reduction ReduceCallOrConstructWithArrayLikeOrSpread(
|
||||
Node* node, int arity, CallFrequency const& frequency);
|
||||
Reduction ReduceJSConstruct(Node* node);
|
||||
Reduction ReduceJSConstructWithSpread(Node* node);
|
||||
Reduction ReduceJSCall(Node* node);
|
||||
Reduction ReduceJSCallWithArrayLike(Node* node);
|
||||
Reduction ReduceJSCallWithSpread(Node* node);
|
||||
Reduction ReduceReturnReceiver(Node* node);
|
||||
|
||||
|
@ -652,6 +652,20 @@ void JSGenericLowering::LowerJSCall(Node* node) {
|
||||
NodeProperties::ChangeOp(node, common()->Call(desc));
|
||||
}
|
||||
|
||||
void JSGenericLowering::LowerJSCallWithArrayLike(Node* node) {
|
||||
Callable callable = CodeFactory::CallWithArrayLike(isolate());
|
||||
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
|
||||
CallDescriptor* desc = Linkage::GetStubCallDescriptor(
|
||||
isolate(), zone(), callable.descriptor(), 1, flags);
|
||||
Node* stub_code = jsgraph()->HeapConstant(callable.code());
|
||||
Node* receiver = node->InputAt(1);
|
||||
Node* arguments_list = node->InputAt(2);
|
||||
node->InsertInput(zone(), 0, stub_code);
|
||||
node->ReplaceInput(3, receiver);
|
||||
node->ReplaceInput(2, arguments_list);
|
||||
NodeProperties::ChangeOp(node, common()->Call(desc));
|
||||
}
|
||||
|
||||
void JSGenericLowering::LowerJSCallWithSpread(Node* node) {
|
||||
SpreadWithArityParameter const& p = SpreadWithArityParameterOf(node->op());
|
||||
int const arg_count = static_cast<int>(p.arity() - 2);
|
||||
|
@ -22,6 +22,11 @@ std::ostream& operator<<(std::ostream& os, CallFrequency f) {
|
||||
return os << f.value();
|
||||
}
|
||||
|
||||
CallFrequency CallFrequencyOf(Operator const* op) {
|
||||
DCHECK_EQ(IrOpcode::kJSCallWithArrayLike, op->opcode());
|
||||
return OpParameter<CallFrequency>(op);
|
||||
}
|
||||
|
||||
VectorSlotPair::VectorSlotPair() {}
|
||||
|
||||
|
||||
@ -821,6 +826,14 @@ const Operator* JSOperatorBuilder::Call(size_t arity, CallFrequency frequency,
|
||||
parameters); // parameter
|
||||
}
|
||||
|
||||
const Operator* JSOperatorBuilder::CallWithArrayLike(CallFrequency frequency) {
|
||||
return new (zone()) Operator1<CallFrequency>( // --
|
||||
IrOpcode::kJSCallWithArrayLike, Operator::kNoProperties, // opcode
|
||||
"JSCallWithArrayLike", // name
|
||||
3, 1, 1, 1, 1, 2, // counts
|
||||
frequency); // parameter
|
||||
}
|
||||
|
||||
const Operator* JSOperatorBuilder::CallWithSpread(uint32_t arity) {
|
||||
SpreadWithArityParameter parameters(arity);
|
||||
return new (zone()) Operator1<SpreadWithArityParameter>( // --
|
||||
|
@ -57,6 +57,8 @@ class CallFrequency final {
|
||||
|
||||
std::ostream& operator<<(std::ostream&, CallFrequency);
|
||||
|
||||
CallFrequency CallFrequencyOf(Operator const* op) WARN_UNUSED_RESULT;
|
||||
|
||||
// Defines a pair of {FeedbackVector} and {FeedbackSlot}, which
|
||||
// is used to access the type feedback for a certain {Node}.
|
||||
class V8_EXPORT_PRIVATE VectorSlotPair {
|
||||
@ -731,6 +733,7 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
|
||||
VectorSlotPair const& feedback = VectorSlotPair(),
|
||||
ConvertReceiverMode convert_mode = ConvertReceiverMode::kAny,
|
||||
TailCallMode tail_call_mode = TailCallMode::kDisallow);
|
||||
const Operator* CallWithArrayLike(CallFrequency frequency);
|
||||
const Operator* CallWithSpread(uint32_t arity);
|
||||
const Operator* CallRuntime(Runtime::FunctionId id);
|
||||
const Operator* CallRuntime(Runtime::FunctionId id, size_t arity);
|
||||
|
@ -166,6 +166,7 @@
|
||||
V(JSConstructWithSpread) \
|
||||
V(JSCallForwardVarargs) \
|
||||
V(JSCall) \
|
||||
V(JSCallWithArrayLike) \
|
||||
V(JSCallWithSpread) \
|
||||
V(JSCallRuntime) \
|
||||
V(JSConvertReceiver) \
|
||||
|
@ -103,6 +103,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) {
|
||||
case IrOpcode::kJSConstructWithSpread:
|
||||
case IrOpcode::kJSCallForwardVarargs:
|
||||
case IrOpcode::kJSCall:
|
||||
case IrOpcode::kJSCallWithArrayLike:
|
||||
case IrOpcode::kJSCallWithSpread:
|
||||
|
||||
// Misc operations
|
||||
|
@ -1610,6 +1610,10 @@ Type* Typer::Visitor::TypeJSCall(Node* node) {
|
||||
return TypeUnaryOp(node, JSCallTyper);
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeJSCallWithArrayLike(Node* node) {
|
||||
return TypeUnaryOp(node, JSCallTyper);
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeJSCallWithSpread(Node* node) {
|
||||
return TypeUnaryOp(node, JSCallTyper);
|
||||
}
|
||||
|
@ -721,6 +721,7 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
break;
|
||||
case IrOpcode::kJSCallForwardVarargs:
|
||||
case IrOpcode::kJSCall:
|
||||
case IrOpcode::kJSCallWithArrayLike:
|
||||
case IrOpcode::kJSCallWithSpread:
|
||||
case IrOpcode::kJSCallRuntime:
|
||||
// Type can be anything.
|
||||
|
136
test/mjsunit/compiler/function-apply.js
Normal file
136
test/mjsunit/compiler/function-apply.js
Normal file
@ -0,0 +1,136 @@
|
||||
// Copyright 2017 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: --allow-natives-syntax
|
||||
|
||||
// Test Function.prototype.apply with null/undefined argumentsList
|
||||
(function() {
|
||||
"use strict";
|
||||
function bar() { return this; }
|
||||
function foo() { return bar.apply(this, null); }
|
||||
|
||||
assertEquals(42, foo.call(42));
|
||||
assertEquals(42, foo.call(42));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(42, foo.call(42));
|
||||
})();
|
||||
(function() {
|
||||
"use strict";
|
||||
function bar() { return this; }
|
||||
function foo() { return bar.apply(this, undefined); }
|
||||
|
||||
assertEquals(42, foo.call(42));
|
||||
assertEquals(42, foo.call(42));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(42, foo.call(42));
|
||||
})();
|
||||
|
||||
// Test Function.prototype.apply within try/catch.
|
||||
(function() {
|
||||
"use strict";
|
||||
function foo(bar) {
|
||||
try {
|
||||
return Function.prototype.apply.call(bar, bar, arguments);
|
||||
} catch (e) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(1, foo());
|
||||
assertEquals(1, foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(1, foo());
|
||||
})();
|
||||
(function() {
|
||||
"use strict";
|
||||
function foo(bar) {
|
||||
try {
|
||||
return Function.prototype.apply.call(bar, bar, bar);
|
||||
} catch (e) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(1, foo());
|
||||
assertEquals(1, foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(1, foo());
|
||||
})();
|
||||
|
||||
// Test Function.prototype.apply with wrong number of arguments.
|
||||
(function() {
|
||||
"use strict";
|
||||
function bar() { return this; }
|
||||
function foo() { return bar.apply(); }
|
||||
|
||||
assertEquals(undefined, foo());
|
||||
assertEquals(undefined, foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(undefined, foo());
|
||||
})();
|
||||
(function() {
|
||||
"use strict";
|
||||
function bar() { return this; }
|
||||
function foo() { return bar.apply(this); }
|
||||
|
||||
assertEquals(42, foo.call(42));
|
||||
assertEquals(42, foo.call(42));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(42, foo.call(42));
|
||||
})();
|
||||
(function() {
|
||||
"use strict";
|
||||
function bar() { return this; }
|
||||
function foo() { return bar.apply(this, arguments, this); }
|
||||
|
||||
assertEquals(42, foo.call(42));
|
||||
assertEquals(42, foo.call(42));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(42, foo.call(42));
|
||||
})();
|
||||
|
||||
// Test proper order of callable check and array-like iteration
|
||||
// in Function.prototype.apply.
|
||||
(function() {
|
||||
var dummy_length_counter = 0;
|
||||
var dummy = { get length() { ++dummy_length_counter; return 0; } };
|
||||
|
||||
function foo() {
|
||||
return Function.prototype.apply.call(undefined, this, dummy);
|
||||
}
|
||||
|
||||
assertThrows(foo, TypeError);
|
||||
assertThrows(foo, TypeError);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertThrows(foo, TypeError);
|
||||
assertEquals(0, dummy_length_counter);
|
||||
})();
|
||||
(function() {
|
||||
var dummy_length_counter = 0;
|
||||
var dummy = { get length() { ++dummy_length_counter; return 0; } };
|
||||
|
||||
function foo() {
|
||||
return Function.prototype.apply.call(null, this, dummy);
|
||||
}
|
||||
|
||||
assertThrows(foo, TypeError);
|
||||
assertThrows(foo, TypeError);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertThrows(foo, TypeError);
|
||||
assertEquals(0, dummy_length_counter);
|
||||
})();
|
||||
(function() {
|
||||
var dummy_length_counter = 0;
|
||||
var dummy = { get length() { ++dummy_length_counter; return 0; } };
|
||||
|
||||
function foo() {
|
||||
return Function.prototype.apply.call(null, this, dummy);
|
||||
}
|
||||
|
||||
assertThrows(foo, TypeError);
|
||||
assertThrows(foo, TypeError);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertThrows(foo, TypeError);
|
||||
assertEquals(0, dummy_length_counter);
|
||||
})();
|
114
test/mjsunit/compiler/reflect-apply.js
Normal file
114
test/mjsunit/compiler/reflect-apply.js
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright 2017 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: --allow-natives-syntax
|
||||
|
||||
// Test Reflect.apply with wrong number of arguments.
|
||||
(function() {
|
||||
"use strict";
|
||||
function bar() { return this; }
|
||||
function foo() { return Reflect.apply(bar); }
|
||||
|
||||
assertThrows(foo);
|
||||
assertThrows(foo);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertThrows(foo);
|
||||
})();
|
||||
(function() {
|
||||
"use strict";
|
||||
function bar() { return this; }
|
||||
function foo() { return Reflect.apply(bar, this); }
|
||||
|
||||
assertThrows(foo);
|
||||
assertThrows(foo);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertThrows(foo);
|
||||
})();
|
||||
(function() {
|
||||
"use strict";
|
||||
function bar() { return this; }
|
||||
function foo() { return Reflect.apply(bar, this, arguments, this); }
|
||||
|
||||
assertEquals(42, foo.call(42));
|
||||
assertEquals(42, foo.call(42));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(42, foo.call(42));
|
||||
})();
|
||||
|
||||
// Test Reflect.apply within try/catch.
|
||||
(function() {
|
||||
"use strict";
|
||||
function foo(bar) {
|
||||
try {
|
||||
return Reflect.apply(bar, bar, arguments);
|
||||
} catch (e) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(1, foo());
|
||||
assertEquals(1, foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(1, foo());
|
||||
})();
|
||||
(function() {
|
||||
"use strict";
|
||||
function foo(bar) {
|
||||
try {
|
||||
return Reflect.apply(bar, bar, bar);
|
||||
} catch (e) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(1, foo());
|
||||
assertEquals(1, foo());
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertEquals(1, foo());
|
||||
})();
|
||||
|
||||
// Test proper order of callable check and array-like iteration
|
||||
// in Reflect.apply.
|
||||
(function() {
|
||||
var dummy_length_counter = 0;
|
||||
var dummy = { get length() { ++dummy_length_counter; return 0; } };
|
||||
|
||||
function foo() {
|
||||
return Reflect.apply(undefined, this, dummy);
|
||||
}
|
||||
|
||||
assertThrows(foo, TypeError);
|
||||
assertThrows(foo, TypeError);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertThrows(foo, TypeError);
|
||||
assertEquals(0, dummy_length_counter);
|
||||
})();
|
||||
(function() {
|
||||
var dummy_length_counter = 0;
|
||||
var dummy = { get length() { ++dummy_length_counter; return 0; } };
|
||||
|
||||
function foo() {
|
||||
return Reflect.apply(null, this, dummy);
|
||||
}
|
||||
|
||||
assertThrows(foo, TypeError);
|
||||
assertThrows(foo, TypeError);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertThrows(foo, TypeError);
|
||||
assertEquals(0, dummy_length_counter);
|
||||
})();
|
||||
(function() {
|
||||
var dummy_length_counter = 0;
|
||||
var dummy = { get length() { ++dummy_length_counter; return 0; } };
|
||||
|
||||
function foo() {
|
||||
return Reflect.apply(null, this, dummy);
|
||||
}
|
||||
|
||||
assertThrows(foo, TypeError);
|
||||
assertThrows(foo, TypeError);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertThrows(foo, TypeError);
|
||||
assertEquals(0, dummy_length_counter);
|
||||
})();
|
@ -28,7 +28,7 @@ test(function() {
|
||||
|
||||
// kApplyNonFunction
|
||||
test(function() {
|
||||
Function.prototype.apply.call(1, []);
|
||||
Reflect.apply(1, []);
|
||||
}, "Function.prototype.apply was called on 1, which is a number " +
|
||||
"and not a function", TypeError);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user