Skip to content

Commit

Permalink
doc: add v8 fast api contribution guidelines
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Jan 13, 2023
1 parent f5dc92c commit 90c81b8
Showing 1 changed file with 150 additions and 0 deletions.
150 changes: 150 additions & 0 deletions doc/contributing/adding-v8-fast-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Adding V8 Fast API

Node.js uses [V8](https://github.com/v8/v8) as the JavaScript engine.
In order to provide fast paths for functions that are called quite often,
V8 proposes the usage of `fast api calls` that does not use any of the V8
internals, but uses internal C functions.

## Limitations

* Fast API functions can not use JavaScript heap allocation.
* Fast API functions can not trigger JavaScript execution.
* Reporting/returning errors is not available on fast API, but can be done
through fallbacks to slow API.
* Not all parameter and return types are supported in fast API calls.
For a full list, please look into
[`v8-fast-api-calls.h`](../../deps/v8/include/v8-fast-api-calls.h) file.

## Requirements

* Each unique fast API function signature should be defined inside the
[`external references`](../../src/node_external_reference.h) file.
* To test fast APIs, make sure to run the tests in a loop with a decent
iterations count to trigger V8 for optimization and to prefer the fast API
over slow one.

## Fallback to slow path

Fast API supports fallback to slow path (implementation that uses V8 internals)
in case logically it is wise to do so, for example when providing a more
detailed error. Fallback mechanism can be enabled, and changed from both caller
JavaScript function or from the fast API function declaration.

Every fast API function accessible from JavaScript side can pass an object
consisting of fallback key with a boolean value as the last parameter
(or the first parameter, if no parameters of the function exist).

In V8 the options fallback is defined as `FastApiCallbackOptions` inside
[`v8-fast-api-calls.h`](../../deps/v8/include/v8-fast-api-calls.h) file.

* JavaScript land

Example of a JavaScript call into a fast API:

```js
// Let divide be a function that provides fast api calls.
const { divide } = internalBinding('custom_namespace');

function divide(a, b) {
return divide(a, b, { fallback: a === 0 })
}
```

* C++ land

Example of a conditional fast path on C++

```cpp
// Anywhere in the execution flow, you can set fallback and stop the execution.
static double divide(const int32_t a,
const int32_t b,
v8::FastApiCallbackOptions& options) {
if (b == 0) {
options.fallback = true;
} else {
return a / b;
}
}
```

## Example

A typical function that communicates between JavaScript and C++ is as follows.

* On the JavaScript side:

```js
const { divide } = internalBinding('custom_namespace');
```

* On the C++ side:

```cpp
#include "v8-fast-api-calls.h"

namespace node {
namespace custom_namespace {

static void divide(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsInt32());
auto a = args[0].As<v8::Int32>();
auto b = args[1].As<v8::Int32>();

if (b->Value() == 0) {
return node::THROW_ERR_INVALID_STATE(env, "Error");
}

double result = a->Value() / b->Value();
args.GetReturnValue().Set(result);
}

static double FastDivide(const int32_t a,
const int32_t b,
v8::FastApiCallbackOptions& options) {
if (b == 0) {
options.fallback = true;
} else {
return a / b;
}
}

CFunction fast_divide_(CFunction::Make(FastDivide));

static void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
SetFastMethod(context, target, "divide", Divide, &fast_divide_);
}

void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(Divide);
registry->Register(FastDivide);
registry->Register(fast_divide_.GetTypeInfo());
}

} // namespace custom_namespace
} // namespace node

NODE_BINDING_CONTEXT_AWARE_INTERNAL(custom_namespace,
node::custom_namespace::Initialize);
NODE_BINDING_EXTERNAL_REFERENCE(
custom_namespace,
node::custom_namespace::RegisterExternalReferences);
```
* Update external references ([`node_external_reference.h`](../../src/node_external_reference.h))
Since our implementation used
`double(const int32_t a, const int32_t b, v8::FastApiCallbackOptions& options)`
signature, we need to add it to external references.

Example declaration:

```cpp
using CFunctionCallbackReturningDouble = double (*)(const int32_t a,
const int32_t b,
v8::FastApiCallbackOptions& options);
```

0 comments on commit 90c81b8

Please sign in to comment.