From fb2d944fc9d89d99672eaf28673adc1b3ba03f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sat, 20 Sep 2025 20:48:38 +0200 Subject: [PATCH] doc: remove fast fallback information from v8 fast api contribution docs --- doc/contributing/adding-v8-fast-api.md | 104 +++++++------------------ 1 file changed, 29 insertions(+), 75 deletions(-) diff --git a/doc/contributing/adding-v8-fast-api.md b/doc/contributing/adding-v8-fast-api.md index e26bc841121e66..f45ebe98f628ff 100644 --- a/doc/contributing/adding-v8-fast-api.md +++ b/doc/contributing/adding-v8-fast-api.md @@ -11,8 +11,8 @@ 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 from within a fast API call, but can be done - through the fallback to the slow API. +* Throwing errors is not available from within a fast API call. If you need to + throw, perform validation in JavaScript or only expose a 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). @@ -28,9 +28,6 @@ for example, they may not trigger garbage collection. information. * Fast API functions must be tested following the example in [Test with Fast API path](#test-with-fast-api-path). -* 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. * If the receiver is used in the callback, it must be passed as a second argument, leaving the first one unused, to prevent the JS land from accidentally omitting the receiver when invoking the fast API method. @@ -42,44 +39,12 @@ for example, they may not trigger garbage collection. static int32_t FastInternalModuleStat( Local unused, Local recv, - const FastOneByteString& input, - FastApiCallbackOptions& options) { + const FastOneByteString& input) { Environment* env = Environment::GetCurrent(recv->GetCreationContextChecked()); // More code } ``` -## Fallback to slow path - -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 `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 -[`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. @@ -87,7 +52,7 @@ A typical function that communicates between JavaScript and C++ is as follows. * On the JavaScript side: ```js - const { divide } = internalBinding('custom_namespace'); + const { add } = internalBinding('custom_namespace'); ``` * On the C++ side: @@ -99,7 +64,11 @@ A typical function that communicates between JavaScript and C++ is as follows. namespace node { namespace custom_namespace { - static void SlowDivide(const FunctionCallbackInfo& args) { + static inline int32_t AddImpl(const int32_t a, const int32_t b) { + return a + b; + } + + static void SlowAdd(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_GE(args.Length(), 2); CHECK(args[0]->IsInt32()); @@ -107,40 +76,29 @@ A typical function that communicates between JavaScript and C++ is as follows. 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(v8::Number::New(env->isolate(), result)); + int32_t result = AddImpl(a->Value(), b->Value()); + args.GetReturnValue().Set(v8::Int32::New(env->isolate(), result)); } - static double FastDivide(const int32_t a, - const int32_t b, - v8::FastApiCallbackOptions& options) { - if (b == 0) { - TRACK_V8_FAST_API_CALL("custom_namespace.divide.error"); - options.fallback = true; - return 0; - } else { - TRACK_V8_FAST_API_CALL("custom_namespace.divide.ok"); - return a / b; - } + static int32_t FastAdd(const int32_t a, + const int32_t b) { + TRACK_V8_FAST_API_CALL("custom_namespace.add"); + return AddImpl(a, b); } - CFunction fast_divide_(CFunction::Make(FastDivide)); + CFunction fast_add_(CFunction::Make(FastAdd)); static void Initialize(Local target, Local unused, Local context, void* priv) { - SetFastMethod(context, target, "divide", SlowDivide, &fast_divide_); + SetFastMethod(context, target, "add", SlowAdd, &fast_add_); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { - registry->Register(SlowDivide); - registry->Register(FastDivide); - registry->Register(fast_divide_.GetTypeInfo()); + registry->Register(SlowAdd); + registry->Register(FastAdd); + registry->Register(fast_add_.GetTypeInfo()); } } // namespace custom_namespace @@ -156,16 +114,15 @@ A typical function that communicates between JavaScript and C++ is as follows. * 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)` + `int32_t(const int32_t a, const int32_t b)` 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); + using CFunctionCallbackReturningInt32 = int32_t (*)(const int32_t a, + const int32_t b); ``` ### Test with Fast API path @@ -188,27 +145,24 @@ const common = require('../common'); const { internalBinding } = require('internal/test/binding'); // We could also require a function that uses the internal binding internally. -const { divide } = internalBinding('custom_namespace'); +const { add } = internalBinding('custom_namespace'); // The function that will be optimized. It has to be a function written in // JavaScript. Since `divide` comes from the C++ side, we need to wrap it. function testFastPath(a, b) { - return divide(a, b); + return add(a, b); } eval('%PrepareFunctionForOptimization(testFastPath)'); // This call will let V8 know about the argument types that the function expects. -assert.strictEqual(testFastPath(6, 3), 2); +assert.strictEqual(testFastPath(6, 3), 9); eval('%OptimizeFunctionOnNextCall(testFastPath)'); -assert.strictEqual(testFastPath(8, 2), 4); -assert.throws(() => testFastPath(1, 0), { - code: 'ERR_INVALID_STATE', -}); +assert.strictEqual(testFastPath(8, 2), 10); +assert.strictEqual(testFastPath(1, 0), 1); if (common.isDebug) { const { getV8FastApiCallCount } = internalBinding('debug'); - assert.strictEqual(getV8FastApiCallCount('custom_namespace.divide.ok'), 1); - assert.strictEqual(getV8FastApiCallCount('custom_namespace.divide.error'), 1); + assert.strictEqual(getV8FastApiCallCount('custom_namespace.add'), 2); } ```