// 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 * * By design, fast calls are limited by the following requirements, which * the embedder should enforce themselves: * - they should not allocate on the JS heap; * - they should not trigger JS execution. * To enforce them, the embedder could use the existing * v8::Isolate::DisallowJavascriptExecutionScope and a utility similar to * Blink's NoAllocationScope: * https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/heap/thread_state_scopes.h;l=16 * * Due to these limitations, it's not directly possible to report errors by * throwing a JS exception or to otherwise do an allocation. There is an * alternative way of creating fast calls that supports falling back to the * slow call and then performing the necessary allocation. When one creates * the fast method by using CFunction::MakeWithFallbackSupport instead of * CFunction::Make, the fast callback gets as last parameter an output variable, * through which it can request falling back to the slow call. So one might * declare their method like: * * \code * void FastMethodWithFallback(int param, FastApiCallbackOptions& options); * \endcode * * If the callback wants to signal an error condition or to perform an * allocation, it must set options.fallback to true and do an early return from * the fast method. Then V8 checks the value of options.fallback and if it's * true, falls back to executing the SlowCallback, which is capable of reporting * the error (either by throwing a JS exception or logging to the console) or * doing the allocation. It's the embedder's responsibility to ensure that the * fast callback is idempotent up to the point where error and fallback * conditions are checked, because otherwise executing the slow callback might * produce visible side-effects twice. * * An example for custom embedder type support might employ a way to wrap/ * unwrap various C++ types in JSObject instances, e.g: * * \code * * // Helper method with a check for field count. * template <typename T, int offset> * inline T* GetInternalField(v8::Local<v8::Object> wrapper) { * assert(offset < wrapper->InternalFieldCount()); * return reinterpret_cast<T*>( * wrapper->GetAlignedPointerFromInternalField(offset)); * } * * class CustomEmbedderType { * public: * // 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(v8::Local<v8::Object> receiver_obj, int param) { * CustomEmbedderType* receiver = static_cast<CustomEmbedderType*>( * receiver_obj->GetAlignedPointerFromInternalField( * kV8EmbedderWrapperObjectIndex)); * * // 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}. * receiver->Method(param); * } * }; * * // TODO(mslekova): Clean-up these constants * // The constants kV8EmbedderWrapperTypeIndex and * // kV8EmbedderWrapperObjectIndex describe the offsets for the type info * // struct 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(isolate, "method", 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 supported return types: * - void * - bool * - int32_t * - uint32_t * - float32_t * - float64_t * Currently supported argument types: * - pointer to an embedder type * - JavaScript array of primitive types * - bool * - int32_t * - uint32_t * - int64_t * - uint64_t * - float32_t * - float64_t * * The 64-bit integer types currently have the IDL (unsigned) long long * semantics: https://heycam.github.io/webidl/#abstract-opdef-converttoint * In the future we'll extend the API to also provide conversions from/to * BigInt to preserve full precision. * The floating point types currently have the IDL (unrestricted) semantics, * which is the only one used by WebGL. We plan to add support also for * restricted floats/doubles, similarly to the BigInt conversion policies. * We also differ from the specific NaN bit pattern that WebIDL prescribes * (https://heycam.github.io/webidl/#es-unrestricted-float) in that Blink * passes NaN values as-is, i.e. doesn't normalize them. * * To be supported types: * - TypedArrays and ArrayBuffers * - arrays of embedder types * * * The API offers a limited support for function overloads: * * \code * void FastMethod_2Args(int param, bool another_param); * void FastMethod_3Args(int param, bool another_param, int third_param); * * v8::CFunction fast_method_2args_c_func = * MakeV8CFunction(FastMethod_2Args); * v8::CFunction fast_method_3args_c_func = * MakeV8CFunction(FastMethod_3Args); * const v8::CFunction fast_method_overloads[] = {fast_method_2args_c_func, * fast_method_3args_c_func}; * Local<v8::FunctionTemplate> method_template = * v8::FunctionTemplate::NewWithCFunctionOverloads( * isolate, SlowCallback, data, signature, length, * constructor_behavior, side_effect_type, * {fast_method_overloads, 2}); * \endcode * * In this example a single FunctionTemplate is associated to multiple C++ * functions. The overload resolution is currently only based on the number of * arguments passed in a call. For example, if this method_template is * registered with a wrapper JS object as described above, a call with two * arguments: * obj.method(42, true); * will result in a fast call to FastMethod_2Args, while a call with three or * more arguments: * obj.method(42, true, 11); * will result in a fast call to FastMethod_3Args. Instead a call with less than * two arguments, like: * obj.method(42); * would not result in a fast call but would fall back to executing the * associated SlowCallback. */ #ifndef INCLUDE_V8_FAST_API_CALLS_H_ #define INCLUDE_V8_FAST_API_CALLS_H_ #include <stddef.h> #include <stdint.h> #include <tuple> #include <type_traits> #include "v8-internal.h" // NOLINT(build/include_directory) #include "v8-local-handle.h" // NOLINT(build/include_directory) #include "v8-typed-array.h" // NOLINT(build/include_directory) #include "v8-value.h" // NOLINT(build/include_directory) #include "v8config.h" // NOLINT(build/include_directory) namespace v8 { class Isolate; class CTypeInfo { … }; struct FastApiTypedArrayBase { … }; template <typename T> struct V8_DEPRECATE_SOON( "When an API function expects a TypedArray as a parameter, the type in the " "signature should be `v8::Local<v8::Value>` instead of " "FastApiTypedArray<>. The API function then has to type-check the " "parameter and convert it to a `v8::Local<v8::TypedArray` to access the " "data. In essence, the parameter should be handled the same as for a " "regular API call.") FastApiTypedArray : public FastApiTypedArrayBase { … }; // Any TypedArray. It uses kTypedArrayBit with base type void // Overloaded args of ArrayBufferView and TypedArray are not supported // (for now) because the generic “any” ArrayBufferView doesn’t have its // own instance type. It could be supported if we specify that // TypedArray<T> always has precedence over the generic ArrayBufferView, // but this complicates overload resolution. struct FastApiArrayBufferView { … }; struct FastApiArrayBuffer { … }; struct FastOneByteString { … }; class V8_EXPORT CFunctionInfo { … }; struct FastApiCallbackOptions; // Provided for testing. AnyCType; static_assert …; class V8_EXPORT CFunction { … }; /** * A struct which may be passed to a fast call callback, like so: * \code * void FastMethodWithOptions(int param, FastApiCallbackOptions& options); * \endcode */ struct FastApiCallbackOptions { … }; internal // namespace internal template <typename T, CTypeInfo::Flags... Flags> class V8_EXPORT CTypeInfoBuilder { … }; namespace internal { template <typename RetBuilder, typename... ArgBuilders> class CFunctionBuilderWithFunction { … }; class CFunctionBuilder { … }; } // namespace internal // static template <typename R, typename... Args> CFunction CFunction::ArgUnwrap<R (*)(Args...)>::Make( R (*func)(Args...), CFunctionInfo::Int64Representation int64_rep) { … } CFunctionBuilder; static constexpr CTypeInfo kTypeInfoInt32 = …; static constexpr CTypeInfo kTypeInfoFloat64 = …; /** * Copies the contents of this JavaScript array to a C++ buffer with * a given max_length. A CTypeInfo is passed as an argument, * instructing different rules for conversion (e.g. restricted float/double). * The element type T of the destination array must match the C type * corresponding to the CTypeInfo (specified by CTypeInfoTraits). * If the array length is larger than max_length or the array is of * unsupported type, the operation will fail, returning false. Generally, an * array which contains objects, undefined, null or anything not convertible * to the requested destination type, is considered unsupported. The operation * returns true on success. `type_info` will be used for conversions. */ template <CTypeInfo::Identifier type_info_id, typename T> bool V8_EXPORT V8_WARN_UNUSED_RESULT TryToCopyAndConvertArrayToCppBuffer( Local<Array> src, T* dst, uint32_t max_length); template <> bool V8_EXPORT V8_WARN_UNUSED_RESULT TryToCopyAndConvertArrayToCppBuffer<CTypeInfoBuilder<int32_t>::Build().GetId(), int32_t>(Local<Array> src, int32_t* dst, uint32_t max_length); template <> bool V8_EXPORT V8_WARN_UNUSED_RESULT TryToCopyAndConvertArrayToCppBuffer<CTypeInfoBuilder<uint32_t>::Build().GetId(), uint32_t>(Local<Array> src, uint32_t* dst, uint32_t max_length); template <> bool V8_EXPORT V8_WARN_UNUSED_RESULT TryToCopyAndConvertArrayToCppBuffer<CTypeInfoBuilder<float>::Build().GetId(), float>(Local<Array> src, float* dst, uint32_t max_length); template <> bool V8_EXPORT V8_WARN_UNUSED_RESULT TryToCopyAndConvertArrayToCppBuffer<CTypeInfoBuilder<double>::Build().GetId(), double>(Local<Array> src, double* dst, uint32_t max_length); } // namespace v8 #endif // INCLUDE_V8_FAST_API_CALLS_H_