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: implement v8 array iteration using the new callback-based API #51758

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
42 changes: 28 additions & 14 deletions src/node_blob.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ namespace {
// Concatenate multiple ArrayBufferView/ArrayBuffers into a single ArrayBuffer.
// This method treats all ArrayBufferView types the same.
void Concat(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Environment* env = Environment::GetCurrent(context);

CHECK(args[0]->IsArray());
Local<Array> array = args[0].As<Array>();

Expand All @@ -54,9 +57,14 @@ void Concat(const FunctionCallbackInfo<Value>& args) {
std::vector<View> views;
size_t total = 0;

for (uint32_t n = 0; n < array->Length(); n++) {
Local<Value> val;
if (!array->Get(env->context(), n).ToLocal(&val)) return;
std::vector<v8::Global<Value>> buffers;
if (FromV8Array(context, array, &buffers).IsNothing()) {
return;
}

size_t count = buffers.size();
for (uint32_t i = 0; i < count; i++) {
Local<Value> val = buffers[i].Get(isolate);
if (val->IsArrayBuffer()) {
auto ab = val.As<ArrayBuffer>();
views.push_back(View{ab->GetBackingStore(), ab->ByteLength(), 0});
Expand Down Expand Up @@ -169,21 +177,27 @@ BaseObjectPtr<Blob> Blob::Create(Environment* env,
}

void Blob::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Environment* env = Environment::GetCurrent(context);

CHECK(args[0]->IsArray()); // sources

Local<Array> array = args[0].As<Array>();
std::vector<std::unique_ptr<DataQueue::Entry>> entries(array->Length());

for (size_t i = 0; i < array->Length(); i++) {
Local<Value> entry;
if (!array->Get(env->context(), i).ToLocal(&entry)) {
return;
}
std::vector<v8::Global<Value>> sources;
if (FromV8Array(context, array, &sources).IsNothing()) {
return;
}

size_t count = sources.size();
for (size_t i = 0; i < count; i++) {
Local<Value> entry = sources[i].Get(isolate);

const auto entryFromArrayBuffer = [env](v8::Local<v8::ArrayBuffer> buf,
size_t byte_length,
size_t byte_offset = 0) {
const auto entryFromArrayBuffer = [isolate](v8::Local<v8::ArrayBuffer> buf,
size_t byte_length,
size_t byte_offset = 0) {
if (buf->IsDetachable()) {
std::shared_ptr<BackingStore> store = buf->GetBackingStore();
USE(buf->Detach(Local<Value>()));
Expand All @@ -193,7 +207,7 @@ void Blob::New(const FunctionCallbackInfo<Value>& args) {

// If the ArrayBuffer is not detachable, we will copy from it instead.
std::shared_ptr<BackingStore> store =
ArrayBuffer::NewBackingStore(env->isolate(), byte_length);
ArrayBuffer::NewBackingStore(isolate, byte_length);
uint8_t* ptr = static_cast<uint8_t*>(buf->Data()) + byte_offset;
std::copy(ptr, ptr + byte_length, static_cast<uint8_t*>(store->Data()));
return DataQueue::CreateInMemoryEntryFromBackingStore(
Expand Down
23 changes: 23 additions & 0 deletions src/util-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,29 @@ inline char* UncheckedCalloc(size_t n) { return UncheckedCalloc<char>(n); }
// headers than we really need to.
void ThrowErrStringTooLong(v8::Isolate* isolate);

struct ArrayIterationData {
std::vector<v8::Global<v8::Value>>* out;
joyeecheung marked this conversation as resolved.
Show resolved Hide resolved
v8::Isolate* isolate = nullptr;
};

inline v8::Array::CallbackResult PushItemToVector(uint32_t index,
v8::Local<v8::Value> element,
void* data) {
auto vec = static_cast<ArrayIterationData*>(data)->out;
auto isolate = static_cast<ArrayIterationData*>(data)->isolate;
vec->push_back(v8::Global<v8::Value>(isolate, element));
return v8::Array::CallbackResult::kContinue;
}

v8::Maybe<void> FromV8Array(v8::Local<v8::Context> context,
v8::Local<v8::Array> js_array,
std::vector<v8::Global<v8::Value>>* out) {
uint32_t count = js_array->Length();
out->reserve(count);
ArrayIterationData data{out, context->GetIsolate()};
return js_array->Iterate(context, PushItemToVector, &data);
}

v8::MaybeLocal<v8::Value> ToV8Value(v8::Local<v8::Context> context,
std::string_view str,
v8::Isolate* isolate) {
Expand Down
10 changes: 10 additions & 0 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,16 @@ struct FunctionDeleter {
template <typename T, void (*function)(T*)>
using DeleteFnPtr = typename FunctionDeleter<T, function>::Pointer;

// Convert a v8::Array into an std::vector using the callback-based API.
// This can be faster than calling Array::Get() repeatedly when the array
// has more than 2 entries.
// Note that iterating over an array in C++ and performing operations on each
// element in a C++ loop is still slower than iterating over the array in JS
// and calling into native in the JS loop repeatedly on each element,
// as of V8 11.9.
inline v8::Maybe<void> FromV8Array(v8::Local<v8::Context> context,
v8::Local<v8::Array> js_array,
std::vector<v8::Global<v8::Value>>* out);
std::vector<std::string_view> SplitString(const std::string_view in,
const std::string_view delim);

Expand Down