Skip to content

V8 Fast API assessment

Eugene Lazutkin edited this page May 12, 2026 · 1 revision

A code-level assessment of V8's Fast API (v8-fast-api-calls.h) as a potential optimization vector for node-re2. Recorded 2026-05-12. Conclusion: not a fit today for any node-re2 use case, for reasons that compound across availability, type support, and stability stance. The boundary-shift performance intent is real and pursued via N-API levers instead — see discussions/#218 and the project queue.

What Fast API is

V8 provides a mechanism by which TurboFan / Maglev can compile JS call sites that invoke registered C++ functions into direct machine-code calls, bypassing the normal v8::FunctionCallback overhead. The introduction blog post (yagiz.co, January 2023) framed it as removing "the high overhead of traditional embedder functions" — measured in tens of nanoseconds per call.

The mechanism: a C++ function registered with a Fast-API signature can be invoked from JIT-compiled JS code without entering V8's runtime. Inputs are passed in C types (int32_t, double, v8::FastOneByteString&, etc.); the runtime synthesises a slow-path fallback of the same function for cases where the fast path cannot be taken.

The relevant V8 header is v8-fast-api-calls.h; Node's own internal contributing doc is adding-v8-fast-api.md.

Supported types

Per the Node internal-bindings documentation and the v8-dev April 2025 thread:

Arguments:

  • Primitives: bool, int32_t, uint32_t, int64_t, uint64_t, float, double
  • v8::Local<v8::Value> — any JS value, but typically loses the fast-path benefit because the C++ has to inspect/unwrap
  • v8::FastOneByteString& — the only fast string type. Verbatim from the Node doc: "only allows sequential one-byte strings, which is often not useful"
  • External pointers (opaque)

Returns: integers, floats, void, pointer-as-External. No Local<Value> returns. Andreas Haas (V8) explained in the v8-dev thread that a Local<Value> lives in a HandleScope that closes when the function returns, and forcing the scope open would defeat the speed purpose. No roadmap to add object/string returns.

TypedArray: support has been deprecated due to bugs. Workaround per the v8-dev thread: use External pointers for FFI instead. The path "pass a TypedArray of UTF-8 bytes, return a TypedArray of result bytes" — the natural shape for RE2 work — does not exist as a fast call today.

Addon-facing availability — closed door

Fast API is implemented in V8 and used internally by Node.js core. It is not exposed to external addons through any officially supported path:

  • Node-API does not expose Fast API. No napi_* symbols for registering fast calls; no v8-fast-api-calls.h consumption from node_api.h.
  • v8-fast-api-calls.h is not shipped in the addon headers directory. nodejs/node#55725 ("Missing v8-fast-api-calls.h header & FastApiTypedArray symbols", opened Nov 2024) was closed as not-planned with no maintainer commitment to ship the header to addons.
  • nan technically exposes raw V8 headers but does not wrap Fast API; using it requires writing direct V8 code and patching Node to ship the missing header.

Net: Fast API is a Node-core-internal-bindings feature today. Both addon APIs we'd consider (nan, Node-API) sit outside it.

Activity and trajectory

Signal State
V8 team stance (Andreas Haas, v8-dev April 2025) "still considered unstable", "APIs are out-dated and would benefit from cleanup"
Node-side champion ronag — Fast-API commits last on 2024-09-05 ("buffer: re-enable Fast API for Buffer.write after fixing UTF8 handling"); no Fast-API commits since
Node-side champion Yagiz Nizipli still active in 2025, but his April 2025 v8-dev post is essentially "is this stable yet?"
Addon-facing header export (nodejs/node#55725) closed not-planned, Nov 2024
TypedArray fast support deprecated
New addon-facing API none announced

Net read: stagnant for the addon use case. Node core continues to opportunistically wire Fast API into hot paths (Buffer write, indexOf, etc.); the addon-facing surface has moved backwards (TypedArray deprecated) and forwards (V8 12.6 lifted some HandleScope / exception constraints) at roughly the same rate. No commitment to public-API stability.

Why it does not fit node-re2

Four compounding blockers.

  1. API path missing. Neither nan nor N-API exposes Fast API. The header is not shipped to addons. #55725 closed not-planned. Using Fast API would require patching Node or shipping a forked binding system — outside scope.
  2. Strings don't match. node-re2 inputs are arbitrary Unicode. JS strings are V8-internal Latin-1 (one-byte) or UTF-16 (two-byte); RE2 wants UTF-8. The only fast-path string type, FastOneByteString, accepts only sequential Latin-1 — and even then does not help with the UTF-8 conversion that dominates per-call cost for non-ASCII data.
  3. Buffer-as-input path closed. The natural shape for RE2 input is "pointer + length to bytes" — TypedArray or Buffer. TypedArray fast support is deprecated; the only remaining path is External pointers, which requires pre-registering every input buffer as an External — defeats the purpose for ad-hoc inputs.
  4. Return types don't match. node-re2 methods return strings, arrays of strings, or RegExp-match-style objects — none are returnable through Fast API. A test()-style bool return is the only method that fits; the rest fall back to the slow path. test() is already near-trivial per-call overhead (see bench/set-match.mjs) — small absolute savings.

What we use instead

N-API exposes a different set of levers that achieve the same boundary-shift intent without depending on Fast API. Tracked in the project queue under "Zero-copy JS↔C++ boundary at N-API rewrite time":

  • napi_create_external_buffer / napi_create_external_string_{latin1,utf16} — result strings/buffers that point into RE2-owned memory; no per-match allocation/copy. V8 strings created this way pin caller-owned memory until GC drops the reference.
  • napi_get_typedarray_info / napi_get_buffer_info — zero-copy access to input bytes when the caller passes a Buffer/TypedArray.
  • Persistent property references — amortise V8 lookup costs across calls.
  • Extending the existing same-string conversion cache — currently amortises UTF-16→UTF-8 within a call; backing match results with external strings would amortise across calls on the same source.

References

See also

Clone this wiki locally