summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala7
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala65
-rw-r--r--src/partest-extras/scala/tools/partest/ASMConverters.scala78
-rw-r--r--test/files/presentation/t7678/Runner.scala3
-rw-r--r--test/files/run/t8029.scala3
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala37
6 files changed, 153 insertions, 40 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
index 4fc05cafdc..4eb24d13e3 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala
@@ -1,7 +1,7 @@
package scala.tools.nsc
package backend.jvm
-import scala.tools.asm.tree.{AbstractInsnNode, MethodNode}
+import scala.tools.asm.tree.{InvokeDynamicInsnNode, AbstractInsnNode, MethodNode}
import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.reflect.internal.util.Position
import scala.tools.nsc.settings.ScalaSettings
@@ -246,6 +246,11 @@ object BackendReporting {
case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String,
callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
+ case object UnknownInvokeDynamicInstruction extends OptimizerWarning {
+ override def toString = "The callee contains an InvokeDynamic instruction with an unknown bootstrap method (not a LambdaMetaFactory)."
+ def emitWarning(settings: ScalaSettings): Boolean = settings.YoptWarningEmitAtInlineFailed
+ }
+
/**
* Used in `rewriteClosureApplyInvocations` when a closure apply callsite cannot be rewritten
* to the closure body method.
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 e8e848161c..4a022b7bc4 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
@@ -9,6 +9,7 @@ package opt
import scala.annotation.tailrec
import scala.tools.asm
+import asm.Handle
import asm.Opcodes._
import asm.tree._
import scala.collection.convert.decorateAsScala._
@@ -687,9 +688,67 @@ class Inliner[BT <: BTypes](val btypes: BT) {
}
}
- case ivd: InvokeDynamicInsnNode =>
- // TODO @lry check necessary conditions to inline an indy, instead of giving up
- Right(false)
+ case indy: InvokeDynamicInsnNode =>
+ // an indy instr points to a "call site specifier" (CSP) [1]
+ // - a reference to a bootstrap method [2]
+ // - bootstrap method name
+ // - references to constant arguments, which can be:
+ // - constant (string, long, int, float, double)
+ // - class
+ // - method type (without name)
+ // - method handle
+ // - a method name+type
+ //
+ // execution [3]
+ // - resolve the CSP, yielding the boostrap method handle, the static args and the name+type
+ // - resolution entails accessibility checking [4]
+ // - execute the `invoke` method of the boostrap method handle (which is signature polymorphic, check its javadoc)
+ // - the descriptor for the call is made up from the actual arguments on the stack:
+ // - the first parameters are "MethodHandles.Lookup, String, MethodType", then the types of the constant arguments,
+ // - the return type is CallSite
+ // - the values for the call are
+ // - the bootstrap method handle of the CSP is the receiver
+ // - the Lookup object for the class in which the callsite occurs (obtained as through calling MethodHandles.lookup())
+ // - the method name of the CSP
+ // - the method type of the CSP
+ // - the constants of the CSP (primitives are not boxed)
+ // - the resulting `CallSite` object
+ // - has as `type` the method type of the CSP
+ // - is popped from the operand stack
+ // - the `invokeExact` method (signature polymorphic!) of the `target` method handle of the CallSite is invoked
+ // - the method descriptor is that of the CSP
+ // - the receiver is the target of the CallSite
+ // - the other argument values are those that were on the operand stack at the indy instruction (indyLambda: the captured values)
+ //
+ // [1] http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.10
+ // [2] http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.23
+ // [3] http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokedynamic
+ // [4] http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3
+
+ // We cannot generically check if an `invokedynamic` instruction can be safely inlined into
+ // a different class, that depends on the bootstrap method. The Lookup object passed to the
+ // bootstrap method is a capability to access private members of the callsite class. We can
+ // only move the invokedynamic to a new class if we know that the bootstrap method doesn't
+ // use this capability for otherwise non-accessible members.
+ // In the case of indyLambda, it depends on the visibility of the implMethod handle. If
+ // 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.
+
+ if (destinationClass == calleeDeclarationClass) {
+ Right(true) // within the same class, any indy instruction can be inlined
+ } else if (closureOptimizer.isClosureInstantiation(indy)) {
+ val implMethod = indy.bsmArgs(1).asInstanceOf[Handle] // safe, checked in isClosureInstantiation
+ val methodRefClass = classBTypeFromParsedClassfile(implMethod.getOwner)
+ for {
+ (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
+ }
+ } else {
+ Left(UnknownInvokeDynamicInstruction)
+ }
case ci: LdcInsnNode => ci.cst match {
case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName), destinationClass)
diff --git a/src/partest-extras/scala/tools/partest/ASMConverters.scala b/src/partest-extras/scala/tools/partest/ASMConverters.scala
index f6e2d2a9ec..e8d327d352 100644
--- a/src/partest-extras/scala/tools/partest/ASMConverters.scala
+++ b/src/partest-extras/scala/tools/partest/ASMConverters.scala
@@ -58,21 +58,24 @@ object ASMConverters {
case class Method(instructions: List[Instruction], handlers: List[ExceptionHandler], localVars: List[LocalVariable])
- case class Field (opcode: Int, owner: String, name: String, desc: String) extends Instruction
- case class Incr (opcode: Int, `var`: Int, incr: Int) extends Instruction
- case class Op (opcode: Int) extends Instruction
- case class IntOp (opcode: Int, operand: Int) extends Instruction
- case class Jump (opcode: Int, label: Label) extends Instruction
- case class Ldc (opcode: Int, cst: Any) extends Instruction
- case class LookupSwitch(opcode: Int, dflt: Label, keys: List[Int], labels: List[Label]) extends Instruction
- case class TableSwitch (opcode: Int, min: Int, max: Int, dflt: Label, labels: List[Label]) extends Instruction
- case class Invoke (opcode: Int, owner: String, name: String, desc: String, itf: Boolean) extends Instruction
- case class NewArray (opcode: Int, desc: String, dims: Int) extends Instruction
- case class TypeOp (opcode: Int, desc: String) extends Instruction
- case class VarOp (opcode: Int, `var`: Int) extends Instruction
- case class Label (offset: Int) extends Instruction { def opcode: Int = -1 }
- case class FrameEntry (`type`: Int, local: List[Any], stack: List[Any]) extends Instruction { def opcode: Int = -1 }
- case class LineNumber (line: Int, start: Label) extends Instruction { def opcode: Int = -1 }
+ case class Field (opcode: Int, owner: String, name: String, desc: String) extends Instruction
+ case class Incr (opcode: Int, `var`: Int, incr: Int) extends Instruction
+ case class Op (opcode: Int) extends Instruction
+ case class IntOp (opcode: Int, operand: Int) extends Instruction
+ case class Jump (opcode: Int, label: Label) extends Instruction
+ case class Ldc (opcode: Int, cst: Any) extends Instruction
+ case class LookupSwitch (opcode: Int, dflt: Label, keys: List[Int], labels: List[Label]) extends Instruction
+ case class TableSwitch (opcode: Int, min: Int, max: Int, dflt: Label, labels: List[Label]) extends Instruction
+ case class Invoke (opcode: Int, owner: String, name: String, desc: String, itf: Boolean) extends Instruction
+ case class InvokeDynamic(opcode: Int, name: String, desc: String, bsm: MethodHandle, bsmArgs: List[AnyRef]) extends Instruction
+ case class NewArray (opcode: Int, desc: String, dims: Int) extends Instruction
+ case class TypeOp (opcode: Int, desc: String) extends Instruction
+ case class VarOp (opcode: Int, `var`: Int) extends Instruction
+ case class Label (offset: Int) extends Instruction { def opcode: Int = -1 }
+ case class FrameEntry (`type`: Int, local: List[Any], stack: List[Any]) extends Instruction { def opcode: Int = -1 }
+ case class LineNumber (line: Int, start: Label) extends Instruction { def opcode: Int = -1 }
+
+ case class MethodHandle(tag: Int, owner: String, name: String, desc: String)
case class ExceptionHandler(start: Label, end: Label, handler: Label, desc: Option[String])
case class LocalVariable(name: String, desc: String, signature: Option[String], start: Label, end: Label, index: Int)
@@ -111,6 +114,7 @@ object ASMConverters {
case i: t.LookupSwitchInsnNode => LookupSwitch (op(i), applyLabel(i.dflt), lst(i.keys) map (x => x: Int), lst(i.labels) map applyLabel)
case i: t.TableSwitchInsnNode => TableSwitch (op(i), i.min, i.max, applyLabel(i.dflt), lst(i.labels) map applyLabel)
case i: t.MethodInsnNode => Invoke (op(i), i.owner, i.name, i.desc, i.itf)
+ case i: t.InvokeDynamicInsnNode => InvokeDynamic(op(i), i.name, i.desc, convertMethodHandle(i.bsm), convertBsmArgs(i.bsmArgs))
case i: t.MultiANewArrayInsnNode => NewArray (op(i), i.desc, i.dims)
case i: t.TypeInsnNode => TypeOp (op(i), i.desc)
case i: t.VarInsnNode => VarOp (op(i), i.`var`)
@@ -119,6 +123,13 @@ object ASMConverters {
case i: t.LineNumberNode => LineNumber (i.line, applyLabel(i.start))
}
+ private def convertBsmArgs(a: Array[Object]): List[Object] = a.map({
+ case h: asm.Handle => convertMethodHandle(h)
+ case _ => a // can be: Class, method Type, primitive constant
+ })(collection.breakOut)
+
+ private def convertMethodHandle(h: asm.Handle): MethodHandle = MethodHandle(h.getTag, h.getOwner, h.getName, h.getDesc)
+
private def convertHandlers(method: t.MethodNode): List[ExceptionHandler] = {
method.tryCatchBlocks.asScala.map(h => ExceptionHandler(applyLabel(h.start), applyLabel(h.end), applyLabel(h.handler), Option(h.`type`)))(collection.breakOut)
}
@@ -197,21 +208,28 @@ object ASMConverters {
case x => x.asInstanceOf[Object]
}
+ def unconvertMethodHandle(h: MethodHandle): asm.Handle = new asm.Handle(h.tag, h.owner, h.name, h.desc)
+ def unconvertBsmArgs(a: List[Object]): Array[Object] = a.map({
+ case h: MethodHandle => unconvertMethodHandle(h)
+ case o => o
+ })(collection.breakOut)
+
private def visitMethod(method: t.MethodNode, instruction: Instruction, asmLabel: Map[Label, asm.Label]): Unit = instruction match {
- case Field(op, owner, name, desc) => method.visitFieldInsn(op, owner, name, desc)
- case Incr(op, vr, incr) => method.visitIincInsn(vr, incr)
- case Op(op) => method.visitInsn(op)
- case IntOp(op, operand) => method.visitIntInsn(op, operand)
- case Jump(op, label) => method.visitJumpInsn(op, asmLabel(label))
- case Ldc(op, cst) => method.visitLdcInsn(cst)
- case LookupSwitch(op, dflt, keys, labels) => method.visitLookupSwitchInsn(asmLabel(dflt), keys.toArray, (labels map asmLabel).toArray)
- case TableSwitch(op, min, max, dflt, labels) => method.visitTableSwitchInsn(min, max, asmLabel(dflt), (labels map asmLabel).toArray: _*)
- case Invoke(op, owner, name, desc, itf) => method.visitMethodInsn(op, owner, name, desc, itf)
- case NewArray(op, desc, dims) => method.visitMultiANewArrayInsn(desc, dims)
- case TypeOp(op, desc) => method.visitTypeInsn(op, desc)
- case VarOp(op, vr) => method.visitVarInsn(op, vr)
- case l: Label => method.visitLabel(asmLabel(l))
- case FrameEntry(tp, local, stack) => method.visitFrame(tp, local.length, frameTypesToAsm(local, asmLabel).toArray, stack.length, frameTypesToAsm(stack, asmLabel).toArray)
- case LineNumber(line, start) => method.visitLineNumber(line, asmLabel(start))
+ case Field(op, owner, name, desc) => method.visitFieldInsn(op, owner, name, desc)
+ case Incr(op, vr, incr) => method.visitIincInsn(vr, incr)
+ case Op(op) => method.visitInsn(op)
+ case IntOp(op, operand) => method.visitIntInsn(op, operand)
+ case Jump(op, label) => method.visitJumpInsn(op, asmLabel(label))
+ case Ldc(op, cst) => method.visitLdcInsn(cst)
+ case LookupSwitch(op, dflt, keys, labels) => method.visitLookupSwitchInsn(asmLabel(dflt), keys.toArray, (labels map asmLabel).toArray)
+ case TableSwitch(op, min, max, dflt, labels) => method.visitTableSwitchInsn(min, max, asmLabel(dflt), (labels map asmLabel).toArray: _*)
+ case Invoke(op, owner, name, desc, itf) => method.visitMethodInsn(op, owner, name, desc, itf)
+ case InvokeDynamic(op, name, desc, bsm, bsmArgs) => method.visitInvokeDynamicInsn(name, desc, unconvertMethodHandle(bsm), unconvertBsmArgs(bsmArgs))
+ case NewArray(op, desc, dims) => method.visitMultiANewArrayInsn(desc, dims)
+ case TypeOp(op, desc) => method.visitTypeInsn(op, desc)
+ case VarOp(op, vr) => method.visitVarInsn(op, vr)
+ case l: Label => method.visitLabel(asmLabel(l))
+ case FrameEntry(tp, local, stack) => method.visitFrame(tp, local.length, frameTypesToAsm(local, asmLabel).toArray, stack.length, frameTypesToAsm(stack, asmLabel).toArray)
+ case LineNumber(line, start) => method.visitLineNumber(line, asmLabel(start))
}
}
diff --git a/test/files/presentation/t7678/Runner.scala b/test/files/presentation/t7678/Runner.scala
index e45f057ff1..14d6dc2a70 100644
--- a/test/files/presentation/t7678/Runner.scala
+++ b/test/files/presentation/t7678/Runner.scala
@@ -1,6 +1,3 @@
-/*
- * filter: inliner warnings; re-run with
- */
import scala.tools.nsc.interactive.tests._
import scala.reflect.internal.util._
diff --git a/test/files/run/t8029.scala b/test/files/run/t8029.scala
index 62629d51bc..dbd5c41387 100644
--- a/test/files/run/t8029.scala
+++ b/test/files/run/t8029.scala
@@ -1,6 +1,3 @@
-/*
- * filter: inliner warning; re-run with
- */
import scala.tools.partest._
import scala.tools.nsc._
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 0309bb97cc..617eced560 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
@@ -991,4 +991,41 @@ class InlinerTest extends ClearAfterClass {
assert(2 == t.collect({case Ldc(_, "hai!") => }).size) // twice the body of f
assert(1 == t.collect({case Jump(IFNONNULL, _) => }).size) // one single null check
}
+
+ @Test
+ def inlineIndyLambda(): Unit = {
+ val code =
+ """object M {
+ | @inline def m(s: String) = {
+ | val f = (x: String) => x.trim
+ | f(s)
+ | }
+ |}
+ |class C {
+ | @inline final def m(s: String) = {
+ | val f = (x: String) => x.trim
+ | f(s)
+ | }
+ | def t1 = m("foo")
+ | def t2 = M.m("bar")
+ |}
+ """.stripMargin
+
+ val List(c, _, _) = compile(code)
+
+ val t1 = getSingleMethod(c, "t1")
+ assert(t1.instructions exists {
+ case _: InvokeDynamic => true
+ case _ => false
+ })
+ // the indy call is inlined into t, and the closure elimination rewrites the closure invocation to the body method
+ assertInvoke(t1, "C", "C$$$anonfun$2")
+
+ val t2 = getSingleMethod(c, "t2")
+ assert(t2.instructions exists {
+ case _: InvokeDynamic => true
+ case _ => false
+ })
+ assertInvoke(t2, "M$", "M$$$anonfun$1")
+ }
}