-
Notifications
You must be signed in to change notification settings - Fork 29.9k
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
ES2016 compat issue with Buffer subclassing Uint8Array #4701
Comments
The associated V8 bug is https://bugs.chromium.org/p/v8/issues/detail?id=4665 |
Another option is to call the three-argument constructor for TypedArray from Buffer.prototype.slice, rather than calling subarray. This would side-step the need to override |
I wouldn't recommend using |
Could you please put |
@jeisinger To clarify, there's no need to merge this patch until |
FWIW, a third possible fix is here: feross/buffer#95 . I think this would be a bit slower than basing it on |
Wouldn't a solution be to let the buffer constructor accept |
@LinusU Belated, but a PR is on it's way to allow Buffers to accept an ArrayBuffer with byteoffset and length. |
Can you expand on this? |
Once you upgrade to V8 5.0, you can take advantage of ES2015's built-in support for TypedArray subclassing. We have a web compat workaround which means that subarray will return instances of a built-in TypedArray type, but generally you can just make an ES2015-style class declaration which extends |
@trevnorris If buffer is declared as Currently, it returns a Does it clear things up? I can try and make some better examples otherwise :) |
@LinusU Yes, but... I had to revert the behavior of |
Yes, hopefully we'll be able to ship this sooner rather than later :) |
@LinusU We're changing the Buffer constructor so it'll accept all the same parameters as |
Sounds good 👌 |
@trevnorris +1 |
What's the status on this one @trevnorris ? |
@jasnell This should be testable now that you've landed the new |
@jasnell My last comment was poorly worded. What I mean is that a blocker was the new signature that has now landed. As for subclassing, that simply isn't an option. It prevents being able to allocate memory ourselves that the |
@trevnorris @jasnell OK, I'd expect that with this change, my @@species-based patch is not needed. Re subclassing: I don't see why subclassing a real TypedArray wouldn't allow you to allocate memory however you want. If you have an underlying ArrayBuffer, then you can make your constructor call the super constructor wit this |
@littledan Don't know why that usage hadn't crossed my mind... I would like some analysis done to see how much of the ecosystem we'd break by requiring |
Thought of it again, unfortunately that won't work exactly because we inherit from Uint8Array :/ So yeah, either disallowing regular calls without |
@RReverser V8 is just trying to follow the spec here; if Node.js diverges from the ES spec, that's a pretty significant decision that has implications for the evolution of JavaScript. I think we all benefit from the web and Node using the same language. There was a "call constructor" proposal before TC39 at some point, to allow classes to implement being called. However, it was dropped, I believe for technical reasons that I don't quite understand. It was championed by @wycats and @allenwb, who might be able to provide more information. |
Ah, that's what happened. Just tried to find it among the proposals and couldn't, even though strongly remember that it existed. |
I'm not arguing with this in any way. Personally, I like the idea of enforcing |
see latest status from Jan 2016 meeting presentation deck |
Note that even without explicit callable constructor support it is still possible to use function declarations to define a subclassable "class". |
@allenwb The difficult part here is not having function that other classes can extend from, but the part where such function instantiates |
@RReverser function Buffer(...args) {
let buf = Reflect.construt(Uint8Array, whatever(args), new.target);
...
return buf
} |
@allenwb That's not very different from factory approach we currently have in the sense that subclassing class B extends Buffer {
constructor() {
super(); // still won't work
}
} |
I might be confusing something here, but isn't the issue basically that the Buffer constructor currently overrides |
@seishun Partially.
The problem is that you can't have all at once - you either lose factory style without |
function Buffer(...args) {
if (new.target===undefined) return new Buffer(...args); //called as factory function
//called as constructor, either via direct new, [[Constructor]], or subclass constructor super
return Reflect.construt(Uint8Array, whatever(args), new.target);
}
class SubBuffer extends Buffer {
constructor(...args) {
super(...args);
}
}
Sure it does, see step 8 of SuperCall : super Arguments at https://tc39.github.io/ecma262/#sec-super-keyword-runtime-semantics-evaluation and step 13 of https://tc39.github.io/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget |
@allenwb I stand corrected! Didn't know that it will automagically become Your example seems to work, so I'll try to prepare PR that would make advantage of it. |
@RReverser can you check benchmarks? The Buffer constructor is one place we're already hurting. @allenwb Just want to double sanity check this. We have to turn |
Sure, that's exactly what I was fixing in previous PR and want to be sure it won't regress again. |
almost, you would want to SetPrototypeOf to But, does using SetPrototypeOf have any deoptimizing impact in V8 or other engines? If it does, I would hope that Reflect.construct did not. (It's the difference between supply the prototype before allocation or changing it after allocation) Regard, when you think you have a complete subclassable JS implementation for Buffer point me at it and I'll review it for you. |
So you're saying we'd also want to do both?: buf->SetPrototypeOf(context, buf_prototype);
buf->GetPrototype()->SetPrototypeOf(context, buf_prototype);
Whether it does or not I'm not sure there's a way around it (that doesn't require hacking the ArrayBuffer allocator) since the data is coming in through the I/O event as a |
OK, I'm loosing track of what exactly you are trying to accomplish. I thought you were trying to define a version of Buffer that could be subclassed, similar to what I showed in #4701 (comment) . Whether Buffer is actually implemented in C++ rather than JS doesn't change the requirements. Assuming that This must be the case regardless of whether Buffer is implemented in JS code or in C++. However, if it is implemented in C++ then the V8 C++ API must provide something that is equivalent to
No, if we are talking about
That is essentially the job of Reflect.construct or an equivalent C++ level API. V8 should be providing this. I probably need more details of the usage, but I'm not sure why ArrayBuffer would be an issue? Aren't we're talking about SubBuffer being a subclass of Buffer which is a subclass of Uint8Array? They would all default to using ArrayBuffer as their backing store. (Of course, ArrayBuffer is (according to ES6) also supposed to be subclassable if that should prove useful). I'm probably missing something about what you need to accomplish so feel free to clarify. |
@allenwb Basically the native code looks like so: Local<ArrayBuffer> ab = ArrayBuffer::New(
isolate, data, length, ArrayBufferCreationMode::kInternalized);
Local<Uint8Array> ui = Uint8Array::New(ab, 0, length);
Maybe<bool> mb = ui->SetPrototype(context, buffer_prototype_object); Where Nevermind my comment about leveraging the |
@trevnorris the last line needs to pass the prototype of the Buffer subclass as the second argument to SetPrototype. But I don't know whether or how V8 makes that value available to you via its C++ API. It should be available because it is required to support the Reflect.construct function (and that is really what is going on here). So, this sounds like a question for @littledan : Has V8 make Uint8Array subclassable yet, as required by ES6? How is the newTarget parameter of [[Construct]] exposed in the V8 C++ API such that |
@trevnorris I guess we don't really need that even now; instead, we can construct |
@RReverser I would be interested in seeing benchmark results for that. |
@trevnorris Well I've made changes for that one locally, but is there any specific benchmark code for those C++ |
@RReverser I was thinking of yanking the specific operations happening in |
If you're a constructor implemented in C++ (as a As far as I can tell, the V8 API currently only exposes Calling out to the actual TypedArray constructor this way will be correct, but might be slower in practice for now in some cases, as it is implemented in JavaScript whereas the constructor path exposed by the API is entirely C++; the transition may have some performance cost. I have not benchmarked it yet. I am looking into rewriting this path in C++ for code health reasons, and that change may have knock-on benefits for the speed of this sort of use case. cc @jochen |
Does this need to remain open? |
I believe this can be closed since the original issue about subclassing was fixed, and Buffer subclassability was further discussed in #9531. |
In the ES2016 draft specification, TypedArray methods like
%TypedArray%.prototype.subarray()
call out to a constructor for the resultbased on the receiver. Ordinarily, the constructor is
instance.constructor
,but subclasses can override this using the
Symbol.species
property on theconstructor.
Buffer.prototype.slice
calls out to%TypedArray%.prototype.subarray
, whichcalls this calculated constructor with three arguments. The argument pattern
doesn't correspond to a constructor for Buffer, so without setting
Symbol.species appropriately, the wrong kind of result is created.
This issue came up because I'm working on implementing spec-compliant
subclassable TypedArrays in V8. feross helpfully reported that I broke his
buffer library for the browser. I wrote a couple patches to update both Node
and the browser buffer library for ES2016 TypedArray subclassing:
littledan@834338e
littledan/buffer@2e60129
For now (Chrome 49/V8 4.9), I'm keeping the legacy behavior that
%TypedArray%.prototype.subarray
calls out to a base class constructorlike Uint8Array. However, in the future, I'd like to ship proper TypedArray subclassing,
which would benefit from a patch like this. What do you think?
The text was updated successfully, but these errors were encountered: