From 9627be8c26e096702d07cb142745e42a23c2a515 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 18 Feb 2019 22:58:19 +0100 Subject: [PATCH] src: add allocation utils to env Add a RAII utility for managing blocks of memory that have been allocated with the `ArrayBuffer::Allocator` for a given `Isolate`. PR-URL: https://github.com/nodejs/node/pull/26207 Reviewed-By: James M Snell Reviewed-By: Joyee Cheung --- src/env-inl.h | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/env.cc | 18 ++++++++++ src/env.h | 41 +++++++++++++++++++++ 3 files changed, 157 insertions(+) diff --git a/src/env-inl.h b/src/env-inl.h index 51c7e0d7b06561..63b71daf15a245 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -715,6 +715,104 @@ inline IsolateData* Environment::isolate_data() const { return isolate_data_; } +inline char* Environment::AllocateUnchecked(size_t size) { + return static_cast( + isolate_data()->allocator()->AllocateUninitialized(size)); +} + +inline char* Environment::Allocate(size_t size) { + char* ret = AllocateUnchecked(size); + CHECK_NE(ret, nullptr); + return ret; +} + +inline void Environment::Free(char* data, size_t size) { + if (data != nullptr) + isolate_data()->allocator()->Free(data, size); +} + +inline AllocatedBuffer Environment::AllocateManaged(size_t size, bool checked) { + char* data = checked ? Allocate(size) : AllocateUnchecked(size); + if (data == nullptr) size = 0; + return AllocatedBuffer(this, uv_buf_init(data, size)); +} + +inline AllocatedBuffer::AllocatedBuffer(Environment* env, uv_buf_t buf) + : env_(env), buffer_(buf) {} + +inline void AllocatedBuffer::Resize(size_t len) { + char* new_data = env_->Reallocate(buffer_.base, buffer_.len, len); + CHECK_IMPLIES(len > 0, new_data != nullptr); + buffer_ = uv_buf_init(new_data, len); +} + +inline uv_buf_t AllocatedBuffer::release() { + uv_buf_t ret = buffer_; + buffer_ = uv_buf_init(nullptr, 0); + return ret; +} + +inline char* AllocatedBuffer::data() { + return buffer_.base; +} + +inline const char* AllocatedBuffer::data() const { + return buffer_.base; +} + +inline size_t AllocatedBuffer::size() const { + return buffer_.len; +} + +inline AllocatedBuffer::AllocatedBuffer(Environment* env) + : env_(env), buffer_(uv_buf_init(nullptr, 0)) {} + +inline AllocatedBuffer::AllocatedBuffer(AllocatedBuffer&& other) + : AllocatedBuffer() { + *this = std::move(other); +} + +inline AllocatedBuffer& AllocatedBuffer::operator=(AllocatedBuffer&& other) { + clear(); + env_ = other.env_; + buffer_ = other.release(); + return *this; +} + +inline AllocatedBuffer::~AllocatedBuffer() { + clear(); +} + +inline void AllocatedBuffer::clear() { + uv_buf_t buf = release(); + env_->Free(buf.base, buf.len); +} + +// It's a bit awkward to define this Buffer::New() overload here, but it +// avoids a circular dependency with node_internals.h. +namespace Buffer { +v8::MaybeLocal New(Environment* env, + char* data, + size_t length, + bool uses_malloc); +} + +inline v8::MaybeLocal AllocatedBuffer::ToBuffer() { + CHECK_NOT_NULL(env_); + v8::MaybeLocal obj = Buffer::New(env_, data(), size(), false); + if (!obj.IsEmpty()) release(); + return obj; +} + +inline v8::Local AllocatedBuffer::ToArrayBuffer() { + CHECK_NOT_NULL(env_); + uv_buf_t buf = release(); + return v8::ArrayBuffer::New(env_->isolate(), + buf.base, + buf.len, + v8::ArrayBufferCreationMode::kInternalized); +} + inline void Environment::ThrowError(const char* errmsg) { ThrowError(v8::Exception::Error, errmsg); } diff --git a/src/env.cc b/src/env.cc index fdf47b964a240c..e5399aa65160ac 100644 --- a/src/env.cc +++ b/src/env.cc @@ -21,6 +21,7 @@ namespace node { using errors::TryCatchScope; +using v8::ArrayBuffer; using v8::Boolean; using v8::Context; using v8::EmbedderGraph; @@ -924,6 +925,23 @@ void Environment::BuildEmbedderGraph(Isolate* isolate, }); } +char* Environment::Reallocate(char* data, size_t old_size, size_t size) { + // If we know that the allocator is our ArrayBufferAllocator, we can let + // if reallocate directly. + if (isolate_data()->uses_node_allocator()) { + return static_cast( + isolate_data()->node_allocator()->Reallocate(data, old_size, size)); + } + // Generic allocators do not provide a reallocation method; we need to + // allocate a new chunk of memory and copy the data over. + char* new_data = AllocateUnchecked(size); + if (new_data == nullptr) return nullptr; + memcpy(new_data, data, std::min(size, old_size)); + if (size > old_size) + memset(new_data + old_size, 0, size - old_size); + Free(data, old_size); + return new_data; +} // Not really any better place than env.cc at this moment. void BaseObject::DeleteMe(void* data) { diff --git a/src/env.h b/src/env.h index c94edf34524906..dc5ba3b40d7a23 100644 --- a/src/env.h +++ b/src/env.h @@ -474,6 +474,38 @@ enum class DebugCategory { CATEGORY_COUNT }; +// A unique-pointer-ish object that is compatible with the JS engine's +// ArrayBuffer::Allocator. +struct AllocatedBuffer { + public: + explicit inline AllocatedBuffer(Environment* env = nullptr); + inline AllocatedBuffer(Environment* env, uv_buf_t buf); + inline ~AllocatedBuffer(); + inline void Resize(size_t len); + + inline uv_buf_t release(); + inline char* data(); + inline const char* data() const; + inline size_t size() const; + inline void clear(); + + inline v8::MaybeLocal ToBuffer(); + inline v8::Local ToArrayBuffer(); + + inline AllocatedBuffer(AllocatedBuffer&& other); + inline AllocatedBuffer& operator=(AllocatedBuffer&& other); + AllocatedBuffer(const AllocatedBuffer& other) = delete; + AllocatedBuffer& operator=(const AllocatedBuffer& other) = delete; + + private: + Environment* env_; + // We do not pass this to libuv directly, but uv_buf_t is a convenient way + // to represent a chunk of memory, and plays nicely with other parts of core. + uv_buf_t buffer_; + + friend class Environment; +}; + class Environment { public: class AsyncHooks { @@ -695,6 +727,15 @@ class Environment { inline IsolateData* isolate_data() const; + // Utilites that allocate memory using the Isolate's ArrayBuffer::Allocator. + // In particular, using AllocateManaged() will provide a RAII-style object + // with easy conversion to `Buffer` and `ArrayBuffer` objects. + inline AllocatedBuffer AllocateManaged(size_t size, bool checked = true); + inline char* Allocate(size_t size); + inline char* AllocateUnchecked(size_t size); + char* Reallocate(char* data, size_t old_size, size_t size); + inline void Free(char* data, size_t size); + inline bool printed_error() const; inline void set_printed_error(bool value);