Skip to content
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

src: turn AllocatedBuffer into thin wrapper around v8::BackingStore #33381

Closed
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
134 changes: 54 additions & 80 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -867,117 +867,91 @@ 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));
AllocatedBuffer Environment::AllocateManaged(size_t size) {
NoArrayBufferZeroFillScope no_zero_fill_scope(isolate_data());
std::unique_ptr<v8::BackingStore> bs =
v8::ArrayBuffer::NewBackingStore(isolate(), size);
return AllocatedBuffer(this, std::move(bs));
}

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));
std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>*
Environment::released_allocated_buffers() {
return &released_allocated_buffers_;
}

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

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);
}
AllocatedBuffer::AllocatedBuffer(
Environment* env, uv_buf_t buffer)
: env_(env) {
if (buffer.base == nullptr) return;

inline uv_buf_t AllocatedBuffer::release() {
uv_buf_t ret = buffer_;
buffer_ = uv_buf_init(nullptr, 0);
return ret;
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);
}

inline char* AllocatedBuffer::data() {
return buffer_.base;
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);
}

inline const char* AllocatedBuffer::data() const {
return buffer_.base;
}
inline uv_buf_t AllocatedBuffer::release() {
if (data() == nullptr) return uv_buf_init(nullptr, 0);

inline size_t AllocatedBuffer::size() const {
return buffer_.len;
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;
}

inline AllocatedBuffer::AllocatedBuffer(Environment* env)
: env_(env), buffer_(uv_buf_init(nullptr, 0)) {}

inline AllocatedBuffer::AllocatedBuffer(AllocatedBuffer&& other)
: AllocatedBuffer() {
*this = std::move(other);
char* AllocatedBuffer::data() {
if (!backing_store_) return nullptr;
return static_cast<char*>(backing_store_->Data());
}

inline AllocatedBuffer& AllocatedBuffer::operator=(AllocatedBuffer&& other) {
clear();
env_ = other.env_;
buffer_ = other.release();
return *this;
const char* AllocatedBuffer::data() const {
if (!backing_store_) return nullptr;
return static_cast<char*>(backing_store_->Data());
}

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

inline void AllocatedBuffer::clear() {
uv_buf_t buf = release();
if (buf.base != nullptr) {
CHECK_NOT_NULL(env_);
env_->Free(buf.base, buf.len);
}
void AllocatedBuffer::clear() {
backing_store_.reset();
}

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

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;
v8::Local<v8::ArrayBuffer> ab = ToArrayBuffer();
return Buffer::New(env_, ab, 0, ab->ByteLength())
.FromMaybe(v8::Local<v8::Uint8Array>());
}

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));
return v8::ArrayBuffer::New(env_->isolate(), std::move(backing_store_));
}

inline void Environment::ThrowError(const char* errmsg) {
Expand Down
29 changes: 10 additions & 19 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1133,29 +1133,20 @@ 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();
}

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;
}

