#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 <unordered_set>
#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() {
if (auto registry = pointer_registry.lock()) {
registry->erase(this);
}
}
FFIFunction(const FFIFunction&) = delete;
FFIFunction& operator=(const FFIFunction&) = delete;
FFIFunction(FFIFunction&&) = delete;
FFIFunction& operator=(FFIFunction&&) = delete;
bool closed = false;
std::weak_ptr<std::unordered_set<FFIFunction*>> pointer_registry;
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 {
// Callback pointers are plain addresses that cannot root the library, so
// each registered callback keeps its owner alive until it is unregistered
// or the library is closed.
BaseObjectPtr<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 ToFunction(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,
void* ptr = nullptr);
v8::MaybeLocal<v8::Function> CreateFunction(
Environment* env,
const std::string& name,
const std::shared_ptr<FFIFunction>& fn);
v8::MaybeLocal<v8::Function> BuildFunction(
Environment* env,
const std::string& name,
const std::shared_ptr<FFIFunction>& fn,
bool optimize);
static void CleanupHook(void* arg);
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::shared_ptr<std::unordered_set<FFIFunction*>> pointer_functions_;
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