Skip to content

Commit 2d90208

Browse files
Merge pull request #23 from nicolasstucki/port-#4963-2
Port scala#4963
2 parents cee714f + 040ccf2 commit 2d90208

File tree

7 files changed

+325
-183
lines changed

7 files changed

+325
-183
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala

Lines changed: 129 additions & 116 deletions
Large diffs are not rendered by default.

src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,3 +710,23 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
710710

711711
} // end of trait JAndroidBuilder
712712
}
713+
714+
object BCodeHelpers {
715+
716+
class InvokeStyle(val style: Int) extends AnyVal {
717+
import InvokeStyle._
718+
def isVirtual: Boolean = this == Virtual
719+
def isStatic : Boolean = this == Static
720+
def isSpecial: Boolean = this == Special
721+
def isSuper : Boolean = this == Super
722+
723+
def hasInstance = this != Static
724+
}
725+
726+
object InvokeStyle {
727+
val Virtual = new InvokeStyle(0) // InvokeVirtual or InvokeInterface
728+
val Static = new InvokeStyle(1) // InvokeStatic
729+
val Special = new InvokeStyle(2) // InvokeSpecial (private methods, constructors)
730+
val Super = new InvokeStyle(3) // InvokeSpecial (super calls)
731+
}
732+
}

src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ trait BCodeIdiomatic {
113113
def jmethod: asm.MethodVisitor
114114

115115
import asm.Opcodes;
116-
import backend.jvm.Opcodes._
117116

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

src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -435,9 +435,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
435435
var varsInScope: List[(Symbol, asm.Label)] = null // (local-var-sym -> start-of-scope)
436436

437437
// helpers around program-points.
438-
def lastInsn: asm.tree.AbstractInsnNode = {
439-
mnode.instructions.getLast
440-
}
438+
def lastInsn: asm.tree.AbstractInsnNode = mnode.instructions.getLast
441439
def currProgramPoint(): asm.Label = {
442440
lastInsn match {
443441
case labnode: asm.tree.LabelNode => labnode.getLabel
@@ -606,8 +604,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
606604
genLoad(rhs, returnType)
607605

608606
rhs match {
609-
case Block(_, Return(_)) => ()
610-
case Return(_) => ()
607+
case Return(_) | Block(_, Return(_)) | Throw(_) | Block(_, Throw(_)) => ()
611608
case EmptyTree =>
612609
error(NoPosition, "Concrete method has no definition: " + dd + (
613610
if (settings_debug) "(found: " + methSymbol.owner.info.decls.toList.mkString(", ") + ")"

src/compiler/scala/tools/nsc/backend/jvm/Opcodes.scala

Lines changed: 0 additions & 60 deletions
This file was deleted.

test/junit/scala/issues/BytecodeTests.scala

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package scala.issues
33
import org.junit.runner.RunWith
44
import org.junit.runners.JUnit4
55
import org.junit.Test
6-
import scala.tools.asm.Opcodes
6+
import scala.tools.asm.Opcodes._
77
import scala.tools.nsc.backend.jvm.AsmUtils
88
import scala.tools.nsc.backend.jvm.CodeGenTools._
99
import org.junit.Assert._
@@ -77,4 +77,129 @@ class BytecodeTests {
7777
// a @Retention annotation are currently emitted as RUNTIME.
7878
check("B.class", "AnnotB")
7979
}
80+
81+
@Test
82+
def t6288bJumpPosition(): Unit = {
83+
val code =
84+
"""object Case3 { // 01
85+
| def unapply(z: Any): Option[Int] = Some(-1) // 02
86+
| def main(args: Array[String]) { // 03
87+
| ("": Any) match { // 04
88+
| case x : String => // 05
89+
| println("case 0") // 06 println and jump at 6
90+
| case _ => // 07
91+
| println("default") // 08 println and jump at 8
92+
| } // 09
93+
| println("done") // 10
94+
| }
95+
|}
96+
""".stripMargin
97+
val List(mirror, module) = compileClasses(compiler)(code)
98+
99+
val unapplyLineNumbers = getSingleMethod(module, "unapply").instructions.filter(_.isInstanceOf[LineNumber])
100+
assert(unapplyLineNumbers == List(LineNumber(2, Label(0))), unapplyLineNumbers)
101+
102+
val expected = List(
103+
LineNumber(4, Label(0)),
104+
LineNumber(5, Label(5)),
105+
Jump(IFEQ, Label(20)),
106+
107+
LineNumber(6, Label(11)),
108+
Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false),
109+
Jump(GOTO, Label(33)),
110+
111+
LineNumber(5, Label(20)),
112+
Jump(GOTO, Label(24)),
113+
114+
LineNumber(8, Label(24)),
115+
Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false),
116+
Jump(GOTO, Label(33)),
117+
118+
LineNumber(10, Label(33)),
119+
Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false)
120+
)
121+
122+
val mainIns = getSingleMethod(module, "main").instructions filter {
123+
case _: LineNumber | _: Invoke | _: Jump => true
124+
case _ => false
125+
}
126+
assertSameCode(mainIns, expected)
127+
}
128+
129+
@Test
130+
def bytecodeForBranches(): Unit = {
131+
val code =
132+
"""class C {
133+
| def t1(b: Boolean) = if (b) 1 else 2
134+
| def t2(x: Int) = if (x == 393) 1 else 2
135+
| def t3(a: Array[String], b: AnyRef) = a != b && b == a
136+
| def t4(a: AnyRef) = a == null || null != a
137+
| def t5(a: AnyRef) = (a eq null) || (null ne a)
138+
| def t6(a: Int, b: Boolean) = if ((a == 10) && b || a != 1) 1 else 2
139+
| def t7(a: AnyRef, b: AnyRef) = a == b
140+
| def t8(a: AnyRef) = Nil == a || "" != a
141+
|}
142+
""".stripMargin
143+
144+
val List(c) = compileClasses(compiler)(code)
145+
146+
// t1: no unnecessary GOTOs
147+
assertSameCode(getSingleMethod(c, "t1").instructions.dropNonOp, List(
148+
VarOp(ILOAD, 1), Jump(IFEQ, Label(6)),
149+
Op(ICONST_1), Jump(GOTO, Label(9)),
150+
Label(6), Op(ICONST_2),
151+
Label(9), Op(IRETURN)))
152+
153+
// t2: no unnecessary GOTOs
154+
assertSameCode(getSingleMethod(c, "t2").instructions.dropNonOp, List(
155+
VarOp(ILOAD, 1), IntOp(SIPUSH, 393), Jump(IF_ICMPNE, Label(7)),
156+
Op(ICONST_1), Jump(GOTO, Label(10)),
157+
Label(7), Op(ICONST_2),
158+
Label(10), Op(IRETURN)))
159+
160+
// t3: Array == is translated to reference equality, AnyRef == to null checks and equals
161+
assertSameCode(getSingleMethod(c, "t3").instructions.dropNonOp, List(
162+
// Array ==
163+
VarOp(ALOAD, 1), VarOp(ALOAD, 2), Jump(IF_ACMPEQ, Label(23)),
164+
// AnyRef ==
165+
VarOp(ALOAD, 2), VarOp(ALOAD, 1), VarOp(ASTORE, 3), Op(DUP), Jump(IFNONNULL, Label(14)),
166+
Op(POP), VarOp(ALOAD, 3), Jump(IFNULL, Label(19)), Jump(GOTO, Label(23)),
167+
Label(14), VarOp(ALOAD, 3), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFEQ, Label(23)),
168+
Label(19), Op(ICONST_1), Jump(GOTO, Label(26)),
169+
Label(23), Op(ICONST_0),
170+
Label(26), Op(IRETURN)))
171+
172+
val t4t5 = List(
173+
VarOp(ALOAD, 1), Jump(IFNULL, Label(6)),
174+
VarOp(ALOAD, 1), Jump(IFNULL, Label(10)),
175+
Label(6), Op(ICONST_1), Jump(GOTO, Label(13)),
176+
Label(10), Op(ICONST_0),
177+
Label(13), Op(IRETURN))
178+
179+
// t4: one side is known null, so just a null check on the other
180+
assertSameCode(getSingleMethod(c, "t4").instructions.dropNonOp, t4t5)
181+
182+
// t5: one side known null, so just a null check on the other
183+
assertSameCode(getSingleMethod(c, "t5").instructions.dropNonOp, t4t5)
184+
185+
// t6: no unnecessary GOTOs
186+
assertSameCode(getSingleMethod(c, "t6").instructions.dropNonOp, List(
187+
VarOp(ILOAD, 1), IntOp(BIPUSH, 10), Jump(IF_ICMPNE, Label(7)),
188+
VarOp(ILOAD, 2), Jump(IFNE, Label(12)),
189+
Label(7), VarOp(ILOAD, 1), Op(ICONST_1), Jump(IF_ICMPEQ, Label(16)),
190+
Label(12), Op(ICONST_1), Jump(GOTO, Label(19)),
191+
Label(16), Op(ICONST_2),
192+
Label(19), Op(IRETURN)))
193+
194+
// t7: universal equality
195+
assertInvoke(getSingleMethod(c, "t7"), "scala/runtime/BoxesRunTime", "equals")
196+
197+
// t8: no null checks invoking equals on modules and constants
198+
assertSameCode(getSingleMethod(c, "t8").instructions.dropNonOp, List(
199+
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)),
200+
Ldc(LDC, ""), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFNE, Label(14)),
201+
Label(10), Op(ICONST_1), Jump(GOTO, Label(17)),
202+
Label(14), Op(ICONST_0),
203+
Label(17), Op(IRETURN)))
204+
}
80205
}

