summaryrefslogtreecommitdiff
path: root/SIP/compiler-phase-init/sip-00002-10.scala
blob: 2065a7035d57ff67245f9ce287bf815cc757aae7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
/* Package information */
package depgraph


/* Simple import of data structures and IO */
import scala.collection.mutable.{HashSet,HashMap}
import java.io.{BufferedWriter,FileWriter}



/* Class made to model the internal compiler phases
 *
 * All internal phases inherits from SubComponent
 */
abstract class SubComponent {
  val phaseName: String
  val runsAfter: List[String]
  val runsRightAfter: Option[String]
  val internal: Boolean = true
}

/*
 *
 */
class Global extends Plugins with PhaseAssembly {

  /* Simple option value to hold the compiler phase chain */
  private var phasesCache : Option[List[SubComponent]] = None

  /* The set of phase objects that is the basis for the compiler phase chain */
  protected val phasesSet : HashSet[SubComponent] = new HashSet[SubComponent]

  /* All the internal phase objects
   *
   */
  object parser extends {
    val phaseName = "nsc::parser"
    val runsAfter = List[String]()
    val runsRightAfter = None
  } with SubComponent

  object typer extends {
    val phaseName = "nsc::typer"
    val runsAfter = List[String]("nsc::parser")
    val runsRightAfter = None
  } with SubComponent

  object pickler extends {
    val phaseName = "nsc::pickler"
    val runsAfter = List[String]("nsc::typer")
    val runsRightAfter = None
  } with SubComponent

  object liftcode extends {
    val phaseName = "nsc::liftcode"
    val runsAfter = List[String]("nsc::pickler")
    val runsRightAfter = None
  } with SubComponent

  object tailcalls extends {
    val phaseName = "nsc::tailcalls"
    val runsAfter = List[String]("nsc::pickler","nsc::liftcode")
    val runsRightAfter = None
  } with SubComponent

  object erasure extends {
    val phaseName = "nsc::erasure"
    val runsAfter = List[String]()
    val runsRightAfter = Some("nsc::tailcalls")
  } with SubComponent

  object cleanup extends {
    val phaseName = "nsc::cleanup"
    val runsAfter = List[String]("nsc::erasure")
    val runsRightAfter = None
  } with SubComponent

  object jvm extends {
    val phaseName = "nsc::jvm"
    val runsAfter = List[String]("nsc::cleanup")
    val runsRightAfter = None
  } with SubComponent

  object terminal extends {
    val phaseName = "nsc::terminal"
    val runsAfter = List[String]("nsc::jvm","nsc::msil")
    val runsRightAfter = None
  } with SubComponent

  /* Helper method
   */
  private def computePhaseDescriptors: List[SubComponent] = {
    computeInternalPhases()            // Global.scala
    computePluginPhases()              // plugins/Plugins.scala
    buildCompilerFromPhasesSet()       // PhaseAssembly.scala
  }

  /* Will add the internal compiler phases to the phases set
   */
  protected def computeInternalPhases() {
    phasesSet += parser
    phasesSet += typer
    phasesSet += pickler
    phasesSet += liftcode
    phasesSet += tailcalls
    phasesSet += erasure
    phasesSet += cleanup
    phasesSet += jvm
    phasesSet += terminal
  }

  /* Getter method for the compiler phases chain
   */
  def phaseDescriptors : List[SubComponent] = {
    if (phasesCache.isEmpty) {
      phasesCache = Some(computePhaseDescriptors)
    }
    phasesCache.get
  }

}


/* Class make to model the Plug-in supplied phases
 *
 * All external phases inherits from PluginComponent
 */
abstract class PluginComponent extends SubComponent {
  override val internal = false
  val runsRightAfter: Option[String] = None
}

/* Trait made to model the behavior of the plugins
 *
 */
trait Plugins { self: Global =>

  /* Example plugin phases
   *
   */
  object plugin1 extends {
    val phaseName = "plug1::optimization1"
    val runsAfter = List[String]("nsc::typer")
  } with PluginComponent

  object plugin2 extends {
    val phaseName = "plug2::optimization1"
    val runsAfter = List[String]("nsc::liftcode")
  } with PluginComponent

  object plugin3 extends {
    val phaseName = "plug2::optimization2"
    val runsAfter = List[String]("plug2::optimization1","nsc::cleanup")
  } with PluginComponent

  /* Add plug-in supplied phase objects to the phases set
   */
  def computePluginPhases() {
    phasesSet += plugin1
    phasesSet += plugin2
    phasesSet += plugin3
  }

}


/* Trait made to seperate the constraint solving from the rest of the compiler
 *
 */
trait PhaseAssembly { self: Global =>

  /* Aux datastructure for solving the constraint system
   * Simple edge with to and from refs
   */
  class Edge(f: Node, t: Node, h: Boolean) {
    var frm = f
    var to = t
    var hard = h
  }

  /* Aux datastructure for solving the constraint system
   * Simple node with name and object ref for the phase object,
   * also sets of in and out going dependencies
   */
  class Node(phs:SubComponent, name:String) {
    var phasename: String = name
    var phaseobj: SubComponent = phs
    var after: HashSet[Edge] = new HashSet[Edge]()
    var deps: HashSet[Edge] = new HashSet[Edge]()
  }

