Skip to content

Commit

Permalink
don't treat invokespecial as statically resolved
Browse files Browse the repository at this point in the history
  • Loading branch information
lrytz committed Sep 14, 2018
1 parent e99a5b4 commit 462b878
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 15 deletions.
10 changes: 5 additions & 5 deletions src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 13 additions & 8 deletions src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)) =>
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 462b878

Please sign in to comment.