Skip to content

Commit

Permalink
[backport] Integrate the LMFInvokeDynamic extractor into LambdaMetaFa…
Browse files Browse the repository at this point in the history
…ctoryCall
  • Loading branch information
adriaanm authored and lrytz committed Jul 23, 2015
1 parent fc1cda2 commit 41b99e2
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ object BytecodeUtils {

def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STRICT) != 0

def isReference(t: Type) = t.getSort == Type.OBJECT || t.getSort == Type.ARRAY

def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = {
var result = instruction
do { result = result.getNext }
Expand Down
115 changes: 53 additions & 62 deletions src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
callsitePosition = callsitePositions.getOrElse(call, NoPosition)
)

case LMFInvokeDynamic(lmf) =>
closureInstantiations += lmf
case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) =>
closureInstantiations += LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType)

case _ =>
}
Expand Down Expand Up @@ -242,7 +242,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
}
final case class LambdaMetaFactoryCall(indy: InvokeDynamicInsnNode, samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type)

object LMFInvokeDynamic {
object LambdaMetaFactoryCall {
private val lambdaMetaFactoryInternalName: InternalName = "java/lang/invoke/LambdaMetafactory"

private val metafactoryHandle = {
Expand All @@ -257,69 +257,60 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
new Handle(Opcodes.H_INVOKESTATIC, lambdaMetaFactoryInternalName, altMetafactoryMethodName, altMetafactoryDesc)
}

private def extractLambdaMetaFactoryCall(indy: InvokeDynamicInsnNode) = {
if (indy.bsm == metafactoryHandle || indy.bsm == altMetafactoryHandle) indy.bsmArgs match {
case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, xs@_*) =>
// LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda
// implementation method (casting, boxing, unboxing, and primitive widening, see Javadoc).
//
// The closure optimizer supports only one of those adaptations: it will cast arguments
// to the correct type when re-writing a closure call to the body method. Example:
//
// val fun: String => String = l => l
// val l = List("")
// fun(l.head)
//
// The samMethodType of Function1 is `(Object)Object`, while the instantiatedMethodType
// is `(String)String`. The return type of `List.head` is `Object`.
//
// The implMethod has the signature `C$anonfun(String)String`.
//
// At the closure callsite, we have an `INVOKEINTERFACE Function1.apply (Object)Object`,
// so the object returned by `List.head` can be directly passed into the call (no cast).
//
// The closure object will cast the object to String before passing it to the implMethod.
//
// When re-writing the closure callsite to the implMethod, we have to insert a cast.
//
// The check below ensures that
// (1) the implMethod type has the expected singature (captured types plus argument types
// from instantiatedMethodType)
// (2) the receiver of the implMethod matches the first captured type
// (3) all parameters that are not the same in samMethodType and instantiatedMethodType
// are reference types, so that we can insert casts to perform the same adaptation
// that the closure object would.

val isStatic = implMethod.getTag == Opcodes.H_INVOKESTATIC
val indyParamTypes = Type.getArgumentTypes(indy.desc)
val instantiatedMethodArgTypes = instantiatedMethodType.getArgumentTypes
val expectedImplMethodType = {
val paramTypes = (if (isStatic) indyParamTypes else indyParamTypes.tail) ++ instantiatedMethodArgTypes
Type.getMethodType(instantiatedMethodType.getReturnType, paramTypes: _*)
}

val isIndyLambda = {
Type.getType(implMethod.getDesc) == expectedImplMethodType // (1)
} && {
isStatic || implMethod.getOwner == indyParamTypes(0).getInternalName // (2)
} && {
def isReference(t: Type) = t.getSort == Type.OBJECT || t.getSort == Type.ARRAY
(samMethodType.getArgumentTypes, instantiatedMethodArgTypes).zipped forall {
case (samArgType, instArgType) =>
samArgType == instArgType || isReference(samArgType) && isReference(instArgType) // (3)
def unapply(insn: AbstractInsnNode): Option[(InvokeDynamicInsnNode, Type, Handle, Type)] = insn match {
case indy: InvokeDynamicInsnNode if indy.bsm == metafactoryHandle || indy.bsm == altMetafactoryHandle =>
indy.bsmArgs match {
case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, xs@_*) => // xs binding because IntelliJ gets confused about _@_*
// LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda
// implementation method (casting, boxing, unboxing, and primitive widening, see Javadoc).
//
// The closure optimizer supports only one of those adaptations: it will cast arguments
// to the correct type when re-writing a closure call to the body method. Example:
//
// val fun: String => String = l => l
// val l = List("")
// fun(l.head)
//
// The samMethodType of Function1 is `(Object)Object`, while the instantiatedMethodType
// is `(String)String`. The return type of `List.head` is `Object`.
//
// The implMethod has the signature `C$anonfun(String)String`.
//
// At the closure callsite, we have an `INVOKEINTERFACE Function1.apply (Object)Object`,
// so the object returned by `List.head` can be directly passed into the call (no cast).
//
// The closure object will cast the object to String before passing it to the implMethod.
//
// When re-writing the closure callsite to the implMethod, we have to insert a cast.
//
// The check below ensures that
// (1) the implMethod type has the expected singature (captured types plus argument types
// from instantiatedMethodType)
// (2) the receiver of the implMethod matches the first captured type
// (3) all parameters that are not the same in samMethodType and instantiatedMethodType
// are reference types, so that we can insert casts to perform the same adaptation
// that the closure object would.

val isStatic = implMethod.getTag == Opcodes.H_INVOKESTATIC
val indyParamTypes = Type.getArgumentTypes(indy.desc)
val instantiatedMethodArgTypes = instantiatedMethodType.getArgumentTypes
val expectedImplMethodType = {
val paramTypes = (if (isStatic) indyParamTypes else indyParamTypes.tail) ++ instantiatedMethodArgTypes
Type.getMethodType(instantiatedMethodType.getReturnType, paramTypes: _*)
}
}

if (isIndyLambda) Some(LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType))
else None
val isIndyLambda = (
Type.getType(implMethod.getDesc) == expectedImplMethodType // (1)
&& (isStatic || implMethod.getOwner == indyParamTypes(0).getInternalName) // (2)
&& samMethodType.getArgumentTypes.corresponds(instantiatedMethodArgTypes)((samArgType, instArgType) =>
samArgType == instArgType || isReference(samArgType) && isReference(instArgType)) // (3)
)

case _ => None
}
else None
}
if (isIndyLambda) Some((indy, samMethodType, implMethod, instantiatedMethodType))
else None

def unapply(insn: AbstractInsnNode): Option[LambdaMetaFactoryCall] = insn match {
case indy: InvokeDynamicInsnNode => extractLambdaMetaFactoryCall(indy)
case _ => None
}
case _ => None
}
}
Expand Down
17 changes: 9 additions & 8 deletions src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,12 @@ class Inliner[BT <: BTypes](val btypes: BT) {
}
}

