From 5d12fa4b791e73d5c99a0e145d28cbaba12823d2 Mon Sep 17 00:00:00 2001 From: Miguel Garcia Date: Sun, 1 Jul 2012 18:08:39 +0200 Subject: GenASM: pipeline disk-write with building of classfiles --- .../tools/nsc/backend/jvm/BytecodeWriters.scala | 22 ++--- .../scala/tools/nsc/backend/jvm/GenASM.scala | 94 ++++++++++++++++------ .../scala/tools/nsc/backend/jvm/GenJVM.scala | 10 ++- 3 files changed, 91 insertions(+), 35 deletions(-) (limited to 'src/compiler') diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala index ff68aba845..42921e733d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala @@ -37,7 +37,7 @@ trait BytecodeWriters { getFile(outputDirectory(sym), clsName, suffix) trait BytecodeWriter { - def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol): Unit + def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile): Unit def close(): Unit = () } @@ -48,7 +48,9 @@ trait BytecodeWriters { ) val writer = new Jar(jfile).jarWriter(jarMainAttrs: _*) - def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { + def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) { + assert(outfile == null, + "The outfile formal param is there just because ClassBytecodeWriter overrides this method and uses it.") val path = jclassName + ".class" val out = writer.newOutputStream(path) @@ -72,21 +74,21 @@ trait BytecodeWriters { try javap(Seq("-verbose", "dummy")) foreach (_.show()) finally pw.close() } - abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { - super.writeClass(label, jclassName, jclassBytes, sym) + abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) { + super.writeClass(label, jclassName, jclassBytes, outfile) - val bytes = getFile(sym, jclassName, ".class").toByteArray val segments = jclassName.split("[./]") val javapFile = segments.foldLeft(baseDir: Path)(_ / _) changeExtension "javap" toFile; javapFile.parent.createDirectory() - emitJavap(bytes, javapFile) + emitJavap(jclassBytes, javapFile) } } trait ClassBytecodeWriter extends BytecodeWriter { - def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { - val outfile = getFile(sym, jclassName, ".class") + def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) { + assert(outfile != null, + "Precisely this override requires its invoker to hand out a non-null AbstractFile.") val outstream = new DataOutputStream(outfile.bufferedOutput) try outstream.write(jclassBytes, 0, jclassBytes.length) @@ -98,8 +100,8 @@ trait BytecodeWriters { trait DumpBytecodeWriter extends BytecodeWriter { val baseDir = Directory(settings.Ydumpclasses.value).createDirectory() - abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { - super.writeClass(label, jclassName, jclassBytes, sym) + abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) { + super.writeClass(label, jclassName, jclassBytes, outfile) val pathName = jclassName var dumpFile = pathName.split("[./]").foldLeft(baseDir: Path) (_ / _) changeExtension "class" toFile; diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 59adcc637a..7ca49bd3f4 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -29,6 +29,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters { val phaseName = "jvm" + case class WorkUnit(label: String, jclassName: String, jclass: asm.ClassWriter, outF: AbstractFile) + + type WorkUnitQueue = _root_.java.util.concurrent.LinkedBlockingQueue[WorkUnit] + /** Create a new phase */ override def newPhase(p: Phase): Phase = new AsmPhase(p) @@ -148,6 +152,44 @@ abstract class GenASM extends SubComponent with BytecodeWriters { } } + // ----------------------------------------------------------------------------------------- + // Allow overlapping disk write of classfiles with building of the next classfiles. + // ----------------------------------------------------------------------------------------- + + val q = new WorkUnitQueue(500) + + class WriteTask(bytecodeWriter: BytecodeWriter) extends _root_.java.lang.Runnable { + + def run() { + var stop = false + try { + while (!stop) { + val WorkUnit(label, jclassName, jclass, outF) = q.take + if(jclass eq null) { stop = true } + else { writeIfNotTooBig(label, jclassName, jclass, outF) } + } + } catch { + case ex: InterruptedException => throw ex + } + } + + private def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, outF: AbstractFile) { + try { + val arr = jclass.toByteArray() + bytecodeWriter.writeClass(label, jclassName, arr, outF) + } catch { + case e: java.lang.RuntimeException if(e.getMessage() == "Class file too large!") => + // TODO check where ASM throws the equivalent of CodeSizeTooBigException + log("Skipped class "+jclassName+" because it exceeds JVM limits (it's too big or has methods that are too long).") + } + } + + } + + // ----------------------------------------------------------------------------------------- + // what AsmPhase does. + // ----------------------------------------------------------------------------------------- + override def run() { if (settings.debug.value) @@ -161,10 +203,14 @@ abstract class GenASM extends SubComponent with BytecodeWriters { var sortedClasses = classes.values.toList sortBy ("" + _.symbol.fullName) debuglog("Created new bytecode generator for " + classes.size + " classes.") - val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint) - val plainCodeGen = new JPlainBuilder(bytecodeWriter) - val mirrorCodeGen = new JMirrorBuilder(bytecodeWriter) - val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter) + val bytecodeWriter = initBytecodeWriter(sortedClasses filter isJavaEntryPoint) + val needsOutfileForSymbol = bytecodeWriter.isInstanceOf[ClassBytecodeWriter] + + val plainCodeGen = new JPlainBuilder(q, needsOutfileForSymbol) + val mirrorCodeGen = new JMirrorBuilder(q, needsOutfileForSymbol) + val beanInfoCodeGen = new JBeanInfoBuilder(q, needsOutfileForSymbol) + + new _root_.java.lang.Thread(new WriteTask(bytecodeWriter)).start() while(!sortedClasses.isEmpty) { val c = sortedClasses.head @@ -187,6 +233,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters { classes -= c.symbol // GC opportunity } + q put WorkUnit(null, null, null, null) + + while(!q.isEmpty) { _root_.java.lang.Thread.sleep(10) } + bytecodeWriter.close() classes.clear() reverseJavaName.clear() @@ -448,7 +498,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { val JAVA_LANG_STRING = asm.Type.getObjectType("java/lang/String") /** basic functionality for class file building */ - abstract class JBuilder(bytecodeWriter: BytecodeWriter) { + abstract class JBuilder(wuQ: WorkUnitQueue, needsOutfileForSymbol: Boolean) { val EMPTY_JTYPE_ARRAY = Array.empty[asm.Type] val EMPTY_STRING_ARRAY = Array.empty[String] @@ -504,17 +554,6 @@ abstract class GenASM extends SubComponent with BytecodeWriters { // utitilies useful when emitting plain, mirror, and beaninfo classes. // ----------------------------------------------------------------------------------------- - def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) { - try { - val arr = jclass.toByteArray() - bytecodeWriter.writeClass(label, jclassName, arr, sym) - } catch { - case e: java.lang.RuntimeException if(e.getMessage() == "Class file too large!") => - // TODO check where ASM throws the equivalent of CodeSizeTooBigException - log("Skipped class "+jclassName+" because it exceeds JVM limits (it's too big or has methods that are too long).") - } - } - /** Specialized array conversion to prevent calling * java.lang.reflect.Array.newInstance via TraversableOnce.toArray */ @@ -728,11 +767,18 @@ abstract class GenASM extends SubComponent with BytecodeWriters { } } + def enqueue(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) { + val outF: scala.tools.nsc.io.AbstractFile = { + if(needsOutfileForSymbol) getFile(sym, jclassName, ".class") else null + } + wuQ put WorkUnit(label, jclassName, jclass, outF) + } + } // end of class JBuilder /** functionality for building plain and mirror classes */ - abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) { + abstract class JCommonBuilder(wuQ: WorkUnitQueue, needsOutfileForSymbol: Boolean) extends JBuilder(wuQ, needsOutfileForSymbol) { // ----------------------------------------------------------------------------------------- // more constants @@ -1290,8 +1336,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters { case class BlockInteval(start: BasicBlock, end: BasicBlock) /** builder of plain classes */ - class JPlainBuilder(bytecodeWriter: BytecodeWriter) - extends JCommonBuilder(bytecodeWriter) + class JPlainBuilder(wuQ: WorkUnitQueue, needsOutfileForSymbol: Boolean) + extends JCommonBuilder(wuQ, needsOutfileForSymbol) with JAndroidBuilder { val MIN_SWITCH_DENSITY = 0.7 @@ -1445,7 +1491,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { addInnerClasses(clasz.symbol, jclass) jclass.visitEnd() - writeIfNotTooBig("" + c.symbol.name, thisName, jclass, c.symbol) + enqueue("" + c.symbol.name, thisName, jclass, c.symbol) } @@ -2863,7 +2909,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { /** builder of mirror classes */ - class JMirrorBuilder(bytecodeWriter: BytecodeWriter) extends JCommonBuilder(bytecodeWriter) { + class JMirrorBuilder(wuQ: WorkUnitQueue, needsOutfileForSymbol: Boolean) extends JCommonBuilder(wuQ, needsOutfileForSymbol) { private var cunit: CompilationUnit = _ def getCurrentCUnit(): CompilationUnit = cunit; @@ -2907,7 +2953,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { addInnerClasses(modsym, mirrorClass) mirrorClass.visitEnd() - writeIfNotTooBig("" + modsym.name, mirrorName, mirrorClass, modsym) + enqueue("" + modsym.name, mirrorName, mirrorClass, modsym) } @@ -2915,7 +2961,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { /** builder of bean info classes */ - class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) { + class JBeanInfoBuilder(wuQ: WorkUnitQueue, needsOutfileForSymbol: Boolean) extends JBuilder(wuQ, needsOutfileForSymbol) { /** * Generate a bean info class that describes the given class. @@ -3036,7 +3082,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { addInnerClasses(clasz.symbol, beanInfoClass) beanInfoClass.visitEnd() - writeIfNotTooBig("BeanInfo ", beanInfoName, beanInfoClass, clasz.symbol) + enqueue("BeanInfo ", beanInfoName, beanInfoClass, clasz.symbol) } } // end of class JBeanInfoBuilder diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index 21260d399c..ad054015ef 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -183,7 +183,6 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with class BytecodeGenerator(bytecodeWriter: BytecodeWriter) extends BytecodeUtil { def this() = this(new ClassBytecodeWriter { }) def debugLevel = settings.debuginfo.indexOfChoice - import bytecodeWriter.writeClass val MIN_SWITCH_DENSITY = 0.7 val INNER_CLASSES_FLAGS = @@ -344,6 +343,15 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with writeClass("" + sym.name, jclass.getName(), toByteArray(jclass), sym) } + val needsOutfileForSymbol = bytecodeWriter.isInstanceOf[ClassBytecodeWriter] + + def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], sym: Symbol) { + val outF: scala.tools.nsc.io.AbstractFile = { + if(needsOutfileForSymbol) getFile(sym, jclassName, ".class") else null + } + bytecodeWriter.writeClass(label, jclassName, jclassBytes, outF) + } + /** Returns the ScalaSignature annotation if it must be added to this class, * none otherwise; furthermore, it adds to `jclass` the ScalaSig marker * attribute (marking that a scala signature annotation is present) or the -- cgit v1.2.3