From f4c05cb9ca6a7ca7767dbdfe3661d532884d3ab0 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Fri, 13 Jan 2023 09:03:49 -0500 Subject: [PATCH 1/7] doc: add v8 fast api contribution guidelines --- doc/contributing/adding-v8-fast-api.md | 143 +++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 doc/contributing/adding-v8-fast-api.md diff --git a/doc/contributing/adding-v8-fast-api.md b/doc/contributing/adding-v8-fast-api.md new file mode 100644 index 00000000000000..e26d7d6b79aab7 --- /dev/null +++ b/doc/contributing/adding-v8-fast-api.md @@ -0,0 +1,143 @@ +# Adding V8 Fast API + +Node.js uses [V8](https://v8.dev/) as its JavaScript engine. +Embedder functions implemented in C++ incur a high overhead, so V8 +provides an API to implement fast-path C functions which may be invoked directly +from JITted code. These functions also come with additional constraints, +for example, they may not trigger garbage collection. + +## Limitations + +* Fast API functions may not trigger garbage collection. This means by proxy + that JavaScript execution and heap allocation are also forbidden. +* Throwing errors is not available on fast API, but can be done + through the fallback 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. +* The fast callback must be idempotent up to the point where error and fallback + conditions are checked, because otherwise executing the slow callback might + produce visible side effects twice. +* +## Fallback to slow path + +Fast API supports fallback to slow path in case logically it is wise to do so, +for example when providing a detailed error, or need to trigger JavaScript. +Fallback mechanism can be enabled and changed from the C++ implementation of +the fast API function declaration. + +Passing a `true` value to `fallback` option will force V8 to run the slow path +with the same arguments. + +In V8, the options fallback is defined as `FastApiCallbackOptions` inside +[`v8-fast-api-calls.h`](../../deps/v8/include/v8-fast-api-calls.h). + +* 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; + return 0; + } 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& args) { + Environment* env = Environment::GetCurrent(args); + CHECK_GE(args.Length(), 2); + CHECK(args[0]->IsInt32()); + CHECK(args[1]->IsInt32()); + auto a = args[0].As(); + auto b = args[1].As(); + + 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 target, + Local unused, + Local 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 and in + `ALLOWED_EXTERNAL_REFERENCE_TYPES`. + + Example declaration: + + ```cpp + using CFunctionCallbackReturningDouble = double (*)(const int32_t a, + const int32_t b, + v8::FastApiCallbackOptions& options); + ``` From b79e5c824250eb8225c3c990f144aa4885cee37a Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 16 Jan 2023 09:19:47 -0500 Subject: [PATCH 2/7] fixup! doc: add v8 fast api contribution guidelines --- doc/contributing/adding-v8-fast-api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/contributing/adding-v8-fast-api.md b/doc/contributing/adding-v8-fast-api.md index e26d7d6b79aab7..c2594f7f397eb6 100644 --- a/doc/contributing/adding-v8-fast-api.md +++ b/doc/contributing/adding-v8-fast-api.md @@ -97,6 +97,7 @@ A typical function that communicates between JavaScript and C++ is as follows. v8::FastApiCallbackOptions& options) { if (b == 0) { options.fallback = true; + return 0; } else { return a / b; } From 20446b5760233f22a50c93da772be995ac23bbdf Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 16 Jan 2023 09:32:52 -0500 Subject: [PATCH 3/7] fixup! doc: add v8 fast api contribution guidelines --- doc/contributing/adding-v8-fast-api.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/contributing/adding-v8-fast-api.md b/doc/contributing/adding-v8-fast-api.md index c2594f7f397eb6..bd233ea74db855 100644 --- a/doc/contributing/adding-v8-fast-api.md +++ b/doc/contributing/adding-v8-fast-api.md @@ -9,7 +9,8 @@ for example, they may not trigger garbage collection. ## Limitations * Fast API functions may not trigger garbage collection. This means by proxy - that JavaScript execution and heap allocation are also forbidden. + that JavaScript execution and heap allocation are also forbidden, including + `v8::Array::Get()` or `v8::Number::New()`. * Throwing errors is not available on fast API, but can be done through the fallback to slow API. * Not all parameter and return types are supported in fast API calls. @@ -26,7 +27,7 @@ for example, they may not trigger garbage collection. * The fast callback must be idempotent up to the point where error and fallback conditions are checked, because otherwise executing the slow callback might produce visible side effects twice. -* + ## Fallback to slow path Fast API supports fallback to slow path in case logically it is wise to do so, From 9acab7d36a9fe27da325aad3a33af272577c65fe Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Wed, 18 Jan 2023 08:29:59 -0500 Subject: [PATCH 4/7] fixup! doc: add v8 fast api contribution guidelines --- doc/contributing/adding-v8-fast-api.md | 17 +++++++++++------ src/README.md | 4 ++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/doc/contributing/adding-v8-fast-api.md b/doc/contributing/adding-v8-fast-api.md index bd233ea74db855..a09f77267148d8 100644 --- a/doc/contributing/adding-v8-fast-api.md +++ b/doc/contributing/adding-v8-fast-api.md @@ -19,8 +19,13 @@ for example, they may not trigger garbage collection. ## Requirements -* Each unique fast API function signature should be defined inside the +* Any function passed to `CFunction::Make`, including fast API function + declarations, should have their signature registered in [`external references`](../../src/node_external_reference.h) file. + Although, it would not start failing or crashing until the function end up + in a snapshot (either the built-in or a user-land one). Please refer to + [binding functions documentation](../../src#binding-functions) for more + information. * 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. @@ -31,7 +36,7 @@ for example, they may not trigger garbage collection. ## Fallback to slow path Fast API supports fallback to slow path in case logically it is wise to do so, -for example when providing a detailed error, or need to trigger JavaScript. +for example when a detailed error or JavaScript execution is needed. Fallback mechanism can be enabled and changed from the C++ implementation of the fast API function declaration. @@ -77,7 +82,7 @@ A typical function that communicates between JavaScript and C++ is as follows. namespace node { namespace custom_namespace { - static void divide(const FunctionCallbackInfo& args) { + static void SlowDivide(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_GE(args.Length(), 2); CHECK(args[0]->IsInt32()); @@ -90,7 +95,7 @@ A typical function that communicates between JavaScript and C++ is as follows. } double result = a->Value() / b->Value(); - args.GetReturnValue().Set(result); + args.GetReturnValue().Set(v8::Number::New(env->isolate(), result)); } static double FastDivide(const int32_t a, @@ -110,11 +115,11 @@ A typical function that communicates between JavaScript and C++ is as follows. Local unused, Local context, void* priv) { - SetFastMethod(context, target, "divide", Divide, &fast_divide_); + SetFastMethod(context, target, "divide", SlowDivide, &fast_divide_); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { - registry->Register(Divide); + registry->Register(SlowDivide); registry->Register(FastDivide); registry->Register(fast_divide_.GetTypeInfo()); } diff --git a/src/README.md b/src/README.md index f5463cd3606298..4c234328c5de36 100644 --- a/src/README.md +++ b/src/README.md @@ -31,6 +31,9 @@ embedder API. Important concepts when using V8 are the ones of [`Isolate`][]s and [JavaScript value handles][]. +V8 supports fast-path C functions called [V8 Fast API][] +which is useful for improving the performance in certain cases. + ## libuv API documentation The other major dependency of Node.js is [libuv][], providing @@ -1061,3 +1064,4 @@ static void GetUserInfo(const FunctionCallbackInfo& args) { [libuv handles]: #libuv-handles-and-requests [libuv requests]: #libuv-handles-and-requests [reference documentation for the libuv API]: http://docs.libuv.org/en/v1.x/ +[V8 Fast API]: ../doc/contributing/adding-v8-fast-api.md From cb3f47aefd34c2a58356ed7c9319c566fe33a131 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Wed, 18 Jan 2023 08:43:40 -0500 Subject: [PATCH 5/7] fixup! doc: add v8 fast api contribution guidelines --- src/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/README.md b/src/README.md index 4c234328c5de36..aadf478fbc1203 100644 --- a/src/README.md +++ b/src/README.md @@ -1043,6 +1043,7 @@ static void GetUserInfo(const FunctionCallbackInfo& args) { [`MakeCallback()`]: #makecallback [`MessagePort`]: https://nodejs.org/api/worker_threads.html#worker_threads_class_messageport [`ReqWrap`]: #reqwrap +[V8 Fast API]: ../doc/contributing/adding-v8-fast-api.md [`async_hooks` module]: https://nodejs.org/api/async_hooks.html [`async_wrap.h`]: async_wrap.h [`base_object.h`]: base_object.h @@ -1064,4 +1065,3 @@ static void GetUserInfo(const FunctionCallbackInfo& args) { [libuv handles]: #libuv-handles-and-requests [libuv requests]: #libuv-handles-and-requests [reference documentation for the libuv API]: http://docs.libuv.org/en/v1.x/ -[V8 Fast API]: ../doc/contributing/adding-v8-fast-api.md From 269c8f26c7b30da98d252815f6c659484064ad09 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Wed, 18 Jan 2023 09:03:36 -0500 Subject: [PATCH 6/7] fixup! doc: add v8 fast api contribution guidelines --- src/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/README.md b/src/README.md index aadf478fbc1203..166b07f2241328 100644 --- a/src/README.md +++ b/src/README.md @@ -1032,6 +1032,7 @@ static void GetUserInfo(const FunctionCallbackInfo& args) { [Callback scopes]: #callback-scopes [JavaScript value handles]: #js-handles [N-API]: https://nodejs.org/api/n-api.html +[V8 Fast API]: ../doc/contributing/adding-v8-fast-api.md [`BaseObject`]: #baseobject [`Context`]: #context [`Environment`]: #environment @@ -1043,7 +1044,6 @@ static void GetUserInfo(const FunctionCallbackInfo& args) { [`MakeCallback()`]: #makecallback [`MessagePort`]: https://nodejs.org/api/worker_threads.html#worker_threads_class_messageport [`ReqWrap`]: #reqwrap -[V8 Fast API]: ../doc/contributing/adding-v8-fast-api.md [`async_hooks` module]: https://nodejs.org/api/async_hooks.html [`async_wrap.h`]: async_wrap.h [`base_object.h`]: base_object.h From a2e2d67c7400bd803dcf5ed59b5c85ba6a898cce Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 23 Jan 2023 17:27:49 -0500 Subject: [PATCH 7/7] fixup! doc: add v8 fast api contribution guidelines --- doc/contributing/adding-v8-fast-api.md | 30 +++++++++++++------------- src/README.md | 6 +++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/doc/contributing/adding-v8-fast-api.md b/doc/contributing/adding-v8-fast-api.md index a09f77267148d8..d78c433ed5e72c 100644 --- a/doc/contributing/adding-v8-fast-api.md +++ b/doc/contributing/adding-v8-fast-api.md @@ -1,8 +1,8 @@ # Adding V8 Fast API Node.js uses [V8](https://v8.dev/) as its JavaScript engine. -Embedder functions implemented in C++ incur a high overhead, so V8 -provides an API to implement fast-path C functions which may be invoked directly +Embedding functions implemented in C++ incur a high overhead, so V8 +provides an API to implement native functions which may be invoked directly from JITted code. These functions also come with additional constraints, for example, they may not trigger garbage collection. @@ -11,36 +11,36 @@ for example, they may not trigger garbage collection. * Fast API functions may not trigger garbage collection. This means by proxy that JavaScript execution and heap allocation are also forbidden, including `v8::Array::Get()` or `v8::Number::New()`. -* Throwing errors is not available on fast API, but can be done - through the fallback to slow API. +* Throwing errors is not available from within a fast API call, but can be done + through the fallback to the 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. + [`v8-fast-api-calls.h`](../../deps/v8/include/v8-fast-api-calls.h). ## Requirements * Any function passed to `CFunction::Make`, including fast API function declarations, should have their signature registered in - [`external references`](../../src/node_external_reference.h) file. - Although, it would not start failing or crashing until the function end up - in a snapshot (either the built-in or a user-land one). Please refer to + [`node_external_reference.h`](../../src/node_external_reference.h) file. + Although, it would not start failing or crashing until the function ends up + in a snapshot (either the built-in or a user-land one). Please refer to the [binding functions documentation](../../src#binding-functions) for more information. * 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. + iterations count to trigger relevant optimizations that prefer the fast API + over the slow one. * The fast callback must be idempotent up to the point where error and fallback conditions are checked, because otherwise executing the slow callback might produce visible side effects twice. ## Fallback to slow path -Fast API supports fallback to slow path in case logically it is wise to do so, -for example when a detailed error or JavaScript execution is needed. -Fallback mechanism can be enabled and changed from the C++ implementation of -the fast API function declaration. +Fast API supports fallback to slow path for when it is desirable to do so, +for example, when throwing a custom error or executing JavaScript code is +needed. The fallback mechanism can be enabled and changed from the C++ +implementation of the fast API function declaration. -Passing a `true` value to `fallback` option will force V8 to run the slow path +Passing `true` to the `fallback` option will force V8 to run the slow path with the same arguments. In V8, the options fallback is defined as `FastApiCallbackOptions` inside diff --git a/src/README.md b/src/README.md index 166b07f2241328..f1af5db84a4f9a 100644 --- a/src/README.md +++ b/src/README.md @@ -31,8 +31,8 @@ embedder API. Important concepts when using V8 are the ones of [`Isolate`][]s and [JavaScript value handles][]. -V8 supports fast-path C functions called [V8 Fast API][] -which is useful for improving the performance in certain cases. +V8 supports [fast API calls][], which can be useful for improving the +performance in certain cases. ## libuv API documentation @@ -1032,7 +1032,6 @@ static void GetUserInfo(const FunctionCallbackInfo& args) { [Callback scopes]: #callback-scopes [JavaScript value handles]: #js-handles [N-API]: https://nodejs.org/api/n-api.html -[V8 Fast API]: ../doc/contributing/adding-v8-fast-api.md [`BaseObject`]: #baseobject [`Context`]: #context [`Environment`]: #environment @@ -1059,6 +1058,7 @@ static void GetUserInfo(const FunctionCallbackInfo& args) { [cleanup hooks]: #cleanup-hooks [event loop]: #event-loop [exception handling]: #exception-handling +[fast API calls]: ../doc/contributing/adding-v8-fast-api.md [internal field]: #internal-fields [introduction for V8 embedders]: https://v8.dev/docs/embed [libuv]: https://libuv.org/