10000 Optimise common operations on Array and List by adriaanm · Pull Request #5664 · scala/scala · GitHub
[go: up one dir, main page]

Skip to content

Optimise common operations on Array and List #5664

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

Merged
merged 6 commits into from
Feb 8, 2017

Conversation

adriaanm
Copy link
Contributor
@adriaanm adriaanm commented Jan 28, 2017

Consolidates the following PRs:

rorygraves and others added 2 commits January 28, 2017 11:07
Co-Authored-By: Jason Zaugg <jzaugg@gmail.com>
Specifically, the `slice` and `take` methods.
@adriaanm
Copy link
Contributor Author

I suggest we either close the other PRs, merge this one, and add the benchmarks, or incorporate these commits into one of the other PRs and close this one.

@rorygraves
Copy link
Contributor

@adriaanm The List.filter/filterNot benchmark already exists on the #5653 PR ListBenchmark.scala

@adriaanm
Copy link
Contributor Author

Ok, I wasn't clear whether that was the last word on that. I cherry-picked the commits, so then the benchmark is in here too.

@adriaanm
Copy link
Contributor Author

Mima should be happy with these new commits. I haven't looked into whether each whitelist item is good to go. Leaving that up to @szeiger

@adriaanm adriaanm requested a review from szeiger January 28, 2017 22:04
@rorygraves
Copy link
Contributor

Looks good.

@adriaanm
Copy link
Contributor Author
adriaanm commented Feb 2, 2017

Thanks, sounds like I can safely close the other PRs, since all their commits have been included here? Will get this one merged asap.

@rorygraves
Copy link
Contributor
rorygraves commented Feb 2, 2017 via email

@adriaanm adriaanm changed the title Consolidate collections performance PRs Optimise common operations on Array and List Feb 2, 2017
@adriaanm
Copy link
Contributor Author
adriaanm commented Feb 2, 2017

