Skip to content

src: extract AllocatedBuffer from env.h #33291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,8 @@
'src/aliased_buffer.h',
'src/aliased_struct.h',
'src/aliased_struct-inl.h',
'src/allocated_buffer.h',
'src/allocated_buffer-inl.h'
'src/async_wrap.h',
'src/async_wrap-inl.h',
'src/base_object.h',
Expand Down
110 changes: 110 additions & 0 deletions src/allocated_buffer-inl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#ifndef SRC_ALLOCATED_BUFFER_INL_H_
#define SRC_ALLOCATED_BUFFER_INL_H_

#include "allocated_buffer.h"
#include "base_object-inl.h"
#include "node_buffer.h"
#include "env-inl.h"
#include "uv.h"
#include "v8.h"
#include "util-inl.h"
#include "node_internals.h"

namespace node {

// 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<v8::Uint8Array> New(Environment* env,
v8::Local<v8::ArrayBuffer> ab,
size_t byte_offset,
size_t length);
}

NoArrayBufferZeroFillScope::NoArrayBufferZeroFillScope(
IsolateData* isolate_data)
: node_allocator_(isolate_data->node_allocator()) {
if (node_allocator_ != nullptr) node_allocator_->zero_fill_field()[0] = 0;
}

NoArrayBufferZeroFillScope::~NoArrayBufferZeroFillScope() {
if (node_allocator_ != nullptr) node_allocator_->zero_fill_field()[0] = 1;
}

AllocatedBuffer AllocatedBuffer::AllocateManaged(
Environment* env,
size_t size) {
NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
std::unique_ptr<v8::BackingStore> bs =
v8::ArrayBuffer::NewBackingStore(env->isolate(), size);
return AllocatedBuffer(env, std::move(bs));
}

AllocatedBuffer::AllocatedBuffer(
Environment* env, std::unique_ptr<v8::BackingStore> bs)
: env_(env), backing_store_(std::move(bs)) {}

AllocatedBuffer::AllocatedBuffer(
Environment* env, uv_buf_t buffer)
: env_(env) {
if (buffer.base == nullptr) return;
auto map = env->released_allocated_buffers();
auto it = map->find(buffer.base);
CHECK_NE(it, map->end());
backing_store_ = std::move(it->second);
map->erase(it);
}

void AllocatedBuffer::Resize(size_t len) {
if (len == 0) {
backing_store_ = v8::ArrayBuffer::NewBackingStore(env_->isolate(), 0);
return;
}
NoArrayBufferZeroFillScope no_zero_fill_scope(env_->isolate_data());
backing_store_ = v8::BackingStore::Reallocate(
env_->isolate(), std::move(backing_store_), len);
}

uv_buf_t AllocatedBuffer::release() {
if (data() == nullptr) return uv_buf_init(nullptr, 0);

CHECK_NOT_NULL(env_);
uv_buf_t ret = uv_buf_init(data(), size());
env_->released_allocated_buffers()->emplace(
ret.base, std::move(backing_store_));
return ret;
}

char* AllocatedBuffer::data() {
if (!backing_store_) return nullptr;
return static_cast<char*>(backing_store_->Data());
}

const char* AllocatedBuffer::data() const {
if (!backing_store_) return nullptr;
return static_cast<char*>(backing_store_->Data());
}


size_t AllocatedBuffer::size() const {
if (!backing_store_) return 0;
return backing_store_->ByteLength();
}

void AllocatedBuffer::clear() {
backing_store_.reset();
}

v8::MaybeLocal<v8::Object> AllocatedBuffer::ToBuffer() {
v8::Local<v8::ArrayBuffer> ab = ToArrayBuffer();
return Buffer::New(env_, ab, 0, ab->ByteLength())
.FromMaybe(v8::Local<v8::Uint8Array>());
}

v8::Local<v8::ArrayBuffer> AllocatedBuffer::ToArrayBuffer() {
return v8::ArrayBuffer::New(env_->isolate(), std::move(backing_store_));
}

} // namespace node

#endif // SRC_ALLOCATED_BUFFER_INL_H_
73 changes: 73 additions & 0 deletions src/allocated_buffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#ifndef SRC_ALLOCATED_BUFFER_H_
#define SRC_ALLOCATED_BUFFER_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "base_object.h"
#include "uv.h"
#include "v8.h"
#include "env.h"

