Skip to content

Commit 7d470b3

Browse files
committed
Sharing backend between scalac and dotty.
Due to a lot of error and workarounds required for backend not regress in terms of speed, I guess the original idea was to high to be reached.. Nevertheless it was good to try.
1 parent 981a218 commit 7d470b3

File tree

7 files changed

+1334
-2
lines changed

7 files changed

+1334
-2
lines changed

src/dotty/tools/backend/jvm/DottyBackendInterface.scala

Lines changed: 546 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
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

Comments
 (0)