From 2b84d8565b3435fd8585f7b7bc0120e1265f2f89 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 29 Jun 2018 13:52:08 +0200 Subject: [PATCH] don't treat invokespecial as statically resolved https://github.com/scala/scala-dev/issues/143 --- .../nsc/backend/jvm/opt/BytecodeUtils.scala | 10 ++++----- .../tools/nsc/backend/jvm/opt/CallGraph.scala | 21 ++++++++++++------- .../backend/jvm/opt/InlinerHeuristics.scala | 4 ++-- 3 files changed, 20 insertions(+), 15 deletions(-) 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 57846847d2ed..b1f5ff924525 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -87,18 +87,18 @@ object BytecodeUtils { def isLoadOrStore(instruction: AbstractInsnNode): Boolean = isLoad(instruction) || isStore(instruction) - def isNonVirtualCall(instruction: AbstractInsnNode): Boolean = { - val op = instruction.getOpcode - op == INVOKESPECIAL || op == INVOKESTATIC + def isStaticCall(instruction: AbstractInsnNode): Boolean = { + instruction.getOpcode == INVOKESTATIC } def isVirtualCall(instruction: AbstractInsnNode): Boolean = { val op = instruction.getOpcode - op == INVOKEVIRTUAL || op == INVOKEINTERFACE + // invokespecial + op == INVOKESPECIAL || op == INVOKEVIRTUAL || op == INVOKEINTERFACE } def isCall(instruction: AbstractInsnNode): Boolean = { - isNonVirtualCall(instruction) || isVirtualCall(instruction) + isStaticCall(instruction) || isVirtualCall(instruction) } def isExecutable(instruction: AbstractInsnNode): Boolean = instruction.getOpcode >= 0 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 dc37e9082404..34e40ccb318d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -156,7 +156,7 @@ abstract class CallGraph { (declarationClassNode, calleeSourceFilePath) <- byteCodeRepository.classNodeAndSourceFilePath(declarationClass): Either[OptimizerWarning, (ClassNode, Option[String])] } yield { val declarationClassBType = classBTypeFromClassNode(declarationClassNode) - val info = analyzeCallsite(method, declarationClassBType, call, calleeSourceFilePath) + val info = analyzeCallsite(method, declarationClassBType, call, calleeSourceFilePath, definingClass) import info._ Callee( callee = method, @@ -288,7 +288,7 @@ abstract class CallGraph { /** * Analyze a callsite and gather meta-data that can be used for inlining decisions. */ - private def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, call: MethodInsnNode, calleeSourceFilePath: Option[String]): CallsiteInfo = { + private def analyzeCallsite(calleeMethodNode: MethodNode, calleeDeclarationClassBType: ClassBType, call: MethodInsnNode, calleeSourceFilePath: Option[String], callsiteClass: ClassBType): CallsiteInfo = { val methodSignature = calleeMethodNode.name + calleeMethodNode.desc try { @@ -297,12 +297,16 @@ abstract class CallGraph { // callee, we only check there for the methodInlineInfo, we should find it there. calleeDeclarationClassBType.info.orThrow.inlineInfo.methodInfos.get(methodSignature) match { case Some(methodInlineInfo) => - val receiverType = classBTypeFromParsedClassfile(call.owner) - // (1) A non-final method can be safe to inline if the receiver type is a final subclass. Example: + // (1) Special case for trait super accessors. trait T { def f = 1 } generates a static + // method t$ which calls `invokespecial T.f`. Even if `f` is not final, this call will + // always resolve to `T.f`. This is a (very) special case. Otherwise, `invokespecial` + // is only used for private methods, constructors and super calls. + // + // (2) A non-final method can be safe to inline if the receiver type is a final subclass. Example: // class A { @inline def f = 1 }; object B extends A; B.f // can be inlined // - // TODO: (1) doesn't cover the following example: + // TODO: (2) doesn't cover the following example: // trait TravLike { def map = ... } // sealed trait List extends TravLike { ... } // assume map is not overridden // final case class :: / final case object Nil @@ -316,9 +320,10 @@ abstract class CallGraph { // TODO: type analysis can render more calls statically resolved. Example: // new A.f // can be inlined, the receiver type is known to be exactly A. val isStaticallyResolved: Boolean = { - isNonVirtualCall(call) || // scala/scala-dev#86: super calls (invokespecial) can be inlined -- TODO: check if that's still needed, and if it's correct: scala-dev#143 - methodInlineInfo.effectivelyFinal || - receiverType.info.orThrow.inlineInfo.isEffectivelyFinal // (1) + isStaticCall(call) || + (call.getOpcode == Opcodes.INVOKESPECIAL && receiverType == callsiteClass) || // (1) + methodInlineInfo.effectivelyFinal || + receiverType.info.orThrow.inlineInfo.isEffectivelyFinal // (2) } val warning = calleeDeclarationClassBType.info.orThrow.inlineInfo.warning.map( 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 1e007e529e30..e3d94947ecc1 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala @@ -57,7 +57,7 @@ abstract class InlinerHeuristics extends PerRunInit { var requests = Set.empty[InlineRequest] callGraph.callsites(methodNode).valuesIterator foreach { case callsite @ Callsite(_, _, _, Right(Callee(callee, _, _, _, _, _, _, callsiteWarning)), _, _, _, pos, _, _) => - inlineRequest(callsite, requests) match { + inlineRequest(callsite) match { case Some(Right(req)) => requests += req case Some(Left(w)) => @@ -134,7 +134,7 @@ abstract class InlinerHeuristics extends PerRunInit { * InlineRequest for the original callsite? new subclass of OptimizerWarning. * `Some(Right)` if the callsite should be and can be inlined */ - def inlineRequest(callsite: Callsite, selectedRequestsForCallee: Set[InlineRequest]): Option[Either[OptimizerWarning, InlineRequest]] = { + def inlineRequest(callsite: Callsite): Option[Either[OptimizerWarning, InlineRequest]] = { def requestIfCanInline(callsite: Callsite, reason: String): Option[Either[OptimizerWarning, InlineRequest]] = { val callee = callsite.callee.get if (!callee.safeToInline) {