Skip to content

Commit f287f58

Browse files
lrytznicolasstucki
authored andcommitted
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. Port of 9b334b2
1 parent fb0f8f1 commit f287f58

File tree

3 files changed

+67
-8
lines changed

3 files changed

+67
-8
lines changed

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -944,10 +944,24 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
944944
* emitted instruction was an ATHROW. As explained above, it is OK to emit a second ATHROW,
945945
* the verifiers will be happy.
946946
*/
947-
emit(asm.Opcodes.ATHROW)
947+
if (lastInsn.getOpcode != asm.Opcodes.ATHROW)
948+
emit(asm.Opcodes.ATHROW)
948949
} else if (from.isNullType) {
949-
bc drop from
950-
emit(asm.Opcodes.ACONST_NULL)
950+
/* After loading an expression of type `scala.runtime.Null$`, introduce POP; ACONST_NULL.
951+
* This is required to pass the verifier: in Scala's type system, Null conforms to any
952+
* reference type. In bytecode, the type Null is represented by scala.runtime.Null$, which
953+
* is not a subtype of all reference types. Example:
954+
*
955+
* def nl: Null = null // in bytecode, nl has return type scala.runtime.Null$
956+
* val a: String = nl // OK for Scala but not for the JVM, scala.runtime.Null$ does not conform to String
957+
*
958+
* In order to fix the above problem, the value returned by nl is dropped and ACONST_NULL is
959+
* inserted instead - after all, an expression of type scala.runtime.Null$ can only be null.
960+
*/
961+
if (lastInsn.getOpcode != asm.Opcodes.ACONST_NULL) {
962+
bc drop from
963+
emit(asm.Opcodes.ACONST_NULL)
964+
}
951965
}
952966
else (from, to) match {
953967
case (BYTE, LONG) | (SHORT, LONG) | (CHAR, LONG) | (INT, LONG) => bc.emitT2T(INT, LONG)

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(", ") + ")"

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)