From f70c77e1c4d5b6c951669d79cf73a1ff0b932312 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 12 Feb 2016 14:04:44 +0100 Subject: Generate leaner code for branches GenBCode used to generate more bytecode for branching instructions than GenASM. A simple method def f(x: Int, b: Boolean) = if (b) 1 else 2 would generate ILOAD 2 IFNE L1 GOTO L2 L1 ICONST_1 GOTO L3 L2 ICONST_2 L3 IRETURN If the conditional branch is negated (IFEQ) the GOTO is unnecessary. While -Yopt:l:method would clean this up, it's also not too hard to generate the leaner bytecode in the first place. --- test/junit/scala/issues/BytecodeTest.scala | 83 ++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 4 deletions(-) (limited to 'test') diff --git a/test/junit/scala/issues/BytecodeTest.scala b/test/junit/scala/issues/BytecodeTest.scala index 7260f43c87..ca1b33ed20 100644 --- a/test/junit/scala/issues/BytecodeTest.scala +++ b/test/junit/scala/issues/BytecodeTest.scala @@ -3,7 +3,7 @@ package scala.issues import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.Test -import scala.tools.asm.Opcodes +import scala.tools.asm.Opcodes._ import scala.tools.nsc.backend.jvm.AsmUtils import scala.tools.nsc.backend.jvm.CodeGenTools._ import org.junit.Assert._ @@ -105,12 +105,10 @@ class BytecodeTest extends ClearAfterClass { val unapplyLineNumbers = getSingleMethod(module, "unapply").instructions.filter(_.isInstanceOf[LineNumber]) assert(unapplyLineNumbers == List(LineNumber(2, Label(0))), unapplyLineNumbers) - import Opcodes._ val expected = List( LineNumber(4, Label(0)), LineNumber(5, Label(5)), - Jump(IFNE, Label(11)), - Jump(GOTO, Label(20)), + Jump(IFEQ, Label(20)), LineNumber(6, Label(11)), Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false), @@ -133,4 +131,81 @@ class BytecodeTest extends ClearAfterClass { } assertSameCode(mainIns, expected) } + + @Test + def bytecodeForBranches(): Unit = { + val code = + """class C { + | def t1(b: Boolean) = if (b) 1 else 2 + | def t2(x: Int) = if (x == 393) 1 else 2 + | def t3(a: Array[String], b: AnyRef) = a != b && b == a + | def t4(a: AnyRef) = a == null || null != a + | def t5(a: AnyRef) = (a eq null) || (null ne a) + | def t6(a: Int, b: Boolean) = if ((a == 10) && b || a != 1) 1 else 2 + | def t7(a: AnyRef, b: AnyRef) = a == b + | def t8(a: AnyRef) = Nil == a || "" != a + |} + """.stripMargin + + val List(c) = compileClasses(compiler)(code) + + // t1: no unnecessary GOTOs + assertSameCode(getSingleMethod(c, "t1").instructions.dropNonOp, List( + VarOp(ILOAD, 1), Jump(IFEQ, Label(6)), + Op(ICONST_1), Jump(GOTO, Label(9)), + Label(6), Op(ICONST_2), + Label(9), Op(IRETURN))) + + // t2: no unnecessary GOTOs + assertSameCode(getSingleMethod(c, "t2").instructions.dropNonOp, List( + VarOp(ILOAD, 1), IntOp(SIPUSH, 393), Jump(IF_ICMPNE, Label(7)), + Op(ICONST_1), Jump(GOTO, Label(10)), + Label(7), Op(ICONST_2), + Label(10), Op(IRETURN))) + + // t3: Array == is translated to reference equality, AnyRef == to null checks and equals + assertSameCode(getSingleMethod(c, "t3").instructions.dropNonOp, List( + // Array == + VarOp(ALOAD, 1), VarOp(ALOAD, 2), Jump(IF_ACMPEQ, Label(23)), + // AnyRef == + VarOp(ALOAD, 2), VarOp(ALOAD, 1), VarOp(ASTORE, 3), Op(DUP), Jump(IFNONNULL, Label(14)), + Op(POP), VarOp(ALOAD, 3), Jump(IFNULL, Label(19)), Jump(GOTO, Label(23)), + Label(14), VarOp(ALOAD, 3), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFEQ, Label(23)), + Label(19), Op(ICONST_1), Jump(GOTO, Label(26)), + Label(23), Op(ICONST_0), + Label(26), Op(IRETURN))) + + val t4t5 = List( + VarOp(ALOAD, 1), Jump(IFNULL, Label(6)), + VarOp(ALOAD, 1), Jump(IFNULL, Label(10)), + Label(6), Op(ICONST_1), Jump(GOTO, Label(13)), + Label(10), Op(ICONST_0), + Label(13), Op(IRETURN)) + + // t4: one side is known null, so just a null check on the other + assertSameCode(getSingleMethod(c, "t4").instructions.dropNonOp, t4t5) + + // t5: one side known null, so just a null check on the other + assertSameCode(getSingleMethod(c, "t5").instructions.dropNonOp, t4t5) + + // t6: no unnecessary GOTOs + assertSameCode(getSingleMethod(c, "t6").instructions.dropNonOp, List( + VarOp(ILOAD, 1), IntOp(BIPUSH, 10), Jump(IF_ICMPNE, Label(7)), + VarOp(ILOAD, 2), Jump(IFNE, Label(12)), + Label(7), VarOp(ILOAD, 1), Op(ICONST_1), Jump(IF_ICMPEQ, Label(16)), + Label(12), Op(ICONST_1), Jump(GOTO, Label(19)), + Label(16), Op(ICONST_2), + Label(19), Op(IRETURN))) + + // t7: universal equality + assertInvoke(getSingleMethod(c, "t7"), "scala/runtime/BoxesRunTime", "equals") + + // t8: no null checks invoking equals on modules and constants + assertSameCode(getSingleMethod(c, "t8").instructions.dropNonOp, List( + Field(GETSTATIC, "scala/collection/immutable/Nil$", "MODULE$", "Lscala/collection/immutable/Nil$;"), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFNE, Label(10)), + Ldc(LDC, ""), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFNE, Label(14)), + Label(10), Op(ICONST_1), Jump(GOTO, Label(17)), + Label(14), Op(ICONST_0), + Label(17), Op(IRETURN))) + } } -- cgit v1.2.3 From 9b334b2ea3f9eee0582447658dcf2ac244121644 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Sun, 14 Feb 2016 21:20:54 +0100 Subject: Avoid generating ACONST_NULL; POP; ACONST_NULL when loading null When loading a value of type scala.runtime.Null$ we need to add POP; ACONST_NULL, see comment in BCodeBodyBuilder.adapt. This is however not necessary if the null value is a simple ACONST_NULL. This patch eliminates that redundancy. --- .../tools/nsc/backend/jvm/BCodeBodyBuilder.scala | 22 ++++++++-- .../tools/nsc/backend/jvm/BCodeSkelBuilder.scala | 10 ++--- .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 1 - .../nsc/backend/jvm/opt/UnreachableCodeTest.scala | 48 ++++++++++++++++++++++ 4 files changed, 69 insertions(+), 12 deletions(-) (limited to 'test') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 1e7ea0c09d..8a90eb9780 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -717,7 +717,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // for callsites marked `f(): @inline/noinline`. For nullary calls, the attachment // is on the Select node (not on the Apply node added by UnCurry). def checkInlineAnnotated(t: Tree): Unit = { - if (t.hasAttachment[InlineAnnotatedAttachment]) bc.jmethod.instructions.getLast match { + if (t.hasAttachment[InlineAnnotatedAttachment]) lastInsn match { case m: MethodInsnNode => if (app.hasAttachment[NoInlineCallsiteAttachment.type]) noInlineAnnotatedCallsites += m else inlineAnnotatedCallsites += m @@ -888,10 +888,24 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { * emitted instruction was an ATHROW. As explained above, it is OK to emit a second ATHROW, * the verifiers will be happy. */ - emit(asm.Opcodes.ATHROW) + if (lastInsn.getOpcode != asm.Opcodes.ATHROW) + emit(asm.Opcodes.ATHROW) } else if (from.isNullType) { - bc drop from - emit(asm.Opcodes.ACONST_NULL) + /* After loading an expression of type `scala.runtime.Null$`, introduce POP; ACONST_NULL. + * This is required to pass the verifier: in Scala's type system, Null conforms to any + * reference type. In bytecode, the type Null is represented by scala.runtime.Null$, which + * is not a subtype of all reference types. Example: + * + * def nl: Null = null // in bytecode, nl has return type scala.runtime.Null$ + * val a: String = nl // OK for Scala but not for the JVM, scala.runtime.Null$ does not conform to String + * + * In order to fix the above problem, the value returned by nl is dropped and ACONST_NULL is + * inserted instead - after all, an expression of type scala.runtime.Null$ can only be null. + */ + if (lastInsn.getOpcode != asm.Opcodes.ACONST_NULL) { + bc drop from + emit(asm.Opcodes.ACONST_NULL) + } } else (from, to) match { case (BYTE, LONG) | (SHORT, LONG) | (CHAR, LONG) | (INT, LONG) => bc.emitT2T(INT, LONG) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 9dea21154f..96796b3244 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -437,9 +437,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { var varsInScope: List[Tuple2[Symbol, asm.Label]] = null // (local-var-sym -> start-of-scope) // helpers around program-points. - def lastInsn: asm.tree.AbstractInsnNode = { - mnode.instructions.getLast - } + def lastInsn: asm.tree.AbstractInsnNode = mnode.instructions.getLast def currProgramPoint(): asm.Label = { lastInsn match { case labnode: asm.tree.LabelNode => labnode.getLabel @@ -598,13 +596,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { genLoad(rhs, returnType) rhs match { - case Block(_, Return(_)) => () - case Return(_) => () + case Return(_) | Block(_, Return(_)) | Throw(_) | Block(_, Throw(_)) => () case EmptyTree => globalError("Concrete method has no definition: " + dd + ( if (settings.debug) "(found: " + methSymbol.owner.info.decls.toList.mkString(", ") + ")" - else "") - ) + else "")) case _ => bc emitRETURN returnType } 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 942b62b32c..70f3060027 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -1507,7 +1507,6 @@ class InlinerTest extends ClearAfterClass { |} """.stripMargin val List(c) = compile(code) - val t = getSingleMethod(c, "t") // box-unbox will clean it up assertEquals(getSingleMethod(c, "t").instructions.summary, diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala index 86c8baa3c6..62ee09e9de 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala @@ -215,4 +215,52 @@ class UnreachableCodeTest extends ClearAfterClass { assertTrue(List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(3)), List("java/lang/Object", Label(4))), Label(3), Label(4)) === List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(1)), List("java/lang/Object", Label(3))), Label(1), Label(3))) } + + @Test + def loadNullNothingBytecode(): Unit = { + val code = + """class C { + | def nl: Null = null + | def nt: Nothing = throw new Error("") + | def cons(a: Any) = () + | + | def t1 = cons(null) + | def t2 = cons(nl) + | def t3 = cons(throw new Error("")) + | def t4 = cons(nt) + |} + """.stripMargin + val List(c) = compileClasses(noOptCompiler)(code) + + assertEquals(getSingleMethod(c, "nl").instructions.summary, List(ACONST_NULL, ARETURN)) + + assertEquals(getSingleMethod(c, "nt").instructions.summary, List( + NEW, DUP, LDC, "", ATHROW)) + + assertEquals(getSingleMethod(c, "t1").instructions.summary, List( + ALOAD, ACONST_NULL, "cons", RETURN)) + + // GenBCode introduces POP; ACONST_NULL after loading an expression of type scala.runtime.Null$, + // see comment in BCodeBodyBuilder.adapt + assertEquals(getSingleMethod(c, "t2").instructions.summary, List( + ALOAD, ALOAD, "nl", POP, ACONST_NULL, "cons", RETURN)) + + // the bytecode generated by GenBCode is ... ATHROW; INVOKEVIRTUAL C.cons; RETURN + // the ASM classfile writer creates a new basic block (creates a label) right after the ATHROW + // and replaces all instructions by NOP*; ATHROW, see comment in BCodeBodyBuilder.adapt + // NOTE: DCE is enabled by default and gets rid of the redundant code (tested below) + assertEquals(getSingleMethod(c, "t3").instructions.summary, List( + ALOAD, NEW, DUP, LDC, "", ATHROW, NOP, NOP, NOP, ATHROW)) + + // GenBCode introduces an ATHROW after the invocation of C.nt, see BCodeBodyBuilder.adapt + // NOTE: DCE is enabled by default and gets rid of the redundant code (tested below) + assertEquals(getSingleMethod(c, "t4").instructions.summary, List( + ALOAD, ALOAD, "nt", ATHROW, NOP, NOP, NOP, ATHROW)) + + val List(cDCE) = compileClasses(dceCompiler)(code) + assertEquals(getSingleMethod(cDCE, "t3").instructions.summary, List( + ALOAD, NEW, DUP, LDC, "", ATHROW)) + assertEquals(getSingleMethod(cDCE, "t4").instructions.summary, List( + ALOAD, ALOAD, "nt", ATHROW)) + } } -- cgit v1.2.3