Skip to content
Open
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: 1 addition & 1 deletion common.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

# Reset this number to 0 on major V8 upgrades.
# Increment by one for each non-official patch applied to deps/v8.
'v8_embedder_string': '-node.21',
'v8_embedder_string': '-node.22',

##### V8 defaults for Node.js #####

Expand Down
20 changes: 20 additions & 0 deletions deps/v8/include/v8-array-buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,26 @@ class V8_EXPORT ArrayBufferView : public Object {
*/
bool HasBuffer() const;

/**
* Copy up to |bytes_to_copy| bytes from |source| (starting |source_start|
* bytes into the view) to |target| (starting |target_start| bytes into the
* view). The byte range is clamped to both views' byte lengths. The views'
* data pointers are resolved directly, without materializing their
* ArrayBuffers, avoiding the overhead of ArrayBufferView::Buffer /
* JSTypedArray::GetBuffer.
*
* Nothing is copied if |source| is detached or out of bounds, or if |target|
* is detached, out of bounds, or backed by an immutable ArrayBuffer. When
* both views are backed by a SharedArrayBuffer the copy uses a relaxed-atomic
* memmove that honors the SharedArrayBuffer memory model. Returns the number
* of bytes actually copied.
*/
static size_t CopyArrayBufferViewBytes(Local<ArrayBufferView> source,
size_t source_start,
Local<ArrayBufferView> target,
size_t target_start,
size_t bytes_to_copy);

V8_INLINE static ArrayBufferView* Cast(Value* value) {
#ifdef V8_ENABLE_CHECKS
CheckCast(value);
Expand Down
71 changes: 71 additions & 0 deletions deps/v8/src/api/api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4246,6 +4246,43 @@ static size_t CopyArrayBufferBytesImpl(const void* source_buffer,
return bytes_to_copy;
}

struct ArrayBufferViewBytes {
char* data;
size_t length;
bool is_shared;
bool is_immutable;
};

// Resolves a view's data pointer, byte length and backing-buffer flags without
// materializing its ArrayBuffer (i.e. without JSTypedArray::GetBuffer, which is
// comparatively expensive). A detached or out-of-bounds view resolves to an
// empty {nullptr, 0} range.
ArrayBufferViewBytes GetArrayBufferViewBytes(
i::Tagged<i::JSArrayBufferView> view) {
if (view->IsDetachedOrOutOfBounds()) return {nullptr, 0, false, false};
if (i::IsJSTypedArray(view)) {
i::Tagged<i::JSTypedArray> array = i::Cast<i::JSTypedArray>(view);
i::Tagged<i::JSArrayBuffer> buffer = array->buffer();
return {reinterpret_cast<char*>(array->DataPtr()), array->GetByteLength(),
buffer->is_shared(), buffer->is_immutable()};
}
if (i::IsJSDataView(view)) {
i::Tagged<i::JSDataView> data_view = i::Cast<i::JSDataView>(view);
i::Tagged<i::JSArrayBuffer> buffer =
i::Cast<i::JSArrayBuffer>(data_view->buffer());
return {reinterpret_cast<char*>(data_view->data_pointer()),
data_view->byte_length(), buffer->is_shared(),
buffer->is_immutable()};
}
i::Tagged<i::JSRabGsabDataView> data_view =
i::Cast<i::JSRabGsabDataView>(view);
i::Tagged<i::JSArrayBuffer> buffer =
i::Cast<i::JSArrayBuffer>(data_view->buffer());
return {reinterpret_cast<char*>(data_view->data_pointer()),
data_view->GetByteLength(), buffer->is_shared(),
buffer->is_immutable()};
}

size_t v8::SharedArrayBuffer::CopyArrayBufferBytes(
size_t source_start, size_t bytes_to_copy, Local<SharedArrayBuffer> target,
size_t target_start) const {
Expand Down Expand Up @@ -8960,6 +8997,40 @@ size_t v8::ArrayBuffer::CopyArrayBufferBytes(size_t source_start,
that->GetByteLength(), bytes_to_copy);
}

size_t v8::ArrayBufferView::CopyArrayBufferViewBytes(
Local<ArrayBufferView> source, size_t source_start,
Local<ArrayBufferView> target, size_t target_start, size_t bytes_to_copy) {
i::DisallowGarbageCollection no_gc;
ArrayBufferViewBytes src =
GetArrayBufferViewBytes(*Utils::OpenDirectHandle(*source));
ArrayBufferViewBytes dst =
GetArrayBufferViewBytes(*Utils::OpenDirectHandle(*target));

// Never write to an immutable target. Detached/out-of-bounds views resolve to
// a zero length, so they fall out through the clamping below.
if (dst.is_immutable) return 0;

source_start = std::min(source_start, src.length);
target_start = std::min(target_start, dst.length);
bytes_to_copy = std::min(
{bytes_to_copy, src.length - source_start, dst.length - target_start});
if (bytes_to_copy == 0) return 0;

char* source_data = src.data + source_start;
char* target_data = dst.data + target_start;
// A relaxed-atomic memmove is only required when both views are backed by a
// SharedArrayBuffer; any other combination performs a plain memmove on the
// backing store, matching v8::ArrayBuffer::CopyArrayBufferBytes.
if (src.is_shared || dst.is_shared) {
base::Relaxed_Memmove(
reinterpret_cast<base::Atomic8*>(target_data),
reinterpret_cast<const base::Atomic8*>(source_data), bytes_to_copy);
} else {
std::memmove(target_data, source_data, bytes_to_copy);
}
return bytes_to_copy;
}

namespace {
std::shared_ptr<i::BackingStore> ToInternal(
std::shared_ptr<i::BackingStoreBase> backing_store) {
Expand Down
15 changes: 8 additions & 7 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,15 +261,16 @@ function copyImpl(source, target, targetStart, sourceStart, sourceEnd) {
throw new ERR_OUT_OF_RANGE('sourceEnd', '>= 0', sourceEnd);
}

if (targetStart >= target.byteLength || sourceStart >= sourceEnd)
const targetLength = target.byteLength;
if (targetStart >= targetLength || sourceStart >= sourceEnd)
return 0;

return _copyActual(source, target, targetStart, sourceStart, sourceEnd);
}

function _copyActual(source, target, targetStart, sourceStart, sourceEnd) {
if (sourceEnd - sourceStart > target.byteLength - targetStart)
sourceEnd = sourceStart + target.byteLength - targetStart;
// Clamp the copy length to what fits in the target and what remains in the
// source. V8 clamps to the underlying ArrayBuffer internally, but that is the
// backing store rather than this view, so the view-relative clamping is done
// here.
if (sourceEnd - sourceStart > targetLength - targetStart)
sourceEnd = sourceStart + targetLength - targetStart;

let nb = sourceEnd - sourceStart;
const sourceLen = source.byteLength - sourceStart;
Expand Down
41 changes: 14 additions & 27 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -605,33 +605,20 @@ size_t CopyImpl(Local<Value> source_obj,
const size_t target_start,
const size_t source_start,
const size_t to_copy) {
Local<ArrayBufferView> source = source_obj.As<ArrayBufferView>();
Local<ArrayBufferView> target = target_obj.As<ArrayBufferView>();

Local<ArrayBuffer> source_ab = source->Buffer();
Local<ArrayBuffer> target_ab = target->Buffer();

const size_t source_offset = source->ByteOffset() + source_start;
const size_t target_offset = target->ByteOffset() + target_start;

// Defer byte-range clamping and detached/immutable handling to V8. When both
// sides are backed by a SharedArrayBuffer the relaxed atomic overload is
// used, which honors the SharedArrayBuffer memory model. Any other
// combination (both regular, or one of each) goes through the ArrayBuffer
// overload: it operates on the underlying backing store regardless of
// shared-ness, so a plain memmove is performed (matching the historical
// behavior for SharedArrayBuffer-backed buffers). The V8 API has no overload
// that mixes ArrayBuffer and SharedArrayBuffer, so the two must never be
// cross-cast.
if (source_ab->IsSharedArrayBuffer() && target_ab->IsSharedArrayBuffer()) {
return source_ab.As<SharedArrayBuffer>()->CopyArrayBufferBytes(
source_offset,
to_copy,
target_ab.As<SharedArrayBuffer>(),
target_offset);
}
return source_ab->CopyArrayBufferBytes(
source_offset, to_copy, target_ab, target_offset);
// Defer byte-range clamping and detached/immutable/shared handling to V8.
// CopyArrayBufferViewBytes resolves the views' data pointers directly,
// without materializing their ArrayBuffers (ArrayBufferView::Buffer /
// JSTypedArray::GetBuffer), which dominates the per-call cost for small
// copies. When both views are backed by a SharedArrayBuffer it performs a
// relaxed-atomic memmove honoring the SharedArrayBuffer memory model; any
// other combination performs a plain memmove on the backing store (matching
// the historical behavior for SharedArrayBuffer-backed buffers).
return ArrayBufferView::CopyArrayBufferViewBytes(
source_obj.As<ArrayBufferView>(),
source_start,
target_obj.As<ArrayBufferView>(),
target_start,
to_copy);
}

// Assume caller has properly validated args.
Expand Down
Loading