-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
SD-98 don't emit unnecessary mixin forwarders #5085
Conversation
PR for running CI, test will follow |
Hehe, JUnit 4 does not test default methods :-) Example:
Using 2.12.0-M4, which generates a forwarder
After this PR:
It seems we'd need JUnit 5 (junit-team/junit4#1078), but they are still working on the first milestone (http://junit.org/junit4/junit5.html). |
57448dd
to
3c62754
Compare
review by @adriaanm |
Nice! Could you elaborate a bit on the pros & cons in terms of safe binary compatible changes. Can a trait method safely become concrete when forwarders are always emitted whereas it can't if don't emit them until needed? Do we defer more errors to linkage from compile time? Not saying these are dealbreakers, but would be good to collect some trade offs. |
after this patch there are two situations where a trait forwarder is emitted:
here, if the two traits are in an inheritance relationship, the JVM will select the method of the subtrait, so we don't need a forwarder:
none of these classes need a forwarder, the JVM always selects
this case also covers the
so what kind of change would be binary compatible? removing a method is incompatible anyway, so only adding (or making an abstract one concrete) can be compatible. adding a method is incompatible if it introduces the need for a forwarder, so cases finally, note that this change also has a positive impact on incremental compilation: adding or removing a method in a trait doesn't require re-compiling subclasses in most cases. |
This should be made prominent in 2.12 release notes! Not only scala/scala JUnit tests will be affected. Arbitrary tests in Scala codebases using JUnit 4 will silently not run anymore when upgrading to 2.12. |
JUnit 4 does not support running `@Test` methods defined as default methods in parent interfaces. JUnit 5 will, but is not yet available. Currently scalac emits a forwarder to every trait method inherited by a class, so tests are correctly executed. The fix for SD-98 will change this.
In most cases when a class inherits a concrete method from a trait we don't need to generate a forwarder to the default method in the class. t5148 is moved to pos as it compiles without error now. the error message ("missing or invalid dependency") is still tested by t6440b.
Ah, I know what happened: I merged #5096, which includes the commits of this PR. So this one definitely still needs an LGTM. |
val needsForwarder = competingMethods.exists(m => { | ||
!m.owner.isTraitOrInterface || | ||
(!m.isDeferred && !mixinSuperInterfaces.contains(m.owner)) | ||
}) |
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 tend to think the following is >= readable, and faster.
@tailrec
def loop(baseClasses: List[Symbol]): Boolean = {
baseClasses match {
case Nil => false
case baseClass :: rest =>
if (baseClass ne mixinClass) {
val m = member.overriddenSymbol(baseClasses)
if (m.exists && (!m.owner.isTraitOrInterface || (!m.isDeferred && !mixinClass.isNonBottomSubclass(m.owner)))
true
else loop(rest)
} else loop(rest)
}
}
val needsForwarder = loop(clazz.baseClasses)
In this benchmark: scala/compiler-benchmark@3b4594c I tested the difference between mono-/mega-morphic calls to a method in a base class, base 2.11 trait, and a base 2.12 trait (as proposed by this encoding, simulated by using Java interfaces). Seems that we pay for the megamorphic call with either 2.11 style explicit forwarder, or with the proposed use of default methods. HotSpot internally installs vtable entries for resolved default methods at class load time, so this isn't all that suprising. @sjrd noted on gitter that Scala.JS can "multi-inline" megamorphic calls to a set of identical methods (e.g. trait forwarders) and is agnostic to this change. |
thanks for the benchmark -- to bad that avoiding forwarders doesn't improve performance. on the other hand we can hope for the JVM to improve over time, as there's a single target method. what would help is going back to the 2.11 scheme and add a static method for every concrete trait method. then the optimizer could inline forwarders and get to the |
I think that is worth exploring. It would also allow us to avoid the redundant parents to enable invokesspecial super calls in favour of an invokestatic |
most of these redundant parents stem from the actually we are in the funny situation that
both |
Yeah, unfortunately we can't just have |
One thing to note about mixin forwarders is that they have an identical bytecode descriptor to the overriden method (rather than one sharpened by generic substitution).
This is good: it means that we have some wiggle room to change our policy about whether to emit them or not, without introducing binary incompatibilities. |
|
In most cases when a class inherits a concrete method from a trait we
don't need to generate a forwarder to the default method in the class.