diff --git a/BUILD.gn b/BUILD.gn index e0f3925263..26504e350f 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -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", diff --git a/include/v8-fast-api-calls.h b/include/v8-fast-api-calls.h new file mode 100644 index 0000000000..24a417a849 --- /dev/null +++ b/include/v8-fast-api-calls.h @@ -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 + * inline T* GetInternalField(v8::Local wrapper) { + * assert(offset < wrapper->InternalFieldCount()); + * return reinterpret_cast( + * wrapper->GetAlignedPointerFromInternalField(offset)); + * } + * + * // Returns the type info from a wrapper JS object. + * inline const WrapperTypeInfo* ToWrapperTypeInfo( + * v8::Local wrapper) { + * return GetInternalField(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 wrapper) { + * return GetInternalField(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& info) { + * v8::Local instance = + * v8::Local::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 { + * 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 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 method_template = + * v8::FunctionTemplate::New( + * isolate, CustomEmbedderType::SlowMethod, v8::Local(), + * v8::Local(), 1, v8::ConstructorBehavior::kAllow, + * v8::SideEffectType::kHasSideEffect, &c_func); + * + * v8::Local 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 object = + * object_template->NewInstance(context).ToLocalChecked(); + * object->SetAlignedPointerInInternalField( + * kV8EmbedderWrapperObjectIndex, + * reinterpret_cast(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 +#include + +#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(wrapper_type_info); + // Check that the lower kIsWrapperTypeBit bits are 0's. + CHECK_EQ( + wrapper_type_info_ptr & ~(static_cast(~0) + << static_cast(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(ctype) << kTypeOffset) & kTypeMask) | flags); + } + + const void* GetWrapperInfo() const; + + constexpr Type GetType() const { + if (payload_ & kIsWrapperTypeBit) { + return Type::kUnwrappedApiObject; + } + return static_cast((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(~0) + << 2; + + static constexpr unsigned int kTypeOffset = kIsWrapperTypeBit; + static constexpr unsigned int kTypeSize = 8 - kTypeOffset; + static constexpr uintptr_t kTypeMask = + (~(static_cast(~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 +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 +struct GetCType { + static_assert(sizeof(T) != sizeof(T), "Unsupported CType"); +}; + +#define SPECIALIZE_GET_C_TYPE_FOR(ctype, ctypeinfo) \ + template <> \ + struct GetCType { \ + 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 +struct EnableIfHasWrapperTypeInfo {}; + +template <> +struct EnableIfHasWrapperTypeInfo {}; + +template +struct EnableIfHasWrapperTypeInfo::GetTypeInfo(), + void())> { + typedef void type; +}; + +// T* where T is a primitive (array of primitives). +template +struct GetCTypePointerImpl { + static constexpr CTypeInfo Get() { + return CTypeInfo::FromCType(GetCType::Get().GetType(), + CTypeInfo::IsArrayBit); + } +}; + +// T* where T is an API object. +template +struct GetCTypePointerImpl::type> { + static constexpr CTypeInfo Get() { + return CTypeInfo::FromWrapperType(WrapperTraits::GetTypeInfo()); + } +}; + +// T** where T is a primitive. Not allowed. +template +struct GetCTypePointerPointerImpl { + static_assert(sizeof(T**) != sizeof(T**), "Unsupported type"); +}; + +// T** where T is an API object (array of API objects). +template +struct GetCTypePointerPointerImpl< + T, typename EnableIfHasWrapperTypeInfo::type> { + static constexpr CTypeInfo Get() { + return CTypeInfo::FromWrapperType(WrapperTraits::GetTypeInfo(), + CTypeInfo::IsArrayBit); + } +}; + +template +struct GetCType : public GetCTypePointerPointerImpl {}; + +template +struct GetCType : public GetCTypePointerImpl {}; + +template +class CFunctionInfoImpl : public CFunctionInfo { + public: + CFunctionInfoImpl() + : return_info_(i::GetCType::Get()), + arg_count_(sizeof...(Args)), + arg_info_{i::GetCType::Get()...} { + static_assert(i::GetCType::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 + static CFunction Make(F* func) { + return ArgUnwrap::Make(func); + } + + private: + const void* address_; + const CFunctionInfo* type_info_; + + CFunction(const void* address, const CFunctionInfo* type_info); + + template + static CFunctionInfo* GetCFunctionInfo() { + static internal::CFunctionInfoImpl instance; + return &instance; + } + + template + class ArgUnwrap { + static_assert(sizeof(F) != sizeof(F), + "CFunction must be created from a function pointer."); + }; + + template + class ArgUnwrap { + public: + static CFunction Make(R (*func)(Args...)) { + return CFunction(reinterpret_cast(func), + GetCFunctionInfo()); + } + }; +}; + +} // namespace v8 + +#endif // INCLUDE_V8_FAST_API_CALLS_H_ diff --git a/include/v8.h b/include/v8.h index edf7a7c52d..20e3da6912 100644 --- a/include/v8.h +++ b/include/v8.h @@ -6284,6 +6284,7 @@ typedef bool (*AccessCheckCallback)(Local accessing_context, Local accessed_object, Local 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 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 data = Local(), Local signature = Local(), 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 data = Local(), - 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); diff --git a/src/api/api.cc b/src/api/api.cc index 700c0d1b07..a3f8f66595 100644 --- a/src/api/api.cc +++ b/src/api/api.cc @@ -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 FunctionTemplateNew( i::Isolate* isolate, FunctionCallback callback, v8::Local data, v8::Local signature, int length, bool do_not_cache, v8::Local cached_property_name = v8::Local(), - SideEffectType side_effect_type = SideEffectType::kHasSideEffect) { + SideEffectType side_effect_type = SideEffectType::kHasSideEffect, + const CFunction* c_function = nullptr) { i::Handle struct_obj = isolate->factory()->NewStruct( i::FUNCTION_TEMPLATE_INFO_TYPE, i::AllocationType::kOld); i::Handle obj = @@ -1489,7 +1491,8 @@ static Local 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 FunctionTemplateNew( Local FunctionTemplate::New( Isolate* isolate, FunctionCallback callback, v8::Local data, v8::Local signature, int length, ConstructorBehavior behavior, - SideEffectType side_effect_type) { + SideEffectType side_effect_type, const CFunction* c_function) { i::Isolate* i_isolate = reinterpret_cast(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(), side_effect_type); + auto templ = + FunctionTemplateNew(i_isolate, callback, data, signature, length, false, + Local(), side_effect_type, c_function); if (behavior == ConstructorBehavior::kThrow) templ->RemovePrototype(); return templ; } @@ -1563,7 +1567,8 @@ Local AccessorSignature::New( void FunctionTemplate::SetCallHandler(FunctionCallback callback, v8::Local 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(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(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 = diff --git a/src/compiler/heap-refs.h b/src/compiler/heap-refs.h index c9bb6bf9d1..946df61e52 100644 --- a/src/compiler/heap-refs.h +++ b/src/compiler/heap-refs.h @@ -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 call_code() const; + Address c_function() const; + const CFunctionInfo* c_signature() const; HolderLookupResult LookupHolderOfExpectedType( MapRef receiver_map, diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc index dc4e4000ca..14afb19196 100644 --- a/src/compiler/js-call-reducer.cc +++ b/src/compiler/js-call-reducer.cc @@ -6,7 +6,9 @@ #include +#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 ReduceFastApiCall() { + int c_arg_count = c_signature_->ArgumentCount(); + Node* function_node = + ExternalConstant(ExternalReference::Create(c_function_)); + base::SmallVector 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 FastApiCall( + base::SmallVector const& inputs) { + return AddNode( + graph()->NewNode(simplified()->FastApiCall(c_signature_, feedback()), + static_cast(inputs.size()), inputs.begin())); + } + + TNode UnwrapApiObject(TNode node, + int wrapper_object_index) { + const int offset = + Internals::kJSObjectHeaderSize + + (Internals::kEmbedderDataSlotSize * wrapper_object_index); + + FieldAccess access(kTaggedBase, offset, MaybeHandle(), + MaybeHandle(), Type::Any(), MachineType::Pointer(), + WriteBarrierKind::kNoWriteBarrier); + TNode load = AddNode(graph()->NewNode( + simplified()->LoadField(access), node, effect(), control())); + return load; + } + + Node* ConvertArgumentIfJSWrapper(CTypeInfo::Type type, TNode 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::UncheckedCast(node), + wrapper_object_index); + default: + return node; + } + } + + const Address c_function_; + const CFunctionInfo* const c_signature_; +}; + TNode JSCallReducerAssembler::SpeculativeToNumber( TNode value, NumberOperationHint hint) { return AddNode( @@ -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(); diff --git a/src/compiler/js-heap-broker.cc b/src/compiler/js-heap-broker.cc index b675d590c8..1d66d3fb79 100644 --- a/src/compiler/js-heap-broker.cc +++ b/src/compiler/js-heap-broker.cc @@ -9,6 +9,7 @@ #include #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 object) : HeapObjectData(broker, storage, object), + c_function_(v8::ToCData
(object->GetCFunction())), + c_signature_(v8::ToCData(object->GetCSignature())), known_receivers_(broker->zone()) { auto function_template_info = Handle::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
(object()->GetCFunction()); + } + return HeapObjectRef::data()->AsFunctionTemplateInfo()->c_function(); +} + +const CFunctionInfo* FunctionTemplateInfoRef::c_signature() const { + if (broker()->mode() == JSHeapBroker::kDisabled) { + return v8::ToCData(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(); diff --git a/src/compiler/linkage.h b/src/compiler/linkage.h index 17a419bab3..b50ed0f3f0 100644 --- a/src/compiler/linkage.h +++ b/src/compiler/linkage.h @@ -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); }; diff --git a/src/compiler/opcodes.h b/src/compiler/opcodes.h index 7836c4425d..3579fd8fd6 100644 --- a/src/compiler/opcodes.h +++ b/src/compiler/opcodes.h @@ -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) \ diff --git a/src/compiler/representation-change.cc b/src/compiler/representation-change.cc index 2c0cb76708..45d0ec994c 100644 --- a/src/compiler/representation-change.cc +++ b/src/compiler/representation-change.cc @@ -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 { diff --git a/src/compiler/representation-change.h b/src/compiler/representation-change.h index ad9e1d6041..78fa1fbe9d 100644 --- a/src/compiler/representation-change.h +++ b/src/compiler/representation-change.h @@ -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, diff --git a/src/compiler/simplified-lowering.cc b/src/compiler/simplified-lowering.cc index 8997a5a831..9fb7607bf5 100644 --- a/src/compiler/simplified-lowering.cc +++ b/src/compiler/simplified-lowering.cc @@ -6,8 +6,11 @@ #include +#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 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: diff --git a/src/compiler/simplified-operator.cc b/src/compiler/simplified-operator.cc index 41547fd132..b3496ce928 100644 --- a/src/compiler/simplified-operator.cc +++ b/src/compiler/simplified-operator.cc @@ -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( + 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(op); } +FastApiCallParameters const& FastApiCallParametersOf(const Operator* op) { + DCHECK_EQ(IrOpcode::kFastApiCall, op->opcode()); + return OpParameter(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( diff --git a/src/compiler/simplified-operator.h b/src/compiler/simplified-operator.h index 4fc9a4be78..a14b76aa8f 100644 --- a/src/compiler/simplified-operator.h +++ b/src/compiler/simplified-operator.h @@ -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_; } diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc index eacec94271..b726be4a79 100644 --- a/src/compiler/typer.cc +++ b/src/compiler/typer.cc @@ -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(); diff --git a/src/compiler/verifier.cc b/src/compiler/verifier.cc index 2291920451..627bec3bc7 100644 --- a/src/compiler/verifier.cc +++ b/src/compiler/verifier.cc @@ -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 // ----------------------- diff --git a/src/execution/isolate.h b/src/execution/isolate.h index 66b8fa33bc..0ad52c1a60 100644 --- a/src/execution/isolate.h +++ b/src/execution/isolate.h @@ -437,7 +437,9 @@ using DebugObjectCache = std::vector>; 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; } \ diff --git a/src/flags/flag-definitions.h b/src/flags/flag-definitions.h index 4a4df0ef5f..4b6e9ca036 100644 --- a/src/flags/flag-definitions.h +++ b/src/flags/flag-definitions.h @@ -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, diff --git a/src/objects/objects.cc b/src/objects/objects.cc index feb78fa090..c483a4c702 100644 --- a/src/objects/objects.cc +++ b/src/objects/objects.cc @@ -1319,6 +1319,8 @@ FunctionTemplateRareData FunctionTemplateInfo::AllocateFunctionTemplateRareData( FUNCTION_TEMPLATE_RARE_DATA_TYPE, AllocationType::kOld); Handle rare_data = i::Handle::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; } diff --git a/src/objects/template.tq b/src/objects/template.tq index 472d968855..02d432804f 100644 --- a/src/objects/template.tq +++ b/src/objects/template.tq @@ -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 diff --git a/src/objects/templates-inl.h b/src/objects/templates-inl.h index be58fc12bc..875c8b8a81 100644 --- a/src/objects/templates-inl.h +++ b/src/objects/templates-inl.h @@ -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() { diff --git a/src/objects/templates.h b/src/objects/templates.h index 25f9f37c03..33fc0bbea3 100644 --- a/src/objects/templates.h +++ b/src/objects/templates.h @@ -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. diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index c83acdd109..3033ae9f07 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -37,6 +37,7 @@ #include // 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 +struct GetDeoptValue { + static Maybe Get(v8::Local value, + v8::Local context); +}; + +template <> +struct GetDeoptValue { + static Maybe Get(v8::Local value, + v8::Local context) { + return value->Int32Value(context); + } +}; + +template <> +struct GetDeoptValue { + static Maybe Get(v8::Local value, + v8::Local context) { + return value->Uint32Value(context); + } +}; + +template <> +struct GetDeoptValue { + static Maybe Get(v8::Local value, + v8::Local context) { + return value->IntegerValue(context); + } +}; + +template <> +struct GetDeoptValue { + static Maybe Get(v8::Local value, + v8::Local context) { + return v8::Just(value->BooleanValue(CcTest::isolate())); + } +}; + +template +struct ApiNumberChecker { + enum Result { + kNotCalled, + kSlowCalled, + kFastCalled, + }; + + explicit ApiNumberChecker(T value) {} + + static void CheckArgFast(ApiNumberChecker* receiver, T argument) { + CHECK_NE(receiver, nullptr); + receiver->result = kFastCalled; + receiver->fast_value = argument; + } + + static void CheckArgSlow(const v8::FunctionCallbackInfo& info) { + CHECK_EQ(info.Length(), 1); + + v8::Object* receiver = v8::Object::Cast(*info.Holder()); + ApiNumberChecker* checker = static_cast*>( + receiver->GetAlignedPointerFromInternalField(kV8WrapperObjectIndex)); + + CHECK_NOT_NULL(checker); + if (checker->result == kSlowCalled) return; + checker->result = kSlowCalled; + + LocalContext env; + checker->slow_value = GetDeoptValue::Get(info[0], env.local()); + } + + T fast_value = T(); + Maybe slow_value = v8::Nothing(); + 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 +void SetupTest(v8::Local initial_value, LocalContext* env, + ApiNumberChecker* checker) { + v8::Isolate* isolate = CcTest::isolate(); + + v8::CFunction c_func = v8::CFunction::Make(ApiNumberChecker::CheckArgFast); + + Local checker_templ = v8::FunctionTemplate::New( + isolate, ApiNumberChecker::CheckArgSlow, v8::Local(), + v8::Local(), 1, v8::ConstructorBehavior::kAllow, + v8::SideEffectType::kHasSideEffect, &c_func); + + v8::Local object_template = + v8::ObjectTemplate::New(isolate); + object_template->SetInternalFieldCount(kV8WrapperObjectIndex + 1); + object_template->Set(v8_str("api_func"), checker_templ); + + v8::Local object = + object_template->NewInstance(env->local()).ToLocalChecked(); + object->SetAlignedPointerInInternalField(kV8WrapperObjectIndex, + reinterpret_cast(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 +void CallAndCheck(T expected_value, Behavior expected_behavior, + PathTaken expected_path, v8::Local initial_value) { + LocalContext env; + v8::TryCatch try_catch(CcTest::isolate()); + ApiNumberChecker checker(expected_value); + + SetupTest(initial_value, &env, &checker); + + if (expected_behavior == Behavior::kThrow) { + CHECK(try_catch.HasCaught()); + CHECK_NE(checker.result, ApiNumberChecker::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::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::kFastCalled); + CHECK_EQ(checker.fast_value, expected_value); + } +} + +void CallAndDeopt() { + LocalContext env; + v8::Local initial_value(v8_num(42)); + ApiNumberChecker checker(42); + SetupTest(initial_value, &env, &checker); + + v8::Local function = CompileRun( + "try { func(BigInt(42)); } catch(e) {}" + "%PrepareFunctionForOptimization(func);" + "%OptimizeFunctionOnNextCall(func);" + "func(value);" + "func;"); + CHECK(function->IsFunction()); + i::Handle ifunction = + i::Handle::cast(v8::Utils::OpenHandle(*function)); + CHECK(ifunction->IsOptimized()); +} + +void CallWithLessArguments() { + LocalContext env; + v8::Local initial_value(v8_num(42)); + ApiNumberChecker checker(42); + SetupTest(initial_value, &env, &checker); + + CompileRun("func();"); + + // Passing not enough arguments should go through the slow path. + CHECK_EQ(checker.result, ApiNumberChecker::kSlowCalled); +} + +void CallWithMoreArguments() { + LocalContext env; + v8::Local initial_value(v8_num(42)); + ApiNumberChecker 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::kFastCalled); +} +} // namespace + +namespace v8 { +template +class WrapperTraits> { + public: + static const void* GetTypeInfo() { + static const int tag = 0; + return reinterpret_cast(&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(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(-42, Behavior::kSuccess, PathTaken::kFast, v8_num(-42)); + CallAndCheck(i::Smi::kMaxValue, Behavior::kSuccess, + PathTaken::kFast, v8_num(i::Smi::kMaxValue)); +#ifdef V8_TARGET_ARCH_X64 + CallAndCheck(static_cast(i::Smi::kMaxValue) + 1, + Behavior::kSuccess, PathTaken::kFast, + v8_num(static_cast(i::Smi::kMaxValue) + 1)); +#endif // V8_TARGET_ARCH_X64 + + CallAndCheck(false, Behavior::kSuccess, PathTaken::kFast, + v8::Boolean::New(isolate, false)); + CallAndCheck(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(0, Behavior::kSuccess, PathTaken::kFast, v8_num(-0.0)); + CallAndCheck(0, Behavior::kSuccess, PathTaken::kFast, + v8_num(std::numeric_limits::quiet_NaN())); + CallAndCheck(0, Behavior::kSuccess, PathTaken::kFast, + v8_num(std::numeric_limits::infinity())); + CallAndCheck(0, Behavior::kSuccess, PathTaken::kSlow, + v8_str("some_string")); + CallAndCheck(0, Behavior::kSuccess, PathTaken::kSlow, + v8::Object::New(isolate)); + CallAndCheck(0, Behavior::kSuccess, PathTaken::kSlow, + v8::Array::New(isolate)); + CallAndCheck(0, Behavior::kThrow, PathTaken::kSlow, + v8::BigInt::New(isolate, 42)); + CallAndCheck(std::numeric_limits::min(), Behavior::kSuccess, + PathTaken::kFast, + v8_num(std::numeric_limits::min())); + CallAndCheck( + std::numeric_limits::min(), Behavior::kSuccess, PathTaken::kFast, + v8_num(static_cast(std::numeric_limits::max()) + 1)); + + CallAndCheck(3, Behavior::kSuccess, PathTaken::kFast, v8_num(3.14)); + + // Corner cases - uint32_t + CallAndCheck(0, Behavior::kSuccess, PathTaken::kFast, v8_num(-0.0)); + CallAndCheck(0, Behavior::kSuccess, PathTaken::kFast, + v8_num(std::numeric_limits::quiet_NaN())); + CallAndCheck(0, Behavior::kSuccess, PathTaken::kFast, + v8_num(std::numeric_limits::infinity())); + CallAndCheck(0, Behavior::kSuccess, PathTaken::kSlow, + v8_str("some_string")); + CallAndCheck(0, Behavior::kSuccess, PathTaken::kSlow, + v8::Object::New(isolate)); + CallAndCheck(0, Behavior::kSuccess, PathTaken::kSlow, + v8::Array::New(isolate)); + CallAndCheck(0, Behavior::kThrow, PathTaken::kSlow, + v8::BigInt::New(isolate, 42)); + CallAndCheck(std::numeric_limits::min(), + Behavior::kSuccess, PathTaken::kFast, + v8_num(std::numeric_limits::max() + 1)); + CallAndCheck(3, Behavior::kSuccess, PathTaken::kFast, v8_num(3.14)); + + // Corner cases - bool + CallAndCheck(false, Behavior::kSuccess, PathTaken::kFast, + v8::Undefined(isolate)); + CallAndCheck(false, Behavior::kSuccess, PathTaken::kFast, + v8::Null(isolate)); + CallAndCheck(false, Behavior::kSuccess, PathTaken::kFast, v8_num(0)); + CallAndCheck(true, Behavior::kSuccess, PathTaken::kFast, v8_num(42)); + CallAndCheck(false, Behavior::kSuccess, PathTaken::kFast, v8_str("")); + CallAndCheck(true, Behavior::kSuccess, PathTaken::kFast, + v8_str("some_string")); + CallAndCheck(true, Behavior::kSuccess, PathTaken::kFast, + v8::Symbol::New(isolate)); + CallAndCheck(false, Behavior::kSuccess, PathTaken::kFast, + v8::BigInt::New(isolate, 0)); + CallAndCheck(true, Behavior::kSuccess, PathTaken::kFast, + v8::BigInt::New(isolate, 42)); + CallAndCheck(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 +}