summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/asm/scala/tools/asm/MethodWriter.java10
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala19
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala19
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala86
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala2
6 files changed, 134 insertions, 4 deletions
diff --git a/src/asm/scala/tools/asm/MethodWriter.java b/src/asm/scala/tools/asm/MethodWriter.java
index 0c4130e499..d30e04c625 100644
--- a/src/asm/scala/tools/asm/MethodWriter.java
+++ b/src/asm/scala/tools/asm/MethodWriter.java
@@ -37,7 +37,7 @@ package scala.tools.asm;
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
-class MethodWriter extends MethodVisitor {
+public class MethodWriter extends MethodVisitor {
/**
* Pseudo access flag used to denote constructors.
@@ -235,11 +235,19 @@ class MethodWriter extends MethodVisitor {
*/
private int maxStack;
+ public int getMaxStack() {
+ return maxStack;
+ }
+
/**
* Maximum number of local variables for this method.
*/
private int maxLocals;
+ public int getMaxLocals() {
+ return maxLocals;
+ }
+
/**
* Number of local variables in the current stack map frame.
*/
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
index 7bf61b4f51..53ac5bfdc7 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
@@ -713,6 +713,8 @@ abstract class BTypes {
// TODO @lry I don't really understand the reasoning here.
// Both this and other are classes. The code takes (transitively) all superclasses and
// finds the first common one.
+ // MOST LIKELY the answer can be found here, see the comments and links by Miguel:
+ // - https://issues.scala-lang.org/browse/SI-3872
firstCommonSuffix(this :: this.superClassesTransitive, other :: other.superClassesTransitive)
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
new file mode 100644
index 0000000000..921aef7aca
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendStats.scala
@@ -0,0 +1,19 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+
+import scala.reflect.internal.util.Statistics
+
+object BackendStats {
+ import Statistics.{newTimer, newSubTimer}
+ val bcodeTimer = newTimer("time in backend", "jvm")
+
+ val bcodeInitTimer = newSubTimer("bcode initialization", bcodeTimer)
+ val bcodeGenStat = newSubTimer("code generation", bcodeTimer)
+ val bcodeDceTimer = newSubTimer("dead code elimination", bcodeTimer)
+ val bcodeWriteTimer = newSubTimer("classfile writing", bcodeTimer)
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
index 0a7c894a69..a7cc1f69cf 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala
@@ -11,6 +11,7 @@ package jvm
import scala.collection.{ mutable, immutable }
import scala.annotation.switch
+import scala.reflect.internal.util.Statistics
import scala.tools.asm
@@ -222,8 +223,12 @@ abstract class GenBCode extends BCodeSyncAndTry {
return
}
else {
- try { addToQ3(item) }
- catch {
+ try {
+ val dceStart = Statistics.startTimer(BackendStats.bcodeDceTimer)
+ opt.LocalOpt.removeUnreachableCode(item.plain)
+ Statistics.stopTimer(BackendStats.bcodeDceTimer, dceStart)
+ addToQ3(item)
+ } catch {
case ex: Throwable =>
ex.printStackTrace()
error(s"Error while emitting ${item.plain.name}\n${ex.getMessage}")
@@ -272,10 +277,13 @@ abstract class GenBCode extends BCodeSyncAndTry {
*
*/
override def run() {
+ val bcodeStart = Statistics.startTimer(BackendStats.bcodeTimer)
+ val initStart = Statistics.startTimer(BackendStats.bcodeInitTimer)
arrivalPos = 0 // just in case
scalaPrimitives.init()
bTypes.intializeCoreBTypes()
+ Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart)
// initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated.
bytecodeWriter = initBytecodeWriter(cleanup.getEntryPoints)
@@ -287,6 +295,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
// closing output files.
bytecodeWriter.close()
+ Statistics.stopTimer(BackendStats.bcodeTimer, bcodeStart)
/* TODO Bytecode can be verified (now that all classfiles have been written to disk)
*
@@ -312,9 +321,15 @@ abstract class GenBCode extends BCodeSyncAndTry {
private def buildAndSendToDisk(needsOutFolder: Boolean) {
feedPipeline1()
+ val genStart = Statistics.startTimer(BackendStats.bcodeGenStat)
(new Worker1(needsOutFolder)).run()
+ Statistics.stopTimer(BackendStats.bcodeGenStat, genStart)
+
(new Worker2).run()
+
+ val writeStart = Statistics.startTimer(BackendStats.bcodeWriteTimer)
drainQ3()
+ Statistics.stopTimer(BackendStats.bcodeWriteTimer, writeStart)
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
new file mode 100644
index 0000000000..b3a515c2ae
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala
@@ -0,0 +1,86 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2014 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import scala.tools.asm.{MethodWriter, ClassWriter}
+import scala.tools.asm.tree.analysis.{Analyzer, BasicValue, BasicInterpreter}
+import scala.tools.asm.tree.{LabelNode, ClassNode, MethodNode}
+import scala.collection.convert.decorateAsScala._
+
+/**
+ * Intra-Method optimizations.
+ */
+object LocalOpt {
+ /**
+ * Remove unreachable instructions from all (non-abstract) methods.
+ *
+ * @param clazz The class whose methods are optimized
+ * @return `true` if unreachable code was elminated in some method, `false` otherwise.
+ */
+ def removeUnreachableCode(clazz: ClassNode): Boolean = {
+ clazz.methods.asScala.foldLeft(false) {
+ case (changed, method) => removeUnreachableCode(method, clazz) || changed
+ }
+ }
+
+ /**
+ * Remove unreachable code from a method.
+ * We rely on dead code elimination provided by the ASM framework, as described in the ASM User
+ * Guide (http://asm.ow2.org/index.html), Section 8.2.1. It runs a data flow analysis, which only
+ * computes Frame information for reachable instructions. Instructions for which no Frame data is
+ * available after the analyis are unreachable.
+ */
+ private def removeUnreachableCode(method: MethodNode, ownerClass: ClassNode): Boolean = {
+ val initialSize = method.instructions.size
+ if (initialSize == 0) return false
+
+ // The data flow analysis requires the maxLocals / maxStack fields of the method to be computed.
+ computeMaxLocalsMaxStack(method)
+ val a = new Analyzer[BasicValue](new BasicInterpreter)
+ a.analyze(ownerClass.name, method)
+ val frames = a.getFrames
+
+ var i = 0
+ val itr = method.instructions.iterator()
+ while (itr.hasNext) {
+ val ins = itr.next()
+ // Don't remove label nodes: they might be referenced for example in a LocalVariableNode
+ if (frames(i) == null && !ins.isInstanceOf[LabelNode]) {
+ // Instruction iterators allow removing during iteration.
+ // Removing is O(1): instructions are doubly linked list elements.
+ itr.remove()
+ }
+ i += 1
+ }
+
+ method.instructions.size == initialSize
+ }
+
+ /**
+ * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM
+ * framework only computes these values during bytecode generation.
+ *
+ * Sicne there's currently no better way, we run a bytecode generator on the method and extract
+ * the computed values. This required changes to the ASM codebase:
+ * - the [[MethodWriter]] class was made public
+ * - accessors for maxLocals / maxStack were added to the MethodWriter class
+ *
+ * We could probably make this faster (and allocate less memory) by hacking the ASM framework
+ * more: create a subclass of MethodWriter with a /dev/null byteVector. Another option would be
+ * to create a separate visitor for computing those values, duplicating the functionality from the
+ * MethodWriter.
+ */
+ private def computeMaxLocalsMaxStack(method: MethodNode) {
+ val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS)
+ val excs = method.exceptions.asScala.toArray
+ val mw = cw.visitMethod(method.access, method.name, method.desc, method.signature, excs).asInstanceOf[MethodWriter]
+ method.accept(mw)
+ method.maxLocals = mw.getMaxLocals
+ method.maxStack = mw.getMaxStack
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index 91b03869e5..98eb831a9d 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -211,7 +211,7 @@ trait ScalaSettings extends AbsScalaSettings
private def removalIn212 = "This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug."
- object YstatisticsPhases extends MultiChoiceEnumeration { val parser, typer, patmat, erasure, cleanup = Value }
+ object YstatisticsPhases extends MultiChoiceEnumeration { val parser, typer, patmat, erasure, cleanup, jvm = Value }
val Ystatistics = {
val description = "Print compiler statistics for specific phases"
MultiChoiceSetting(