summaryrefslogtreecommitdiff
path: root/test/junit
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2015-11-13 16:34:27 +0100
committerLukas Rytz <lukas.rytz@gmail.com>2015-11-13 20:03:03 +0100
commitd4bfc59b781b465ef980cd7082462eb2cfe2c8b8 (patch)
tree49a3775a4fb175f72a596e2605d492bb2f0f94f3 /test/junit
parent12b6598714d72bd1d275d756de0b8e7df04d1f16 (diff)
downloadscala-d4bfc59b781b465ef980cd7082462eb2cfe2c8b8.tar.gz
scala-d4bfc59b781b465ef980cd7082462eb2cfe2c8b8.tar.bz2
scala-d4bfc59b781b465ef980cd7082462eb2cfe2c8b8.zip
Support specialized functions in closure optimizer
Fixes https://github.com/scala/scala-dev/issues/52. An IndyLambda may create a specialized function type, where the SAM is the corresponding specialized variant of apply. If this closure is invoked through the generic apply method, the closure optimizer would previously not re-write the invocation to the $anonfun method. This is now done, including the necessary box / unbox operations.
Diffstat (limited to 'test/junit')
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala17
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala190
2 files changed, 207 insertions, 0 deletions
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala
index 54724458e2..99041b5497 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala
@@ -70,4 +70,21 @@ class ClosureOptimizerTest extends ClearAfterClass {
assert(bodyCall.getNext.getOpcode == POP)
assert(bodyCall.getNext.getNext.getOpcode == ACONST_NULL)
}
+
+ @Test
+ def makeLMFCastExplicit(): Unit = {
+ val code =
+ """class C {
+ | def t(l: List[String]) = {
+ | val fun: String => String = s => s
+ | fun(l.head)
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compileClasses(compiler)(code)
+ assertSameCode(getSingleMethod(c, "t").instructions.dropNonOp,
+ List(VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "scala/collection/immutable/List", "head", "()Ljava/lang/Object;", false),
+ TypeOp(CHECKCAST, "java/lang/String"), Invoke(INVOKESTATIC, "C", "C$$$anonfun$1", "(Ljava/lang/String;)Ljava/lang/String;", false),
+ TypeOp(CHECKCAST, "java/lang/String"), Op(ARETURN)))
+ }
}
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 1037fd5221..75c1a0d3ad 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
@@ -1226,4 +1226,194 @@ class InlinerTest extends ClearAfterClass {
assertNoInvoke(getSingleMethod(c, "g"))
assertNoInvoke(getSingleMethod(d, "t"))
}
+
+ @Test
+ def optimizeSpecializedClosures(): Unit = {
+ val code =
+ """class ValKl(val x: Int) extends AnyVal
+ |
+ |class C {
+ | def t1 = {
+ | // IndyLambda: SAM type is JFunction1$mcII$sp, SAM is apply$mcII$sp(I)I, body method is $anonfun(I)I
+ | val f = (x: Int) => x + 1
+ | // invocation of apply$mcII$sp(I)I, matches the SAM in IndyLambda. no boxing / unboxing needed.
+ | f(10)
+ | // opt: re-write the invocation to the body method
+ | }
+ |
+ | @inline final def m1a(f: Long => Int) = f(1l)
+ | def t1a = m1a(l => l.toInt) // after inlining m1a, we have the same situation as in t1
+ |
+ | def t2 = {
+ | // there is no specialized variant of Function2 for this combination of types, so the IndyLambda has to create a generic Function2.
+ | // IndyLambda: SAM type is JFunction2, SAM is apply(ObjectObject)Object, body method is $anonfun$adapted(ObjectObject)Object
+ | val f = (b: Byte, i: Int) => i + b
+ | // invocation of apply(ObjectOjbect)Object, matches SAM in IndyLambda. arguments are boxed, result unboxed.
+ | f(1, 2)
+ | // opt: re-wrtie to $anonfun$adapted
+ | // inline that call, then we get box-unbox pairs (can be eliminated) and a call to $anonfun(BI)I
+ | }
+ |
+ | def t3 = {
+ | // similar to t2: for functions with value class parameters, IndyLambda always uses the generic Function version.
+ | // IndyLambda: SAM type is JFunction1, SAM is apply(Object)Object, body method is $anonfun$adapted(Object)Object
+ | val f = (a: ValKl) => a
+ | // invocation of apply(Object)Object, ValKl instance is created, result extracted
+ | f(new ValKl(1))
+ | // opt: re-write to $anonfun$adapted.
+ | // inline that call, then we get value class instantiation-extraction pairs and a call to $anonfun(I)I
+ | }
+ |
+ | def t4 = {
+ | // IndyLambda: SAM type is JFunction1$mcII$sp, SAM is apply$mcII$sp(I)I, body method is $anonfun(I)I
+ | val f: Int => Any = (x: Int) => 1
+ | // invocation of apply(Object)Object, argument is boxed. method name and type doesn't match IndyLambda.
+ | f(10)
+ | // opt: rewriting to the body method requires inserting an unbox operation for the argument, and a box operation for the result
+ | // that produces a box-unbox pair and a call to $anonfun(I)I
+ | }
+ |
+ |
+ | @inline final def m4a[T, U, V](f: (T, U) => V, x: T, y: U) = f(x, y) // invocation to generic apply(ObjectObject)Object
+ | def t4a = m4a((x: Int, y: Double) => 1l + x + y.toLong, 1, 2d) // IndyLambda uses specilized JFunction2$mcJID$sp. after inlining m4a, similar to t4.
+ |
+ | def t5 = {
+ | // no specialization for the comibnation of primitives
+ | // IndyLambda: SAM type is JFunction2, SAM is generic apply, body method is $anonfun$adapted
+ | val f: (Int, Byte) => Any = (x: Int, b: Byte) => 1
+ | // invocation of generic apply.
+ | f(10, 3)
+ | // opt: re-write to $anonfun$adapted, inline that method. generates box-unbox pairs and a call to $anonfun(IB)I
+ | }
+ |
+ | def t5a = m4a((x: Int, y: Byte) => 1, 12, 31.toByte) // similar to t5 after inlining m4a
+ |
+ | // m6$mIVc$sp invokes apply$mcVI$sp
+ | @inline final def m6[@specialized(Int) T, @specialized(Unit) U](f: T => U, x: T): Unit = f(x)
+ | // IndyLambda: JFunction1$mcVI$sp, SAM is apply$mcVI$sp, body method $anonfun(I)V
+ | // invokes m6$mIVc$sp (Lscala/Function1;I)V
+ | def t6 = m6((x: Int) => (), 10)
+ | // opt: after inlining m6, the closure method invocation (apply$mcVI$sp) matches the IndyLambda, the call can be rewritten, no boxing
+ |
+ | // m7 invokes apply
+ | @inline final def m7[@specialized(Boolean) T, @specialized(Int) U](f: T => U, x: T): Unit = f(x)
+ | // IndyLambda: JFunction1, SAM is apply(Object)Object, body method is $anonfun$adapted(Obj)Obj
+ | // `true` is boxed before passing to m7
+ | def t7 = m7((x: Boolean) => (), true)
+ | // opt: after inlining m7, the apply call is re-written to $anonfun$adapted, which is then inlined.
+ | // we get a box-unbox pair and a call to $anonfun(Z)V
+ |
+ |
+ | // invokes the generic apply(ObjObj)Obj
+ | @inline final def m8[T, U, V](f: (T, U) => V, x: T, y: U) = f(x, y)
+ | // IndyLambda: JFunction2$mcJID$sp, SAM is apply$mcJID$sp, body method $anonfun(ID)J
+ | // boxes the int and double arguments and calls m8, unboxToLong the result
+ | def t8 = m8((x: Int, y: Double) => 1l + x + y.toLong, 1, 2d)
+ | // opt: after inlining m8, rewrite to the body method $anonfun(ID)J, which requires inserting unbox operations for the params, box for the result
+ | // the box-unbox pairs can then be optimized away
+ |
+ | // m9$mVc$sp invokes apply$mcVI$sp
+ | @inline final def m9[@specialized(Unit) U](f: Int => U): Unit = f(1)
+ | // IndyLambda: JFunction1, SAM is apply(Obj)Obj, body method $anonfun$adapted(Ojb)Obj
+ | // invocation of m9$mVc$sp
+ | def t9 = m9(println)
+ | // opt: after inlining m9, rewrite to $anonfun$adapted(Ojb)Obj, which requires inserting a box operation for the parameter.
+ | // then we inline $adapted, which has signature (Obj)V. the `BoxedUnit.UNIT` from the body of $anonfun$adapted is eliminated by push-pop
+ |
+ | def t9a = (1 to 10) foreach println // similar to t9
+ |
+ | def intCons(i: Int): Unit = ()
+ | // IndyLambda: JFunction1$mcVI$sp, SAM is apply$mcVI$sp, body method $anonfun(I)V
+ | def t10 = m9(intCons)
+ | // after inlining m9, rewrite the apply$mcVI$sp call to the body method, no adaptations required
+ |
+ | def t10a = (1 to 10) foreach intCons // similar to t10
+ |}
+ """.stripMargin
+ val List(c, _, _) = compile(code)
+
+ def instructionSummary(m: Method): List[Any] = m.instructions.dropNonOp map {
+ case i: Invoke => i.name
+ case i => i.opcode
+ }
+
+ assertEquals(instructionSummary(getSingleMethod(c, "t1")),
+ List(BIPUSH, "C$$$anonfun$1", IRETURN))
+
+ assertEquals(instructionSummary(getSingleMethod(c, "t1a")),
+ List(LCONST_1, "C$$$anonfun$2", IRETURN))
+
+ assertEquals(instructionSummary(getSingleMethod(c, "t2")), List(
+ ICONST_1, "boxToByte",
+ ICONST_2, "boxToInteger", ASTORE,
+ "unboxToByte",
+ ALOAD, "unboxToInt",
+ "C$$$anonfun$3",
+ "boxToInteger", "unboxToInt", IRETURN))
+
+ // val a = new ValKl(n); new ValKl(anonfun(a.x)).x
+ // value class instantiation-extraction should be optimized by boxing elim
+ assertEquals(instructionSummary(getSingleMethod(c, "t3")), List(
+ NEW, DUP, ICONST_1, "<init>", ASTORE,
+ NEW, DUP, ALOAD, CHECKCAST, "x",
+ "C$$$anonfun$4",
+ "<init>", CHECKCAST,
+ "x", IRETURN))
+
+ assertEquals(instructionSummary(getSingleMethod(c, "t4")), List(
+ BIPUSH, "boxToInteger", "unboxToInt",
+ "C$$$anonfun$5",
+ "boxToInteger", ARETURN))
+
+ assertEquals(instructionSummary(getSingleMethod(c, "t4a")), List(
+ ICONST_1, "boxToInteger",
+ LDC, "boxToDouble", "unboxToDouble", DSTORE,
+ "unboxToInt", DLOAD, "C$$$anonfun$6",
+ "boxToLong", "unboxToLong", LRETURN))
+
+ assertEquals(instructionSummary(getSingleMethod(c, "t5")), List(
+ BIPUSH, "boxToInteger",
+ ICONST_3, "boxToByte", ASTORE,
+ "unboxToInt", ALOAD,
+ "unboxToByte",
+ "C$$$anonfun$7",
+ "boxToInteger", ARETURN))
+
+ assertEquals(instructionSummary(getSingleMethod(c, "t5a")), List(
+ BIPUSH, "boxToInteger",
+ BIPUSH, I2B, "boxToByte", ASTORE,
+ "unboxToInt", ALOAD,
+ "unboxToByte",
+ "C$$$anonfun$8",
+ "boxToInteger", "unboxToInt", IRETURN))
+
+ assertEquals(instructionSummary(getSingleMethod(c, "t6")), List(
+ BIPUSH, "C$$$anonfun$9", RETURN))
+
+ assertEquals(instructionSummary(getSingleMethod(c, "t7")), List(
+ ICONST_1, "boxToBoolean", "unboxToBoolean",
+ "C$$$anonfun$10", RETURN))
+
+ assertEquals(instructionSummary(getSingleMethod(c, "t8")), List(
+ ICONST_1, "boxToInteger",
+ LDC, "boxToDouble", "unboxToDouble", DSTORE,
+ "unboxToInt", DLOAD, "C$$$anonfun$11",
+ "boxToLong", "unboxToLong", LRETURN))
+
+ assertEquals(instructionSummary(getSingleMethod(c, "t9")), List(
+ ICONST_1, "boxToInteger", "C$$$anonfun$12", RETURN))
+
+ // t9a inlines Range.foreach, which is quite a bit of code, so just testing the core
+ assertInvoke(getSingleMethod(c, "t9a"), "C", "C$$$anonfun$13")
+ assert(instructionSummary(getSingleMethod(c, "t9a")).contains("boxToInteger"))
+
+ assertEquals(instructionSummary(getSingleMethod(c, "t10")), List(
+ ICONST_1, ISTORE,
+ ALOAD, ILOAD,
+ "C$$$anonfun$14", RETURN))
+
+ // t10a inlines Range.foreach
+ assertInvoke(getSingleMethod(c, "t10a"), "C", "C$$$anonfun$15")
+ assert(!instructionSummary(getSingleMethod(c, "t10a")).contains("boxToInteger"))
+ }
}