From 7d51b3fd1569917cb804363bd418466a306f5c89 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 6 Jun 2016 14:24:38 +1000 Subject: 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. We also need to add an restriction related to invokespecial to Java parents: to support a super call to one of these to implement a super accessor, the interface must be listed as a direct parent of the class. The static method names has a trailing $ added to avoid duplicate name and signature errors in classfiles. --- .../tools/nsc/backend/jvm/BCodeBodyBuilder.scala | 49 +++++++------ .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 71 +++---------------- .../tools/nsc/backend/jvm/BCodeIdiomatic.scala | 22 +++--- .../tools/nsc/backend/jvm/BCodeSkelBuilder.scala | 17 ++++- .../scala/tools/nsc/backend/jvm/BTypes.scala | 22 +----- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 80 +++++++++++++++++++++- .../nsc/backend/jvm/analysis/BackendUtils.scala | 14 ++-- .../tools/nsc/backend/jvm/opt/BytecodeUtils.scala | 9 +++ .../tools/nsc/backend/jvm/opt/CallGraph.scala | 7 +- .../nsc/backend/jvm/opt/InlineInfoAttribute.scala | 13 ---- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 28 ++++---- .../nsc/backend/jvm/opt/InlinerHeuristics.scala | 28 ++++++-- 12 files changed, 203 insertions(+), 157 deletions(-) (limited to 'src/compiler/scala/tools/nsc/backend/jvm') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index d7106ae908..55fe47bde6 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -11,10 +11,10 @@ package jvm import scala.annotation.switch import scala.reflect.internal.Flags - import scala.tools.asm import GenBCode._ import BackendReporting._ +import scala.tools.asm.Opcodes import scala.tools.asm.tree.MethodInsnNode import scala.tools.nsc.backend.jvm.BCodeHelpers.{InvokeStyle, TestOp} @@ -637,7 +637,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val nativeKind = tpeTK(expr) genLoad(expr, nativeKind) val MethodNameAndType(mname, methodType) = srBoxesRuntimeBoxToMethods(nativeKind) - bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos) + bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos) generatedType = boxResultType(fun.symbol) case Apply(fun, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) => @@ -645,7 +645,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val boxType = unboxResultType(fun.symbol) generatedType = boxType val MethodNameAndType(mname, methodType) = srBoxesRuntimeUnboxToMethods(boxType) - bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, app.pos) + bc.invokestatic(srBoxesRunTimeRef.internalName, mname, methodType.descriptor, itf = false, app.pos) case app @ Apply(fun, args) => val sym = fun.symbol @@ -1058,31 +1058,40 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } receiverClass.info // ensure types the type is up to date; erasure may add lateINTERFACE to traits - val receiverName = internalName(receiverClass) - - // super calls are only allowed to direct parents - if (style.isSuper && receiverClass.isTraitOrInterface && !cnode.interfaces.contains(receiverName)) { - thisBType.info.get.inlineInfo.lateInterfaces += receiverName - cnode.interfaces.add(receiverName) - } + val receiverBType = classBTypeFromSymbol(receiverClass) + val receiverName = receiverBType.internalName def needsInterfaceCall(sym: Symbol) = { sym.isTraitOrInterface || sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass) } - val jname = method.javaSimpleName.toString - val bmType = methodBTypeFromSymbol(method) - val mdescr = bmType.descriptor + val jname = method.javaSimpleName.toString + val bmType = methodBTypeFromSymbol(method) + val mdescr = bmType.descriptor + val isInterface = receiverBType.isInterface.get import InvokeStyle._ - style match { - case Static => bc.invokestatic (receiverName, jname, mdescr, pos) - case Special => bc.invokespecial (receiverName, jname, mdescr, pos) - 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) + if (style == Super) { + assert(receiverClass == methodOwner, s"for super call, expecting $receiverClass == $methodOwner") + if (receiverClass.isTrait && !receiverClass.isJavaDefined) { + val staticDesc = MethodBType(typeToBType(method.owner.info) :: bmType.argumentTypes, bmType.returnType).descriptor + val staticName = traitImplMethodName(method).toString + bc.invokestatic(receiverName, staticName, staticDesc, isInterface, pos) + } else { + if (receiverClass.isTraitOrInterface) { + // An earlier check in Mixin reports an error in this case, so it doesn't reach the backend + assert(cnode.interfaces.contains(receiverName), s"cannot invokespecial $receiverName.$jname, the interface is not a direct parent.") + } + bc.invokespecial(receiverName, jname, mdescr, isInterface, pos) + } + } else { + val opc = style match { + case Static => Opcodes.INVOKESTATIC + case Special => Opcodes.INVOKESPECIAL + case Virtual => if (isInterface) Opcodes.INVOKEINTERFACE else Opcodes.INVOKEVIRTUAL + } + bc.emitInvoke(opc, receiverName, jname, mdescr, isInterface, pos) } bmType.returnType diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 5a5747c81f..df3c2cb3d5 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -11,6 +11,7 @@ import scala.tools.asm import scala.tools.nsc.io.AbstractFile import GenBCode._ import BackendReporting._ +import scala.reflect.internal.Flags /* * Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes. @@ -49,6 +50,14 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } } + def needsStaticImplMethod(sym: Symbol) = sym.hasAttachment[global.mixer.NeedStaticImpl.type] + + final def traitImplMethodName(sym: Symbol): Name = { + val name = sym.javaSimpleName + if (sym.isMixinConstructor) name + else name.append(nme.NAME_JOIN_STRING) + } + /** * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a * member class. This method is used to decide if we should emit an EnclosingMethod attribute. @@ -230,58 +239,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { sym.isErroneous } - /** - * Build the [[InlineInfo]] for a class symbol. - */ - def buildInlineInfoFromClassSymbol(classSym: Symbol, classSymToInternalName: Symbol => InternalName, methodSymToDescriptor: Symbol => String): InlineInfo = { - val isEffectivelyFinal = classSym.isEffectivelyFinal - - val sam = { - if (classSym.isEffectivelyFinal) None - else { - // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an - // empty parameter list in later phases and would therefore be picked as SAM. - val samSym = exitingPickler(definitions.samOf(classSym.tpe)) - if (samSym == NoSymbol) None - else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym)) - } - } - - var warning = Option.empty[ClassSymbolInfoFailureSI9111] - - // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some - // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. - val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({ - case methodSym => - if (completeSilentlyAndCheckErroneous(methodSym)) { - // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler. - if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes") - warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName)) - None - } else { - val name = methodSym.javaSimpleName.toString // same as in genDefDef - val signature = name + methodSymToDescriptor(methodSym) - - // In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the - // method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded - // so they are not marked final in the InlineInfo attribute. - // - // However, due to https://github.com/scala/scala-dev/issues/126, this currently does not - // work, the abstract accessor for O will be marked effectivelyFinal. - val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !methodSym.isDeferred - - val info = MethodInlineInfo( - effectivelyFinal = effectivelyFinal, - annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), - annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass) - ) - Some((signature, info)) - } - }).toMap - - InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning) - } - /* * must-single-thread */ @@ -568,15 +525,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { /** * The class internal name for a given class symbol. */ - final def internalName(sym: Symbol): String = { - // For each java class, the scala compiler creates a class and a module (thus a module class). - // If the `sym` is a java module class, we use the java class instead. This ensures that the - // ClassBType is created from the main class (instead of the module class). - // The two symbols have the same name, so the resulting internalName is the same. - // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting) - val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym - classBTypeFromSymbol(classSym).internalName - } + final def internalName(sym: Symbol): String = classBTypeFromSymbol(sym).internalName } // end of trait BCInnerClassGen trait BCAnnotGen extends BCInnerClassGen { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index ed1b4ec325..e3d45a9b3e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -190,6 +190,7 @@ abstract class BCodeIdiomatic extends SubComponent { JavaStringBuilderClassName, INSTANCE_CONSTRUCTOR_NAME, "()V", + itf = false, pos ) } @@ -373,30 +374,27 @@ abstract class BCodeIdiomatic extends SubComponent { final def rem(tk: BType) { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread // can-multi-thread - final def invokespecial(owner: String, name: String, desc: String, pos: Position) { - addInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, false, pos) + final def invokespecial(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = { + emitInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, itf, pos) } // can-multi-thread - final def invokestatic(owner: String, name: String, desc: String, pos: Position) { - addInvoke(Opcodes.INVOKESTATIC, owner, name, desc, false, pos) + final def invokestatic(owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = { + emitInvoke(Opcodes.INVOKESTATIC, owner, name, desc, itf, pos) } // can-multi-thread - final def invokeinterface(owner: String, name: String, desc: String, pos: Position) { - addInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, true, pos) + final def invokeinterface(owner: String, name: String, desc: String, pos: Position): Unit = { + emitInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, itf = true, pos) } // can-multi-thread - final def invokevirtual(owner: String, name: String, desc: String, pos: Position) { - addInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, false, pos) + final def invokevirtual(owner: String, name: String, desc: String, pos: Position): Unit = { + emitInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, itf = false, pos) } - private def addInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position) = { + def emitInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Position): Unit = { val node = new MethodInsnNode(opcode, owner, name, desc, itf) jmethod.instructions.add(node) if (settings.optInlinerEnabled) callsitePositions(node) = pos } - final def invokedynamic(owner: String, name: String, desc: String) { - jmethod.visitMethodInsn(Opcodes.INVOKEDYNAMIC, owner, name, desc) - } // can-multi-thread final def goTo(label: asm.Label) { jmethod.visitJumpInsn(Opcodes.GOTO, label) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index bddc41e5c6..1bff8519ec 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -488,7 +488,22 @@ 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 => + val sym = dd.symbol + if (needsStaticImplMethod(sym)) { + val staticDefDef = global.gen.mkStatic(dd, traitImplMethodName(sym), _.cloneSymbol) + val forwarderDefDef = { + val forwarderBody = Apply(global.gen.mkAttributedRef(staticDefDef.symbol), This(sym.owner).setType(sym.owner.typeConstructor) :: dd.vparamss.head.map(p => global.gen.mkAttributedIdent(p.symbol))).setType(sym.info.resultType) + // we don't want to the optimizer to inline the static method into the forwarder. Instead, + // the backend has a special case to transitively inline into a callsite of the forwarder + // when the forwarder itself is inlined. + forwarderBody.updateAttachment(NoInlineCallsiteAttachment) + deriveDefDef(dd)(_ => global.atPos(dd.pos)(forwarderBody)) + } + genDefDef(staticDefDef) + if (!sym.isMixinConstructor) + genDefDef(forwarderDefDef) + } else genDefDef(dd) case Template(_, _, body) => body foreach gen diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index a708feb0a7..7b2686e7a9 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -225,8 +225,7 @@ abstract class BTypes { val inlineInfo = inlineInfoFromClassfile(classNode) - val classfileInterfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) - val interfaces = classfileInterfaces.filterNot(i => inlineInfo.lateInterfaces.contains(i.internalName)) + val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)) classBType @@ -1147,25 +1146,6 @@ object BTypes { sam: Option[String], methodInfos: Map[String, MethodInlineInfo], warning: Option[ClassInlineInfoWarning]) { - /** - * A super call (invokespecial) to a default method T.m is only allowed if the interface T is - * a direct parent of the class. Super calls are introduced for example in Mixin when generating - * forwarder methods: - * - * trait T { override def clone(): Object = "hi" } - * trait U extends T - * class C extends U - * - * The class C gets a forwarder that invokes T.clone(). During code generation the interface T - * is added as direct parent to class C. Note that T is not a (direct) parent in the frontend - * type of class C. - * - * All interfaces that are added to a class during code generation are added to this buffer and - * stored in the InlineInfo classfile attribute. This ensures that the ClassBTypes for a - * specific class is the same no matter if it's constructed from a Symbol or from a classfile. - * This is tested in BTypesFromClassfileTest. - */ - val lateInterfaces: ListBuffer[InternalName] = ListBuffer.empty } val EmptyInlineInfo = InlineInfo(false, None, Map.empty, None) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 21ea351a99..1a4590e7d1 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -97,11 +97,19 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { * for the Nothing / Null. If used for example as a parameter type, we use the runtime classes * in the classfile method signature. */ - final def classBTypeFromSymbol(classSym: Symbol): ClassBType = { + final def classBTypeFromSymbol(sym: Symbol): ClassBType = { + // For each java class, the scala compiler creates a class and a module (thus a module class). + // If the `sym` is a java module class, we use the java class instead. This ensures that the + // ClassBType is created from the main class (instead of the module class). + // The two symbols have the same name, so the resulting internalName is the same. + // Phase travel (exitingPickler) required for SI-6613 - linkedCoC is only reliable in early phases (nesting) + val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym + assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol") assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym") assertClassNotArrayNotPrimitive(classSym) assert(!primitiveTypeToBType.contains(classSym) || isCompilingPrimitive, s"Cannot create ClassBType for primitive class symbol $classSym") + if (classSym == NothingClass) srNothingRef else if (classSym == NullClass) srNullRef else { @@ -509,7 +517,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { * classfile attribute. */ private def buildInlineInfo(classSym: Symbol, internalName: InternalName): InlineInfo = { - def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym, classBTypeFromSymbol(_).internalName, methodBTypeFromSymbol(_).descriptor) + def buildFromSymbol = buildInlineInfoFromClassSymbol(classSym) // phase travel required, see implementation of `compiles`. for nested classes, it checks if the // enclosingTopLevelClass is being compiled. after flatten, all classes are considered top-level, @@ -530,6 +538,74 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } } + /** + * Build the [[InlineInfo]] for a class symbol. + */ + def buildInlineInfoFromClassSymbol(classSym: Symbol): InlineInfo = { + val isEffectivelyFinal = classSym.isEffectivelyFinal + + val sam = { + if (classSym.isEffectivelyFinal) None + else { + // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an + // empty parameter list in later phases and would therefore be picked as SAM. + val samSym = exitingPickler(definitions.samOf(classSym.tpe)) + if (samSym == NoSymbol) None + else Some(samSym.javaSimpleName.toString + methodBTypeFromSymbol(samSym).descriptor) + } + } + + var warning = Option.empty[ClassSymbolInfoFailureSI9111] + + // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some + // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. + val methodInlineInfos = classSym.info.decls.iterator.filter(m => m.isMethod && !scalaPrimitives.isPrimitive(m)).flatMap({ + case methodSym => + if (completeSilentlyAndCheckErroneous(methodSym)) { + // Happens due to SI-9111. Just don't provide any MethodInlineInfo for that method, we don't need fail the compiler. + if (!classSym.isJavaDefined) devWarning("SI-9111 should only be possible for Java classes") + warning = Some(ClassSymbolInfoFailureSI9111(classSym.fullName)) + Nil + } else { + val name = methodSym.javaSimpleName.toString // same as in genDefDef + val signature = name + methodBTypeFromSymbol(methodSym).descriptor + + // In `trait T { object O }`, `oSym.isEffectivelyFinalOrNotOverridden` is true, but the + // method is abstract in bytecode, `defDef.rhs.isEmpty`. Abstract methods are excluded + // so they are not marked final in the InlineInfo attribute. + // + // However, due to https://github.com/scala/scala-dev/issues/126, this currently does not + // work, the abstract accessor for O will be marked effectivelyFinal. + val effectivelyFinal = methodSym.isEffectivelyFinalOrNotOverridden && !methodSym.isDeferred + + val info = MethodInlineInfo( + effectivelyFinal = effectivelyFinal, + annotatedInline = methodSym.hasAnnotation(ScalaInlineClass), + annotatedNoInline = methodSym.hasAnnotation(ScalaNoInlineClass)) + + if (needsStaticImplMethod(methodSym)) { + val staticName = traitImplMethodName(methodSym).toString + val selfParam = methodSym.newSyntheticValueParam(methodSym.owner.typeConstructor, nme.SELF) + val staticMethodType = methodSym.info match { + case mt @ MethodType(params, res) => copyMethodType(mt, selfParam :: params, res) + } + val staticMethodSignature = staticName + methodBTypeFromMethodType(staticMethodType, isConstructor = false) + val staticMethodInfo = MethodInlineInfo( + effectivelyFinal = true, + annotatedInline = info.annotatedInline, + annotatedNoInline = info.annotatedNoInline) + if (methodSym.isMixinConstructor) + List((staticMethodSignature, staticMethodInfo)) + else + List((signature, info), (staticMethodSignature, staticMethodInfo)) + } else + List((signature, info)) + } + }).toMap + + InlineInfo(isEffectivelyFinal, sam, methodInlineInfos, warning) + } + /** * For top-level objects without a companion class, the compiler generates a mirror class with * static forwarders (Java compat). There's no symbol for the mirror class, but we still need a diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala index 513c71fe2e..539435a326 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala @@ -2,18 +2,18 @@ package scala.tools.nsc package backend.jvm package analysis +import java.lang.invoke.LambdaMetafactory + import scala.annotation.switch -import scala.tools.asm.{Handle, Type} +import scala.collection.JavaConverters._ +import scala.collection.mutable import scala.tools.asm.Opcodes._ import scala.tools.asm.tree._ import scala.tools.asm.tree.analysis._ -import GenBCode._ +import scala.tools.asm.{Handle, Type} import scala.tools.nsc.backend.jvm.BTypes._ +import scala.tools.nsc.backend.jvm.GenBCode._ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ -import java.lang.invoke.LambdaMetafactory - -import scala.collection.mutable -import scala.collection.JavaConverters._ /** * This component hosts tools and utilities used in the backend that require access to a `BTypes` @@ -39,7 +39,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { case ae: AnalyzerException => throw new AnalyzerException(null, "While processing " + classInternalName + "." + methodNode.name, ae) } - def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode) + def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode) } /** diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala index 63906d80e5..e21c46dbe9 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -93,6 +93,15 @@ object BytecodeUtils { op == INVOKESPECIAL || op == INVOKESTATIC } + def isVirtualCall(instruction: AbstractInsnNode): Boolean = { + val op = instruction.getOpcode + op == INVOKEVIRTUAL || op == INVOKEINTERFACE + } + + def isCall(instruction: AbstractInsnNode): Boolean = { + isNonVirtualCall(instruction) || isVirtualCall(instruction) + } + def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0 def isConstructor(methodNode: MethodNode): Boolean = { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala index 40344809bf..d6942d9ff9 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -9,11 +9,11 @@ package opt import scala.collection.immutable.IntMap import scala.reflect.internal.util.{NoPosition, Position} -import scala.tools.asm.{Opcodes, Type, Handle} +import scala.tools.asm.{Handle, Opcodes, Type} import scala.tools.asm.tree._ import scala.collection.{concurrent, mutable} import scala.collection.JavaConverters._ -import scala.tools.nsc.backend.jvm.BTypes.InternalName +import scala.tools.nsc.backend.jvm.BTypes.{InternalName, MethodInlineInfo} import scala.tools.nsc.backend.jvm.BackendReporting._ import scala.tools.nsc.backend.jvm.analysis._ import BytecodeUtils._ @@ -67,6 +67,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { } def containsCallsite(callsite: Callsite): Boolean = callsites(callsite.callsiteMethod) contains callsite.callsiteInstruction + def findCallSite(method: MethodNode, call: MethodInsnNode): Option[Callsite] = callsites.getOrElse(method, Map.empty).get(call) def removeClosureInstantiation(indy: InvokeDynamicInsnNode, methodNode: MethodNode): Option[ClosureInstantiation] = { val methodClosureInits = closureInstantiations(methodNode) @@ -356,7 +357,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { "Invocation of" + s" ${callee.map(_.calleeDeclarationClass.internalName).getOrElse("?")}.${callsiteInstruction.name + callsiteInstruction.desc}" + s"@${callsiteMethod.instructions.indexOf(callsiteInstruction)}" + - s" in ${callsiteClass.internalName}.${callsiteMethod.name}" + s" in ${callsiteClass.internalName}.${callsiteMethod.name}${callsiteMethod.desc}" } final case class ClonedCallsite(callsite: Callsite, clonedWhenInlining: Callsite) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala index 79d26b0b4e..5ce7072c60 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlineInfoAttribute.scala @@ -51,7 +51,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI if (inlineInfo.isEffectivelyFinal) flags |= 1 // flags |= 2 // no longer written if (inlineInfo.sam.isDefined) flags |= 4 - if (inlineInfo.lateInterfaces.nonEmpty) flags |= 8 result.putByte(flags) for (samNameDesc <- inlineInfo.sam) { @@ -79,9 +78,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI result.putByte(inlineInfo) } - result.putShort(inlineInfo.lateInterfaces.length) - for (i <- inlineInfo.lateInterfaces) result.putShort(cw.newUTF8(i)) - result } @@ -105,7 +101,6 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI val isFinal = (flags & 1) != 0 val hasSelf = (flags & 2) != 0 val hasSam = (flags & 4) != 0 - val hasLateInterfaces = (flags & 8) != 0 if (hasSelf) nextUTF8() // no longer used @@ -128,13 +123,7 @@ case class InlineInfoAttribute(inlineInfo: InlineInfo) extends Attribute(InlineI (name + desc, MethodInlineInfo(isFinal, isInline, isNoInline)) }).toMap - val lateInterfaces = if (!hasLateInterfaces) Nil else { - val numLateInterfaces = nextShort() - (0 until numLateInterfaces).map(_ => nextUTF8()) - } - val info = InlineInfo(isFinal, sam, infos, None) - info.lateInterfaces ++= lateInterfaces InlineInfoAttribute(info) } else { val msg = UnknownScalaInlineInfoVersion(cr.getClassName, version) @@ -161,8 +150,6 @@ object InlineInfoAttribute { * [u2] name (reference) * [u2] descriptor (reference) * [u1] isFinal (<< 0), traitMethodWithStaticImplementation (<< 1), hasInlineAnnotation (<< 2), hasNoInlineAnnotation (<< 3) - * [u2]? numLateInterfaces - * [u2] lateInterface (reference) */ final val VERSION: Byte = 1 diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index 7b4cfe2a18..9c5a1a9f98 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -106,6 +106,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { val elided = mutable.Set.empty[InlineRequest] def nonElidedRequests(methodNode: MethodNode): Set[InlineRequest] = requestsByMethod(methodNode) diff elided + def allCallees(r: InlineRequest): Set[MethodNode] = r.post.flatMap(allCallees).toSet + r.callsite.callee.get.callee + /** * Break cycles in the inline request graph by removing callsites. * @@ -114,20 +116,20 @@ class Inliner[BT <: BTypes](val btypes: BT) { */ def breakInlineCycles: List[InlineRequest] = { // is there a path of inline requests from start to goal? - def isReachable(start: MethodNode, goal: MethodNode): Boolean = { - @tailrec def reachableImpl(check: List[MethodNode], visited: Set[MethodNode]): Boolean = check match { - case x :: xs => + def isReachable(start: Set[MethodNode], goal: MethodNode): Boolean = { + @tailrec def reachableImpl(check: Set[MethodNode], visited: Set[MethodNode]): Boolean = { + if (check.isEmpty) false + else { + val x = check.head if (x == goal) true - else if (visited(x)) reachableImpl(xs, visited) + else if (visited(x)) reachableImpl(check - x, visited) else { - val callees = nonElidedRequests(x).map(_.callsite.callee.get.callee) - reachableImpl(xs ::: callees.toList, visited + x) + val callees = nonElidedRequests(x).flatMap(allCallees) + reachableImpl(check - x ++ callees, visited + x) } - - case Nil => - false + } } - reachableImpl(List(start), Set.empty) + reachableImpl(start, Set.empty) } val result = new mutable.ListBuffer[InlineRequest]() @@ -136,7 +138,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { java.util.Arrays.sort(requests, callsiteOrdering) for (r <- requests) { // is there a chain of inlining requests that would inline the callsite method into the callee? - if (isReachable(r.callsite.callee.get.callee, r.callsite.callsiteMethod)) + if (isReachable(allCallees(r), r.callsite.callsiteMethod)) elided += r else result += r @@ -150,8 +152,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { if (requests.isEmpty) Nil else { val (leaves, others) = requests.partition(r => { - val inlineRequestsForCallee = nonElidedRequests(r.callsite.callee.get.callee) - inlineRequestsForCallee.forall(visited) + val inlineRequestsForCallees = allCallees(r).flatMap(nonElidedRequests) + inlineRequestsForCallees.forall(visited) }) assert(leaves.nonEmpty, requests) leaves ::: leavesFirst(others, visited ++ leaves) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala index 009742501e..79e74f3eb7 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala @@ -7,14 +7,14 @@ package scala.tools.nsc package backend.jvm package opt -import scala.tools.asm.tree.MethodNode -import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.collection.JavaConverters._ +import scala.tools.asm.Opcodes +import scala.tools.asm.tree.{MethodInsnNode, MethodNode} +import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.tools.nsc.backend.jvm.BackendReporting.OptimizerWarning class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { import bTypes._ - import inliner._ import callGraph._ case class InlineRequest(callsite: Callsite, post: List[InlineRequest], reason: String) { @@ -93,7 +93,27 @@ class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { val callee = callsite.callee.get def requestIfCanInline(callsite: Callsite, reason: String): Either[OptimizerWarning, InlineRequest] = inliner.earlyCanInlineCheck(callsite) match { case Some(w) => Left(w) - case None => Right(InlineRequest(callsite, Nil, reason)) + case None => + val callee = callsite.callee.get + val postInlineRequest: List[InlineRequest] = callee.calleeDeclarationClass.isInterface match { + case Right(true) => + // Treat the pair of trait interface method and static method as one for the purposes of inlining: + // if we inline invokeinterface, invoke the invokestatic, too. + val calls = callee.callee.instructions.iterator().asScala.filter(BytecodeUtils.isCall).take(2).toList + calls match { + case List(x: MethodInsnNode) if x.getOpcode == Opcodes.INVOKESTATIC && x.name == (callee.callee.name + "$") => + callGraph.addIfMissing(callee.callee, callee.calleeDeclarationClass) + val maybeNodeToCallsite1 = callGraph.findCallSite(callee.callee, x) + maybeNodeToCallsite1.toList.flatMap(x => requestIfCanInline(x, reason).right.toOption) + case _ => + Nil + + } + case _ => Nil + } + + Right(InlineRequest(callsite, postInlineRequest, reason)) + } compilerSettings.YoptInlineHeuristics.value match { -- cgit v1.2.3