|
| 1 | +/* NSC -- new Scala compiler |
| 2 | + * Copyright 2005-2012 LAMP/EPFL |
| 3 | + * @author Martin Odersky |
| 4 | + */ |
| 5 | + |
| 6 | + |
| 7 | +package scala |
| 8 | +package tools.nsc |
| 9 | +package backend |
| 10 | +package jvm |
| 11 | + |
| 12 | +import scala.collection.{ mutable, immutable } |
| 13 | +import scala.annotation.switch |
| 14 | + |
| 15 | +import scala.tools.asm |
| 16 | + |
| 17 | +/* |
| 18 | + * Prepare in-memory representations of classfiles using the ASM Tree API, and serialize them to disk. |
| 19 | + * |
| 20 | + * Three pipelines are at work, each taking work items from a queue dedicated to that pipeline: |
| 21 | + * |
| 22 | + * (There's another pipeline so to speak, the one that populates queue-1 by traversing a CompilationUnit until ClassDefs are found, |
| 23 | + * but the "interesting" pipelines are the ones described below) |
| 24 | + * |
| 25 | + * (1) In the first queue, an item consists of a ClassDef along with its arrival position. |
| 26 | + * This position is needed at the time classfiles are serialized to disk, |
| 27 | + * so as to emit classfiles in the same order CleanUp handed them over. |
| 28 | + * As a result, two runs of the compiler on the same files produce jars that are identical on a byte basis. |
| 29 | + * See `ant test.stability` |
| 30 | + * |
| 31 | + * (2) The second queue contains items where a ClassDef has been lowered into: |
| 32 | + * (a) an optional mirror class, |
| 33 | + * (b) a plain class, and |
| 34 | + * (c) an optional bean class. |
| 35 | + * |
| 36 | + * (3) The third queue contains items ready for serialization. |
| 37 | + * It's a priority queue that follows the original arrival order, |
| 38 | + * so as to emit identical jars on repeated compilation of the same sources. |
| 39 | + * |
| 40 | + * Plain, mirror, and bean classes are built respectively by PlainClassBuilder, JMirrorBuilder, and JBeanInfoBuilder. |
| 41 | + * |
| 42 | + * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ |
| 43 | + * @version 1.0 |
| 44 | + * |
| 45 | + */ |
| 46 | +abstract class GenBCode extends BCodeSyncAndTry { |
| 47 | + import global._ |
| 48 | + |
| 49 | + val phaseName = "jvm" |
| 50 | + |
| 51 | + override def newPhase(prev: Phase) = new BCodePhase(prev) |
| 52 | + |
| 53 | + final class PlainClassBuilder(cunit: CompilationUnit) extends SyncAndTryBuilder(cunit) |
| 54 | + |
| 55 | + class BCodePhase(prev: Phase) extends StdPhase(prev) { |
| 56 | + |
| 57 | + override def name = phaseName |
| 58 | + override def description = "Generate bytecode from ASTs using the ASM library" |
| 59 | + override def erasedTypes = true |
| 60 | + |
| 61 | + private var bytecodeWriter : BytecodeWriter = null |
| 62 | + private var mirrorCodeGen : JMirrorBuilder = null |
| 63 | + private var beanInfoCodeGen : JBeanInfoBuilder = null |
| 64 | + |
| 65 | + /* ---------------- q1 ---------------- */ |
| 66 | + |
| 67 | + case class Item1(arrivalPos: Int, cd: ClassDef, cunit: CompilationUnit) { |
| 68 | + def isPoison = { arrivalPos == Int.MaxValue } |
| 69 | + } |
| 70 | + private val poison1 = Item1(Int.MaxValue, null, null) |
| 71 | + private val q1 = new java.util.LinkedList[Item1] |
| 72 | + |
| 73 | + /* ---------------- q2 ---------------- */ |
| 74 | + |
| 75 | + case class Item2(arrivalPos: Int, |
| 76 | + mirror: asm.tree.ClassNode, |
| 77 | + plain: asm.tree.ClassNode, |
| 78 | + bean: asm.tree.ClassNode, |
| 79 | + outFolder: scala.tools.nsc.io.AbstractFile) { |
| 80 | + def isPoison = { arrivalPos == Int.MaxValue } |
| 81 | + } |
| 82 | + |
| 83 | + private val poison2 = Item2(Int.MaxValue, null, null, null, null) |
| 84 | + private val q2 = new _root_.java.util.LinkedList[Item2] |
| 85 | + |
| 86 | + /* ---------------- q3 ---------------- */ |
| 87 | + |
| 88 | + /* |
| 89 | + * An item of queue-3 (the last queue before serializing to disk) contains three of these |
| 90 | + * (one for each of mirror, plain, and bean classes). |
| 91 | + * |
| 92 | + * @param jclassName internal name of the class |
| 93 | + * @param jclassBytes bytecode emitted for the class SubItem3 represents |
| 94 | + */ |
| 95 | + case class SubItem3( |
| 96 | + jclassName: String, |
| 97 | + jclassBytes: Array[Byte] |
| 98 | + ) |
| 99 | + |
| 100 | + case class Item3(arrivalPos: Int, |
| 101 | + mirror: SubItem3, |
| 102 | + plain: SubItem3, |
| 103 | + bean: SubItem3, |
| 104 | + outFolder: scala.tools.nsc.io.AbstractFile) { |
| 105 | + |
| 106 | + def isPoison = { arrivalPos == Int.MaxValue } |
| 107 | + } |
| 108 | + private val i3comparator = new java.util.Comparator[Item3] { |
| 109 | + override def compare(a: Item3, b: Item3) = { |
| 110 | + if (a.arrivalPos < b.arrivalPos) -1 |
| 111 | + else if (a.arrivalPos == b.arrivalPos) 0 |
| 112 | + else 1 |
| 113 | + } |
| 114 | + } |
| 115 | + private val poison3 = Item3(Int.MaxValue, null, null, null, null) |
| 116 | + private val q3 = new java.util.PriorityQueue[Item3](1000, i3comparator) |
| 117 | + |
| 118 | + /* |
| 119 | + * Pipeline that takes ClassDefs from queue-1, lowers them into an intermediate form, placing them on queue-2 |
| 120 | + */ |
| 121 | + class Worker1(needsOutFolder: Boolean) { |
| 122 | + |
| 123 | + val caseInsensitively = mutable.Map.empty[String, Symbol] |
| 124 | + |
| 125 | + def run() { |
| 126 | + while (true) { |
| 127 | + val item = q1.poll |
| 128 | + if (item.isPoison) { |
| 129 | + q2 add poison2 |
| 130 | + return |
| 131 | + } |
| 132 | + else { |
| 133 | + try { visit(item) } |
| 134 | + catch { |
| 135 | + case ex: Throwable => |
| 136 | + ex.printStackTrace() |
| 137 | + error(s"Error while emitting ${item.cunit.source}\n${ex.getMessage}") |
| 138 | + } |
| 139 | + } |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + /* |
| 144 | + * Checks for duplicate internal names case-insensitively, |
| 145 | + * builds ASM ClassNodes for mirror, plain, and bean classes; |
| 146 | + * enqueues them in queue-2. |
| 147 | + * |
| 148 | + */ |
| 149 | + def visit(item: Item1) { |
| 150 | + val Item1(arrivalPos, cd, cunit) = item |
| 151 | + val claszSymbol = cd.symbol |
| 152 | + |
| 153 | + // GenASM checks this before classfiles are emitted, https://github.com/scala/scala/commit/e4d1d930693ac75d8eb64c2c3c69f2fc22bec739 |
| 154 | + val lowercaseJavaClassName = claszSymbol.javaClassName.toLowerCase |
| 155 | + caseInsensitively.get(lowercaseJavaClassName) match { |
| 156 | + case None => |
| 157 | + caseInsensitively.put(lowercaseJavaClassName, claszSymbol) |
| 158 | + case Some(dupClassSym) => |
| 159 | + item.cunit.warning( |
| 160 | + claszSymbol.pos, |
| 161 | + s"Class ${claszSymbol.javaClassName} differs only in case from ${dupClassSym.javaClassName}. " + |
| 162 | + "Such classes will overwrite one another on case-insensitive filesystems." |
| 163 | + ) |
| 164 | + } |
| 165 | + |
| 166 | + // -------------- mirror class, if needed -------------- |
| 167 | + val mirrorC = |
| 168 | + if (isStaticModule(claszSymbol) && isTopLevelModule(claszSymbol)) { |
| 169 | + if (claszSymbol.companionClass == NoSymbol) { |
| 170 | + mirrorCodeGen.genMirrorClass(claszSymbol, cunit) |
| 171 | + } else { |
| 172 | + log(s"No mirror class for module with linked class: ${claszSymbol.fullName}") |
| 173 | + null |
| 174 | + } |
| 175 | + } else null |
| 176 | + |
| 177 | + // -------------- "plain" class -------------- |
| 178 | + val pcb = new PlainClassBuilder(cunit) |
| 179 | + pcb.genPlainClass(cd) |
| 180 | + val outF = if (needsOutFolder) getOutFolder(claszSymbol, pcb.thisName, cunit) else null; |
| 181 | + val plainC = pcb.cnode |
| 182 | + |
| 183 | + // -------------- bean info class, if needed -------------- |
| 184 | + val beanC = |
| 185 | + if (claszSymbol hasAnnotation BeanInfoAttr) { |
| 186 | + beanInfoCodeGen.genBeanInfoClass( |
| 187 | + claszSymbol, cunit, |
| 188 | + fieldSymbols(claszSymbol), |
| 189 | + methodSymbols(cd) |
| 190 | + ) |
| 191 | + } else null |
| 192 | + |
| 193 | + // ----------- hand over to pipeline-2 |
| 194 | + |
| 195 | + val item2 = |
| 196 | + Item2(arrivalPos, |
| 197 | + mirrorC, plainC, beanC, |
| 198 | + outF) |
| 199 | + |
| 200 | + q2 add item2 // at the very end of this method so that no Worker2 thread starts mutating before we're done. |
| 201 | + |
| 202 | + } // end of method visit(Item1) |
| 203 | + |
| 204 | + } // end of class BCodePhase.Worker1 |
| 205 | + |
| 206 | + /* |
| 207 | + * Pipeline that takes ClassNodes from queue-2. The unit of work depends on the optimization level: |
| 208 | + * |
| 209 | + * (a) no optimization involves: |
| 210 | + * - converting the plain ClassNode to byte array and placing it on queue-3 |
| 211 | + */ |
| 212 | + class Worker2 { |
| 213 | + |
| 214 | + def run() { |
| 215 | + while (true) { |
| 216 | + val item = q2.poll |
| 217 | + if (item.isPoison) { |
| 218 | + q3 add poison3 |
| 219 | + return |
| 220 | + } |
| 221 | + else { |
| 222 | + try { addToQ3(item) } |
| 223 | + catch { |
| 224 | + case ex: Throwable => |
| 225 | + ex.printStackTrace() |
| 226 | + error(s"Error while emitting ${item.plain.name}\n${ex.getMessage}") |
| 227 | + } |
| 228 | + } |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + private def addToQ3(item: Item2) { |
| 233 | + |
| 234 | + def getByteArray(cn: asm.tree.ClassNode): Array[Byte] = { |
| 235 | + val cw = new CClassWriter(extraProc) |
| 236 | + cn.accept(cw) |
| 237 | + cw.toByteArray |
| 238 | + } |
| 239 | + |
| 240 | + val Item2(arrivalPos, mirror, plain, bean, outFolder) = item |
| 241 | + |
| 242 | + val mirrorC = if (mirror == null) null else SubItem3(mirror.name, getByteArray(mirror)) |
| 243 | + val plainC = SubItem3(plain.name, getByteArray(plain)) |
| 244 | + val beanC = if (bean == null) null else SubItem3(bean.name, getByteArray(bean)) |
| 245 | + |
| 246 | + q3 add Item3(arrivalPos, mirrorC, plainC, beanC, outFolder) |
| 247 | + |
| 248 | + } |
| 249 | + |
| 250 | + } // end of class BCodePhase.Worker2 |
| 251 | + |
| 252 | + var arrivalPos = 0 |
| 253 | + |
| 254 | + /* |
| 255 | + * A run of the BCodePhase phase comprises: |
| 256 | + * |
| 257 | + * (a) set-up steps (most notably supporting maps in `BCodeTypes`, |
| 258 | + * but also "the" writer where class files in byte-array form go) |
| 259 | + * |
| 260 | + * (b) building of ASM ClassNodes, their optimization and serialization. |
| 261 | + * |
| 262 | + * (c) tear down (closing the classfile-writer and clearing maps) |
| 263 | + * |
| 264 | + */ |
| 265 | + override def run() { |
| 266 | + |
| 267 | + arrivalPos = 0 // just in case |
| 268 | + scalaPrimitives.init |
| 269 | + initBCodeTypes() |
| 270 | + |
| 271 | + // initBytecodeWriter invokes fullName, thus we have to run it before the typer-dependent thread is activated. |
| 272 | + bytecodeWriter = initBytecodeWriter(cleanup.getEntryPoints) |
| 273 | + mirrorCodeGen = new JMirrorBuilder |
| 274 | + beanInfoCodeGen = new JBeanInfoBuilder |
| 275 | + |
| 276 | + val needsOutfileForSymbol = bytecodeWriter.isInstanceOf[ClassBytecodeWriter] |
| 277 | + buildAndSendToDisk(needsOutfileForSymbol) |
| 278 | + |
| 279 | + // closing output files. |
| 280 | + bytecodeWriter.close() |
| 281 | + |
| 282 | + /* TODO Bytecode can be verified (now that all classfiles have been written to disk) |
| 283 | + * |
| 284 | + * (1) asm.util.CheckAdapter.verify() |
| 285 | + * public static void verify(ClassReader cr, ClassLoader loader, boolean dump, PrintWriter pw) |
| 286 | + * passing a custom ClassLoader to verify inter-dependent classes. |
| 287 | + * Alternatively, |
| 288 | + * - an offline-bytecode verifier could be used (e.g. Maxine brings one as separate tool). |
| 289 | + * - -Xverify:all |
| 290 | + * |
| 291 | + * (2) if requested, check-java-signatures, over and beyond the syntactic checks in `getGenericSignature()` |
| 292 | + * |
| 293 | + */ |
| 294 | + |
| 295 | + // clearing maps |
| 296 | + clearBCodeTypes() |
| 297 | + } |
| 298 | + |
| 299 | + /* |
| 300 | + * Sequentially: |
| 301 | + * (a) place all ClassDefs in queue-1 |
| 302 | + * (b) dequeue one at a time from queue-1, convert it to ASM ClassNode, place in queue-2 |
| 303 | + * (c) dequeue one at a time from queue-2, convert it to byte-array, place in queue-3 |
| 304 | + * (d) serialize to disk by draining queue-3. |
| 305 | + */ |
| 306 | + private def buildAndSendToDisk(needsOutFolder: Boolean) { |
| 307 | + |
| 308 | + feedPipeline1() |
| 309 | + (new Worker1(needsOutFolder)).run() |
| 310 | + (new Worker2).run() |
| 311 | + drainQ3() |
| 312 | + |
| 313 | + } |
| 314 | + |
| 315 | + /* Feed pipeline-1: place all ClassDefs on q1, recording their arrival position. */ |
| 316 | + private def feedPipeline1() { |
| 317 | + super.run() |
| 318 | + q1 add poison1 |
| 319 | + } |
| 320 | + |
| 321 | + /* Pipeline that writes classfile representations to disk. */ |
| 322 | + private def drainQ3() { |
| 323 | + |
| 324 | + def sendToDisk(cfr: SubItem3, outFolder: scala.tools.nsc.io.AbstractFile) { |
| 325 | + if (cfr != null){ |
| 326 | + val SubItem3(jclassName, jclassBytes) = cfr |
| 327 | + try { |
| 328 | + val outFile = |
| 329 | + if (outFolder == null) null |
| 330 | + else getFileForClassfile(outFolder, jclassName, ".class") |
| 331 | + bytecodeWriter.writeClass(jclassName, jclassName, jclassBytes, outFile) |
| 332 | + } |
| 333 | + catch { |
| 334 | + case e: FileConflictException => |
| 335 | + error(s"error writing $jclassName: ${e.getMessage}") |
| 336 | + } |
| 337 | + } |
| 338 | + } |
| 339 | + |
| 340 | + var moreComing = true |
| 341 | + // `expected` denotes the arrivalPos whose Item3 should be serialized next |
| 342 | + var expected = 0 |
| 343 | + |
| 344 | + while (moreComing) { |
| 345 | + val incoming = q3.poll |
| 346 | + moreComing = !incoming.isPoison |
| 347 | + if (moreComing) { |
| 348 | + val item = incoming |
| 349 | + val outFolder = item.outFolder |
| 350 | + sendToDisk(item.mirror, outFolder) |
| 351 | + sendToDisk(item.plain, outFolder) |
| 352 | + sendToDisk(item.bean, outFolder) |
| 353 | + expected += 1 |
| 354 | + } |
| 355 | + } |
| 356 | + |
| 357 | + // we're done |
| 358 | + assert(q1.isEmpty, s"Some ClassDefs remained in the first queue: $q1") |
| 359 | + assert(q2.isEmpty, s"Some classfiles remained in the second queue: $q2") |
| 360 | + assert(q3.isEmpty, s"Some classfiles weren't written to disk: $q3") |
| 361 | + |
| 362 | + } |
| 363 | + |
| 364 | + override def apply(cunit: CompilationUnit): Unit = { |
| 365 | + |
| 366 | + def gen(tree: Tree) { |
| 367 | + tree match { |
| 368 | + case EmptyTree => () |
| 369 | + case PackageDef(_, stats) => stats foreach gen |
| 370 | + case cd: ClassDef => |
| 371 | + q1 add Item1(arrivalPos, cd, cunit) |
| 372 | + arrivalPos += 1 |
| 373 | + } |
| 374 | + } |
| 375 | + |
| 376 | + gen(cunit.body) |
| 377 | + } |
| 378 | + |
| 379 | + } // end of class BCodePhase |
| 380 | + |
| 381 | +} // end of class GenBCode |
0 commit comments