[fastcall] Resolve CFunction overloads based on type checks at runtime

This CL implements the resolution of function overloads based on
run-time checks of the type of arguments passed to the JS function.
For the moment, the only supported overload resolution is between
JSArrays and TypedArrays.

Bug: v8:11739
Change-Id: Iabb79149f021037470a3adf071d1cccb6f00acd1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2987599
Reviewed-by: Georg Neis <neis@chromium.org>
Reviewed-by: Maya Lekova <mslekova@chromium.org>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#75664}
This commit is contained in:
Paolo Severini 2021-07-07 17:48:40 -07:00 committed by V8 LUCI CQ
parent afa6126921
commit 2690d46507
14 changed files with 666 additions and 154 deletions

View File

@ -2517,6 +2517,7 @@ v8_header_set("v8_internal_headers") {
"src/compiler/effect-control-linearizer.h",
"src/compiler/escape-analysis-reducer.h",
"src/compiler/escape-analysis.h",
"src/compiler/fast-api-calls.h",
"src/compiler/feedback-source.h",
"src/compiler/frame-states.h",
"src/compiler/frame.h",
@ -3517,6 +3518,7 @@ v8_compiler_sources = [
"src/compiler/effect-control-linearizer.cc",
"src/compiler/escape-analysis-reducer.cc",
"src/compiler/escape-analysis.cc",
"src/compiler/fast-api-calls.cc",
"src/compiler/feedback-source.cc",
"src/compiler/frame-states.cc",
"src/compiler/frame.cc",

View File

@ -611,6 +611,16 @@ struct TypeInfoHelper<v8::Local<v8::Array>> {
}
};
template <>
struct TypeInfoHelper<v8::Local<v8::Uint32Array>> {
static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; }
static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::kUint32; }
static constexpr CTypeInfo::SequenceType SequenceType() {
return CTypeInfo::SequenceType::kIsTypedArray;
}
};
template <>
struct TypeInfoHelper<FastApiCallbackOptions&> {
static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; }

View File

