diff --git a/benchmark/buffers/buffer-copy.js b/benchmark/buffers/buffer-copy.js index c2dafc8515c4f2..9caa7712036529 100644 --- a/benchmark/buffers/buffer-copy.js +++ b/benchmark/buffers/buffer-copy.js @@ -4,12 +4,13 @@ const common = require('../common.js'); const bench = common.createBenchmark(main, { bytes: [8, 128, 1024], partial: ['true', 'false'], + shared: ['true', 'false'], n: [6e6], }); -function main({ n, bytes, partial }) { - const source = Buffer.allocUnsafe(bytes); - const target = Buffer.allocUnsafe(bytes); +function main({ n, bytes, partial, shared }) { + const source = shared === 'true' ? Buffer.from(new SharedArrayBuffer(bytes)) : Buffer.allocUnsafe(bytes); + const target = shared === 'true' ? Buffer.from(new SharedArrayBuffer(bytes)) : Buffer.allocUnsafe(bytes); const sourceStart = (partial === 'true' ? Math.floor(bytes / 2) : 0); bench.start(); for (let i = 0; i < n; i++) { diff --git a/deps/v8/include/v8-array-buffer.h b/deps/v8/include/v8-array-buffer.h index 3e64ece5debda3..19193d3762fe4c 100644 --- a/deps/v8/include/v8-array-buffer.h +++ b/deps/v8/include/v8-array-buffer.h @@ -456,6 +456,16 @@ class V8_EXPORT ArrayBufferView : public Object { */ bool HasBuffer() const; + /** + * Copies |count| bytes from |source| starting at |source_start| into + * |target| starting at |target_start| using memmove semantics. Unlike + * CopyContents, this method handles both source and destination, supports + * byte offsets, and requires no HandleScope. + */ + static void FastCopy(Local source, size_t source_start, + Local target, size_t target_start, + size_t count); + V8_INLINE static ArrayBufferView* Cast(Value* value) { #ifdef V8_ENABLE_CHECKS CheckCast(value); diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc index 5a879e9ff5d9e8..81a3f91d9656ca 100644 --- a/deps/v8/src/api/api.cc +++ b/deps/v8/src/api/api.cc @@ -9044,6 +9044,39 @@ size_t v8::ArrayBufferView::CopyContents(void* dest, size_t byte_length) { return bytes_to_copy; } +// static +void v8::ArrayBufferView::FastCopy(Local source, + size_t source_start, + Local target, + size_t target_start, size_t count) { + i::DisallowGarbageCollection no_gc; + auto src = Utils::OpenDirectHandle(*source); + auto dst = Utils::OpenDirectHandle(*target); + + if (V8_UNLIKELY(src->IsDetachedOrOutOfBounds() || + dst->IsDetachedOrOutOfBounds())) { + return; + } + + char* src_data; + if (V8_LIKELY(i::IsJSTypedArray(*src))) { + src_data = reinterpret_cast(i::Cast(*src)->DataPtr()); + } else { + src_data = reinterpret_cast( + i::Cast(*src)->data_pointer()); + } + + char* dst_data; + if (V8_LIKELY(i::IsJSTypedArray(*dst))) { + dst_data = reinterpret_cast(i::Cast(*dst)->DataPtr()); + } else { + dst_data = reinterpret_cast( + i::Cast(*dst)->data_pointer()); + } + + memmove(dst_data + target_start, src_data + source_start, count); +} + v8::MemorySpan v8::ArrayBufferView::GetContents( v8::MemorySpan storage) { internal::DisallowGarbageCollection no_gc; diff --git a/doc/api/buffer.md b/doc/api/buffer.md index 329d1f3f9915df..6703154885e185 100644 --- a/doc/api/buffer.md +++ b/doc/api/buffer.md @@ -1767,9 +1767,14 @@ added: v0.1.90 Copies data from a region of `buf` to a region in `target`, even if the `target` memory region overlaps with `buf`. -[`TypedArray.prototype.set()`][] performs the same operation, and is available -for all TypedArrays, including Node.js `Buffer`s, although it takes -different function arguments. +[`TypedArray.prototype.set()`][] performs a similar operation and is available +for all TypedArrays, including Node.js `Buffer`s, although it takes different +function arguments. It differs from `buf.copy()` in two ways: + +1. If either buffer is detached, it throws a `TypeError` instead of performing + a no-op. +2. If either buffer is backed by a [`SharedArrayBuffer`][], it performs a + slower copy based on a stricter memory model. ```mjs import { Buffer } from 'node:buffer'; @@ -5588,6 +5593,7 @@ introducing security vulnerabilities into an application. [`ERR_OUT_OF_RANGE`]: errors.md#err_out_of_range [`JSON.stringify()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify [`Number.MAX_SAFE_INTEGER`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER +[`SharedArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer [`String.prototype.indexOf()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf [`String.prototype.lastIndexOf()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf [`String.prototype.length`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length diff --git a/lib/buffer.js b/lib/buffer.js index 4d96a536a15f8b..e29d8c3e7d8bbd 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -267,7 +267,7 @@ function copyImpl(source, target, targetStart, sourceStart, sourceEnd) { return _copyActual(source, target, targetStart, sourceStart, sourceEnd); } -function _copyActual(source, target, targetStart, sourceStart, sourceEnd, isUint8Copy = false) { +function _copyActual(source, target, targetStart, sourceStart, sourceEnd) { if (sourceEnd - sourceStart > target.byteLength - targetStart) sourceEnd = sourceStart + target.byteLength - targetStart; @@ -279,11 +279,7 @@ function _copyActual(source, target, targetStart, sourceStart, sourceEnd, isUint if (nb <= 0) return 0; - if (sourceStart === 0 && nb === sourceLen && (isUint8Copy || isUint8Array(target))) { - TypedArrayPrototypeSet(target, source, targetStart); - } else { - _copy(source, target, targetStart, sourceStart, nb); - } + _copy(source, target, targetStart, sourceStart, nb); return nb; } diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 362ac268483ea3..7fbfd178b354d8 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -585,26 +585,17 @@ void StringSlice(const FunctionCallbackInfo& args) { } } -void CopyImpl(Local source_obj, - Local target_obj, - const uint32_t target_start, - const uint32_t source_start, - const uint32_t to_copy) { - ArrayBufferViewContents source(source_obj); - SPREAD_BUFFER_ARG(target_obj, target); - - memmove(target_data + target_start, source.data() + source_start, to_copy); -} - // Assume caller has properly validated args. void SlowCopy(const FunctionCallbackInfo& args) { - Local source_obj = args[0]; - Local target_obj = args[1]; const uint32_t target_start = args[2].As()->Value(); const uint32_t source_start = args[3].As()->Value(); const uint32_t to_copy = args[4].As()->Value(); - CopyImpl(source_obj, target_obj, target_start, source_start, to_copy); + ArrayBufferView::FastCopy(args[0].As(), + source_start, + args[1].As(), + target_start, + to_copy); args.GetReturnValue().Set(to_copy); } @@ -618,10 +609,12 @@ uint32_t FastCopy(Local receiver, uint32_t to_copy, // NOLINTNEXTLINE(runtime/references) FastApiCallbackOptions& options) { - HandleScope scope(options.isolate); - - CopyImpl(source_obj, target_obj, target_start, source_start, to_copy); - + TRACK_V8_FAST_API_CALL("buffer.copy"); + ArrayBufferView::FastCopy(source_obj.As(), + source_start, + target_obj.As(), + target_start, + to_copy); return to_copy; }