From 82f75456e03d09321d487672a1af765407f50726 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 4 May 2016 11:08:27 +1000 Subject: [PATCH] Emit trait method bodies in statics And use this as the target of the default methods or statically resolved super or $init calls. The call-site change is predicated on `-Yuse-trait-statics` as a stepping stone for experimentation / bootstrapping. I have performed this transformation in the backend, rather than trying to reflect this in the view from Scala symbols + ASTs. Once we commit to this change, we can remove the `lateInterfaces` bookkeeping from the backend, as we no long will need to add ancestor interfaces as direct parents to allow use of invokespecial for super calls. ``` > ;scalac sandbox/test.scala ; scala Test [info] Running scala.tools.nsc.MainGenericRunner -usejavacp Test T C [success] Total time: 2 s, completed 04/05/2016 11:07:13 AM > eval "javap -classpath . -c -private C".!! [info] ans: String = Compiled from "test.scala" [info] public class C implements T { [info] public C(); [info] Code: [info] 0: aload_0 [info] 1: invokespecial #14 // Method java/lang/Object."":()V [info] 4: aload_0 [info] 5: invokespecial #17 // Method T.$init$:()V [info] 8: getstatic #23 // Field scala/Predef$.MODULE$:Lscala/Predef$; [info] 11: ldc #24 // String C [info] 13: invokevirtual #28 // Method scala/Predef$.println:(Ljava/lang/Object;)V [info] 16: aload_0 [info] 17: invokespecial #32 // Method T.foo:()I [info] 20: pop [info] 21: return [info] } > ;scalac -Yuse-trait-statics sandbox/test.scala ; scala Test [info] Running scala.tools.nsc.MainGenericRunner -usejavacp Test T C [success] Total time: 2 s, completed 04/05/2016 11:07:39 AM > eval "javap -classpath . -c -private C".!! [info] ans: String = Compiled from "test.scala" [info] public class C implements T { [info] public C(); [info] Code: [info] 0: aload_0 [info] 1: invokespecial #14 // Method java/lang/Object."":()V [info] 4: aload_0 [info] 5: invokestatic #18 // Method T.$init$:(LT;)V [info] 8: getstatic #24 // Field scala/Predef$.MODULE$:Lscala/Predef$; [info] 11: ldc #25 // String C [info] 13: invokevirtual #29 // Method scala/Predef$.println:(Ljava/lang/Object;)V [info] 16: aload_0 [info] 17: invokestatic #33 // Method T.foo:(LT;)I [info] 20: pop [info] 21: return [info] } > eval "javap -classpath . -c -private T".!! [info] ans: String = Compiled from "test.scala" [info] public interface T { [info] public static int foo(T); [info] Code: [info] 0: iconst_0 [info] 1: ireturn [info] [info] public int foo(); [info] Code: [info] 0: aload_0 [info] 1: invokestatic #15 // Method foo:(LT;)I [info] 4: ireturn [info] [info] public static void $init$(T); [info] Code: [info] 0: getstatic #24 // Field scala/Predef$.MODULE$:Lscala/Predef$; [info] 3: ldc #25 // String T [info] 5: invokevirtual #29 // Method scala/Predef$.println:(Ljava/lang/Object;)V [info] 8: return [info] [info] public void $init$(); [info] Code: [info] 0: aload_0 [info] 1: invokestatic #32 // Method $init$:(LT;)V [info] 4: return [info] } ``` --- project/ScalaOptionParser.scala | 2 +- .../nsc/backend/jvm/BCodeBodyBuilder.scala | 8 +++++-- .../nsc/backend/jvm/BCodeSkelBuilder.scala | 24 +++++++++++++++++-- .../tools/nsc/settings/ScalaSettings.scala | 1 + 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/project/ScalaOptionParser.scala b/project/ScalaOptionParser.scala index f2fd4d86d7c0..34e05e62451d 100644 --- a/project/ScalaOptionParser.scala +++ b/project/ScalaOptionParser.scala @@ -90,7 +90,7 @@ object ScalaOptionParser { "-Ypresentation-strict", "-Ypresentation-verbose", "-Yquasiquote-debug", "-Yrangepos", "-Yreify-copypaste", "-Yreify-debug", "-Yrepl-class-based", "-Yrepl-sync", "-Yshow-member-pos", "-Yshow-symkinds", "-Yshow-symowners", "-Yshow-syms", "-Yshow-trees", "-Yshow-trees-compact", "-Yshow-trees-stringified", "-Ytyper-debug", "-Ywarn-adapted-args", "-Ywarn-dead-code", "-Ywarn-inaccessible", "-Ywarn-infer-any", "-Ywarn-nullary-override", "-Ywarn-nullary-unit", "-Ywarn-numeric-widen", "-Ywarn-unused", "-Ywarn-unused-import", "-Ywarn-value-discard", - "-deprecation", "-explaintypes", "-feature", "-help", "-no-specialization", "-nobootcp", "-nowarn", "-optimise", "-print", "-unchecked", "-uniqid", "-usejavacp", "-usemanifestcp", "-verbose", "-version") + "-deprecation", "-explaintypes", "-feature", "-help", "-no-specialization", "-nobootcp", "-nowarn", "-optimise", "-print", "-unchecked", "-uniqid", "-usejavacp", "-usemanifestcp", "-verbose", "-version", "-Yuse-trait-statics") private def stringSettingNames = List("-Xgenerate-phase-graph", "-Xmain-class", "-Xpluginsdir", "-Xshow-class", "-Xshow-object", "-Xsource-reader", "-Ydump-classes", "-Ygen-asmp", "-Ypresentation-log", "-Ypresentation-replay", "-Yrepl-outdir", "-d", "-dependencyfile", "-encoding", "-Xscript") private def pathSettingNames = List("-bootclasspath", "-classpath", "-extdirs", "-javabootclasspath", "-javaextdirs", "-sourcepath", "-toolcp") diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 5d152ef0e8e2..ea17088d4e55 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -1061,7 +1061,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val receiverName = internalName(receiverClass) // super calls are only allowed to direct parents - if (style.isSuper && receiverClass.isTraitOrInterface && !cnode.interfaces.contains(receiverName)) { + if (!settings.YuseTraitStatics.value && style.isSuper && receiverClass.isTraitOrInterface && !cnode.interfaces.contains(receiverName)) { thisBType.info.get.inlineInfo.lateInterfaces += receiverName cnode.interfaces.add(receiverName) } @@ -1082,7 +1082,11 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case Virtual => if (needsInterfaceCall(receiverClass)) bc.invokeinterface(receiverName, jname, mdescr, pos) else bc.invokevirtual (receiverName, jname, mdescr, pos) - case Super => bc.invokespecial (receiverName, jname, mdescr, pos) + case Super => + if (receiverClass.isTraitOrInterface && settings.YuseTraitStatics.value) { + val staticDesc = MethodBType(typeToBType(method.owner.info) :: method.info.paramTypes.map(typeToBType), typeToBType(method.info.resultType)).descriptor + bc.invokestatic(receiverName, jname, staticDesc, pos) + } else bc.invokespecial (receiverName, jname, mdescr, pos) } bmType.returnType diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index f190c1f2de26..2b6f58942a6c 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -489,7 +489,27 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { case ValDef(mods, name, tpt, rhs) => () // fields are added in `genPlainClass()`, via `addClassFields()` - case dd : DefDef => genDefDef(dd) + case dd : DefDef => + if (dd.symbol.owner.isTrait && dd.rhs != EmptyTree && !dd.symbol.isPrivate && !dd.symbol.hasFlag(Flags.STATIC)) { + // Split concrete methods in traits (including mixin constructors) into a static method + // with an explicit this parameter, and a non-static forwarder method. + val staticDefDef = { + val staticSym = dd.symbol.cloneSymbol + staticSym.setFlag(Flags.STATIC | Flags.ARTIFACT) + val selfParamSym = staticSym.newSyntheticValueParam(dd.symbol.owner.typeConstructor, nme.SELF) + staticSym.modifyInfo { + case mt @ MethodType(params, res) => copyMethodType(mt, selfParamSym :: params, res) + } + val selfParam = ValDef(selfParamSym) + treeCopy.DefDef(dd, dd.mods, dd.name, dd.tparams, (selfParam :: dd.vparamss.head) :: Nil, dd.tpt, dd.rhs).setSymbol(staticSym) + } + val forwarderDefDef = { + val forwarderBody = Apply(global.gen.mkAttributedRef(staticDefDef.symbol), This(dd.symbol.owner).setType(dd.symbol.owner.info) :: dd.vparamss.head.map(p => global.gen.mkAttributedIdent(p.symbol))).setType(dd.symbol.info.resultType) + deriveDefDef(dd)(_ => global.atPos(dd.pos)(forwarderBody)) + } + genDefDef(staticDefDef) + genDefDef(forwarderDefDef) + } else genDefDef(dd) case Template(_, _, body) => body foreach gen @@ -554,7 +574,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val isNative = methSymbol.hasAnnotation(definitions.NativeAttr) val isAbstractMethod = rhs == EmptyTree - val flags = GenBCode.mkFlags( + var flags = GenBCode.mkFlags( javaFlags(methSymbol), if (isAbstractMethod) asm.Opcodes.ACC_ABSTRACT else 0, if (methSymbol.isStrictFP) asm.Opcodes.ACC_STRICT else 0, diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 7b9801175955..1953ef495cb6 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -202,6 +202,7 @@ trait ScalaSettings extends AbsScalaSettings val inferByName = BooleanSetting ("-Yinfer-by-name", "Allow inference of by-name types. This is a temporary option to ease transition. See SI-7899.").withDeprecationMessage(removalIn212) val YclasspathImpl = ChoiceSetting ("-YclasspathImpl", "implementation", "Choose classpath scanning method.", List(ClassPathRepresentationType.Recursive, ClassPathRepresentationType.Flat), ClassPathRepresentationType.Flat) val YdisableFlatCpCaching = BooleanSetting ("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.") + val YuseTraitStatics = BooleanSetting ("-Yuse-trait-statics", "Emit super calls to trait methods with invokestatic") val exposeEmptyPackage = BooleanSetting ("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly() val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "method")