Skip to content

Commit f52cf5e

Browse files
ShogunPandabengl
authored andcommitted
ffi: add experimental fast FFI call API for AArch64 and x86_64
Add fast API trampolines for AArch64 and x86_64. IsJitMemorySupported() maps an RW page, writes a ret instruction (0xD65F03C0 AArch64 / 0xC3 x86_64), and mprotects it to RX. A successful RX transition is treated as the support signal. The page is deliberately not executed: this check may run during normal operation (when an FFI function is first created), and executing freshly written code from a capability probe could SIGSEGV/SIGKILL the process on systems that block executable memory. The real trampoline emitter performs the same mprotect at creation time and falls back to libffi when it is rejected. The result is computed once via std::call_once and cached for the lifetime of the process, so concurrent callers never observe a provisional value. Wired into CreateFastFFIMetadata() for early nullptr bail-out when JIT memory is unavailable. Windows stub returns false (no trampolines yet on this branch). IsFastCallEligible() validates at parse time whether a signature can use the fast-call path, covering: - Return and argument type eligibility (numeric, pointer; no structs) - Argument count cap (8, matching V8 fast-call limit) - Per-ABI register pressure limits that mirror the trampoline emitters (AArch64 and x86_64 SysV). Platforms without an emitter, including Win64, are reported ineligible. - Buffer and float args cannot coexist, and buffer args additionally consume an extra GP register slot on both supported ABIs. The arg/arg-name lengths are checked before the per-arg loop so a malformed signature cannot index out of bounds. Returns nullptr from CreateFastFFIMetadata() for ineligible signatures, falling back to libffi. Co-authored-by: Bryan English <bryan@bryanenglish.com> Signed-off-by: Paolo Insogna <paolo@cowtech.it> Signed-off-by: Bryan English <bryan@bryanenglish.com> Assisted-By: OpenAI:GPT-5.5 <openai/gpt-5.5> PR-URL: #63068 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Bryan English <bryan@bryanenglish.com>
1 parent be1b204 commit f52cf5e

50 files changed

Lines changed: 3575 additions & 108 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

benchmark/ffi/add-64.js

Lines 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: { return: 'f64', arguments: ['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(20.5, 21.5);
25+
bench.end(n);
26+
27+
lib.close();
28+
}

benchmark/ffi/add-f32.js

Lines 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_f32: { return: 'f32', arguments: ['f32', 'f32'] },
17+
});
18+
19+
const add = functions.add_f32;
20+
21+
function main({ n }) {
22+
bench.start();
23+
for (let i = 0; i < n; ++i)
24+
add(20.5, 21.5);
25+
bench.end(n);
26+
27+
lib.close();
28+
}

benchmark/ffi/add-i16.js

Lines 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_i16: { return: 'i16', arguments: ['i16', 'i16'] },
17+
});
18+
19+
const add = functions.add_i16;
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+
}

benchmark/ffi/add-i64.js

Lines 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_i64: { return: 'i64', arguments: ['i64', 'i64'] },
17+
});
18+
19+
const add = functions.add_i64;
20+
21+
function main({ n }) {
22+
bench.start();
23+
for (let i = 0; i < n; ++i)
24+
add(20n, 22n);
25+
bench.end(n);
26+
27+
lib.close();
28+
}

benchmark/ffi/add-i8.js

Lines 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_i8: { return: 'i8', arguments: ['i8', 'i8'] },
17+
});
18+
19+
const add = functions.add_i8;
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+
}

benchmark/ffi/add-u16.js

Lines 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_u16: { return: 'u16', arguments: ['u16', 'u16'] },
17+
});
18+
19+
const add = functions.add_u16;
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+
}

benchmark/ffi/add-u64.js

Lines 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_u64: { return: 'u64', arguments: ['u64', 'u64'] },
17+
});
18+
19+
const add = functions.add_u64;
20+
21+
function main({ n }) {
22+
bench.start();
23+
for (let i = 0; i < n; ++i)
24+
add(20n, 22n);
25+
bench.end(n);
26+
27+
lib.close();
28+
}

benchmark/ffi/add-u8.js

Lines 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_u8: { return: 'u8', arguments: ['u8', 'u8'] },
17+
});
18+
19+
const add = functions.add_u8;
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+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
first_byte: { return: 'u8', arguments: ['pointer'] },
17+
});
18+
19+
const fn = functions.first_byte;
20+
const bytes = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
21+
const pointer = ffi.getRawPointer(bytes);
22+
23+
function main({ n }) {
24+
bench.start();
25+
for (let i = 0; i < n; ++i)
26+
fn(pointer);
27+
bench.end(n);
28+
29+
lib.close();
30+
}

benchmark/ffi/buffer-first-byte.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
first_byte: { return: 'u8', arguments: ['buffer'] },
17+
});
18+
19+
const fn = functions.first_byte;
20+
const bytes = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
21+
22+
function main({ n }) {
23+
bench.start();
24+
for (let i = 0; i < n; ++i)
25+
fn(bytes);
26+
bench.end(n);
27+
28+
lib.close();
29+
}

0 commit comments

Comments
 (0)