diff options
author | Paul Phillips <paulp@improving.org> | 2013-04-30 04:27:26 -0700 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2013-04-30 04:27:26 -0700 |
commit | fc14edde4cd0dde064b78b0bde35adbfd561597d (patch) | |
tree | f51b9f82120804b8a087b1e53c5949eb5c64faf3 /src | |
parent | 1f2772a2375a02937d71b33ac5a6a9cc6eb2fdb5 (diff) | |
parent | 1931f45a9cf6027ef9bb037f63aeb2e926203000 (diff) | |
download | scala-fc14edde4cd0dde064b78b0bde35adbfd561597d.tar.gz scala-fc14edde4cd0dde064b78b0bde35adbfd561597d.tar.bz2 scala-fc14edde4cd0dde064b78b0bde35adbfd561597d.zip |
Merge pull request #2459 from magarciaEPFL/backendish7
similar in purpose to -Ygen-javap but with some advantages: -Ygen-asmp
Diffstat (limited to 'src')
3 files changed, 74 insertions, 28 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala index 66aed14d1c..0df828393d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala @@ -8,7 +8,6 @@ package backend.jvm import java.io.{ DataOutputStream, FileOutputStream, IOException, OutputStream, File => JFile } import scala.tools.nsc.io._ -import scala.tools.nsc.util.ScalaClassLoader import java.util.jar.Attributes.Name import scala.language.postfixOps @@ -38,11 +37,22 @@ trait BytecodeWriters { for (part <- pathParts.init) dir = ensureDirectory(dir) subdirectoryNamed part ensureDirectory(dir) fileNamed pathParts.last + suffix } - private def getFile(sym: Symbol, clsName: String, suffix: String): AbstractFile = + def getFile(sym: Symbol, clsName: String, suffix: String): AbstractFile = getFile(outputDirectory(sym), clsName, suffix) + def factoryNonJarBytecodeWriter(): BytecodeWriter = { + val emitAsmp = settings.Ygenasmp.isSetByUser + val doDump = settings.Ydumpclasses.isSetByUser + (emitAsmp, doDump) match { + case (false, false) => new ClassBytecodeWriter { } + case (false, true ) => new ClassBytecodeWriter with DumpBytecodeWriter { } + case (true, false) => new ClassBytecodeWriter with AsmpBytecodeWriter + case (true, true ) => new ClassBytecodeWriter with AsmpBytecodeWriter with DumpBytecodeWriter { } + } + } + 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 = () } @@ -53,7 +63,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) @@ -65,9 +77,47 @@ trait BytecodeWriters { override def close() = writer.close() } + /* + * The ASM textual representation for bytecode overcomes disadvantages of javap ouput in three areas: + * (a) pickle dingbats undecipherable to the naked eye; + * (b) two constant pools, while having identical contents, are displayed differently due to physical layout. + * (c) stack maps (classfile version 50 and up) are displayed in encoded form by javap, + * their expansion by ASM is more readable. + * + * */ + trait AsmpBytecodeWriter extends BytecodeWriter { + import scala.tools.asm + + private val baseDir = Directory(settings.Ygenasmp.value).createDirectory() + + private def emitAsmp(jclassBytes: Array[Byte], asmpFile: io.File) { + val pw = asmpFile.printWriter() + try { + val cnode = new asm.tree.ClassNode() + val cr = new asm.ClassReader(jclassBytes) + cr.accept(cnode, 0) + val trace = new scala.tools.asm.util.TraceClassVisitor(new java.io.PrintWriter(new java.io.StringWriter())) + cnode.accept(trace) + trace.p.print(pw) + } + finally pw.close() + } + + abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) { + super.writeClass(label, jclassName, jclassBytes, outfile) + + val segments = jclassName.split("[./]") + val asmpFile = segments.foldLeft(baseDir: Path)(_ / _) changeExtension "asmp" toFile; + + asmpFile.parent.createDirectory() + emitAsmp(jclassBytes, asmpFile) + } + } + 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) @@ -79,8 +129,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 val 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 db43e633fc..e9f42ea5ce 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -75,16 +75,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { new DirectToJarfileWriter(f.file) - case _ => - if (settings.Ydumpclasses.isDefault) - new ClassBytecodeWriter { } - else - new ClassBytecodeWriter with DumpBytecodeWriter { } - // TODO A ScalapBytecodeWriter could take asm.util.Textifier as starting point. - // Three areas where javap ouput is less than ideal (e.g. when comparing versions of the same classfile) are: - // (a) unreadable pickle; - // (b) two constant pools, while having identical contents, are displayed differently due to physical layout. - // (c) stack maps (classfile version 50 and up) are displayed in encoded form by javap, their expansion makes more sense instead. + case _ => factoryNonJarBytecodeWriter() } } @@ -111,9 +102,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { debuglog(s"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 needsOutfile = bytecodeWriter.isInstanceOf[ClassBytecodeWriter] + val plainCodeGen = new JPlainBuilder( bytecodeWriter, needsOutfile) + val mirrorCodeGen = new JMirrorBuilder( bytecodeWriter, needsOutfile) + val beanInfoCodeGen = new JBeanInfoBuilder(bytecodeWriter, needsOutfile) def emitFor(c: IClass) { if (isStaticModule(c.symbol) && isTopLevelModule(c.symbol)) { @@ -406,7 +398,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { } /** basic functionality for class file building */ - abstract class JBuilder(bytecodeWriter: BytecodeWriter) { + abstract class JBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) { val EMPTY_STRING_ARRAY = Array.empty[String] @@ -464,7 +456,10 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { def writeIfNotTooBig(label: String, jclassName: String, jclass: asm.ClassWriter, sym: Symbol) { try { val arr = jclass.toByteArray() - bytecodeWriter.writeClass(label, jclassName, arr, sym) + val outF: scala.tools.nsc.io.AbstractFile = { + if(needsOutfile) getFile(sym, jclassName, ".class") else null + } + 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 @@ -695,7 +690,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { /** functionality for building plain and mirror classes */ - abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) { + abstract class JCommonBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) { def debugLevel = settings.debuginfo.indexOfChoice @@ -1245,8 +1240,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { case class BlockInteval(start: BasicBlock, end: BasicBlock) /** builder of plain classes */ - class JPlainBuilder(bytecodeWriter: BytecodeWriter) - extends JCommonBuilder(bytecodeWriter) + class JPlainBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) + extends JCommonBuilder(bytecodeWriter, needsOutfile) with JAndroidBuilder { val MIN_SWITCH_DENSITY = 0.7 @@ -2880,7 +2875,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { /** builder of mirror classes */ - class JMirrorBuilder(bytecodeWriter: BytecodeWriter) extends JCommonBuilder(bytecodeWriter) { + class JMirrorBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JCommonBuilder(bytecodeWriter, needsOutfile) { private var cunit: CompilationUnit = _ def getCurrentCUnit(): CompilationUnit = cunit @@ -2932,7 +2927,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { /** builder of bean info classes */ - class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter) extends JBuilder(bytecodeWriter) { + class JBeanInfoBuilder(bytecodeWriter: BytecodeWriter, needsOutfile: Boolean) extends JBuilder(bytecodeWriter, needsOutfile) { /** * Generate a bean info class that describes the given class. diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index a30f144802..57af1b9c9a 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -156,6 +156,7 @@ trait ScalaSettings extends AbsScalaSettings val Yshowsymkinds = BooleanSetting ("-Yshow-symkinds", "Print abbreviated symbol kinds next to symbol names.") val skip = PhasesSetting ("-Yskip", "Skip") val Ygenjavap = StringSetting ("-Ygen-javap", "dir", "Generate a parallel output directory of .javap files.", "") + val Ygenasmp = StringSetting ("-Ygen-asmp", "dir", "Generate a parallel output directory of .asmp files (ie ASM Textifier output).", "") val Ydumpclasses = StringSetting ("-Ydump-classes", "dir", "Dump the generated bytecode to .class files (useful for reflective compilation that utilizes in-memory classloaders).", "") val Ynosqueeze = BooleanSetting ("-Yno-squeeze", "Disable creation of compact code in matching.") val Ystatistics = BooleanSetting ("-Ystatistics", "Print compiler statistics.") andThen (scala.reflect.internal.util.Statistics.enabled = _) |