koffi 3.0.0-alpha.9 → 3.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -4
- package/README.md +0 -2
- package/cnoke.cjs +44 -44
- package/doc/benchmarks.md +17 -65
- package/doc/index.md +4 -6
- package/index.d.ts +47 -12
- package/lib/native/base/base.cc +7 -4
- package/package.json +17 -1
- package/src/koffi/CMakeLists.txt +4 -10
- package/src/koffi/index.cjs +82 -87
- package/src/koffi/index.js +70 -75
- package/src/koffi/indirect.cjs +82 -87
- package/src/koffi/indirect.js +14 -5
- package/src/koffi/src/abi/arm64.cc +51 -145
- package/src/koffi/src/abi/loong64_asm.S +50 -50
- package/src/koffi/src/abi/riscv64.cc +1009 -567
- package/src/koffi/src/abi/riscv64_asm.S +50 -50
- package/src/koffi/src/abi/x64sysv.cc +98 -102
- package/src/koffi/src/abi/x64sysv_asm.S +0 -4
- package/src/koffi/src/abi/x64win.cc +56 -77
- package/src/koffi/src/abi/x86.cc +54 -94
- package/src/koffi/src/abi/x86_asm.S +2 -2
- package/src/koffi/src/call.cc +755 -93
- package/src/koffi/src/call.hh +26 -1
- package/src/koffi/src/ffi.cc +357 -603
- package/src/koffi/src/ffi.hh +37 -39
- package/src/koffi/src/trampolines.cjs +2 -2
- package/src/koffi/src/util.cc +106 -96
- package/src/koffi/src/util.hh +75 -76
- package/src/koffi/src/uv.def +0 -3
- package/src/koffi/src/uv.hh +0 -8
- package/build/koffi/darwin_arm64/koffi.node +0 -0
- package/build/koffi/darwin_x64/koffi.node +0 -0
- package/build/koffi/freebsd_arm64/koffi.node +0 -0
- package/build/koffi/freebsd_ia32/koffi.node +0 -0
- package/build/koffi/freebsd_x64/koffi.node +0 -0
- package/build/koffi/linux_arm64/koffi.node +0 -0
- package/build/koffi/linux_ia32/koffi.node +0 -0
- package/build/koffi/linux_x64/koffi.node +0 -0
- package/build/koffi/musl_arm64/koffi.node +0 -0
- package/build/koffi/musl_x64/koffi.node +0 -0
- package/build/koffi/openbsd_ia32/koffi.node +0 -0
- package/build/koffi/openbsd_x64/koffi.node +0 -0
- package/build/koffi/win32_ia32/koffi.exp +0 -0
- package/build/koffi/win32_ia32/koffi.lib +0 -0
- package/build/koffi/win32_ia32/koffi.node +0 -0
- package/build/koffi/win32_x64/koffi.exp +0 -0
- package/build/koffi/win32_x64/koffi.lib +0 -0
- package/build/koffi/win32_x64/koffi.node +0 -0
- package/doc/packaging.md +0 -88
- package/src/koffi/src/abi/arm32.cc +0 -1022
- package/src/koffi/src/abi/arm32_asm.S +0 -166
package/CHANGELOG.md
CHANGED
|
@@ -7,19 +7,35 @@
|
|
|
7
7
|
|
|
8
8
|
### Koffi 3.0
|
|
9
9
|
|
|
10
|
-
#### Koffi 3.0.0
|
|
10
|
+
#### Koffi 3.0.0
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
*Released on 2026-05-16*
|
|
13
|
+
|
|
14
|
+
**Highlights:**
|
|
15
|
+
|
|
16
|
+
- Rewrite call preparation and execution for **vastly improved performance**
|
|
17
|
+
- Distribute prebuilt binaries in **separate subpackages**
|
|
18
|
+
|
|
19
|
+
**Breaking changes:**
|
|
13
20
|
|
|
14
21
|
- Replace use of externals with type objects:
|
|
15
22
|
* Use `koffi.type()` to resolve type specifiers (strings or objects) to type objects
|
|
16
|
-
* Access type information directly on type
|
|
23
|
+
* Access type information directly on type objects without `koffi.introspect()`
|
|
17
24
|
- Replace use of externals with BigInt pointers
|
|
18
|
-
-
|
|
25
|
+
- Support ESM and CJS module types
|
|
19
26
|
|
|
20
27
|
**Other changes:**
|
|
21
28
|
|
|
22
29
|
- Add `koffi.enumeration()` to create [enum types](input#enum-types)
|
|
30
|
+
- Add fast decode functions for integers, floats and strings
|
|
31
|
+
- Use proper types for various objects and handles:
|
|
32
|
+
* Use *LibraryHandle* objects for loaded libraries
|
|
33
|
+
* Use *TypeObject* objects for Koffi types
|
|
34
|
+
* Use *PollHandle* for socket poll handles
|
|
35
|
+
- Add `Symbol.dispose` on library objects and poll handles
|
|
36
|
+
- Prefer types to interfaces in TypeScript declaration file
|
|
37
|
+
- Fix various bugs and small leaks (such as library handles)
|
|
38
|
+
- The ARM32 backend has been temporarily removed
|
|
23
39
|
|
|
24
40
|
**Newly deprecated functions:**
|
|
25
41
|
|
|
@@ -37,6 +53,12 @@ Consult the [migration guide](migration) for more information.
|
|
|
37
53
|
|
|
38
54
|
### Koffi 2.16
|
|
39
55
|
|
|
56
|
+
#### Koffi 2.16.2
|
|
57
|
+
|
|
58
|
+
*Released on 2026-05-06*
|
|
59
|
+
|
|
60
|
+
- Fix string truncation bugs when passing some kinds of long V8 strings (see [Koromix/koffi#266](https://github.com/Koromix/koffi/issues/266))
|
|
61
|
+
|
|
40
62
|
#### Koffi 2.16.1
|
|
41
63
|
|
|
42
64
|
*Released on 2026-04-17*
|
package/README.md
CHANGED
|
@@ -13,13 +13,11 @@ ISA / OS | Windows | Linux/glibc | Linux/musl | macOS | Fre
|
|
|
13
13
|
------------------ | ----------- | ----------- | ----------- | ----------- | ----------- | --------
|
|
14
14
|
x86 (IA32) [^1] | ✅ Yes | ✅ Yes | 🟨 Probably | ⬜️ *N/A* | ✅ Yes | ✅ Yes
|
|
15
15
|
x86_64 (AMD64) | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes
|
|
16
|
-
ARM32 LE [^2] | ⬜️ *N/A* | ✅ Yes | 🟨 Probably | ⬜️ *N/A* | 🟨 Probably | 🟨 Probably
|
|
17
16
|
ARM64 (AArch64) LE | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | 🟨 Probably
|
|
18
17
|
RISC-V 64 [^3] | ⬜️ *N/A* | ✅ Yes | 🟨 Probably | ⬜️ *N/A* | 🟨 Probably | 🟨 Probably
|
|
19
18
|
LoongArch64 | ⬜️ *N/A* | ✅ Yes | 🟨 Probably | ⬜️ *N/A* | 🟨 Probably | 🟨 Probably
|
|
20
19
|
|
|
21
20
|
[^1]: The following call conventions are supported: cdecl, stdcall, MS fastcall, thiscall.
|
|
22
|
-
[^2]: The prebuilt binary uses the hard float ABI and expects a VFP coprocessor. Build from source to use Koffi with a different ABI (softfp, soft).
|
|
23
21
|
[^3]: The prebuilt binary uses the LP64D (double-precision float) ABI. The LP64 ABI is supported in theory if you build Koffi from source but this is untested. The LP64F ABI is not supported.
|
|
24
22
|
|
|
25
23
|
Go to the web site for more information: https://koffi.dev/
|
package/cnoke.cjs
CHANGED
|
@@ -23,16 +23,16 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// ../cnoke/cnoke.js
|
|
26
|
-
var
|
|
26
|
+
var import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
27
27
|
|
|
28
28
|
// ../cnoke/src/builder.js
|
|
29
|
-
var
|
|
30
|
-
var
|
|
31
|
-
var
|
|
32
|
-
var
|
|
29
|
+
var import_node_fs3 = __toESM(require("node:fs"), 1);
|
|
30
|
+
var import_node_os = __toESM(require("node:os"), 1);
|
|
31
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
32
|
+
var import_node_child_process = require("node:child_process");
|
|
33
33
|
|
|
34
34
|
// ../cnoke/src/abi.js
|
|
35
|
-
var
|
|
35
|
+
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
36
36
|
function determineAbi() {
|
|
37
37
|
let abi = process.arch;
|
|
38
38
|
if (abi == "riscv32" || abi == "riscv64") {
|
|
@@ -76,13 +76,13 @@ function determineAbi() {
|
|
|
76
76
|
function readFileHeader(filename, read) {
|
|
77
77
|
let fd = null;
|
|
78
78
|
try {
|
|
79
|
-
let fd2 =
|
|
79
|
+
let fd2 = import_node_fs.default.openSync(filename);
|
|
80
80
|
let buf = Buffer.allocUnsafe(read);
|
|
81
|
-
let len =
|
|
81
|
+
let len = import_node_fs.default.readSync(fd2, buf);
|
|
82
82
|
return buf.subarray(0, len);
|
|
83
83
|
} finally {
|
|
84
84
|
if (fd != null)
|
|
85
|
-
|
|
85
|
+
import_node_fs.default.closeSync(fd);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
function decodeElfHeader(buf) {
|
|
@@ -122,7 +122,7 @@ function decodeElfHeader(buf) {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
// ../cnoke/src/util.js
|
|
125
|
-
var
|
|
125
|
+
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
126
126
|
function pathIsAbsolute(path2) {
|
|
127
127
|
if (process.platform == "win32" && path2.match(/^[a-zA-Z]:/))
|
|
128
128
|
path2 = path2.substr(2);
|
|
@@ -138,28 +138,28 @@ function isPathSeparator(c) {
|
|
|
138
138
|
function syncFiles(src_dir, dest_dir) {
|
|
139
139
|
let keep = /* @__PURE__ */ new Set();
|
|
140
140
|
{
|
|
141
|
-
let entries =
|
|
141
|
+
let entries = import_node_fs2.default.readdirSync(src_dir, { withFileTypes: true });
|
|
142
142
|
for (let entry of entries) {
|
|
143
143
|
if (!entry.isFile())
|
|
144
144
|
continue;
|
|
145
145
|
keep.add(entry.name);
|
|
146
|
-
|
|
146
|
+
import_node_fs2.default.copyFileSync(src_dir + `/${entry.name}`, dest_dir + `/${entry.name}`);
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
{
|
|
150
|
-
let entries =
|
|
150
|
+
let entries = import_node_fs2.default.readdirSync(dest_dir, { withFileTypes: true });
|
|
151
151
|
for (let entry of entries) {
|
|
152
152
|
if (!entry.isFile())
|
|
153
153
|
continue;
|
|
154
154
|
if (keep.has(entry.name))
|
|
155
155
|
continue;
|
|
156
|
-
|
|
156
|
+
import_node_fs2.default.unlinkSync(dest_dir + `/${entry.name}`);
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
function unlinkRecursive(path2) {
|
|
161
161
|
try {
|
|
162
|
-
|
|
162
|
+
import_node_fs2.default.rmSync(path2, { recursive: true, maxRetries: process.platform == "win32" ? 3 : 0 });
|
|
163
163
|
} catch (err) {
|
|
164
164
|
if (err.code !== "ENOENT")
|
|
165
165
|
throw err;
|
|
@@ -413,10 +413,10 @@ function Builder(config = {}) {
|
|
|
413
413
|
checkCompatibility();
|
|
414
414
|
console.log(`>> Node: ${runtime_version}`);
|
|
415
415
|
console.log(`>> Toolchain: ${toolchain ?? "native"}`);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
retry &=
|
|
416
|
+
import_node_fs3.default.mkdirSync(build_dir, { recursive: true, mode: 493 });
|
|
417
|
+
import_node_fs3.default.mkdirSync(work_dir, { recursive: true, mode: 493 });
|
|
418
|
+
import_node_fs3.default.mkdirSync(output_dir, { recursive: true, mode: 493 });
|
|
419
|
+
retry &= import_node_fs3.default.existsSync(work_dir + "/CMakeCache.txt");
|
|
420
420
|
args.push(`-DNODE_JS_EXECPATH=${process.execPath}`);
|
|
421
421
|
if (options2.api == null) {
|
|
422
422
|
let downloaded = false;
|
|
@@ -427,7 +427,7 @@ function Builder(config = {}) {
|
|
|
427
427
|
let api_dir = expandPath(options2.api, project_dir);
|
|
428
428
|
args.push(`-DNODE_JS_INCLUDE_DIRS=${api_dir}/include`);
|
|
429
429
|
}
|
|
430
|
-
|
|
430
|
+
import_node_fs3.default.writeFileSync(work_dir + "/FindCNoke.cmake", FIND_CNOKE_CMAKE);
|
|
431
431
|
args.push(`-DCMAKE_MODULE_PATH=${work_dir}`);
|
|
432
432
|
let win32 = (toolchain ?? host).startsWith("win32_");
|
|
433
433
|
let mingw = process.platform == "win32" && process.env.MSYSTEM != null;
|
|
@@ -444,16 +444,16 @@ function Builder(config = {}) {
|
|
|
444
444
|
args.push(`-DNODE_JS_LINK_DEF=${api_dir}/def/node_api.def`);
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
|
-
|
|
447
|
+
import_node_fs3.default.writeFileSync(work_dir + "/win_delay_hook.c", WIN_DELAY_HOOK_C);
|
|
448
448
|
args.push(`-DNODE_JS_SOURCES=${work_dir}/win_delay_hook.c`);
|
|
449
449
|
}
|
|
450
450
|
if (process.platform != "win32" || mingw) {
|
|
451
|
-
if ((0,
|
|
451
|
+
if ((0, import_node_child_process.spawnSync)("ninja", ["--version"]).status === 0) {
|
|
452
452
|
args.push("-G", "Ninja");
|
|
453
453
|
} else if (process.platform == "win32") {
|
|
454
454
|
args.push("-G", "MinGW Makefiles");
|
|
455
455
|
}
|
|
456
|
-
if (config.ccache && (0,
|
|
456
|
+
if (config.ccache && (0, import_node_child_process.spawnSync)("ccache", ["--version"]).status === 0) {
|
|
457
457
|
args.push("-DCMAKE_C_COMPILER_LAUNCHER=ccache");
|
|
458
458
|
args.push("-DCMAKE_CXX_COMPILER_LAUNCHER=ccache");
|
|
459
459
|
}
|
|
@@ -475,7 +475,7 @@ function Builder(config = {}) {
|
|
|
475
475
|
args.push(`-D${define}`);
|
|
476
476
|
args.push("--no-warn-unused-cli");
|
|
477
477
|
console.log(">> Running configuration");
|
|
478
|
-
let proc = (0,
|
|
478
|
+
let proc = (0, import_node_child_process.spawnSync)(cmake_bin, args, { cwd: work_dir, stdio: "inherit" });
|
|
479
479
|
if (proc.status !== 0) {
|
|
480
480
|
unlinkRecursive(work_dir);
|
|
481
481
|
if (retry)
|
|
@@ -494,10 +494,10 @@ function Builder(config = {}) {
|
|
|
494
494
|
}
|
|
495
495
|
}
|
|
496
496
|
checkCMake();
|
|
497
|
-
if (!
|
|
497
|
+
if (!import_node_fs3.default.existsSync(work_dir + "/CMakeCache.txt"))
|
|
498
498
|
await self.configure();
|
|
499
499
|
if (process.env.MAKEFLAGS == null)
|
|
500
|
-
process.env.MAKEFLAGS = "-j" + (
|
|
500
|
+
process.env.MAKEFLAGS = "-j" + (import_node_os.default.cpus().length || 1);
|
|
501
501
|
let args = [
|
|
502
502
|
"--build",
|
|
503
503
|
work_dir,
|
|
@@ -509,14 +509,14 @@ function Builder(config = {}) {
|
|
|
509
509
|
for (let target of targets)
|
|
510
510
|
args.push("--target", target);
|
|
511
511
|
console.log(">> Running build");
|
|
512
|
-
let proc = (0,
|
|
512
|
+
let proc = (0, import_node_child_process.spawnSync)(cmake_bin, args, { stdio: "inherit" });
|
|
513
513
|
if (proc.status !== 0)
|
|
514
514
|
throw new Error("Failed to run build step");
|
|
515
515
|
console.log(">> Copy target files");
|
|
516
516
|
syncFiles(output_dir, build_dir);
|
|
517
517
|
};
|
|
518
518
|
async function checkPrebuild() {
|
|
519
|
-
let proc = (0,
|
|
519
|
+
let proc = (0, import_node_child_process.spawnSync)(process.execPath, ["-e", "require(process.argv[1])", package_dir]);
|
|
520
520
|
return proc.status === 0;
|
|
521
521
|
}
|
|
522
522
|
this.clean = function() {
|
|
@@ -526,9 +526,9 @@ function Builder(config = {}) {
|
|
|
526
526
|
if (process.platform == "win32")
|
|
527
527
|
dirname = dirname.replace(/\\/g, "/");
|
|
528
528
|
do {
|
|
529
|
-
if (
|
|
529
|
+
if (import_node_fs3.default.existsSync(dirname + "/" + basename))
|
|
530
530
|
return dirname;
|
|
531
|
-
dirname =
|
|
531
|
+
dirname = import_node_path.default.dirname(dirname);
|
|
532
532
|
} while (!dirname.endsWith("/"));
|
|
533
533
|
return null;
|
|
534
534
|
}
|
|
@@ -537,7 +537,7 @@ function Builder(config = {}) {
|
|
|
537
537
|
let cache_dir2 = process.env["LOCALAPPDATA"] || process.env["APPDATA"];
|
|
538
538
|
if (cache_dir2 == null)
|
|
539
539
|
throw new Error("Missing LOCALAPPDATA and APPDATA environment variable");
|
|
540
|
-
cache_dir2 =
|
|
540
|
+
cache_dir2 = import_node_path.default.join(cache_dir2, "cnoke");
|
|
541
541
|
return cache_dir2;
|
|
542
542
|
} else {
|
|
543
543
|
let cache_dir2 = process.env["XDG_CACHE_HOME"];
|
|
@@ -545,29 +545,29 @@ function Builder(config = {}) {
|
|
|
545
545
|
let home = process.env["HOME"];
|
|
546
546
|
if (home == null)
|
|
547
547
|
throw new Error("Missing HOME environment variable");
|
|
548
|
-
cache_dir2 =
|
|
548
|
+
cache_dir2 = import_node_path.default.join(home, ".cache");
|
|
549
549
|
}
|
|
550
|
-
cache_dir2 =
|
|
550
|
+
cache_dir2 = import_node_path.default.join(cache_dir2, "cnoke");
|
|
551
551
|
return cache_dir2;
|
|
552
552
|
}
|
|
553
553
|
}
|
|
554
554
|
function checkCMake() {
|
|
555
555
|
if (cmake_bin != null)
|
|
556
556
|
return;
|
|
557
|
-
if (!
|
|
557
|
+
if (!import_node_fs3.default.existsSync(project_dir + "/CMakeLists.txt"))
|
|
558
558
|
throw new Error("This directory does not appear to have a CMakeLists.txt file");
|
|
559
559
|
{
|
|
560
|
-
let proc = (0,
|
|
560
|
+
let proc = (0, import_node_child_process.spawnSync)("cmake", ["--version"]);
|
|
561
561
|
if (proc.status === 0) {
|
|
562
562
|
cmake_bin = "cmake";
|
|
563
563
|
} else {
|
|
564
564
|
if (process.platform == "win32") {
|
|
565
|
-
let proc2 = (0,
|
|
565
|
+
let proc2 = (0, import_node_child_process.spawnSync)("reg", ["query", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Kitware\\CMake", "/v", "InstallDir"]);
|
|
566
566
|
if (proc2.status === 0) {
|
|
567
567
|
let matches = proc2.stdout.toString("utf-8").match(/InstallDir[ \t]+REG_[A-Z_]+[ \t]+(.*)+/);
|
|
568
568
|
if (matches != null) {
|
|
569
|
-
let bin =
|
|
570
|
-
if (
|
|
569
|
+
let bin = import_node_path.default.join(matches[1].trim(), "bin\\cmake.exe");
|
|
570
|
+
if (import_node_fs3.default.existsSync(bin))
|
|
571
571
|
cmake_bin = bin;
|
|
572
572
|
}
|
|
573
573
|
}
|
|
@@ -599,7 +599,7 @@ function Builder(config = {}) {
|
|
|
599
599
|
let cnoke = null;
|
|
600
600
|
if (package_dir != null) {
|
|
601
601
|
try {
|
|
602
|
-
let json =
|
|
602
|
+
let json = import_node_fs3.default.readFileSync(package_dir + "/package.json", { encoding: "utf-8" });
|
|
603
603
|
pkg = JSON.parse(json);
|
|
604
604
|
directory = package_dir;
|
|
605
605
|
} catch (err) {
|
|
@@ -610,7 +610,7 @@ function Builder(config = {}) {
|
|
|
610
610
|
if (cnoke == null)
|
|
611
611
|
cnoke = pkg?.cnoke ?? {};
|
|
612
612
|
options = {
|
|
613
|
-
name: pkg?.name ??
|
|
613
|
+
name: pkg?.name ?? import_node_path.default.basename(project_dir),
|
|
614
614
|
version: pkg?.version ?? null,
|
|
615
615
|
directory,
|
|
616
616
|
...cnoke
|
|
@@ -644,8 +644,8 @@ function Builder(config = {}) {
|
|
|
644
644
|
function expandPath(str, root) {
|
|
645
645
|
let expanded = expandString(str);
|
|
646
646
|
if (!pathIsAbsolute(expanded))
|
|
647
|
-
expanded =
|
|
648
|
-
expanded =
|
|
647
|
+
expanded = import_node_path.default.join(root, expanded);
|
|
648
|
+
expanded = import_node_path.default.normalize(expanded);
|
|
649
649
|
return expanded;
|
|
650
650
|
}
|
|
651
651
|
}
|
|
@@ -690,11 +690,11 @@ async function main() {
|
|
|
690
690
|
} else if (arg == "-D" || arg == "--directory") {
|
|
691
691
|
if (value == null)
|
|
692
692
|
throw new Error(`Missing value for ${arg}`);
|
|
693
|
-
config.project_dir =
|
|
693
|
+
config.project_dir = import_node_fs4.default.realpathSync(value);
|
|
694
694
|
} else if (arg == "-P" || arg == "--package") {
|
|
695
695
|
if (value == null)
|
|
696
696
|
throw new Error(`Missing value for ${arg}`);
|
|
697
|
-
config.package_dir =
|
|
697
|
+
config.package_dir = import_node_fs4.default.realpathSync(value);
|
|
698
698
|
} else if (arg == "-O" || arg == "--output") {
|
|
699
699
|
if (value == null)
|
|
700
700
|
throw new Error(`Missing value for ${arg}`);
|
package/doc/benchmarks.md
CHANGED
|
@@ -4,12 +4,10 @@ Here is a quick overview of the execution time of Koffi calls on three benchmark
|
|
|
4
4
|
|
|
5
5
|
- The first benchmark is based on `rand()` calls
|
|
6
6
|
- The second benchmark is based on `atoi()` calls
|
|
7
|
-
- The third benchmark is based on
|
|
7
|
+
- The third benchmark is based on `memset()` calls
|
|
8
8
|
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
<a href="{{ ASSET static/perf_windows.png }}" target="_blank"><img src="{{ ASSET static/perf_windows.png }}" alt="Windows x86_64 performance" style="width: 350px;"/></a>
|
|
12
|
-
</p>
|
|
9
|
+
<div class="benchmark chart" data-platform="linux_x64"></div>
|
|
10
|
+
<div class="benchmark chart" data-platform="win32_x64"></div>
|
|
13
11
|
|
|
14
12
|
These results are detailed and explained below, and compared to node-ffi/node-ffi-napi.
|
|
15
13
|
|
|
@@ -19,17 +17,9 @@ The results presented below were measured on my x86_64 Linux machine (Intel® Co
|
|
|
19
17
|
|
|
20
18
|
## rand results
|
|
21
19
|
|
|
22
|
-
This test is based around repeated calls to a simple standard C function `rand`, and
|
|
20
|
+
This test is based around repeated calls to a simple standard C function `rand`, which takes no parameter and returns a 32-bit integer.
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
- the second one calls rand through Koffi
|
|
26
|
-
- the third one uses the official Node.js FFI implementation, node-ffi-napi
|
|
27
|
-
|
|
28
|
-
rand | Iteration time | Relative performance | Overhead
|
|
29
|
-
------------- | -------------- | -------------------- | --------
|
|
30
|
-
napi | 256 ns | x1.00 | +0%
|
|
31
|
-
koffi | 375 ns | x0.68 | +46%
|
|
32
|
-
node-ffi-napi | 29783 ns | x0.009 | +11544%
|
|
22
|
+
<div class="benchmark table" data-platform="linux_x64" data-benchmark="rand"></div>
|
|
33
23
|
|
|
34
24
|
Because rand is a pretty small function, the FFI overhead is clearly visible.
|
|
35
25
|
|
|
@@ -37,27 +27,13 @@ Because rand is a pretty small function, the FFI overhead is clearly visible.
|
|
|
37
27
|
|
|
38
28
|
This test is similar to the rand one, but it is based on `atoi`, which takes a string parameter. Javascript (V8) to C string conversion is relatively slow and heavy.
|
|
39
29
|
|
|
40
|
-
|
|
41
|
-
------------- | -------------- | -------------------- | --------
|
|
42
|
-
napi | 371 ns | x1.00 | +0%
|
|
43
|
-
koffi | 557 ns | x0.67 | +50%
|
|
44
|
-
node-ffi-napi | 104340 ns | x0.004 | +27988%
|
|
45
|
-
|
|
46
|
-
Because atoi is a pretty small function, the FFI overhead is clearly visible.
|
|
47
|
-
|
|
48
|
-
## Raylib results
|
|
30
|
+
<div class="benchmark table" data-platform="linux_x64" data-benchmark="atoi"></div>
|
|
49
31
|
|
|
50
|
-
|
|
32
|
+
## memset results
|
|
51
33
|
|
|
52
|
-
|
|
53
|
-
- [node-raylib](https://github.com/RobLoach/node-raylib): This is a native wrapper implemented with N-API
|
|
34
|
+
This test is based around repeated calls to the standard C function `memset`. All implementations pass a Node.js Buffer for the pointer argument.
|
|
54
35
|
|
|
55
|
-
|
|
56
|
-
------------- | -------------- | -------------------- | --------
|
|
57
|
-
C++ | 10.8 µs | x1.14 | -12%
|
|
58
|
-
node-raylib | 12.3 µs | x1.00 | +0%
|
|
59
|
-
koffi | 13.2 µs | x0.92 | +8%
|
|
60
|
-
node-ffi-napi | 80.3 µs | x0.15 | +555%
|
|
36
|
+
<div class="benchmark table" data-platform="linux_x64" data-benchmark="memset"></div>
|
|
61
37
|
|
|
62
38
|
# Windows x86_64
|
|
63
39
|
|
|
@@ -65,47 +41,21 @@ The results presented below were measured on my x86_64 Windows machine (Intel®
|
|
|
65
41
|
|
|
66
42
|
## rand results
|
|
67
43
|
|
|
68
|
-
This test is based around repeated calls to a simple standard C function `rand`, and
|
|
69
|
-
|
|
70
|
-
- the first one is the reference, it calls rand through an N-API module, and is close to the theoretical limit of a perfect (no overhead) Node.js > C FFI implementation (pre-compiled static glue code)
|
|
71
|
-
- the second one calls rand through Koffi
|
|
72
|
-
- the third one uses the official Node.js FFI implementation, node-ffi-napi
|
|
73
|
-
|
|
74
|
-
rand | Iteration time | Relative performance | Overhead
|
|
75
|
-
------------- | -------------- | -------------------- | --------
|
|
76
|
-
napi | 859 ns | x1.00 | (ref)
|
|
77
|
-
koffi | 1352 ns | x0.64 | +57%
|
|
78
|
-
node-ffi-napi | 35640 ns | x0.02 | +4048%
|
|
44
|
+
This test is based around repeated calls to a simple standard C function `rand`, which takes no parameter and returns a 32-bit integer.
|
|
79
45
|
|
|
80
|
-
|
|
46
|
+
<div class="benchmark table" data-platform="win32_x64" data-benchmark="rand"></div>
|
|
81
47
|
|
|
82
48
|
## atoi results
|
|
83
49
|
|
|
84
50
|
This test is similar to the rand one, but it is based on `atoi`, which takes a string parameter. Javascript (V8) to C string conversion is relatively slow and heavy.
|
|
85
51
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
atoi | Iteration time | Relative performance | Overhead
|
|
89
|
-
------------- | -------------- | -------------------- | --------
|
|
90
|
-
napi | 1336 ns | x1.00 | (ref)
|
|
91
|
-
koffi | 2440 ns | x0.55 | +83%
|
|
92
|
-
node-ffi-napi | 136890 ns | x0.010 | +10144%
|
|
93
|
-
|
|
94
|
-
Because atoi is a pretty small function, the FFI overhead is clearly visible.
|
|
52
|
+
<div class="benchmark table" data-platform="win32_x64" data-benchmark="atoi"></div>
|
|
95
53
|
|
|
96
|
-
##
|
|
54
|
+
## memset results
|
|
97
55
|
|
|
98
|
-
This
|
|
56
|
+
This test is based around repeated calls to the standard C function `memset`. All implementations pass a Node.js Buffer for the pointer argument.
|
|
99
57
|
|
|
100
|
-
|
|
101
|
-
- raylib_cc: C++ implementation of the benchmark, without any Javascript
|
|
102
|
-
|
|
103
|
-
raylib | Iteration time | Relative performance | Overhead
|
|
104
|
-
------------- | -------------- | -------------------- | --------
|
|
105
|
-
C++ | 18.2 µs | x1.50 | -33%
|
|
106
|
-
node-raylib | 27.3 µs | x1.00 | (ref)
|
|
107
|
-
koffi | 29.8 µs | x0.92 | +9%
|
|
108
|
-
node-ffi-napi | 96.3 µs | x0.28 | +253%
|
|
58
|
+
<div class="benchmark table" data-platform="win32_x64" data-benchmark="memset"></div>
|
|
109
59
|
|
|
110
60
|
# Running benchmarks
|
|
111
61
|
|
|
@@ -124,3 +74,5 @@ Once everything is built and ready, run:
|
|
|
124
74
|
```sh
|
|
125
75
|
node benchmark.js
|
|
126
76
|
```
|
|
77
|
+
|
|
78
|
+
<script src="{{ ASSET static/benchmarks.js }}"></script>
|
package/doc/index.md
CHANGED
|
@@ -21,18 +21,16 @@ The following combinations of OS and architectures __are officially supported an
|
|
|
21
21
|
|
|
22
22
|
ISA / OS | Windows | Linux/glibc | Linux/musl | macOS | FreeBSD | OpenBSD
|
|
23
23
|
------------------ | ------- | ----------- | ---------- | ----- | ------- | -------
|
|
24
|
-
x86 (IA32) [^
|
|
24
|
+
x86 (IA32) [^1] | ✅ | ✅ | 🟨 | ⬜️ | ✅ | ✅
|
|
25
25
|
x86_64 (AMD64) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅
|
|
26
|
-
ARM32 LE [^3] | ⬜️ | ✅ | 🟨 | ⬜️ | 🟨 | 🟨
|
|
27
26
|
ARM64 (AArch64) LE | ✅ | ✅ | ✅ | ✅ | ✅ | 🟨
|
|
28
|
-
RISC-V 64 [^
|
|
27
|
+
RISC-V 64 [^2] | ⬜️ | ✅ | 🟨 | ⬜️ | 🟨 | 🟨
|
|
29
28
|
LoongArch64 | ⬜️ | ✅ | 🟨 | ⬜️ | 🟨 | 🟨
|
|
30
29
|
|
|
31
30
|
<div class="legend">✅ Yes | 🟨 Probably | ⬜️ Not applicable</div>
|
|
32
31
|
|
|
33
|
-
[^
|
|
34
|
-
[^
|
|
35
|
-
[^4]: The prebuilt binary uses the LP64D (double-precision float) ABI. The LP64 ABI is supported in theory if you build Koffi from source (untested), the LP64F ABI is not supported.
|
|
32
|
+
[^1]: The following call conventions are supported for forward calls: cdecl, stdcall, MS fastcall, thiscall. Only cdecl and stdcall can be used for C to JS callbacks.
|
|
33
|
+
[^2]: The prebuilt binary uses the LP64D (double-precision float) ABI. The LP64 ABI is supported in theory if you build Koffi from source (untested), the LP64F ABI is not supported.
|
|
36
34
|
|
|
37
35
|
For all fully supported platforms (green check marks), a prebuilt binary is included in the NPM package which means you can install Koffi without a C++ compiler.
|
|
38
36
|
|
package/index.d.ts
CHANGED
|
@@ -43,8 +43,6 @@ export type KoffiFunc<T extends (...args: any) => any> = T & {
|
|
|
43
43
|
info: PrototypeInfo;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
export type CallbackHandle = {};
|
|
47
|
-
|
|
48
46
|
type LoadOptions = {
|
|
49
47
|
lazy?: boolean,
|
|
50
48
|
global?: boolean,
|
|
@@ -68,6 +66,8 @@ export type LibraryHandle = {
|
|
|
68
66
|
symbol(name: string, type: TypeSpec): any;
|
|
69
67
|
|
|
70
68
|
unload(): void;
|
|
69
|
+
|
|
70
|
+
[Symbol.dispose](): void;
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
export function load(path: string | null, options?: LoadOptions): LibraryHandle;
|
|
@@ -116,23 +116,54 @@ export function proto(convention: string, result: TypeSpec, arguments: TypeSpec[
|
|
|
116
116
|
export function proto(name: string | null | undefined, result: TypeSpec, arguments: TypeSpec[]): TypeObject;
|
|
117
117
|
export function proto(convention: string, name: string | null | undefined, result: TypeSpec, arguments: TypeSpec[]): TypeObject;
|
|
118
118
|
|
|
119
|
-
export function register(callback: Function, type: TypeSpec):
|
|
120
|
-
export function register(thisValue: any, callback: Function, type: TypeSpec):
|
|
121
|
-
export function unregister(callback:
|
|
119
|
+
export function register(callback: Function, type: TypeSpec): bigint;
|
|
120
|
+
/** @deprecated */ export function register(thisValue: any, callback: Function, type: TypeSpec): bigint;
|
|
121
|
+
export function unregister(callback: bigint): void;
|
|
122
122
|
|
|
123
123
|
export function as(value: any, type: TypeSpec): IKoffiPointerCast;
|
|
124
|
-
export function decode(value: any, type: TypeSpec): any;
|
|
125
|
-
export function decode(value: any, type: TypeSpec, len: number): any;
|
|
126
|
-
export function decode(value: any, offset: number, type: TypeSpec): any;
|
|
127
|
-
export function decode(value: any, offset: number, type: TypeSpec, len: number): any;
|
|
128
124
|
export function address(value: any): bigint;
|
|
129
125
|
export function call(value: any, type: TypeSpec, ...args: any[]): any;
|
|
126
|
+
export function view(ref: any, len: number): ArrayBuffer;
|
|
127
|
+
|
|
128
|
+
export const decode: {
|
|
129
|
+
(value: any, type: TypeSpec): any;
|
|
130
|
+
(value: any, type: TypeSpec, len: number): any;
|
|
131
|
+
(value: any, offset: number, type: TypeSpec): any;
|
|
132
|
+
(value: any, offset: number, type: TypeSpec, len: number): any;
|
|
133
|
+
|
|
134
|
+
char(ptr: any): number;
|
|
135
|
+
short(ptr: any): number;
|
|
136
|
+
int(ptr: any): number;
|
|
137
|
+
long(ptr: any): number | bigint;
|
|
138
|
+
longlong(ptr: any): number | bigint;
|
|
139
|
+
uchar(ptr: any): number;
|
|
140
|
+
ushort(ptr: any): number;
|
|
141
|
+
uint(ptr: any): number;
|
|
142
|
+
ulong(ptr: any): number | bigint;
|
|
143
|
+
ulonglong(ptr: any): number | bigint;
|
|
144
|
+
|
|
145
|
+
int8(ptr: any): number;
|
|
146
|
+
int16(ptr: any): number;
|
|
147
|
+
int32(ptr: any): number;
|
|
148
|
+
int64(ptr: any): number | bigint;
|
|
149
|
+
uint8(ptr: any): number;
|
|
150
|
+
uint16(ptr: any): number;
|
|
151
|
+
uint32(ptr: any): number;
|
|
152
|
+
uint64(ptr: any): number | bigint;
|
|
153
|
+
|
|
154
|
+
float(ptr: any): number;
|
|
155
|
+
double(ptr: any): number;
|
|
156
|
+
|
|
157
|
+
string(ptr: any, length?: number | bigint | null): string;
|
|
158
|
+
string16(ptr: any, length?: number | bigint | null): string;
|
|
159
|
+
string32(ptr: any, length?: number | bigint | null): string;
|
|
160
|
+
};
|
|
161
|
+
|
|
130
162
|
export function encode(ref: any, type: TypeSpec, value: any): void;
|
|
131
163
|
export function encode(ref: any, type: TypeSpec, value: any, len: number): void;
|
|
132
164
|
export function encode(ref: any, offset: number, type: TypeSpec): void;
|
|
133
165
|
export function encode(ref: any, offset: number, type: TypeSpec, value: any): void;
|
|
134
166
|
export function encode(ref: any, offset: number, type: TypeSpec, value: any, len: number): void;
|
|
135
|
-
export function view(ref: any, len: number): ArrayBuffer;
|
|
136
167
|
|
|
137
168
|
export function type(type: TypeSpec): TypeObject;
|
|
138
169
|
export function sizeof(type: TypeSpec): number;
|
|
@@ -261,13 +292,13 @@ export const types: Record<PrimitiveTypes, TypeObject>;
|
|
|
261
292
|
export namespace node {
|
|
262
293
|
export const env: { __brand: 'IKoffiNodeEnv' };
|
|
263
294
|
|
|
264
|
-
|
|
295
|
+
type PollOptions = {
|
|
265
296
|
readable?: boolean;
|
|
266
297
|
writable?: boolean;
|
|
267
298
|
disconnect?: boolean;
|
|
268
299
|
};
|
|
269
300
|
|
|
270
|
-
|
|
301
|
+
type PollEvents = {
|
|
271
302
|
readable: boolean;
|
|
272
303
|
writable: boolean;
|
|
273
304
|
disconnect: boolean;
|
|
@@ -277,9 +308,13 @@ export namespace node {
|
|
|
277
308
|
start(opts: PollOptions, callback: (ev: PollEvents) => void): void;
|
|
278
309
|
start(callback: (ev: PollEvents) => void): void;
|
|
279
310
|
stop(): void;
|
|
311
|
+
|
|
280
312
|
close(): void;
|
|
313
|
+
|
|
281
314
|
unref(): void;
|
|
282
315
|
ref(): void;
|
|
316
|
+
|
|
317
|
+
[Symbol.dispose](): void;
|
|
283
318
|
}
|
|
284
319
|
|
|
285
320
|
export function poll(fd: number, opts: PollOptions, callback: (ev: PollEvents) => void): PollHandle;
|
package/lib/native/base/base.cc
CHANGED
|
@@ -2075,12 +2075,15 @@ const char *GetEnv(const char *name)
|
|
|
2075
2075
|
|
|
2076
2076
|
bool GetDebugFlag(const char *name)
|
|
2077
2077
|
{
|
|
2078
|
-
const char
|
|
2078
|
+
Span<const char> str = TrimStr(GetEnv(name));
|
|
2079
2079
|
|
|
2080
|
-
if (
|
|
2080
|
+
if (str.len) {
|
|
2081
2081
|
bool ret = false;
|
|
2082
|
-
if (!ParseBool(
|
|
2083
|
-
LogError("Environment variable '%1' is not a boolean", name);
|
|
2082
|
+
if (!ParseBool(str, &ret, K_DEFAULT_PARSE_FLAGS & ~(int)ParseFlag::Log)) {
|
|
2083
|
+
LogError("Environment variable '%1=%2' is not a boolean", name, str);
|
|
2084
|
+
}
|
|
2085
|
+
if (ret) {
|
|
2086
|
+
LogWarning("Debug flag '%1' is in effect", name);
|
|
2084
2087
|
}
|
|
2085
2088
|
return ret;
|
|
2086
2089
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koffi",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-rc.1",
|
|
4
4
|
"description": "Fast and simple C FFI (foreign function interface) for Node.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"foreign",
|
|
@@ -32,6 +32,22 @@
|
|
|
32
32
|
"napi": 8
|
|
33
33
|
},
|
|
34
34
|
"funding": "https://liberapay.com/Koromix",
|
|
35
|
+
"optionalDependencies": {
|
|
36
|
+
"@koromix/koffi-linux-arm64": "3.0.0-rc.1",
|
|
37
|
+
"@koromix/koffi-linux-ia32": "3.0.0-rc.1",
|
|
38
|
+
"@koromix/koffi-linux-x64": "3.0.0-rc.1",
|
|
39
|
+
"@koromix/koffi-linux-riscv64d": "3.0.0-rc.1",
|
|
40
|
+
"@koromix/koffi-freebsd-ia32": "3.0.0-rc.1",
|
|
41
|
+
"@koromix/koffi-freebsd-x64": "3.0.0-rc.1",
|
|
42
|
+
"@koromix/koffi-freebsd-arm64": "3.0.0-rc.1",
|
|
43
|
+
"@koromix/koffi-openbsd-ia32": "3.0.0-rc.1",
|
|
44
|
+
"@koromix/koffi-openbsd-x64": "3.0.0-rc.1",
|
|
45
|
+
"@koromix/koffi-win32-ia32": "3.0.0-rc.1",
|
|
46
|
+
"@koromix/koffi-win32-x64": "3.0.0-rc.1",
|
|
47
|
+
"@koromix/koffi-darwin-x64": "3.0.0-rc.1",
|
|
48
|
+
"@koromix/koffi-darwin-arm64": "3.0.0-rc.1",
|
|
49
|
+
"@koromix/koffi-linux-loong64": "3.0.0-rc.1"
|
|
50
|
+
},
|
|
35
51
|
"type": "module",
|
|
36
52
|
"main": "./index.cjs",
|
|
37
53
|
"module": "./index.js",
|