summaryrefslogtreecommitdiff
path: root/chromium/v8/include/v8-fast-api-calls.h
blob: 2dea8db271ad81fb80a7299c6ec4f3bf48a4dca7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
// 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::ApiObject receiver_obj, int param) {
 *        v8::Object* v8_object = reinterpret_cast<v8::Object*>(&api_object);
 *        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 only void return types are supported.
 * Currently supported argument types:
 *  - pointer to an embedder type
 *  - 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:
 *  - arrays of C types
 *  - arrays of embedder types
 */

#ifndef INCLUDE_V8_FAST_API_CALLS_H_
#define INCLUDE_V8_FAST_API_CALLS_H_

#include <stddef.h>
#include <stdint.h>

#include "v8config.h"  // NOLINT(build/include_directory)

namespace v8 {

class CTypeInfo {
 public:
  enum class Type : char {
    kVoid,
    kBool,
    kInt32,
    kUint32,
    kInt64,
    kUint64,
    kFloat32,
    kFloat64,
    kV8Value,
  };

  enum class ArgFlags : uint8_t {
    kNone = 0,
    kIsArrayBit = 1 << 0,  // This argument is first in an array of values.
  };

  static CTypeInfo FromWrapperType(ArgFlags flags = ArgFlags::kNone) {
    return CTypeInfo(static_cast<int>(flags) | kIsWrapperTypeBit);
  }

  static constexpr CTypeInfo FromCType(Type ctype,
                                       ArgFlags flags = ArgFlags::kNone) {
    // TODO(mslekova): Refactor the manual bit manipulations to use
    // PointerWithPayload instead.
    // ctype cannot be Type::kV8Value.
    return CTypeInfo(
        ((static_cast<uintptr_t>(ctype) << kTypeOffset) & kTypeMask) |
        static_cast<int>(flags));
  }

  const void* GetWrapperInfo() const;

  constexpr Type GetType() const {
    if (payload_ & kIsWrapperTypeBit) {
      return Type::kV8Value;
    }
    return static_cast<Type>((payload_ & kTypeMask) >> kTypeOffset);
  }

  constexpr bool IsArray() const {
    return payload_ & static_cast<int>(ArgFlags::kIsArrayBit);
  }

  static const CTypeInfo& Invalid() {
    static CTypeInfo invalid = CTypeInfo(0);
    return invalid;
  }

 private:
  explicit constexpr CTypeInfo(uintptr_t payload) : payload_(payload) {}

  // That must be the last bit after ArgFlags.
  static constexpr uintptr_t kIsWrapperTypeBit = 1 << 1;
  static constexpr uintptr_t kWrapperTypeInfoMask = static_cast<uintptr_t>(~0)
                                                    << 2;

  static constexpr unsigned int kTypeOffset = kIsWrapperTypeBit;
  static constexpr unsigned int kTypeSize = 8 - kTypeOffset;
  static constexpr uintptr_t kTypeMask =
      (~(static_cast<uintptr_t>(~0) << kTypeSize)) << kTypeOffset;

