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