From d39525ac06c8d319b43d3388d624fb2c1dcbd601 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 13 Oct 2014 14:34:24 +0200 Subject: GenBCode: Compact local variable slots After eliminating unreachable code, there may be unused local varaible slots. Such gaps increase the method's frame size unnecessarily. The optimization does not attempt to re-use the same local variable slot for non-overlapping locals (proper register allocation): def f(b: Boolean) = if (b) { val x = e; x } else { val y = e; y } x and y will not use the same slot, even though they could. This was originally implemented by Miguel in https://github.com/lrytz/scala/commit/09c40359338f8770e4f99d045999af062112973e --- .../jvm/opt/CompactLocalVariablesTest.scala | 80 ++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala (limited to 'test/junit') diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala new file mode 100644 index 0000000000..fc748196d0 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala @@ -0,0 +1,80 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ + +@RunWith(classOf[JUnit4]) +class CompactLocalVariablesTest { + + // recurse-unreachable-jumps is required for eliminating catch blocks, in the first dce round they + // are still live.only after eliminating the empty handler the catch blocks become unreachable. + val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,recurse-unreachable-jumps,compact-locals") + val noCompactVarsCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,recurse-unreachable-jumps") + + @Test + def compactUnused(): Unit = { + val code = + """def f: Double = { + | try { } + | catch { + | case _: Throwable => + | // eliminated by dce + | val i = 1 + | val d = 1d + | val f = 1f + | val l = 1l + | } + | + | val i = 1 // variable index 1 (it's an instance method, so at index 0 we have `this`) + | val d = 1d // 2,3 + | val f = 1f // 4 + | val l = 1l // 5,6 + | + | try { } + | catch { + | case _: Throwable => + | // eliminated by dce + | val i = 1 + | val d = 1d + | val f = 1f + | val l = 1l + | } + | + | val ii = 1 // 7 + | val dd = 1d // 8,9 + | val ff = 1f // 10 + | val ll = 1l // 11,12 + | + | i + ii + d + dd + f + ff + l + ll + |} + |""".stripMargin + + val List(noCompact) = compileMethods(noCompactVarsCompiler)(code) + val List(withCompact) = compileMethods(methodOptCompiler)(code) + + // code is the same, except for local var indices + assertTrue(noCompact.instructions.size == withCompact.instructions.size) + + val varOpSlots = convertMethod(withCompact).instructions collect { + case VarOp(_, v) => v + } + assertTrue(varOpSlots.toString, varOpSlots == List(1, 2, 4, 5, 7, 8, 10, 11, // stores + 1, 7, 2, 8, 4, 10, 5, 11)) // loads + + // the local variables descriptor table is cleaned up to remove stale entries after dce, + // also when the slots are not compacted + assertTrue(noCompact.localVariables.size == withCompact.localVariables.size) + + assertTrue(noCompact.maxLocals == 25) + assertTrue(withCompact.maxLocals == 13) + } +} -- cgit v1.2.3