Skip to content

Commit 9b334b2

Browse files
committed
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.
1 parent f70c77e commit 9b334b2

File tree

4 files changed

+69
-12
lines changed

4 files changed

+69
-12
lines changed

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
717717
// for callsites marked `f(): @inline/noinline`. For nullary calls, the attachment
718718
// is on the Select node (not on the Apply node added by UnCurry).
719719
def checkInlineAnnotated(t: Tree): Unit = {
720-
if (t.hasAttachment[InlineAnnotatedAttachment]) bc.jmethod.instructions.getLast match {
720+
if (t.hasAttachment[InlineAnnotatedAttachment]) lastInsn match {
721721
case m: MethodInsnNode =>
722722
if (app.hasAttachment[NoInlineCallsiteAttachment.type]) noInlineAnnotatedCallsites += m
723723
else inlineAnnotatedCallsites += m
@@ -888,10 +888,24 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
888888
* emitted instruction was an ATHROW. As explained above, it is OK to emit a second ATHROW,
889889
* the verifiers will be happy.
890890
*/
891-
emit(asm.Opcodes.ATHROW)
891+
if (lastInsn.getOpcode != asm.Opcodes.ATHROW)
892+
emit(asm.Opcodes.ATHROW)
892893
} else if (from.isNullType) {
893-
bc drop from
894-
emit(asm.Opcodes.ACONST_NULL)
894+
/* After loading an expression of type `scala.runtime.Null$`, introduce POP; ACONST_NULL.
895+
* This is required to pass the verifier: in Scala's type system, Null conforms to any
896+
* reference type. In bytecode, the type Null is represented by scala.runtime.Null$, which
897+
* is not a subtype of all reference types. Example:
898+
*
899+
* def nl: Null = null // in bytecode, nl has return type scala.runtime.Null$
900+
* val a: String = nl // OK for Scala but not for the JVM, scala.runtime.Null$ does not conform to String
901+
*
902+
* In order to fix the above problem, the value returned by nl is dropped and ACONST_NULL is
903+
* inserted instead - after all, an expression of type scala.runtime.Null$ can only be null.
904+
*/
905+
if (lastInsn.getOpcode != asm.Opcodes.ACONST_NULL) {
906+
bc drop from
907+
emit(asm.Opcodes.ACONST_NULL)
908+
}
895909
}
896910
else (from, to) match {
897911
case (BYTE, LONG) | (SHORT, LONG) | (CHAR, LONG) | (INT, LONG) => bc.emitT2T(INT, LONG)

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -437,9 +437,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
437437
var varsInScope: List[Tuple2[Symbol, asm.Label]] = null // (local-var-sym -> start-of-scope)
438438

439439
// helpers around program-points.
440-
def lastInsn: asm.tree.AbstractInsnNode = {
441-
mnode.instructions.getLast
442-
}
440+
def lastInsn: asm.tree.AbstractInsnNode = mnode.instructions.getLast
443441
def currProgramPoint(): asm.Label = {
444442
lastInsn match {
445443
case labnode: asm.tree.LabelNode => labnode.getLabel
@@ -598,13 +596,11 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
598596
genLoad(rhs, returnType)
599597

600598
rhs match {
601-
case Block(_, Return(_)) => ()
602-
case Return(_) => ()
599+
case Return(_) | Block(_, Return(_)) | Throw(_) | Block(_, Throw(_)) => ()
603600
case EmptyTree =>
604601
globalError("Concrete method has no definition: " + dd + (
605602
if (settings.debug) "(found: " + methSymbol.owner.info.decls.toList.mkString(", ") + ")"
606-
else "")
607-
)
603+
else ""))
608604
case _ =>
609605
bc emitRETURN returnType
610606
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1507,7 +1507,6 @@ class InlinerTest extends ClearAfterClass {
15071507
|}
15081508
""".stripMargin
15091509
val List(c) = compile(code)
1510-
val t = getSingleMethod(c, "t")
15111510

15121511
// box-unbox will clean it up
15131512
assertEquals(getSingleMethod(c, "t").instructions.summary,

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

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

0 commit comments

Comments
 (0)