  const uintptr_t payload_;
};

class CFunctionInfo {
 public:
  virtual const CTypeInfo& ReturnInfo() const = 0;
  virtual unsigned int ArgumentCount() const = 0;
  virtual const CTypeInfo& ArgumentInfo(unsigned int index) const = 0;
};

struct ApiObject {
  uintptr_t address;
};

namespace internal {

template <typename T>
struct GetCType {
  static constexpr CTypeInfo Get() {
    return CTypeInfo::FromCType(CTypeInfo::Type::kV8Value);
  }
};

#define SPECIALIZE_GET_C_TYPE_FOR(ctype, ctypeinfo)            \
  template <>                                                  \
  struct GetCType<ctype> {                                     \
    static constexpr CTypeInfo Get() {                         \
      return CTypeInfo::FromCType(CTypeInfo::Type::ctypeinfo); \
    }                                                          \
  };

#define SUPPORTED_C_TYPES(V) \
  V(void, kVoid)             \
  V(bool, kBool)             \
  V(int32_t, kInt32)         \
  V(uint32_t, kUint32)       \
  V(int64_t, kInt64)         \
  V(uint64_t, kUint64)       \
  V(float, kFloat32)         \
  V(double, kFloat64)        \
  V(ApiObject, kV8Value)

SUPPORTED_C_TYPES(SPECIALIZE_GET_C_TYPE_FOR)

// T* where T is a primitive (array of primitives).
template <typename T, typename = void>
struct GetCTypePointerImpl {
  static constexpr CTypeInfo Get() {
    return CTypeInfo::FromCType(GetCType<T>::Get().GetType(),
                                CTypeInfo::ArgFlags::kIsArrayBit);
  }
};

// T* where T is an API object.
template <typename T>
struct GetCTypePointerImpl<T, void> {
  static constexpr CTypeInfo Get() { return CTypeInfo::FromWrapperType(); }
};

// T** where T is a primitive. Not allowed.
template <typename T, typename = void>
struct GetCTypePointerPointerImpl {
  static_assert(sizeof(T**) != sizeof(T**), "Unsupported type");
};

// T** where T is an API object (array of API objects).
template <typename T>
struct GetCTypePointerPointerImpl<T, void> {
  static constexpr CTypeInfo Get() {
    return CTypeInfo::FromWrapperType(CTypeInfo::ArgFlags::kIsArrayBit);
  }
};

template <typename T>
struct GetCType<T**> : public GetCTypePointerPointerImpl<T> {};

template <typename T>
struct GetCType<T*> : public GetCTypePointerImpl<T> {};

template <typename R, bool RaisesException, typename... Args>
class CFunctionInfoImpl : public CFunctionInfo {
 public:
  static constexpr int kFallbackArgCount = (RaisesException ? 1 : 0);
  static constexpr int kReceiverCount = 1;
  CFunctionInfoImpl()
      : return_info_(internal::GetCType<R>::Get()),
        arg_count_(sizeof...(Args) - kFallbackArgCount),
        arg_info_{internal::GetCType<Args>::Get()...} {
    static_assert(sizeof...(Args) >= kFallbackArgCount + kReceiverCount,
                  "The receiver or the fallback argument is missing.");
    static_assert(
        internal::GetCType<R>::Get().GetType() == CTypeInfo::Type::kVoid,
        "Only void return types are currently supported.");
  }

  const CTypeInfo& ReturnInfo() const override { return return_info_; }
  unsigned int ArgumentCount() const override { return arg_count_; }
  const CTypeInfo& ArgumentInfo(unsigned int index) const override {
    if (index >= ArgumentCount()) {
      return CTypeInfo::Invalid();
    }
    return arg_info_[index];
  }

 private:
  const CTypeInfo return_info_;
  const unsigned int arg_count_;
  const CTypeInfo arg_info_[sizeof...(Args)];
};

}  // namespace internal

class V8_EXPORT CFunction {
 public:
  constexpr CFunction() : address_(nullptr), type_info_(nullptr) {}

  const CTypeInfo& ReturnInfo() const { return type_info_->ReturnInfo(); }

  const CTypeInfo& ArgumentInfo(unsigned int index) const {
    return type_info_->ArgumentInfo(index);
  }

  unsigned int ArgumentCount() const { return type_info_->ArgumentCount(); }

  const void* GetAddress() const { return address_; }
  const CFunctionInfo* GetTypeInfo() const { return type_info_; }

  template <typename F>
  static CFunction Make(F* func) {
    return ArgUnwrap<F*>::Make(func);
  }

  template <typename F>
  static CFunction MakeWithFallbackSupport(F* func) {
    return ArgUnwrap<F*>::MakeWithFallbackSupport(func);
  }

  template <typename F>
  static CFunction Make(F* func, const CFunctionInfo* type_info) {
    return CFunction(reinterpret_cast<const void*>(func), type_info);
  }

 private:
  const void* address_;
  const CFunctionInfo* type_info_;

  CFunction(const void* address, const CFunctionInfo* type_info);

  template <typename R, bool RaisesException, typename... Args>
  static CFunctionInfo* GetCFunctionInfo() {
    static internal::CFunctionInfoImpl<R, RaisesException, Args...> instance;
    return &instance;
  }

  template <typename F>
  class ArgUnwrap {
    static_assert(sizeof(F) != sizeof(F),
                  "CFunction must be created from a function pointer.");
  };

  template <typename R, typename... Args>
  class ArgUnwrap<R (*)(Args...)> {
   public:
    static CFunction Make(R (*func)(Args...)) {
      return CFunction(reinterpret_cast<const void*>(func),
                       GetCFunctionInfo<R, false, Args...>());
    }
    static CFunction MakeWithFallbackSupport(R (*func)(Args...)) {
      return CFunction(reinterpret_cast<const void*>(func),
                       GetCFunctionInfo<R, true, Args...>());
    }
  };
};

struct FastApiCallbackOptions {
  bool fallback;
};

}  // namespace v8

#endif  // INCLUDE_V8_FAST_API_CALLS_H_