• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 /**
6  * This file provides additional API on top of the default one for making
7  * API calls, which come from embedder C++ functions. The functions are being
8  * called directly from optimized code, doing all the necessary typechecks
9  * in the compiler itself, instead of on the embedder side. Hence the "fast"
10  * in the name. Example usage might look like:
11  *
12  * \code
13  *    void FastMethod(int param, bool another_param);
14  *
15  *    v8::FunctionTemplate::New(isolate, SlowCallback, data,
16  *                              signature, length, constructor_behavior
17  *                              side_effect_type,
18  *                              &v8::CFunction::Make(FastMethod));
19  * \endcode
20  *
21  * By design, fast calls are limited by the following requirements, which
22  * the embedder should enforce themselves:
23  *   - they should not allocate on the JS heap;
24  *   - they should not trigger JS execution.
25  * To enforce them, the embedder could use the existing
26  * v8::Isolate::DisallowJavascriptExecutionScope and a utility similar to
27  * Blink's NoAllocationScope:
28  * https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/heap/thread_state_scopes.h;l=16
29  *
30  * Due to these limitations, it's not directly possible to report errors by
31  * throwing a JS exception or to otherwise do an allocation. There is an
32  * alternative way of creating fast calls that supports falling back to the
33  * slow call and then performing the necessary allocation. When one creates
34  * the fast method by using CFunction::MakeWithFallbackSupport instead of
35  * CFunction::Make, the fast callback gets as last parameter an output variable,
36  * through which it can request falling back to the slow call. So one might
37  * declare their method like:
38  *
39  * \code
40  *    void FastMethodWithFallback(int param, FastApiCallbackOptions& options);
41  * \endcode
42  *
43  * If the callback wants to signal an error condition or to perform an
44  * allocation, it must set options.fallback to true and do an early return from
45  * the fast method. Then V8 checks the value of options.fallback and if it's
46  * true, falls back to executing the SlowCallback, which is capable of reporting
47  * the error (either by throwing a JS exception or logging to the console) or
48  * doing the allocation. It's the embedder's responsibility to ensure that the
49  * fast callback is idempotent up to the point where error and fallback
50  * conditions are checked, because otherwise executing the slow callback might
51  * produce visible side-effects twice.
52  *
53  * An example for custom embedder type support might employ a way to wrap/
54  * unwrap various C++ types in JSObject instances, e.g:
55  *
56  * \code
57  *
58  *    // Helper method with a check for field count.
59  *    template <typename T, int offset>
60  *    inline T* GetInternalField(v8::Local<v8::Object> wrapper) {
61  *      assert(offset < wrapper->InternalFieldCount());
62  *      return reinterpret_cast<T*>(
63  *          wrapper->GetAlignedPointerFromInternalField(offset));
64  *    }
65  *
66  *    class CustomEmbedderType {
67  *     public:
68  *      // Returns the raw C object from a wrapper JS object.
69  *      static CustomEmbedderType* Unwrap(v8::Local<v8::Object> wrapper) {
70  *        return GetInternalField<CustomEmbedderType,
71  *                                kV8EmbedderWrapperObjectIndex>(wrapper);
72  *      }
73  *      static void FastMethod(v8::ApiObject receiver_obj, int param) {
74  *        v8::Object* v8_object = reinterpret_cast<v8::Object*>(&api_object);
75  *        CustomEmbedderType* receiver = static_cast<CustomEmbedderType*>(
76  *          receiver_obj->GetAlignedPointerFromInternalField(
77  *            kV8EmbedderWrapperObjectIndex));
78  *
79  *        // Type checks are already done by the optimized code.
80  *        // Then call some performance-critical method like:
81  *        // receiver->Method(param);
82  *      }
83  *
84  *      static void SlowMethod(
85  *          const v8::FunctionCallbackInfo<v8::Value>& info) {
86  *        v8::Local<v8::Object> instance =
87  *          v8::Local<v8::Object>::Cast(info.Holder());
88  *        CustomEmbedderType* receiver = Unwrap(instance);
89  *        // TODO: Do type checks and extract {param}.
90  *        receiver->Method(param);
91  *      }
92  *    };
93  *
94  *    // TODO(mslekova): Clean-up these constants
95  *    // The constants kV8EmbedderWrapperTypeIndex and
96  *    // kV8EmbedderWrapperObjectIndex describe the offsets for the type info
97  *    // struct and the native object, when expressed as internal field indices
98  *    // within a JSObject. The existance of this helper function assumes that
99  *    // all embedder objects have their JSObject-side type info at the same
100  *    // offset, but this is not a limitation of the API itself. For a detailed
101  *    // use case, see the third example.
102  *    static constexpr int kV8EmbedderWrapperTypeIndex = 0;
103  *    static constexpr int kV8EmbedderWrapperObjectIndex = 1;
104  *
105  *    // The following setup function can be templatized based on
106  *    // the {embedder_object} argument.
107  *    void SetupCustomEmbedderObject(v8::Isolate* isolate,
108  *                                   v8::Local<v8::Context> context,
109  *                                   CustomEmbedderType* embedder_object) {
110  *      isolate->set_embedder_wrapper_type_index(
111  *        kV8EmbedderWrapperTypeIndex);
112  *      isolate->set_embedder_wrapper_object_index(
113  *        kV8EmbedderWrapperObjectIndex);
114  *
115  *      v8::CFunction c_func =
116  *        MakeV8CFunction(CustomEmbedderType::FastMethod);
117  *
118  *      Local<v8::FunctionTemplate> method_template =
119  *        v8::FunctionTemplate::New(
120  *          isolate, CustomEmbedderType::SlowMethod, v8::Local<v8::Value>(),
121  *          v8::Local<v8::Signature>(), 1, v8::ConstructorBehavior::kAllow,
122  *          v8::SideEffectType::kHasSideEffect, &c_func);
123  *
124  *      v8::Local<v8::ObjectTemplate> object_template =
125  *        v8::ObjectTemplate::New(isolate);
126  *      object_template->SetInternalFieldCount(
127  *        kV8EmbedderWrapperObjectIndex + 1);
128  *      object_template->Set(isolate, "method", method_template);
129  *
130  *      // Instantiate the wrapper JS object.
131  *      v8::Local<v8::Object> object =
132  *          object_template->NewInstance(context).ToLocalChecked();
133  *      object->SetAlignedPointerInInternalField(
134  *        kV8EmbedderWrapperObjectIndex,
135  *        reinterpret_cast<void*>(embedder_object));
136  *
137  *      // TODO: Expose {object} where it's necessary.
138  *    }
139  * \endcode
140  *
141  * For instance if {object} is exposed via a global "obj" variable,
142  * one could write in JS:
143  *   function hot_func() {
144  *     obj.method(42);
145  *   }
146  * and once {hot_func} gets optimized, CustomEmbedderType::FastMethod
147  * will be called instead of the slow version, with the following arguments:
148  *   receiver := the {embedder_object} from above
149  *   param := 42
150  *
151  * Currently only void return types are supported.
152  * Currently supported argument types:
153  *  - pointer to an embedder type
154  *  - bool
155  *  - int32_t
156  *  - uint32_t
157  *  - int64_t
158  *  - uint64_t
159  *  - float32_t
160  *  - float64_t
161  *
162  * The 64-bit integer types currently have the IDL (unsigned) long long
163  * semantics: https://heycam.github.io/webidl/#abstract-opdef-converttoint
164  * In the future we'll extend the API to also provide conversions from/to
165  * BigInt to preserve full precision.
166  * The floating point types currently have the IDL (unrestricted) semantics,
167  * which is the only one used by WebGL. We plan to add support also for
168  * restricted floats/doubles, similarly to the BigInt conversion policies.
169  * We also differ from the specific NaN bit pattern that WebIDL prescribes
170  * (https://heycam.github.io/webidl/#es-unrestricted-float) in that Blink
171  * passes NaN values as-is, i.e. doesn't normalize them.
172  *
173  * To be supported types:
174  *  - arrays of C types
175  *  - arrays of embedder types
176  */
177 
178 #ifndef INCLUDE_V8_FAST_API_CALLS_H_
179 #define INCLUDE_V8_FAST_API_CALLS_H_
180 
181 #include <stddef.h>
182 #include <stdint.h>
183 
184 #include "v8config.h"  // NOLINT(build/include_directory)
185 
186 namespace v8 {
187 
188 class CTypeInfo {
189  public:
190   enum class Type : char {
191     kVoid,
192     kBool,
193     kInt32,
194     kUint32,
195     kInt64,
196     kUint64,
197     kFloat32,
198     kFloat64,
199     kV8Value,
200   };
201 
202   enum class ArgFlags : uint8_t {
203     kNone = 0,
204     kIsArrayBit = 1 << 0,  // This argument is first in an array of values.
205   };
206 
207   static CTypeInfo FromWrapperType(ArgFlags flags = ArgFlags::kNone) {
208     return CTypeInfo(static_cast<int>(flags) | kIsWrapperTypeBit);
209   }
210 
211   static constexpr CTypeInfo FromCType(Type ctype,
212                                        ArgFlags flags = ArgFlags::kNone) {
213     // TODO(mslekova): Refactor the manual bit manipulations to use
214     // PointerWithPayload instead.
215     // ctype cannot be Type::kV8Value.
216     return CTypeInfo(
217         ((static_cast<uintptr_t>(ctype) << kTypeOffset) & kTypeMask) |
218         static_cast<int>(flags));
219   }
220 
221   const void* GetWrapperInfo() const;
222 
GetType()223   constexpr Type GetType() const {
224     if (payload_ & kIsWrapperTypeBit) {
225       return Type::kV8Value;
226     }
227     return static_cast<Type>((payload_ & kTypeMask) >> kTypeOffset);
228   }
229 
IsArray()230   constexpr bool IsArray() const {
231     return payload_ & static_cast<int>(ArgFlags::kIsArrayBit);
232   }
233 
Invalid()234   static const CTypeInfo& Invalid() {
235     static CTypeInfo invalid = CTypeInfo(0);
236     return invalid;
237   }
238 
239  private:
CTypeInfo(uintptr_t payload)240   explicit constexpr CTypeInfo(uintptr_t payload) : payload_(payload) {}
241 
242   // That must be the last bit after ArgFlags.
243   static constexpr uintptr_t kIsWrapperTypeBit = 1 << 1;
244   static constexpr uintptr_t kWrapperTypeInfoMask = static_cast<uintptr_t>(~0)
245                                                     << 2;
246 
247   static constexpr unsigned int kTypeOffset = kIsWrapperTypeBit;
248   static constexpr unsigned int kTypeSize = 8 - kTypeOffset;
249   static constexpr uintptr_t kTypeMask =
250       (~(static_cast<uintptr_t>(~0) << kTypeSize)) << kTypeOffset;
251 
252   const uintptr_t payload_;
253 };
254 
255 class CFunctionInfo {
256  public:
257   virtual const CTypeInfo& ReturnInfo() const = 0;
258   virtual unsigned int ArgumentCount() const = 0;
259   virtual const CTypeInfo& ArgumentInfo(unsigned int index) const = 0;
260 };
261 
262 struct ApiObject {
263   uintptr_t address;
264 };
265 
266 namespace internal {
267 
268 template <typename T>
269 struct GetCType {
GetGetCType270   static constexpr CTypeInfo Get() {
271     return CTypeInfo::FromCType(CTypeInfo::Type::kV8Value);
272   }
273 };
274 
275 #define SPECIALIZE_GET_C_TYPE_FOR(ctype, ctypeinfo)            \
276   template <>                                                  \
277   struct GetCType<ctype> {                                     \
278     static constexpr CTypeInfo Get() {                         \
279       return CTypeInfo::FromCType(CTypeInfo::Type::ctypeinfo); \
280     }                                                          \
281   };
282 
283 #define SUPPORTED_C_TYPES(V) \
284   V(void, kVoid)             \
285   V(bool, kBool)             \
286   V(int32_t, kInt32)         \
287   V(uint32_t, kUint32)       \
288   V(int64_t, kInt64)         \
289   V(uint64_t, kUint64)       \
290   V(float, kFloat32)         \
291   V(double, kFloat64)        \
292   V(ApiObject, kV8Value)
293 
294 SUPPORTED_C_TYPES(SPECIALIZE_GET_C_TYPE_FOR)
295 
296 // T* where T is a primitive (array of primitives).
297 template <typename T, typename = void>
298 struct GetCTypePointerImpl {
GetGetCTypePointerImpl299   static constexpr CTypeInfo Get() {
300     return CTypeInfo::FromCType(GetCType<T>::Get().GetType(),
301                                 CTypeInfo::ArgFlags::kIsArrayBit);
302   }
303 };
304 
305 // T* where T is an API object.
306 template <typename T>
307 struct GetCTypePointerImpl<T, void> {
308   static constexpr CTypeInfo Get() { return CTypeInfo::FromWrapperType(); }
309 };
310 
311 // T** where T is a primitive. Not allowed.
312 template <typename T, typename = void>
313 struct GetCTypePointerPointerImpl {
314   static_assert(sizeof(T**) != sizeof(T**), "Unsupported type");
315 };
316 
317 // T** where T is an API object (array of API objects).
318 template <typename T>
319 struct GetCTypePointerPointerImpl<T, void> {
320   static constexpr CTypeInfo Get() {
321     return CTypeInfo::FromWrapperType(CTypeInfo::ArgFlags::kIsArrayBit);
322   }
323 };
324 
325 template <typename T>
326 struct GetCType<T**> : public GetCTypePointerPointerImpl<T> {};
327 
328 template <typename T>
329 struct GetCType<T*> : public GetCTypePointerImpl<T> {};
330 
331 template <typename R, bool RaisesException, typename... Args>
332 class CFunctionInfoImpl : public CFunctionInfo {
333  public:
334   static constexpr int kFallbackArgCount = (RaisesException ? 1 : 0);
335   static constexpr int kReceiverCount = 1;
336   CFunctionInfoImpl()
337       : return_info_(internal::GetCType<R>::Get()),
338         arg_count_(sizeof...(Args) - kFallbackArgCount),
339         arg_info_{internal::GetCType<Args>::Get()...} {
340     static_assert(sizeof...(Args) >= kFallbackArgCount + kReceiverCount,
341                   "The receiver or the fallback argument is missing.");
342     static_assert(
343         internal::GetCType<R>::Get().GetType() == CTypeInfo::Type::kVoid,
344         "Only void return types are currently supported.");
345   }
346 
347   const CTypeInfo& ReturnInfo() const override { return return_info_; }
348   unsigned int ArgumentCount() const override { return arg_count_; }
349   const CTypeInfo& ArgumentInfo(unsigned int index) const override {
350     if (index >= ArgumentCount()) {
351       return CTypeInfo::Invalid();
352     }
353     return arg_info_[index];
354   }
355 
356  private:
357   const CTypeInfo return_info_;
358   const unsigned int arg_count_;
359   const CTypeInfo arg_info_[sizeof...(Args)];
360 };
361 
362 }  // namespace internal
363 
364 class V8_EXPORT CFunction {
365  public:
366   constexpr CFunction() : address_(nullptr), type_info_(nullptr) {}
367 
368   const CTypeInfo& ReturnInfo() const { return type_info_->ReturnInfo(); }
369 
370   const CTypeInfo& ArgumentInfo(unsigned int index) const {
371     return type_info_->ArgumentInfo(index);
372   }
373 
374   unsigned int ArgumentCount() const { return type_info_->ArgumentCount(); }
375 
376   const void* GetAddress() const { return address_; }
377   const CFunctionInfo* GetTypeInfo() const { return type_info_; }
378 
379   template <typename F>
380   static CFunction Make(F* func) {
381     return ArgUnwrap<F*>::Make(func);
382   }
383 
384   template <typename F>
385   static CFunction MakeWithFallbackSupport(F* func) {
386     return ArgUnwrap<F*>::MakeWithFallbackSupport(func);
387   }
388 
389   template <typename F>
390   static CFunction Make(F* func, const CFunctionInfo* type_info) {
391     return CFunction(reinterpret_cast<const void*>(func), type_info);
392   }
393 
394  private:
395   const void* address_;
396   const CFunctionInfo* type_info_;
397 
398   CFunction(const void* address, const CFunctionInfo* type_info);
399 
400   template <typename R, bool RaisesException, typename... Args>
401   static CFunctionInfo* GetCFunctionInfo() {
402     static internal::CFunctionInfoImpl<R, RaisesException, Args...> instance;
403     return &instance;
404   }
405 
406   template <typename F>
407   class ArgUnwrap {
408     static_assert(sizeof(F) != sizeof(F),
409                   "CFunction must be created from a function pointer.");
410   };
411 
412   template <typename R, typename... Args>
413   class ArgUnwrap<R (*)(Args...)> {
414    public:
415     static CFunction Make(R (*func)(Args...)) {
416       return CFunction(reinterpret_cast<const void*>(func),
417                        GetCFunctionInfo<R, false, Args...>());
418     }
419     static CFunction MakeWithFallbackSupport(R (*func)(Args...)) {
420       return CFunction(reinterpret_cast<const void*>(func),
421                        GetCFunctionInfo<R, true, Args...>());
422     }
423   };
424 };
425 
426 struct FastApiCallbackOptions {
427   bool fallback;
428 };
429 
430 }  // namespace v8
431 
432 #endif  // INCLUDE_V8_FAST_API_CALLS_H_
433