  /* Aux datastructure for solving the constraint system
   * The depency graph container with helper methods for node and edge creation
   */
  class DependencyGraph {

    val nodes = new HashMap[String,Node]()
    val edges = new HashSet[Edge]()

    /* Given a phase object, get the node for this phase object. If the
     * node object does not exist, then create it.
     */
    def getNodeByPhase(phs : SubComponent) : Node = {
      var node : Node = getNodeByPhase(phs.phaseName)
      if (node.phaseobj == null) {
	node.phaseobj = phs
      }
      node
    }

    /* Given the name of a phase object, get the node for that name. If the
     * node object does not exits, then create it.
     */
    def getNodeByPhase(name : String) : Node = {
      var node : Node = null
      this.nodes.get(name) match {
	case None =>
          node = new Node(null,name)
          nodes += (name->node)
	case Some(n) =>
          node = n
      }
      node
    }

    /* Connect the frm and to nodes with an edge and make it soft.
     * Also add the edge object to the set of edges, and to the dependency
     * list of the nodes
     */
    def softConnectNodes(frm: Node, to: Node) {
      var e = new Edge(frm, to, false)
      this.edges += e

      frm.after += e
      to.deps += e
    }

    /* Connect the frm and to nodes with an edge and make it hard.
     * Also add the edge object to the set of edges, and to the dependency
     * list of the nodes
     */
    def hardConnectNodes(frm: Node, to: Node) {
      var e = new Edge(frm, to, true)
      this.edges += e

      frm.after += e
      to.deps += e
    }
  }

  /* This method will simplify the graph, by removing unneeded edges and turning the graph into
   * a tree.
   */
  private def simplifyGraphFromNode(node : Node, graph : DependencyGraph) : Unit = {
    var removal : List[Edge] = Nil
    for(edge <- node.deps) {
      if (edge.frm.after.size > 1)
	removal = edge :: removal
    }
    for(edge <- removal) {
	println("[removing edge: " + edge.frm.phasename + " => " + edge.to.phasename + "]")
	node.deps -= edge
	edge.frm.after -= edge
	graph.edges -= edge
      }

    var nodes = dependencyOrder(node.deps)
    for(nd <- nodes) {
      simplifyGraphFromNode(nd, graph)
    }
  }

  /* This is a simple method that tests for cycles in the graph. If a cycle is found, a fatal error
   * will be produced.
   */
  private def testForCycles(node : Node, names : HashSet[String]) : Unit = {

    if (names.contains( node.phasename ) ) {
      println("There is a cycle in this graph! The algorithm was able to reach the node " + node.phasename + " twice!")
      System.exit(1)
    }

    names += node.phasename

    var nodes = dependencyOrder(node.deps)
    for(nd  <- nodes) {
      testForCycles(nd, names)
    }

    names -= node.phasename
  }

  /* Given the dependency list of a node, return a list so that first come the reversed
   * external phases sorted alphabetically, followed by the internal phase
   */
  private def dependencyOrder(deps : HashSet[Edge]) : List[Node] = {
    var external = deps.filter(e => (! e.frm.phaseobj.internal))
    var internal = deps.filter(e => e.frm.phaseobj.internal)

    var extnodes = (Nil ++ external.map(e => e.frm)).sort((n1,n2) => (n1.phasename compareTo n2.phasename) < 0)
    extnodes = extnodes.reverse

    var nodes = Nil ++ internal.map(e => e.frm) ++ extnodes
    nodes = nodes.reverse
    return nodes
  }

  /* Find all edges in the given graph that are hard links. For each hard link we
   * need to check that its the only dependency. If not, then we will promote the
   * other dependencies down
   */
  private def enforceHardlinks(graph : DependencyGraph) : Unit = {
    var rerun = true
    while(rerun) {
      rerun = false
      var hardlinks = graph.edges.filter(e => e.hard)
      for(hl <- hardlinks) {
	var sanity = Nil ++ hl.to.deps.filter(e => e.hard)
	if (sanity.length == 0) {

	  println("This is not supposed to happen!")
	  System.exit(1)
          // throw new FataError()
	} else if (sanity.length > 1) {

	  println("Multiple phases want to run right after the same phase")
	  println("Phases:")
	  for (edge <- sanity) {
	    println(" - " + edge.frm.phasename)
	  }
	  System.exit(1)

	} else {

	  var promote = hl.to.deps.filter(e => (!e.hard))
	  hl.to.deps.clear
	  sanity foreach (edge => hl.to.deps += edge)
	  for (edge <- promote) {
	    rerun = true
	    println("[promote the dependency of " + edge.frm.phasename + ": "  + edge.to.phasename + " => " + hl.frm.phasename + "]")
	    edge.to = hl.frm
	    hl.frm.deps += edge
	  }
	}
      }
    }
  }