// Not really any better place than env.cc at this moment.
void BaseObject::DeleteMe(void* data) {
BaseObject* self = static_cast<BaseObject*>(data);
Expand Down
48 changes: 33 additions & 15 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -557,13 +557,29 @@ struct ContextInfo {

class EnabledDebugList;

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

private:
NodeArrayBufferAllocator* node_allocator_;
};

// A unique-pointer-ish object that is compatible with the JS engine's
// ArrayBuffer::Allocator.
struct AllocatedBuffer {
// TODO(addaleax): We may want to start phasing this out as it's only a thin
// wrapper around v8::BackingStore at this point.
class AllocatedBuffer {
public:
explicit inline AllocatedBuffer(Environment* env = nullptr);
inline AllocatedBuffer(Environment* env, uv_buf_t buf);
inline ~AllocatedBuffer();
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();
Expand All @@ -575,16 +591,14 @@ struct AllocatedBuffer {
inline v8::MaybeLocal<v8::Object> ToBuffer();
inline v8::Local<v8::ArrayBuffer> ToArrayBuffer();

inline AllocatedBuffer(AllocatedBuffer&& other);
inline AllocatedBuffer& operator=(AllocatedBuffer&& other);
AllocatedBuffer(AllocatedBuffer&& other) = default;
AllocatedBuffer& operator=(AllocatedBuffer&& other) = default;
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_;
Environment* env_ = nullptr;
std::unique_ptr<v8::BackingStore> backing_store_;

friend class Environment;
};
Expand Down Expand Up @@ -959,11 +973,7 @@ class Environment : public MemoryRetainer {
// 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 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 AllocatedBuffer AllocateManaged(size_t size);

inline bool printed_error() const;
inline void set_printed_error(bool value);
Expand Down Expand Up @@ -1251,6 +1261,9 @@ class Environment : public MemoryRetainer {
void RunAndClearNativeImmediates(bool only_refed = false);
void RunAndClearInterrupts();

inline std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>*
released_allocated_buffers();

private:
template <typename Fn>
inline void CreateImmediate(Fn&& cb, bool ref);
Expand Down Expand Up @@ -1413,6 +1426,11 @@ class Environment : public MemoryRetainer {
// We should probably find a way to just use plain `v8::String`s created from
// the source passed to LoadEnvironment() directly instead.
std::unique_ptr<v8::String::Value> main_utf16_;

// Used by AllocatedBuffer::release() to keep track of the BackingStore for
// a given pointer.
std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>
released_allocated_buffers_;
};

} // namespace node
Expand Down
59 changes: 7 additions & 52 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -327,16 +327,7 @@ MaybeLocal<Object> New(Environment* env, size_t length) {
return Local<Object>();
}

AllocatedBuffer ret(env);
if (length > 0) {
ret = env->AllocateManaged(length, false);
if (ret.data() == nullptr) {
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
return Local<Object>();
}
}

return scope.EscapeMaybe(ret.ToBuffer());
return scope.EscapeMaybe(env->AllocateManaged(length).ToBuffer());
}


Expand All @@ -363,14 +354,8 @@ MaybeLocal<Object> Copy(Environment* env, const char* data, size_t length) {
return Local<Object>();
}

AllocatedBuffer ret(env);
AllocatedBuffer ret = env->AllocateManaged(length);
if (length > 0) {
CHECK_NOT_NULL(data);
ret = env->AllocateManaged(length, false);
if (ret.data() == nullptr) {
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
return Local<Object>();
}
memcpy(ret.data(), data, length);
}

Expand Down Expand Up @@ -445,53 +430,23 @@ MaybeLocal<Object> New(Isolate* isolate, char* data, size_t length) {
return MaybeLocal<Object>();
}
Local<Object> obj;
if (Buffer::New(env, data, length, true).ToLocal(&obj))
if (Buffer::New(env, data, length).ToLocal(&obj))
return handle_scope.Escape(obj);
return Local<Object>();
}

// Warning: If this call comes through the public node_buffer.h API,
// the contract for this function is that `data` is allocated with malloc()
// The contract for this function is that `data` is allocated with malloc()
// and not necessarily isolate's ArrayBuffer::Allocator.
MaybeLocal<Object> New(Environment* env,
char* data,
size_t length,
bool uses_malloc) {
size_t length) {
if (length > 0) {
CHECK_NOT_NULL(data);
CHECK(length <= kMaxLength);
}

if (uses_malloc) {
if (!env->isolate_data()->uses_node_allocator()) {
// We don't know for sure that the allocator is malloc()-based, so we need
// to fall back to the FreeCallback variant.
auto free_callback = [](char* data, void* hint) { free(data); };
return New(env, data, length, free_callback, nullptr);
} else {
// This is malloc()-based, so we can acquire it into our own
// ArrayBufferAllocator.
CHECK_NOT_NULL(env->isolate_data()->node_allocator());
env->isolate_data()->node_allocator()->RegisterPointer(data, length);
}
}

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(data,
length,
callback,
env->isolate()
->GetArrayBufferAllocator());
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(),
std::move(backing));

return Buffer::New(env, ab, 0, length).FromMaybe(Local<Object>());
auto free_callback = [](char* data, void* hint) { free(data); };
return New(env, data, length, free_callback, nullptr);
}

namespace {
Expand Down