-
Notifications
You must be signed in to change notification settings - Fork 858
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
Implement super
#1735
Implement super
#1735
Conversation
b5a3170
to
f0f46f3
Compare
Wow -- this is really big, and I appreciate the attention to detail. I want to understand it better, since you're adding a bunch of new types of operations in the runtime, so give me (and a few others) a week or so to think carefully about it, but this is really cool and I'm looking forward to having it! |
@andreabergia @0xe another great step forward, great work. Will do the usual test with HtmlUnit over the weekend (had to do a release of HtmlUnit before). Many, many thanks for all the hard work... |
Just so you know where I am on this:
Thanks and we'll get this in to the product soon! |
I had a chance to look this over more and read the description -- I think that this is a good approach for implementing this. Can you please rebase? There were one or two places where I could probably get it right but I'd rather that you all make doubly sure. Thanks! |
f0f46f3
to
f82d9aa
Compare
Rebased. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a good approach and I'm happy to merge it.
It sounds like you'd prefer to merge the five or six other PRs either into this one or into a new PR -- I'm happy either way, just let me know when that is ready. I had a few comments on the other ones but only one was non-optional.
It'd probably help to have marked some of those PRs as "draft" while we go through this but FWIW I don't always look at drafts!
@andreabergia, @0xe sorry for not doing the smoke test so far, but there was not really enough time and because of this stacked commits it was complicated to merge into something testable. |
4244ad6
to
6bd0cf1
Compare
Merged also #1741 onto this |
Looks like progress -- how many other PRs do you want to include in this one before we look at it one more time and merge it? It's looking really close. |
Thanks -- let me know when you've merged the last one, or if you want me to try. It looks like this one also might conflict with a few other PRs you wrote, so I'm going to hold off on those until we finish this. |
@gbrail I haven't merged it just because it was not approved by you yet 😊 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for all the work on this! Since I was looking at code coverage for something else, I looked here too, and I saw a few things -- mostly minor, but definitely not new behavior -- that aren't covered by the existing tests.
Can you please take a look at these and see if there are ways that you could hit these few edge cases so that we can have excellent code coverage for all this new code? With those small exceptions, this stuff is very well covered by both new and existing tests.
} else if (op.isOperation(RhinoOperation.GETINDEX)) { | ||
mh = lookup.findStatic(ScriptRuntime.class, "getObjectIndex", mType); | ||
} else if (op.isOperation(RhinoOperation.GETINDEXSUPER)) { | ||
mh = lookup.findStatic(ScriptRuntime.class, "getSuperIndex", mType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm looking at code coverage in a few places and I sese that this operation isn't covered by the existing test cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed completely that case because it can never happen!
The reason is that functions with super
will always use activation, and Optimizer
will not optimize functions that do. Therefore, we will never hit a case of having both properties ISNUMBER_PROP
and SUPER_PROPERTY_ACCESS
at the same time.
This means that, if we have a super
access, we will not have an extra-optimized path for integer properties and we will go to the generic "element" case, but we think it's fine. We're talking about accessing things like super[0]
cases here, not something that should be very common.
We haven't added any assertion or check that both properties are not true at the same time because, in any case, the generic "getelem" case can handle integer/double, so even if the optimizer changes, this won't break - it might be slower than for non-super access, but it is correct.
} else if (op.isOperation(RhinoOperation.SETINDEX)) { | ||
mh = lookup.findStatic(ScriptRuntime.class, "setObjectIndex", mType); | ||
} else if (op.isOperation(RhinoOperation.SETINDEXSUPER)) { | ||
mh = lookup.findStatic(ScriptRuntime.class, "setSuperIndex", mType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This operation is not covered by code coverage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed (see above)
addDynamicInvoke("PROP:GETINDEX", Signatures.PROP_GET_INDEX); | ||
if (isSuper) { | ||
cfw.addALoad(thisObjLocal); | ||
addDynamicInvoke("PROP:GETINDEXSUPER", Signatures.PROP_GET_INDEX_SUPER); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code coverage is not reaching this new code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed (see above)
addDynamicInvoke("PROP:GETELEMENT", Signatures.PROP_GET_ELEMENT); | ||
if (isSuper) { | ||
cfw.addALoad(thisObjLocal); | ||
addDynamicInvoke("PROP:GETELEMENTSUPER", Signatures.PROP_GET_ELEMENT_SUPER); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code coverage is not reaching this new code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed (see above)
if (isSuper) { | ||
cfw.addALoad(thisObjLocal); | ||
if (indexIsNumber) { | ||
addDynamicInvoke("PROP:SETINDEXSUPER", Signatures.PROP_SET_INDEX_SUPER); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code coverage is not reaching this new code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed (see above)
|
||
if (result == Scriptable.NOT_FOUND) { | ||
result = Undefined.instance; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We never get here in code coverage either, which makes me wonder whether this is really needed, since I've seen bugs before where the confusion between NOT_FOUND and Undefined creeps in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added coverage for this
|
||
String s = toString(dblIndex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't get here in code coverage either, which is a pretty minor use case, but still...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added coverage for this (and similar for setSuperIndex
which had the same issue)
// how the home object will ever be null since `super` is | ||
// legal _only_ in method definitions, where we do have a | ||
// home object! | ||
stack[++stackTop] = Undefined.instance; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems important but isn't covered by the existing tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We agree. But, as discussed by comment, I cannot imagine how that line would ever be hit - the super
token should only be allowed inside methods, which will always have a home object set, and all the activations stuff
should work correctly and make the home object accessible. Otherwise, we get syntax errors before we reach the interpreter.
So, this is a case of where we implemented the spec word by word, but we can't imagine that this will ever happen given the actual implementation of the interpreter.
Merged also the last PR, #1738 onto this one |
There is a code path, in compiled mode only (not in interpreted!), where something like `o = { [-1]: xxx }` would not work correctly. The problem is a combination of factors: - in the interpreter, we would treat `-1` as a `double`, not an `int`; - but there are various places in `ScriptRuntime` where integer keys are allowed _only_ for positive or zero numbers, per the spec - otherwise they need to be treated as strings. We have modified the existing test for computed properties and fixed the code. Co-authored-by: Satish Srinivasan <satish.srinivasan@servicenow.com>
Co-authored-by: Satish Srinivasan <satish.srinivasan@servicenow.com>
Co-authored-by: Satish Srinivasan <satish.srinivasan@servicenow.com>
See spec 13.5.1.2 at https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation Co-authored-by: Satish Srinivasan <satish.srinivasan@servicenow.com>
Co-authored-by: Andrea Bergia <andrea.bergia@servicenow.com>
Co-authored-by: Satish Srinivasan <satish.srinivasan@servicenow.com>
This reverts commit b845d14. Co-authored-by: Satish Srinivasan <satish.srinivasan@servicenow.com>
Co-authored-by: Satish Srinivasan <satish.srinivasan@servicenow.com>
Those invokedynamic bridges were never being executed because they where generated only in the combination of `super` with an integer index in optimized functions. However, to make `super` work, the function is marked with "requires activation", which disables the optimizer, which means that the combination `SUPER_PROPERTY_ACCESS` and `ISNUMBER_PROP` actually never occurs. We removed all the dead code to improve coverage. Co-authored-by: Satish Srinivasan <satish.srinivasan@servicenow.com>
Co-authored-by: Satish Srinivasan <satish.srinivasan@servicenow.com>
9011ce0
to
2d7d455
Compare
OK -- this looks good. Thanks for all the hard work! |
This is a huge PR that me and @0xe worked on for a while. We couldn't really figure out a way to make this a nice set of commits for easy review, so we ended up squashing everything because keeping the history wasn't really helpful. Some additional details are implemented as separated PRs stacked on top of this, but this is the smallest PR we could come up with that was coherent.
It is a bit complicated to review I think, sorry about that.
This implements the
super
operator - which can actually be used outside of classes and will do something that intuitively is "refer to the prototype". The actual details are rather complicated; the MDN page is very clear and detailed. Be sure to read that page carefully before reviewing this.This PR is stacked on top of #1734 and also includes changes from #1740. It will need to be rebased once #1740 gets merged in. The following PRs are stacked on top of this one:
super
insideeval
#1736super.__proto__
, which has a special implementation #1737super.x++
and similar #1738delete super.x
by throwing an error #1739We propose to merge these stacked PR into this one and merge this one onto master, if accepted.
Parser changes
super
, per the spec, should be allowed only inside methods (and class constructors, which are not part of this PR). A method is something that is written with the shorthand syntax:Note that the following is not a method:
The changes in the parser mostly relate to keeping track of a flag
insideMethod
and handling thesuper
token properly, allowing it only in methods. Usingsuper()
as a method call is forbidden at the moment - this will need to be changed when classes are implemented, because it needs to be allowed in constructors.IR Factory
Most of the changes are done to set a property
SUPER_PROPERTY_ACCESS
on the IR nodes whenever we encounter asuper
reference; i.e. if we have a reference tosuper.x
, a nodeGETPROP
is created (as for any other property access), but we store theSUPER_PROPERTY_ACCESS
flag on it. There's a bit of complication for handling stuff likesuper.x = expr
but the logic is the same - we record that theSETPROP
on the left is onsuper
.Finally, we mark every function that uses
super
as "requires activation".Function objects
Every function now stores a property called "home object", following the ECMAScript spec. This is a hidden property of functions that, for methods, is is recorded as the object where the method is declared. For non methods, it is set to null. Thus, for the following JS:
we record that
method
has a[[HomeObject]]
set too
, whereas the other functions have it empty (null
).This is done by emitting a new bytecode/new instructions in the JVM bytecode whenever we are initializing a method. To do this, the changes done in #1734 are necessary, because we need to create the object
o
before we create the closure that represents the method.Interpreter and java bytecode
The actual logic is implemented in the same way in the interpreter and the compiled classes. The token
SUPER
will push the "super" object (i.e. the home object's prototype) on the stack, similar to whatTHIS
would do. Then, unfortunately, every kind of property access or method call needs to have special handling - we cannot reuse the logic for (say)GETPROP
but we need to have a new tokenGETPROP_SUPER
. There is quite a bit of duplication with the existing tokens, but we figured that this is worthwhile because it doesn't modify the (vastly more common) case of normal property access, and therefore it doesn't make it slower.We have created various new tokens/bytecodes for the interpreter, and
invokedynamic
cases for the compiled classes.The semantics of
super
access are basically "resolve property on the super object, then access it using the this object", which intuitively means that doing something likesuper.f()
would resolve thef
function in the prototype, but then call it with the currentthis
object. An useful analogy is to think about normal virtual calls in other languages - although the semantics are not really the same, because JS is complicated. 🙂 The same idea also applies to getter or setters - they are resolved on thesuper
object, but called with the currentthis
.Function activation
We mentioned earlier that we mark every method that uses
super
as "requires activation". The approach we ended up with is to store the current function's home object in the activation record (theNativeCall
). The reason for this, rather than just looking it up in the current function, has to do with lambda functions defined inside a method: they need to capture thesuper
, per the ECMAScript spec.We have done this by storing the captured home object in the activation. We have looked at what other engines do (v8, jsc, quickjs) and they all seem to be doing something similar, even if the implementation greatly differs between all of them.
Tests
We have implemented a lot of tests in a new class
SuperTest
, using junit5's nested classes to organize them. We pass most test262 cases except for those related toeval
(fixed in #1736). All other test262 cases that do not pass use eitherclass
or spread syntax, which are not supported.Not implemented
We have not added support for all XML stuff.
Also, if
super.x
is a property stored in theExternalArrayData
, it might not work correctly - we have not tested it.