diff options
author | Lukas Rytz <lukas.rytz@gmail.com> | 2015-06-23 12:53:23 +0200 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@gmail.com> | 2015-06-23 13:15:49 +0200 |
commit | 3e7776e6418e047e11d0b11e5a85dfdc500b517e (patch) | |
tree | 199db99b6863d089327a4be823633718c4c908f3 | |
parent | 5be0722abc913deea1a0bc3e433f1bf4c29f4e09 (diff) | |
download | scala-3e7776e6418e047e11d0b11e5a85dfdc500b517e.tar.gz scala-3e7776e6418e047e11d0b11e5a85dfdc500b517e.tar.bz2 scala-3e7776e6418e047e11d0b11e5a85dfdc500b517e.zip |
Cast arguments where necessary before rewriting closure invocations
The parameter types of a closure invocation can be supertypes of the
parameter types in the implementation method. The closure automatically
casts arguments to the right type. We need to do the same when
rewriting closure invocations. Example:
val fun: String => String = l => l
val l = List("")
fun(l.head)
The closure object calls `apply(Object)Object`. The body method takes
an argument of type `String`.
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala | 85 |
1 files changed, 70 insertions, 15 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala index 29896b5ffd..1648a53ed8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala @@ -52,18 +52,53 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { 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 (rewriteClosureApplyInvocations) does not currently support these - // adaptations, so we don't consider indy calls that need adaptations for rewriting. - // Indy calls emitted by scalac never rely on adaptation, they are implemented explicitly - // in the implMethod. // - // Note that we don't check all the invariants requried for a metafactory indy call, only - // those required not to crash the compiler. + // 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 == 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 implMethodType = Type.getType(implMethod.getDesc) - val numCaptures = implMethodType.getArgumentTypes.length - instantiatedMethodType.getArgumentTypes.length - val implMethodTypeWithoutCaputres = Type.getMethodType(implMethodType.getReturnType, implMethodType.getArgumentTypes.drop(numCaptures): _*) - implMethodTypeWithoutCaputres == instantiatedMethodType + { + 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) + } + } case _ => false @@ -104,7 +139,11 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { // If the variable is not modified within the method, we could avoid introducing yet another // local. On the other hand, further optimizations (copy propagation, remove unused locals) will // clean it up. - val localsForCaptures = LocalsList.fromTypes(firstCaptureLocal, capturedTypes) + + // Captured variables don't need to be cast when loaded at the callsite (castLoadTypes are None). + // This is checked in `isClosureInstantiation`: the types of the captured variables in the indy + // instruction match exactly the corresponding parameter types in the body method. + val localsForCaptures = LocalsList.fromTypes(firstCaptureLocal, capturedTypes, castLoadTypes = _ => None) methodNode.maxLocals = firstCaptureLocal + localsForCaptures.size insertStoreOps(indy, methodNode, localsForCaptures) @@ -140,6 +179,8 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { val varOp = new VarInsnNode(if (store) l.storeOpcode else l.loadOpcode, l.local) if (store) methodNode.instructions.insert(previous, varOp) else methodNode.instructions.insertBefore(before, varOp) + if (!store) for (castType <- l.castLoadedValue) + methodNode.instructions.insert(varOp, new TypeInsnNode(CHECKCAST, castType.getInternalName)) } } @@ -187,7 +228,21 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { // if there are multiple callsites, the same locals are re-used. val argTypes = indy.bsmArgs(0).asInstanceOf[Type].getArgumentTypes // safe, checked in isClosureInstantiation val firstArgLocal = methodNode.maxLocals - val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes) + + // The comment in `isClosureInstantiation` explains why we have to introduce casts for + // arguments that have different types in samMethodType and instantiatedMethodType. + val castLoadTypes = { + val instantiatedMethodType = indy.bsmArgs(2).asInstanceOf[Type] + (argTypes, instantiatedMethodType.getArgumentTypes).zipped map { + case (samArgType, instantiatedArgType) if samArgType != instantiatedArgType => + // isClosureInstantiation ensures that the two types are reference types, so we don't + // end up casting primitive values. + Some(instantiatedArgType) + case _ => + None + } + } + val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes, castLoadTypes) methodNode.maxLocals = firstArgLocal + argLocals.size (captureLocals, argLocals) @@ -286,12 +341,12 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { * Local(6, refOpOffset) :: * Nil */ - def fromTypes(firstLocal: Int, types: Array[Type]): LocalsList = { + def fromTypes(firstLocal: Int, types: Array[Type], castLoadTypes: Int => Option[Type]): LocalsList = { var sizeTwoOffset = 0 val locals: List[Local] = types.indices.map(i => { // The ASM method `type.getOpcode` returns the opcode for operating on a value of `type`. val offset = types(i).getOpcode(ILOAD) - ILOAD - val local = Local(firstLocal + i + sizeTwoOffset, offset) + val local = Local(firstLocal + i + sizeTwoOffset, offset, castLoadTypes(i)) if (local.size == 2) sizeTwoOffset += 1 local })(collection.breakOut) @@ -305,7 +360,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { * The xLOAD / xSTORE opcodes are in the following sequence: I, L, F, D, A, so the offset for * a local variable holding a reference (`A`) is 4. See also method `getOpcode` in [[scala.tools.asm.Type]]. */ - case class Local(local: Int, opcodeOffset: Int) { + case class Local(local: Int, opcodeOffset: Int, castLoadedValue: Option[Type]) { def size = if (loadOpcode == LLOAD || loadOpcode == DLOAD) 2 else 1 def loadOpcode = ILOAD + opcodeOffset |