Version
main
Platform
Subsystem
ffi
What steps will reproduce the bug?
repro.c
#include <stdint.h>
int32_t add2(int32_t a, int32_t b) {
return a + b;
}
repro.js
import { DynamicLibrary, suffix } from 'node:ffi';
import { stat } from 'node:fs/promises';
const libPath = new URL(`./repro.${suffix}`, import.meta.url).pathname;
await stat(libPath);
const lib = new DynamicLibrary(libPath);
lib.getFunction('add2', { arguments: ['i32', 'i32'], return: 'i32' });
console.log('same wrapper on two reads:', lib.functions.add2 === lib.functions.add2);
// Idiomatic call through the accessor, which re-resolves on every iteration.
const before = process.memoryUsage.rss();
let sum = 0;
for (let i = 0; i < 20000; i++) sum += lib.functions.add2(20, 22);
const after = process.memoryUsage.rss();
console.log(`sum: ${sum}`);
console.log(`rss growth: ${((after - before) / 1024 / 1024).toFixed(1)} MiB`);
lib.close();
Commands to run:
$ cc -shared -fPIC -O2 -o repro.so repro.c
$ node --experimental-ffi repro.js
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
Reading an already-resolved function off library.functions returns the same wrapper each time, and calling through the accessor in a loop does not grow memory.
What do you see instead?
same wrapper on two reads: false
sum: 840000
rss growth: 56.0 MiB
Each read builds a new wrapper, so lib.functions.add2 !== lib.functions.add2, and the loop allocates a fresh trampoline page plus a new FFIFunctionInfo per iteration, holding all of them until GC.
Additional information
No response
Version
main
Platform
Subsystem
ffi
What steps will reproduce the bug?
repro.crepro.jsCommands to run:
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
Reading an already-resolved function off
library.functionsreturns the same wrapper each time, and calling through the accessor in a loop does not grow memory.What do you see instead?
Each read builds a new wrapper, so
lib.functions.add2 !== lib.functions.add2, and the loop allocates a fresh trampoline page plus a newFFIFunctionInfoper iteration, holding all of them until GC.Additional information
No response