diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 5d152ef0e8e2..92aa75ef68f1 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -1060,12 +1060,6 @@ 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) - } - def needsInterfaceCall(sym: Symbol) = { sym.isTraitOrInterface || sym.isJavaDefined && sym.isNonBottomSubClass(definitions.ClassfileAnnotationClass) @@ -1082,7 +1076,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) { + 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/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index a5744983b280..f179c11c7ae5 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,9 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } } + def isTraitMethodRequiringStaticImpl(sym: Symbol) = + sym.hasAttachment[global.mixer.NeedStaticImpl.type] + /** * 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 +234,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 */ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index bddc41e5c6ac..93d6990d31ab 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -488,7 +488,24 @@ 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 (isTraitMethodRequiringStaticImpl(sym)) { + // 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 = global.gen.mkStatic(dd, _.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 a708feb0a7b8..7b2686e7a9be 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 21ea351a998f..33a49dc63b77 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -509,7 +509,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 +530,73 @@ 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 (isTraitMethodRequiringStaticImpl(methodSym)) { + 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 = name + 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/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala index 63906d80e50b..e21c46dbe99e 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 d4ff6493a379..1658eadbb7de 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -9,14 +9,14 @@ 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 ByteCodeRepository.{Source, CompilationUnit} +import ByteCodeRepository.{CompilationUnit, Source} import BytecodeUtils._ class CallGraph[BT <: BTypes](val btypes: BT) { @@ -68,6 +68,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) @@ -359,7 +360,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) @@ -394,6 +395,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { samParamTypes: IntMap[btypes.ClassBType], calleeInfoWarning: Option[CalleeInfoWarning]) { override def toString = s"Callee($calleeDeclarationClass.${callee.name})" + def textifyCallee = AsmUtils.textify(callee) } /** 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 79d26b0b4eee..5ce7072c60c0 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 809b9e310dce..4b082d17b238 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -29,7 +29,8 @@ class Inliner[BT <: BTypes](val btypes: BT) { var inlineLog: List[InlineLog] = Nil def runInliner(): Unit = { - for (request <- collectAndOrderInlineRequests) { + val orderedRequests = collectAndOrderInlineRequests + for (request <- orderedRequests) { val Right(callee) = request.callsite.callee // collectAndOrderInlineRequests returns callsites with a known callee // TODO: if the request has downstream requests, create a snapshot to which we could roll back in case some downstream callsite cannot be inlined @@ -106,6 +107,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 +117,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 +139,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 +153,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 17807fb385ed..673c3b863314 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala @@ -7,10 +7,11 @@ package scala.tools.nsc package backend.jvm package opt -import scala.tools.asm.tree.MethodNode +import scala.tools.asm.tree.{AbstractInsnNode, MethodInsnNode, MethodNode} import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.collection.JavaConverters._ -import scala.tools.nsc.backend.jvm.BackendReporting.OptimizerWarning +import scala.tools.asm.Opcodes +import scala.tools.nsc.backend.jvm.BackendReporting.{Invalid, OptimizerWarning} class InlinerHeuristics[BT <: BTypes](val bTypes: BT) { import bTypes._ diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 6df0b992ed49..ea2bfa5bb1d0 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -143,6 +143,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // implementation class, as it's a clone that was made after erasure, and thus it does not // know its info at the beginning of erasure anymore. val sym = mixinMember cloneSymbol clazz + sym.removeAttachment[NeedStaticImpl.type] val erasureMap = erasure.erasure(mixinMember) val erasedInterfaceInfo: Type = erasureMap(mixinMember.info) @@ -1007,7 +1008,13 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { case tree => tree } // add all new definitions to current class or interface - treeCopy.Template(tree, parents1, self, addNewDefs(currentOwner, bodyEmptyAccessors)) + val body1 = addNewDefs(currentOwner, bodyEmptyAccessors) + body1 foreach { + case dd: DefDef if isTraitMethodRequiringStaticImpl(dd) => + dd.symbol.updateAttachment(NeedStaticImpl) + case _ => + } + treeCopy.Template(tree, parents1, self, body1) case Select(qual, name) if sym.owner.isTrait && !sym.isMethod => // refer to fields in some trait an abstract getter in the interface. @@ -1023,7 +1030,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { typedPos(tree.pos)((qual DOT setter)(rhs)) - case _ => tree } @@ -1042,4 +1048,14 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { finally localTyper = saved } } + + private def isTraitMethodRequiringStaticImpl(dd: DefDef): Boolean = { + val sym = dd.symbol + sym != null && sym.owner.isTrait && !sym.isDeferred && + (sym.isLazy || !sym.isAccessor) && !sym.isSuperAccessor && !sym.isModule && // these are not deferred, but defDef.rhs.isEmpty + !sym.isPrivate && // no need to put implementations of private methods into a static method + !sym.hasFlag(Flags.STATIC) + } + + case object NeedStaticImpl extends PlainAttachment } diff --git a/test/files/instrumented/InstrumentationTest.check b/test/files/instrumented/InstrumentationTest.check index 74f9c9d26885..d317fc42077d 100644 --- a/test/files/instrumented/InstrumentationTest.check +++ b/test/files/instrumented/InstrumentationTest.check @@ -6,5 +6,5 @@ Method call statistics: 1 instrumented/Foo2.someMethod()I 1 scala/DeprecatedConsole.()V 1 scala/Predef$.println(Ljava/lang/Object;)V - 1 scala/io/AnsiColor.$init$()V + 1 scala/io/AnsiColor.$init$(Lscala/io/AnsiColor;)V 1 scala/runtime/BoxesRunTime.boxToBoolean(Z)Ljava/lang/Boolean; diff --git a/test/files/run/mixin-signatures.check b/test/files/run/mixin-signatures.check index 9961992e2d1c..1cb7d54f5b68 100644 --- a/test/files/run/mixin-signatures.check +++ b/test/files/run/mixin-signatures.check @@ -1,6 +1,7 @@ class Test$bar1$ { public default java.lang.String Foo1.f(java.lang.Object) generic: public default java.lang.String Foo1.f(T) + public static java.lang.String Foo1.f(Foo1,java.lang.Object) public java.lang.Object Test$bar1$.f(java.lang.Object) public java.lang.String Test$bar1$.g(java.lang.String) public java.lang.Object Test$bar1$.g(java.lang.Object) @@ -12,6 +13,7 @@ class Test$bar1$ { class Test$bar2$ { public default java.lang.Object Foo2.f(java.lang.String) generic: public default R Foo2.f(java.lang.String) + public static java.lang.Object Foo2.f(Foo2,java.lang.String) public java.lang.Object Test$bar2$.f(java.lang.Object) public java.lang.String Test$bar2$.g(java.lang.String) public java.lang.Object Test$bar2$.g(java.lang.Object) diff --git a/test/files/run/t4891.check b/test/files/run/t4891.check index 1b1108e9eee0..3b739bc4f755 100644 --- a/test/files/run/t4891.check +++ b/test/files/run/t4891.check @@ -1,6 +1,7 @@ test.generic.T1 - (m) public default void test.generic.T1.$init$() + (m) public static void test.generic.T1.$init$(test.generic.T1) (m) public default A test.generic.T1.t1(A) + (m) public static java.lang.Object test.generic.T1.t1(test.generic.T1,java.lang.Object) test.generic.C1 (m) public void test.generic.C1.m1() test.generic.C2 diff --git a/test/files/run/t5652.check b/test/files/run/t5652.check index a0fb6fe9b4e5..6653dd47f10f 100644 --- a/test/files/run/t5652.check +++ b/test/files/run/t5652.check @@ -1,6 +1,8 @@ public default int T1.T1$$g$1() public default int T1.f0() -public default void T1.$init$() +public static int T1.T1$$g$1(T1) +public static int T1.f0(T1) +public static void T1.$init$(T1) public final int A1.A1$$g$2() public int A1.f1() public final int A2.A2$$g$1() diff --git a/test/files/run/t7700.check b/test/files/run/t7700.check index 1d51e68877ca..948bce900cd1 100644 --- a/test/files/run/t7700.check +++ b/test/files/run/t7700.check @@ -1,3 +1,4 @@ -public default void C.$init$() +public static void C.$init$(C) +public static java.lang.Object C.bar(C,java.lang.Object) public default java.lang.Object C.bar(java.lang.Object) public abstract java.lang.Object C.foo(java.lang.Object) diff --git a/test/files/run/t7932.check b/test/files/run/t7932.check index a2ad84cd4635..d3c82386bd19 100644 --- a/test/files/run/t7932.check +++ b/test/files/run/t7932.check @@ -1,6 +1,10 @@ public Category C.category() public Category C.category1() +public static Category M1.category(M1) +public static Category M1.category1(M1) public default Category M1.category() public default Category M1.category1() public default Category M2.category() public default Category M2.category1() +public static Category M2.category(M2) +public static Category M2.category1(M2) diff --git a/test/junit/scala/lang/traits/BytecodeTest.scala b/test/junit/scala/lang/traits/BytecodeTest.scala index f47fc9c12724..3e3ba386b1d6 100644 --- a/test/junit/scala/lang/traits/BytecodeTest.scala +++ b/test/junit/scala/lang/traits/BytecodeTest.scala @@ -9,6 +9,7 @@ import scala.collection.JavaConverters._ import scala.tools.asm.Opcodes import scala.tools.asm.Opcodes._ import scala.tools.asm.tree.ClassNode +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils import scala.tools.partest.ASMConverters._ import scala.tools.testing.BytecodeTesting import scala.tools.testing.BytecodeTesting._ @@ -18,8 +19,8 @@ class BytecodeTest extends BytecodeTesting { import compiler._ def checkForwarder(classes: Map[String, ClassNode], clsName: Symbol, target: String) = { - val List(f) = getMethods(classes(clsName.name), "f") - assertSameCode(f, List(VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, target, "f", "()I", false), Op(IRETURN))) + val f = getMethod(classes(clsName.name), "f") + assertSameCode(f, List(VarOp(ALOAD, 0), Invoke(INVOKESTATIC, target, "f", s"(L$target;)I", false), Op(IRETURN))) } @Test @@ -141,7 +142,7 @@ class BytecodeTest extends BytecodeTesting { def invocationReceivers(): Unit = { val List(c1, c2, t, u) = compileClasses(invocationReceiversTestCode.definitions("Object")) // mixin forwarder in C1 - assertSameCode(getMethod(c1, "clone"), List(VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, "T", "clone", "()Ljava/lang/Object;", false), Op(ARETURN))) + assertSameCode(getMethod(c1, "clone"), List(VarOp(ALOAD, 0), Invoke(INVOKESTATIC, "T", "clone", "(LT;)Ljava/lang/Object;", false), Op(ARETURN))) assertInvoke(getMethod(c1, "f1"), "T", "clone") assertInvoke(getMethod(c1, "f2"), "T", "clone") assertInvoke(getMethod(c1, "f3"), "C1", "clone") @@ -151,7 +152,7 @@ class BytecodeTest extends BytecodeTesting { val List(c1b, c2b, tb, ub) = compileClasses(invocationReceiversTestCode.definitions("String")) def ms(c: ClassNode, n: String) = c.methods.asScala.toList.filter(_.name == n) - assert(ms(tb, "clone").length == 1) + assert(ms(tb, "clone").filterNot(BytecodeUtils.isStaticMethod).length == 1) assert(ms(ub, "clone").isEmpty) val List(c1Clone) = ms(c1b, "clone") assertEquals(c1Clone.desc, "()Ljava/lang/Object;") diff --git a/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala index c9a958ee4f4d..841e850b491b 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala @@ -5,6 +5,7 @@ import org.junit.Test import scala.collection.JavaConverters import scala.collection.JavaConverters._ +import scala.reflect.internal.Flags import scala.tools.asm.Opcodes import scala.tools.asm.tree.ClassNode import scala.tools.testing.BytecodeTesting @@ -21,7 +22,7 @@ class DefaultMethodTest extends BytecodeTesting { /** Transforms a single tree. */ override def transform(tree: global.Tree): global.Tree = tree match { case dd @ DefDef(_, Foo, _, _, _, _) => - dd.symbol.setFlag(reflect.internal.Flags.JAVA_DEFAULTMETHOD) + dd.symbol.setFlag(Flags.JAVA_DEFAULTMETHOD).resetFlag(Flags.DEFERRED) copyDefDef(dd)(rhs = Literal(Constant(1)).setType(definitions.IntTpe)) case _ => super.transform(tree) } @@ -31,6 +32,4 @@ class DefaultMethodTest extends BytecodeTesting { assertTrue("default method should not be abstract", (foo.access & Opcodes.ACC_ABSTRACT) == 0) assertTrue("default method body emitted", foo.instructions.size() > 0) } - - } diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala index a28599cd9212..cc1d4a361444 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala @@ -1,7 +1,9 @@ package scala.tools.nsc.backend.jvm +import java.nio.file.{Files, Paths} + import org.junit.Assert._ -import org.junit.Test +import org.junit.{Ignore, Test} import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -62,7 +64,7 @@ class DirectCompileTest extends BytecodeTesting { VarOp(ILOAD, 1), Op(ICONST_0), Jump(IF_ICMPNE, - Label(7)), + Label(7)), Ldc(LDC, "a"), Op(ARETURN), Label(7), diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala index fdc17ec73f80..4ffbfa79b37a 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -475,11 +475,9 @@ class InlinerTest extends BytecodeTesting { | def t2 = this.f |} """.stripMargin - val warns = Set( - "C::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", - "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden") + val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden" var count = 0 - val List(c, t) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)}) + val List(c, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn}) assert(count == 2, count) assertInvoke(getMethod(c, "t1"), "T", "f") assertInvoke(getMethod(c, "t2"), "C", "f") @@ -1526,4 +1524,19 @@ class InlinerTest extends BytecodeTesting { val c :: _ = compileClassesSeparately(codes, extraArgs = compilerArgs) assertInvoke(getMethod(c, "t"), "p1/Implicits$RichFunction1$", "toRx$extension") } + + @Test + def traitHO(): Unit = { + val code = + """trait T { + | def foreach(f: Int => Unit): Unit = f(1) + |} + |final class C extends T { + | def cons(x: Int): Unit = () + | def t1 = foreach(cons) + |} + """.stripMargin + val List(c, t) = compile(code) + assertNoIndy(getMethod(c, "t1")) + } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala index 4791a29bfbb7..fb49ffbce0ff 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala @@ -31,6 +31,14 @@ class ScalaInlineInfoTest extends BytecodeTesting { r.toString } + def assertSameMethods(c: ClassNode, nameAndSigs: Set[String]): Unit = { + val r = new StringBuilder + val inClass = c.methods.iterator.asScala.map(m => m.name + m.desc).toSet + for (m <- inClass.diff(nameAndSigs)) r.append(s"method in classfile found, but no inline info: $m") + for (m <- nameAndSigs.diff(inClass)) r.append(s"inline info found, but no method in classfile: $m") + assert(r.isEmpty, r.toString) + } + @Test def traitMembersInlineInfo(): Unit = { val code = @@ -79,26 +87,32 @@ class ScalaInlineInfoTest extends BytecodeTesting { ("T$$super$toString()Ljava/lang/String;", MethodInlineInfo(true ,false,false)), ("T$_setter_$x1_$eq(I)V", MethodInlineInfo(false,false,false)), ("f1()I", MethodInlineInfo(false,false,false)), - ("f2()I", MethodInlineInfo(true, false,false)), + ("f1(LT;)I", MethodInlineInfo(true ,false,false)), + ("f2()I", MethodInlineInfo(true ,false,false)), // no static impl method for private method f2 ("f3()I", MethodInlineInfo(false,false,false)), + ("f3(LT;)I", MethodInlineInfo(true ,false,false)), ("f4()Ljava/lang/String;", MethodInlineInfo(false,true, false)), + ("f4(LT;)Ljava/lang/String;", MethodInlineInfo(true ,true, false)), ("f5()I", MethodInlineInfo(true ,false,false)), - ("f6()I", MethodInlineInfo(false,false,true )), + ("f5(LT;)I", MethodInlineInfo(true ,false,false)), + ("f6()I", MethodInlineInfo(false,false,true )), // no static impl method for abstract method f6 ("x1()I", MethodInlineInfo(false,false,false)), ("y2()I", MethodInlineInfo(false,false,false)), ("y2_$eq(I)V", MethodInlineInfo(false,false,false)), ("x3()I", MethodInlineInfo(false,false,false)), ("x3_$eq(I)V", MethodInlineInfo(false,false,false)), ("x4()I", MethodInlineInfo(false,false,false)), + ("x4(LT;)I", MethodInlineInfo(true ,false,false)), ("x5()I", MethodInlineInfo(true, false,false)), ("L$lzycompute$1(Lscala/runtime/VolatileObjectRef;)LT$L$2$;", MethodInlineInfo(true, false,false)), ("L$1(Lscala/runtime/VolatileObjectRef;)LT$L$2$;", MethodInlineInfo(true ,false,false)), ("nest$1()I", MethodInlineInfo(true, false,false)), - ("$init$()V", MethodInlineInfo(false,false,false))), + ("$init$(LT;)V", MethodInlineInfo(true,false,false))), None // warning ) assert(infoT == expectT, mapDiff(expectT.methodInfos, infoT.methodInfos) + infoT) + assertSameMethods(t, expectT.methodInfos.keySet) val infoC = inlineInfo(c) val expectC = InlineInfo(false, None, Map( @@ -119,6 +133,7 @@ class ScalaInlineInfoTest extends BytecodeTesting { None) assert(infoC == expectC, mapDiff(expectC.methodInfos, infoC.methodInfos) + infoC) + assertSameMethods(c, expectC.methodInfos.keySet) } @Test @@ -156,7 +171,6 @@ class ScalaInlineInfoTest extends BytecodeTesting { ("F",None), ("T",Some("h(Ljava/lang/String;)I")), ("U",None))) - } @Test @@ -169,5 +183,6 @@ class ScalaInlineInfoTest extends BytecodeTesting { "O$lzycompute()LC$O$;" -> MethodInlineInfo(true,false,false), "O()LC$O$;" -> MethodInlineInfo(true,false,false)) assert(infoC.methodInfos == expected, mapDiff(infoC.methodInfos, expected)) + assertSameMethods(c, expected.keySet) } } diff --git a/test/junit/scala/tools/testing/BytecodeTesting.scala b/test/junit/scala/tools/testing/BytecodeTesting.scala index 1a0c1e210a48..755466d52a9f 100644 --- a/test/junit/scala/tools/testing/BytecodeTesting.scala +++ b/test/junit/scala/tools/testing/BytecodeTesting.scala @@ -12,6 +12,7 @@ import scala.tools.asm.tree.{AbstractInsnNode, ClassNode, MethodNode} import scala.tools.cmd.CommandLineParser import scala.tools.nsc.backend.jvm.AsmUtils import scala.tools.nsc.backend.jvm.AsmUtils._ +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.reporters.StoreReporter import scala.tools.nsc.{Global, Settings} @@ -247,11 +248,19 @@ object BytecodeTesting { def getAsmMethod(c: ClassNode, name: String): MethodNode = { val methods = getAsmMethods(c, name) + def fail() = { + val allNames = getAsmMethods(c, _ => true).map(_.name) + throw new AssertionFailedError(s"Could not find method named $name among ${allNames}") + } methods match { case List(m) => m - case ms => - val allNames = getAsmMethods(c, _ => true).map(_.name) - throw new AssertionFailedError(s"Could not find method named $name among ${allNames}") + case ms @ List(m1, m2) if BytecodeUtils.isInterface(c) => + val (statics, nonStatics) = ms.partition(BytecodeUtils.isStaticMethod) + (statics, nonStatics) match { + case (List(staticMethod), List(_)) => m1 // prefer the static method of the pair if methods in traits + case _ => fail() + } + case ms => fail() } }