namespace node {

class Environment;

// Disables zero-filling for ArrayBuffer allocations in this scope. This is
// similar to how we implement Buffer.allocUnsafe() in JS land.
class NoArrayBufferZeroFillScope{
public:
inline explicit NoArrayBufferZeroFillScope(IsolateData* isolate_data);
inline ~NoArrayBufferZeroFillScope();

private:
NodeArrayBufferAllocator* node_allocator_;

friend class Environment;
};

// A unique-pointer-ish object that is compatible with the JS engine's
// ArrayBuffer::Allocator.
// TODO(addaleax): We may want to start phasing this out as it's only a
// thin wrapper around v8::BackingStore at this point
struct AllocatedBuffer {
public:
// Utilities 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 static AllocatedBuffer AllocateManaged(Environment* env, size_t size);

AllocatedBuffer() = default;
inline AllocatedBuffer(
Environment* env, std::unique_ptr<v8::BackingStore> bs);
// For this constructor variant, `buffer` *must* come from an earlier call
// to .release
inline AllocatedBuffer(Environment* env, uv_buf_t buffer);

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<v8::Object> ToBuffer();
inline v8::Local<v8::ArrayBuffer> ToArrayBuffer();

AllocatedBuffer(AllocatedBuffer&& other) = default;
AllocatedBuffer& operator=(AllocatedBuffer&& other) = default;
AllocatedBuffer(const AllocatedBuffer& other) = delete;
AllocatedBuffer& operator=(const AllocatedBuffer& other) = delete;

private:
Environment* env_ = nullptr;
std::unique_ptr<v8::BackingStore> backing_store_;

friend class Environment;
};

} // namespace node

#endif // NODE_WANT_INTERNALS

#endif // SRC_ALLOCATED_BUFFER_H_
114 changes: 3 additions & 111 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -867,117 +867,9 @@ inline IsolateData* Environment::isolate_data() const {
return isolate_data_;
}

inline char* Environment::AllocateUnchecked(size_t size) {
return static_cast<char*>(
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) {
// The `len` check is to make sure we don't end up with `nullptr` as our base.
char* new_data = env_->Reallocate(buffer_.base, buffer_.len,
len > 0 ? len : 1);
CHECK_NOT_NULL(new_data);
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();
if (buf.base != nullptr) {
CHECK_NOT_NULL(env_);
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<v8::Object> New(Environment* env,
char* data,
size_t length,
bool uses_malloc);
}

inline v8::MaybeLocal<v8::Object> AllocatedBuffer::ToBuffer() {
CHECK_NOT_NULL(env_);
v8::MaybeLocal<v8::Object> obj = Buffer::New(env_, data(), size(), false);
if (!obj.IsEmpty()) release();
return obj;
}

inline v8::Local<v8::ArrayBuffer> AllocatedBuffer::ToArrayBuffer() {
CHECK_NOT_NULL(env_);
uv_buf_t buf = release();
auto callback = [](void* data, size_t length, void* deleter_data){
CHECK_NOT_NULL(deleter_data);

static_cast<v8::ArrayBuffer::Allocator*>(deleter_data)
->Free(data, length);
};
std::unique_ptr<v8::BackingStore> backing =
v8::ArrayBuffer::NewBackingStore(buf.base,
buf.len,
callback,
env_->isolate()
->GetArrayBufferAllocator());
return v8::ArrayBuffer::New(env_->isolate(),
std::move(backing));
std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>*
Environment::released_allocated_buffers() {
return &released_allocated_buffers_;
}

inline void Environment::ThrowError(const char* errmsg) {
Expand Down
21 changes: 1 addition & 20 deletions src/env.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "env.h"

#include "allocated_buffer-inl.h"
#include "async_wrap.h"
#include "base_object-inl.h"
#include "debug_utils-inl.h"
Expand Down Expand Up @@ -1104,25 +1104,6 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const {
// node, we shift its sizeof() size out of the Environment node.
}

char* Environment::Reallocate(char* data, size_t old_size, size_t size) {
if (old_size == size) return data;
// If we know that the allocator is our ArrayBufferAllocator, we can let
// if reallocate directly.
if (isolate_data()->uses_node_allocator()) {
return static_cast<char*>(
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;
}

void Environment::RunWeakRefCleanup() {
isolate()->ClearKeptObjects();
}
Expand Down
Loading