@@ -52,18 +52,53 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
5252 case Array (samMethodType : Type , implMethod : Handle , instantiatedMethodType : Type , xs @ _* ) =>
5353 // LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda
5454 // implementation method (casting, boxing, unboxing, and primitive widening, see Javadoc).
55- // The closure optimizer (rewriteClosureApplyInvocations) does not currently support these
56- // adaptations, so we don't consider indy calls that need adaptations for rewriting.
57- // Indy calls emitted by scalac never rely on adaptation, they are implemented explicitly
58- // in the implMethod.
5955 //
60- // Note that we don't check all the invariants requried for a metafactory indy call, only
61- // those required not to crash the compiler.
56+ // The closure optimizer supports only one of those adaptations: it will cast arguments
57+ // to the correct type when re-writing a closure call to the body method. Example:
58+ //
59+ // val fun: String => String = l => l
60+ // val l = List("")
61+ // fun(l.head)
62+ //
63+ // The samMethodType of Function1 is `(Object)Object`, while the instantiatedMethodType
64+ // is `(String)String`. The return type of `List.head` is `Object`.
65+ //
66+ // The implMethod has the signature `C$anonfun(String)String`.
67+ //
68+ // At the closure callsite, we have an `INVOKEINTERFACE Function1.apply (Object)Object`,
69+ // so the object returned by `List.head` can be directly passed into the call (no cast).
70+ //
71+ // The closure object will cast the object to String before passing it to the implMethod.
72+ //
73+ // When re-writing the closure callsite to the implMethod, we have to insert a cast.
74+ //
75+ // The check below ensures that
76+ // (1) the implMethod type has the expected singature (captured types plus argument types
77+ // from instantiatedMethodType)
78+ // (2) the receiver of the implMethod matches the first captured type
79+ // (3) all parameters that are not the same in samMethodType and instantiatedMethodType
80+ // are reference types, so that we can insert casts to perform the same adaptation
81+ // that the closure object would.
82+
83+ val isStatic = implMethod.getTag == H_INVOKESTATIC
84+ val indyParamTypes = Type .getArgumentTypes(indy.desc)
85+ val instantiatedMethodArgTypes = instantiatedMethodType.getArgumentTypes
86+ val expectedImplMethodType = {
87+ val paramTypes = (if (isStatic) indyParamTypes else indyParamTypes.tail) ++ instantiatedMethodArgTypes
88+ Type .getMethodType(instantiatedMethodType.getReturnType, paramTypes : _* )
89+ }
6290
63- val implMethodType = Type .getType(implMethod.getDesc)
64- val numCaptures = implMethodType.getArgumentTypes.length - instantiatedMethodType.getArgumentTypes.length
65- val implMethodTypeWithoutCaputres = Type .getMethodType(implMethodType.getReturnType, implMethodType.getArgumentTypes.drop(numCaptures): _* )
66- implMethodTypeWithoutCaputres == instantiatedMethodType
91+ {
92+ Type .getType(implMethod.getDesc) == expectedImplMethodType // (1)
93+ } && {
94+ isStatic || implMethod.getOwner == indyParamTypes(0 ).getInternalName // (2)
95+ } && {
96+ def isReference (t : Type ) = t.getSort == Type .OBJECT || t.getSort == Type .ARRAY
97+ (samMethodType.getArgumentTypes, instantiatedMethodArgTypes).zipped forall {
98+ case (samArgType, instArgType) =>
99+ samArgType == instArgType || isReference(samArgType) && isReference(instArgType) // (3)
100+ }
101+ }
67102
68103 case _ =>
69104 false
@@ -104,7 +139,11 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
104139 // If the variable is not modified within the method, we could avoid introducing yet another
105140 // local. On the other hand, further optimizations (copy propagation, remove unused locals) will
106141 // clean it up.
107- val localsForCaptures = LocalsList .fromTypes(firstCaptureLocal, capturedTypes)
142+
143+ // Captured variables don't need to be cast when loaded at the callsite (castLoadTypes are None).
144+ // This is checked in `isClosureInstantiation`: the types of the captured variables in the indy
145+ // instruction match exactly the corresponding parameter types in the body method.
146+ val localsForCaptures = LocalsList .fromTypes(firstCaptureLocal, capturedTypes, castLoadTypes = _ => None )
108147 methodNode.maxLocals = firstCaptureLocal + localsForCaptures.size
109148
110149 insertStoreOps(indy, methodNode, localsForCaptures)
@@ -140,6 +179,8 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
140179 val varOp = new VarInsnNode (if (store) l.storeOpcode else l.loadOpcode, l.local)
141180 if (store) methodNode.instructions.insert(previous, varOp)
142181 else methodNode.instructions.insertBefore(before, varOp)
182+ if (! store) for (castType <- l.castLoadedValue)
183+ methodNode.instructions.insert(varOp, new TypeInsnNode (CHECKCAST , castType.getInternalName))
143184 }
144185 }
145186
@@ -187,7 +228,21 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
187228 // if there are multiple callsites, the same locals are re-used.
188229 val argTypes = indy.bsmArgs(0 ).asInstanceOf [Type ].getArgumentTypes // safe, checked in isClosureInstantiation
189230 val firstArgLocal = methodNode.maxLocals
190- val argLocals = LocalsList .fromTypes(firstArgLocal, argTypes)
231+
232+ // The comment in `isClosureInstantiation` explains why we have to introduce casts for
233+ // arguments that have different types in samMethodType and instantiatedMethodType.
234+ val castLoadTypes = {
235+ val instantiatedMethodType = indy.bsmArgs(2 ).asInstanceOf [Type ]
236+ (argTypes, instantiatedMethodType.getArgumentTypes).zipped map {
237+ case (samArgType, instantiatedArgType) if samArgType != instantiatedArgType =>
238+ // isClosureInstantiation ensures that the two types are reference types, so we don't
239+ // end up casting primitive values.
240+ Some (instantiatedArgType)
241+ case _ =>
242+ None
243+ }
244+ }
245+ val argLocals = LocalsList .fromTypes(firstArgLocal, argTypes, castLoadTypes)
191246 methodNode.maxLocals = firstArgLocal + argLocals.size
192247
193248 (captureLocals, argLocals)
@@ -286,12 +341,12 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
286341 * Local(6, refOpOffset) ::
287342 * Nil
288343 */
289- def fromTypes (firstLocal : Int , types : Array [Type ]): LocalsList = {
344+ def fromTypes (firstLocal : Int , types : Array [Type ], castLoadTypes : Int => Option [ Type ] ): LocalsList = {
290345 var sizeTwoOffset = 0
291346 val locals : List [Local ] = types.indices.map(i => {
292347 // The ASM method `type.getOpcode` returns the opcode for operating on a value of `type`.
293348 val offset = types(i).getOpcode(ILOAD ) - ILOAD
294- val local = Local (firstLocal + i + sizeTwoOffset, offset)
349+ val local = Local (firstLocal + i + sizeTwoOffset, offset, castLoadTypes(i) )
295350 if (local.size == 2 ) sizeTwoOffset += 1
296351 local
297352 })(collection.breakOut)
@@ -305,7 +360,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
305360 * The xLOAD / xSTORE opcodes are in the following sequence: I, L, F, D, A, so the offset for
306361 * a local variable holding a reference (`A`) is 4. See also method `getOpcode` in [[scala.tools.asm.Type ]].
307362 */
308- case class Local (local : Int , opcodeOffset : Int ) {
363+ case class Local (local : Int , opcodeOffset : Int , castLoadedValue : Option [ Type ] ) {
309364 def size = if (loadOpcode == LLOAD || loadOpcode == DLOAD ) 2 else 1
310365
311366 def loadOpcode = ILOAD + opcodeOffset
0 commit comments