From 041daaa273e7ef68b590afbcb233e56d9661b950 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sun, 1 Dec 2019 01:30:40 +0100 Subject: [PATCH] src: port memory-tracking allocator from QUIC repo This implements a memory-tracking allocator that can be used to provide memory allocation facilities to several thread-safe C libraries, including nghttp2, nghttp3, ngtcp3 and uvwasi. Refs: https://github.com/nodejs/quic/blob/34ee0bc96f804c73cb22b2945a1a78f780938492/src/node_mem.h Co-authored-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/30745 Refs: https://github.com/nodejs/quic/pull/126 Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Rich Trott --- node.gyp | 2 + src/node_mem-inl.h | 112 +++++++++++++++++++++++++++++++++++++++++++++ src/node_mem.h | 41 +++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 src/node_mem-inl.h create mode 100644 src/node_mem.h diff --git a/node.gyp b/node.gyp index 0a6bbd61009e84..7b971b57397124 100644 --- a/node.gyp +++ b/node.gyp @@ -627,6 +627,8 @@ 'src/node_i18n.h', 'src/node_internals.h', 'src/node_main_instance.h', + 'src/node_mem.h', + 'src/node_mem-inl.h', 'src/node_messaging.h', 'src/node_metadata.h', 'src/node_mutex.h', diff --git a/src/node_mem-inl.h b/src/node_mem-inl.h new file mode 100644 index 00000000000000..ad6fc45b36942d --- /dev/null +++ b/src/node_mem-inl.h @@ -0,0 +1,112 @@ +#ifndef SRC_NODE_MEM_INL_H_ +#define SRC_NODE_MEM_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node_mem.h" +#include "node_internals.h" + +namespace node { +namespace mem { + +template +AllocatorStruct NgLibMemoryManager::MakeAllocator() { + return AllocatorStruct { + static_cast(static_cast(this)), + MallocImpl, + FreeImpl, + CallocImpl, + ReallocImpl + }; +} + +template +void* NgLibMemoryManager::ReallocImpl(void* ptr, + size_t size, + void* user_data) { + Class* manager = static_cast(user_data); + + size_t previous_size = 0; + char* original_ptr = nullptr; + + // We prepend each allocated buffer with a size_t containing the full + // size of the allocation. + if (size > 0) size += sizeof(size_t); + + if (ptr != nullptr) { + // We are free()ing or re-allocating. + original_ptr = static_cast(ptr) - sizeof(size_t); + previous_size = *reinterpret_cast(original_ptr); + // This means we called StopTracking() on this pointer before. + if (previous_size == 0) { + // Fall back to the standard Realloc() function. + char* ret = UncheckedRealloc(original_ptr, size); + if (ret != nullptr) + ret += sizeof(size_t); + return ret; + } + } + + manager->CheckAllocatedSize(previous_size); + + char* mem = UncheckedRealloc(original_ptr, size); + + if (mem != nullptr) { + // Adjust the memory info counter. + // TODO(addaleax): Avoid the double bookkeeping we do with + // current_nghttp2_memory_ + AdjustAmountOfExternalAllocatedMemory + // and provide versions of our memory allocation utilities that take an + // Environment*/Isolate* parameter and call the V8 method transparently. + const int64_t new_size = size - previous_size; + manager->IncreaseAllocatedSize(new_size); + manager->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( + new_size); + *reinterpret_cast(mem) = size; + mem += sizeof(size_t); + } else if (size == 0) { + manager->DecreaseAllocatedSize(previous_size); + manager->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( + -static_cast(previous_size)); + } + return mem; +} + +template +void* NgLibMemoryManager::MallocImpl(size_t size, void* user_data) { + return ReallocImpl(nullptr, size, user_data); +} + +template +void NgLibMemoryManager::FreeImpl(void* ptr, void* user_data) { + if (ptr == nullptr) return; + CHECK_NULL(ReallocImpl(ptr, 0, user_data)); +} + +template +void* NgLibMemoryManager::CallocImpl(size_t nmemb, + size_t size, + void* user_data) { + size_t real_size = MultiplyWithOverflowCheck(nmemb, size); + void* mem = MallocImpl(real_size, user_data); + if (mem != nullptr) + memset(mem, 0, real_size); + return mem; +} + +template +void NgLibMemoryManager::StopTrackingMemory(void* ptr) { + size_t* original_ptr = reinterpret_cast( + static_cast(ptr) - sizeof(size_t)); + Class* manager = static_cast(this); + manager->DecreaseAllocatedSize(*original_ptr); + manager->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( + -static_cast(*original_ptr)); + *original_ptr = 0; +} + +} // namespace mem +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_MEM_INL_H_ diff --git a/src/node_mem.h b/src/node_mem.h new file mode 100644 index 00000000000000..0d3388ad4766bb --- /dev/null +++ b/src/node_mem.h @@ -0,0 +1,41 @@ +#ifndef SRC_NODE_MEM_H_ +#define SRC_NODE_MEM_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include + +namespace node { +namespace mem { + +// Both ngtcp2 and nghttp2 allow custom allocators that +// follow exactly the same structure and behavior, but +// use different struct names. To allow for code re-use, +// the NgLibMemoryManager template class can be used for both. + +template +class NgLibMemoryManager { + public: + // Class needs to provide these methods: + // void CheckAllocatedSize(size_t previous_size) const; + // void IncreaseAllocatedSize(size_t size); + // void DecreaseAllocatedSize(size_t size); + // Environment* env() const; + + AllocatorStructName MakeAllocator(); + + void StopTrackingMemory(void* ptr); + + private: + static void* ReallocImpl(void* ptr, size_t size, void* user_data); + static void* MallocImpl(size_t size, void* user_data); + static void FreeImpl(void* ptr, void* user_data); + static void* CallocImpl(size_t nmemb, size_t size, void* user_data); +}; + +} // namespace mem +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_MEM_H_