GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||
GeezerVison is showing github.com — feat: V8 fast API calls through Node-API (unstable extension) by edusperoni · Pull Request #2005 · NativeScript/android Conversation
|
| Layer | Mechanism | Question |
|---|---|---|
| Compile time | NODE_API_NS_FAST_CALLS_VERSION, always defined by the header |
"does this runtime offer it?" — lets a portable addon #ifdef / __has_include and keep one source for every runtime |
| Compile time | NODE_API_NS_EXPERIMENTAL_FAST_CALLS required, else hard #error |
"do you accept an unstable API?" — nobody lands here by accident |
| Run time | node_api_ns_fast_calls_available() |
"can a fast call actually fire?" — false on a jitless embed; derived from the app's V8 startup flags, not hardcoded |
#define NODE_API_NS_EXPERIMENTAL_FAST_CALLS #include <node_api_ns_fast.h> static int32_t FastAdd(ns_fast_receiver recv, int32_t a, int32_t b) { return a + b; } static napi_value SlowAdd(napi_env env, napi_callback_info info) { /* identical */ } ns_fast_descriptor d = {0}; d.slow_cb = SlowAdd; d.fast_fn = (const void*)FastAdd; d.return_type.type = ns_fast_int32; d.argc = 2; d.arg_types = params; // {ns_fast_int32}, {ns_fast_int32} node_api_ns_create_fast_function(env, "add", NAPI_AUTO_LENGTH, &d, &add);
ns_fast_type, ns_fast_param, flags). No V8 type ever reaches plugin source, so V8's churn — which has broken embedders twice already (the receiver parameter, the fallback field) — stays inside the runtime.fast_fn == NULL returns a plain Node-API function with zero wrapper overhead. That is the portable-addon path: one source compiles for this runtime and for the jitless iOS runtime, going fast only where the tier exists.ns_fast_options and throws through node_api_ns_fast_throw_{error,type_error,range_error}, which open the handle scope internally so no isolate is ever exposed.clamp, enforce_range, is_restricted, allow_shared) are validated at registration — a flag on a type that cannot carry it is napi_invalid_arg rather than a V8 assert somewhere later.Full contract, type table and "where it pays" in docs/node-api-fast-calls.md.
CFunctionInfo keeps its argument array by raw pointer, and V8 keeps the CFunctionInfo by pointer for the template's life, so both are interned per distinct signature and intentionally never freed — the shape Node gets for free from file-scope statics.js_native_api_v8.cc defines CallbackBundle/FunctionCallbackWrapper inside namespace v8impl { namespace { … } } — internal linkage — so no other translation unit can invoke a napi_callback or fabricate a napi_callback_info. The forwarding callback therefore calls a real function made by napi_create_function, preserving napi's argument/receiver/exception semantics exactly, at the cost of one extra call whenever the fast path is not taken. That is the warmup path for anything hot enough to want fast calls; the real fix would be upstream, not a local ODR hack against internal classes.ConstructorBehavior::kThrow): new would have to run the slow path with a new.target the forwarding cannot reproduce.fallible and int64_as_bigint.void only.Full suite on an arm64 emulator (API 35): 895 passing / 0 failing — the feat/node-api baseline plus 16 fast-call specs.
The load-bearing ones:
fastCallCount > 0 and fast + slow account for exactly all 300,000 calls (the addon counts each path separately).RangeError is identical from either path. All 300 throws are caught with the right message and code, and the caller keeps running afterwards.While testing I hit intermittent SIGSEGVs (2 of 4 runs) that are not from this branch or #2004. They land within ~40 ms of a Worker runtime starting, and the fast-call specs create no workers. The device carries tombstones for this test app in threads named W10: ./EvalWork dated 2026-07-30, 08-03, 08-11 and 08-12 — weeks before any Node-API work — the most recent faulting inside v8::base::OS::Abort(). Worth tracking separately (no issue filed yet); flagging here so it is not mistaken for fallout from this stack. It also explains an occasional timing failure in the Throw error in onerror worker spec.
Measured on this runtime (not borrowed figures): 2M calls of a two-int32 addition — the cheapest possible body, so essentially pure call overhead — from an optimized caller on an arm64 emulator. Absolute numbers are inflated by virtualization; the ratio is the meaningful part.
| ns/call | |
|---|---|
| Fast path (99.4% of calls; the rest ran before the caller tiered up) | 24 |
| Plain Node-API function | 860 |
≈ 35× on call overhead — the ceiling, not a typical result: as soon as the body does real work, or the arguments need anything the fast path forbids, the ratio collapses toward 1. The slow path's extra forwarding call is not separately measured.
FastApiCallbackOptions data pointer.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Lets an addon's hot leaf functions be called from optimized JS with unboxed arguments, without writing V8 code. Upstream Node-API declined to expose fast calls (nodejs/node#54731, not planned) because a fast callback may not call napi_*, so this is an embedder extension and is gated as explicitly unstable: node_api_ns_fast.h defines NODE_API_NS_FAST_CALLS_VERSION for compile-time feature detection and is a hard #error unless the addon first defines NODE_API_NS_EXPERIMENTAL_FAST_CALLS. - node_api_ns_create_fast_function() registers both paths from a descriptor struct: the addon's own ns_fast_* type vocabulary, so V8's CTypeInfo churn never reaches plugin source. Registering with fast_fn == NULL yields a plain Node-API function, which is what a portable addon compiles to on a jitless embed (iOS) where the fast path can never fire. - Fallible fast functions are supported (V8 12.6+ allows a fast callback to throw): the addon takes an opaque trailing options handle and throws through node_api_ns_fast_throw_{error,type_error,range_error}, which open the handle scope so no isolate reaches the addon. - CFunctionInfo keeps its argument array by pointer and V8 keeps the CFunctionInfo by pointer, so both are interned per signature and never freed. - The slow path forwards to a function made by napi_create_function: the vendored js_native_api_v8.cc builds napi_callback_info out of classes in an anonymous namespace, so no other translation unit can invoke a napi_callback directly. Costs one extra V8 call whenever the fast path is not taken. - node_api_ns_fast_calls_available() reports whether a fast call can fire at all, derived from the app's V8 startup flags rather than hardcoded. Verified on an arm64 emulator: after 300k calls through a monomorphic caller the fast path fires and fast+slow account for every call; semantics, WebIDL clamping, one-byte vs two-byte strings, slow-only registration and constructor rejection all hold. The addon counts throws raised inside the fast function so the specs can prove the throw shim ran under optimized code.
2M calls of a two-int32 addition from an optimized caller, arm64 emulator: 24 ns/call on the fast path (99.4% of calls) against 860 ns/call for a plain Node-API function — roughly 35x on call overhead for a body that does nothing else, which is the ceiling rather than a typical result. Replaces the borrowed Deno figure with our own, and states the emulator caveat.
6332c96 to
e1d3018
Compare
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.