Reland "[turbofan] Fast API calls from TurboFan"

Relanding the Fast C API code with fix for UBSan undefined behavior
issue.

Design doc:
http://doc/1SAHn7d8M7CoazTd1laVF8gduFC_ikZWiYuytrR9c4Oc/

This CL implements basic API with integer and pointer types marshaling.

What is not supported yet:
- sequences
- annotations
- floating point arguments
- 64-bit arguments
- exception handling
- InstanceOf checks for the pointer types
- functions with non-void return type

Bug: chromium:1052746

TBR=yangguo@chromium.org,mvstanton@chromium.org,neis@chromium.org,leszeks@chromium.org,verwaest@chromium.org,mslekova@chromium.org

Change-Id: Ifca9de3156cf18c9dac0d14c19f8d6a7004cad83
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2066971
Reviewed-by: Michael Stanton <mvstanton@chromium.org>
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Commit-Queue: Michael Stanton <mvstanton@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66391}
This commit is contained in:
Mike Stanton 2020-02-21 15:46:07 +01:00 committed by Commit Bot
parent 8199a7ac23
commit 4f28e6d9a1
23 changed files with 1130 additions and 27 deletions

View File

@ -1715,6 +1715,7 @@ v8_header_set("v8_headers") {
public_configs = [ ":v8_header_features" ]
sources = [
"include/v8-fast-api-calls.h",
"include/v8-internal.h",
"include/v8.h",
"include/v8config.h",
@ -2036,6 +2037,7 @@ v8_source_set("v8_base_without_compiler") {
### gcmole(all) ###
"$target_gen_dir/builtins-generated/bytecodes-builtins-list.h",
"include/v8-fast-api-calls.h",
"include/v8-inspector-protocol.h",
"include/v8-inspector.h",
"include/v8-internal.h",

404
include/v8-fast-api-calls.h Normal file
View File

@ -0,0 +1,404 @@
// Copyright 2020 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.
/**
* This file provides additional API on top of the default one for making
* API calls, which come from embedder C++ functions. The functions are being
* called directly from optimized code, doing all the necessary typechecks
* in the compiler itself, instead of on the embedder side. Hence the "fast"
* in the name. Example usage might look like:
*
* \code
* void FastMethod(int param, bool another_param);
*
* v8::FunctionTemplate::New(isolate, SlowCallback, data,
* signature, length, constructor_behavior
* side_effect_type,
* &v8::CFunction::Make(FastMethod));
* \endcode
*
* An example for custom embedder type support might employ a way to wrap/
* unwrap various C++ types in JSObject instances, e.g:
*
* \code
*
* // Represents the way this type system maps C++ and JS values.
* struct WrapperTypeInfo {
* // Store e.g. a method to map from exposed C++ types to the already
* // created v8::FunctionTemplate's for instantiating them.
* };
*
* // Helper method with a sanity check.
* template <typename T, int offset>
* inline T* GetInternalField(v8::Local<v8::Object> wrapper) {
* assert(offset < wrapper->InternalFieldCount());
* return reinterpret_cast<T*>(
* wrapper->GetAlignedPointerFromInternalField(offset));
* }
*
* // Returns the type info from a wrapper JS object.
* inline const WrapperTypeInfo* ToWrapperTypeInfo(
* v8::Local<v8::Object> wrapper) {
* return GetInternalField<WrapperTypeInfo,
* kV8EmbedderWrapperTypeIndex>(wrapper);
* }
*
* class CustomEmbedderType {
* public:
* static constexpr const WrapperTypeInfo* GetWrapperTypeInfo() {
* return &custom_type_wrapper_type_info;
* }
* // Returns the raw C object from a wrapper JS object.
* static CustomEmbedderType* Unwrap(v8::Local<v8::Object> wrapper) {
* return GetInternalField<CustomEmbedderType,
* kV8EmbedderWrapperObjectIndex>(wrapper);
* }
* static void FastMethod(CustomEmbedderType* receiver, int param) {
* assert(receiver != nullptr);
* // Type checks are already done by the optimized code.
* // Then call some performance-critical method like:
* // receiver->Method(param);
* }
*
* static void SlowMethod(
* const v8::FunctionCallbackInfo<v8::Value>& info) {
* v8::Local<v8::Object> instance =
* v8::Local<v8::Object>::Cast(info.Holder());
* CustomEmbedderType* receiver = Unwrap(instance);
* // TODO: Do type checks and extract {param}.
* FastMethod(receiver, param);
* }
*
* private:
* static const WrapperTypeInfo custom_type_wrapper_type_info;
* };
*
* // Support for custom embedder types via specialization of WrapperTraits.
* namespace v8 {
* template <>
* class WrapperTraits<CustomEmbedderType> {
* public:
* static const void* GetTypeInfo() {
* // We use the already defined machinery for the custom type.
* return CustomEmbedderType::GetWrapperTypeInfo();
* }
* };
* } // namespace v8
*
* // The constants kV8EmbedderWrapperTypeIndex and
* // kV8EmbedderWrapperObjectIndex describe the offsets for the type info
* // struct (the one returned by WrapperTraits::GetTypeInfo) and the
* // native object, when expressed as internal field indices within a
* // JSObject. The existance of this helper function assumes that all
* // embedder objects have their JSObject-side type info at the same
* // offset, but this is not a limitation of the API itself. For a detailed
* // use case, see the third example.
* static constexpr int kV8EmbedderWrapperTypeIndex = 0;
* static constexpr int kV8EmbedderWrapperObjectIndex = 1;
*
* // The following setup function can be templatized based on
* // the {embedder_object} argument.
* void SetupCustomEmbedderObject(v8::Isolate* isolate,
* v8::Local<v8::Context> context,
* CustomEmbedderType* embedder_object) {
* isolate->set_embedder_wrapper_type_index(
* kV8EmbedderWrapperTypeIndex);
* isolate->set_embedder_wrapper_object_index(
* kV8EmbedderWrapperObjectIndex);
*
* v8::CFunction c_func =
* MakeV8CFunction(CustomEmbedderType::FastMethod);
*
* Local<v8::FunctionTemplate> method_template =
* v8::FunctionTemplate::New(
* isolate, CustomEmbedderType::SlowMethod, v8::Local<v8::Value>(),
* v8::Local<v8::Signature>(), 1, v8::ConstructorBehavior::kAllow,
* v8::SideEffectType::kHasSideEffect, &c_func);
*
* v8::Local<v8::ObjectTemplate> object_template =
* v8::ObjectTemplate::New(isolate);
* object_template->SetInternalFieldCount(
* kV8EmbedderWrapperObjectIndex + 1);
* object_template->Set(v8::String::NewFromUtf8(isolate, "method",
* v8::NewStringType::kNormal)
* .ToLocalChecked(), method_template);
*
* // Instantiate the wrapper JS object.
* v8::Local<v8::Object> object =
* object_template->NewInstance(context).ToLocalChecked();
* object->SetAlignedPointerInInternalField(
* kV8EmbedderWrapperObjectIndex,
* reinterpret_cast<void*>(embedder_object));
*
* // TODO: Expose {object} where it's necessary.
* }
* \endcode
*
* For instance if {object} is exposed via a global "obj" variable,
* one could write in JS:
* function hot_func() {
* obj.method(42);
* }
* and once {hot_func} gets optimized, CustomEmbedderType::FastMethod
* will be called instead of the slow version, with the following arguments:
* receiver := the {embedder_object} from above
* param := 42
*
* Currently only void return types are supported.
* Currently supported argument types:
* - pointer to an embedder type
* - bool
* - int32_t
* - uint32_t
* To be supported types:
* - int64_t
* - uint64_t
* - float32_t
* - float64_t
* - arrays of C types
* - arrays of embedder types
*/
#ifndef INCLUDE_V8_FAST_API_CALLS_H_
#define INCLUDE_V8_FAST_API_CALLS_H_
#include <stddef.h>
#include <stdint.h>
#include "v8config.h" // NOLINT(build/include)
namespace v8 {
class CTypeInfo {
public:
enum class Type : char {
kVoid,
kBool,
kInt32,
kUint32,
kInt64,
kUint64,
kFloat32,
kFloat64,
kUnwrappedApiObject,
};
enum ArgFlags : char {
None = 0,
IsArrayBit = 1 << 0, // This argument is first in an array of values.
};
static CTypeInfo FromWrapperType(const void* wrapper_type_info,
ArgFlags flags = ArgFlags::None) {
uintptr_t wrapper_type_info_ptr =
reinterpret_cast<uintptr_t>(wrapper_type_info);
// Check that the lower kIsWrapperTypeBit bits are 0's.
CHECK_EQ(
wrapper_type_info_ptr & ~(static_cast<uintptr_t>(~0)
<< static_cast<uintptr_t>(kIsWrapperTypeBit)),
0);
// TODO(mslekova): Refactor the manual bit manipulations to use
// PointerWithPayload instead.
return CTypeInfo(wrapper_type_info_ptr | flags | kIsWrapperTypeBit);
}
static constexpr CTypeInfo FromCType(Type ctype,
ArgFlags flags = ArgFlags::None) {
// ctype cannot be Type::kUnwrappedApiObject.
return CTypeInfo(
((static_cast<uintptr_t>(ctype) << kTypeOffset) & kTypeMask) | flags);
}
const void* GetWrapperInfo() const;
constexpr Type GetType() const {
if (payload_ & kIsWrapperTypeBit) {
return Type::kUnwrappedApiObject;
}
return static_cast<Type>((payload_ & kTypeMask) >> kTypeOffset);
}
constexpr bool IsArray() const { return payload_ & ArgFlags::IsArrayBit; }
private:
explicit constexpr CTypeInfo(uintptr_t payload) : payload_(payload) {}
// That must be the last bit after ArgFlags.
static constexpr uintptr_t kIsWrapperTypeBit = 1 << 1;
static constexpr uintptr_t kWrapperTypeInfoMask = static_cast<uintptr_t>(~0)
<< 2;
static constexpr unsigned int kTypeOffset = kIsWrapperTypeBit;
static constexpr unsigned int kTypeSize = 8 - kTypeOffset;
static constexpr uintptr_t kTypeMask =
(~(static_cast<uintptr_t>(~0) << kTypeSize)) << kTypeOffset;
const uintptr_t payload_;
};
class CFunctionInfo {
public:
virtual const CTypeInfo& ReturnInfo() const = 0;
virtual unsigned int ArgumentCount() const = 0;
virtual const CTypeInfo* ArgumentInfo() const = 0;
};
template <typename T>
class WrapperTraits {
public:
static const void* GetTypeInfo() {
static_assert(sizeof(T) != sizeof(T),
"WrapperTraits must be specialized for this type.");
return nullptr;
}
};
namespace internal {
template <typename T>
struct GetCType {
static_assert(sizeof(T) != sizeof(T), "Unsupported CType");
};
#define SPECIALIZE_GET_C_TYPE_FOR(ctype, ctypeinfo) \
template <> \
struct GetCType<ctype> { \
static constexpr CTypeInfo Get() { \
return CTypeInfo::FromCType(CTypeInfo::Type::ctypeinfo); \
} \
};
#define SUPPORTED_C_TYPES(V) \
V(void, kVoid) \
V(bool, kBool) \
V(int32_t, kInt32) \
V(uint32_t, kUint32) \
V(int64_t, kInt64) \
V(uint64_t, kUint64) \
V(float, kFloat32) \
V(double, kFloat64)
SUPPORTED_C_TYPES(SPECIALIZE_GET_C_TYPE_FOR)
template <typename T, typename = void>
struct EnableIfHasWrapperTypeInfo {};
template <>
struct EnableIfHasWrapperTypeInfo<void> {};
template <typename T>
struct EnableIfHasWrapperTypeInfo<T, decltype(WrapperTraits<T>::GetTypeInfo(),
void())> {
typedef void type;
};
// T* where T is a primitive (array of primitives).
template <typename T, typename = void>
struct GetCTypePointerImpl {
static constexpr CTypeInfo Get() {
return CTypeInfo::FromCType(GetCType<T>::Get().GetType(),
CTypeInfo::IsArrayBit);
}
};
// T* where T is an API object.
template <typename T>
struct GetCTypePointerImpl<T, typename EnableIfHasWrapperTypeInfo<T>::type> {
static constexpr CTypeInfo Get() {
return CTypeInfo::FromWrapperType(WrapperTraits<T>::GetTypeInfo());
}
};
// T** where T is a primitive. Not allowed.
template <typename T, typename = void>
struct GetCTypePointerPointerImpl {
static_assert(sizeof(T**) != sizeof(T**), "Unsupported type");
};
// T** where T is an API object (array of API objects).
template <typename T>
struct GetCTypePointerPointerImpl<
T, typename EnableIfHasWrapperTypeInfo<T>::type> {
static constexpr CTypeInfo Get() {
return CTypeInfo::FromWrapperType(WrapperTraits<T>::GetTypeInfo(),
CTypeInfo::IsArrayBit);
}
};
template <typename T>
struct GetCType<T**> : public GetCTypePointerPointerImpl<T> {};
template <typename T>
struct GetCType<T*> : public GetCTypePointerImpl<T> {};
template <typename R, typename... Args>
class CFunctionInfoImpl : public CFunctionInfo {
public:
CFunctionInfoImpl()
: return_info_(i::GetCType<R>::Get()),
arg_count_(sizeof...(Args)),
arg_info_{i::GetCType<Args>::Get()...} {
static_assert(i::GetCType<R>::Get().GetType() == CTypeInfo::Type::kVoid,
"Only void return types are currently supported.");
}
const CTypeInfo& ReturnInfo() const override { return return_info_; }
unsigned int ArgumentCount() const override { return arg_count_; }
const CTypeInfo* ArgumentInfo() const override { return arg_info_; }
private:
CTypeInfo return_info_;
const unsigned int arg_count_;
CTypeInfo arg_info_[sizeof...(Args)];
};
} // namespace internal
class V8_EXPORT CFunction {
public:
const CTypeInfo& ReturnInfo() const { return type_info_->ReturnInfo(); }
const CTypeInfo* ArgumentInfo() const { return type_info_->ArgumentInfo(); }
unsigned int ArgumentCount() const { return type_info_->ArgumentCount(); }
const void* GetAddress() const { return address_; }
const CFunctionInfo* GetTypeInfo() const { return type_info_; }
template <typename F>
static CFunction Make(F* func) {
return ArgUnwrap<F*>::Make(func);
}
private:
const void* address_;
const CFunctionInfo* type_info_;
CFunction(const void* address, const CFunctionInfo* type_info);
template <typename R, typename... Args>
static CFunctionInfo* GetCFunctionInfo() {
static internal::CFunctionInfoImpl<R, Args...> instance;
return &instance;
}
template <typename F>
class ArgUnwrap {
static_assert(sizeof(F) != sizeof(F),
"CFunction must be created from a function pointer.");
};
template <typename R, typename... Args>
class ArgUnwrap<R (*)(Args...)> {
public:
static CFunction Make(R (*func)(Args...)) {
return CFunction(reinterpret_cast<const void*>(func),
GetCFunctionInfo<R, Args...>());
}
};
};
} // namespace v8
#endif // INCLUDE_V8_FAST_API_CALLS_H_

View File

@ -6284,6 +6284,7 @@ typedef bool (*AccessCheckCallback)(Local<Context> accessing_context,
Local<Object> accessed_object,
Local<Value> data);
class CFunction;
/**
* A FunctionTemplate is used to create functions at runtime. There
* can only be one function created from a FunctionTemplate in a
@ -6383,6 +6384,12 @@ typedef bool (*AccessCheckCallback)(Local<Context> accessing_context,
* child_instance.instance_accessor calls 'InstanceAccessorCallback'
* child_instance.instance_property == 3;
* \endcode
*
* The additional 'c_function' parameter refers to a fast API call, which
* must not trigger GC or JavaScript execution, or call into V8 in other
* ways. For more information how to define them, see
* include/v8-fast-api-calls.h. Please note that this feature is still
* experimental.
*/
class V8_EXPORT FunctionTemplate : public Template {
public:
@ -6392,7 +6399,8 @@ class V8_EXPORT FunctionTemplate : public Template {
Local<Value> data = Local<Value>(),
Local<Signature> signature = Local<Signature>(), int length = 0,
ConstructorBehavior behavior = ConstructorBehavior::kAllow,
SideEffectType side_effect_type = SideEffectType::kHasSideEffect);
SideEffectType side_effect_type = SideEffectType::kHasSideEffect,
const CFunction* c_function = nullptr);
/** Get a template included in the snapshot by index. */
V8_DEPRECATED("Use v8::Isolate::GetDataFromSnapshotOnce instead")
@ -6424,11 +6432,13 @@ class V8_EXPORT FunctionTemplate : public Template {
/**
* Set the call-handler callback for a FunctionTemplate. This
* callback is called whenever the function created from this
* FunctionTemplate is called.
* FunctionTemplate is called. The 'c_function' represents a fast
* API call, see the comment above the class declaration.
*/
void SetCallHandler(
FunctionCallback callback, Local<Value> data = Local<Value>(),
SideEffectType side_effect_type = SideEffectType::kHasSideEffect);
SideEffectType side_effect_type = SideEffectType::kHasSideEffect,
const CFunction* c_function = nullptr);
/** Set the predefined length property for the FunctionTemplate. */
void SetLength(int length);

View File

@ -13,6 +13,7 @@
#include "src/api/api-inl.h"
#include "include/v8-fast-api-calls.h"
#include "include/v8-profiler.h"
#include "include/v8-util.h"
#include "src/api/api-natives.h"
@ -1471,7 +1472,8 @@ static Local<FunctionTemplate> FunctionTemplateNew(
i::Isolate* isolate, FunctionCallback callback, v8::Local<Value> data,
v8::Local<Signature> signature, int length, bool do_not_cache,
v8::Local<Private> cached_property_name = v8::Local<Private>(),
SideEffectType side_effect_type = SideEffectType::kHasSideEffect) {
SideEffectType side_effect_type = SideEffectType::kHasSideEffect,
const CFunction* c_function = nullptr) {
i::Handle<i::Struct> struct_obj = isolate->factory()->NewStruct(
i::FUNCTION_TEMPLATE_INFO_TYPE, i::AllocationType::kOld);
i::Handle<i::FunctionTemplateInfo> obj =
@ -1489,7 +1491,8 @@ static Local<FunctionTemplate> FunctionTemplateNew(
obj->set_serial_number(i::Smi::FromInt(next_serial_number));
}
if (callback != nullptr) {
Utils::ToLocal(obj)->SetCallHandler(callback, data, side_effect_type);
Utils::ToLocal(obj)->SetCallHandler(callback, data, side_effect_type,
c_function);
}
obj->set_undetectable(false);
obj->set_needs_access_check(false);
@ -1507,14 +1510,15 @@ static Local<FunctionTemplate> FunctionTemplateNew(
Local<FunctionTemplate> FunctionTemplate::New(
Isolate* isolate, FunctionCallback callback, v8::Local<Value> data,
v8::Local<Signature> signature, int length, ConstructorBehavior behavior,
SideEffectType side_effect_type) {
SideEffectType side_effect_type, const CFunction* c_function) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
// Changes to the environment cannot be captured in the snapshot. Expect no
// function templates when the isolate is created for serialization.
LOG_API(i_isolate, FunctionTemplate, New);
ENTER_V8_NO_SCRIPT_NO_EXCEPTION(i_isolate);
auto templ = FunctionTemplateNew(i_isolate, callback, data, signature, length,
false, Local<Private>(), side_effect_type);
auto templ =
FunctionTemplateNew(i_isolate, callback, data, signature, length, false,
Local<Private>(), side_effect_type, c_function);
if (behavior == ConstructorBehavior::kThrow) templ->RemovePrototype();
return templ;
}
@ -1563,7 +1567,8 @@ Local<AccessorSignature> AccessorSignature::New(
void FunctionTemplate::SetCallHandler(FunctionCallback callback,
v8::Local<Value> data,
SideEffectType side_effect_type) {
SideEffectType side_effect_type,
const CFunction* c_function) {
auto info = Utils::OpenHandle(this);
EnsureNotInstantiated(info, "v8::FunctionTemplate::SetCallHandler");
i::Isolate* isolate = info->GetIsolate();
@ -1577,6 +1582,15 @@ void FunctionTemplate::SetCallHandler(FunctionCallback callback,
data = v8::Undefined(reinterpret_cast<v8::Isolate*>(isolate));
}
obj->set_data(*Utils::OpenHandle(*data));
if (c_function != nullptr) {
DCHECK_NOT_NULL(c_function->GetAddress());
i::FunctionTemplateInfo::SetCFunction(
isolate, info,
i::handle(*FromCData(isolate, c_function->GetAddress()), isolate));
i::FunctionTemplateInfo::SetCSignature(
isolate, info,
i::handle(*FromCData(isolate, c_function->GetTypeInfo()), isolate));
}
info->set_call_code(*obj);
}
@ -10866,6 +10880,34 @@ void EmbedderHeapTracer::ResetHandleInNonTracingGC(
UNREACHABLE();
}
const void* CTypeInfo::GetWrapperInfo() const {
DCHECK(payload_ & kWrapperTypeInfoMask);
return reinterpret_cast<const void*>(payload_ & kWrapperTypeInfoMask);
}
CFunction::CFunction(const void* address, const CFunctionInfo* type_info)
: address_(address), type_info_(type_info) {
CHECK_NOT_NULL(address_);
CHECK_NOT_NULL(type_info_);
for (size_t i = 0; i < type_info_->ArgumentCount(); ++i) {
if (type_info_->ArgumentInfo()[i].IsArray()) {
// Array args require an integer passed for their length
// as the next argument.
DCHECK_LT(i + 1, type_info_->ArgumentCount());
switch (type_info_->ArgumentInfo()[i + 1].GetType()) {
case CTypeInfo::Type::kInt32:
case CTypeInfo::Type::kUint32:
case CTypeInfo::Type::kInt64:
case CTypeInfo::Type::kUint64:
break;
default:
UNREACHABLE();
break;
}
}
}
}
namespace internal {
const size_t HandleScopeImplementer::kEnteredContextsOffset =

View File

@ -12,6 +12,8 @@
#include "src/objects/instance-type.h"
namespace v8 {
class CFunctionInfo;
namespace internal {
class BytecodeArray;
@ -659,6 +661,8 @@ class FunctionTemplateInfoRef : public HeapObjectRef {
void SerializeCallCode();
base::Optional<CallHandlerInfoRef> call_code() const;
Address c_function() const;
const CFunctionInfo* c_signature() const;
HolderLookupResult LookupHolderOfExpectedType(
MapRef receiver_map,

View File

@ -6,7 +6,9 @@
#include <functional>
#include "include/v8-fast-api-calls.h"
#include "src/api/api-inl.h"
#include "src/base/small-vector.h"
#include "src/builtins/builtins-promise.h"
#include "src/builtins/builtins-utils.h"
#include "src/codegen/code-factory.h"
@ -864,6 +866,88 @@ class PromiseBuiltinReducerAssembler : public JSCallReducerAssembler {
}
};
class FastApiCallReducerAssembler : public JSCallReducerAssembler {
public:
FastApiCallReducerAssembler(JSGraph* jsgraph, Zone* zone, Node* node,
Address c_function,
const CFunctionInfo* c_signature)
: JSCallReducerAssembler(jsgraph, zone, node),
c_function_(c_function),
c_signature_(c_signature) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
DCHECK_NE(c_function_, kNullAddress);
CHECK_NOT_NULL(c_signature_);
}
TNode<Object> ReduceFastApiCall() {
int c_arg_count = c_signature_->ArgumentCount();
Node* function_node =
ExternalConstant(ExternalReference::Create(c_function_));
base::SmallVector<Node*, kInlineSize + kExtraInputsCount> inputs(0);
inputs.emplace_back(function_node);
int wrapper_object_index = isolate()->embedder_wrapper_object_index();
CHECK_GE(wrapper_object_index, 0);
for (int i = 0; i < c_arg_count; ++i) {
if (i + kFunctionArgCount < ValueInputCount()) {
inputs.emplace_back(ConvertArgumentIfJSWrapper(
c_signature_->ArgumentInfo()[i].GetType(),
ValueInput(i + kFunctionArgCount), wrapper_object_index));
} else {
inputs.emplace_back(UndefinedConstant());
}
}
inputs.emplace_back(effect());
inputs.emplace_back(control());
return FastApiCall(inputs);
}
private:
static constexpr int kFunctionArgCount = 1;
static constexpr int kExtraInputsCount =
kFunctionArgCount + 2; // effect, control
static constexpr int kInlineSize = 10;
TNode<Object> FastApiCall(
base::SmallVector<Node*, kInlineSize + kExtraInputsCount> const& inputs) {
return AddNode<Object>(
graph()->NewNode(simplified()->FastApiCall(c_signature_, feedback()),
static_cast<int>(inputs.size()), inputs.begin()));
}
TNode<RawPtrT> UnwrapApiObject(TNode<JSObject> node,
int wrapper_object_index) {
const int offset =
Internals::kJSObjectHeaderSize +
(Internals::kEmbedderDataSlotSize * wrapper_object_index);
FieldAccess access(kTaggedBase, offset, MaybeHandle<Name>(),
MaybeHandle<Map>(), Type::Any(), MachineType::Pointer(),
WriteBarrierKind::kNoWriteBarrier);
TNode<RawPtrT> load = AddNode<RawPtrT>(graph()->NewNode(
simplified()->LoadField(access), node, effect(), control()));
return load;
}
Node* ConvertArgumentIfJSWrapper(CTypeInfo::Type type, TNode<Object> node,
int wrapper_object_index) {
switch (type) {
case CTypeInfo::Type::kUnwrappedApiObject:
// This call assumes that {node} is a JSObject with an internal field
// set to a C pointer. It should fail in all other cases.
// TODO(mslekova): Implement instanceOf check for the C pointer type.
// TODO(mslekova): Introduce a GraphAssembler primitive for safe cast.
return UnwrapApiObject(TNode<JSObject>::UncheckedCast(node),
wrapper_object_index);
default:
return node;
}
}
const Address c_function_;
const CFunctionInfo* const c_signature_;
};
TNode<Number> JSCallReducerAssembler::SpeculativeToNumber(
TNode<Object> value, NumberOperationHint hint) {
return AddNode<Number>(
@ -3368,8 +3452,9 @@ Reduction JSCallReducer::ReduceCallApiFunction(
// See if we can constant-fold the compatible receiver checks.
HolderLookupResult api_holder =
function_template_info.LookupHolderOfExpectedType(first_receiver_map);
if (api_holder.lookup == CallOptimization::kHolderNotFound)
if (api_holder.lookup == CallOptimization::kHolderNotFound) {
return inference.NoChange();
}
// Check that all {receiver_maps} are actually JSReceiver maps and
// that the {function_template_info} accepts them without access
@ -3471,6 +3556,18 @@ Reduction JSCallReducer::ReduceCallApiFunction(
<< function_template_info);
return NoChange();
}
Address c_function = function_template_info.c_function();
if (FLAG_turbo_fast_api_calls && c_function != kNullAddress) {
const CFunctionInfo* c_signature = function_template_info.c_signature();
FastApiCallReducerAssembler a(jsgraph(), graph()->zone(), node, c_function,
c_signature);
Node* c_call = a.ReduceFastApiCall();
ReplaceWithSubgraph(&a, c_call);
return Replace(c_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

@ -9,6 +9,7 @@
#include <algorithm>
#endif
#include "include/v8-fast-api-calls.h"
#include "src/api/api-inl.h"
#include "src/ast/modules.h"
#include "src/codegen/code-factory.h"
@ -226,6 +227,8 @@ class FunctionTemplateInfoData : public HeapObjectData {
void SerializeCallCode(JSHeapBroker* broker);
CallHandlerInfoData* call_code() const { return call_code_; }
Address c_function() const { return c_function_; }
const CFunctionInfo* c_signature() const { return c_signature_; }
KnownReceiversMap& known_receivers() { return known_receivers_; }
private:
@ -234,6 +237,8 @@ class FunctionTemplateInfoData : public HeapObjectData {
bool has_call_code_ = false;
CallHandlerInfoData* call_code_ = nullptr;
const Address c_function_;
const CFunctionInfo* const c_signature_;
KnownReceiversMap known_receivers_;
};
@ -257,6 +262,8 @@ FunctionTemplateInfoData::FunctionTemplateInfoData(
JSHeapBroker* broker, ObjectData** storage,
Handle<FunctionTemplateInfo> object)
: HeapObjectData(broker, storage, object),
c_function_(v8::ToCData<Address>(object->GetCFunction())),
c_signature_(v8::ToCData<CFunctionInfo*>(object->GetCSignature())),
known_receivers_(broker->zone()) {
auto function_template_info = Handle<FunctionTemplateInfo>::cast(object);
is_signature_undefined_ =
@ -3676,6 +3683,20 @@ Address CallHandlerInfoRef::callback() const {
return HeapObjectRef::data()->AsCallHandlerInfo()->callback();
}
Address FunctionTemplateInfoRef::c_function() const {
if (broker()->mode() == JSHeapBroker::kDisabled) {
return v8::ToCData<Address>(object()->GetCFunction());
}
return HeapObjectRef::data()->AsFunctionTemplateInfo()->c_function();
}
const CFunctionInfo* FunctionTemplateInfoRef::c_signature() const {
if (broker()->mode() == JSHeapBroker::kDisabled) {
return v8::ToCData<CFunctionInfo*>(object()->GetCSignature());
}
return HeapObjectRef::data()->AsFunctionTemplateInfo()->c_signature();
}
bool StringRef::IsSeqString() const {
IF_ACCESS_FROM_HEAP_C(String, IsSeqString);
return data()->AsString()->is_seq_string();

View File

@ -19,6 +19,8 @@
#include "src/zone/zone.h"
namespace v8 {
class CFunctionInfo;
namespace internal {
class CallInterfaceDescriptor;
@ -357,6 +359,13 @@ 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;
@ -374,6 +383,7 @@ class V8_EXPORT_PRIVATE CallDescriptor final
const RegList allocatable_registers_;
const Flags flags_;
const char* const debug_name_;
const CFunctionInfo* c_function_info_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(CallDescriptor);
};

View File

@ -475,7 +475,8 @@
V(PoisonIndex) \
V(RuntimeAbort) \
V(AssertType) \
V(DateNow)
V(DateNow) \
V(FastApiCall)
#define SIMPLIFIED_SPECULATIVE_BIGINT_BINOP_LIST(V) \
V(SpeculativeBigIntAdd) \

View File

@ -889,12 +889,12 @@ Node* RepresentationChanger::GetWord32RepresentationFor(
if (use_info.type_check() == TypeCheckKind::kSignedSmall ||
use_info.type_check() == TypeCheckKind::kSigned32 ||
use_info.type_check() == TypeCheckKind::kArrayIndex) {
bool indentify_zeros = use_info.truncation().IdentifiesZeroAndMinusZero();
bool identify_zeros = use_info.truncation().IdentifiesZeroAndMinusZero();
if (output_type.Is(Type::Signed32()) ||
(indentify_zeros && output_type.Is(Type::Signed32OrMinusZero()))) {
(identify_zeros && output_type.Is(Type::Signed32OrMinusZero()))) {
return node;
} else if (output_type.Is(Type::Unsigned32()) ||
(indentify_zeros &&
(identify_zeros &&
output_type.Is(Type::Unsigned32OrMinusZero()))) {
op = simplified()->CheckedUint32ToInt32(use_info.feedback());
} else {

View File

@ -196,6 +196,9 @@ class UseInfo {
static UseInfo Float32() {
return UseInfo(MachineRepresentation::kFloat32, Truncation::Any());
}
static UseInfo Float64() {
return UseInfo(MachineRepresentation::kFloat64, Truncation::Any());
}
static UseInfo TruncatingFloat64(
IdentifyZeros identify_zeros = kDistinguishZeros) {
return UseInfo(MachineRepresentation::kFloat64,

View File

@ -6,8 +6,11 @@
#include <limits>
#include "include/v8-fast-api-calls.h"
#include "src/base/bits.h"
#include "src/base/small-vector.h"
#include "src/codegen/code-factory.h"
#include "src/codegen/machine-type.h"
#include "src/codegen/tick-counter.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/common-operator.h"
@ -1695,6 +1698,102 @@ class RepresentationSelector {
}
}
static MachineType MachineTypeFor(CTypeInfo::Type type) {
switch (type) {
case CTypeInfo::Type::kVoid:
return MachineType::Int32();
case CTypeInfo::Type::kBool:
return MachineType::Bool();
case CTypeInfo::Type::kInt32:
return MachineType::Int32();
case CTypeInfo::Type::kUint32:
return MachineType::Uint32();
case CTypeInfo::Type::kInt64:
return MachineType::Int64();
case CTypeInfo::Type::kUint64:
return MachineType::Uint64();
case CTypeInfo::Type::kFloat32:
return MachineType::Float32();
case CTypeInfo::Type::kFloat64:
return MachineType::Float64();
case CTypeInfo::Type::kUnwrappedApiObject:
return MachineType::Pointer();
}
}
UseInfo UseInfoForFastApiCallArgument(CTypeInfo::Type type,
FeedbackSource const& feedback) {
switch (type) {
case CTypeInfo::Type::kVoid:
UNREACHABLE();
case CTypeInfo::Type::kBool:
return UseInfo::Bool();
case CTypeInfo::Type::kInt32:
case CTypeInfo::Type::kUint32:
case CTypeInfo::Type::kFloat32:
return UseInfo::CheckedNumberAsWord32(feedback);
case CTypeInfo::Type::kInt64:
return UseInfo::CheckedSigned64AsWord64(kIdentifyZeros, feedback);
case CTypeInfo::Type::kFloat64:
return UseInfo::CheckedNumberAsFloat64(kIdentifyZeros, feedback);
// UseInfo::Word64 does not propagate any TypeCheckKind, so it relies
// on the implicit assumption that Word64 representation only holds
// Numbers, which is already no longer true with BigInts. By now,
// BigInts are handled in a very conservative way to make sure they don't
// fall into that pit, but future changes may break this here.
case CTypeInfo::Type::kUint64:
return UseInfo::Word64();
case CTypeInfo::Type::kUnwrappedApiObject:
return UseInfo::Word();
}
}
static constexpr int kInitialArgumentsCount = 10;
void VisitFastApiCall(Node* node) {
FastApiCallParameters const& params = FastApiCallParametersOf(node->op());
const CFunctionInfo* c_signature = params.signature();
int c_arg_count = c_signature->ArgumentCount();
int value_input_count = node->op()->ValueInputCount();
// function, ... C args
CHECK_EQ(c_arg_count + 1, value_input_count);
base::SmallVector<UseInfo, kInitialArgumentsCount> arg_use_info(
value_input_count);
arg_use_info[0] = UseInfo::Word();
// Propagate representation information from TypeInfo.
for (int i = 0; i < value_input_count; i++) {
arg_use_info[i] = UseInfoForFastApiCallArgument(
c_signature->ArgumentInfo()[i - 1].GetType(), params.feedback());
ProcessInput(node, i, arg_use_info[i]);
}
MachineType return_type =
MachineTypeFor(c_signature->ReturnInfo().GetType());
SetOutput(node, return_type.representation());
if (lower()) {
MachineSignature::Builder builder(graph()->zone(), 1, c_arg_count);
builder.AddReturn(return_type);
for (int i = 0; i < c_arg_count; ++i) {
MachineType machine_type =
MachineTypeFor(c_signature->ArgumentInfo()[i].GetType());
// Here the arg_use_info are indexed starting from 1 because of the
// function input, while this loop is only over the actual arguments.
DCHECK_EQ(arg_use_info[i + 1].representation(),
machine_type.representation());
builder.AddParam(machine_type);
}
CallDescriptor* call_descriptor = Linkage::GetSimplifiedCDescriptor(
graph()->zone(), builder.Build(), CallDescriptor::kNoFlags);
call_descriptor->SetCFunctionInfo(c_signature);
NodeProperties::ChangeOp(node, common()->Call(call_descriptor));
}
}
// Dispatching routine for visiting the node {node} with the usage {use}.
// Depending on the operator, propagate new usage info to the inputs.
void VisitNode(Node* node, Truncation truncation,
@ -3557,6 +3656,11 @@ class RepresentationSelector {
return;
}
case IrOpcode::kFastApiCall: {
VisitFastApiCall(node);
return;
}
// Operators with all inputs tagged and no or tagged output have uniform
// handling.
case IrOpcode::kEnd:

View File

@ -4,6 +4,7 @@
#include "src/compiler/simplified-operator.h"
#include "include/v8-fast-api-calls.h"
#include "src/base/lazy-instance.h"
#include "src/compiler/opcodes.h"
#include "src/compiler/operator.h"
@ -1256,6 +1257,16 @@ const Operator* SimplifiedOperatorBuilder::AssertType(Type type) {
"AssertType", 1, 0, 0, 1, 0, 0, type);
}
const Operator* SimplifiedOperatorBuilder::FastApiCall(
const CFunctionInfo* signature, FeedbackSource const& feedback) {
// function, c args
int value_input_count = signature->ArgumentCount() + 1;
return new (zone()) Operator1<FastApiCallParameters>(
IrOpcode::kFastApiCall, Operator::kNoThrow, "FastApiCall",
value_input_count, 1, 1, 1, 1, 0,
FastApiCallParameters(signature, feedback));
}
const Operator* SimplifiedOperatorBuilder::CheckIf(
DeoptimizeReason reason, const FeedbackSource& feedback) {
if (!feedback.IsValid()) {
@ -1679,6 +1690,25 @@ int NewArgumentsElementsMappedCountOf(const Operator* op) {
return OpParameter<int>(op);
}
FastApiCallParameters const& FastApiCallParametersOf(const Operator* op) {
DCHECK_EQ(IrOpcode::kFastApiCall, op->opcode());
return OpParameter<FastApiCallParameters>(op);
}
std::ostream& operator<<(std::ostream& os, FastApiCallParameters const& p) {
return os << p.signature() << ", " << p.feedback();
}
size_t hash_value(FastApiCallParameters const& p) {
return base::hash_combine(p.signature(),
FeedbackSource::Hash()(p.feedback()));
}
bool operator==(FastApiCallParameters const& lhs,
FastApiCallParameters const& rhs) {
return lhs.signature() == rhs.signature() && lhs.feedback() == rhs.feedback();
}
const Operator* SimplifiedOperatorBuilder::Allocate(Type type,
AllocationType allocation) {
return new (zone()) Operator1<AllocateParameters>(

View File

@ -580,6 +580,30 @@ DeoptimizeReason DeoptimizeReasonOf(const Operator* op) V8_WARN_UNUSED_RESULT;
int NewArgumentsElementsMappedCountOf(const Operator* op) V8_WARN_UNUSED_RESULT;
class FastApiCallParameters {
public:
explicit FastApiCallParameters(const CFunctionInfo* signature,
FeedbackSource const& feedback)
: signature_(signature), feedback_(feedback) {}
const CFunctionInfo* signature() const { return signature_; }
FeedbackSource const& feedback() const { return feedback_; }
private:
const CFunctionInfo* signature_;
const FeedbackSource feedback_;
};
FastApiCallParameters const& FastApiCallParametersOf(const Operator* op)
V8_WARN_UNUSED_RESULT;
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream&,
FastApiCallParameters const&);
size_t hash_value(FastApiCallParameters const&);
bool operator==(FastApiCallParameters const&, FastApiCallParameters const&);
// Interface for building simplified operators, which represent the
// medium-level operations of V8, including adding numbers, allocating objects,
// indexing into objects and arrays, etc.
@ -920,6 +944,10 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* DateNow();
// Stores the signature and feedback of a fast C call
const Operator* FastApiCall(const CFunctionInfo* signature,
FeedbackSource const& feedback);
private:
Zone* zone() const { return zone_; }

View File

@ -1005,6 +1005,8 @@ Type Typer::Visitor::TypeTypedObjectState(Node* node) {
Type Typer::Visitor::TypeCall(Node* node) { return Type::Any(); }
Type Typer::Visitor::TypeFastApiCall(Node* node) { return Type::Any(); }
Type Typer::Visitor::TypeProjection(Node* node) {
Type const type = Operand(node, 0);
if (type.Is(Type::None())) return Type::None();

View File

@ -1529,7 +1529,7 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
// Object -> fieldtype
// TODO(rossberg): activate once machine ops are typed.
// CheckValueInputIs(node, 0, Type::Object());
// CheckTypeIs(node, FieldAccessOf(node->op()).type));
// CheckTypeIs(node, FieldAccessOf(node->op()).type);
break;
case IrOpcode::kLoadElement:
case IrOpcode::kLoadStackArgument:
@ -1539,7 +1539,7 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
// CheckTypeIs(node, ElementAccessOf(node->op()).type));
break;
case IrOpcode::kLoadFromObject:
// TODO(gsps): Can we check some types here?
CheckValueInputIs(node, 0, Type::Receiver());
break;
case IrOpcode::kLoadTypedElement:
break;
@ -1599,6 +1599,10 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckValueInputIs(node, 0, Type::Any());
CheckTypeIs(node, Type::BigInt());
break;
case IrOpcode::kFastApiCall:
CHECK_GE(value_count, 1);
CheckValueInputIs(node, 0, Type::ExternalPointer());
break;
// Machine operators
// -----------------------

View File

@ -437,7 +437,9 @@ using DebugObjectCache = std::vector<Handle<HeapObject>>;
V(v8_inspector::V8Inspector*, inspector, nullptr) \
V(bool, next_v8_call_is_safe_for_termination, false) \
V(bool, only_terminate_in_safe_scope, false) \
V(bool, detailed_source_positions_for_profiling, FLAG_detailed_line_info)
V(bool, detailed_source_positions_for_profiling, FLAG_detailed_line_info) \
V(int, embedder_wrapper_type_index, -1) \
V(int, embedder_wrapper_object_index, -1)
#define THREAD_LOCAL_TOP_ACCESSOR(type, name) \
inline void set_##name(type v) { thread_local_top()->name##_ = v; } \

View File

@ -638,6 +638,7 @@ DEFINE_BOOL(turbo_rewrite_far_jumps, true,
DEFINE_BOOL(
stress_gc_during_compilation, false,
"simulate GC/compiler thread race related to https://crbug.com/v8/8520")
DEFINE_BOOL(turbo_fast_api_calls, false, "enable fast API calls from TurboFan")
// Favor memory over execution speed.
DEFINE_BOOL(optimize_for_size, false,

View File

@ -1319,6 +1319,8 @@ FunctionTemplateRareData FunctionTemplateInfo::AllocateFunctionTemplateRareData(
FUNCTION_TEMPLATE_RARE_DATA_TYPE, AllocationType::kOld);
Handle<FunctionTemplateRareData> rare_data =
i::Handle<FunctionTemplateRareData>::cast(struct_obj);
rare_data->set_c_function(Smi(0));
rare_data->set_c_signature(Smi(0));
function_template_info->set_rare_data(*rare_data);
return *rare_data;
}

View File

@ -23,6 +23,8 @@ extern class FunctionTemplateRareData extends Struct {
instance_template: Object;
instance_call_handler: Object;
access_check_info: Object;
c_function: Foreign|Smi;
c_signature: Foreign|Smi;
}
@generateCppClass

View File

@ -50,11 +50,11 @@ FunctionTemplateRareData FunctionTemplateInfo::EnsureFunctionTemplateRareData(
}
}
#define RARE_ACCESSORS(Name, CamelName, Type) \
#define RARE_ACCESSORS(Name, CamelName, Type, Default) \
DEF_GETTER(FunctionTemplateInfo, Get##CamelName, Type) { \
HeapObject extra = rare_data(isolate); \
HeapObject undefined = GetReadOnlyRoots(isolate).undefined_value(); \
return extra == undefined ? undefined \
return extra == undefined ? Default \
: FunctionTemplateRareData::cast(extra).Name(); \
} \
inline void FunctionTemplateInfo::Set##CamelName( \
@ -65,14 +65,18 @@ FunctionTemplateRareData FunctionTemplateInfo::EnsureFunctionTemplateRareData(
rare_data.set_##Name(*Name); \
}
RARE_ACCESSORS(prototype_template, PrototypeTemplate, Object)
RARE_ACCESSORS(prototype_provider_template, PrototypeProviderTemplate, Object)
RARE_ACCESSORS(parent_template, ParentTemplate, Object)
RARE_ACCESSORS(named_property_handler, NamedPropertyHandler, Object)
RARE_ACCESSORS(indexed_property_handler, IndexedPropertyHandler, Object)
RARE_ACCESSORS(instance_template, InstanceTemplate, Object)
RARE_ACCESSORS(instance_call_handler, InstanceCallHandler, Object)
RARE_ACCESSORS(access_check_info, AccessCheckInfo, Object)
RARE_ACCESSORS(prototype_template, PrototypeTemplate, Object, undefined)
RARE_ACCESSORS(prototype_provider_template, PrototypeProviderTemplate, Object,
undefined)
RARE_ACCESSORS(parent_template, ParentTemplate, Object, undefined)
RARE_ACCESSORS(named_property_handler, NamedPropertyHandler, Object, undefined)
RARE_ACCESSORS(indexed_property_handler, IndexedPropertyHandler, Object,
undefined)
RARE_ACCESSORS(instance_template, InstanceTemplate, Object, undefined)
RARE_ACCESSORS(instance_call_handler, InstanceCallHandler, Object, undefined)
RARE_ACCESSORS(access_check_info, AccessCheckInfo, Object, undefined)
RARE_ACCESSORS(c_function, CFunction, Object, Smi(0))
RARE_ACCESSORS(c_signature, CSignature, Object, Smi(0))
#undef RARE_ACCESSORS
bool FunctionTemplateInfo::instantiated() {

View File

@ -82,6 +82,9 @@ class FunctionTemplateInfo
DECL_RARE_ACCESSORS(instance_call_handler, InstanceCallHandler, Object)
DECL_RARE_ACCESSORS(access_check_info, AccessCheckInfo, Object)
DECL_RARE_ACCESSORS(c_function, CFunction, Object)
DECL_RARE_ACCESSORS(c_signature, CSignature, Object)
#undef DECL_RARE_ACCESSORS
// Internal field to store a flag bitfield.

View File

@ -37,6 +37,7 @@
#include <unistd.h> // NOLINT
#endif
#include "include/v8-fast-api-calls.h"
#include "include/v8-util.h"
#include "src/api/api-inl.h"
#include "src/base/overflowing-math.h"
@ -27013,3 +27014,329 @@ UNINITIALIZED_TEST(NestedIsolates) {
}
#undef THREADED_PROFILED_TEST
#ifndef V8_LITE_MODE
namespace {
// The following should correspond to Chromium's kV8DOMWrapperObjectIndex.
static const int kV8WrapperTypeIndex = 0;
static const int kV8WrapperObjectIndex = 1;
template <typename T>
struct GetDeoptValue {
static Maybe<T> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context);
};
template <>
struct GetDeoptValue<int32_t> {
static Maybe<int32_t> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
return value->Int32Value(context);
}
};
template <>
struct GetDeoptValue<uint32_t> {
static Maybe<uint32_t> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
return value->Uint32Value(context);
}
};
template <>
struct GetDeoptValue<int64_t> {
static Maybe<int64_t> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
return value->IntegerValue(context);
}
};
template <>
struct GetDeoptValue<bool> {
static Maybe<bool> Get(v8::Local<v8::Value> value,
v8::Local<v8::Context> context) {
return v8::Just<bool>(value->BooleanValue(CcTest::isolate()));
}
};
template <typename T>
struct ApiNumberChecker {
enum Result {
kNotCalled,
kSlowCalled,
kFastCalled,
};
explicit ApiNumberChecker(T value) {}
static void CheckArgFast(ApiNumberChecker<T>* receiver, T argument) {
CHECK_NE(receiver, nullptr);
receiver->result = kFastCalled;
receiver->fast_value = argument;
}
static void CheckArgSlow(const v8::FunctionCallbackInfo<v8::Value>& info) {
CHECK_EQ(info.Length(), 1);
v8::Object* receiver = v8::Object::Cast(*info.Holder());
ApiNumberChecker<T>* checker = static_cast<ApiNumberChecker<T>*>(
receiver->GetAlignedPointerFromInternalField(kV8WrapperObjectIndex));
CHECK_NOT_NULL(checker);
if (checker->result == kSlowCalled) return;
checker->result = kSlowCalled;
LocalContext env;
checker->slow_value = GetDeoptValue<T>::Get(info[0], env.local());
}
T fast_value = T();
Maybe<T> slow_value = v8::Nothing<T>();
Result result = kNotCalled;
};
enum class Behavior {
kSuccess, // The callback function should be called with the expected value,
// which == initial.
kThrow, // An exception should be thrown by the callback function.
};
enum class PathTaken {
kFast, // The fast path is taken after optimization.
kSlow, // The slow path is taken always.
};
template <typename T>
void SetupTest(v8::Local<v8::Value> initial_value, LocalContext* env,
ApiNumberChecker<T>* checker) {
v8::Isolate* isolate = CcTest::isolate();
v8::CFunction c_func = v8::CFunction::Make(ApiNumberChecker<T>::CheckArgFast);
Local<v8::FunctionTemplate> checker_templ = v8::FunctionTemplate::New(
isolate, ApiNumberChecker<T>::CheckArgSlow, v8::Local<v8::Value>(),
v8::Local<v8::Signature>(), 1, v8::ConstructorBehavior::kAllow,
v8::SideEffectType::kHasSideEffect, &c_func);
v8::Local<v8::ObjectTemplate> object_template =
v8::ObjectTemplate::New(isolate);
object_template->SetInternalFieldCount(kV8WrapperObjectIndex + 1);
object_template->Set(v8_str("api_func"), checker_templ);
v8::Local<v8::Object> object =
object_template->NewInstance(env->local()).ToLocalChecked();
object->SetAlignedPointerInInternalField(kV8WrapperObjectIndex,
reinterpret_cast<void*>(checker));
CHECK((*env)
->Global()
->Set(env->local(), v8_str("receiver"), object)
.FromJust());
CHECK((*env)
->Global()
->Set(env->local(), v8_str("value"), initial_value)
.FromJust());
CompileRun(
"function func(arg) { receiver.api_func(arg); }"
"%PrepareFunctionForOptimization(func);"
"func(value);"
"%OptimizeFunctionOnNextCall(func);"
"func(value);");
}
template <typename T>
void CallAndCheck(T expected_value, Behavior expected_behavior,
PathTaken expected_path, v8::Local<v8::Value> initial_value) {
LocalContext env;
v8::TryCatch try_catch(CcTest::isolate());
ApiNumberChecker<T> checker(expected_value);
SetupTest<T>(initial_value, &env, &checker);
if (expected_behavior == Behavior::kThrow) {
CHECK(try_catch.HasCaught());
CHECK_NE(checker.result, ApiNumberChecker<T>::kFastCalled);
} else {
CHECK_EQ(try_catch.HasCaught(), false);
}
if (expected_path == PathTaken::kSlow) {
// The slow version callback should have been called twice.
CHECK_EQ(checker.result, ApiNumberChecker<T>::kSlowCalled);
if (expected_behavior != Behavior::kThrow) {
T slow_value_typed = checker.slow_value.ToChecked();
CHECK_EQ(slow_value_typed, expected_value);
}
} else if (expected_path == PathTaken::kFast) {
CHECK_EQ(checker.result, ApiNumberChecker<T>::kFastCalled);
CHECK_EQ(checker.fast_value, expected_value);
}
}
void CallAndDeopt() {
LocalContext env;
v8::Local<v8::Value> initial_value(v8_num(42));
ApiNumberChecker<int32_t> checker(42);
SetupTest(initial_value, &env, &checker);
v8::Local<v8::Value> function = CompileRun(
"try { func(BigInt(42)); } catch(e) {}"
"%PrepareFunctionForOptimization(func);"
"%OptimizeFunctionOnNextCall(func);"
"func(value);"
"func;");
CHECK(function->IsFunction());
i::Handle<i::JSFunction> ifunction =
i::Handle<i::JSFunction>::cast(v8::Utils::OpenHandle(*function));
CHECK(ifunction->IsOptimized());
}
void CallWithLessArguments() {
LocalContext env;
v8::Local<v8::Value> initial_value(v8_num(42));
ApiNumberChecker<int32_t> checker(42);
SetupTest(initial_value, &env, &checker);
CompileRun("func();");
// Passing not enough arguments should go through the slow path.
CHECK_EQ(checker.result, ApiNumberChecker<int32_t>::kSlowCalled);
}
void CallWithMoreArguments() {
LocalContext env;
v8::Local<v8::Value> initial_value(v8_num(42));
ApiNumberChecker<int32_t> checker(42);
SetupTest(initial_value, &env, &checker);
CompileRun(
"%PrepareFunctionForOptimization(func);"
"%OptimizeFunctionOnNextCall(func);"
"func(value, value);");
// Passing too many arguments should just ignore the extra ones.
CHECK_EQ(checker.result, ApiNumberChecker<int32_t>::kFastCalled);
}
} // namespace
namespace v8 {
template <typename T>
class WrapperTraits<ApiNumberChecker<T>> {
public:
static const void* GetTypeInfo() {
static const int tag = 0;
return reinterpret_cast<const void*>(&tag);
}
};
} // namespace v8
#endif // V8_LITE_MODE
TEST(FastApiCalls) {
#ifndef V8_LITE_MODE
if (i::FLAG_jitless) return;
i::FLAG_turbo_fast_api_calls = true;
i::FLAG_opt = true;
i::FLAG_allow_natives_syntax = true;
// Disable --always_opt, otherwise we haven't generated the necessary
// feedback to go down the "best optimization" path for the fast call.
i::FLAG_always_opt = false;
v8::Isolate* isolate = CcTest::isolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
i_isolate->set_embedder_wrapper_type_index(kV8WrapperTypeIndex);
i_isolate->set_embedder_wrapper_object_index(kV8WrapperObjectIndex);
v8::HandleScope scope(isolate);
LocalContext env;
// Main cases (the value fits in the type)
CallAndCheck<int32_t>(-42, Behavior::kSuccess, PathTaken::kFast, v8_num(-42));
CallAndCheck<uint32_t>(i::Smi::kMaxValue, Behavior::kSuccess,
PathTaken::kFast, v8_num(i::Smi::kMaxValue));
#ifdef V8_TARGET_ARCH_X64
CallAndCheck<int64_t>(static_cast<int64_t>(i::Smi::kMaxValue) + 1,
Behavior::kSuccess, PathTaken::kFast,
v8_num(static_cast<int64_t>(i::Smi::kMaxValue) + 1));
#endif // V8_TARGET_ARCH_X64
CallAndCheck<bool>(false, Behavior::kSuccess, PathTaken::kFast,
v8::Boolean::New(isolate, false));
CallAndCheck<bool>(true, Behavior::kSuccess, PathTaken::kFast,
v8::Boolean::New(isolate, true));
// Corner cases (the value is out of bounds or of different type) - int32_t
CallAndCheck<int32_t>(0, Behavior::kSuccess, PathTaken::kFast, v8_num(-0.0));
CallAndCheck<int32_t>(0, Behavior::kSuccess, PathTaken::kFast,
v8_num(std::numeric_limits<double>::quiet_NaN()));
CallAndCheck<int32_t>(0, Behavior::kSuccess, PathTaken::kFast,
v8_num(std::numeric_limits<double>::infinity()));
CallAndCheck<int32_t>(0, Behavior::kSuccess, PathTaken::kSlow,
v8_str("some_string"));
CallAndCheck<int32_t>(0, Behavior::kSuccess, PathTaken::kSlow,
v8::Object::New(isolate));
CallAndCheck<int32_t>(0, Behavior::kSuccess, PathTaken::kSlow,
v8::Array::New(isolate));
CallAndCheck<int32_t>(0, Behavior::kThrow, PathTaken::kSlow,
v8::BigInt::New(isolate, 42));
CallAndCheck<int32_t>(std::numeric_limits<int32_t>::min(), Behavior::kSuccess,
PathTaken::kFast,
v8_num(std::numeric_limits<int32_t>::min()));
CallAndCheck<int32_t>(
std::numeric_limits<int32_t>::min(), Behavior::kSuccess, PathTaken::kFast,
v8_num(static_cast<double>(std::numeric_limits<int32_t>::max()) + 1));
CallAndCheck<int32_t>(3, Behavior::kSuccess, PathTaken::kFast, v8_num(3.14));
// Corner cases - uint32_t
CallAndCheck<uint32_t>(0, Behavior::kSuccess, PathTaken::kFast, v8_num(-0.0));
CallAndCheck<uint32_t>(0, Behavior::kSuccess, PathTaken::kFast,
v8_num(std::numeric_limits<double>::quiet_NaN()));
CallAndCheck<uint32_t>(0, Behavior::kSuccess, PathTaken::kFast,
v8_num(std::numeric_limits<double>::infinity()));
CallAndCheck<uint32_t>(0, Behavior::kSuccess, PathTaken::kSlow,
v8_str("some_string"));
CallAndCheck<uint32_t>(0, Behavior::kSuccess, PathTaken::kSlow,
v8::Object::New(isolate));
CallAndCheck<uint32_t>(0, Behavior::kSuccess, PathTaken::kSlow,
v8::Array::New(isolate));
CallAndCheck<uint32_t>(0, Behavior::kThrow, PathTaken::kSlow,
v8::BigInt::New(isolate, 42));
CallAndCheck<uint32_t>(std::numeric_limits<uint32_t>::min(),
Behavior::kSuccess, PathTaken::kFast,
v8_num(std::numeric_limits<uint32_t>::max() + 1));
CallAndCheck<uint32_t>(3, Behavior::kSuccess, PathTaken::kFast, v8_num(3.14));
// Corner cases - bool
CallAndCheck<bool>(false, Behavior::kSuccess, PathTaken::kFast,
v8::Undefined(isolate));
CallAndCheck<bool>(false, Behavior::kSuccess, PathTaken::kFast,
v8::Null(isolate));
CallAndCheck<bool>(false, Behavior::kSuccess, PathTaken::kFast, v8_num(0));
CallAndCheck<bool>(true, Behavior::kSuccess, PathTaken::kFast, v8_num(42));
CallAndCheck<bool>(false, Behavior::kSuccess, PathTaken::kFast, v8_str(""));
CallAndCheck<bool>(true, Behavior::kSuccess, PathTaken::kFast,
v8_str("some_string"));
CallAndCheck<bool>(true, Behavior::kSuccess, PathTaken::kFast,
v8::Symbol::New(isolate));
CallAndCheck<bool>(false, Behavior::kSuccess, PathTaken::kFast,
v8::BigInt::New(isolate, 0));
CallAndCheck<bool>(true, Behavior::kSuccess, PathTaken::kFast,
v8::BigInt::New(isolate, 42));
CallAndCheck<bool>(true, Behavior::kSuccess, PathTaken::kFast,
v8::Object::New(isolate));
// Check for the deopt loop protection
CallAndDeopt();
// Wrong number of arguments
CallWithLessArguments();
CallWithMoreArguments();
// TODO(mslekova): Add corner cases for 64-bit values.
// TODO(mslekova): Add main cases for float and double.
// TODO(mslekova): Restructure the tests so that the fast optimized calls
// are compared against the slow optimized calls.
#endif // V8_LITE_MODE
}