@@ -52,18 +52,53 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
52
52
case Array (samMethodType : Type , implMethod : Handle , instantiatedMethodType : Type , xs @ _* ) =>
53
53
// LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda
54
54
// 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.
59
55
//
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
+ }
62
90
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
+ }
67
102
68
103
case _ =>
69
104
false
@@ -104,7 +139,11 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
104
139
// If the variable is not modified within the method, we could avoid introducing yet another
105
140
// local. On the other hand, further optimizations (copy propagation, remove unused locals) will
106
141
// 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 )
108
147
methodNode.maxLocals = firstCaptureLocal + localsForCaptures.size
109
148
110
149
insertStoreOps(indy, methodNode, localsForCaptures)
@@ -140,6 +179,8 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
140
179
val varOp = new VarInsnNode (if (store) l.storeOpcode else l.loadOpcode, l.local)
141
180
if (store) methodNode.instructions.insert(previous, varOp)
142
181
else methodNode.instructions.insertBefore(before, varOp)
182
+ if (! store) for (castType <- l.castLoadedValue)
183
+ methodNode.instructions.insert(varOp, new TypeInsnNode (CHECKCAST , castType.getInternalName))
143
184
}
144
185
}
145
186
@@ -187,7 +228,21 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
187
228
// if there are multiple callsites, the same locals are re-used.
188
229
val argTypes = indy.bsmArgs(0 ).asInstanceOf [Type ].getArgumentTypes // safe, checked in isClosureInstantiation
189
230
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)
191
246
methodNode.maxLocals = firstArgLocal + argLocals.size
192
247
193
248
(captureLocals, argLocals)
@@ -286,12 +341,12 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
286
341
* Local(6, refOpOffset) ::
287
342
* Nil
288
343
*/
289
- def fromTypes (firstLocal : Int , types : Array [Type ]): LocalsList = {
344
+ def fromTypes (firstLocal : Int , types : Array [Type ], castLoadTypes : Int => Option [ Type ] ): LocalsList = {
290
345
var sizeTwoOffset = 0
291
346
val locals : List [Local ] = types.indices.map(i => {
292
347
// The ASM method `type.getOpcode` returns the opcode for operating on a value of `type`.
293
348
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) )
295
350
if (local.size == 2 ) sizeTwoOffset += 1
296
351
local
297
352
})(collection.breakOut)
@@ -305,7 +360,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
305
360
* The xLOAD / xSTORE opcodes are in the following sequence: I, L, F, D, A, so the offset for
306
361
* a local variable holding a reference (`A`) is 4. See also method `getOpcode` in [[scala.tools.asm.Type ]].
307
362
*/
308
- case class Local (local : Int , opcodeOffset : Int ) {
363
+ case class Local (local : Int , opcodeOffset : Int , castLoadedValue : Option [ Type ] ) {
309
364
def size = if (loadOpcode == LLOAD || loadOpcode == DLOAD ) 2 else 1
310
365
311
366
def loadOpcode = ILOAD + opcodeOffset
0 commit comments