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::Local<v8::Object> receiver_obj, int param) {
74 * CustomEmbedderType* receiver = static_cast<CustomEmbedderType*>(
75 * receiver_obj->GetAlignedPointerFromInternalField(
76 * kV8EmbedderWrapperObjectIndex));
77 *
78 * // Type checks are already done by the optimized code.
79 * // Then call some performance-critical method like:
80 * // receiver->Method(param);
81 * }
82 *
83 * static void SlowMethod(
84 * const v8::FunctionCallbackInfo<v8::Value>& info) {
85 * v8::Local<v8::Object> instance =
86 * v8::Local<v8::Object>::Cast(info.Holder());
87 * CustomEmbedderType* receiver = Unwrap(instance);
88 * // TODO: Do type checks and extract {param}.
89 * receiver->Method(param);
90 * }
91 * };
92 *
93 * // TODO(mslekova): Clean-up these constants
94 * // The constants kV8EmbedderWrapperTypeIndex and
95 * // kV8EmbedderWrapperObjectIndex describe the offsets for the type info
96 * // struct and the native object, when expressed as internal field indices
97 * // within a JSObject. The existance of this helper function assumes that
98 * // all embedder objects have their JSObject-side type info at the same
99 * // offset, but this is not a limitation of the API itself. For a detailed
100 * // use case, see the third example.
101 * static constexpr int kV8EmbedderWrapperTypeIndex = 0;
102 * static constexpr int kV8EmbedderWrapperObjectIndex = 1;
103 *
104 * // The following setup function can be templatized based on
105 * // the {embedder_object} argument.
106 * void SetupCustomEmbedderObject(v8::Isolate* isolate,
107 * v8::Local<v8::Context> context,
108 * CustomEmbedderType* embedder_object) {
109 * isolate->set_embedder_wrapper_type_index(
110 * kV8EmbedderWrapperTypeIndex);
111 * isolate->set_embedder_wrapper_object_index(
112 * kV8EmbedderWrapperObjectIndex);
113 *
114 * v8::CFunction c_func =
115 * MakeV8CFunction(CustomEmbedderType::FastMethod);
116 *
117 * Local<v8::FunctionTemplate> method_template =
118 * v8::FunctionTemplate::New(
119 * isolate, CustomEmbedderType::SlowMethod, v8::Local<v8::Value>(),
120 * v8::Local<v8::Signature>(), 1, v8::ConstructorBehavior::kAllow,
121 * v8::SideEffectType::kHasSideEffect, &c_func);
122 *
123 * v8::Local<v8::ObjectTemplate> object_template =
124 * v8::ObjectTemplate::New(isolate);
125 * object_template->SetInternalFieldCount(
126 * kV8EmbedderWrapperObjectIndex + 1);
127 * object_template->Set(isolate, "method", method_template);
128 *
129 * // Instantiate the wrapper JS object.
130 * v8::Local<v8::Object> object =
131 * object_template->NewInstance(context).ToLocalChecked();
132 * object->SetAlignedPointerInInternalField(
133 * kV8EmbedderWrapperObjectIndex,
134 * reinterpret_cast<void*>(embedder_object));
135 *
136 * // TODO: Expose {object} where it's necessary.
137 * }
138 * \endcode
139 *
140 * For instance if {object} is exposed via a global "obj" variable,
141 * one could write in JS:
142 * function hot_func() {
143 * obj.method(42);
144 * }
145 * and once {hot_func} gets optimized, CustomEmbedderType::FastMethod
146 * will be called instead of the slow version, with the following arguments:
147 * receiver := the {embedder_object} from above
148 * param := 42
149 *
150 * Currently supported return types:
151 * - void
152 * - bool
153 * - int32_t
154 * - uint32_t
155 * - float32_t
156 * - float64_t
157 * Currently supported argument types:
158 * - pointer to an embedder type
159 * - JavaScript array of primitive types
160 * - bool
161 * - int32_t
162 * - uint32_t
163 * - int64_t
164 * - uint64_t
165 * - float32_t
166 * - float64_t
167 *
168 * The 64-bit integer types currently have the IDL (unsigned) long long
169 * semantics: https://heycam.github.io/webidl/#abstract-opdef-converttoint
170 * In the future we'll extend the API to also provide conversions from/to
171 * BigInt to preserve full precision.
172 * The floating point types currently have the IDL (unrestricted) semantics,
173 * which is the only one used by WebGL. We plan to add support also for
174 * restricted floats/doubles, similarly to the BigInt conversion policies.
175 * We also differ from the specific NaN bit pattern that WebIDL prescribes
176 * (https://heycam.github.io/webidl/#es-unrestricted-float) in that Blink
177 * passes NaN values as-is, i.e. doesn't normalize them.
178 *
179 * To be supported types:
180 * - TypedArrays and ArrayBuffers
181 * - arrays of embedder types
182 *
183 *
184 * The API offers a limited support for function overloads:
185 *
186 * \code
187 * void FastMethod_2Args(int param, bool another_param);
188 * void FastMethod_3Args(int param, bool another_param, int third_param);
189 *
190 * v8::CFunction fast_method_2args_c_func =
191 * MakeV8CFunction(FastMethod_2Args);
192 * v8::CFunction fast_method_3args_c_func =
193 * MakeV8CFunction(FastMethod_3Args);
194 * const v8::CFunction fast_method_overloads[] = {fast_method_2args_c_func,
195 * fast_method_3args_c_func};
196 * Local<v8::FunctionTemplate> method_template =
197 * v8::FunctionTemplate::NewWithCFunctionOverloads(
198 * isolate, SlowCallback, data, signature, length,
199 * constructor_behavior, side_effect_type,
200 * {fast_method_overloads, 2});
201 * \endcode
202 *
203 * In this example a single FunctionTemplate is associated to multiple C++
204 * functions. The overload resolution is currently only based on the number of
205 * arguments passed in a call. For example, if this method_template is
206 * registered with a wrapper JS object as described above, a call with two
207 * arguments:
208 * obj.method(42, true);
209 * will result in a fast call to FastMethod_2Args, while a call with three or
210 * more arguments:
211 * obj.method(42, true, 11);
212 * will result in a fast call to FastMethod_3Args. Instead a call with less than
213 * two arguments, like:
214 * obj.method(42);
215 * would not result in a fast call but would fall back to executing the
216 * associated SlowCallback.
217 */
218
219 #ifndef INCLUDE_V8_FAST_API_CALLS_H_
220 #define INCLUDE_V8_FAST_API_CALLS_H_
221
222 #include <stddef.h>
223 #include <stdint.h>
224
225 #include <tuple>
226 #include <type_traits>
227
228 #include "v8-internal.h" // NOLINT(build/include_directory)
229 #include "v8-local-handle.h" // NOLINT(build/include_directory)
230 #include "v8-typed-array.h" // NOLINT(build/include_directory)
231 #include "v8-value.h" // NOLINT(build/include_directory)
232 #include "v8config.h" // NOLINT(build/include_directory)
233
234 namespace v8 {
235
236 class Isolate;
237
238 class CTypeInfo {
239 public:
240 enum class Type : uint8_t {
241 kVoid,
242 kBool,
243 kUint8,
244 kInt32,
245 kUint32,
246 kInt64,
247 kUint64,
248 kFloat32,
249 kFloat64,
250 kPointer,
251 kV8Value,
252 kSeqOneByteString,
253 kApiObject, // This will be deprecated once all users have
254 // migrated from v8::ApiObject to v8::Local<v8::Value>.
255 kAny, // This is added to enable untyped representation of fast
256 // call arguments for test purposes. It can represent any of
257 // the other types stored in the same memory as a union (see
258 // the AnyCType struct declared below). This allows for
259 // uniform passing of arguments w.r.t. their location
260 // (in a register or on the stack), independent of their
261 // actual type. It's currently used by the arm64 simulator
262 // and can be added to the other simulators as well when fast
263 // calls having both GP and FP params need to be supported.
264 };
265
266 // kCallbackOptionsType is not part of the Type enum
267 // because it is only used internally. Use value 255 that is larger
268 // than any valid Type enum.
269 static constexpr Type kCallbackOptionsType = Type(255);
270
271 enum class SequenceType : uint8_t {
272 kScalar,
273 kIsSequence, // sequence<T>
274 kIsTypedArray, // TypedArray of T or any ArrayBufferView if T
275 // is void
276 kIsArrayBuffer // ArrayBuffer
277 };
278
279 enum class Flags : uint8_t {
280 kNone = 0,
281 kAllowSharedBit = 1 << 0, // Must be an ArrayBuffer or TypedArray
282 kEnforceRangeBit = 1 << 1, // T must be integral
283 kClampBit = 1 << 2, // T must be integral
284 kIsRestrictedBit = 1 << 3, // T must be float or double
285 };
286
CTypeInfo( Type type, SequenceType sequence_type = SequenceType::kScalar, Flags flags = Flags::kNone)287 explicit constexpr CTypeInfo(
288 Type type, SequenceType sequence_type = SequenceType::kScalar,
289 Flags flags = Flags::kNone)
290 : type_(type), sequence_type_(sequence_type), flags_(flags) {}
291
292 typedef uint32_t Identifier;
CTypeInfo(Identifier identifier)293 explicit constexpr CTypeInfo(Identifier identifier)
294 : CTypeInfo(static_cast<Type>(identifier >> 16),
295 static_cast<SequenceType>((identifier >> 8) & 255),
296 static_cast<Flags>(identifier & 255)) {}
GetId() const297 constexpr Identifier GetId() const {
298 return static_cast<uint8_t>(type_) << 16 |
299 static_cast<uint8_t>(sequence_type_) << 8 |
300 static_cast<uint8_t>(flags_);
301 }
302
GetType() const303 constexpr Type GetType() const { return type_; }
GetSequenceType() const304 constexpr SequenceType GetSequenceType() const { return sequence_type_; }
GetFlags() const305 constexpr Flags GetFlags() const { return flags_; }
306
IsIntegralType(Type type)307 static constexpr bool IsIntegralType(Type type) {
308 return type == Type::kUint8 || type == Type::kInt32 ||
309 type == Type::kUint32 || type == Type::kInt64 ||
310 type == Type::kUint64;
311 }
312
IsFloatingPointType(Type type)313 static constexpr bool IsFloatingPointType(Type type) {
314 return type == Type::kFloat32 || type == Type::kFloat64;
315 }
316
IsPrimitive(Type type)317 static constexpr bool IsPrimitive(Type type) {
318 return IsIntegralType(type) || IsFloatingPointType(type) ||
319 type == Type::kBool;
320 }
321
322 private:
323 Type type_;
324 SequenceType sequence_type_;
325 Flags flags_;
326 };
327
328 struct FastApiTypedArrayBase {
329 public:
330 // Returns the length in number of elements.
lengthv8::FastApiTypedArrayBase331 size_t V8_EXPORT length() const { return length_; }
332 // Checks whether the given index is within the bounds of the collection.
333 void V8_EXPORT ValidateIndex(size_t index) const;
334
335 protected:
336 size_t length_ = 0;
337 };
338
339 template <typename T>
340 struct FastApiTypedArray : public FastApiTypedArrayBase {
341 public:
getv8::FastApiTypedArray342 V8_INLINE T get(size_t index) const {
343 #ifdef DEBUG
344 ValidateIndex(index);
345 #endif // DEBUG
346 T tmp;
347 memcpy(&tmp, reinterpret_cast<T*>(data_) + index, sizeof(T));
348 return tmp;
349 }
350
getStorageIfAlignedv8::FastApiTypedArray351 bool getStorageIfAligned(T** elements) const {
352 if (reinterpret_cast<uintptr_t>(data_) % alignof(T) != 0) {
353 return false;
354 }
355 *elements = reinterpret_cast<T*>(data_);
356 return true;
357 }
358
359 private:
360 // This pointer should include the typed array offset applied.
361 // It's not guaranteed that it's aligned to sizeof(T), it's only
362 // guaranteed that it's 4-byte aligned, so for 8-byte types we need to
363 // provide a special implementation for reading from it, which hides
364 // the possibly unaligned read in the `get` method.
365 void* data_;
366 };
367
368 // Any TypedArray. It uses kTypedArrayBit with base type void
369 // Overloaded args of ArrayBufferView and TypedArray are not supported
370 // (for now) because the generic “any” ArrayBufferView doesn’t have its
371 // own instance type. It could be supported if we specify that
372 // TypedArray<T> always has precedence over the generic ArrayBufferView,
373 // but this complicates overload resolution.
374 struct FastApiArrayBufferView {
375 void* data;
376 size_t byte_length;
377 };
378
379 struct FastApiArrayBuffer {
380 void* data;
381 size_t byte_length;
382 };
383
384 struct FastOneByteString {
385 const char* data;
386 uint32_t length;
387 };
388
389 class V8_EXPORT CFunctionInfo {
390 public:
391 // Construct a struct to hold a CFunction's type information.
392 // |return_info| describes the function's return type.
393 // |arg_info| is an array of |arg_count| CTypeInfos describing the
394 // arguments. Only the last argument may be of the special type
395 // CTypeInfo::kCallbackOptionsType.
396 CFunctionInfo(const CTypeInfo& return_info, unsigned int arg_count,
397 const CTypeInfo* arg_info);
398
ReturnInfo() const399 const CTypeInfo& ReturnInfo() const { return return_info_; }
400
401 // The argument count, not including the v8::FastApiCallbackOptions
402 // if present.
ArgumentCount() const403 unsigned int ArgumentCount() const {
404 return HasOptions() ? arg_count_ - 1 : arg_count_;
405 }
406
407 // |index| must be less than ArgumentCount().
408 // Note: if the last argument passed on construction of CFunctionInfo
409 // has type CTypeInfo::kCallbackOptionsType, it is not included in
410 // ArgumentCount().
411 const CTypeInfo& ArgumentInfo(unsigned int index) const;
412
HasOptions() const413 bool HasOptions() const {
414 // The options arg is always the last one.
415 return arg_count_ > 0 && arg_info_[arg_count_ - 1].GetType() ==
416 CTypeInfo::kCallbackOptionsType;
417 }
418
419 private:
420 const CTypeInfo return_info_;
421 const unsigned int arg_count_;
422 const CTypeInfo* arg_info_;
423 };
424
425 struct FastApiCallbackOptions;
426
427 // Provided for testing.
428 struct AnyCType {
AnyCTypev8::AnyCType429 AnyCType() : int64_value(0) {}
430
431 union {
432 bool bool_value;
433 int32_t int32_value;
434 uint32_t uint32_value;
435 int64_t int64_value;
436 uint64_t uint64_value;
437 float float_value;
438 double double_value;
439 void* pointer_value;
440 Local<Object> object_value;
441 Local<Array> sequence_value;
442 const FastApiTypedArray<uint8_t>* uint8_ta_value;
443 const FastApiTypedArray<int32_t>* int32_ta_value;
444 const FastApiTypedArray<uint32_t>* uint32_ta_value;
445 const FastApiTypedArray<int64_t>* int64_ta_value;
446 const FastApiTypedArray<uint64_t>* uint64_ta_value;
447 const FastApiTypedArray<float>* float_ta_value;
448 const FastApiTypedArray<double>* double_ta_value;
449 const FastOneByteString* string_value;
450 FastApiCallbackOptions* options_value;
451 };
452 };
453
454 static_assert(
455 sizeof(AnyCType) == 8,
456 "The AnyCType struct should have size == 64 bits, as this is assumed "
457 "by EffectControlLinearizer.");
458
459 class V8_EXPORT CFunction {
460 public:
CFunction()461 constexpr CFunction() : address_(nullptr), type_info_(nullptr) {}
462
ReturnInfo() const463 const CTypeInfo& ReturnInfo() const { return type_info_->ReturnInfo(); }
464
ArgumentInfo(unsigned int index) const465 const CTypeInfo& ArgumentInfo(unsigned int index) const {
466 return type_info_->ArgumentInfo(index);
467 }
468
ArgumentCount() const469 unsigned int ArgumentCount() const { return type_info_->ArgumentCount(); }
470
GetAddress() const471 const void* GetAddress() const { return address_; }
GetTypeInfo() const472 const CFunctionInfo* GetTypeInfo() const { return type_info_; }
473
474 enum class OverloadResolution { kImpossible, kAtRuntime, kAtCompileTime };
475
476 // Returns whether an overload between this and the given CFunction can
477 // be resolved at runtime by the RTTI available for the arguments or at
478 // compile time for functions with different number of arguments.
GetOverloadResolution(const CFunction* other)479 OverloadResolution GetOverloadResolution(const CFunction* other) {
480 // Runtime overload resolution can only deal with functions with the
481 // same number of arguments. Functions with different arity are handled
482 // by compile time overload resolution though.
483 if (ArgumentCount() != other->ArgumentCount()) {
484 return OverloadResolution::kAtCompileTime;
485 }
486
487 // The functions can only differ by a single argument position.
488 int diff_index = -1;
489 for (unsigned int i = 0; i < ArgumentCount(); ++i) {
490 if (ArgumentInfo(i).GetSequenceType() !=
491 other->ArgumentInfo(i).GetSequenceType()) {
492 if (diff_index >= 0) {
493 return OverloadResolution::kImpossible;
494 }
495 diff_index = i;
496
497 // We only support overload resolution between sequence types.
498 if (ArgumentInfo(i).GetSequenceType() ==
499 CTypeInfo::SequenceType::kScalar ||
500 other->ArgumentInfo(i).GetSequenceType() ==
501 CTypeInfo::SequenceType::kScalar) {
502 return OverloadResolution::kImpossible;
503 }
504 }
505 }
506
507 return OverloadResolution::kAtRuntime;
508 }
509
510 template <typename F>
Make(F* func)511 static CFunction Make(F* func) {
512 return ArgUnwrap<F*>::Make(func);
513 }
514
515 // Provided for testing purposes.
516 template <typename R, typename... Args, typename R_Patch,
517 typename... Args_Patch>
Make(R (func)Args...), R_Patch (*patching_func)(Args_Patch...))518 static CFunction Make(R (*func)(Args...),
519 R_Patch (*patching_func)(Args_Patch...)) {
520 CFunction c_func = ArgUnwrap<R (*)(Args...)>::Make(func);
521 static_assert(
522 sizeof...(Args_Patch) == sizeof...(Args),
523 "The patching function must have the same number of arguments.");
524 c_func.address_ = reinterpret_cast<void*>(patching_func);
525 return c_func;
526 }
527
528 CFunction(const void* address, const CFunctionInfo* type_info);
529
530 private:
531 const void* address_;
532 const CFunctionInfo* type_info_;
533
534 template <typename F>
535 class ArgUnwrap {
536 static_assert(sizeof(F) != sizeof(F),
537 "CFunction must be created from a function pointer.");
538 };
539
540 template <typename R, typename... Args>
541 class ArgUnwrap<R (*)(Args...)> {
542 public:
543 static CFunction Make(R (*func)(Args...));
544 };
545 };
546
547 /**
548 * A struct which may be passed to a fast call callback, like so:
549 * \code
550 * void FastMethodWithOptions(int param, FastApiCallbackOptions& options);
551 * \endcode
552 */
553 struct FastApiCallbackOptions {
554 /**
555 * Creates a new instance of FastApiCallbackOptions for testing purpose. The
556 * returned instance may be filled with mock data.
557 */
CreateForTestingv8::FastApiCallbackOptions558 static FastApiCallbackOptions CreateForTesting(Isolate* isolate) {
559 return {false, {0}, nullptr};
560 }
561
562 /**
563 * If the callback wants to signal an error condition or to perform an
564 * allocation, it must set options.fallback to true and do an early return
565 * from the fast method. Then V8 checks the value of options.fallback and if
566 * it's true, falls back to executing the SlowCallback, which is capable of
567 * reporting the error (either by throwing a JS exception or logging to the
568 * console) or doing the allocation. It's the embedder's responsibility to
569 * ensure that the fast callback is idempotent up to the point where error and
570 * fallback conditions are checked, because otherwise executing the slow
571 * callback might produce visible side-effects twice.
572 */
573 bool fallback;
574
575 /**
576 * The `data` passed to the FunctionTemplate constructor, or `undefined`.
577 * `data_ptr` allows for default constructing FastApiCallbackOptions.
578 */
579 union {
580 uintptr_t data_ptr;
581 v8::Local<v8::Value> data;
582 };
583
584 /**
585 * When called from WebAssembly, a view of the calling module's memory.
586 */
587 FastApiTypedArray<uint8_t>* const wasm_memory;
588 };
589
590 namespace internal {
591
592 // Helper to count the number of occurances of `T` in `List`
593 template <typename T, typename... List>
594 struct count : std::integral_constant<int, 0> {};
595 template <typename T, typename... Args>
596 struct count<T, T, Args...>
597 : std::integral_constant<std::size_t, 1 + count<T, Args...>::value> {};
598 template <typename T, typename U, typename... Args>
599 struct count<T, U, Args...> : count<T, Args...> {};
600
601 template <typename RetBuilder, typename... ArgBuilders>
602 class CFunctionInfoImpl : public CFunctionInfo {
603 static constexpr int kOptionsArgCount =
604 count<FastApiCallbackOptions&, ArgBuilders...>();
605 static constexpr int kReceiverCount = 1;
606
607 static_assert(kOptionsArgCount == 0 || kOptionsArgCount == 1,
608 "Only one options parameter is supported.");
609
610 static_assert(sizeof...(ArgBuilders) >= kOptionsArgCount + kReceiverCount,
611 "The receiver or the options argument is missing.");
612
613 public:
CFunctionInfoImpl()614 constexpr CFunctionInfoImpl()
615 : CFunctionInfo(RetBuilder::Build(), sizeof...(ArgBuilders),
616 arg_info_storage_),
617 arg_info_storage_{ArgBuilders::Build()...} {
618 constexpr CTypeInfo::Type kReturnType = RetBuilder::Build().GetType();
619 static_assert(kReturnType == CTypeInfo::Type::kVoid ||
620 kReturnType == CTypeInfo::Type::kBool ||
621 kReturnType == CTypeInfo::Type::kInt32 ||
622 kReturnType == CTypeInfo::Type::kUint32 ||
623 kReturnType == CTypeInfo::Type::kFloat32 ||
624 kReturnType == CTypeInfo::Type::kFloat64 ||
625 kReturnType == CTypeInfo::Type::kPointer ||
626 kReturnType == CTypeInfo::Type::kAny,
627 "64-bit int, string and api object values are not currently "
628 "supported return types.");
629 }
630
631 private:
632 const CTypeInfo arg_info_storage_[sizeof...(ArgBuilders)];
633 };
634
635 template <typename T>
636 struct TypeInfoHelper {
637 static_assert(sizeof(T) != sizeof(T), "This type is not supported");
638 };
639
640 #define SPECIALIZE_GET_TYPE_INFO_HELPER_FOR(T, Enum) \
641 template <> \
642 struct TypeInfoHelper<T> { \
643 static constexpr CTypeInfo::Flags Flags() { \
644 return CTypeInfo::Flags::kNone; \
645 } \
646 \
647 static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::Enum; } \
648 static constexpr CTypeInfo::SequenceType SequenceType() { \
649 return CTypeInfo::SequenceType::kScalar; \
650 } \
651 };
652
653 template <CTypeInfo::Type type>
654 struct CTypeInfoTraits {};
655
656 #define DEFINE_TYPE_INFO_TRAITS(CType, Enum) \
657 template <> \
658 struct CTypeInfoTraits<CTypeInfo::Type::Enum> { \
659 using ctype = CType; \
660 };
661
662 #define PRIMITIVE_C_TYPES(V) \
663 V(bool, kBool) \
664 V(uint8_t, kUint8) \
665 V(int32_t, kInt32) \
666 V(uint32_t, kUint32) \
667 V(int64_t, kInt64) \
668 V(uint64_t, kUint64) \
669 V(float, kFloat32) \
670 V(double, kFloat64) \
671 V(void*, kPointer)
672
673 // Same as above, but includes deprecated types for compatibility.
674 #define ALL_C_TYPES(V) \
675 PRIMITIVE_C_TYPES(V) \
676 V(void, kVoid) \
677 V(v8::Local<v8::Value>, kV8Value) \
678 V(v8::Local<v8::Object>, kV8Value) \
679 V(AnyCType, kAny)
680
681 // ApiObject was a temporary solution to wrap the pointer to the v8::Value.
682 // Please use v8::Local<v8::Value> in new code for the arguments and
683 // v8::Local<v8::Object> for the receiver, as ApiObject will be deprecated.
684
685 ALL_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR)
686 PRIMITIVE_C_TYPES(DEFINE_TYPE_INFO_TRAITS)
687
688 #undef PRIMITIVE_C_TYPES
689 #undef ALL_C_TYPES
690
691 #define SPECIALIZE_GET_TYPE_INFO_HELPER_FOR_TA(T, Enum) \
692 template <> \
693 struct TypeInfoHelper<const FastApiTypedArray<T>&> { \
694 static constexpr CTypeInfo::Flags Flags() { \
695 return CTypeInfo::Flags::kNone; \
696 } \
697 \
698 static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::Enum; } \
699 static constexpr CTypeInfo::SequenceType SequenceType() { \
700 return CTypeInfo::SequenceType::kIsTypedArray; \
701 } \
702 };
703
704 #define TYPED_ARRAY_C_TYPES(V) \
705 V(uint8_t, kUint8) \
706 V(int32_t, kInt32) \
707 V(uint32_t, kUint32) \
708 V(int64_t, kInt64) \
709 V(uint64_t, kUint64) \
710 V(float, kFloat32) \
711 V(double, kFloat64)
712
713 TYPED_ARRAY_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR_TA)
714
715 #undef TYPED_ARRAY_C_TYPES
716
717 template <>
718 struct TypeInfoHelper<v8::Local<v8::Array>> {
Flagsv8::internal::TypeInfoHelper719 static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; }
720
Typev8::internal::TypeInfoHelper721 static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::kVoid; }
SequenceTypev8::internal::TypeInfoHelper722 static constexpr CTypeInfo::SequenceType SequenceType() {
723 return CTypeInfo::SequenceType::kIsSequence;
724 }
725 };
726
727 template <>
728 struct TypeInfoHelper<v8::Local<v8::Uint32Array>> {
Flagsv8::internal::TypeInfoHelper729 static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; }
730
Typev8::internal::TypeInfoHelper731 static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::kUint32; }
SequenceTypev8::internal::TypeInfoHelper732 static constexpr CTypeInfo::SequenceType SequenceType() {
733 return CTypeInfo::SequenceType::kIsTypedArray;
734 }
735 };
736
737 template <>
738 struct TypeInfoHelper<FastApiCallbackOptions&> {
Flagsv8::internal::TypeInfoHelper739 static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; }
740
Typev8::internal::TypeInfoHelper741 static constexpr CTypeInfo::Type Type() {
742 return CTypeInfo::kCallbackOptionsType;
743 }
SequenceTypev8::internal::TypeInfoHelper744 static constexpr CTypeInfo::SequenceType SequenceType() {
745 return CTypeInfo::SequenceType::kScalar;
746 }
747 };
748
749 template <>
750 struct TypeInfoHelper<const FastOneByteString&> {
Flagsv8::internal::TypeInfoHelper751 static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; }
752
Typev8::internal::TypeInfoHelper753 static constexpr CTypeInfo::Type Type() {
754 return CTypeInfo::Type::kSeqOneByteString;
755 }
SequenceTypev8::internal::TypeInfoHelper756 static constexpr CTypeInfo::SequenceType SequenceType() {
757 return CTypeInfo::SequenceType::kScalar;
758 }
759 };
760
761 #define STATIC_ASSERT_IMPLIES(COND, ASSERTION, MSG) \
762 static_assert(((COND) == 0) || (ASSERTION), MSG)
763
764 } // namespace internal
765
766 template <typename T, CTypeInfo::Flags... Flags>
767 class V8_EXPORT CTypeInfoBuilder {
768 public:
769 using BaseType = T;
770
Build()771 static constexpr CTypeInfo Build() {
772 constexpr CTypeInfo::Flags kFlags =
773 MergeFlags(internal::TypeInfoHelper<T>::Flags(), Flags...);
774 constexpr CTypeInfo::Type kType = internal::TypeInfoHelper<T>::Type();
775 constexpr CTypeInfo::SequenceType kSequenceType =
776 internal::TypeInfoHelper<T>::SequenceType();
777
778 STATIC_ASSERT_IMPLIES(
779 uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kAllowSharedBit),
780 (kSequenceType == CTypeInfo::SequenceType::kIsTypedArray ||
781 kSequenceType == CTypeInfo::SequenceType::kIsArrayBuffer),
782 "kAllowSharedBit is only allowed for TypedArrays and ArrayBuffers.");
783 STATIC_ASSERT_IMPLIES(
784 uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kEnforceRangeBit),
785 CTypeInfo::IsIntegralType(kType),
786 "kEnforceRangeBit is only allowed for integral types.");
787 STATIC_ASSERT_IMPLIES(
788 uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kClampBit),
789 CTypeInfo::IsIntegralType(kType),
790 "kClampBit is only allowed for integral types.");
791 STATIC_ASSERT_IMPLIES(
792 uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kIsRestrictedBit),
793 CTypeInfo::IsFloatingPointType(kType),
794 "kIsRestrictedBit is only allowed for floating point types.");
795 STATIC_ASSERT_IMPLIES(kSequenceType == CTypeInfo::SequenceType::kIsSequence,
796 kType == CTypeInfo::Type::kVoid,
797 "Sequences are only supported from void type.");
798 STATIC_ASSERT_IMPLIES(
799 kSequenceType == CTypeInfo::SequenceType::kIsTypedArray,
800 CTypeInfo::IsPrimitive(kType) || kType == CTypeInfo::Type::kVoid,
801 "TypedArrays are only supported from primitive types or void.");
802
803 // Return the same type with the merged flags.
804 return CTypeInfo(internal::TypeInfoHelper<T>::Type(),
805 internal::TypeInfoHelper<T>::SequenceType(), kFlags);
806 }
807
808 private:
809 template <typename... Rest>
MergeFlags(CTypeInfo::Flags flags, Rest... rest)810 static constexpr CTypeInfo::Flags MergeFlags(CTypeInfo::Flags flags,
811 Rest... rest) {
812 return CTypeInfo::Flags(uint8_t(flags) | uint8_t(MergeFlags(rest...)));
813 }
MergeFlags()814 static constexpr CTypeInfo::Flags MergeFlags() { return CTypeInfo::Flags(0); }
815 };
816
817 namespace internal {
818 template <typename RetBuilder, typename... ArgBuilders>
819 class CFunctionBuilderWithFunction {
820 public:
CFunctionBuilderWithFunction(const void* fn)821 explicit constexpr CFunctionBuilderWithFunction(const void* fn) : fn_(fn) {}
822
823 template <CTypeInfo::Flags... Flags>
Ret()824 constexpr auto Ret() {
825 return CFunctionBuilderWithFunction<
826 CTypeInfoBuilder<typename RetBuilder::BaseType, Flags...>,
827 ArgBuilders...>(fn_);
828 }
829
830 template <unsigned int N, CTypeInfo::Flags... Flags>
Arg()831 constexpr auto Arg() {
832 // Return a copy of the builder with the Nth arg builder merged with
833 // template parameter pack Flags.
834 return ArgImpl<N, Flags...>(
835 std::make_index_sequence<sizeof...(ArgBuilders)>());
836 }
837
838 // Provided for testing purposes.
839 template <typename Ret, typename... Args>
Patch(Ret (patching_func)Args...))840 auto Patch(Ret (*patching_func)(Args...)) {
841 static_assert(
842 sizeof...(Args) == sizeof...(ArgBuilders),
843 "The patching function must have the same number of arguments.");
844 fn_ = reinterpret_cast<void*>(patching_func);
845 return *this;
846 }
847
Build()848 auto Build() {
849 static CFunctionInfoImpl<RetBuilder, ArgBuilders...> instance;
850 return CFunction(fn_, &instance);
851 }
852
853 private:
854 template <bool Merge, unsigned int N, CTypeInfo::Flags... Flags>
855 struct GetArgBuilder;
856
857 // Returns the same ArgBuilder as the one at index N, including its flags.
858 // Flags in the template parameter pack are ignored.
859 template <unsigned int N, CTypeInfo::Flags... Flags>
860 struct GetArgBuilder<false, N, Flags...> {
861 using type =
862 typename std::tuple_element<N, std::tuple<ArgBuilders...>>::type;
863 };
864
865 // Returns an ArgBuilder with the same base type as the one at index N,
866 // but merges the flags with the flags in the template parameter pack.
867 template <unsigned int N, CTypeInfo::Flags... Flags>
868 struct GetArgBuilder<true, N, Flags...> {
869 using type = CTypeInfoBuilder<
870 typename std::tuple_element<N,
871 std::tuple<ArgBuilders...>>::type::BaseType,
872 std::tuple_element<N, std::tuple<ArgBuilders...>>::type::Build()
873 .GetFlags(),
874 Flags...>;
875 };
876
877 // Return a copy of the CFunctionBuilder, but merges the Flags on
878 // ArgBuilder index N with the new Flags passed in the template parameter
879 // pack.
880 template <unsigned int N, CTypeInfo::Flags... Flags, size_t... I>
ArgImpl(std::index_sequence<I...>)881 constexpr auto ArgImpl(std::index_sequence<I...>) {
882 return CFunctionBuilderWithFunction<
883 RetBuilder, typename GetArgBuilder<N == I, I, Flags...>::type...>(fn_);
884 }
885
886 const void* fn_;
887 };
888
889 class CFunctionBuilder {
890 public:
CFunctionBuilder()891 constexpr CFunctionBuilder() {}
892
893 template <typename R, typename... Args>
Fn(R (fn)Args...))894 constexpr auto Fn(R (*fn)(Args...)) {
895 return CFunctionBuilderWithFunction<CTypeInfoBuilder<R>,
896 CTypeInfoBuilder<Args>...>(
897 reinterpret_cast<const void*>(fn));
898 }
899 };
900
901 } // namespace internal
902
903 // static
904 template <typename R, typename... Args>
Make(R (func)Args...))905 CFunction CFunction::ArgUnwrap<R (*)(Args...)>::Make(R (*func)(Args...)) {
906 return internal::CFunctionBuilder().Fn(func).Build();
907 }
908
909 using CFunctionBuilder = internal::CFunctionBuilder;
910
911 static constexpr CTypeInfo kTypeInfoInt32 = CTypeInfo(CTypeInfo::Type::kInt32);
912 static constexpr CTypeInfo kTypeInfoFloat64 =
913 CTypeInfo(CTypeInfo::Type::kFloat64);
914
915 /**
916 * Copies the contents of this JavaScript array to a C++ buffer with
917 * a given max_length. A CTypeInfo is passed as an argument,
918 * instructing different rules for conversion (e.g. restricted float/double).
919 * The element type T of the destination array must match the C type
920 * corresponding to the CTypeInfo (specified by CTypeInfoTraits).
921 * If the array length is larger than max_length or the array is of
922 * unsupported type, the operation will fail, returning false. Generally, an
923 * array which contains objects, undefined, null or anything not convertible
924 * to the requested destination type, is considered unsupported. The operation
925 * returns true on success. `type_info` will be used for conversions.
926 */
927 template <CTypeInfo::Identifier type_info_id, typename T>
928 bool V8_EXPORT V8_WARN_UNUSED_RESULT TryToCopyAndConvertArrayToCppBuffer(
929 Local<Array> src, T* dst, uint32_t max_length);
930
931 template <>
932 bool V8_EXPORT V8_WARN_UNUSED_RESULT
933 TryToCopyAndConvertArrayToCppBuffer<CTypeInfoBuilder<int32_t>::Build().GetId(),
934 int32_t>(Local<Array> src, int32_t* dst,
935 uint32_t max_length);
936
937 template <>
938 bool V8_EXPORT V8_WARN_UNUSED_RESULT
939 TryToCopyAndConvertArrayToCppBuffer<CTypeInfoBuilder<uint32_t>::Build().GetId(),
940 uint32_t>(Local<Array> src, uint32_t* dst,
941 uint32_t max_length);
942
943 template <>
944 bool V8_EXPORT V8_WARN_UNUSED_RESULT
945 TryToCopyAndConvertArrayToCppBuffer<CTypeInfoBuilder<float>::Build().GetId(),
946 float>(Local<Array> src, float* dst,
947 uint32_t max_length);
948
949 template <>
950 bool V8_EXPORT V8_WARN_UNUSED_RESULT
951 TryToCopyAndConvertArrayToCppBuffer<CTypeInfoBuilder<double>::Build().GetId(),
952 double>(Local<Array> src, double* dst,
953 uint32_t max_length);
954
955 } // namespace v8
956
957 #endif // INCLUDE_V8_FAST_API_CALLS_H_
958