@ -12,6 +12,7 @@
#include "src/common/ptr-compr-inl.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/compiler-source-position-table.h"
#include "src/compiler/fast-api-calls.h"
#include "src/compiler/feedback-source.h"
#include "src/compiler/graph-assembler.h"
#include "src/compiler/js-graph.h"
@ -198,10 +199,22 @@ class EffectControlLinearizer {
Node* LowerLoadMessage(Node* node);
Node* AdaptFastCallArgument(Node* node, CTypeInfo arg_type,
GraphAssemblerLabel<0>* if_error);
struct AdaptOverloadedFastCallResult {
Node* target_address;
Node* argument;
};
AdaptOverloadedFastCallResult AdaptOverloadedFastCallArgument(
Node* node, const FastApiCallFunctionVector& c_functions,
const fast_api_call::OverloadsResolutionResult&
overloads_resolution_result,
GraphAssemblerLabel<0>* if_error);
Node* WrapFastCall(const CallDescriptor* call_descriptor, int inputs_size,
Node** inputs, Node* target,
const CFunctionInfo* c_signature, int c_arg_count,
Node* stack_slot);
Node* GenerateSlowApiCall(Node* node);
Node* LowerFastApiCall(Node* node);
Node* LowerLoadTypedElement(Node* node);
Node* LowerLoadDataViewElement(Node* node);
@ -5040,12 +5053,98 @@ Node* EffectControlLinearizer::AdaptFastCallArgument(
return stack_slot;
}
case CTypeInfo::SequenceType::kIsTypedArray:
// TODO(mslekova): Implement typed arrays.
return node;
default: {
UNREACHABLE(); // TODO(mslekova): Implement typed arrays.
UNREACHABLE();
}
}
}
EffectControlLinearizer::AdaptOverloadedFastCallResult
EffectControlLinearizer::AdaptOverloadedFastCallArgument(
Node* node, const FastApiCallFunctionVector& c_functions,
const fast_api_call::OverloadsResolutionResult& overloads_resolution_result,
GraphAssemblerLabel<0>* if_error) {
static constexpr int kReceiver = 1;
auto merge = __ MakeLabel(MachineRepresentation::kTagged);
int kAlign = alignof(uintptr_t);
int kSize = sizeof(uintptr_t);
Node* stack_slot = __ StackSlot(kSize, kAlign);
__ Store(StoreRepresentation(MachineType::PointerRepresentation(),
kNoWriteBarrier),
stack_slot, 0, node);
for (size_t func_index = 0; func_index < c_functions.size(); func_index++) {
const CFunctionInfo* c_signature = c_functions[func_index].signature;
CTypeInfo arg_type = c_signature->ArgumentInfo(
overloads_resolution_result.distinguishable_arg_index + kReceiver);
auto next = __ MakeLabel();
// Check that the value is a HeapObject.
Node* value_is_smi = ObjectIsSmi(node);
__ GotoIf(value_is_smi, if_error);
switch (arg_type.GetSequenceType()) {
case CTypeInfo::SequenceType::kIsSequence: {
CHECK_EQ(arg_type.GetType(), CTypeInfo::Type::kVoid);
// Check that the value is a JSArray.
Node* value_map = __ LoadField(AccessBuilder::ForMap(), node);
Node* value_instance_type =
__ LoadField(AccessBuilder::ForMapInstanceType(), value_map);
Node* value_is_js_array = __ Word32Equal(
value_instance_type, __ Int32Constant(JS_ARRAY_TYPE));
__ GotoIfNot(value_is_js_array, &next);
Node* target_address = __ ExternalConstant(
ExternalReference::Create(c_functions[func_index].address));
__ Goto(&merge, target_address);
break;
}
case CTypeInfo::SequenceType::kIsTypedArray: {
// Check that the value is a TypedArray with a type that matches the
// type declared in the c-function.
ElementsKind typed_array_elements_kind =
fast_api_call::GetTypedArrayElementsKind(
overloads_resolution_result.element_type);
Node* value_map = __ LoadField(AccessBuilder::ForMap(), node);
Node* value_bit_field2 =
__ LoadField(AccessBuilder::ForMapBitField2(), value_map);
Node* value_elements_kind = __ WordShr(
__ WordAnd(value_bit_field2,
__ Int32Constant(Map::Bits2::ElementsKindBits::kMask)),
__ Int32Constant(Map::Bits2::ElementsKindBits::kShift));
Node* is_same_kind = __ Word32Equal(
value_elements_kind,
__ Int32Constant(GetPackedElementsKind(typed_array_elements_kind)));
__ GotoIfNot(is_same_kind, &next);
Node* target_address = __ ExternalConstant(
ExternalReference::Create(c_functions[func_index].address));
__ Goto(&merge, target_address);
break;
}
default: {
UNREACHABLE();
}
}
__ Bind(&next);
}
__ Goto(if_error);
__ Bind(&merge);
return {merge.PhiAt(0), stack_slot};
}
Node* EffectControlLinearizer::WrapFastCall(
const CallDescriptor* call_descriptor, int inputs_size, Node** inputs,
Node* target, const CFunctionInfo* c_signature, int c_arg_count,
@ -5100,10 +5199,39 @@ Node* EffectControlLinearizer::WrapFastCall(
return call;
}
Node* EffectControlLinearizer::GenerateSlowApiCall(Node* node) {
FastApiCallNode n(node);
FastApiCallParameters const& params = n.Parameters();
const CFunctionInfo* c_signature = params.c_functions()[0].signature;
const int c_arg_count = c_signature->ArgumentCount();
Node** const slow_inputs = graph()->zone()->NewArray<Node*>(
n.SlowCallArgumentCount() + FastApiCallNode::kEffectAndControlInputCount);
int fast_call_params = c_arg_count;
CHECK_EQ(node->op()->ValueInputCount() - fast_call_params,
n.SlowCallArgumentCount());
int index = 0;
for (; index < n.SlowCallArgumentCount(); ++index) {
slow_inputs[index] = n.SlowCallArgument(index);
}
slow_inputs[index] = __ effect();
slow_inputs[index + 1] = __ control();
Node* slow_call_result = __ Call(
params.descriptor(), index + FastApiCallNode::kEffectAndControlInputCount,
slow_inputs);
return slow_call_result;
}
Node* EffectControlLinearizer::LowerFastApiCall(Node* node) {
FastApiCallNode n(node);
FastApiCallParameters const& params = n.Parameters();
const CFunctionInfo* c_signature = params.signature();
static constexpr int kReceiver = 1;
const FastApiCallFunctionVector& c_functions = params.c_functions();
const CFunctionInfo* c_signature = params.c_functions()[0].signature;
const int c_arg_count = c_signature->ArgumentCount();
CallDescriptor* js_call_descriptor = params.descriptor();
int js_arg_count = static_cast<int>(js_call_descriptor->ParameterCount());
@ -5149,26 +5277,82 @@ Node* EffectControlLinearizer::LowerFastApiCall(Node* node) {
CallDescriptor* call_descriptor =
Linkage::GetSimplifiedCDescriptor(graph()->zone(), builder.Build());
call_descriptor->SetCFunctionInfo(c_signature);
Node** const inputs = graph()->zone()->NewArray<Node*>(
c_arg_count + n.FastCallExtraInputCount());
inputs[0] = n.target();
// Hint to fast path.
auto if_success = __ MakeLabel();
auto if_error = __ MakeDeferredLabel();
for (int i = FastApiCallNode::kFastTargetInputCount;
i < c_arg_count + FastApiCallNode::kFastTargetInputCount; ++i) {
Node* value = NodeProperties::GetValueInput(node, i);
CTypeInfo type = c_signature->ArgumentInfo(i - 1);
inputs[i] = AdaptFastCallArgument(value, type, &if_error);
// Overload resolution
bool generate_fast_call = false;
int distinguishable_arg_index = INT_MIN;
fast_api_call::OverloadsResolutionResult overloads_resolution_result =
fast_api_call::OverloadsResolutionResult::Invalid();
if (c_functions.size() == 1) {
generate_fast_call = true;
} else {
DCHECK_EQ(c_functions.size(), 2);
overloads_resolution_result = fast_api_call::ResolveOverloads(
graph()->zone(), c_functions, c_arg_count);
if (overloads_resolution_result.is_valid()) {
generate_fast_call = true;
distinguishable_arg_index =
overloads_resolution_result.distinguishable_arg_index;
}
}
Node* c_call_result =
WrapFastCall(call_descriptor, c_arg_count + n.FastCallExtraInputCount(),
inputs, n.target(), c_signature, c_arg_count, stack_slot);
if (!generate_fast_call) {
// Only generate the slow call.
return GenerateSlowApiCall(node);
}
// Generate fast call.
const int kFastTargetAddressInputIndex = 0;
const int kFastTargetAddressInputCount = 1;
Node** const inputs = graph()->zone()->NewArray<Node*>(
kFastTargetAddressInputCount + c_arg_count + n.FastCallExtraInputCount());
// The inputs to {Call} node for the fast call look like:
// [fast callee, receiver, ... C arguments, [optional Options], effect,
// control].
//
// The first input node represents the target address for the fast call.
// If the function is not overloaded (c_functions.size() == 1) this is the
// address associated to the first and only element in the c_functions vector.
// If there are multiple overloads the value of this input will be set later
// with a Phi node created by AdaptOverloadedFastCallArgument.
inputs[kFastTargetAddressInputIndex] =
(c_functions.size() == 1) ? __ ExternalConstant(ExternalReference::Create(
c_functions[0].address))
: nullptr;
for (int i = 0; i < c_arg_count; ++i) {
Node* value = NodeProperties::GetValueInput(node, i);
if (i == distinguishable_arg_index + kReceiver) {
// This only happens when the FastApiCall node represents multiple
// overloaded functions and {i} is the index of the distinguishable
// argument.
AdaptOverloadedFastCallResult nodes = AdaptOverloadedFastCallArgument(
value, c_functions, overloads_resolution_result, &if_error);
inputs[i + kFastTargetAddressInputCount] = nodes.argument;
// Replace the target address node with a Phi node that represents the
// choice between the target addreseses of overloaded functions.
inputs[kFastTargetAddressInputIndex] = nodes.target_address;
} else {
CTypeInfo type = c_signature->ArgumentInfo(i);
inputs[i + kFastTargetAddressInputCount] =
AdaptFastCallArgument(value, type, &if_error);
}
}
DCHECK_NOT_NULL(inputs[0]);
Node* c_call_result = WrapFastCall(
call_descriptor, c_arg_count + n.FastCallExtraInputCount() + 1, inputs,
inputs[0], c_signature, c_arg_count, stack_slot);
Node* fast_call_result;
switch (c_signature->ReturnInfo().GetType()) {
@ -5220,22 +5404,7 @@ Node* EffectControlLinearizer::LowerFastApiCall(Node* node) {
// Generate direct slow call.
__ Bind(&if_error);
{
Node** const slow_inputs = graph()->zone()->NewArray<Node*>(
n.SlowCallArgumentCount() +
FastApiCallNode::kEffectAndControlInputCount);
int fast_call_params = c_arg_count + FastApiCallNode::kFastTargetInputCount;
CHECK_EQ(value_input_count - fast_call_params, n.SlowCallArgumentCount());
int index = 0;
for (; index < n.SlowCallArgumentCount(); ++index) {
slow_inputs[index] = n.SlowCallArgument(index);
}
slow_inputs[index] = __ effect();
slow_inputs[index + 1] = __ control();
Node* slow_call_result = __ Call(
params.descriptor(),
index + FastApiCallNode::kEffectAndControlInputCount, slow_inputs);
Node* slow_call_result = GenerateSlowApiCall(node);
__ Goto(&merge, slow_call_result);
}

View File

@ -0,0 +1,84 @@
// Copyright 2021 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.
#include "src/compiler/fast-api-calls.h"
namespace v8 {
namespace internal {
namespace compiler {
namespace fast_api_call {
ElementsKind GetTypedArrayElementsKind(CTypeInfo::Type type) {
switch (type) {
case CTypeInfo::Type::kInt32:
return INT32_ELEMENTS;
case CTypeInfo::Type::kUint32:
return UINT32_ELEMENTS;
case CTypeInfo::Type::kInt64:
return BIGINT64_ELEMENTS;
case CTypeInfo::Type::kUint64:
return BIGUINT64_ELEMENTS;
case CTypeInfo::Type::kFloat32:
return FLOAT32_ELEMENTS;
case CTypeInfo::Type::kFloat64:
return FLOAT64_ELEMENTS;
case CTypeInfo::Type::kVoid:
case CTypeInfo::Type::kBool:
case CTypeInfo::Type::kV8Value:
case CTypeInfo::Type::kApiObject:
UNREACHABLE();
break;
}
}
OverloadsResolutionResult ResolveOverloads(
Zone* zone, const FastApiCallFunctionVector& candidates,
unsigned int arg_count) {
DCHECK_GT(arg_count, 0);
static constexpr int kReceiver = 1;
// Only the case of the overload resolution of two functions, one with a
// JSArray param and the other with a typed array param is currently
// supported.
DCHECK_EQ(candidates.size(), 2);
for (unsigned int arg_index = 0; arg_index < arg_count - kReceiver;
arg_index++) {
int index_of_func_with_js_array_arg = -1;
int index_of_func_with_typed_array_arg = -1;
CTypeInfo::Type element_type = CTypeInfo::Type::kVoid;
for (size_t i = 0; i < candidates.size(); i++) {
const CTypeInfo& type_info =
candidates[i].signature->ArgumentInfo(arg_index + kReceiver);
CTypeInfo::SequenceType sequence_type = type_info.GetSequenceType();
if (sequence_type == CTypeInfo::SequenceType::kIsSequence) {
DCHECK_LT(index_of_func_with_js_array_arg, 0);
index_of_func_with_js_array_arg = static_cast<int>(i);
} else if (sequence_type == CTypeInfo::SequenceType::kIsTypedArray) {
DCHECK_LT(index_of_func_with_typed_array_arg, 0);
index_of_func_with_typed_array_arg = static_cast<int>(i);
element_type = type_info.GetType();
} else {
DCHECK_LT(index_of_func_with_js_array_arg, 0);
DCHECK_LT(index_of_func_with_typed_array_arg, 0);
}
}
if (index_of_func_with_js_array_arg >= 0 &&
index_of_func_with_typed_array_arg >= 0) {
return {static_cast<int>(arg_index), element_type};
}
}
// No overload found with a JSArray and a typed array as i-th argument.
return OverloadsResolutionResult::Invalid();
}
} // namespace fast_api_call
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,50 @@
// Copyright 2021 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.
#ifndef V8_COMPILER_FAST_API_CALLS_H_
#define V8_COMPILER_FAST_API_CALLS_H_
#include "include/v8-fast-api-calls.h"
#include "src/compiler/graph-assembler.h"
namespace v8 {
namespace internal {
namespace compiler {
namespace fast_api_call {
struct OverloadsResolutionResult {
static OverloadsResolutionResult Invalid() {
return OverloadsResolutionResult(-1, CTypeInfo::Type::kVoid);
}
OverloadsResolutionResult(int distinguishable_arg_index_,
CTypeInfo::Type element_type_)
: distinguishable_arg_index(distinguishable_arg_index_),
element_type(element_type_) {
DCHECK(distinguishable_arg_index_ < 0 ||
element_type_ != CTypeInfo::Type::kVoid);
}
bool is_valid() const { return distinguishable_arg_index >= 0; }
// The index of the distinguishable overload argument. Only the case where the
// types of this argument is a JSArray vs a TypedArray is supported.
int distinguishable_arg_index;
// The element type in the typed array argument.
CTypeInfo::Type element_type;
};
ElementsKind GetTypedArrayElementsKind(CTypeInfo::Type type);
OverloadsResolutionResult ResolveOverloads(
Zone* zone, const FastApiCallFunctionVector& candidates,
unsigned int arg_count);
} // namespace fast_api_call
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_COMPILER_FAST_API_CALLS_H_

View File

@ -895,10 +895,9 @@ class FastApiCallReducerAssembler : public JSCallReducerAssembler {
FastApiCallReducerAssembler(
JSCallReducer* reducer, Node* node,
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)
const FastApiCallFunctionVector& c_candidate_functions, Node* receiver,
Node* holder, const SharedFunctionInfoRef shared, Node* target,
const int arity, Node* effect)
: JSCallReducerAssembler(reducer, node),
c_candidate_functions_(c_candidate_functions),
function_template_info_(function_template_info),
@ -915,24 +914,17 @@ class FastApiCallReducerAssembler : public JSCallReducerAssembler {
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_candidate_functions_[c_overloads_index].second->ArgumentCount());
// All functions in c_candidate_functions_ have the same number of
// arguments, so extract c_argument_count from the first function.
const int c_argument_count =
static_cast<int>(c_candidate_functions_[0].signature->ArgumentCount());
CHECK_GE(c_argument_count, kReceiver);
int cursor = 0;
base::SmallVector<Node*, kInlineSize> inputs(c_argument_count + arity_ +
kExtraInputsCount);
// Multiple function overloads not supported yet, always call the first
// overload.
inputs[cursor++] = ExternalConstant(ExternalReference::Create(
c_candidate_functions_[c_overloads_index].first));
inputs[cursor++] = n.receiver();
// TODO(turbofan): Consider refactoring CFunctionInfo to distinguish
@ -986,32 +978,29 @@ class FastApiCallReducerAssembler : public JSCallReducerAssembler {
DCHECK_EQ(cursor, c_argument_count + arity_ + kExtraInputsCount);
return FastApiCall(call_descriptor, inputs.begin(), inputs.size(),
c_overloads_index);
return FastApiCall(call_descriptor, inputs.begin(), inputs.size());
}
private:
static constexpr int kTarget = 1;
static constexpr int kSlowTarget = 1;
static constexpr int kEffectAndControl = 2;
static constexpr int kContextAndFrameState = 2;
static constexpr int kCallCodeDataAndArgc = 3;
static constexpr int kHolder = 1, kReceiver = 1;
static constexpr int kExtraInputsCount =
kTarget * 2 + kEffectAndControl + kContextAndFrameState +
kSlowTarget + kEffectAndControl + kContextAndFrameState +
kCallCodeDataAndArgc + kHolder + kReceiver;
static constexpr int kInlineSize = 12;
TNode<Object> FastApiCall(CallDescriptor* descriptor, Node** inputs,
size_t inputs_size, size_t c_overloads_index) {
size_t inputs_size) {
return AddNode<Object>(
graph()->NewNode(simplified()->FastApiCall(
c_candidate_functions_[c_overloads_index].second,
graph()->NewNode(simplified()->FastApiCall(c_candidate_functions_,
feedback(), descriptor),
static_cast<int>(inputs_size), inputs));
}
const ZoneVector<std::pair<Address, const CFunctionInfo*>>
c_candidate_functions_;
const FastApiCallFunctionVector c_candidate_functions_;
const FunctionTemplateInfoRef function_template_info_;
Node* const receiver_;
Node* const holder_;
@ -3580,10 +3569,10 @@ bool Has64BitIntegerParamsInSignature(const CFunctionInfo* c_signature) {
// 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(
FastApiCallFunctionVector CanOptimizeFastCall(
Zone* zone, const FunctionTemplateInfoRef& function_template_info,
size_t argc) {
ZoneVector<std::pair<Address, const CFunctionInfo*>> result(zone);
FastApiCallFunctionVector result(zone);
if (!FLAG_turbo_fast_api_calls) return result;
static constexpr int kReceiver = 1;
@ -3798,8 +3787,12 @@ Reduction JSCallReducer::ReduceCallApiFunction(
return NoChange();
}
ZoneVector<std::pair<Address, const CFunctionInfo*>> c_candidate_functions =
// Handles overloaded functions.
FastApiCallFunctionVector c_candidate_functions =
CanOptimizeFastCall(graph()->zone(), function_template_info, argc);
DCHECK_LE(c_candidate_functions.size(), 2);
if (!c_candidate_functions.empty()) {
FastApiCallReducerAssembler a(this, node, function_template_info,
c_candidate_functions, receiver, holder,
@ -3810,6 +3803,8 @@ Reduction JSCallReducer::ReduceCallApiFunction(
return Replace(fast_call_subgraph);
}
// Slow call
CallHandlerInfoRef call_handler_info = *function_template_info.call_code();
Callable call_api_callback = CodeFactory::CallApiCallback(isolate());
CallInterfaceDescriptor cid = call_api_callback.descriptor();

View File

@ -418,13 +418,6 @@ class V8_EXPORT_PRIVATE CallDescriptor final
return allocatable_registers_ != 0;
}
// Stores the signature information for a fast API call - C++ functions
// that can be called directly from TurboFan.
void SetCFunctionInfo(const CFunctionInfo* c_function_info) {
c_function_info_ = c_function_info;
}
const CFunctionInfo* GetCFunctionInfo() const { return c_function_info_; }
private:
friend class Linkage;
@ -443,7 +436,6 @@ class V8_EXPORT_PRIVATE CallDescriptor final
const Flags flags_;
const StackArgumentOrder stack_order_;
const char* const debug_name_;
const CFunctionInfo* c_function_info_ = nullptr;
};
DEFINE_OPERATORS_FOR_FLAGS(CallDescriptor::Flags)

View File

@ -1815,8 +1815,11 @@ class RepresentationSelector {
CHECK_EQ(type.GetType(), CTypeInfo::Type::kVoid);
return UseInfo::AnyTagged();
}
case CTypeInfo::SequenceType::kIsTypedArray: {
return UseInfo::AnyTagged();
}
default: {
UNREACHABLE(); // TODO(mslekova): Implement typed arrays.
UNREACHABLE(); // TODO(mslekova): Implement array buffers.
}
}
}
@ -1827,7 +1830,12 @@ class RepresentationSelector {
void VisitFastApiCall(Node* node, SimplifiedLowering* lowering) {
FastApiCallParameters const& op_params =
FastApiCallParametersOf(node->op());
const CFunctionInfo* c_signature = op_params.signature();
// We only consider the first function signature here. In case of function
// overloads, we only support the case of two functions that differ for one
// argument, which must be a JSArray in one function and a TypedArray in the
// other function, and both JSArrays and TypedArrays have the same UseInfo
// UseInfo::AnyTagged(). All the other argument types must match.
const CFunctionInfo* c_signature = op_params.c_functions()[0].signature;
const int c_arg_count = c_signature->ArgumentCount();
CallDescriptor* call_descriptor = op_params.descriptor();
int js_arg_count = static_cast<int>(call_descriptor->ParameterCount());
@ -1837,28 +1845,21 @@ class RepresentationSelector {
base::SmallVector<UseInfo, kInitialArgumentsCount> arg_use_info(
c_arg_count);
// The target of the fast call.
ProcessInput<T>(node, 0, UseInfo::Word());
// Propagate representation information from TypeInfo.
for (int i = 0; i < c_arg_count; i++) {
arg_use_info[i] = UseInfoForFastApiCallArgument(
c_signature->ArgumentInfo(i), op_params.feedback());
ProcessInput<T>(node, i + FastApiCallNode::kFastTargetInputCount,
arg_use_info[i]);
ProcessInput<T>(node, i, arg_use_info[i]);
}
// The call code for the slow call.
ProcessInput<T>(node, c_arg_count + FastApiCallNode::kFastTargetInputCount,
UseInfo::AnyTagged());
ProcessInput<T>(node, c_arg_count, UseInfo::AnyTagged());
for (int i = 1; i <= js_arg_count; i++) {
ProcessInput<T>(node,
c_arg_count + FastApiCallNode::kFastTargetInputCount + i,
ProcessInput<T>(node, c_arg_count + i,
TruncatingUseInfoFromRepresentation(
call_descriptor->GetInputType(i).representation()));
}
for (int i = c_arg_count + FastApiCallNode::kFastTargetInputCount +
js_arg_count;
i < value_input_count; ++i) {
for (int i = c_arg_count + js_arg_count; i < value_input_count; ++i) {
ProcessInput<T>(node, i, UseInfo::AnyTagged());
}
ProcessRemainingInputs<T>(node, value_input_count);

View File

@ -1752,17 +1752,26 @@ FastApiCallParameters const& FastApiCallParametersOf(const Operator* op) {
}
std::ostream& operator<<(std::ostream& os, FastApiCallParameters const& p) {
return os << p.signature() << ", " << p.feedback() << ", " << p.descriptor();
const auto& c_functions = p.c_functions();
for (size_t i = 0; i < c_functions.size(); i++) {
os << c_functions[i].address << ":" << c_functions[i].signature << ", ";
}
return os << p.feedback() << ", " << p.descriptor();
}
size_t hash_value(FastApiCallParameters const& p) {
return base::hash_combine(p.signature(), FeedbackSource::Hash()(p.feedback()),
const auto& c_functions = p.c_functions();
size_t hash = 0;
for (size_t i = 0; i < c_functions.size(); i++) {
hash = base::hash_combine(c_functions[i].address, c_functions[i].signature);
}
return base::hash_combine(hash, FeedbackSource::Hash()(p.feedback()),
p.descriptor());
}
bool operator==(FastApiCallParameters const& lhs,
FastApiCallParameters const& rhs) {
return lhs.signature() == rhs.signature() &&
return lhs.c_functions() == rhs.c_functions() &&
lhs.feedback() == rhs.feedback() &&
lhs.descriptor() == rhs.descriptor();
}
@ -1954,27 +1963,39 @@ const Operator* SimplifiedOperatorBuilder::TransitionAndStoreNonNumberElement(
}
const Operator* SimplifiedOperatorBuilder::FastApiCall(
const CFunctionInfo* signature, FeedbackSource const& feedback,
CallDescriptor* descriptor) {
const FastApiCallFunctionVector& c_functions,
FeedbackSource const& feedback, CallDescriptor* descriptor) {
DCHECK(!c_functions.empty());
// All function overloads have the same number of arguments and options.
const CFunctionInfo* signature = c_functions[0].signature;
const int argument_count = signature->ArgumentCount();
for (size_t i = 1; i < c_functions.size(); i++) {
CHECK_NOT_NULL(c_functions[i].signature);
DCHECK_EQ(c_functions[i].signature->ArgumentCount(), argument_count);
DCHECK_EQ(c_functions[i].signature->HasOptions(),
c_functions[0].signature->HasOptions());
}
int value_input_count =
(signature->ArgumentCount() +
FastApiCallNode::kFastTargetInputCount) + // fast call
argument_count +
static_cast<int>(descriptor->ParameterCount()) + // slow call
FastApiCallNode::kEffectAndControlInputCount;
return zone()->New<Operator1<FastApiCallParameters>>(
IrOpcode::kFastApiCall, Operator::kNoThrow, "FastApiCall",
value_input_count, 1, 1, 1, 1, 0,
FastApiCallParameters(signature, feedback, descriptor));
FastApiCallParameters(c_functions, feedback, descriptor));
}
int FastApiCallNode::FastCallExtraInputCount() const {
return kFastTargetInputCount + kEffectAndControlInputCount +
(Parameters().signature()->HasOptions() ? 1 : 0);
const CFunctionInfo* signature = Parameters().c_functions()[0].signature;
CHECK_NOT_NULL(signature);
return kEffectAndControlInputCount + (signature->HasOptions() ? 1 : 0);
}
int FastApiCallNode::FastCallArgumentCount() const {
FastApiCallParameters p = FastApiCallParametersOf(node()->op());
const CFunctionInfo* signature = p.signature();
const CFunctionInfo* signature = p.c_functions()[0].signature;
CHECK_NOT_NULL(signature);
return signature->ArgumentCount();
}

View File

@ -696,19 +696,33 @@ std::ostream& operator<<(std::ostream&, const NewArgumentsElementsParameters&);
const NewArgumentsElementsParameters& NewArgumentsElementsParametersOf(
const Operator*) V8_WARN_UNUSED_RESULT;
struct FastApiCallFunction {
Address address;
const CFunctionInfo* signature;
bool operator==(const FastApiCallFunction& rhs) const {
return address == rhs.address && signature == rhs.signature;
}
};
typedef ZoneVector<FastApiCallFunction> FastApiCallFunctionVector;
class FastApiCallParameters {
public:
explicit FastApiCallParameters(const CFunctionInfo* signature,
explicit FastApiCallParameters(const FastApiCallFunctionVector& c_functions,
FeedbackSource const& feedback,
CallDescriptor* descriptor)
: signature_(signature), feedback_(feedback), descriptor_(descriptor) {}
: c_functions_(c_functions),
feedback_(feedback),
descriptor_(descriptor) {}
const CFunctionInfo* signature() const { return signature_; }
const FastApiCallFunctionVector& c_functions() const { return c_functions_; }
FeedbackSource const& feedback() const { return feedback_; }
CallDescriptor* descriptor() const { return descriptor_; }
private:
const CFunctionInfo* signature_;
// A single FastApiCall node can represent multiple overloaded functions.
const FastApiCallFunctionVector c_functions_;
const FeedbackSource feedback_;
CallDescriptor* descriptor_;
};
@ -1094,9 +1108,9 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* DateNow();
// Represents the inputs necessary to construct a fast and a slow API call.
const Operator* FastApiCall(const CFunctionInfo* signature,
FeedbackSource const& feedback,
CallDescriptor* descriptor);
const Operator* FastApiCall(
const FastApiCallFunctionVector& c_candidate_functions,
FeedbackSource const& feedback, CallDescriptor* descriptor);
private:
Zone* zone() const { return zone_; }
@ -1156,19 +1170,15 @@ class FastApiCallNode final : public SimplifiedNodeWrapperBase {
return FastApiCallParametersOf(node()->op());
}
#define INPUTS(V) \
V(Target, target, 0, Object) \
V(Receiver, receiver, 1, Object)
#define INPUTS(V) V(Receiver, receiver, 0, Object)
INPUTS(DEFINE_INPUT_ACCESSORS)
#undef INPUTS
// Besides actual arguments, FastApiCall nodes also take:
static constexpr int kFastTargetInputCount = 1;
static constexpr int kSlowTargetInputCount = 1;
static constexpr int kFastReceiverInputCount = 1;
static constexpr int kSlowReceiverInputCount = 1;
static constexpr int kExtraInputCount =
kFastTargetInputCount + kFastReceiverInputCount;
static constexpr int kExtraInputCount = kFastReceiverInputCount;
static constexpr int kArityInputCount = 1;
static constexpr int kNewTargetInputCount = 1;
@ -1185,8 +1195,7 @@ class FastApiCallNode final : public SimplifiedNodeWrapperBase {
// This is the arity fed into FastApiCallArguments.
static constexpr int ArityForArgc(int c_arg_count, int js_arg_count) {
return c_arg_count + kFastTargetInputCount + js_arg_count +
kEffectAndControlInputCount;
return c_arg_count + js_arg_count + kEffectAndControlInputCount;
}
int FastCallArgumentCount() const;
@ -1204,9 +1213,7 @@ class FastApiCallNode final : public SimplifiedNodeWrapperBase {
NodeProperties::GetValueInput(node(), FastCallArgumentIndex(i)));
}
int FirstSlowCallArgumentIndex() const {
return FastCallArgumentCount() + FastApiCallNode::kFastTargetInputCount;
}
int FirstSlowCallArgumentIndex() const { return FastCallArgumentCount(); }
int SlowCallArgumentIndex(int i) const {
return FirstSlowCallArgumentIndex() + i;
}

View File

@ -1627,9 +1627,8 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckTypeIs(node, Type::BigInt());
break;
case IrOpcode::kFastApiCall:
CHECK_GE(value_count, 2);
CheckValueInputIs(node, 0, Type::ExternalPointer()); // callee
CheckValueInputIs(node, 1, Type::Any()); // receiver
CHECK_GE(value_count, 1);
CheckValueInputIs(node, 0, Type::Any()); // receiver
break;
#if V8_ENABLE_WEBASSEMBLY
case IrOpcode::kJSWasmCall:

View File

@ -148,6 +148,12 @@ class FastCApiObject {
isolate->ThrowError("This method expects at least 2 arguments.");
return;
}
if (args[1]->IsTypedArray()) {
// Not supported yet.
Type dummy_result = 0;
args.GetReturnValue().Set(Number::New(isolate, dummy_result));
return;
}
if (!args[1]->IsArray()) {
isolate->ThrowError("This method expects an array as a second argument.");
return;
@ -176,6 +182,36 @@ class FastCApiObject {
args.GetReturnValue().Set(Number::New(isolate, sum));
}
// TODO(mslekova) - The typed array param should be a
// {size_t length, uint32_t* data}
static Type AddAllTypedArrayFastCallback(Local<Object> receiver,
bool should_fallback,
Local<Uint32Array> typed_array_arg,
FastApiCallbackOptions& options) {
FastCApiObject* self = UnwrapObject(receiver);
CHECK_SELF_OR_FALLBACK(0);
self->fast_call_count_++;
if (should_fallback) {
options.fallback = 1;
return 0;
}
// Not implemented.
return 0;
}
static void AddAllTypedArraySlowCallback(
const FunctionCallbackInfo<Value>& args) {
// Not implemented.
}
static int32_t AddAllIntInvalidCallback(Local<Object> receiver,
bool should_fallback, int32_t arg_i32,
FastApiCallbackOptions& options) {
// This should never be called
UNREACHABLE();
}
static int Add32BitIntFastCallback(v8::Local<v8::Object> receiver,
bool should_fallback, int32_t arg_i32,
uint32_t arg_u32,
@ -436,6 +472,39 @@ Local<FunctionTemplate> Shell::CreateTestFastCApiTemplate(Isolate* isolate) {
signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &add_all_seq_c_func));
CFunction add_all_typed_array_c_func =
CFunction::Make(FastCApiObject::AddAllTypedArrayFastCallback);
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "add_all_typed_array",
FunctionTemplate::New(
isolate, FastCApiObject::AddAllTypedArraySlowCallback,
Local<Value>(), signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, &add_all_typed_array_c_func));
const CFunction add_all_overloads[] = {
add_all_typed_array_c_func,
add_all_seq_c_func,
};
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "add_all_overload",
FunctionTemplate::NewWithCFunctionOverloads(
isolate, FastCApiObject::AddAllSequenceSlowCallback, Local<Value>(),
signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, {add_all_overloads, 2}));
CFunction add_all_int_invalid_func =
CFunction::Make(FastCApiObject::AddAllIntInvalidCallback);
const CFunction add_all_invalid_overloads[] = {
add_all_int_invalid_func,
add_all_seq_c_func,
};
api_obj_ctor->PrototypeTemplate()->Set(
isolate, "add_all_invalid_overload",
FunctionTemplate::NewWithCFunctionOverloads(
isolate, FastCApiObject::AddAllSequenceSlowCallback, Local<Value>(),
signature, 1, ConstructorBehavior::kThrow,
SideEffectType::kHasSideEffect, {add_all_invalid_overloads, 2}));
CFunction add_all_32bit_int_6args_c_func =
CFunction::Make(FastCApiObject::AddAll32BitIntFastCallback_6Args);
CFunction add_all_32bit_int_5args_c_func =

View File

@ -126,8 +126,8 @@ assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
// ----------- Test various signature mismatches -----------
function add_32bit_int_mismatch(arg0, arg1, arg2, arg3) {
return fast_c_api.add_32bit_int(arg0, arg1, arg2, arg3);
function add_32bit_int_mismatch(arg0, arg1, arg2) {
return fast_c_api.add_32bit_int(arg0, arg1, arg2);
}
%PrepareFunctionForOptimization(add_32bit_int_mismatch);
@ -179,32 +179,40 @@ assertOptimized(add_32bit_int_mismatch);
assertEquals(1, fast_c_api.fast_call_count());
assertEquals(0, fast_c_api.slow_call_count());
// Test function overloads
// Test function overloads with different arity.
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;
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 () {
function overloaded_add_all(should_fallback = false) {
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);
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);
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);
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];
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);
let result_5args_with_undefined = 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, undefined);
return [result_under, result_5args, result_6args, result_over,
result_5args_with_undefined];
}
%PrepareFunctionForOptimization(overloaded_add_all);
@ -213,6 +221,7 @@ 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]);
assertEquals(add_all_32bit_int_result_4args, result[4]);
fast_c_api.reset_counts();
%OptimizeFunctionOnNextCall(overloaded_add_all);
@ -220,10 +229,12 @@ 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(4, 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]);
assertEquals(add_all_32bit_int_result_4args, result[4]);
})();

View File

@ -63,8 +63,7 @@ if (fast_c_api.supports_fp_params) {
}
function add_all_sequence_mismatch(arg) {
return fast_c_api.add_all_sequence(false /*should_fallback*/,
arg);
return fast_c_api.add_all_sequence(false /*should_fallback*/, arg);
}
%PrepareFunctionForOptimization(add_all_sequence_mismatch);
@ -95,3 +94,106 @@ assertThrows(() => add_all_sequence_mismatch(Symbol()));
assertOptimized(add_all_sequence_mismatch);
assertEquals(0, fast_c_api.fast_call_count());
assertEquals(1, fast_c_api.slow_call_count());
//----------- Test function overloads with same arity. -----------
//Only overloads between JSArray and TypedArray are supported
// Test with TypedArray.
(function () {
function overloaded_test(should_fallback = false) {
let typed_array = new Uint32Array([1, 2, 3]);
return fast_c_api.add_all_overload(false /* should_fallback */,
typed_array);
}
%PrepareFunctionForOptimization(overloaded_test);
let result = overloaded_test();
assertEquals(0, result);
fast_c_api.reset_counts();
%OptimizeFunctionOnNextCall(overloaded_test);
result = overloaded_test();
assertEquals(0, result);
assertOptimized(overloaded_test);
assertEquals(1, fast_c_api.fast_call_count());
})();
// Mismatched TypedArray.
(function () {
function overloaded_test(should_fallback = false) {
let typed_array = new Float32Array([1.1, 2.2, 3.3]);
return fast_c_api.add_all_overload(false /* should_fallback */,
typed_array);
}
%PrepareFunctionForOptimization(overloaded_test);
let result = overloaded_test();
assertEquals(0, result);
fast_c_api.reset_counts();
%OptimizeFunctionOnNextCall(overloaded_test);
result = overloaded_test();
assertEquals(0, result);
assertOptimized(overloaded_test);
assertEquals(0, fast_c_api.fast_call_count());
})();
// Test with JSArray.
(function () {
function overloaded_test(should_fallback = false) {
let js_array = [26, -6, 42];
return fast_c_api.add_all_overload(false /* should_fallback */, js_array);
}
%PrepareFunctionForOptimization(overloaded_test);
let result = overloaded_test();
assertEquals(62, result);
fast_c_api.reset_counts();
%OptimizeFunctionOnNextCall(overloaded_test);
result = overloaded_test();
assertEquals(62, result);
assertOptimized(overloaded_test);
assertEquals(1, fast_c_api.fast_call_count());
})();
// Test function overloads with undefined.
(function () {
function overloaded_test(should_fallback = false) {
return fast_c_api.add_all_overload(false /* should_fallback */, undefined);
}
%PrepareFunctionForOptimization(overloaded_test);
assertThrows(() => overloaded_test());
fast_c_api.reset_counts();
%OptimizeFunctionOnNextCall(overloaded_test);
assertThrows(() => overloaded_test());
assertOptimized(overloaded_test);
assertEquals(0, fast_c_api.fast_call_count());
})();
// Test function with invalid overloads.
(function () {
function overloaded_test(should_fallback = false) {
return fast_c_api.add_all_invalid_overload(false /* should_fallback */,
[26, -6, 42]);
}
%PrepareFunctionForOptimization(overloaded_test);
result = overloaded_test();
assertEquals(62, result);
fast_c_api.reset_counts();
%OptimizeFunctionOnNextCall(overloaded_test);
result = overloaded_test();
assertEquals(62, result);
// Here we deopt because with this invalid overload:
// - add_all_int_invalid_func(Receiver, Bool, Int32, Options)
// - add_all_seq_c_func(Receiver, Bool, JSArray, Options)
// we expect that a number will be passed as 3rd argument
// (SimplifiedLowering takes the type from the first overloaded function).
assertUnoptimized(overloaded_test);
assertEquals(0, fast_c_api.fast_call_count());
})();