Skip to content

Commit e598f87

Browse files
committed
Output .dot graph of state machine in verbose mode.
Sample: https://gist.github.com/88225478b11c118609b9348d61e13630 View with a local Graphviz install or http://graphviz.it/#/gallery/unix.gv Sample generated from late expansion of: val result = run( """ import scala.async.run.late.{autoawait,lateasync} case class FixedFoo(foo: Int) class Foobar(val foo: Int, val bar: Double) { def guard: Boolean = true @autoawait @lateasync def getValue = 4.2 @autoawait @lateasync def func(f: Any) = { ("": Any) match { case (x1, y1) if guard => x1.toString; y1.toString case (x2, y2) if guard => x2.toString; y2.toString case (x3, y3) if guard => x3.toString; y3.toString case (x4, y4) => getValue; x4.toString; y4.toString } } } object Test { @lateasync def test() = new Foobar(0, 0).func(4) } """)
1 parent cb3ba84 commit e598f87

File tree

3 files changed

+101
-14
lines changed

3 files changed

+101
-14
lines changed

src/main/scala/scala/async/internal/AsyncTransform.scala

+9-4
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,6 @@ trait AsyncTransform {
7070
buildAsyncBlock(anfTree, symLookup)
7171
}
7272

73-
if(AsyncUtils.verbose)
74-
logDiagnostics(anfTree, asyncBlock.asyncStates.map(_.toString))
75-
7673
val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates)
7774

7875
// live variables analysis
@@ -114,10 +111,14 @@ trait AsyncTransform {
114111
futureSystemOps.spawn(body, execContext) // generate lean code for the simple case of `async { 1 + 1 }`
115112
else
116113
startStateMachine
114+
115+
if(AsyncUtils.verbose) {
116+
logDiagnostics(anfTree, asyncBlock, asyncBlock.asyncStates.map(_.toString))
117+
}
117118
cleanupContainsAwaitAttachments(result)
118119
}
119120

