1# Adding V8 Fast API
2
3Node.js uses [V8](https://v8.dev/) as its JavaScript engine.
4Embedding functions implemented in C++ incur a high overhead, so V8
5provides an API to implement native functions which may be invoked directly
6from JIT-ed code. These functions also come with additional constraints,
7for example, they may not trigger garbage collection.
8
9## Limitations
10
11* Fast API functions may not trigger garbage collection. This means by proxy
12  that JavaScript execution and heap allocation are also forbidden, including
13  `v8::Array::Get()` or `v8::Number::New()`.
14* Throwing errors is not available from within a fast API call, but can be done
15  through the fallback to the slow API.
16* Not all parameter and return types are supported in fast API calls.
17  For a full list, please look into
18  [`v8-fast-api-calls.h`](../../deps/v8/include/v8-fast-api-calls.h).
19
20## Requirements
21
22* Any function passed to `CFunction::Make`, including fast API function
23  declarations, should have their signature registered in
24  [`node_external_reference.h`](../../src/node_external_reference.h) file.
25  Although, it would not start failing or crashing until the function ends up
26  in a snapshot (either the built-in or a user-land one). Please refer to the
27  [binding functions documentation](../../src#binding-functions) for more
28  information.
29* To test fast APIs, make sure to run the tests in a loop with a decent
30  iterations count to trigger relevant optimizations that prefer the fast API
31  over the slow one.
32* The fast callback must be idempotent up to the point where error and fallback
33  conditions are checked, because otherwise executing the slow callback might
34  produce visible side effects twice.
35
36## Fallback to slow path
37
38Fast API supports fallback to slow path for when it is desirable to do so,
39for example, when throwing a custom error or executing JavaScript code is
40needed. The fallback mechanism can be enabled and changed from the C++
41implementation of the fast API function declaration.
42
43Passing `true` to the `fallback` option will force V8 to run the slow path
44with the same arguments.
45
46In V8, the options fallback is defined as `FastApiCallbackOptions` inside
47[`v8-fast-api-calls.h`](../../deps/v8/include/v8-fast-api-calls.h).
48
49* C++ land
50
51  Example of a conditional fast path on C++
52
53  ```cpp
54  // Anywhere in the execution flow, you can set fallback and stop the execution.
55  static double divide(const int32_t a,
56                       const int32_t b,
57                       v8::FastApiCallbackOptions& options) {
58    if (b == 0) {
59      options.fallback = true;
60      return 0;
61    } else {
62      return a / b;
63    }
64  }
65  ```
66
67## Example
68
69A typical function that communicates between JavaScript and C++ is as follows.
70
71* On the JavaScript side:
72
73  ```js
74  const { divide } = internalBinding('custom_namespace');
75  ```
76
77* On the C++ side:
78
79  ```cpp
80  #include "v8-fast-api-calls.h"
81
82  namespace node {
83  namespace custom_namespace {
84
85  static void SlowDivide(const FunctionCallbackInfo<Value>& args) {
86    Environment* env = Environment::GetCurrent(args);
87    CHECK_GE(args.Length(), 2);
88    CHECK(args[0]->IsInt32());
89    CHECK(args[1]->IsInt32());
90    auto a = args[0].As<v8::Int32>();
91    auto b = args[1].As<v8::Int32>();
92
93    if (b->Value() == 0) {
94      return node::THROW_ERR_INVALID_STATE(env, "Error");
95    }
96
97    double result = a->Value() / b->Value();
98    args.GetReturnValue().Set(v8::Number::New(env->isolate(), result));
99  }
100
101  static double FastDivide(const int32_t a,
102                           const int32_t b,
103                           v8::FastApiCallbackOptions& options) {
104    if (b == 0) {
105      options.fallback = true;
106      return 0;
107    } else {
108      return a / b;
109    }
110  }
111
112  CFunction fast_divide_(CFunction::Make(FastDivide));
113
114  static void Initialize(Local<Object> target,
115                         Local<Value> unused,
116                         Local<Context> context,
117                         void* priv) {
118    SetFastMethod(context, target, "divide", SlowDivide, &fast_divide_);
119  }
120
121  void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
122    registry->Register(SlowDivide);
123    registry->Register(FastDivide);
124    registry->Register(fast_divide_.GetTypeInfo());
125  }
126
127  } // namespace custom_namespace
128  } // namespace node
129
130  NODE_BINDING_CONTEXT_AWARE_INTERNAL(custom_namespace,
131                                      node::custom_namespace::Initialize);
132  NODE_BINDING_EXTERNAL_REFERENCE(
133                        custom_namespace,
134                        node::custom_namespace::RegisterExternalReferences);
135  ```
136
137* Update external references ([`node_external_reference.h`](../../src/node_external_reference.h))
138
139  Since our implementation used
140  `double(const int32_t a, const int32_t b, v8::FastApiCallbackOptions& options)`
141  signature, we need to add it to external references and in
142  `ALLOWED_EXTERNAL_REFERENCE_TYPES`.
143
144  Example declaration:
145
146  ```cpp
147  using CFunctionCallbackReturningDouble = double (*)(const int32_t a,
148                                                      const int32_t b,
149                                                      v8::FastApiCallbackOptions& options);
150  ```
151