case LMFInvokeDynamic(lmf) =>
case _: InvokeDynamicInsnNode if destinationClass == calleeDeclarationClass =>
// within the same class, any indy instruction can be inlined
Right(true)

// does the InvokeDynamicInsnNode call LambdaMetaFactory?
case LambdaMetaFactoryCall(_, _, implMethod, _) =>
// an indy instr points to a "call site specifier" (CSP) [1]
// - a reference to a bootstrap method [2]
// - bootstrap method name
Expand Down Expand Up @@ -734,20 +739,16 @@ class Inliner[BT <: BTypes](val btypes: BT) {
// the implMethod is public, lambdaMetaFactory doesn't use the Lookup object's extended
// capability, and we can safely inline the instruction into a different class.

val methodRefClass = classBTypeFromParsedClassfile(lmf.implMethod.getOwner)
val methodRefClass = classBTypeFromParsedClassfile(implMethod.getOwner)
for {
(methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, lmf.implMethod.getName, lmf.implMethod.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)]
(methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, implMethod.getName, implMethod.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)]
methodDeclClass = classBTypeFromParsedClassfile(methodDeclClassNode)
res <- memberIsAccessible(methodNode.access, methodDeclClass, methodRefClass, destinationClass)
} yield {
res
}

case indy: InvokeDynamicInsnNode =>
if (destinationClass == calleeDeclarationClass)
Right(true) // within the same class, any indy instruction can be inlined
else
Left(UnknownInvokeDynamicInstruction)
case _: InvokeDynamicInsnNode => Left(UnknownInvokeDynamicInstruction)

case ci: LdcInsnNode => ci.cst match {
case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName), destinationClass)
Expand Down

0 comments on commit 41b99e2

Please sign in to comment.