120-
def logDiagnostics(anfTree: Tree, states: Seq[String]) {
121+
def logDiagnostics(anfTree: Tree, block: AsyncBlock, states: Seq[String]) {
121122
def location = try {
122123
macroPos.source.path
123124
} catch {
@@ -129,6 +130,10 @@ trait AsyncTransform {
129130
AsyncUtils.vprintln(s"${c.macroApplication}")
130131
AsyncUtils.vprintln(s"ANF transform expands to:\n $anfTree")
131132
states foreach (s => AsyncUtils.vprintln(s))
133+
AsyncUtils.vprintln("===== BEFORE DSE =====")
134+
AsyncUtils.vprintln(block.toDot(afterDSE = false))
135+
AsyncUtils.vprintln("===== AFTER DSE =====")
136+
AsyncUtils.vprintln(block.toDot(afterDSE = true))
132137
}
133138

134139
/**

src/main/scala/scala/async/internal/ExprBuilder.scala

+54-2
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,8 @@ trait ExprBuilder {
345345
def asyncStates: List[AsyncState]
346346

347347
def onCompleteHandler[T: WeakTypeTag]: Tree
348+
349+
def toDot(afterDSE: Boolean): String
348350
}
349351

350352
case class SymLookup(stateMachineClass: Symbol, applyTrParam: Symbol) {
@@ -369,6 +371,58 @@ trait ExprBuilder {
369371
val blockBuilder = new AsyncBlockBuilder(stats, expr, startState, endState, symLookup)
370372

371373
new AsyncBlock {
374+
val liveStates = mutable.AnyRefMap[Integer, Integer]()
375+
val deadStates = mutable.AnyRefMap[Integer, Integer]()
376+
377+
// render with http://graphviz.it/#/new
378+
def toDot(afterDSE: Boolean): String = {
379+
val states = asyncStates
380+
def toHtmlLabel(label: String, preText: String, builder: StringBuilder): Unit = {
381+
builder.append("<b>").append(label).append("</b>").append("<br/>")
382+
builder.append("<font face=\"Courier\">")
383+
preText.split("\n").foreach {
384+
(line: String) =>
385+
builder.append("<br/>")
386+
builder.append(line.replaceAllLiterally("\"", "&quot;").replaceAllLiterally("<", "&lt;").replaceAllLiterally(">", "&gt;"))
387+
}
388+
builder.append("</font>")
389+
}
390+
val dotBuilder = new StringBuilder()
391+
dotBuilder.append("digraph {\n")
392+
def stateLabel(s: Int) = {
393+
val beforeDseLabel = if (s == 0) "INITIAL" else if (s == Int.MaxValue) "TERMINAL" else if (s > 0) "S" + s else "C" + Math.abs(s)
394+
if (afterDSE) {
395+
"\"S" + liveStates.getOrElse(s, s) + " (" + beforeDseLabel + ")\""
396+
} else {
397+
beforeDseLabel
398+
}
399+
400+
}
401+
val length = asyncStates.size
402+
for ((state, i) <- asyncStates.zipWithIndex) {
403+
val liveStateIdOpt: Option[Int] = if (afterDSE) {
404+
getClass
405+
liveStates.get(state.state).map(_.intValue())
406+
} else Some(state.state)
407+
for (_ <- liveStateIdOpt) {
408+
dotBuilder.append(s"""${stateLabel(state.state)} [label=""").append("<")
409+
if (i != length - 1) {
410+
val CaseDef(_, _, body) = state.mkHandlerCaseForState
411+
toHtmlLabel(stateLabel(state.state), showCode(body), dotBuilder)
412+
} else {
413+
toHtmlLabel(stateLabel(state.state), state.allStats.map(showCode(_)).mkString("\n"), dotBuilder)
414+
}
415+
dotBuilder.append("> ]\n")
416+
}
417+
}
418+
for (state <- states; if liveStates.contains(state.state); succ <- state.nextStates) {
419+
dotBuilder.append(s"""${stateLabel(state.state)} -> ${stateLabel(succ)}""")
420+
dotBuilder.append("\n")
421+
}
422+
dotBuilder.append("}\n")
423+
dotBuilder.toString
424+
}
425+
372426
def asyncStates = blockBuilder.asyncStates.toList
373427

374428
def mkCombinedHandlerCases[T: WeakTypeTag]: List[CaseDef] = {
@@ -436,8 +490,6 @@ trait ExprBuilder {
436490
// enable emission of a tableswitch.
437491
private def eliminateDeadStates(m: Match): Tree = {
438492
object DeadState {
439-
private val liveStates = mutable.AnyRefMap[Integer, Integer]()
440-
private val deadStates = mutable.AnyRefMap[Integer, Integer]()
441493
private var compactedStateId = 1
442494
for (CaseDef(Literal(Constant(stateId: Integer)), EmptyTree, body) <- m.cases) {
443495
body match {

src/test/scala/scala/async/run/late/LateExpansion.scala

+38-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import org.junit.{Assert, Test}
77

88
import scala.annotation.StaticAnnotation
99
import scala.annotation.meta.{field, getter}
10-
import scala.async.TreeInterrogation
10+
import scala.async.TreeInterrogationApp
1111
import scala.async.internal.AsyncId
1212
import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader
1313
import scala.tools.nsc._
@@ -27,6 +27,7 @@ class LateExpansion {
2727
| """.stripMargin)
2828
assertEquals("foobar", result)
2929
}
30+
3031
@Test def testGuard(): Unit = {
3132
val result = wrapAndRun(
3233
"""
@@ -143,6 +144,7 @@ class LateExpansion {
143144
|}
144145
| """.stripMargin)
145146
}
147+
146148
@Test def shadowing2(): Unit = {
147149
val result = run(
148150
"""
@@ -366,6 +368,7 @@ class LateExpansion {
366368
}
367369
""")
368370
}
371+
369372
@Test def testNegativeArraySizeExceptionFine1(): Unit = {
370373
val result = run(
371374
"""
@@ -386,10 +389,35 @@ class LateExpansion {
386389
}
387390
""")
388391
}
392+
393+
@Test def testNull(): Unit = {
394+
val result = run(
395+
"""
396+
import scala.async.run.late.{autoawait,lateasync}
397+
case class FixedFoo(foo: Int)
398+
class Foobar(val foo: Int, val bar: Double) {
399+
def guard: Boolean = true
400+
@autoawait @lateasync def getValue = 4.2
401+
@autoawait @lateasync def func(f: Any) = {
402+
("": Any) match {
403+
case (x1, y1) if guard => x1.toString; y1.toString
404+
case (x2, y2) if guard => x2.toString; y2.toString
405+
case (x3, y3) if guard => x3.toString; y3.toString
406+
case (x4, y4) =>
407+
getValue; x4.toString; y4.toString
408+
}
409+
}
410+
}
411+
object Test {
412+
@lateasync def test() = new Foobar(0, 0).func(4)
413+
}
414+
""")
415+
}
416+
389417
def run(code: String): Any = {
390418
val reporter = new StoreReporter
391419
val settings = new Settings(println(_))
392-
// settings.processArgumentString("-Xprint:patmat,postpatmat,jvm -Ybackend:GenASM -nowarn")
420+
settings.processArgumentString("-Xprint:patmat,postpatmat,jvm -nowarn")
393421
settings.outdir.value = sys.props("java.io.tmpdir")
394422
settings.embeddedDefaults(getClass.getClassLoader)
395423
val isInSBT = !settings.classpath.isSetByUser
@@ -407,9 +435,9 @@ class LateExpansion {
407435

408436
val run = new Run
409437
val source = newSourceFile(code)
410-
// TreeInterrogation.withDebug {
411-
run.compileSources(source :: Nil)
412-
// }
438+
TreeInterrogationApp.withDebug {
439+
run.compileSources(source :: Nil)
440+
}
413441
Assert.assertTrue(reporter.infos.mkString("\n"), !reporter.hasErrors)
414442
val loader = new URLClassLoader(Seq(new File(settings.outdir.value).toURI.toURL), global.getClass.getClassLoader)
415443
val cls = loader.loadClass("Test")
@@ -418,6 +446,7 @@ class LateExpansion {
418446
}
419447

420448
abstract class LatePlugin extends Plugin {
449+
421450
import global._
422451

423452
override val components: List[PluginComponent] = List(new PluginComponent with TypingTransformers {
@@ -434,16 +463,16 @@ abstract class LatePlugin extends Plugin {
434463
super.transform(tree) match {
435464
case ap@Apply(fun, args) if fun.symbol.hasAnnotation(autoAwaitSym) =>
436465
localTyper.typed(Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, awaitSym), TypeTree(ap.tpe) :: Nil), ap :: Nil))
437-
case sel@Select(fun, _) if sel.symbol.hasAnnotation(autoAwaitSym) && !(tree.tpe.isInstanceOf[MethodTypeApi] || tree.tpe.isInstanceOf[PolyTypeApi] ) =>
466+
case sel@Select(fun, _) if sel.symbol.hasAnnotation(autoAwaitSym) && !(tree.tpe.isInstanceOf[MethodTypeApi] || tree.tpe.isInstanceOf[PolyTypeApi]) =>
438467
localTyper.typed(Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, awaitSym), TypeTree(sel.tpe) :: Nil), sel :: Nil))
439468
case dd: DefDef if dd.symbol.hasAnnotation(lateAsyncSym) => atOwner(dd.symbol) {
440-
deriveDefDef(dd){ rhs: Tree =>
469+
deriveDefDef(dd) { rhs: Tree =>
441470
val invoke = Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, asyncSym), TypeTree(rhs.tpe) :: Nil), List(rhs))
442471
localTyper.typed(atPos(dd.pos)(invoke))
443472
}
444473
}
445474
case vd: ValDef if vd.symbol.hasAnnotation(lateAsyncSym) => atOwner(vd.symbol) {
446-
deriveValDef(vd){ rhs: Tree =>
475+
deriveValDef(vd) { rhs: Tree =>
447476
val invoke = Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, asyncSym), TypeTree(rhs.tpe) :: Nil), List(rhs))
448477
localTyper.typed(atPos(vd.pos)(invoke))
449478
}
@@ -454,6 +483,7 @@ abstract class LatePlugin extends Plugin {
454483
}
455484
}
456485
}
486+
457487
override def newPhase(prev: Phase): Phase = new StdPhase(prev) {
458488
override def apply(unit: CompilationUnit): Unit = {
459489
val translated = newTransformer(unit).transformUnit(unit)

0 commit comments

Comments
 (0)