  /* Remove all nodes in the given graph, that have no phase object
   * Make sure to clean up all edges when removing the node object
   */
  private def removeDanglingNodes(graph : DependencyGraph) : Unit = {
    var dnodes = graph.nodes.values.filter(n => (n.phaseobj == null))
    for(node <- dnodes) {
      println("[dropping depend on node with no phase: " + node.phasename + "]")
      graph.nodes -= node.phasename
      for(edge <- node.deps) {
	graph.edges -= edge
	edge.frm.after -= edge
      }
    }
  }

  /* This is a helper method, that given a dependency graph will generate a graphviz dot
   * file showing its structure.
   * Plug-in supplied phases are marked as green nodes and hard links are marked as blue edges.
   */
  private def graphToDotFile(graph : DependencyGraph, filename : String) : Unit = {
    var sbuf = new StringBuffer()
    var extnodes = new HashSet[Node]()
    sbuf.append("digraph G {\n")
    for(edge <- graph.edges) {
      sbuf.append("\"" + edge.frm.phasename + "\"->\"" + edge.to.phasename + "\"")
      if (! edge.frm.phaseobj.internal) {
       	extnodes += edge.frm
      }
      if (edge.hard) {
	sbuf.append(" [color=\"#0000ff\"]\n")
      } else {
	sbuf.append(" [color=\"#000000\"]\n")
      }
    }
    for(node <- extnodes) {
      sbuf.append("\"" + node.phasename + "\" [color=\"#00ff00\"]\n")
    }
    sbuf.append("}\n")
    var out = new BufferedWriter(new FileWriter(filename))
    out.write(sbuf.toString)
    out.flush()
    out.close()
  }


  /* Given the phases set, will build a dependency graph from the phases set
   * Using the aux. method of the DependencyGraph to create nodes and egdes
   */
  private def phasesSetToDepGraph(phsSet : HashSet[SubComponent]) : DependencyGraph = {
    val graph = new DependencyGraph()

    for(phs <- phsSet) {

      var fromnode = graph.getNodeByPhase(phs)

      phs.runsRightAfter match {
	case None =>
	  for(phsname <- phs.runsAfter) {
	    if (! (phsname equals "terminal")) {
	      var tonode = graph.getNodeByPhase(phsname)
	      graph.softConnectNodes(fromnode, tonode)
	    } else {
	      println("[depends on terminal not allowed, dropping depend: " + fromnode.phasename + " => "+ phsname +"]")
	    }
	  }
	case Some(phsname) =>
	  if (! (phsname equals "terminal")) {
	    var tonode = graph.getNodeByPhase(phsname)
            graph.hardConnectNodes(fromnode, tonode)
	  } else {
	    println("[depends on terminal not allowed, dropping depend: " + fromnode.phasename + " => "+ phsname +"]")
	  }
      }

    }

    return graph
  }


  /* Simple transformation function, that given a dependency graph transforms it into a dependency tree
   * Will return the root of the tree
   */
  private def depGraphToDepTree(graph : DependencyGraph) : Node = {

    graphToDotFile(graph, "depgraph1.dot")

    // Remove nodes without phaseobj
    removeDanglingNodes(graph)

    graphToDotFile(graph, "depgraph2.dot")

    // Enforce hardlinks / runsRightAfter and promote nodes down the tree
    enforceHardlinks(graph)

    graphToDotFile(graph, "depgraph3.dot")

    var root = graph.getNodeByPhase("nsc::parser")

    // Test for cycles in graph, if found will generate fatal error
    testForCycles(root, new HashSet[String]())

    // Simplify graph by removing edges starting from the root
    simplifyGraphFromNode(root, graph)

    graphToDotFile(graph, "depgraph4.dot")

    root
  }


  /* Given a node and a list of phases, it will traverse the dependencies of the node object and
   * call itself recursively
   *
   */
  private def depTree2CompilerPhaseList(node : Node, pchain : List[SubComponent]) : List[SubComponent] = {
    var chain : List[SubComponent] = pchain
    chain = chain ::: List(node.phaseobj)
    if (node.deps.size == 0)
      return chain

    else if (node.deps.size == 1) {
      for(edge <- node.deps)
	chain = depTree2CompilerPhaseList(edge.frm, chain)
      return chain

    } else {

      var nodes = dependencyOrder(node.deps)
      for(nd <- nodes) {
	chain = depTree2CompilerPhaseList(nd, chain)
      }
      return chain
    }
  }


  /* Method called from computePhaseDescriptors in class Global
   * This method will call three aux. methods to convert the phases set into a dependency graph
   * Convert the dependency graph into a dependency tree and return the root of the tree
   * Assemble the compiler phase chain from the root of the dependency tree
   */
  def buildCompilerFromPhasesSet() : List[SubComponent] = {

    // Add all phases in the set to the graph
    val graph = phasesSetToDepGraph(phasesSet)

    val root = depGraphToDepTree(graph)

    return depTree2CompilerPhaseList(root, Nil)
  }

}


/**
 * Test object that will create a new object from the Global class
 * and call the method phaseDescriptors to get the list of phase objects
 * and print the phase names to stdout
 *
 */
object DepGraphTest extends Application {

  val global = new Global()

  var compilerchain = global.phaseDescriptors

  for(phase <- compilerchain) {
    println(" - " + phase.phaseName)
  }

}