mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
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>
213 lines
7.5 KiB
C++
213 lines
7.5 KiB
C++
#pragma once
|
|
|
|
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
|
|
|
#include "base_object.h"
|
|
#include "ffi.h"
|
|
#include "ffi/fast.h"
|
|
#include "uv.h"
|
|
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
// libffi only accelerates reusable call plans on x86-64 System V. Other
|
|
// targets implement the API by calling ffi_call(), which adds no benefit.
|
|
#if defined(FFI_VERSION_NUMBER) && FFI_VERSION_NUMBER >= 30700 && \
|
|
defined(__x86_64__) && !defined(__ILP32__) && !defined(X86_WIN64) && \
|
|
!defined(_WIN32)
|
|
#define NODE_FFI_HAS_FAST_CALL_PLAN 1
|
|
#endif
|
|
|
|
namespace node::ffi {
|
|
|
|
class DynamicLibrary;
|
|
struct FFIFunction;
|
|
|
|
struct FFIFunction {
|
|
FFIFunction() = default;
|
|
FFIFunction(const FFIFunction&) = delete;
|
|
FFIFunction& operator=(const FFIFunction&) = delete;
|
|
FFIFunction(FFIFunction&&) = delete;
|
|
FFIFunction& operator=(FFIFunction&&) = delete;
|
|
|
|
bool closed = false;
|
|
|
|
void* ptr = nullptr;
|
|
ffi_cif cif = {};
|
|
std::vector<ffi_type*> args;
|
|
ffi_type* return_type = nullptr;
|
|
std::vector<std::string> arg_type_names;
|
|
std::string return_type_name;
|
|
#if defined(NODE_FFI_HAS_FAST_CALL_PLAN)
|
|
// The plan borrows cif, so it must remain uniquely owned by this instance.
|
|
std::unique_ptr<ffi_call_plan, decltype(&ffi_call_plan_free)> call_plan{
|
|
nullptr, ffi_call_plan_free};
|
|
#endif
|
|
|
|
void Invoke(void* result, void** values);
|
|
};
|
|
|
|
class FFIFunctionInfo final : public BaseObject {
|
|
public:
|
|
enum InternalFields {
|
|
kLibrary = BaseObject::kInternalFieldCount,
|
|
kInternalFieldCount
|
|
};
|
|
|
|
FFIFunctionInfo(Environment* env,
|
|
v8::Local<v8::Object> object,
|
|
std::shared_ptr<FFIFunction> fn,
|
|
DynamicLibrary* library);
|
|
|
|
void MemoryInfo(MemoryTracker* tracker) const override;
|
|
SET_MEMORY_INFO_NAME(FFIFunctionInfo)
|
|
SET_SELF_SIZE(FFIFunctionInfo)
|
|
|
|
static BaseObjectPtr<FFIFunctionInfo> Create(Environment* env,
|
|
std::shared_ptr<FFIFunction> fn,
|
|
DynamicLibrary* library);
|
|
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
|
|
IsolateData* isolate_data);
|
|
|
|
private:
|
|
std::shared_ptr<FFIFunction> fn;
|
|
std::shared_ptr<v8::BackingStore> sb_backing;
|
|
std::unique_ptr<FastFFIMetadata> fast_metadata;
|
|
std::unique_ptr<FastFFIMetadata> fast_buffer_metadata;
|
|
|
|
friend class DynamicLibrary;
|
|
};
|
|
|
|
struct FFICallback {
|
|
DynamicLibrary* owner;
|
|
Environment* env;
|
|
std::thread::id thread_id;
|
|
v8::Global<v8::Function> fn;
|
|
ffi_closure* closure;
|
|
|
|
void* ptr;
|
|
ffi_cif cif;
|
|
std::vector<ffi_type*> args;
|
|
ffi_type* return_type;
|
|
|
|
~FFICallback() {
|
|
if (!fn.IsEmpty()) {
|
|
fn.Reset();
|
|
}
|
|
|
|
if (closure != nullptr) {
|
|
ffi_closure_free(closure);
|
|
closure = nullptr;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct ResolvedFunction {
|
|
std::string name;
|
|
std::shared_ptr<FFIFunction> fn;
|
|
bool should_cache_symbol;
|
|
bool should_cache_function;
|
|
};
|
|
|
|
class DynamicLibrary : public BaseObject {
|
|
public:
|
|
DynamicLibrary(Environment* env, v8::Local<v8::Object> object);
|
|
~DynamicLibrary() override;
|
|
|
|
void MemoryInfo(MemoryTracker* tracker) const override;
|
|
|
|
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
|
|
Environment* env);
|
|
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
static void InvokeFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
static void InvokeFunctionSB(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
static void InvokeCallback(ffi_cif* cif,
|
|
void* ret,
|
|
void** args,
|
|
void* user_data);
|
|
|
|
static void GetPath(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
static void GetFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
static void GetFunctions(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
static void GetSymbol(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
static void GetSymbols(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
|
|
static void RegisterCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
static void UnregisterCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
static void RefCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
static void UnrefCallback(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
|
|
SET_MEMORY_INFO_NAME(DynamicLibrary)
|
|
SET_SELF_SIZE(DynamicLibrary)
|
|
|
|
private:
|
|
void Close();
|
|
v8::Maybe<void*> ResolveSymbol(Environment* env, const std::string& name);
|
|
struct PreparedFunction {
|
|
std::shared_ptr<FFIFunction> fn;
|
|
bool should_cache_symbol;
|
|
bool should_cache_function;
|
|
};
|
|
v8::Maybe<PreparedFunction> PrepareFunction(Environment* env,
|
|
const std::string& name,
|
|
v8::Local<v8::Object> signature);
|
|
v8::MaybeLocal<v8::Function> CreateFunction(
|
|
Environment* env,
|
|
const std::string& name,
|
|
const std::shared_ptr<FFIFunction>& fn);
|
|
static void CleanupFunctionInfo(
|
|
const v8::WeakCallbackInfo<FFIFunctionInfo>& data);
|
|
bool is_closed() const;
|
|
|
|
uv_lib_t lib_ = {};
|
|
std::string path_;
|
|
std::unordered_map<std::string, void*> symbols_;
|
|
std::unordered_map<std::string, std::shared_ptr<FFIFunction>> functions_;
|
|
// Callables created for `functions_`, so repeated resolution of the same
|
|
// symbol reuses one wrapper instead of emitting another trampoline. The
|
|
// handles are weak: an entry disappears once user code drops the wrapper,
|
|
// which keeps the map from rooting the library through the wrapper's
|
|
// FFIFunctionInfo.
|
|
std::unordered_map<std::string, v8::Global<v8::Function>> function_wrappers_;
|
|
std::unordered_map<void*, std::unique_ptr<FFICallback>> callbacks_;
|
|
};
|
|
|
|
void GetInt8(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void GetUint8(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void GetInt16(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void GetUint16(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void GetInt32(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void GetUint32(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void GetInt64(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void GetUint64(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void GetFloat32(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void GetFloat64(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
|
|
void SetInt8(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void SetUint8(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void SetInt16(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void SetUint16(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void SetInt32(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void SetUint32(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void SetInt64(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void SetUint64(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void SetFloat32(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void SetFloat64(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
|
|
void ToString(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void ToBuffer(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void ToArrayBuffer(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void ExportBytes(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void GetRawPointer(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
void GetCurrentEventLoop(const v8::FunctionCallbackInfo<v8::Value>& args);
|
|
|
|
} // namespace node::ffi
|
|
|
|
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|