-
Notifications
You must be signed in to change notification settings - Fork 14
new trait encoding: skip unnecessary mixin methods #98
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
Comments
Note that with impl classes, the optimizer has a chance to fix it: it rewrites |
Related: scala/scala3#1104 / dotty-staging/dotty@74d5820 Basically, we could/should try to use Java's rules to statically resolve The forwarder would still be needed in cases like:
|
One variation on our design might be to put trait impl methods in static methods with a self parameter (in the interface, rather than in the impl class). The default method would forward to these. Subclasses (or any callsite) could then call a trait implementation method, which would also avoid the hack we have that has to add transitively inherited interfaces as direct parents to allow |
Or the other way around: we could create a static accessor for every trait impl method. |
the are not necessary in this case. Dotty will not add them after scala/scala3#1104 |
Note that they would be necessary in case of slightly changed example: abstract class S { def m: Int }
trait T extends S { final def m = 1 }
class C extends T
class D extends T
class A {
def foo(t: T) = t.m
} as JVM would prefer the abstract method in |
Another factor to consider is whether the analysis that leads us to omit the forwarders holds up under separate compilation. I don't have an example at hand, but I remember discussing this with @adriaanm |
@DarkDimius BTW, are you aware that in dotty, you end up listing all transitive parent interfaces as direct parents? I wasn't sure when looking at your bridge to GenBCode whether this was intentional or accidental.
I briefly tried the same sledgehammer approach in my changes to Scalac. It is super convenient, because you can emit arbitrary However, it found it caused huge slowdowns in startup time, which turned out to be in the JVM's classfile parser, which does default method resolution. This isn't optimized to avoid repeating work for parent interfaces that have already been processed. My current approach is to add redundant parents selectively (when we need to emit |
For the record, I identified the slowness with the system profiler.
|
@retronym thanks, I wasn't aware of this.
would you kindly point me to place where this is done? |
That implementation is problematic because the information is not reflected in the corresponding symbol, as discussed here: retronym/scala#19 (comment) |
Here is the spot when I add the parents: https://github.com/scala/scala/pull/5003/files#diff-2e0fd5042aa83407a80a7557cc2a666bR1072 Lukas found the spot where I hacked his nice test to be lenient enough to deal with this inconsistency between the bytecode parents and those in the original |
Here's a patch that implements the dotty approach diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala
index ed7ef0d..330e134 100644
--- a/src/compiler/scala/tools/nsc/transform/Mixin.scala
+++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala
@@ -8,6 +8,7 @@ package transform
import symtab._
import Flags._
+import scala.annotation.tailrec
import scala.collection.mutable
abstract class Mixin extends InfoTransform with ast.TreeDSL {
@@ -237,11 +238,24 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
/* Mix in members of implementation class mixinClass into class clazz */
def mixinTraitForwarders(mixinClass: Symbol) {
+ val baseClasses = clazz.baseClasses
for (member <- mixinClass.info.decls ; if isImplementedStatically(member)) {
member overridingSymbol clazz match {
case NoSymbol =>
- if (clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives contains member)
- cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member
+ if (clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives contains member) {
+ @tailrec
+ def needsForwarder(baseClasses: List[Symbol]): Boolean = baseClasses match {
+ case Nil => false
+ case baseClass :: tail =>
+ def okay(sym: Symbol) = sym == NoSymbol || (sym.owner.isTrait && !sym.isDeferred)
+ if ((baseClass eq member.owner) || okay(member overridingSymbol baseClass))
+ needsForwarder(tail)
+ else
+ true
+ }
+ if (needsForwarder(baseClasses))
+ cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member
+ }
case _ =>
}
}
|
Until now, concrete methods in traits were encoded with "trait implementation classes". - Such a trait would compile to two class files - the trait interface, a Java interface, and - the implementation class, containing "trait implementation methods" - trait implementation methods are static methods has an explicit self parameter. - some methods don't require addition of an interface method, such as private methods. Calls to these directly call the implementation method - classes that mixin a trait install "trait forwarders", which implement the abstract method in the interface by forwarding to the trait implementation method. The new encoding: - no longer emits trait implementation classes or trait implementation methods. - instead, concrete methods are simply retained in the interface, as JVM 8 default interface methods (the JVM spec changes in [JSR-335](http://download.oracle.com/otndocs/jcp/lambda-0_9_3-fr-eval-spec/index.html) pave the way) - use `invokespecial` to call private or particular super implementations of a method (rather `invokestatic`) - in cases when we `invokespecial` to a method in an indirect ancestor, we add that ancestor redundantly as a direct parent. We are investigating alternatives approaches here. - we still emit trait fowrarders, although we are [investigating](scala/scala-dev#98) ways to only do this when the JVM would be unable to resolve the correct method using its rules for default method resolution. Here's an example: ``` trait T { println("T") def m1 = m2 private def m2 = "m2" } trait U extends T { println("T") override def m1 = super[T].m1 } class C extends U { println("C") def test = m1 } ``` The old and new encodings are displayed and diffed here: https://gist.github.com/retronym/f174d23f859f0e053580 Some notes in the implementation: - No need to filter members from class decls at all in AddInterfaces (although we do have to trigger side effecting info transformers) - We can now emit an EnclosingMethod attribute for classes nested in private trait methods - Created a factory method for an AST shape that is used in a number of places to symbolically bind to a particular super method without needed to specify the qualifier of the `Super` tree (which is too limiting, as it only allows you to refer to direct parents.) - I also found a similar tree shape created in Delambdafy, that is better expressed with an existing tree creation factory method, mkSuperInit.
i looked a bit into the question of adding interfaces as direct parents to allow i prefer the current solution over adding an additional static method next to every default method. i don't see a good way to decide early what interfaces need to be direct parents and add them to the symbol info. if we care about the consistency of there's no urgency to implement this, but it would be pretty straightforward. i can give it a shot and we discuss on the PR. |
another advantage of only mixing in forwarders when absolutely necessary, is that we avoid the problem described in #178 (mixing in a method that has a sharper signature in a subclass that instantiates a type parameter in the method's signature) |
Can we skip emitting unnecessary mixin methods in the new trait encoding scheme?
With revision retronym/scala@8ee7aca I compiled the following example:
C
andD
get anoverride def m = super[T].m
. Are the necessary?T
be markedfinal
?C
andD
ifT.m
was notfinal
?In the current situation, the invocation
t.m
in classA
is polymorphic: the bytecode isINVOKEINTERFACE T.m ()I
, which resolves to eitherC.m
orD.m
.The text was updated successfully, but these errors were encountered: