Commit 99e13e58 authored by Simon Zünd's avatar Simon Zünd Committed by Commit Bot

[builtins] Add FastCallFunction builtin that elides some checks

This CL adds a new "Call" stub that can be used by builtins that will
call the same JS call-back function often (e.g. compare function in
Array.p.sort). The checks have to be done upfront once, but can then
be omitted.

R=jgruber@chromium.org

Bug: v8:7861
Change-Id: Id6e4ca27c3d488a7b1f708cbcb4cbe6cc382513e
Reviewed-on: https://chromium-review.googlesource.com/1208574
Commit-Queue: Simon Zünd <szuend@google.com>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#55769}
parent d8306028
......@@ -1724,6 +1724,33 @@ void Builtins::Generate_CallOrConstructForwardVarargs(MacroAssembler* masm,
__ Jump(code, RelocInfo::CODE_TARGET);
}
// static
// The CSA macro "BranchIfCanUseFastCallFunction" should be used to determine
// whether a JSFunction can be called using this stub.
void Builtins::Generate_FastCallFunction(MacroAssembler* masm) {
// ----------- S t a t e -------------
// -- r0 : the number of arguments (not including the receiver)
// -- r1 : the function to call (checked to be a JSFunction)
// -----------------------------------
__ AssertFunction(r1);
__ ldr(cp, FieldMemOperand(r1, JSFunction::kContextOffset));
// ----------- S t a t e -------------
// -- r0 : the number of arguments (not including the receiver)
// -- r1 : the function to call (checked to be a JSFunction)
// -- cp : the function context.
// -----------------------------------
// On function call, call into the debugger if necessary.
__ CheckDebugHook(r1, no_reg, ParameterCount(r0), ParameterCount(r0));
__ LoadRoot(r3, Heap::kUndefinedValueRootIndex);
Register code = kJavaScriptCallCodeStartRegister;
__ ldr(code, FieldMemOperand(r1, JSFunction::kCodeOffset));
__ add(code, code, Operand(Code::kHeaderSize - kHeapObjectTag));
__ Jump(code);
}
// static
void Builtins::Generate_CallFunction(MacroAssembler* masm,
ConvertReceiverMode mode) {
......
......@@ -2073,6 +2073,34 @@ void Builtins::Generate_CallOrConstructForwardVarargs(MacroAssembler* masm,
__ Jump(code, RelocInfo::CODE_TARGET);
}
// static
// The CSA macro "BranchIfCanUseFastCallFunction" should be used to determine
// whether a JSFunction can be called using this stub.
void Builtins::Generate_FastCallFunction(MacroAssembler* masm) {
// ----------- S t a t e -------------
// -- x0 : the number of arguments (not including the receiver)
// -- x1 : the function to call (checked to be a JSFunction)
// -----------------------------------
__ AssertFunction(x1);
__ Ldr(cp, FieldMemOperand(x1, JSFunction::kContextOffset));
// ----------- S t a t e -------------
// -- x0 : the number of arguments (not including the receiver)
// -- x1 : the function to call (checked to be a JSFunction)
// -- cp : the function context.
// -----------------------------------
// On function call, call into the debugger if necessary.
__ Mov(x2, x0);
__ CheckDebugHook(x1, no_reg, ParameterCount(x0), ParameterCount(x2));
__ LoadRoot(x3, Heap::kUndefinedValueRootIndex);
Register code = kJavaScriptCallCodeStartRegister;
__ Ldr(code, FieldMemOperand(x1, JSFunction::kCodeOffset));
__ Add(code, code, Operand(Code::kHeaderSize - kHeapObjectTag));
__ Jump(code);
}
// static
void Builtins::Generate_CallFunction(MacroAssembler* masm,
ConvertReceiverMode mode) {
......
......@@ -761,6 +761,8 @@ extern macro Call(
extern macro Call(
Context, Callable, Object, Object, Object, Object, Object, Object): Object;
extern macro FastCall(Context, Callable, Object, Object): Object;
extern macro ExtractFixedArray(FixedArrayBase, Smi, Smi, Smi): FixedArrayBase;
extern macro ExtractFixedArray(FixedArrayBase, Smi, Smi, Smi,
constexpr ExtractFixedArrayFlags): FixedArrayBase;
......@@ -828,6 +830,9 @@ macro NumberIsNaN(number: Number): bool {
}
extern macro BranchIfToBooleanIsTrue(Object): never labels Taken, NotTaken;
extern macro BranchIfCanUseFastCallFunction(HeapObject, int32):
never labels Taken,
NotTaken;
macro ToBoolean(obj: Object): bool {
if (BranchIfToBooleanIsTrue(obj)) {
......
......@@ -46,6 +46,11 @@ void Builtins::Generate_Call_ReceiverIsAny(MacroAssembler* masm) {
Generate_Call(masm, ConvertReceiverMode::kAny);
}
void Builtins::Generate_FastCallFunction_ReceiverIsNullOrUndefined(
MacroAssembler* masm) {
Generate_FastCallFunction(masm);
}
void Builtins::Generate_CallVarargs(MacroAssembler* masm) {
Generate_CallOrConstructVarargs(masm, masm->isolate()->builtins()->Call());
}
......@@ -230,9 +235,9 @@ void CallOrConstructBuiltinsAssembler::CallOrConstructWithArrayLike(
}
}
// Takes a FixedArray of doubles and creates a new FixedArray with those doubles
// boxed as HeapNumbers, then tail calls CallVarargs/ConstructVarargs depending
// on whether {new_target} was passed.
// Takes a FixedArray of doubles and creates a new FixedArray with those
// doubles boxed as HeapNumbers, then tail calls CallVarargs/ConstructVarargs
// depending on whether {new_target} was passed.
void CallOrConstructBuiltinsAssembler::CallOrConstructDoubleVarargs(
TNode<Object> target, SloppyTNode<Object> new_target,
TNode<FixedDoubleArray> elements, TNode<Int32T> length,
......
......@@ -51,6 +51,8 @@ namespace internal {
ASM(Call_ReceiverIsNotNullOrUndefined) \
ASM(Call_ReceiverIsAny) \
\
ASM(FastCallFunction_ReceiverIsNullOrUndefined) \
\
/* ES6 section 9.5.12[[Call]] ( thisArgument, argumentsList ) */ \
TFC(CallProxy, CallTrampoline, 1) \
ASM(CallVarargs) \
......
......@@ -165,6 +165,8 @@ class Builtins {
static void Generate_CallFunction(MacroAssembler* masm,
ConvertReceiverMode mode);
static void Generate_FastCallFunction(MacroAssembler* masm);
static void Generate_CallBoundFunctionImpl(MacroAssembler* masm);
static void Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode);
......
......@@ -1853,6 +1853,33 @@ void Builtins::Generate_CallOrConstructForwardVarargs(MacroAssembler* masm,
__ Jump(code, RelocInfo::CODE_TARGET);
}
// static
// The CSA macro "BranchIfCanUseFastCallFunction" should be used to determine
// whether a JSFunction can be called using this stub.
void Builtins::Generate_FastCallFunction(MacroAssembler* masm) {
// ----------- S t a t e -------------
// -- eax : the number of arguments (not including the receiver)
// -- edi : the function to call (checked to be a JSFunction)
// -----------------------------------
__ AssertFunction(edi);
__ mov(esi, FieldOperand(edi, JSFunction::kContextOffset));
// ----------- S t a t e -------------
// -- eax : the number of arguments (not including the receiver)
// -- edi : the function to call (checked to be a JSFunction)
// -- esi : the function context.
// -----------------------------------
// On function call, call into the debugger if necessary.
__ CheckDebugHook(edi, no_reg, ParameterCount(eax), ParameterCount(eax));
__ mov(edx, __ isolate()->factory()->undefined_value());
static_assert(kJavaScriptCallCodeStartRegister == ecx, "ABI mismatch");
__ mov(ecx, FieldOperand(edi, JSFunction::kCodeOffset));
__ add(ecx, Immediate(Code::kHeaderSize - kHeapObjectTag));
__ jmp(ecx);
}
// static
void Builtins::Generate_CallFunction(MacroAssembler* masm,
ConvertReceiverMode mode) {
......
......@@ -1722,6 +1722,33 @@ void Builtins::Generate_CallOrConstructForwardVarargs(MacroAssembler* masm,
__ Jump(code, RelocInfo::CODE_TARGET);
}
// static
// The CSA macro "BranchIfCanUseFastCallFunction" should be used to determine
// whether a JSFunction can be called using this stub.
void Builtins::Generate_FastCallFunction(MacroAssembler* masm) {
// ----------- S t a t e -------------
// -- a0 : the number of arguments (not including the receiver)
// -- a1 : the function to call (checked to be a JSFunction)
// -----------------------------------
__ AssertFunction(a1);
__ lw(cp, FieldMemOperand(a1, JSFunction::kContextOffset));
// ----------- S t a t e -------------
// -- a0 : the number of arguments (not including the receiver)
// -- a1 : the function to call (checked to be a JSFunction)
// -- cp : the function context.
// -----------------------------------
// On function call, call into the debugger if necessary.
__ CheckDebugHook(a1, no_reg, ParameterCount(a0), ParameterCount(a0));
__ LoadRoot(a3, Heap::kUndefinedValueRootIndex);
Register code = kJavaScriptCallCodeStartRegister;
__ lw(code, FieldMemOperand(a1, JSFunction::kCodeOffset));
__ Addu(code, code, Code::kHeaderSize - kHeapObjectTag);
__ Jump(code);
}
// static
void Builtins::Generate_CallFunction(MacroAssembler* masm,
ConvertReceiverMode mode) {
......
......@@ -1743,6 +1743,33 @@ void Builtins::Generate_CallOrConstructForwardVarargs(MacroAssembler* masm,
__ Jump(code, RelocInfo::CODE_TARGET);
}
// static
// The CSA macro "BranchIfCanUseFastCallFunction" should be used to determine
// whether a JSFunction can be called using this stub.
void Builtins::Generate_FastCallFunction(MacroAssembler* masm) {
// ----------- S t a t e -------------
// -- a0 : the number of arguments (not including the receiver)
// -- a1 : the function to call (checked to be a JSFunction)
// -----------------------------------
__ AssertFunction(a1);
__ Ld(cp, FieldMemOperand(a1, JSFunction::kContextOffset));
// ----------- S t a t e -------------
// -- a0 : the number of arguments (not including the receiver)
// -- a1 : the function to call (checked to be a JSFunction)
// -- cp : the function context.
// -----------------------------------
// On function call, call into the debugger if necessary.
__ CheckDebugHook(a1, no_reg, ParameterCount(a0), ParameterCount(a0));
__ LoadRoot(a3, Heap::kUndefinedValueRootIndex);
Register code = kJavaScriptCallCodeStartRegister;
__ Ld(code, FieldMemOperand(a1, JSFunction::kCodeOffset));
__ Daddu(code, code, Operand(Code::kHeaderSize - kHeapObjectTag));
__ Jump(code);
}
// static
void Builtins::Generate_CallFunction(MacroAssembler* masm,
ConvertReceiverMode mode) {
......
......@@ -1792,6 +1792,33 @@ void Builtins::Generate_CallOrConstructForwardVarargs(MacroAssembler* masm,
__ Jump(code, RelocInfo::CODE_TARGET);
}
// static
// The CSA macro "BranchIfCanUseFastCallFunction" should be used to determine
// whether a JSFunction can be called using this stub.
void Builtins::Generate_FastCallFunction(MacroAssembler* masm) {
// ----------- S t a t e -------------
// -- r3 : the number of arguments (not including the receiver)
// -- r4 : the function to call (checked to be a JSFunction)
// -----------------------------------
__ AssertFunction(r4);
__ LoadP(cp, FieldMemOperand(r4, JSFunction::kContextOffset));
// ----------- S t a t e -------------
// -- r3 : the number of arguments (not including the receiver)
// -- r4 : the function to call (checked to be a JSFunction)
// -- cp : the function context.
// -----------------------------------
// On function call, call into the debugger if necessary.
__ CheckDebugHook(r4, no_reg, ParameterCount(r3), ParameterCount(r3));
__ LoadRoot(r6, Heap::kUndefinedValueRootIndex);
Register code = kJavaScriptCallCodeStartRegister;
__ LoadP(code, FieldMemOperand(r4, JSFunction::kCodeOffset));
__ addi(code, code, Operand(Code::kHeaderSize - kHeapObjectTag));
__ JumpToJSEntry(code);
}
// static
void Builtins::Generate_CallFunction(MacroAssembler* masm,
ConvertReceiverMode mode) {
......
......@@ -1797,6 +1797,33 @@ void Builtins::Generate_CallOrConstructForwardVarargs(MacroAssembler* masm,
__ Jump(code, RelocInfo::CODE_TARGET);
}
// static
// The CSA macro "BranchIfCanUseFastCallFunction" should be used to determine
// whether a JSFunction can be called using this stub.
void Builtins::Generate_FastCallFunction(MacroAssembler* masm) {
// ----------- S t a t e -------------
// -- r2 : the number of arguments (not including the receiver)
// -- r3 : the function to call (checked to be a JSFunction)
// -----------------------------------
__ AssertFunction(r3);
__ LoadP(cp, FieldMemOperand(r3, JSFunction::kContextOffset));
// ----------- S t a t e -------------
// -- r2 : the number of arguments (not including the receiver)
// -- r3 : the function to call (checked to be a JSFunction)
// -- cp : the function context.
// -----------------------------------
// On function call, call into the debugger if necessary.
__ CheckDebugHook(r3, no_reg, ParameterCount(r2), ParameterCount(r2));
__ LoadRoot(r5, Heap::kUndefinedValueRootIndex);
Register code = kJavaScriptCallCodeStartRegister;
__ LoadP(code, FieldMemOperand(r3, JSFunction::kCodeOffset));
__ AddP(code, code, Operand(Code::kHeaderSize - kHeapObjectTag));
__ JumpToJSEntry(code);
}
// static
void Builtins::Generate_CallFunction(MacroAssembler* masm,
ConvertReceiverMode mode) {
......
......@@ -1903,6 +1903,32 @@ void Builtins::Generate_CallOrConstructForwardVarargs(MacroAssembler* masm,
__ Jump(code, RelocInfo::CODE_TARGET);
}
// static
// The CSA macro "BranchIfCanUseFastCallFunction" should be used to determine
// whether a JSFunction can be called using this stub.
void Builtins::Generate_FastCallFunction(MacroAssembler* masm) {
// ----------- S t a t e -------------
// -- rax : the number of arguments (not including the receiver)
// -- rdi : the function to call (checked to be a JSFunction)
// -----------------------------------
__ AssertFunction(rdi);
__ movp(rsi, FieldOperand(rdi, JSFunction::kContextOffset));
// ----------- S t a t e -------------
// -- rax : the number of arguments (not including the receiver)
// -- rdi : the function to call (checked to be a JSFunction)
// -- rsi : the function context.
// -----------------------------------
// On function call, call into the debugger if necessary.
__ CheckDebugHook(rdi, no_reg, ParameterCount(rax), ParameterCount(rax));
__ LoadRoot(rdx, Heap::kUndefinedValueRootIndex);
__ movp(rcx, FieldOperand(rdi, JSFunction::kCodeOffset));
__ addp(rcx, Immediate(Code::kHeaderSize - kHeapObjectTag));
__ jmp(rcx);
}
// static
void Builtins::Generate_CallFunction(MacroAssembler* masm,
ConvertReceiverMode mode) {
......
......@@ -2587,6 +2587,58 @@ TNode<Map> CodeStubAssembler::LoadJSArrayElementsMap(
LoadContextElement(native_context, Context::ArrayMapIndex(kind)));
}
void CodeStubAssembler::BranchIfCanUseFastCallFunction(
TNode<HeapObject> callable, TNode<Int32T> actualParameterCount,
Label* if_true, Label* if_false) {
GotoIfNot(IsJSFunction(callable), if_false);
TNode<JSFunction> function = CAST(callable);
TNode<SharedFunctionInfo> sfi = LoadSharedFunctionInfo(function);
TNode<Word32T> flags = UncheckedCast<Word32T>(LoadObjectField(
sfi, SharedFunctionInfo::kFlagsOffset, MachineType::Uint32()));
GotoIf(IsSetWord32<SharedFunctionInfo::IsClassConstructorBit>(flags),
if_false);
// Receiver needs to be converted for non-native sloppy mode functions.
GotoIfNot(IsSetWord32(flags, SharedFunctionInfo::IsNativeBit::kMask |
SharedFunctionInfo::IsStrictBit::kMask),
if_false);
Branch(Word32Equal(actualParameterCount, LoadFormalParameterCount(sfi)),
if_true, if_false);
}
TNode<BoolT> CodeStubAssembler::CanUseFastCallFunction(
TNode<HeapObject> callable, TNode<Int32T> actualParameterCount) {
Label if_true(this), if_false(this), done(this);
TVARIABLE(BoolT, result);
BranchIfCanUseFastCallFunction(callable, actualParameterCount, &if_true,
&if_false);
BIND(&if_true);
result = Int32TrueConstant();
Goto(&done);
BIND(&if_false);
result = Int32FalseConstant();
Goto(&done);
BIND(&done);
return result.value();
}
TNode<SharedFunctionInfo> CodeStubAssembler::LoadSharedFunctionInfo(
TNode<JSFunction> function) {
return CAST(LoadObjectField(function, JSFunction::kSharedFunctionInfoOffset));
}
TNode<Int32T> CodeStubAssembler::LoadFormalParameterCount(
TNode<SharedFunctionInfo> sfi) {
return UncheckedCast<Int32T>(
LoadObjectField(sfi, SharedFunctionInfo::kFormalParameterCountOffset,
MachineType::Uint16()));
}
TNode<BoolT> CodeStubAssembler::IsGeneratorFunction(
TNode<JSFunction> function) {
TNode<SharedFunctionInfo> const shared_function_info =
......
......@@ -679,6 +679,34 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
callable, receiver, args...));
}
// Checks whether we can use "FastCall" instead of "Call" when calling
// a JSFunction. This can be used for builtins where the user provides a
// callback. The callback doesn't change during execution of the builtin, so
// a lot of the checks that "Call" does can be done once upfront.
//
// These checks need to be kept in-sync with the "FastCall" and "Call*" stubs.
void BranchIfCanUseFastCallFunction(TNode<HeapObject> callable,
TNode<Int32T> actualParameterCount,
Label* if_true, Label* if_false);
// Uses the above function to simply return {true} or {false}, used in a
// CSA_SLOW_ASSERT.
TNode<BoolT> CanUseFastCallFunction(TNode<HeapObject> callable,
TNode<Int32T> actualParameterCount);
template <class... TArgs>
TNode<Object> FastCall(TNode<Context> context, TNode<Object> callable,
TArgs... args) {
CSA_SLOW_ASSERT(this, CanUseFastCallFunction(
CAST(callable), Int32Constant(sizeof...(TArgs))));
Callable call(isolate()->builtins()->builtin_handle(
Builtins::kFastCallFunction_ReceiverIsNullOrUndefined),
CallTrampolineDescriptor{});
return UncheckedCast<Object>(
CallJS(call, context, callable, UndefinedConstant(), args...));
}
template <class A, class F, class G>
TNode<A> Select(SloppyTNode<BoolT> condition, const F& true_body,
const G& false_body) {
......@@ -1137,6 +1165,9 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
TNode<Map> LoadJSArrayElementsMap(SloppyTNode<Int32T> kind,
SloppyTNode<Context> native_context);
TNode<SharedFunctionInfo> LoadSharedFunctionInfo(TNode<JSFunction> function);
TNode<Int32T> LoadFormalParameterCount(TNode<SharedFunctionInfo> sfi);
TNode<BoolT> IsGeneratorFunction(TNode<JSFunction> function);
TNode<BoolT> HasPrototypeProperty(TNode<JSFunction> function, TNode<Map> map);
void GotoIfPrototypeRequiresRuntimeLookup(TNode<JSFunction> function,
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
const kArraySize = 4000;
let template_array = [];
......
// Copyright 2018 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.
// Exercises the check that determines whether to use the
// "FastCallFunction_" stub when calling the comparison function.
(function TestClassConstructorAsCmpFn() {
class FooBar {};
assertThrows(() => [1, 2].sort(FooBar));
})();
const globalThis = this;
(function TestGlobalProxyIsSetAsReceiverWhenSloppy() {
[1, 2].sort((a, b) => {
assertSame(globalThis, this);
return a - b;
});
})();
(function TestReceiverIsUndefinedWhenStrict() {
"use strict";
[1, 2].sort((a, b) => {
assertSame(undefined, this);
return a - b;
});
})();
(function TestBoundFunctionAsCmpFn() {
const object = { foo: "bar" };
function cmpfn(a, b) {
assertSame(this, object);
assertSame(this.foo, "bar");
return a - b;
};
const bound_cmpfn = cmpfn.bind(object);
[1, 2].sort(bound_cmpfn);
})();
......@@ -323,6 +323,22 @@ module array {
return v;
}
builtin FastSortCompareUserFn(
context: Context, comparefn: Object, x: Object, y: Object): Number {
assert(comparefn != Undefined);
const cmpfn: Callable = unsafe_cast<Callable>(comparefn);
// a. Let v be ? ToNumber(? Call(comparefn, undefined, x, y)).
const v: Number =
ToNumber_Inline(context, FastCall(context, cmpfn, x, y));
// b. If v is NaN, return +0.
if (NumberIsNaN(v)) return 0;
// c. return v.
return v;
}
builtin CanUseSameAccessor<ElementsAccessor : type>(
context: Context, receiver: JSReceiver, initialReceiverMap: Object,
initialReceiverLength: Number): Boolean {
......@@ -1650,6 +1666,24 @@ module array {
CanUseSameAccessor<GenericElementsAccessor>;
}
// If no comparison function was provided, the default lexicographic compare
// is used. Otherwise we try to use a faster JS call by eliding some checks.
macro InitializeSortCompareFn(sortState: FixedArray, comparefnObj: Object) {
if (comparefnObj == Undefined) {
sortState[kSortComparePtrIdx] = SortCompareDefault;
return;
}
assert(TaggedIsNotSmi(comparefnObj));
assert(IsCallable(unsafe_cast<HeapObject>(comparefnObj)));
sortState[kSortComparePtrIdx] =
BranchIfCanUseFastCallFunction(
unsafe_cast<HeapObject>(comparefnObj), 2) ?
FastSortCompareUserFn :
SortCompareUserFn;
}
macro ArrayTimSortImpl(context: Context, sortState: FixedArray, length: Smi)
labels Bailout {
InitializeSortState(sortState);
......@@ -1741,11 +1775,10 @@ module array {
let map: Map = obj.map;
const sort_state: FixedArray = AllocateZeroedFixedArray(kSortStateSize);
InitializeSortCompareFn(sort_state, comparefnObj);
sort_state[kReceiverIdx] = obj;
sort_state[kUserCmpFnIdx] = comparefnObj;
sort_state[kSortComparePtrIdx] =
comparefnObj != Undefined ? SortCompareUserFn : SortCompareDefault;
sort_state[kInitialReceiverMapIdx] = map;
sort_state[kBailoutStatusIdx] = kSuccess;
......
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