Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 129 additions & 116 deletions src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -710,3 +710,23 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {

} // end of trait JAndroidBuilder
}

object BCodeHelpers {

class InvokeStyle(val style: Int) extends AnyVal {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you port this, I think it would also make sense to port the rest of 7a12e81 if it's not too hard.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smarter It is done

import InvokeStyle._
def isVirtual: Boolean = this == Virtual
def isStatic : Boolean = this == Static
def isSpecial: Boolean = this == Special
def isSuper : Boolean = this == Super

def hasInstance = this != Static
}

object InvokeStyle {
val Virtual = new InvokeStyle(0) // InvokeVirtual or InvokeInterface
val Static = new InvokeStyle(1) // InvokeStatic
val Special = new InvokeStyle(2) // InvokeSpecial (private methods, constructors)
val Super = new InvokeStyle(3) // InvokeSpecial (super calls)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ trait BCodeIdiomatic {
def jmethod: asm.MethodVisitor

import asm.Opcodes;
import backend.jvm.Opcodes._

final def emit(opc: Int): Unit = { jmethod.visitInsn(opc) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
var varsInScope: List[(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
Expand Down Expand Up @@ -606,8 +604,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
genLoad(rhs, returnType)

rhs match {
case Block(_, Return(_)) => ()
case Return(_) => ()
case Return(_) | Block(_, Return(_)) | Throw(_) | Block(_, Throw(_)) => ()
case EmptyTree =>
error(NoPosition, "Concrete method has no definition: " + dd + (
if (settings_debug) "(found: " + methSymbol.owner.info.decls.toList.mkString(", ") + ")"
Expand Down
60 changes: 0 additions & 60 deletions src/compiler/scala/tools/nsc/backend/jvm/Opcodes.scala

This file was deleted.

127 changes: 126 additions & 1 deletion test/junit/scala/issues/BytecodeTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand Down Expand Up @@ -77,4 +77,129 @@ class BytecodeTests {
// a @Retention annotation are currently emitted as RUNTIME.
check("B.class", "AnnotB")
}

@Test
def t6288bJumpPosition(): Unit = {
val code =
"""object Case3 { // 01
| def unapply(z: Any): Option[Int] = Some(-1) // 02
| def main(args: Array[String]) { // 03
| ("": Any) match { // 04
| case x : String => // 05
| println("case 0") // 06 println and jump at 6
| case _ => // 07
| println("default") // 08 println and jump at 8
| } // 09
| println("done") // 10
| }
|}
""".stripMargin
val List(mirror, module) = compileClasses(compiler)(code)

val unapplyLineNumbers = getSingleMethod(module, "unapply").instructions.filter(_.isInstanceOf[LineNumber])
assert(unapplyLineNumbers == List(LineNumber(2, Label(0))), unapplyLineNumbers)

val expected = List(
LineNumber(4, Label(0)),
LineNumber(5, Label(5)),
Jump(IFEQ, Label(20)),

LineNumber(6, Label(11)),
Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false),
Jump(GOTO, Label(33)),

LineNumber(5, Label(20)),
Jump(GOTO, Label(24)),

LineNumber(8, Label(24)),
Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false),
Jump(GOTO, Label(33)),

LineNumber(10, Label(33)),
Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false)
)

val mainIns = getSingleMethod(module, "main").instructions filter {
case _: LineNumber | _: Invoke | _: Jump => true
case _ => false
}
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)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,52 @@ class UnreachableCodeTest {
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, "<init>", 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, "<init>", 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, "<init>", ATHROW))
assertEquals(getSingleMethod(cDCE, "t4").instructions.summary, List(
ALOAD, ALOAD, "nt", ATHROW))
}
}