test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,52 @@ class UnreachableCodeTest {
208208
assertTrue(List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(3)), List("java/lang/Object", Label(4))), Label(3), Label(4)) ===
209209
List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(1)), List("java/lang/Object", Label(3))), Label(1), Label(3)))
210210
}
211+
212+
@Test
213+
def loadNullNothingBytecode(): Unit = {
214+
val code =
215+
"""class C {
216+
| def nl: Null = null
217+
| def nt: Nothing = throw new Error("")
218+
| def cons(a: Any) = ()
219+
|
220+
| def t1 = cons(null)
221+
| def t2 = cons(nl)
222+
| def t3 = cons(throw new Error(""))
223+
| def t4 = cons(nt)
224+
|}
225+
""".stripMargin
226+
val List(c) = compileClasses(noOptCompiler)(code)
227+
228+
assertEquals(getSingleMethod(c, "nl").instructions.summary, List(ACONST_NULL, ARETURN))
229+
230+
assertEquals(getSingleMethod(c, "nt").instructions.summary, List(
231+
NEW, DUP, LDC, "<init>", ATHROW))
232+
233+
assertEquals(getSingleMethod(c, "t1").instructions.summary, List(
234+
ALOAD, ACONST_NULL, "cons", RETURN))
235+
236+
// GenBCode introduces POP; ACONST_NULL after loading an expression of type scala.runtime.Null$,
237+
// see comment in BCodeBodyBuilder.adapt
238+
assertEquals(getSingleMethod(c, "t2").instructions.summary, List(
239+
ALOAD, ALOAD, "nl", POP, ACONST_NULL, "cons", RETURN))
240+
241+
// the bytecode generated by GenBCode is ... ATHROW; INVOKEVIRTUAL C.cons; RETURN
242+
// the ASM classfile writer creates a new basic block (creates a label) right after the ATHROW
243+
// and replaces all instructions by NOP*; ATHROW, see comment in BCodeBodyBuilder.adapt
244+
// NOTE: DCE is enabled by default and gets rid of the redundant code (tested below)
245+
assertEquals(getSingleMethod(c, "t3").instructions.summary, List(
246+
ALOAD, NEW, DUP, LDC, "<init>", ATHROW, NOP, NOP, NOP, ATHROW))
247+
248+
// GenBCode introduces an ATHROW after the invocation of C.nt, see BCodeBodyBuilder.adapt
249+
// NOTE: DCE is enabled by default and gets rid of the redundant code (tested below)
250+
assertEquals(getSingleMethod(c, "t4").instructions.summary, List(
251+
ALOAD, ALOAD, "nt", ATHROW, NOP, NOP, NOP, ATHROW))
252+
253+
val List(cDCE) = compileClasses(dceCompiler)(code)
254+
assertEquals(getSingleMethod(cDCE, "t3").instructions.summary, List(
255+
ALOAD, NEW, DUP, LDC, "<init>", ATHROW))
256+
assertEquals(getSingleMethod(cDCE, "t4").instructions.summary, List(
257+
ALOAD, ALOAD, "nt", ATHROW))
258+
}
211259
}

0 commit comments

Comments
 (0)