Commit 3e12e60a authored by Paolo Severini's avatar Paolo Severini Committed by V8 LUCI CQ

[fastcall] Resolve CFunction overloads based on arity

To support Fast API calls with overloads, implement compile-time
function resolution based on the number of arguments passed to the JS
function.

Bug: v8:11739
Change-Id: I96839dc0b6fc540eff94573ac9e77f678908fc3a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2901249Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#74837}
parent 0ebe286f
......@@ -178,6 +178,41 @@
* To be supported types:
* - arrays of C types
* - arrays of embedder types
*
*
* The API offers a limited support for function overloads:
*
* \code
* void FastMethod_2Args(int param, bool another_param);
* void FastMethod_3Args(int param, bool another_param, int third_param);
*
* v8::CFunction fast_method_2args_c_func =
* MakeV8CFunction(FastMethod_2Args);
* v8::CFunction fast_method_3args_c_func =
* MakeV8CFunction(FastMethod_3Args);
* const v8::CFunction fast_method_overloads[] = {fast_method_2args_c_func,
* fast_method_3args_c_func};
* Local<v8::FunctionTemplate> method_template =
* v8::FunctionTemplate::NewWithCFunctionOverloads(
* isolate, SlowCallback, data, signature, length,
* constructor_behavior, side_effect_type,
* {fast_method_overloads, 2});
* \endcode
*
* In this example a single FunctionTemplate is associated to multiple C++
* functions. The overload resolution is currently only based on the number of
* arguments passed in a call. For example, if this method_template is
* registered with a wrapper JS object as described above, a call with two
* arguments:
* obj.method(42, true);
* will result in a fast call to FastMethod_2Args, while a call with three or
* more arguments:
* obj.method(42, true, 11);
* will result in a fast call to FastMethod_3Args. Instead a call with less than
* two arguments, like:
* obj.method(42);
* would not result in a fast call but would fall back to executing the
* associated SlowCallback.
*/
#ifndef INCLUDE_V8_FAST_API_CALLS_H_
......
......@@ -886,12 +886,13 @@ class FastApiCallReducerAssembler : public JSCallReducerAssembler {
public:
FastApiCallReducerAssembler(
JSCallReducer* reducer, Node* node,
const FunctionTemplateInfoRef function_template_info, Node* receiver,
Node* holder, const SharedFunctionInfoRef shared, Node* target,
const int arity, Node* effect)
const FunctionTemplateInfoRef function_template_info,
const ZoneVector<std::pair<Address, const CFunctionInfo*>>
c_candidate_functions,
Node* receiver, Node* holder, const SharedFunctionInfoRef shared,
Node* target, const int arity, Node* effect)
: JSCallReducerAssembler(reducer, node),
c_functions_(function_template_info.c_functions()),
c_signatures_(function_template_info.c_signatures()),
c_candidate_functions_(c_candidate_functions),
function_template_info_(function_template_info),
receiver_(receiver),
holder_(holder),
......@@ -899,17 +900,21 @@ class FastApiCallReducerAssembler : public JSCallReducerAssembler {
target_(target),
arity_(arity) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CHECK_GT(c_functions_.size(), 0);
CHECK_GT(c_signatures_.size(), 0);
CHECK_GT(c_candidate_functions.size(), 0);
InitializeEffectControl(effect, NodeProperties::GetControlInput(node));
}
TNode<Object> ReduceFastApiCall() {
JSCallNode n(node_ptr());
// Multiple function overloads not supported yet, always call the first
// overload with the same arity.
const size_t c_overloads_index = 0;
// C arguments include the receiver at index 0. Thus C index 1 corresponds
// to the JS argument 0, etc.
const int c_argument_count =
static_cast<int>(c_signatures_[0]->ArgumentCount());
const int c_argument_count = static_cast<int>(
c_candidate_functions_[c_overloads_index].second->ArgumentCount());
CHECK_GE(c_argument_count, kReceiver);
int cursor = 0;
......@@ -917,8 +922,8 @@ class FastApiCallReducerAssembler : public JSCallReducerAssembler {
kExtraInputsCount);
// Multiple function overloads not supported yet, always call the first
// overload.
inputs[cursor++] =
ExternalConstant(ExternalReference::Create(c_functions_[0]));
inputs[cursor++] = ExternalConstant(ExternalReference::Create(
c_candidate_functions_[c_overloads_index].first));
inputs[cursor++] = n.receiver();
......@@ -973,7 +978,8 @@ class FastApiCallReducerAssembler : public JSCallReducerAssembler {
DCHECK_EQ(cursor, c_argument_count + arity_ + kExtraInputsCount);
return FastApiCall(call_descriptor, inputs.begin(), inputs.size());
return FastApiCall(call_descriptor, inputs.begin(), inputs.size(),
c_overloads_index);
}
private:
......@@ -988,14 +994,16 @@ class FastApiCallReducerAssembler : public JSCallReducerAssembler {
static constexpr int kInlineSize = 12;
TNode<Object> FastApiCall(CallDescriptor* descriptor, Node** inputs,
size_t inputs_size) {
return AddNode<Object>(graph()->NewNode(
simplified()->FastApiCall(c_signatures_[0], feedback(), descriptor),
static_cast<int>(inputs_size), inputs));
size_t inputs_size, size_t c_overloads_index) {
return AddNode<Object>(
graph()->NewNode(simplified()->FastApiCall(
c_candidate_functions_[c_overloads_index].second,
feedback(), descriptor),
static_cast<int>(inputs_size), inputs));
}
const ZoneVector<Address> c_functions_;
const ZoneVector<const CFunctionInfo*> c_signatures_;
const ZoneVector<std::pair<Address, const CFunctionInfo*>>
c_candidate_functions_;
const FunctionTemplateInfoRef function_template_info_;
Node* const receiver_;
Node* const holder_;
......@@ -3540,26 +3548,66 @@ bool Has64BitIntegerParamsInSignature(const CFunctionInfo* c_signature) {
} // namespace
#endif
bool CanOptimizeFastCall(
const FunctionTemplateInfoRef& function_template_info) {
if (function_template_info.c_functions().empty()) return false;
// Given a FunctionTemplateInfo, checks whether the fast API call can be
// optimized, applying the initial step of the overload resolution algorithm:
// Given an overload set function_template_info.c_signatures, and a list of
// arguments of size argc:
// 1. Let max_arg be the length of the longest type list of the entries in
// function_template_info.c_signatures.
// 2. Let argc be the size of the arguments list.
// 3. Initialize arg_count = min(max_arg, argc).
// 4. Remove from the set all entries whose type list is not of length
// arg_count.
// Returns an array with the indexes of the remaining entries in S, which
// represents the set of "optimizable" function overloads.
ZoneVector<std::pair<Address, const CFunctionInfo*>> CanOptimizeFastCall(
Zone* zone, const FunctionTemplateInfoRef& function_template_info,
size_t argc) {
ZoneVector<std::pair<Address, const CFunctionInfo*>> result(zone);
if (!FLAG_turbo_fast_api_calls) return result;
static constexpr int kReceiver = 1;
ZoneVector<Address> functions = function_template_info.c_functions();
ZoneVector<const CFunctionInfo*> signatures =
function_template_info.c_signatures();
const size_t overloads_count = signatures.size();
// Calculates the length of the longest type list of the entries in
// function_template_info.
size_t max_arg = 0;
for (size_t i = 0; i < overloads_count; i++) {
const CFunctionInfo* c_signature = signatures[i];
// C arguments should include the receiver at index 0.
DCHECK_GE(c_signature->ArgumentCount(), kReceiver);
const size_t len = c_signature->ArgumentCount() - kReceiver;
if (len > max_arg) max_arg = len;
}
const size_t arg_count = std::min(max_arg, argc);
// Only considers entries whose type list length matches arg_count.
for (size_t i = 0; i < overloads_count; i++) {
const CFunctionInfo* c_signature = signatures[i];
const size_t len = c_signature->ArgumentCount() - kReceiver;
bool optimize_to_fast_call = (len == arg_count);
// Multiple function overloads not supported yet, always call the first
// overload.
const CFunctionInfo* c_signature = function_template_info.c_signatures()[0];
bool optimize_to_fast_call = FLAG_turbo_fast_api_calls;
#ifndef V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
optimize_to_fast_call =
optimize_to_fast_call && !HasFPParamsInSignature(c_signature);
optimize_to_fast_call =
optimize_to_fast_call && !HasFPParamsInSignature(c_signature);
#else
USE(c_signature);
USE(c_signature);
#endif
#ifndef V8_TARGET_ARCH_64_BIT
optimize_to_fast_call =
optimize_to_fast_call && !Has64BitIntegerParamsInSignature(c_signature);
optimize_to_fast_call =
optimize_to_fast_call && !Has64BitIntegerParamsInSignature(c_signature);
#endif
return optimize_to_fast_call;
if (optimize_to_fast_call) {
result.push_back({functions[i], c_signature});
}
}
return result;
}
Reduction JSCallReducer::ReduceCallApiFunction(
......@@ -3731,9 +3779,12 @@ Reduction JSCallReducer::ReduceCallApiFunction(
return NoChange();
}
if (CanOptimizeFastCall(function_template_info)) {
FastApiCallReducerAssembler a(this, node, function_template_info, receiver,
holder, shared, target, argc, effect);
ZoneVector<std::pair<Address, const CFunctionInfo*>> c_candidate_functions =
CanOptimizeFastCall(graph()->zone(), function_template_info, argc);
if (!c_candidate_functions.empty()) {
FastApiCallReducerAssembler a(this, node, function_template_info,
c_candidate_functions, receiver, holder,
shared, target, argc, effect);
Node* fast_call_subgraph = a.ReduceFastApiCall();
ReplaceWithSubgraph(&a, fast_call_subgraph);
......
......@@ -55,14 +55,6 @@ class FastCApiObject {
static_cast<double>(arg_i64) + static_cast<double>(arg_u64) +
static_cast<double>(arg_f32) + arg_f64;
}
static double AddAllFastCallback_5Args(Local<Object> receiver,
bool should_fallback, int32_t arg_i32,
uint32_t arg_u32, int64_t arg_i64,
uint64_t arg_u64, float arg_f32,
FastApiCallbackOptions& options) {
return AddAllFastCallback(receiver, should_fallback, arg_i32, arg_u32,
arg_i64, arg_u64, arg_f32, 0, options);
}
static void AddAllSlowCallback(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
......@@ -134,6 +126,62 @@ class FastCApiObject {
args.GetReturnValue().Set(Number::New(isolate, sum));
}
static int AddAll32BitIntFastCallback_6Args(
Local<Object> receiver, bool should_fallback, int32_t arg1_i32,
int32_t arg2_i32, int32_t arg3_i32, uint32_t arg4_u32, uint32_t arg5_u32,
uint32_t arg6_u32, FastApiCallbackOptions& options) {
FastCApiObject* self = UnwrapObject(receiver);
CHECK_SELF_OR_FALLBACK(0);
self->fast_call_count_++;
if (should_fallback) {
options.fallback = 1;
return 0;
}
return arg1_i32 + arg2_i32 + arg3_i32 + arg4_u32 + arg5_u32 + arg6_u32;
}
static int AddAll32BitIntFastCallback_5Args(
Local<Object> receiver, bool should_fallback, int32_t arg1_i32,
int32_t arg2_i32, int32_t arg3_i32, uint32_t arg4_u32, uint32_t arg5_u32,
FastApiCallbackOptions& options) {
return AddAll32BitIntFastCallback_6Args(receiver, should_fallback, arg1_i32,
arg2_i32, arg3_i32, arg4_u32,
arg5_u32, 0, options);
}
static void AddAll32BitIntSlowCallback(
const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
FastCApiObject* self = UnwrapObject(args.This());
CHECK_SELF_OR_THROW();
self->slow_call_count_++;
HandleScope handle_scope(isolate);
double sum = 0;
if (args.Length() > 1 && args[1]->IsNumber()) {
sum += args[1]->Int32Value(isolate->GetCurrentContext()).FromJust();
}
if (args.Length() > 2 && args[2]->IsNumber()) {
sum += args[2]->Int32Value(isolate->GetCurrentContext()).FromJust();
}
if (args.Length() > 3 && args[3]->IsNumber()) {
sum += args[3]->Int32Value(isolate->GetCurrentContext()).FromJust();
}
if (args.Length() > 4 && args[4]->IsNumber()) {
sum += args[4]->Uint32Value(isolate->GetCurrentContext()).FromJust();
}
if (args.Length() > 5 && args[5]->IsNumber()) {
sum += args[5]->Uint32Value(isolate->GetCurrentContext()).FromJust();
}
if (args.Length() > 6 && args[6]->IsNumber()) {
sum += args[6]->Uint32Value(isolate->GetCurrentContext()).FromJust();
}
args.GetReturnValue().Set(Number::New(isolate, sum));
}
static bool IsFastCApiObjectFastCallback(v8::Local<v8::Object> receiver,
bool should_fallback,
v8::Local<v8::Value> arg,
......@@ -290,15 +338,16 @@ Local<FunctionTemplate> Shell::CreateTestFastCApiTemplate(Isolate* isolate) {
ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &add_all_c_func));
// To test function overloads.
CFunction add_all_5args_c_func =
CFunction::Make(FastCApiObject::AddAllFastCallback_5Args);
const CFunction c_function_overloads[] = {add_all_c_func,
add_all_5args_c_func};
CFunction add_all_32bit_int_6args_c_func =
CFunction::Make(FastCApiObject::AddAll32BitIntFastCallback_6Args);
CFunction add_all_32bit_int_5args_c_func =
CFunction::Make(FastCApiObject::AddAll32BitIntFastCallback_5Args);
const CFunction c_function_overloads[] = {add_all_32bit_int_6args_c_func,
add_all_32bit_int_5args_c_func};
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "overloaded_add_all",
isolate, "overloaded_add_all_32bit_int",
FunctionTemplate::NewWithCFunctionOverloads(
isolate, FastCApiObject::AddAllSlowCallback, Local<Value>(),
isolate, FastCApiObject::AddAll32BitIntSlowCallback, Local<Value>(),
signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, {c_function_overloads, 2}));
......
......@@ -28114,8 +28114,8 @@ void CallWithLessArguments() {
"%OptimizeFunctionOnNextCall(func);"
"func();");
// Passing not enough arguments should go through the fast path.
CHECK(checker.DidCallFast());
// Passing not enough arguments should not go through the fast path.
CHECK(checker.DidCallSlow());
}
void CallWithMoreArguments() {
......@@ -180,13 +180,50 @@ assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
// Test function overloads
const add_all_32bit_int_arg1 = -42;
const add_all_32bit_int_arg2 = 45;
const add_all_32bit_int_arg3 = -12345678;
const add_all_32bit_int_arg4 = 0x1fffffff;
const add_all_32bit_int_arg5 = 1e6;
const add_all_32bit_int_arg6 = 1e8;
const add_all_32bit_int_result_4args = add_all_32bit_int_arg1 + add_all_32bit_int_arg2 + add_all_32bit_int_arg3 +
add_all_32bit_int_arg4;
const add_all_32bit_int_result_5args = add_all_32bit_int_result_4args + add_all_32bit_int_arg5;
const add_all_32bit_int_result_6args = add_all_32bit_int_result_5args + add_all_32bit_int_arg6;
function overloaded_add_all(should_fallback = false) {
return fast_c_api.overloaded_add_all(should_fallback,
-42, 45, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER,
max_safe_float * 0.5, Math.PI);
let result_under = fast_c_api.overloaded_add_all_32bit_int(should_fallback,
add_all_32bit_int_arg1, add_all_32bit_int_arg2, add_all_32bit_int_arg3, add_all_32bit_int_arg4);
let result_5args = fast_c_api.overloaded_add_all_32bit_int(should_fallback,
add_all_32bit_int_arg1, add_all_32bit_int_arg2, add_all_32bit_int_arg3, add_all_32bit_int_arg4,
add_all_32bit_int_arg5);
let result_6args = fast_c_api.overloaded_add_all_32bit_int(should_fallback,
add_all_32bit_int_arg1, add_all_32bit_int_arg2, add_all_32bit_int_arg3, add_all_32bit_int_arg4,
add_all_32bit_int_arg5, add_all_32bit_int_arg6);
let result_over = fast_c_api.overloaded_add_all_32bit_int(should_fallback,
add_all_32bit_int_arg1, add_all_32bit_int_arg2, add_all_32bit_int_arg3, add_all_32bit_int_arg4,
add_all_32bit_int_arg5, add_all_32bit_int_arg6, 42);
return [result_under, result_5args, result_6args, result_over];
}
%PrepareFunctionForOptimization(overloaded_add_all);
assertEquals(add_all_result, overloaded_add_all());
let result = overloaded_add_all();
assertEquals(add_all_32bit_int_result_4args, result[0]);
assertEquals(add_all_32bit_int_result_5args, result[1]);
assertEquals(add_all_32bit_int_result_6args, result[2]);
assertEquals(add_all_32bit_int_result_6args, result[3]);
fast_c_api.reset_counts();
%OptimizeFunctionOnNextCall(overloaded_add_all);
assertEquals(add_all_result, overloaded_add_all());
result = overloaded_add_all();
assertOptimized(overloaded_add_all);
// Only the call with less arguments goes falls back to the slow path.
assertEquals(3, fast_c_api.fast_call_count());
assertEquals(1, fast_c_api.slow_call_count());
assertEquals(add_all_32bit_int_result_4args, result[0]);
assertEquals(add_all_32bit_int_result_5args, result[1]);
assertEquals(add_all_32bit_int_result_6args, result[2]);
assertEquals(add_all_32bit_int_result_6args, result[3]);
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