Skip to content

Commit 3ef0e38

Browse files
committed
src: turn AllocatedBuffer into thin wrapper around v8::BackingStore
With V8 8.3, the two APIs basically have feature parity, so `AllocatedBuffer` can be turned into a wrapper around `v8::BackingStore`.
1 parent 241ed44 commit 3ef0e38

File tree

7 files changed

+109
-174
lines changed

7 files changed

+109
-174
lines changed

src/env-inl.h

Lines changed: 54 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -867,117 +867,91 @@ inline IsolateData* Environment::isolate_data() const {
867867
return isolate_data_;
868868
}
869869

870-
inline char* Environment::AllocateUnchecked(size_t size) {
871-
return static_cast<char*>(
872-
isolate_data()->allocator()->AllocateUninitialized(size));
870+
AllocatedBuffer Environment::AllocateManaged(size_t size) {
871+
NoArrayBufferZeroFillScope no_zero_fill_scope(isolate_data());
872+
std::unique_ptr<v8::BackingStore> bs =
873+
v8::ArrayBuffer::NewBackingStore(isolate(), size);
874+
return AllocatedBuffer(this, std::move(bs));
873875
}
874876

875-
inline char* Environment::Allocate(size_t size) {
876-
char* ret = AllocateUnchecked(size);
877-
CHECK_NE(ret, nullptr);
878-
return ret;
879-
}
880-
881-
inline void Environment::Free(char* data, size_t size) {
882-
if (data != nullptr)
883-
isolate_data()->allocator()->Free(data, size);
884-
}
885-
886-
inline AllocatedBuffer Environment::AllocateManaged(size_t size, bool checked) {
887-
char* data = checked ? Allocate(size) : AllocateUnchecked(size);
888-
if (data == nullptr) size = 0;
889-
return AllocatedBuffer(this, uv_buf_init(data, size));
877+
std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>*
878+
Environment::released_allocated_buffers() {
879+
return &released_allocated_buffers_;
890880
}
891881

892-
inline AllocatedBuffer::AllocatedBuffer(Environment* env, uv_buf_t buf)
893-
: env_(env), buffer_(buf) {}
882+
AllocatedBuffer::AllocatedBuffer(
883+
Environment* env, std::unique_ptr<v8::BackingStore> bs)
884+
: env_(env), backing_store_(std::move(bs)) {}
894885

895-
inline void AllocatedBuffer::Resize(size_t len) {
896-
// The `len` check is to make sure we don't end up with `nullptr` as our base.
897-
char* new_data = env_->Reallocate(buffer_.base, buffer_.len,
898-
len > 0 ? len : 1);
899-
CHECK_NOT_NULL(new_data);
900-
buffer_ = uv_buf_init(new_data, len);
901-
}
886+
AllocatedBuffer::AllocatedBuffer(
887+
Environment* env, uv_buf_t buffer)
888+
: env_(env) {
889+
if (buffer.base == nullptr) return;
902890

903-
inline uv_buf_t AllocatedBuffer::release() {
904-
uv_buf_t ret = buffer_;
905-
buffer_ = uv_buf_init(nullptr, 0);
906-
return ret;
891+
auto map = env->released_allocated_buffers();
892+
auto it = map->find(buffer.base);
893+
CHECK_NE(it, map->end());
894+
backing_store_ = std::move(it->second);
895+
map->erase(it);
907896
}
908897

909-
inline char* AllocatedBuffer::data() {
910-
return buffer_.base;
898+
void AllocatedBuffer::Resize(size_t len) {
899+
if (len == 0) {
900+
backing_store_ = v8::ArrayBuffer::NewBackingStore(env_->isolate(), 0);
901+
return;
902+
}
903+
NoArrayBufferZeroFillScope no_zero_fill_scope(env_->isolate_data());
904+
backing_store_ = v8::BackingStore::Reallocate(
905+
env_->isolate(), std::move(backing_store_), len);
911906
}
912907

913-
inline const char* AllocatedBuffer::data() const {
914-
return buffer_.base;
915-
}
908+
inline uv_buf_t AllocatedBuffer::release() {
909+
if (data() == nullptr) return uv_buf_init(nullptr, 0);
916910

917-
inline size_t AllocatedBuffer::size() const {
918-
return buffer_.len;
911+
CHECK_NOT_NULL(env_);
912+
uv_buf_t ret = uv_buf_init(data(), size());
913+
env_->released_allocated_buffers()->emplace(
914+
ret.base, std::move(backing_store_));
915+
return ret;
919916
}
920917

921-
inline AllocatedBuffer::AllocatedBuffer(Environment* env)
922-
: env_(env), buffer_(uv_buf_init(nullptr, 0)) {}
923-
924-
inline AllocatedBuffer::AllocatedBuffer(AllocatedBuffer&& other)
925-
: AllocatedBuffer() {
926-
*this = std::move(other);
918+
char* AllocatedBuffer::data() {
919+
if (!backing_store_) return nullptr;
920+
return static_cast<char*>(backing_store_->Data());
927921
}
928922

929-
inline AllocatedBuffer& AllocatedBuffer::operator=(AllocatedBuffer&& other) {
930-
clear();
931-
env_ = other.env_;
932-
buffer_ = other.release();
933-
return *this;
923+
const char* AllocatedBuffer::data() const {
924+
if (!backing_store_) return nullptr;
925+
return static_cast<char*>(backing_store_->Data());
934926
}
935927

936-
inline AllocatedBuffer::~AllocatedBuffer() {
937-
clear();
928+
size_t AllocatedBuffer::size() const {
929+
if (!backing_store_) return 0;
930+
return backing_store_->ByteLength();
938931
}
939932

940-
inline void AllocatedBuffer::clear() {
941-
uv_buf_t buf = release();
942-
if (buf.base != nullptr) {
943-
CHECK_NOT_NULL(env_);
944-
env_->Free(buf.base, buf.len);
945-
}
933+
void AllocatedBuffer::clear() {
934+
backing_store_.reset();
946935
}
947936

948937
// It's a bit awkward to define this Buffer::New() overload here, but it
949938
// avoids a circular dependency with node_internals.h.
950939
namespace Buffer {
951-
v8::MaybeLocal<v8::Object> New(Environment* env,
952-
char* data,
953-
size_t length,
954-
bool uses_malloc);
940+
v8::MaybeLocal<v8::Uint8Array> New(Environment* env,
941+
v8::Local<v8::ArrayBuffer> ab,
942+
size_t byte_offset,
943+
size_t length);
955944
}
956945

957946
inline v8::MaybeLocal<v8::Object> AllocatedBuffer::ToBuffer() {
958-
CHECK_NOT_NULL(env_);
959-
v8::MaybeLocal<v8::Object> obj = Buffer::New(env_, data(), size(), false);
960-
if (!obj.IsEmpty()) release();
961-
return obj;
947+
v8::Local<v8::ArrayBuffer> ab = ToArrayBuffer();
948+
return Buffer::New(env_, ab, 0, ab->ByteLength())
949+
.FromMaybe(v8::Local<v8::Uint8Array>());
962950
}
963951

964952
inline v8::Local<v8::ArrayBuffer> AllocatedBuffer::ToArrayBuffer() {
965953
CHECK_NOT_NULL(env_);
966-
uv_buf_t buf = release();
967-
auto callback = [](void* data, size_t length, void* deleter_data){
968-
CHECK_NOT_NULL(deleter_data);
969-
970-
static_cast<v8::ArrayBuffer::Allocator*>(deleter_data)
971-
->Free(data, length);
972-
};
973-
std::unique_ptr<v8::BackingStore> backing =
974-
v8::ArrayBuffer::NewBackingStore(buf.base,
975-
buf.len,
976-
callback,
977-
env_->isolate()
978-
->GetArrayBufferAllocator());
979-
return v8::ArrayBuffer::New(env_->isolate(),
980-
std::move(backing));
954+
return v8::ArrayBuffer::New(env_->isolate(), std::move(backing_store_));
981955
}
982956

983957
inline void Environment::ThrowError(const char* errmsg) {

src/env.cc

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,29 +1133,20 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const {
11331133
// node, we shift its sizeof() size out of the Environment node.
11341134
}
11351135

1136-
char* Environment::Reallocate(char* data, size_t old_size, size_t size) {
1137-
if (old_size == size) return data;
1138-
// If we know that the allocator is our ArrayBufferAllocator, we can let
1139-
// if reallocate directly.
1140-
if (isolate_data()->uses_node_allocator()) {
1141-
return static_cast<char*>(
1142-
isolate_data()->node_allocator()->Reallocate(data, old_size, size));
1143-
}
1144-
// Generic allocators do not provide a reallocation method; we need to
1145-
// allocate a new chunk of memory and copy the data over.
1146-
char* new_data = AllocateUnchecked(size);
1147-
if (new_data == nullptr) return nullptr;
1148-
memcpy(new_data, data, std::min(size, old_size));
1149-
if (size > old_size)
1150-
memset(new_data + old_size, 0, size - old_size);
1151-
Free(data, old_size);
1152-
return new_data;
1153-
}
1154-
11551136
void Environment::RunWeakRefCleanup() {
11561137
isolate()->ClearKeptObjects();
11571138
}
11581139

1140+
NoArrayBufferZeroFillScope::NoArrayBufferZeroFillScope(
1141+
IsolateData* isolate_data)
1142+
: node_allocator_(isolate_data->node_allocator()) {
1143+
if (node_allocator_ != nullptr) node_allocator_->zero_fill_field()[0] = 0;
1144+
}
1145+
1146+
NoArrayBufferZeroFillScope::~NoArrayBufferZeroFillScope() {
1147+
if (node_allocator_ != nullptr) node_allocator_->zero_fill_field()[0] = 1;
1148+
}
1149+
11591150
// Not really any better place than env.cc at this moment.
11601151
void BaseObject::DeleteMe(void* data) {
11611152
BaseObject* self = static_cast<BaseObject*>(data);

src/env.h

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -557,13 +557,29 @@ struct ContextInfo {
557557

558558
class EnabledDebugList;
559559

560+
// Disables zero-filling for ArrayBuffer allocations in this scope. This is
561+
// similar to how we implement Buffer.allocUnsafe() in JS land.
562+
class NoArrayBufferZeroFillScope{
563+
public:
564+
explicit NoArrayBufferZeroFillScope(IsolateData* isolate_data);
565+
~NoArrayBufferZeroFillScope();
566+
567+
private:
568+
NodeArrayBufferAllocator* node_allocator_;
569+
};
570+
560571
// A unique-pointer-ish object that is compatible with the JS engine's
561572
// ArrayBuffer::Allocator.
562-
struct AllocatedBuffer {
573+
// TODO(addaleax): We may want to start phasing this out as it's only a thin
574+
// wrapper around v8::BackingStore at this point.
575+
class AllocatedBuffer {
563576
public:
564-
explicit inline AllocatedBuffer(Environment* env = nullptr);
565-
inline AllocatedBuffer(Environment* env, uv_buf_t buf);
566-
inline ~AllocatedBuffer();
577+
AllocatedBuffer() = default;
578+
inline AllocatedBuffer(
579+
Environment* env, std::unique_ptr<v8::BackingStore> bs);
580+
// For this constructor variant, `buffer` *must* come from an earlier call
581+
// to .release().
582+
inline AllocatedBuffer(Environment* env, uv_buf_t buffer);
567583
inline void Resize(size_t len);
568584

569585
inline uv_buf_t release();
@@ -575,16 +591,14 @@ struct AllocatedBuffer {
575591
inline v8::MaybeLocal<v8::Object> ToBuffer();
576592
inline v8::Local<v8::ArrayBuffer> ToArrayBuffer();
577593

578-
inline AllocatedBuffer(AllocatedBuffer&& other);
579-
inline AllocatedBuffer& operator=(AllocatedBuffer&& other);
594+
AllocatedBuffer(AllocatedBuffer&& other) = default;
595+
AllocatedBuffer& operator=(AllocatedBuffer&& other) = default;
580596
AllocatedBuffer(const AllocatedBuffer& other) = delete;
581597
AllocatedBuffer& operator=(const AllocatedBuffer& other) = delete;
582598

583599
private:
584-
Environment* env_;
585-
// We do not pass this to libuv directly, but uv_buf_t is a convenient way
586-
// to represent a chunk of memory, and plays nicely with other parts of core.
587-
uv_buf_t buffer_;
600+
Environment* env_ = nullptr;
601+
std::unique_ptr<v8::BackingStore> backing_store_;
588602

589603
friend class Environment;
590604
};
@@ -959,11 +973,7 @@ class Environment : public MemoryRetainer {
959973
// Utilities that allocate memory using the Isolate's ArrayBuffer::Allocator.
960974
// In particular, using AllocateManaged() will provide a RAII-style object
961975
// with easy conversion to `Buffer` and `ArrayBuffer` objects.
962-
inline AllocatedBuffer AllocateManaged(size_t size, bool checked = true);
963-
inline char* Allocate(size_t size);
964-
inline char* AllocateUnchecked(size_t size);
965-
char* Reallocate(char* data, size_t old_size, size_t size);
966-
inline void Free(char* data, size_t size);
976+
inline AllocatedBuffer AllocateManaged(size_t size);
967977

968978
inline bool printed_error() const;
969979
inline void set_printed_error(bool value);
@@ -1251,6 +1261,9 @@ class Environment : public MemoryRetainer {
12511261
void RunAndClearNativeImmediates(bool only_refed = false);
12521262
void RunAndClearInterrupts();
12531263

1264+
inline std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>*
1265+
released_allocated_buffers();
1266+
12541267
private:
12551268
template <typename Fn>
12561269
inline void CreateImmediate(Fn&& cb, bool ref);
@@ -1413,6 +1426,11 @@ class Environment : public MemoryRetainer {
14131426
// We should probably find a way to just use plain `v8::String`s created from
14141427
// the source passed to LoadEnvironment() directly instead.
14151428
std::unique_ptr<v8::String::Value> main_utf16_;
1429+
1430+
// Used by AllocatedBuffer::release() to keep track of the BackingStore for
1431+
// a given pointer.
1432+
std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>
1433+
released_allocated_buffers_;
14161434
};
14171435

14181436
} // namespace node

src/node_buffer.cc

Lines changed: 7 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -327,16 +327,7 @@ MaybeLocal<Object> New(Environment* env, size_t length) {
327327
return Local<Object>();
328328
}
329329

330-
AllocatedBuffer ret(env);
331-
if (length > 0) {
332-
ret = env->AllocateManaged(length, false);
333-
if (ret.data() == nullptr) {
334-
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
335-
return Local<Object>();
336-
}
337-
}
338-
339-
return scope.EscapeMaybe(ret.ToBuffer());
330+
return scope.EscapeMaybe(env->AllocateManaged(length).ToBuffer());
340331
}
341332

342333

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

366-
AllocatedBuffer ret(env);
357+
AllocatedBuffer ret = env->AllocateManaged(length);
367358
if (length > 0) {
368-
CHECK_NOT_NULL(data);
369-
ret = env->AllocateManaged(length, false);
370-
if (ret.data() == nullptr) {
371-
THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
372-
return Local<Object>();
373-
}
374359
memcpy(ret.data(), data, length);
375360
}
376361

@@ -445,53 +430,23 @@ MaybeLocal<Object> New(Isolate* isolate, char* data, size_t length) {
445430
return MaybeLocal<Object>();
446431
}
447432
Local<Object> obj;
448-
if (Buffer::New(env, data, length, true).ToLocal(&obj))
433+
if (Buffer::New(env, data, length).ToLocal(&obj))
449434
return handle_scope.Escape(obj);
450435
return Local<Object>();
451436
}
452437

453-
// Warning: If this call comes through the public node_buffer.h API,
454-
// the contract for this function is that `data` is allocated with malloc()
438+
// The contract for this function is that `data` is allocated with malloc()
455439
// and not necessarily isolate's ArrayBuffer::Allocator.
456440
MaybeLocal<Object> New(Environment* env,
457441
char* data,
458-
size_t length,
459-
bool uses_malloc) {
442+
size_t length) {
460443
if (length > 0) {
461444
CHECK_NOT_NULL(data);
462445
CHECK(length <= kMaxLength);
463446
}
464447

465-
if (uses_malloc) {
466-
if (!env->isolate_data()->uses_node_allocator()) {
467-
// We don't know for sure that the allocator is malloc()-based, so we need
468-
// to fall back to the FreeCallback variant.
469-
auto free_callback = [](char* data, void* hint) { free(data); };
470-
return New(env, data, length, free_callback, nullptr);
471-
} else {
472-
// This is malloc()-based, so we can acquire it into our own
473-
// ArrayBufferAllocator.
474-
CHECK_NOT_NULL(env->isolate_data()->node_allocator());
475-
env->isolate_data()->node_allocator()->RegisterPointer(data, length);
476-
}
477-
}
478-
479-
auto callback = [](void* data, size_t length, void* deleter_data){
480-
CHECK_NOT_NULL(deleter_data);
481-
482-
static_cast<v8::ArrayBuffer::Allocator*>(deleter_data)
483-
->Free(data, length);
484-
};
485-
std::unique_ptr<v8::BackingStore> backing =
486-
v8::ArrayBuffer::NewBackingStore(data,
487-
length,
488-
callback,
489-
env->isolate()
490-
->GetArrayBufferAllocator());
491-
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(),
492-
std::move(backing));
493-
494-
return Buffer::New(env, ab, 0, length).FromMaybe(Local<Object>());
448+
auto free_callback = [](char* data, void* hint) { free(data); };
449+
return New(env, data, length, free_callback, nullptr);
495450
}
496451

497452
namespace {

0 commit comments

Comments
 (0)