@@ -76,13 +76,13 @@ trait ExprBuilder {
76
76
mkHandlerCase(state, stats)
77
77
78
78
override val toString : String =
79
- s " AsyncStateWithoutAwait # $state, nextStates = $nextStates"
79
+ s " AsyncStateWithoutAwait # $state, nextStates = ${ nextStates.toList} "
80
80
}
81
81
82
82
/** A sequence of statements that concludes with an `await` call. The `onComplete`
83
83
* handler will unconditionally transition to `nextState`.
84
84
*/
85
- final class AsyncStateWithAwait (var stats : List [Tree ], val state : Int , onCompleteState : Int , nextState : Int ,
85
+ final class AsyncStateWithAwait (var stats : List [Tree ], val state : Int , val onCompleteState : Int , nextState : Int ,
86
86
val awaitable : Awaitable , symLookup : SymLookup )
87
87
extends AsyncState {
88
88
@@ -268,11 +268,11 @@ trait ExprBuilder {
268
268
}
269
269
270
270
// populate asyncStates
271
- def add (stat : Tree ): Unit = stat match {
271
+ def add (stat : Tree , afterState : Option [ Int ] = None ): Unit = stat match {
272
272
// the val name = await(..) pattern
273
273
case vd @ ValDef (mods, name, tpt, Apply (fun, arg :: Nil )) if isAwait(fun) =>
274
274
val onCompleteState = nextState()
275
- val afterAwaitState = nextState()
275
+ val afterAwaitState = afterState.getOrElse( nextState() )
276
276
val awaitable = Awaitable (arg, stat.symbol, tpt.tpe, vd)
277
277
asyncStates += stateBuilder.resultWithAwait(awaitable, onCompleteState, afterAwaitState) // complete with await
278
278
currState = afterAwaitState
@@ -283,7 +283,7 @@ trait ExprBuilder {
283
283
284
284
val thenStartState = nextState()
285
285
val elseStartState = nextState()
286
- val afterIfState = nextState()
286
+ val afterIfState = afterState.getOrElse( nextState() )
287
287
288
288
asyncStates +=
289
289
// the two Int arguments are the start state of the then branch and the else branch, respectively
@@ -305,7 +305,7 @@ trait ExprBuilder {
305
305
java.util.Arrays .setAll(caseStates, new IntUnaryOperator {
306
306
override def applyAsInt (operand : Int ): Int = nextState()
307
307
})
308
- val afterMatchState = nextState()
308
+ val afterMatchState = afterState.getOrElse( nextState() )
309
309
310
310
asyncStates +=
311
311
stateBuilder.resultWithMatch(scrutinee, cases, caseStates, symLookup)
@@ -323,15 +323,16 @@ trait ExprBuilder {
323
323
if containsAwait(rhs) || directlyAdjacentLabelDefs(ld).exists(containsAwait) =>
324
324
325
325
val startLabelState = stateIdForLabel(ld.symbol)
326
- val afterLabelState = nextState()
326
+ val afterLabelState = afterState.getOrElse( nextState() )
327
327
asyncStates += stateBuilder.resultWithLabel(startLabelState, symLookup)
328
328
labelDefStates(ld.symbol) = startLabelState
329
329
val builder = nestedBlockBuilder(rhs, startLabelState, afterLabelState)
330
330
asyncStates ++= builder.asyncStates
331
331
currState = afterLabelState
332
332
stateBuilder = new AsyncStateBuilder (currState, symLookup)
333
333
case b @ Block (stats, expr) =>
334
- (stats :+ expr) foreach (add)
334
+ for (stat <- stats) add(stat)
335
+ add(expr, afterState = Some (endState))
335
336
case _ =>
336
337
checkForUnsupportedAwait(stat)
337
338
stateBuilder += stat
@@ -345,6 +346,8 @@ trait ExprBuilder {
345
346
def asyncStates : List [AsyncState ]
346
347
347
348
def onCompleteHandler [T : WeakTypeTag ]: Tree
349
+
350
+ def toDot : String
348
351
}
349
352
350
353
case class SymLookup (stateMachineClass : Symbol , applyTrParam : Symbol ) {
@@ -369,7 +372,78 @@ trait ExprBuilder {
369
372
val blockBuilder = new AsyncBlockBuilder (stats, expr, startState, endState, symLookup)
370
373
371
374
new AsyncBlock {
372
- def asyncStates = blockBuilder.asyncStates.toList
375
+ val switchIds = mutable.AnyRefMap [Integer , Integer ]()
376
+
377
+ // render with http://graphviz.it/#/new
378
+ def toDot : 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(" \" " , " "" ).replaceAllLiterally(" <" , " <" ).replaceAllLiterally(" >" , " >" ))
387
+ }
388
+ builder.append(" </font>" )
389
+ }
390
+ val dotBuilder = new StringBuilder ()
391
+ dotBuilder.append(" digraph {\n " )
392
+ def stateLabel (s : Int ) = {
393
+ if (s == 0 ) " INITIAL" else if (s == Int .MaxValue ) " TERMINAL" else switchIds.getOrElse(s, s).toString
394
+ }
395
+ val length = asyncStates.size
396
+ for ((state, i) <- asyncStates.zipWithIndex) {
397
+ dotBuilder.append(s """ ${stateLabel(state.state)} [label= """ ).append(" <" )
398
+ if (i != length - 1 ) {
399
+ val CaseDef (_, _, body) = state.mkHandlerCaseForState
400
+ toHtmlLabel(stateLabel(state.state), showCode(body), dotBuilder)
401
+ } else {
402
+ toHtmlLabel(stateLabel(state.state), state.allStats.map(showCode(_)).mkString(" \n " ), dotBuilder)
403
+ }
404
+ dotBuilder.append(" > ]\n " )
405
+ }
406
+ for (state <- states; succ <- state.nextStates) {
407
+ dotBuilder.append(s """ ${stateLabel(state.state)} -> ${stateLabel(succ)}""" )
408
+ dotBuilder.append(" \n " )
409
+ }
410
+ dotBuilder.append(" }\n " )
411
+ dotBuilder.toString
412
+ }
413
+
414
+ lazy val asyncStates : List [AsyncState ] = filterStates
415
+
416
+ def filterStates = {
417
+ val all = blockBuilder.asyncStates.toList
418
+ val (initial :: rest) = all
419
+ val map = all.iterator.map(x => (x.state, x)).toMap
420
+ var seen = mutable.HashSet [Int ]()
421
+ def loop (state : AsyncState ): Unit = {
422
+ seen.add(state.state)
423
+ for (i <- state.nextStates) {
424
+ if (i != Int .MaxValue && ! seen.contains(i)) {
425
+ loop(map(i))
426
+ }
427
+ }
428
+ }
429
+ loop(initial)
430
+ val live = rest.filter(state => seen(state.state))
431
+ var nextSwitchId = 0
432
+ (initial :: live).foreach { state =>
433
+ val switchId = nextSwitchId
434
+ switchIds(state.state) = switchId
435
+ nextSwitchId += 1
436
+ state match {
437
+ case state : AsyncStateWithAwait =>
438
+ val switchId = nextSwitchId
439
+ switchIds(state.onCompleteState) = switchId
440
+ nextSwitchId += 1
441
+ case _ =>
442
+ }
443
+ }
444
+ initial :: live
445
+
446
+ }
373
447
374
448
def mkCombinedHandlerCases [T : WeakTypeTag ]: List [CaseDef ] = {
375
449
val caseForLastState : CaseDef = {
@@ -413,7 +487,7 @@ trait ExprBuilder {
413
487
val stateMemberSymbol = symLookup.stateMachineMember(name.state)
414
488
val stateMemberRef = symLookup.memberRef(name.state)
415
489
val body = Match (stateMemberRef, mkCombinedHandlerCases[T ] ++ initStates.flatMap(_.mkOnCompleteHandler[T ]) ++ List (CaseDef (Ident (nme.WILDCARD ), EmptyTree , Throw (Apply (Select (New (Ident (defn.IllegalStateExceptionClass )), termNames.CONSTRUCTOR ), List ())))))
416
- val body1 = eliminateDeadStates (body)
490
+ val body1 = compactStates (body)
417
491
418
492
maybeTry(
419
493
body1,
@@ -432,48 +506,12 @@ trait ExprBuilder {
432
506
})), EmptyTree )
433
507
}
434
508
435
- // Identify dead states: `case <id> => { state = nextId; (); (); ... }, eliminated, and compact state ids to
436
- // enable emission of a tableswitch.
437
- private def eliminateDeadStates (m : Match ): Tree = {
438
- object DeadState {
439
- private val liveStates = mutable.AnyRefMap [Integer , Integer ]()
440
- private val deadStates = mutable.AnyRefMap [Integer , Integer ]()
441
- private var compactedStateId = 1
442
- for (CaseDef (Literal (Constant (stateId : Integer )), EmptyTree , body) <- m.cases) {
443
- body match {
444
- case _ if (stateId == 0 ) => liveStates(stateId) = stateId
445
- case Block (Assign (_, Literal (Constant (nextState : Integer ))) :: rest, expr) if (expr :: rest).forall(t => isLiteralUnit(t)) =>
446
- deadStates(stateId) = nextState
447
- case _ =>
448
- liveStates(stateId) = compactedStateId
449
- compactedStateId += 1
450
- }
451
- }
452
- if (deadStates.nonEmpty)
453
- AsyncUtils .vprintln(s " ${deadStates.size} dead states eliminated " )
454
- def isDead (i : Integer ) = deadStates.contains(i)
455
- def translatedStateId (i : Integer , tree : Tree ): Integer = {
456
- def chaseDead (i : Integer ): Integer = {
457
- val replacement = deadStates.getOrNull(i)
458
- if (replacement == null ) i
459
- else chaseDead(replacement)
460
- }
461
-
462
- val live = chaseDead(i)
463
- liveStates.get(live) match {
464
- case Some (x) => x
465
- case None => sys.error(s " $live, $liveStates \n $deadStates\n $m\n\n ==== \n $tree" )
466
- }
467
- }
468
- }
509
+ private def compactStates (m : Match ): Tree = {
469
510
val stateMemberSymbol = symLookup.stateMachineMember(name.state)
470
- // - remove CaseDef-s for dead states
471
- // - rewrite state transitions to dead states to instead transition to the
472
- // non-dead successor.
473
- val elimDeadStateTransform = new Transformer {
511
+ val compactStateTransform = new Transformer {
474
512
override def transform (tree : Tree ): Tree = tree match {
475
513
case as @ Assign (lhs, Literal (Constant (i : Integer ))) if lhs.symbol == stateMemberSymbol =>
476
- val replacement = DeadState .translatedStateId(i, as )
514
+ val replacement = switchIds(i )
477
515
treeCopy.Assign (tree, lhs, Literal (Constant (replacement)))
478
516
case _ : Match | _ : CaseDef | _ : Block | _ : If =>
479
517
super .transform(tree)
@@ -482,12 +520,9 @@ trait ExprBuilder {
482
520
}
483
521
val cases1 = m.cases.flatMap {
484
522
case cd @ CaseDef (Literal (Constant (i : Integer )), EmptyTree , rhs) =>
485
- if (DeadState .isDead(i)) Nil
486
- else {
487
- val replacement = DeadState .translatedStateId(i, cd)
488
- val rhs1 = elimDeadStateTransform.transform(rhs)
489
- treeCopy.CaseDef (cd, Literal (Constant (replacement)), EmptyTree , rhs1) :: Nil
490
- }
523
+ val replacement = switchIds(i)
524
+ val rhs1 = compactStateTransform.transform(rhs)
525
+ treeCopy.CaseDef (cd, Literal (Constant (replacement)), EmptyTree , rhs1) :: Nil
491
526
case x => x :: Nil
492
527
}
493
528
treeCopy.Match (m, m.selector, cases1)
0 commit comments