CreateFunction() ran on every getFunction() call, every getFunctions()
call, and every read of the functions accessor, each time emitting a
trampoline, allocating an FFIFunctionInfo, and on the SharedBuffer path
an ArrayBuffer. lib.functions.foo was therefore a different function on
each read, and calling through the accessor in a loop leaked a page per
iteration until GC: 20000 calls grew RSS by 58 MiB.
Cache the created callable per symbol in function_wrappers_, and
memoize the JS wrapper composed around it. Both entries are weak, so
dropping the last user reference still releases the wrapper and its
trampoline. The JS side stores a WeakRef because V8 can keep a raw
function alive after the wrapper is gone, and a strong value would pin
every wrapper for the lifetime of the library.
Signed-off-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com>
Assisted-by: claude:opus-5
PR-URL: https://github.com/nodejs/node/pull/64971
Fixes: https://github.com/nodejs/node/issues/64970
Reviewed-By: Paolo Insogna <paolo@cowtech.it>