@szeiger, are you ready to sign off on this? /cc @retronym

},
{
matchName="scala.collection.mutable.WrappedArray.sliceImpl"
problemName=DirectMissingMethodProblem
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change. WrappedArrray is public and the new method is protected. As discussed before, all these changes need to be moved down into a private implementation class.

},
{
matchName="scala.collection.mutable.WrappedArray.emptyImpl"
problemName=DirectMissingMethodProblem
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

},
{
matchName="scala.collection.mutable.WrappedArray.slice"
problemName=IncompatibleResultTypeProblem
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also a problem.

@szeiger
Copy link
Contributor
szeiger commented Feb 2, 2017

I'll try to fix these remaining issues after lunch. Should be ready in a few hours.

@szeiger
Copy link
Contributor
szeiger commented Feb 3, 2017

adriaanm#26

We introduce a package-private superclass with the overridden
implementations. This is public at the bytecode level, so it needs
to be whitelisted.
@adriaanm
Copy link
Contributor Author
adriaanm commented Feb 4, 2017

@szeiger I merged your fixes for your own review -- thanks!

@adriaanm adriaanm merged commit 214a158 into scala:2.11.x Feb 8, 2017
SethTisue added a commit to SethTisue/scala that referenced this pull request Feb 13, 2017
@adriaanm
Copy link
Contributor Author
adriaanm commented Apr 3, 2017

Looks like this is causing LinkageErrors in user code. Should we revert, declare 2.11.9 a dud and cut 2.11.10?

Detailed diagnosis by @xuwei-k:

https://github.com/xuwei-k/Scala-2.11.9-FilteredTraversableInternal

Explanation by @lrytz (hinted at by @sjrd)

The reason why a reference to FilteredTraversableInternal appears in @xuwei-k's example:

List(1).map(identity).filter(_ => true)

In erasure type checking, List(1).map(identity) has type Object (the result type of map is That which is a type parameter, therefore erases to Object). Selecting the filter method requires a cast (List(1).map(identity).asInstanceOf[FilteredTraversableInternal].filter). This is done by erasure's adaptMember, which casts to the filter method symbol's owner, which is now FilteredTraversableInternal.

@szeiger
Copy link
Contributor
szeiger commented Apr 3, 2017

@lrytz So we now have a situation that looks binary compatible in bytecode but leads to incompatibilities due to additional information in ScalaSig?

@lrytz
Copy link
Member
lrytz commented Apr 3, 2017 via email

@lrytz
Copy link
Member
lrytz commented Apr 3, 2017 via email

@szeiger
Copy link
Contributor
szeiger commented Apr 3, 2017

"in the bytecode instructions" is the effect, not the cause. Binary compatibility only matters when you have separate compilation, in which case the compiler must be getting the idea of casting to FilteredTraversableInternal from somewhere in the class file (ScalaSig?). If I understand you correctly the erased return type of filter is Object but somehow the compiler knows that the return type is really a type parameter that was instantiated as FilteredTraversableInternal and inserts a cast to that type. This implies that looking at the erased types is not sufficient for checking binary compatibility in MiMa. OTOH, why isn't there a bridge method for filter if it's overridden in FilteredTraversableInternal? Shouldn't we see two copies of this method (one returning Object and the other one returning FilteredTraversableInternal in all subclasses? Or maybe we do but MiMa doesn't recognize it correctly? Or is it missing because they type is private and therefore must not leak (but still does due to a compiler bug)?

(I'm only speculating here. I'd need more time to look into this but I don't expect to find any until after ScalaDays. For now the only safe solution appears to be never to add a new superclass, even if it's declared private and not supposed to leak.)

@lrytz
Copy link
Member
lrytz commented Apr 3, 2017

If I understand you correctly the erased return type of filter is Object but somehow the compiler knows that the return type is really a type parameter that was instantiated as FilteredTraversableInternal and inserts a cast to that type

My explanation was unclear, sorry: the return type of map is its That type parameter. When type-checking List(1).map(identity).filter during erasure, the qualifier List(1).map(identity) gets type Object, so the selection qual.filter needs a cast: qual.asInstanceOf[<owner-of-filter>].filter. This happens here: https://github.com/scala/scala/blob/v2.11.9/src/compiler/scala/tools/nsc/transform/Erasure.scala#L674-L676

This implies that looking at the erased types is not sufficient for checking binary compatibility in MiMa

I would say that MiMa did its job correctly: it identified the class being added, it was whitelisted. Any reference to a new class is a binary incompatibility.

Shouldn't we see two copies of this method (one returning Object and the other one returning FilteredTraversableInternal) in all subclasses?

The return type of FilteredTraversableInternal.filter in bytecode is Object.

@szeiger
Copy link
Contributor
szeiger commented Apr 3, 2017

Thanks, that makes it clearer. So we'd have to change this to cast to the actual type of the qualifier instead of the method's owner (which can be a supertype) to prevent these leaks?

I would say that MiMa did its job correctly: it identified the class being added, it was whitelisted. Any reference to a new class is a binary incompatibility.

I was hoping we could change MiMa to ignore new non-accessible superclasses and treat this case as binary compatible but with these owner casts leaking superclasses the only safe situation would be one where all methods implemented by the new superclass are overridden in all subclasses.

@lrytz
Copy link
Member
lrytz commented Apr 3, 2017

Thanks, that makes it clearer. So we'd have to change this to cast to the actual type of the qualifier instead of the method's owner (which can be a supertype) to prevent these leaks?

This cannot easily be done, unfortunately. At the beginning of erasure, all types of trees are set to null, and then the type checker is run again, this time assigning only erased types. So List(1).map(identity) really gets type Object, I don't think there's a way to get List at this point.

We could remember the type that the tree had after typer (List[Int]) and use an erased version of that for the cast, but that sounds like a hack. Incidentally, we need to do exactly that in order to avoid IllegalAccessErrors for selections of Java-defined methods: the method may be defined in a package-protected class that is not accessible. I very recently worked on this: #5792. I'm not sure we should use this hack in general, for Scala-only code.

@szeiger
Copy link
Contributor
szeiger commented Apr 3, 2017

After further consideration

the only safe situation would be one where all methods implemented by the new superclass are overridden in all subclasses

even this wouldn't be safe because it's not transitive. Adding a new superclass with overrides of all methods in all subclasses would be considered safe, adding a new override to an existing private class is safe, but the combination of both breaks binary compatibility.

szeiger added a commit that referenced this pull request Apr 4, 2017
Revert #5664 because the binary incompatible change leaks via erasure
@SethTisue SethTisue modified the milestones: 2.11.11, 2.11.9 May 1, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants
0