stream: move WHATWG byte-stream helpers to C++#63570
Conversation
|
Review requested:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #63570 +/- ##
==========================================
+ Coverage 90.32% 90.34% +0.01%
==========================================
Files 730 733 +3
Lines 234209 236495 +2286
Branches 43934 44542 +608
==========================================
+ Hits 211558 213652 +2094
- Misses 14372 14549 +177
- Partials 8279 8294 +15
🚀 New features to boost your workflow:
|
| size_t from_byte_length = BufferByteLength(args[2]); | ||
|
|
||
| bool ok = static_cast<uint64_t>(to_index) + count <= to_byte_length && | ||
| static_cast<uint64_t>(from_index) + count <= from_byte_length; |
There was a problem hiding this comment.
Should this guard against overflows?
There was a problem hiding this comment.
defensively, probably. In practice overflow is extremely unlikely so I'd say it's likely optional.
Implement three defensive helpers from lib/internal/webstreams/util.js
in a new internal binding (src/node_webstreams.cc):
* getArrayBufferView
* canCopyArrayBuffer
* cloneAsUint8Array
The previous JavaScript versions used Reflect.get on
view.constructor.prototype and called ArrayBuffer.prototype methods
through primordials so they would survive prototype tampering. The C++
versions use the V8 ArrayBufferView and ArrayBuffer APIs directly,
preserving the same robustness without the JS-side overhead.
getArrayBufferView returns { buffer, byteOffset, byteLength } in a
single binding crossing, replacing three separate accessors.
cloneAsUint8Array uses ArrayBuffer::MaybeNew so the process is not
killed on allocation failure.
These functions sit on the byte-stream hot paths
(ReadableByteStreamController enqueue/read, pull-into descriptor copy,
tee clones). ReadableStream type='bytes' throughput on
benchmark/webstreams/readable-read.js improves by ~15% on the BYOB
read path on my workstation.
WPT streams parity is preserved (1403 subtests passing, 0 unexpected
failures, identical to baseline).
Signed-off-by: Matteo Collina <hello@matteocollina.com>
a71a3f7 to
e2c7b65
Compare
| CHECK(args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer()); | ||
| CHECK(args[1]->IsUint32()); | ||
| CHECK(args[2]->IsArrayBuffer() || args[2]->IsSharedArrayBuffer()); | ||
| CHECK(args[3]->IsUint32()); | ||
| CHECK(args[4]->IsUint32()); |
There was a problem hiding this comment.
Note that this will crash the process with large buffers, eg.
new ReadableStream({
type: 'bytes',
pull(controller) { controller.enqueue(new Uint8Array(2 ** 32)) },
}).getReader({ mode: 'byob' }).read(new Uint8Array(2 ** 32))The byte lengths should probably be validated as Numbers and read as int64_t with IntegerValue().
| Number::New(isolate, static_cast<double>(view->ByteLength())), | ||
| }; | ||
| args.GetReturnValue().Set( | ||
| Object::New(isolate, Null(isolate), names, values, arraysize(names))); |
There was a problem hiding this comment.
Using a cached DictionaryTemplate is going to be faster.
There was a problem hiding this comment.
Alternatively, returning these as an Array might also be faster.
| // Reflect.get(view.constructor.prototype, ..., view). Uses the V8 API | ||
| // directly so it is immune to prototype tampering and avoids the JS-side | ||
| // overhead of the defensive accessors in lib/internal/. | ||
| void GetArrayBufferView(const FunctionCallbackInfo<Value>& args) { |
There was a problem hiding this comment.
Given that these aren't specific to web streams, it might make sense to just include in them in the existing utils internal binding rathe than creating Yet Another Internal Binding.
| Local<Name> names[] = { | ||
| FIXED_ONE_BYTE_STRING(isolate, "buffer"), | ||
| FIXED_ONE_BYTE_STRING(isolate, "byteOffset"), | ||
| FIXED_ONE_BYTE_STRING(isolate, "byteLength"), |
There was a problem hiding this comment.
If you're not going to use a cached DictionaryTemplate or Array, then these should use the existing env->buffer_string() and add env->byte_offset_string() and env->byte_length_string() to avoid the additional allocations on every call.
| namespace node { | ||
| namespace webstreams { | ||
|
|
||
| using v8::ArrayBuffer; | ||
| using v8::ArrayBufferView; | ||
| using v8::Context; | ||
| using v8::FunctionCallbackInfo; | ||
| using v8::Isolate; | ||
| using v8::Local; | ||
| using v8::Name; | ||
| using v8::Null; | ||
| using v8::Number; | ||
| using v8::Object; | ||
| using v8::Uint32; | ||
| using v8::Uint8Array; | ||
| using v8::Value; |
There was a problem hiding this comment.
Nit: matching convention....
| namespace node { | |
| namespace webstreams { | |
| using v8::ArrayBuffer; | |
| using v8::ArrayBufferView; | |
| using v8::Context; | |
| using v8::FunctionCallbackInfo; | |
| using v8::Isolate; | |
| using v8::Local; | |
| using v8::Name; | |
| using v8::Null; | |
| using v8::Number; | |
| using v8::Object; | |
| using v8::Uint32; | |
| using v8::Uint8Array; | |
| using v8::Value; | |
| namespace node { | |
| using v8::ArrayBuffer; | |
| using v8::ArrayBufferView; | |
| using v8::Context; | |
| using v8::FunctionCallbackInfo; | |
| using v8::Isolate; | |
| using v8::Local; | |
| using v8::Name; | |
| using v8::Null; | |
| using v8::Number; | |
| using v8::Object; | |
| using v8::Uint32; | |
| using v8::Uint8Array; | |
| using v8::Value; | |
| namespace webstreams { |
Implement five defensive helpers from
lib/internal/webstreams/util.jsin a new internal binding (src/node_webstreams.cc) usingv8::ArrayBufferView/v8::ArrayBufferAPIs directly:arrayBufferViewGet{Buffer,ByteLength,ByteOffset}canCopyArrayBuffercloneAsUint8ArrayThe previous JS versions used
Reflect.getagainstview.constructor.prototypeandArrayBuffer.prototype.{slice,getDetached,getByteLength}via primordials to survive prototype tampering. The C++ versions preserve the same defensive semantics without the JS-side overhead.benchmark/webstreams/readable-read.js type=bytes(BYOB read path, which exercises these helpers on every chunk) improves by ~15% on my workstation. WPT streams parity preserved: 1403 subtests passing, 0 unexpected failures.