diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2016-05-04 21:16:41 +1000 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2016-06-01 11:15:48 +1000 |
commit | 0533a3df71e9c855ac68e10d060c2c87d16994e0 (patch) | |
tree | 4aa840fb0367b00a0a2dc0c1108b064e7166669c /test/junit | |
parent | 7b132f39b82e4fc47cd95eadce9e3f22da8c8d82 (diff) | |
download | scala-0533a3df71e9c855ac68e10d060c2c87d16994e0.tar.gz scala-0533a3df71e9c855ac68e10d060c2c87d16994e0.tar.bz2 scala-0533a3df71e9c855ac68e10d060c2c87d16994e0.zip |
Lambda impl methods static and more stably named
The body of lambdas is compiled into a synthetic method
in the enclosing class. Previously, this method was a public
virtual method named `fully$qualified$Class$$anonfun$n`.
For lambdas that didn't capture a `this` reference, a static
method was used.
This commit changes two aspects.
Firstly, all lambda impl methods are now emitted static.
An extra parameter is added to those that require a this
reference.
This is an improvement as it:
- allows, shorter, more readable names for the lambda impl method
- avoids pollution of the vtable of the class. Note that javac uses
private instance methods, rather than public static methods. If
we followed its lead, we would be unable to support important use
cases in our inliner
Secondly, the name of the enclosing method has been included in
the name of the lambda impl method to improve debuggability and
to improve serialization compatibility. The serialization improvement
comes from the way that fresh names for the impl methods are
allocated: adding or removing lambdas in methods not named "foo" won't
change the numbering of the `anonfun$foo$n` impl methods from methods
named "foo". This is in line with user expectations about anonymous
class and lambda serialization stability. Brian Goetz has described
this tricky area well in:
http://cr.openjdk.java.net/~briangoetz/eg-attachments/lambda-serialization.html
This commit doesn't go as far a Javac, we don't use the hash of the
lambda type info, param names, etc to map to a lambda impl method name.
As such, we are more prone to the type-1 and -2 failures described there.
However, our Scala 2.11.8 has similar characteristics, so we aren't going
backwards.
Special case in the naming: Use "new" rather than "<init>" for constructor enclosed
lambdas, as javac does.
I have also changed the way that "delambdafy target" methods are identifed.
Rather than relying on the naming convention, I have switched to using a
symbol attachment. The assumption is that we only need to identify them
from within the same compilation unit.
This means we can distinguish impl metbods for expanded functions
(ones called from an `apply` method of an ahead-of-time expanded
anonfun class), from those that truly end up as targets for lambda
metafactory. Only the latter are translated to static methods in
this patch.
Diffstat (limited to 'test/junit')
6 files changed, 46 insertions, 40 deletions
diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala index 2bcbcc870c..1ad02c10cf 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala @@ -43,7 +43,7 @@ class IndySammyTest extends BytecodeTesting { val c = compileClass(s"class C { ${lamDef(from, to, body)}; ${appDef(arg)} }", allowMessage = allowMessage) val applySig = getAsmMethod(funClass, "apply").desc - val anonfun = getMethod(c, "C$$$anonfun$1") + val anonfun = getMethod(c, "$anonfun$lam$1") val lamInsn = getInstructions(c, "lam").dropNonOp val applyInvoke = getMethod(c, "app") diff --git a/test/junit/scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala index 8cf6a655d2..b64a5ae3ce 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala @@ -43,9 +43,9 @@ class OptimizedBytecodeTest extends BytecodeTesting { val c = compileClass(code) assertSameSummary(getMethod(c, "t"), List( - LDC, ASTORE, ALOAD /*0*/, ALOAD /*1*/, "C$$$anonfun$1", IRETURN)) - assertSameSummary(getMethod(c, "C$$$anonfun$1"), List(LDC, "C$$$anonfun$2", IRETURN)) - assertSameSummary(getMethod(c, "C$$$anonfun$2"), List(-1 /*A*/, GOTO /*A*/)) + LDC, ASTORE, ALOAD /*0*/, ALOAD /*1*/, "$anonfun$t$1", IRETURN)) + assertSameSummary(getMethod(c, "$anonfun$t$1"), List(ALOAD, IFNONNULL, ACONST_NULL, ATHROW, -1, LDC, "$anonfun$t$2", IRETURN)) + assertSameSummary(getMethod(c, "$anonfun$t$2"), List(-1 /*A*/, GOTO /*A*/)) } @Test @@ -295,9 +295,9 @@ class OptimizedBytecodeTest extends BytecodeTesting { |} """.stripMargin val c = compileClass(code, allowMessage = _.msg.contains("exception handler declared in the inlined method")) - assertInvoke(getMethod(c, "f1a"), "C", "C$$$anonfun$1") + assertInvoke(getMethod(c, "f1a"), "C", "$anonfun$f1a$1") assertInvoke(getMethod(c, "f1b"), "C", "wrapper1") - assertInvoke(getMethod(c, "f2a"), "C", "C$$$anonfun$3") + assertInvoke(getMethod(c, "f2a"), "C", "$anonfun$f2a$1") assertInvoke(getMethod(c, "f2b"), "C", "wrapper2") } @@ -331,7 +331,7 @@ class OptimizedBytecodeTest extends BytecodeTesting { |class Listt """.stripMargin val List(c, nil, nilMod, listt) = compileClasses(code) - assertInvoke(getMethod(c, "t"), "C", "C$$$anonfun$1") + assertInvoke(getMethod(c, "t"), "C", "$anonfun$t$1") } @Test @@ -357,6 +357,6 @@ class OptimizedBytecodeTest extends BytecodeTesting { def optimiseEnablesNewOpt(): Unit = { val code = """class C { def t = (1 to 10) foreach println }""" val List(c) = readAsmClasses(newCompiler(extraArgs = "-optimise -deprecation").compileToBytes(code, allowMessage = _.msg.contains("is deprecated"))) - assertInvoke(getMethod(c, "t"), "C", "C$$$anonfun$1") // range-foreach inlined from classpath + assertInvoke(getMethod(c, "t"), "C", "$anonfun$t$1") // range-foreach inlined from classpath } } 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 2da2ecdb72..f672237f10 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala @@ -28,7 +28,7 @@ class ClosureOptimizerTest extends BytecodeTesting { val c = compileClass(code) val t = getAsmMethod(c, "t") - val bodyCall = findInstr(t, "INVOKESTATIC C.C$$$anonfun$1 ()Lscala/runtime/Nothing$") + val bodyCall = findInstr(t, "INVOKESTATIC C.$anonfun$t$1 ()Lscala/runtime/Nothing$") assert(bodyCall.getNext.getOpcode == ATHROW) } @@ -44,7 +44,7 @@ class ClosureOptimizerTest extends BytecodeTesting { val c = compileClass(code) val t = getAsmMethod(c, "t") - val bodyCall = findInstr(t, "INVOKESTATIC C.C$$$anonfun$1 ()Lscala/runtime/Null$") + val bodyCall = findInstr(t, "INVOKESTATIC C.$anonfun$t$1 ()Lscala/runtime/Null$") assert(bodyCall.getNext.getOpcode == POP) assert(bodyCall.getNext.getNext.getOpcode == ACONST_NULL) } @@ -62,7 +62,7 @@ class ClosureOptimizerTest extends BytecodeTesting { val c = compileClass(code) assertSameCode(getMethod(c, "t"), 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"), Invoke(INVOKESTATIC, "C", "$anonfun$t$1", "(Ljava/lang/String;)Ljava/lang/String;", false), 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 333792677a..7234659a1d 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -945,11 +945,11 @@ class InlinerTest extends BytecodeTesting { val t1 = getMethod(c, "t1") assertNoIndy(t1) // 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") + assertInvoke(t1, "C", "$anonfun$m$2") val t2 = getMethod(c, "t2") assertNoIndy(t2) - assertInvoke(t2, "M$", "M$$$anonfun$1") + assertInvoke(t2, "M$", "$anonfun$m$1") } @Test @@ -1033,7 +1033,7 @@ class InlinerTest extends BytecodeTesting { """.stripMargin val List(c) = compile(code) - assertInvoke(getMethod(c, "t1"), "C", "C$$$anonfun$1") + assertInvoke(getMethod(c, "t1"), "C", "$anonfun$t1$1") assertInvoke(getMethod(c, "t2"), "C", "a") assertInvoke(getMethod(c, "t3"), "C", "b") assertNoInvoke(getMethod(c, "t4")) @@ -1097,8 +1097,8 @@ class InlinerTest extends BytecodeTesting { """.stripMargin val List(c) = compile(code) - assertInvoke(getMethod(c, "t1"), "C", "C$$$anonfun$1") - assertInvoke(getMethod(c, "t2"), "C", "C$$$anonfun$2") + assertInvoke(getMethod(c, "t1"), "C", "$anonfun$t1$1") + assertInvoke(getMethod(c, "t2"), "C", "$anonfun$t2$1") assertInvoke(getMethod(c, "t3"), "scala/Function1", "apply$mcII$sp") assertInvoke(getMethod(c, "t4"), "scala/Function1", "apply$mcII$sp") assertInvoke(getMethod(c, "t5"), "C", "h") @@ -1273,39 +1273,39 @@ class InlinerTest extends BytecodeTesting { """.stripMargin val List(c, _, _) = compile(code) - assertSameSummary(getMethod(c, "t1"), List(BIPUSH, "C$$$anonfun$1", IRETURN)) - assertSameSummary(getMethod(c, "t1a"), List(LCONST_1, "C$$$anonfun$2", IRETURN)) - assertSameSummary(getMethod(c, "t2"), List(ICONST_1, ICONST_2, "C$$$anonfun$3",IRETURN)) + assertSameSummary(getMethod(c, "t1"), List(BIPUSH, "$anonfun$t1$1", IRETURN)) + assertSameSummary(getMethod(c, "t1a"), List(LCONST_1, "$anonfun$t1a$1", IRETURN)) + assertSameSummary(getMethod(c, "t2"), List(ICONST_1, ICONST_2, "$anonfun$t2$1",IRETURN)) // val a = new ValKl(n); new ValKl(anonfun(a.x)).x // value class instantiation-extraction should be optimized by boxing elim assertSameSummary(getMethod(c, "t3"), List( NEW, DUP, ICONST_1, "<init>", ASTORE, NEW, DUP, ALOAD, "x", - "C$$$anonfun$4", + "$anonfun$t3$1", "<init>", "x", IRETURN)) - assertSameSummary(getMethod(c, "t4"), List(BIPUSH, "C$$$anonfun$5", "boxToInteger", ARETURN)) - assertSameSummary(getMethod(c, "t4a"), List(ICONST_1, LDC, "C$$$anonfun$6", LRETURN)) - assertSameSummary(getMethod(c, "t5"), List(BIPUSH, ICONST_3, "C$$$anonfun$7", "boxToInteger", ARETURN)) - assertSameSummary(getMethod(c, "t5a"), List(BIPUSH, BIPUSH, I2B, "C$$$anonfun$8", IRETURN)) - assertSameSummary(getMethod(c, "t6"), List(BIPUSH, "C$$$anonfun$9", RETURN)) - assertSameSummary(getMethod(c, "t7"), List(ICONST_1, "C$$$anonfun$10", RETURN)) - assertSameSummary(getMethod(c, "t8"), List(ICONST_1, LDC, "C$$$anonfun$11", LRETURN)) - assertSameSummary(getMethod(c, "t9"), List(ICONST_1, "boxToInteger", "C$$$anonfun$12", RETURN)) + assertSameSummary(getMethod(c, "t4"), List(BIPUSH, "$anonfun$t4$1", "boxToInteger", ARETURN)) + assertSameSummary(getMethod(c, "t4a"), List(ICONST_1, LDC, "$anonfun$t4a$1", LRETURN)) + assertSameSummary(getMethod(c, "t5"), List(BIPUSH, ICONST_3, "$anonfun$t5$1", "boxToInteger", ARETURN)) + assertSameSummary(getMethod(c, "t5a"), List(BIPUSH, BIPUSH, I2B, "$anonfun$t5a$1", IRETURN)) + assertSameSummary(getMethod(c, "t6"), List(BIPUSH, "$anonfun$t6$1", RETURN)) + assertSameSummary(getMethod(c, "t7"), List(ICONST_1, "$anonfun$t7$1", RETURN)) + assertSameSummary(getMethod(c, "t8"), List(ICONST_1, LDC, "$anonfun$t8$1", LRETURN)) + assertSameSummary(getMethod(c, "t9"), List(ICONST_1, "boxToInteger", "$anonfun$t9$1", RETURN)) // t9a inlines Range.foreach, which is quite a bit of code, so just testing the core - assertInvoke(getMethod(c, "t9a"), "C", "C$$$anonfun$13") + assertInvoke(getMethod(c, "t9a"), "C", "$anonfun$t9a$1") assertInvoke(getMethod(c, "t9a"), "scala/runtime/BoxesRunTime", "boxToInteger") assertSameSummary(getMethod(c, "t10"), List( ICONST_1, ISTORE, ALOAD, ILOAD, - "C$$$anonfun$14", RETURN)) + "$anonfun$t10$1", RETURN)) // t10a inlines Range.foreach - assertInvoke(getMethod(c, "t10a"), "C", "C$$$anonfun$15") + assertInvoke(getMethod(c, "t10a"), "C", "$anonfun$t10a$1") assertDoesNotInvoke(getMethod(c, "t10a"), "boxToInteger") } @@ -1330,8 +1330,8 @@ class InlinerTest extends BytecodeTesting { """.stripMargin val List(c) = compile(code) assertSameCode(getMethod(c, "t1"), List(Op(ICONST_0), Op(ICONST_1), Op(IADD), Op(IRETURN))) - assertEquals(getInstructions(c, "t2") collect { case i: Invoke => i.owner +"."+ i.name }, List( - "scala/runtime/IntRef.create", "C.C$$$anonfun$1")) + assertEquals(getMethod(c, "t2").instructions collect { case i: Invoke => i.owner +"."+ i.name }, List( + "scala/runtime/IntRef.create", "C.$anonfun$t2$1")) } @Test @@ -1449,9 +1449,9 @@ class InlinerTest extends BytecodeTesting { // box-unbox will clean it up assertSameSummary(getMethod(c, "t"), List( - ALOAD, "C$$$anonfun$1", IFEQ /*A*/, - "C$$$anonfun$2", IRETURN, - -1 /*A*/, "C$$$anonfun$3", IRETURN)) + ALOAD, "$anonfun$t$1", IFEQ /*A*/, + "$anonfun$t$2", IRETURN, + -1 /*A*/, "$anonfun$t$3", IRETURN)) } @Test @@ -1501,7 +1501,7 @@ class InlinerTest extends BytecodeTesting { val List(c) = compile(code) val t = getMethod(c, "t") assertNoIndy(t) - assertInvoke(t, "C", "C$$$anonfun$1") + assertInvoke(t, "C", "$anonfun$t$1") } @Test diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala index 9675e2e445..938bc7b846 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala @@ -221,7 +221,7 @@ class MethodLevelOptsTest extends BytecodeTesting { VarOp(ILOAD, 1), VarOp(ILOAD, 2), VarOp(ILOAD, 3), - Invoke(INVOKESTATIC, "C", "C$$$anonfun$1", "(III)I", false), Op(IRETURN))) + Invoke(INVOKESTATIC, "C", "$anonfun$t$1", "(III)I", false), Op(IRETURN))) } @Test diff --git a/test/junit/scala/tools/testing/BytecodeTesting.scala b/test/junit/scala/tools/testing/BytecodeTesting.scala index b11ad27148..1a0c1e210a 100644 --- a/test/junit/scala/tools/testing/BytecodeTesting.scala +++ b/test/junit/scala/tools/testing/BytecodeTesting.scala @@ -1,5 +1,6 @@ package scala.tools.testing +import junit.framework.AssertionFailedError import org.junit.Assert._ import scala.collection.JavaConverters._ @@ -245,8 +246,13 @@ object BytecodeTesting { getAsmMethods(c, _ == name) def getAsmMethod(c: ClassNode, name: String): MethodNode = { - val List(m) = getAsmMethods(c, name) - m + val methods = getAsmMethods(c, name) + methods match { + case List(m) => m + case ms => + val allNames = getAsmMethods(c, _ => true).map(_.name) + throw new AssertionFailedError(s"Could not find method named $name among ${allNames}") + } } def getMethods(c: ClassNode, name: String): List[Method] = |