Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 528f8b2

Browse filesBrowse files
bengladuh95
authored andcommitted
ffi: add shared-buffer fast path for numeric and pointer signatures
Adds an ArrayBuffer-based invocation path for FFI functions whose signatures are composed entirely of numeric types (i8..i64, u8..u64, f32, f64, bool, char) and/or pointer types. The JS wrapper packs arguments directly into a per-function AB via primordial DataView setters and the C++ invoker (`InvokeFunctionSB`) reads them without going through V8's `FunctionCallbackInfo`. Results are returned the same way. Pointer arguments use runtime dispatch: BigInt, null, and undefined take the fast path, while Buffer, ArrayBuffer, ArrayBufferView, and String fall back transparently to the classic `InvokeFunction` path via a stashed `_invokeSlow` function. Signatures containing non-numeric/non-pointer types also bypass the fast path. The fast path is disabled on big-endian platforms. Callers do not opt in, and the fast path is transparent in every way users should rely on. One observable change: function wrappers returned by `library.getFunction`, `library.getFunctions`, and `library.functions` now have `.length` equal to the declared parameter count rather than `0`. Code that relied on the previous value will need to be updated. Adds microbenchmarks covering the common FFI call shapes so future changes to the invoker can be evaluated: - add-i32.js: 2-arg integer - add-f64.js: 2-arg float - many-args.js: 6-arg integer - pointer-bigint.js: 1-arg pointer (BigInt) - sum-buffer.js: pointer + length (Buffer) A `common.js` helper resolves the fixture-library path from `test/ffi/fixture_library` without pulling in the test harness, and throws a clear message if the fixture hasn't been built yet. Also adds `sum_6_i32` to the fixture library for the many-args case. Signed-off-by: Bryan English <bryan@bryanenglish.com> PR-URL: #62918 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent 2dc1d20 commit 528f8b2
Copy full SHA for 528f8b2

17 files changed

+2,060-29Lines changed: 2060 additions & 29 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎benchmark/ffi/add-f64.js‎

Copy file name to clipboard
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const ffi = require('node:ffi');
5+
const { libraryPath, ensureFixtureLibrary } = require('./common.js');
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [1e7],
9+
}, {
10+
flags: ['--experimental-ffi'],
11+
});
12+
13+
ensureFixtureLibrary();
14+
15+
const { lib, functions } = ffi.dlopen(libraryPath, {
16+
add_f64: { result: 'f64', parameters: ['f64', 'f64'] },
17+
});
18+
19+
const add = functions.add_f64;
20+
21+
function main({ n }) {
22+
bench.start();
23+
for (let i = 0; i < n; ++i)
24+
add(1.5, 2.5);
25+
bench.end(n);
26+
27+
lib.close();
28+
}
Collapse file

‎benchmark/ffi/add-i32.js‎

Copy file name to clipboard
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const ffi = require('node:ffi');
5+
const { libraryPath, ensureFixtureLibrary } = require('./common.js');
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [1e7],
9+
}, {
10+
flags: ['--experimental-ffi'],
11+
});
12+
13+
ensureFixtureLibrary();
14+
15+
const { lib, functions } = ffi.dlopen(libraryPath, {
16+
add_i32: { result: 'i32', parameters: ['i32', 'i32'] },
17+
});
18+
19+
const add = functions.add_i32;
20+
21+
function main({ n }) {
22+
bench.start();
23+
for (let i = 0; i < n; ++i)
24+
add(20, 22);
25+
bench.end(n);
26+
27+
lib.close();
28+
}
Collapse file

‎benchmark/ffi/common.js‎

Copy file name to clipboard
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const fs = require('node:fs');
5+
const path = require('node:path');
6+
7+
// Cannot use test/ffi/ffi-test-common.js because it requires test/common
8+
// (the test harness module). Construct the path directly.
9+
const libraryPath = path.join(__dirname, '..', '..', 'test', 'ffi',
10+
'fixture_library', 'build', common.buildType,
11+
process.platform === 'win32' ? 'ffi_test_library.dll' :
12+
process.platform === 'darwin' ? 'ffi_test_library.dylib' :
13+
'ffi_test_library.so');
14+
15+
function ensureFixtureLibrary() {
16+
if (!fs.existsSync(libraryPath)) {
17+
throw new Error(
18+
`Missing FFI fixture library: ${libraryPath}. ` +
19+
'Build it with `tools/test.py test/ffi/test-ffi-calls.js` first.',
20+
);
21+
}
22+
}
23+
24+
module.exports = { libraryPath, ensureFixtureLibrary };
Collapse file

‎benchmark/ffi/many-args.js‎

Copy file name to clipboard
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const ffi = require('node:ffi');
5+
const { libraryPath, ensureFixtureLibrary } = require('./common.js');
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [1e7],
9+
}, {
10+
flags: ['--experimental-ffi'],
11+
});
12+
13+
ensureFixtureLibrary();
14+
15+
const { lib, functions } = ffi.dlopen(libraryPath, {
16+
sum_6_i32: { result: 'i32', parameters: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32'] },
17+
});
18+
19+
const fn = functions.sum_6_i32;
20+
21+
function main({ n }) {
22+
bench.start();
23+
for (let i = 0; i < n; ++i)
24+
fn(1, 2, 3, 4, 5, 6);
25+
bench.end(n);
26+
27+
lib.close();
28+
}
Collapse file

‎benchmark/ffi/pointer-bigint.js‎

Copy file name to clipboard
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const ffi = require('node:ffi');
5+
const { libraryPath, ensureFixtureLibrary } = require('./common.js');
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [1e7],
9+
}, {
10+
flags: ['--experimental-ffi'],
11+
});
12+
13+
ensureFixtureLibrary();
14+
15+
const { lib, functions } = ffi.dlopen(libraryPath, {
16+
pointer_to_usize: { result: 'u64', parameters: ['pointer'] },
17+
});
18+
19+
const fn = functions.pointer_to_usize;
20+
21+
function main({ n }) {
22+
bench.start();
23+
for (let i = 0; i < n; ++i)
24+
fn(0xdeadbeefn);
25+
bench.end(n);
26+
27+
lib.close();
28+
}
Collapse file

‎benchmark/ffi/sum-buffer.js‎

Copy file name to clipboard
+33Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const ffi = require('node:ffi');
5+
const { libraryPath, ensureFixtureLibrary } = require('./common.js');
6+
7+
const bench = common.createBenchmark(main, {
8+
size: [64, 1024, 16384],
9+
n: [1e6],
10+
}, {
11+
flags: ['--experimental-ffi'],
12+
});
13+
14+
ensureFixtureLibrary();
15+
16+
const { lib, functions } = ffi.dlopen(libraryPath, {
17+
sum_buffer: { result: 'u64', parameters: ['pointer', 'u64'] },
18+
});
19+
20+
function main({ n, size }) {
21+
const buf = Buffer.alloc(size, 0x42);
22+
const ptr = ffi.getRawPointer(buf);
23+
const len = BigInt(size);
24+
25+
const sum = functions.sum_buffer;
26+
27+
bench.start();
28+
for (let i = 0; i < n; ++i)
29+
sum(ptr, len);
30+
bench.end(n);
31+
32+
lib.close();
33+
}
Collapse file

‎lib/ffi.js‎

Copy file name to clipboardExpand all lines: lib/ffi.js
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ const {
5353
toArrayBuffer,
5454
} = internalBinding('ffi');
5555

56+
require('internal/ffi-shared-buffer');
57+
5658
function checkFFIPermission() {
5759
if (!permission.isEnabled() || permission.has('ffi')) {
5860
return;

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.