From 0ad1dfb0495302581abc3d48b915e65445311e83 Mon Sep 17 00:00:00 2001
From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com>
Date: Thu, 20 Aug 2026 00:10:56 -0700
Subject: [PATCH] ffi: prefer canonical type names
Document long type names as canonical and use them in examples.
Group alternative spellings separately in the documentation and
internal type maps while retaining support for every existing alias.
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
---
doc/api/ffi.md | 75 +++++++++++++++++++------------
lib/internal/ffi-shared-buffer.js | 34 +++++++-------
lib/internal/ffi/fast-api.js | 24 +++++-----
src/ffi/fast.cc | 41 +++++++++--------
src/ffi/types.cc | 35 +++++++--------
5 files changed, 115 insertions(+), 94 deletions(-)
diff --git a/doc/api/ffi.md b/doc/api/ffi.md
index 54de34da7a18..fcb39d223f01 100644
--- a/doc/api/ffi.md
+++ b/doc/api/ffi.md
@@ -63,22 +63,40 @@ Supported type names:
* `void`
* `char`
-* `i8`, `int8`
-* `u8`, `uint8`, `bool`
-* `i16`, `int16`
-* `u16`, `uint16`
-* `i32`, `int32`
-* `u32`, `uint32`
-* `i64`, `int64`
-* `u64`, `uint64`
-* `f32`, `float`, `float32`
-* `f64`, `double`, `float64`
-* `pointer`, `ptr`
-* `string`, `str`
+* `int8`
+* `uint8`
+* `int16`
+* `uint16`
+* `int32`
+* `uint32`
+* `int64`
+* `uint64`
+* `float32`
+* `float64`
+* `pointer`
+* `string`
* `buffer`
* `arraybuffer`
* `function`
+
+Alternative spellings
+
+* `i8` for `int8`
+* `u8` and `bool` for `uint8`
+* `i16` for `int16`
+* `u16` for `uint16`
+* `i32` for `int32`
+* `u32` for `uint32`
+* `i64` for `int64`
+* `u64` for `uint64`
+* `f32` and `float` for `float32`
+* `f64` and `double` for `float64`
+* `ptr` for `pointer`
+* `str` for `string`
+
+
+
These type names are also exposed as constants on `ffi.types`:
* `ffi.types.VOID` = `'void'`
@@ -116,15 +134,15 @@ through reentrant JavaScript such as FFI callbacks. Doing so may crash the
process, produce incorrect output, or corrupt memory.
The `char` type follows the platform C ABI. On platforms where plain C `char`
-is signed it behaves like `i8`; otherwise it behaves like `u8`.
+is signed it behaves like `int8`; otherwise it behaves like `uint8`.
The `bool` type is marshaled as an 8-bit unsigned integer. Pass numeric values
such as `0` and `1`; JavaScript `true` and `false` are not accepted.
-On optimized Fast FFI calls, `pointer`, `ptr`, and `function` parameters accept
-raw pointer `bigint` values. For pointer-like parameters, `null`, `undefined`,
-strings, `Buffer`, typed array, `DataView`, and `ArrayBuffer` values are
-converted on the JavaScript side before calling the optimized native wrapper.
+On optimized Fast FFI calls, `pointer` and `function` parameters accept raw
+pointer `bigint` values. For pointer-like parameters, `null`, `undefined`,
+strings, `Buffer`, typed array, `DataView`, and `ArrayBuffer` values are converted
+on the JavaScript side before calling the optimized native wrapper.
Optimized Fast FFI calls fall back to the generic FFI call path when a
function's arguments or return type do not fit the platform-specific fast
@@ -161,8 +179,8 @@ optional:
```js
const signature = {
- return: 'i32',
- arguments: ['i32', 'i32'],
+ return: 'int32',
+ arguments: ['int32', 'int32'],
};
```
@@ -220,7 +238,7 @@ import { dlopen, suffix } from 'node:ffi';
{
using handle = dlopen(`./mylib.${suffix}`, {
- add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
+ add_i32: { arguments: ['int32', 'int32'], return: 'int32' },
});
console.log(handle.functions.add_i32(20, 22));
} // handle.lib.close() is invoked automatically here.
@@ -230,8 +248,8 @@ import { dlopen, suffix } from 'node:ffi';
import { dlopen, suffix } from 'node:ffi';
const { lib, functions } = dlopen(`./mylib.${suffix}`, {
- add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
- string_length: { arguments: ['pointer'], return: 'u64' },
+ add_i32: { arguments: ['int32', 'int32'], return: 'int32' },
+ string_length: { arguments: ['pointer'], return: 'uint64' },
});
console.log(functions.add_i32(20, 22));
@@ -241,8 +259,8 @@ console.log(functions.add_i32(20, 22));
const { dlopen, suffix } = require('node:ffi');
const { lib, functions } = dlopen(`./mylib.${suffix}`, {
- add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
- string_length: { arguments: ['pointer'], return: 'u64' },
+ add_i32: { arguments: ['int32', 'int32'], return: 'int32' },
+ string_length: { arguments: ['pointer'], return: 'uint64' },
});
console.log(functions.add_i32(20, 22));
@@ -384,8 +402,8 @@ const { DynamicLibrary, suffix } = require('node:ffi');
const lib = new DynamicLibrary(`./mylib.${suffix}`);
const add = lib.getFunction('add_i32', {
- arguments: ['i32', 'i32'],
- return: 'i32',
+ arguments: ['int32', 'int32'],
+ return: 'int32',
});
console.log(add(20, 22));
@@ -435,7 +453,7 @@ const { DynamicLibrary, suffix } = require('node:ffi');
const lib = new DynamicLibrary(`./mylib.${suffix}`);
const callback = lib.registerCallback(
- { arguments: ['i32'], return: 'i32' },
+ { arguments: ['int32'], return: 'int32' },
(value) => value * 2,
);
```
@@ -498,7 +516,8 @@ Argument conversion depends on the declared FFI type.
For 8-, 16-, and 32-bit integer types and for floating-point types, pass
JavaScript `number` values that match the declared type.
-For 64-bit integer types (`i64` and `u64`), pass JavaScript `bigint` values.
+For 64-bit integer types (`int64` and `uint64`), pass JavaScript `bigint`
+values.
For pointer-like arguments:
diff --git a/lib/internal/ffi-shared-buffer.js b/lib/internal/ffi-shared-buffer.js
index 94764bdf0db7..13002193cd9c 100644
--- a/lib/internal/ffi-shared-buffer.js
+++ b/lib/internal/ffi-shared-buffer.js
@@ -64,38 +64,40 @@ const gF64 = DataViewPrototypeGetFloat64;
const sbTypeInfo = {
__proto__: null,
- i8: { set: sI8, get: gI8, kind: 'int', min: -128, max: 127, label: 'an int8' },
- int8: { set: sI8, get: gI8, kind: 'int', min: -128, max: 127, label: 'an int8' },
char: charIsSigned ?
{ set: sI8, get: gI8, kind: 'int', min: -128, max: 127, label: 'an int8' } :
{ set: sU8, get: gU8, kind: 'int', min: 0, max: 255, label: 'a uint8' },
- u8: { set: sU8, get: gU8, kind: 'int', min: 0, max: 255, label: 'a uint8' },
+ int8: { set: sI8, get: gI8, kind: 'int', min: -128, max: 127, label: 'an int8' },
uint8: { set: sU8, get: gU8, kind: 'int', min: 0, max: 255, label: 'a uint8' },
+ int16: { set: sI16, get: gI16, kind: 'int', min: -32768, max: 32767, label: 'an int16' },
+ uint16: { set: sU16, get: gU16, kind: 'int', min: 0, max: 65535, label: 'a uint16' },
+ int32: { set: sI32, get: gI32, kind: 'int', min: -2147483648, max: 2147483647, label: 'an int32' },
+ uint32: { set: sU32, get: gU32, kind: 'int', min: 0, max: 4294967295, label: 'a uint32' },
+ int64: { set: sI64, get: gI64, kind: 'i64', label: 'an int64' },
+ uint64: { set: sU64, get: gU64, kind: 'u64', label: 'a uint64' },
+ float32: { set: sF32, get: gF32, kind: 'float', label: 'a float' },
+ float64: { set: sF64, get: gF64, kind: 'float', label: 'a double' },
+ pointer: { set: sU64, get: gU64, kind: 'pointer' },
+ string: { set: sU64, get: gU64, kind: 'pointer' },
+ buffer: { set: sU64, get: gU64, kind: 'pointer' },
+ arraybuffer: { set: sU64, get: gU64, kind: 'pointer' },
+ function: { set: sU64, get: gU64, kind: 'pointer' },
+
+ // Alternative spellings.
+ i8: { set: sI8, get: gI8, kind: 'int', min: -128, max: 127, label: 'an int8' },
+ u8: { set: sU8, get: gU8, kind: 'int', min: 0, max: 255, label: 'a uint8' },
bool: { set: sU8, get: gU8, kind: 'int', min: 0, max: 255, label: 'a uint8' },
i16: { set: sI16, get: gI16, kind: 'int', min: -32768, max: 32767, label: 'an int16' },
- int16: { set: sI16, get: gI16, kind: 'int', min: -32768, max: 32767, label: 'an int16' },
u16: { set: sU16, get: gU16, kind: 'int', min: 0, max: 65535, label: 'a uint16' },
- uint16: { set: sU16, get: gU16, kind: 'int', min: 0, max: 65535, label: 'a uint16' },
i32: { set: sI32, get: gI32, kind: 'int', min: -2147483648, max: 2147483647, label: 'an int32' },
- int32: { set: sI32, get: gI32, kind: 'int', min: -2147483648, max: 2147483647, label: 'an int32' },
u32: { set: sU32, get: gU32, kind: 'int', min: 0, max: 4294967295, label: 'a uint32' },
- uint32: { set: sU32, get: gU32, kind: 'int', min: 0, max: 4294967295, label: 'a uint32' },
i64: { set: sI64, get: gI64, kind: 'i64', label: 'an int64' },
- int64: { set: sI64, get: gI64, kind: 'i64', label: 'an int64' },
u64: { set: sU64, get: gU64, kind: 'u64', label: 'a uint64' },
- uint64: { set: sU64, get: gU64, kind: 'u64', label: 'a uint64' },
f32: { set: sF32, get: gF32, kind: 'float', label: 'a float' },
float: { set: sF32, get: gF32, kind: 'float', label: 'a float' },
- float32: { set: sF32, get: gF32, kind: 'float', label: 'a float' },
f64: { set: sF64, get: gF64, kind: 'float', label: 'a double' },
double: { set: sF64, get: gF64, kind: 'float', label: 'a double' },
- float64: { set: sF64, get: gF64, kind: 'float', label: 'a double' },
- pointer: { set: sU64, get: gU64, kind: 'pointer' },
ptr: { set: sU64, get: gU64, kind: 'pointer' },
- function: { set: sU64, get: gU64, kind: 'pointer' },
- buffer: { set: sU64, get: gU64, kind: 'pointer' },
- arraybuffer: { set: sU64, get: gU64, kind: 'pointer' },
- string: { set: sU64, get: gU64, kind: 'pointer' },
str: { set: sU64, get: gU64, kind: 'pointer' },
};
diff --git a/lib/internal/ffi/fast-api.js b/lib/internal/ffi/fast-api.js
index 4b531dfe039a..38f4f4e34372 100644
--- a/lib/internal/ffi/fast-api.js
+++ b/lib/internal/ffi/fast-api.js
@@ -47,26 +47,28 @@ const fastLibraryStates = new SafeWeakMap();
// conversions, so the public FFI ranges must be checked before the raw call.
const fastIntegerTypeInfo = {
__proto__: null,
- i8: { kind: 'number', min: -128, max: 127, label: 'an int8' },
- int8: { kind: 'number', min: -128, max: 127, label: 'an int8' },
char: charIsSigned ?
{ kind: 'number', min: -128, max: 127, label: 'an int8' } :
{ kind: 'number', min: 0, max: 255, label: 'a uint8' },
- u8: { kind: 'number', min: 0, max: 255, label: 'a uint8' },
+ int8: { kind: 'number', min: -128, max: 127, label: 'an int8' },
uint8: { kind: 'number', min: 0, max: 255, label: 'a uint8' },
+ int16: { kind: 'number', min: -32768, max: 32767, label: 'an int16' },
+ uint16: { kind: 'number', min: 0, max: 65535, label: 'a uint16' },
+ int32: { kind: 'number', min: -2147483648, max: 2147483647, label: 'an int32' },
+ uint32: { kind: 'number', min: 0, max: 4294967295, label: 'a uint32' },
+ int64: { kind: 'bigint', min: I64_MIN, max: I64_MAX, label: 'an int64' },
+ uint64: { kind: 'bigint', min: 0n, max: U64_MAX, label: 'a uint64' },
+
+ // Alternative spellings.
+ i8: { kind: 'number', min: -128, max: 127, label: 'an int8' },
+ u8: { kind: 'number', min: 0, max: 255, label: 'a uint8' },
bool: { kind: 'number', min: 0, max: 255, label: 'a uint8' },
i16: { kind: 'number', min: -32768, max: 32767, label: 'an int16' },
- int16: { kind: 'number', min: -32768, max: 32767, label: 'an int16' },
u16: { kind: 'number', min: 0, max: 65535, label: 'a uint16' },
- uint16: { kind: 'number', min: 0, max: 65535, label: 'a uint16' },
i32: { kind: 'number', min: -2147483648, max: 2147483647, label: 'an int32' },
- int32: { kind: 'number', min: -2147483648, max: 2147483647, label: 'an int32' },
u32: { kind: 'number', min: 0, max: 4294967295, label: 'a uint32' },
- uint32: { kind: 'number', min: 0, max: 4294967295, label: 'a uint32' },
i64: { kind: 'bigint', min: I64_MIN, max: I64_MAX, label: 'an int64' },
- int64: { kind: 'bigint', min: I64_MIN, max: I64_MAX, label: 'an int64' },
u64: { kind: 'bigint', min: 0n, max: U64_MAX, label: 'a uint64' },
- uint64: { kind: 'bigint', min: 0n, max: U64_MAX, label: 'a uint64' },
};
function throwFFIArgError(msg) {
@@ -97,8 +99,8 @@ function needsRawPointerConversion(type) {
}
function needsPointerLikeConversion(type) {
- return type === 'pointer' || type === 'ptr' || type === 'function' ||
- type === 'buffer' || type === 'arraybuffer';
+ return type === 'pointer' || type === 'function' || type === 'buffer' ||
+ type === 'arraybuffer' || type === 'ptr';
}
function needsStringPointerConversion(type) {
diff --git a/src/ffi/fast.cc b/src/ffi/fast.cc
index 8e92c56aebb7..24809e744a20 100644
--- a/src/ffi/fast.cc
+++ b/src/ffi/fast.cc
@@ -36,34 +36,32 @@ bool FastScalarTypeFromName(std::string_view type, FastFFIType* out) {
// JavaScript wrappers handle strings and object-to-pointer conversions.
if (type == "void") {
*out = FastFFIType::kVoid;
- } else if (type == "bool") {
- *out = FastFFIType::kUint8;
- } else if (IsTypeName(type, {"i8", "int8"})) {
- *out = FastFFIType::kInt8;
- } else if (IsTypeName(type, {"u8", "uint8"})) {
- *out = FastFFIType::kUint8;
} else if (type == "char") {
*out = CHAR_MIN < 0 ? FastFFIType::kInt8 : FastFFIType::kUint8;
- } else if (IsTypeName(type, {"i16", "int16"})) {
+ } else if (IsTypeName(type, {"int8", "i8"})) {
+ *out = FastFFIType::kInt8;
+ } else if (IsTypeName(type, {"uint8", "u8", "bool"})) {
+ *out = FastFFIType::kUint8;
+ } else if (IsTypeName(type, {"int16", "i16"})) {
*out = FastFFIType::kInt16;
- } else if (IsTypeName(type, {"u16", "uint16"})) {
+ } else if (IsTypeName(type, {"uint16", "u16"})) {
*out = FastFFIType::kUint16;
- } else if (IsTypeName(type, {"i32", "int32"})) {
+ } else if (IsTypeName(type, {"int32", "i32"})) {
*out = FastFFIType::kInt32;
- } else if (IsTypeName(type, {"u32", "uint32"})) {
+ } else if (IsTypeName(type, {"uint32", "u32"})) {
*out = FastFFIType::kUint32;
- } else if (IsTypeName(type, {"i64", "int64"})) {
+ } else if (IsTypeName(type, {"int64", "i64"})) {
*out = FastFFIType::kInt64;
- } else if (IsTypeName(type, {"u64", "uint64"})) {
+ } else if (IsTypeName(type, {"uint64", "u64"})) {
*out = FastFFIType::kUint64;
- } else if (IsTypeName(type, {"f32", "float", "float32"})) {
+ } else if (IsTypeName(type, {"float32", "f32", "float"})) {
*out = FastFFIType::kFloat32;
- } else if (IsTypeName(type, {"f64", "double", "float64"})) {
+ } else if (IsTypeName(type, {"float64", "f64", "double"})) {
*out = FastFFIType::kFloat64;
} else if (IsTypeName(type, {"buffer", "arraybuffer"})) {
*out = FastFFIType::kPointer;
} else if (IsTypeName(type,
- {"pointer", "ptr", "string", "str", "function"})) {
+ {"pointer", "string", "function", "ptr", "str"})) {
*out = FastFFIType::kPointer;
} else {
return false;
@@ -162,11 +160,12 @@ bool SignatureNeedsFastIntegerValidation(const FFIFunction& fn) {
// V8 widens narrow integers to 32 bits and truncates BigInts to 64 bits for
// Fast API calls. These types need a JS range check before the trampoline.
for (const std::string& name : fn.arg_type_names) {
- if (name == "bool" || name == "char" || name == "i8" || name == "int8" ||
- name == "u8" || name == "uint8" || name == "i16" || name == "int16" ||
- name == "u16" || name == "uint16" || name == "i32" || name == "int32" ||
- name == "u32" || name == "uint32" || name == "i64" || name == "int64" ||
- name == "u64" || name == "uint64") {
+ if (name == "char" || name == "int8" || name == "uint8" ||
+ name == "int16" || name == "uint16" || name == "int32" ||
+ name == "uint32" || name == "int64" || name == "uint64" ||
+ name == "i8" || name == "u8" || name == "bool" || name == "i16" ||
+ name == "u16" || name == "i32" || name == "u32" || name == "i64" ||
+ name == "u64") {
return true;
}
}
@@ -176,7 +175,7 @@ bool SignatureNeedsFastIntegerValidation(const FFIFunction& fn) {
bool IsPointerTypeName(const std::string& name) {
// `pointer`, `ptr`, and `function` all use the same uintptr ABI slot; only
// the public type spelling differs.
- return name == "pointer" || name == "ptr" || name == "function";
+ return name == "pointer" || name == "function" || name == "ptr";
}
bool IsBufferTypeName(const std::string& name) {
diff --git a/src/ffi/types.cc b/src/ffi/types.cc
index db0c913c547d..83be0f48eccd 100644
--- a/src/ffi/types.cc
+++ b/src/ffi/types.cc
@@ -552,34 +552,33 @@ void WriteFFIReturnToBuffer(ffi_type* type,
v8::Maybe ToFFIType(Environment* env, std::string_view type_str) {
if (type_str == "void") {
return Just(&ffi_type_void);
- } else if (type_str == "i8" || type_str == "int8") {
- return Just(&ffi_type_sint8);
- } else if (type_str == "u8" || type_str == "uint8" || type_str == "bool") {
- return Just(&ffi_type_uint8);
} else if (type_str == "char") {
return Just(CHAR_MIN < 0 ? &ffi_type_sint8 : &ffi_type_uint8);
- } else if (type_str == "i16" || type_str == "int16") {
+ } else if (type_str == "int8" || type_str == "i8") {
+ return Just(&ffi_type_sint8);
+ } else if (type_str == "uint8" || type_str == "u8" || type_str == "bool") {
+ return Just(&ffi_type_uint8);
+ } else if (type_str == "int16" || type_str == "i16") {
return Just(&ffi_type_sint16);
- } else if (type_str == "u16" || type_str == "uint16") {
+ } else if (type_str == "uint16" || type_str == "u16") {
return Just(&ffi_type_uint16);
- } else if (type_str == "i32" || type_str == "int32") {
+ } else if (type_str == "int32" || type_str == "i32") {
return Just(&ffi_type_sint32);
- } else if (type_str == "u32" || type_str == "uint32") {
+ } else if (type_str == "uint32" || type_str == "u32") {
return Just(&ffi_type_uint32);
- } else if (type_str == "i64" || type_str == "int64") {
+ } else if (type_str == "int64" || type_str == "i64") {
return Just(&ffi_type_sint64);
- } else if (type_str == "u64" || type_str == "uint64") {
+ } else if (type_str == "uint64" || type_str == "u64") {
return Just(&ffi_type_uint64);
- } else if (type_str == "f32" || type_str == "float" ||
- type_str == "float32") {
+ } else if (type_str == "float32" || type_str == "f32" ||
+ type_str == "float") {
return Just(&ffi_type_float);
- } else if (type_str == "f64" || type_str == "double" ||
- type_str == "float64") {
+ } else if (type_str == "float64" || type_str == "f64" ||
+ type_str == "double") {
return Just(&ffi_type_double);
- } else if (type_str == "buffer" || type_str == "arraybuffer" ||
- type_str == "string" || type_str == "str" ||
- type_str == "pointer" || type_str == "ptr" ||
- type_str == "function") {
+ } else if (type_str == "pointer" || type_str == "string" ||
+ type_str == "buffer" || type_str == "arraybuffer" ||
+ type_str == "function" || type_str == "ptr" || type_str == "str") {
return Just(&ffi_type_pointer);
} else {
THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported FFI type: %s", type_str);