Commit a2d9924c authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Introduce a CallFunctionTemplate builtin.

When calling into API callbacks from TurboFan optimized, we can
currently only take a fast-path when TurboFan is able to find some
information about the receiver in the graph, or when the API callback
specifies that it neither requires an access check (aka "accepts any
receiver") nor an interface check (aka "compatible receiver check").

This change introduces a new CallFunctionTemplate builtin that sits
in front of the CallApiCallback builtin and does both the access as well
as the interface check as necessary (and raises appropriate exceptions).
This way TurboFan can still call into the API callback via the fast-path
even without ahead knowledge about the receiver, which is significantly
faster than the generic call machinery for API callbacks.

On the test case from the Angular team[1], the interesting metrics
improve from

  DOM_mono: 0.273 ms
  DOM_mega: 0.571 ms
  DOM_call: 0.649 ms

to

  DOM_mono: 0.264 ms
  DOM_mega: 0.572 ms
  DOM_call: 0.368 ms

so the DOM_call is only about **1.4 times slower** than the DOM_mono and
about **1.5 times faster** than the DOM_mega case (compared to **2.4
times slower**). Execution time in the DOM_call was reduced by around
**~45%**.

Currently this new code path is limited to TurboFan optimized code, but
the idea is to eventually migrate the API calls from baseline to also
use the new CSA functionality, but there are lot's of subleties to take
into account, so starting with small changes to get coverage for the
basic building blocks.

[1]: https://mhevery.github.io/perf-tests/DOM-megamorphic.html

Bug: v8:8820
Change-Id: Ie1029cf182ce05a6e519fd9a9d4fa825db8adb4c
Cq-Include-Trybots: luci.chromium.try:linux-blink-rel
Reviewed-on: https://chromium-review.googlesource.com/c/1470129
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59598}
parent a08a150c
......@@ -99,6 +99,14 @@ void CallForwardVarargsDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void CallFunctionTemplateDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
// r1 : function template info
// r2 : number of arguments (on the stack, not including receiver)
Register registers[] = {r1, r2};
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void CallWithSpreadDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
// r0 : number of arguments (on the stack, not including receiver)
......
......@@ -99,6 +99,14 @@ void CallForwardVarargsDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void CallFunctionTemplateDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
// x1 : function template info
// x2 : number of arguments (on the stack, not including receiver)
Register registers[] = {x1, x2};
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void CallWithSpreadDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
// x0 : number of arguments (on the stack, not including receiver)
......
This diff is collapsed.
......@@ -28,6 +28,21 @@ class CallOrConstructBuiltinsAssembler : public CodeStubAssembler {
void CallOrConstructWithSpread(TNode<Object> target, TNode<Object> new_target,
TNode<Object> spread, TNode<Int32T> args_count,
TNode<Context> context);
enum class CallFunctionTemplateMode : uint8_t {
kCheckAccess,
kCheckCompatibleReceiver,
kCheckAccessAndCompatibleReceiver,
};
void CallFunctionTemplate(CallFunctionTemplateMode mode,
TNode<FunctionTemplateInfo> function_template_info,
TNode<IntPtrT> argc, TNode<Context> context);
private:
TNode<JSReceiver> GetCompatibleReceiver(TNode<JSReceiver> receiver,
TNode<HeapObject> signature,
TNode<Context> context);
};
} // namespace internal
......
......@@ -61,6 +61,12 @@ namespace internal {
TFC(CallWithArrayLike, CallWithArrayLike, 1) \
ASM(CallForwardVarargs, CallForwardVarargs) \
ASM(CallFunctionForwardVarargs, CallForwardVarargs) \
/* Call an API callback via a {FunctionTemplateInfo}, doing appropriate */ \
/* access and compatible receiver checks. */ \
TFC(CallFunctionTemplate_CheckAccess, CallFunctionTemplate, 1) \
TFC(CallFunctionTemplate_CheckCompatibleReceiver, CallFunctionTemplate, 1) \
TFC(CallFunctionTemplate_CheckAccessAndCompatibleReceiver, \
CallFunctionTemplate, 1) \
\
/* Construct */ \
/* ES6 section 9.2.2 [[Construct]] ( argumentsList, newTarget) */ \
......
......@@ -2956,8 +2956,27 @@ Reduction JSCallReducer::ReduceCallApiFunction(
graph()->NewNode(simplified()->ConvertReceiver(p.convert_mode()),
receiver, global_proxy, effect, control);
} else {
// We cannot do a fast API call in this case.
return NoChange();
// We don't have enough information to eliminate the access check
// and/or the compatible receiver check, so use the generic builtin
// that does those checks dynamically. This is still significantly
// faster than the generic call sequence.
Builtins::Name builtin_name =
!function_template_info->accept_any_receiver()
? (function_template_info->signature()->IsUndefined(isolate())
? Builtins::kCallFunctionTemplate_CheckAccess
: Builtins::
kCallFunctionTemplate_CheckAccessAndCompatibleReceiver)
: Builtins::kCallFunctionTemplate_CheckCompatibleReceiver;
Callable callable = Builtins::CallableFor(isolate(), builtin_name);
auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(),
argc + 1 /* implicit receiver */, CallDescriptor::kNeedsFrameState);
node->InsertInput(graph()->zone(), 0,
jsgraph()->HeapConstant(callable.code()));
node->ReplaceInput(1, jsgraph()->HeapConstant(function_template_info));
node->InsertInput(graph()->zone(), 2, jsgraph()->Constant(argc));
NodeProperties::ChangeOp(node, common()->Call(call_descriptor));
return Changed(node);
}
// TODO(turbofan): Consider introducing a JSCallApiCallback operator for
......@@ -2969,8 +2988,7 @@ Reduction JSCallReducer::ReduceCallApiFunction(
Callable call_api_callback = CodeFactory::CallApiCallback(isolate());
CallInterfaceDescriptor cid = call_api_callback.descriptor();
auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), cid,
cid.GetStackParameterCount() + argc + 1 /* implicit receiver */,
graph()->zone(), cid, argc + 1 /* implicit receiver */,
CallDescriptor::kNeedsFrameState);
ApiFunction api_function(v8::ToCData<Address>(call_handler_info->callback()));
ExternalReference function_reference = ExternalReference::Create(
......
......@@ -100,6 +100,14 @@ void CallForwardVarargsDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void CallFunctionTemplateDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
// edx : function template info
// ecx : number of arguments (on the stack, not including receiver)
Register registers[] = {edx, ecx};
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void CallWithSpreadDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
// eax : number of arguments (on the stack, not including receiver)
......
......@@ -31,6 +31,7 @@ namespace internal {
V(BigIntToWasmI64) \
V(BinaryOp) \
V(CallForwardVarargs) \
V(CallFunctionTemplate) \
V(CallTrampoline) \
V(CallVarargs) \
V(CallWithArrayLike) \
......@@ -793,6 +794,14 @@ class CallForwardVarargsDescriptor : public CallInterfaceDescriptor {
DECLARE_DESCRIPTOR(CallForwardVarargsDescriptor, CallInterfaceDescriptor)
};
class CallFunctionTemplateDescriptor : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS(kFunctionTemplateInfo, kArgumentsCount)
DEFINE_PARAMETER_TYPES(MachineType::AnyTagged(), // kFunctionTemplateInfo
MachineType::IntPtr()) // kArgumentsCount
DECLARE_DESCRIPTOR(CallFunctionTemplateDescriptor, CallInterfaceDescriptor)
};
class CallWithSpreadDescriptor : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS(kTarget, kArgumentsCount, kSpread)
......
......@@ -224,13 +224,6 @@ class FunctionTemplateInfo : public TemplateInfo {
static MaybeHandle<Name> TryGetCachedPropertyName(Isolate* isolate,
Handle<Object> getter);
private:
static inline FunctionTemplateRareData EnsureFunctionTemplateRareData(
Isolate* isolate, Handle<FunctionTemplateInfo> function_template_info);
static FunctionTemplateRareData AllocateFunctionTemplateRareData(
Isolate* isolate, Handle<FunctionTemplateInfo> function_template_info);
// Bit position in the flag, from least significant bit position.
static const int kHiddenPrototypeBit = 0;
static const int kUndetectableBit = 1;
......@@ -240,6 +233,13 @@ class FunctionTemplateInfo : public TemplateInfo {
static const int kDoNotCacheBit = 5;
static const int kAcceptAnyReceiver = 6;
private:
static inline FunctionTemplateRareData EnsureFunctionTemplateRareData(
Isolate* isolate, Handle<FunctionTemplateInfo> function_template_info);
static FunctionTemplateRareData AllocateFunctionTemplateRareData(
Isolate* isolate, Handle<FunctionTemplateInfo> function_template_info);
OBJECT_CONSTRUCTORS(FunctionTemplateInfo, TemplateInfo);
};
......
......@@ -27,6 +27,17 @@
namespace v8 {
namespace internal {
RUNTIME_FUNCTION(Runtime_AccessCheck) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0);
if (!isolate->MayAccess(handle(isolate->context(), isolate), object)) {
isolate->ReportFailedAccessCheck(object);
RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate);
}
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_CheckIsBootstrapping) {
SealHandleScope shs(isolate);
DCHECK_EQ(0, args.length());
......
......@@ -205,6 +205,7 @@ namespace internal {
#endif // V8_INTL_SUPPORT
#define FOR_EACH_INTRINSIC_INTERNAL(F, I) \
F(AccessCheck, 1, 1) \
F(AllocateInNewSpace, 1, 1) \
F(AllocateInTargetSpace, 2, 1) \
F(AllocateSeqOneByteString, 1, 1) \
......
......@@ -101,6 +101,14 @@ void CallForwardVarargsDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void CallFunctionTemplateDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
// rdx: the function template info
// rcx: number of arguments (on the stack, not including receiver)
Register registers[] = {rdx, rcx};
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void CallWithSpreadDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
// rax : number of arguments (on the stack, not including receiver)
......
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