Skip to content

Commit 2c4ac2f

Browse files
committed
Determistic output from the async macro
Eliminates the failures reported by the new `testDeterminism` task: Before: ``` % sbt testDeterminism [error] java.lang.AssertionError: /Users/jz/code/scala-async/target/scala-2.12/test-classes-baseline/scala/async/neg/LocalClasses0Spec$stateMachine$async$2.class is not equal to /Users/jz/code/scala-async/target/scala-2.12/test-classes/scala/async/neg/LocalClasses0Spec$stateMachine$async$2.class [error] Use 'last' for the full log. $ jardiff /Users/jz/code/scala-async/target/scala-2.12/test-classes-baseline /Users/jz/code/scala-async/target/scala-2.12/test-classes diff --git a/scala/async/run/ifelse4/TestIfElse4Class$stateMachine$async$1.class.asm b/scala/async/run/ifelse4/TestIfElse4Class$stateMachine$async$1.class.asm index f6f7d26..585e1b5 100644 --- a/scala/async/run/ifelse4/TestIfElse4Class$stateMachine$async$1.class.asm +++ b/scala/async/run/ifelse4/TestIfElse4Class$stateMachine$async$1.class.asm @@ -256,10 +256,10 @@ PUTFIELD scala/async/run/ifelse4/TestIfElse4Class$stateMachine$async$1.state$async : I ALOAD 0 ACONST_NULL - PUTFIELD scala/async/run/ifelse4/TestIfElse4Class$stateMachine$async$1.await$async$0 : Lscala/async/run/ifelse4/TestIfElse4Class$S; + PUTFIELD scala/async/run/ifelse4/TestIfElse4Class$stateMachine$async$1.await$async$1 : Lscala/async/run/ifelse4/TestIfElse4Class$S; ALOAD 0 ACONST_NULL - PUTFIELD scala/async/run/ifelse4/TestIfElse4Class$stateMachine$async$1.await$async$1 : Lscala/async/run/ifelse4/TestIfElse4Class$S; + PUTFIELD scala/async/run/ifelse4/TestIfElse4Class$stateMachine$async$1.await$async$0 : Lscala/async/run/ifelse4/TestIfElse4Class$S; ALOAD 0 GETSTATIC scala/runtime/BoxedUnit.UNIT : Lscala/runtime/BoxedUnit; PUTFIELD scala/async/run/ifelse4/TestIfElse4Class$stateMachine$async$1.match$async$1 : Ljava/lang/Object; diff --git a/scala/async/run/match0/MatchSpec$stateMachine$async$5.class.asm b/scala/async/run/match0/MatchSpec$stateMachine$async$5.class.asm index caa05ea..fac9cd9 100644 --- a/scala/async/run/match0/MatchSpec$stateMachine$async$5.class.asm +++ b/scala/async/run/match0/MatchSpec$stateMachine$async$5.class.asm @@ -88,7 +88,7 @@ L3 ALOAD 0 ICONST_0 - PUTFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.match$async$0$async$1 : I + PUTFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.match$async$0 : I LDC "" INVOKEVIRTUAL java/lang/String.isEmpty ()Z IFEQ L17 @@ -123,7 +123,7 @@ ALOAD 0 ALOAD 0 GETFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.await$async$0 : I - PUTFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.match$async$0$async$1 : I + PUTFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.match$async$0 : I GOTO L1 L7 ALOAD 0 @@ -135,7 +135,7 @@ ATHROW L8 ALOAD 0 - GETFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.match$async$0$async$1 : I + GETFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.match$async$0 : I ISTORE 5 ALOAD 0 BIPUSH 6 @@ -151,7 +151,7 @@ L10 ALOAD 0 ICONST_0 - PUTFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.match$async$0 : I + PUTFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.match$async$0$async$1 : I ALOAD 0 GETFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.await$async$1 : I ISTORE 6 @@ -216,7 +216,7 @@ GETFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.y$async$1 : I ILOAD 10 IMUL - PUTFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.match$async$0 : I + PUTFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.match$async$0$async$1 : I GOTO L1 L15 ALOAD 0 @@ -224,7 +224,7 @@ NEW scala/util/Success DUP ALOAD 0 - GETFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.match$async$0 : I + GETFIELD scala/async/run/match0/MatchSpec$stateMachine$async$5.match$async$0$async$1 : I INVOKESTATIC scala/runtime/BoxesRunTime.boxToInteger (I)Ljava/lang/Integer; INVOKESPECIAL scala/util/Success.<init> (Ljava/lang/Object;)V INVOKEVIRTUAL scala/util/Success.get ()Ljava/lang/Object; diff --git a/scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$17.class.asm b/scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$17.class.asm index 642ac1b..d637739 100644 --- a/scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$17.class.asm +++ b/scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$17.class.asm @@ -219,6 +219,9 @@ ICONST_5 PUTFIELD scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$17.state$async : I ALOAD 0 + ACONST_NULL + PUTFIELD scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$17.await$async$0 : Lscala/None$; + ALOAD 0 NEW scala/async/run/toughtype/ParamWrapper DUP ACONST_NULL @@ -234,9 +237,6 @@ CHECKCAST java/lang/String L23 PUTFIELD scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$17.valueHolder$async$1 : Ljava/lang/String; - ALOAD 0 - ACONST_NULL - PUTFIELD scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$17.await$async$0 : Lscala/None$; ALOAD 0 GETSTATIC scala/None$.MODULE$ : Lscala/None$; PUTFIELD scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$17.match$async$0 : Lscala/None$; diff --git a/scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$18.class.asm b/scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$18.class.asm index 5c15112..0b81dba 100644 --- a/scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$18.class.asm +++ b/scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$18.class.asm @@ -216,9 +216,6 @@ ICONST_5 PUTFIELD scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$18.state$async : I ALOAD 0 - ACONST_NULL - PUTFIELD scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$18.await$async$0 : Lscala/None$; - ALOAD 0 NEW scala/async/run/toughtype/PrivateWrapper DUP ACONST_NULL @@ -234,6 +231,9 @@ L23 PUTFIELD scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$18.valueHolder$async$1 : Ljava/lang/String; ALOAD 0 + ACONST_NULL + PUTFIELD scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$18.await$async$0 : Lscala/None$; + ALOAD 0 GETSTATIC scala/None$.MODULE$ : Lscala/None$; PUTFIELD scala/async/run/toughtype/ToughTypeSpec$stateMachine$async$18.match$async$0 : Lscala/None$; GOTO L1 ``` After: ``` $ for i in {1..10}; do sbt testDeterminism || break; done ... <NO ERRORS> ```
1 parent 6ecaf0f commit 2c4ac2f

File tree

3 files changed

+38
-26
lines changed

3 files changed

+38
-26
lines changed

src/main/scala/scala/async/internal/Lifter.scala

+18-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package scala.async.internal
22

3+
import scala.collection.mutable
4+
35
trait Lifter {
46
self: AsyncMacro =>
57
import c.universe._
@@ -37,7 +39,7 @@ trait Lifter {
3739
}
3840

3941

40-
val defs: Map[Tree, Int] = {
42+
val defs: mutable.LinkedHashMap[Tree, Int] = {
4143
/** Collect the DefTrees directly enclosed within `t` that have the same owner */
4244
def collectDirectlyEnclosedDefs(t: Tree): List[DefTree] = t match {
4345
case ld: LabelDef => Nil
@@ -48,33 +50,33 @@ trait Lifter {
4850
companionship.record(childDefs)
4951
childDefs
5052
}
51-
asyncStates.flatMap {
53+
mutable.LinkedHashMap(asyncStates.flatMap {
5254
asyncState =>
5355
val defs = collectDirectlyEnclosedDefs(Block(asyncState.allStats: _*))
5456
defs.map((_, asyncState.state))
55-
}.toMap
57+
}: _*)
5658
}
5759

5860
// In which block are these symbols defined?
59-
val symToDefiningState: Map[Symbol, Int] = defs.map {
61+
val symToDefiningState: mutable.LinkedHashMap[Symbol, Int] = defs.map {
6062
case (k, v) => (k.symbol, v)
6163
}
6264

6365
// The definitions trees
64-
val symToTree: Map[Symbol, Tree] = defs.map {
66+
val symToTree: mutable.LinkedHashMap[Symbol, Tree] = defs.map {
6567
case (k, v) => (k.symbol, k)
6668
}
6769

6870
// The direct references of each definition tree
69-
val defSymToReferenced: Map[Symbol, List[Symbol]] = defs.keys.map {
70-
case tree => (tree.symbol, tree.collect {
71+
val defSymToReferenced: mutable.LinkedHashMap[Symbol, List[Symbol]] = defs.map {
72+
case (tree, _) => (tree.symbol, tree.collect {
7173
case rt: RefTree if symToDefiningState.contains(rt.symbol) => rt.symbol
7274
})
73-
}.toMap
75+
}
7476

7577
// The direct references of each block, excluding references of `DefTree`-s which
7678
// are already accounted for.
77-
val stateIdToDirectlyReferenced: Map[Int, List[Symbol]] = {
79+
val stateIdToDirectlyReferenced: mutable.LinkedHashMap[Int, List[Symbol]] = {
7880
val refs: List[(Int, Symbol)] = asyncStates.flatMap(
7981
asyncState => asyncState.stats.filterNot(t => t.isDef && !isLabel(t.symbol)).flatMap(_.collect {
8082
case rt: RefTree
@@ -84,8 +86,8 @@ trait Lifter {
8486
toMultiMap(refs)
8587
}
8688

87-
def liftableSyms: Set[Symbol] = {
88-
val liftableMutableSet = collection.mutable.Set[Symbol]()
89+
def liftableSyms: mutable.LinkedHashSet[Symbol] = {
90+
val liftableMutableSet = mutable.LinkedHashSet[Symbol]()
8991
def markForLift(sym: Symbol): Unit = {
9092
if (!liftableMutableSet(sym)) {
9193
liftableMutableSet += sym
@@ -97,19 +99,19 @@ trait Lifter {
9799
}
98100
}
99101
// Start things with DefTrees directly referenced from statements from other states...
100-
val liftableStatementRefs: List[Symbol] = stateIdToDirectlyReferenced.toList.flatMap {
102+
val liftableStatementRefs: List[Symbol] = stateIdToDirectlyReferenced.iterator.flatMap {
101103
case (i, syms) => syms.filter(sym => symToDefiningState(sym) != i)
102-
}
104+
}.toList
103105
// .. and likewise for DefTrees directly referenced by other DefTrees from other states
104106
val liftableRefsOfDefTrees = defSymToReferenced.toList.flatMap {
105107
case (referee, referents) => referents.filter(sym => symToDefiningState(sym) != symToDefiningState(referee))
106108
}
107109
// Mark these for lifting, which will follow transitive references.
108110
(liftableStatementRefs ++ liftableRefsOfDefTrees).foreach(markForLift)
109-
liftableMutableSet.toSet
111+
liftableMutableSet
110112
}
111113

112-
val lifted = liftableSyms.map(symToTree).toList.map {
114+
liftableSyms.iterator.map(symToTree).map {
113115
t =>
114116
val sym = t.symbol
115117
val treeLifted = t match {
@@ -147,7 +149,6 @@ trait Lifter {
147149
treeCopy.TypeDef(td, Modifiers(sym.flags), sym.name, tparams, rhs)
148150
}
149151
atPos(t.pos)(treeLifted)
150-
}
151-
lifted
152+
}.toList
152153
}
153154
}

src/main/scala/scala/async/internal/LiveVariables.scala

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package scala.async.internal
22

3+
import scala.collection.mutable
4+
35
import java.util
46
import java.util.function.{IntConsumer, IntPredicate}
57

@@ -19,12 +21,12 @@ trait LiveVariables {
1921
* @return a map mapping a state to the fields that should be nulled out
2022
* upon resuming that state
2123
*/
22-
def fieldsToNullOut(asyncStates: List[AsyncState], liftables: List[Tree]): Map[Int, List[Tree]] = {
24+
def fieldsToNullOut(asyncStates: List[AsyncState], liftables: List[Tree]): mutable.LinkedHashMap[Int, List[Tree]] = {
2325
// live variables analysis:
2426
// the result map indicates in which states a given field should be nulled out
25-
val liveVarsMap: Map[Tree, StateSet] = liveVars(asyncStates, liftables)
27+
val liveVarsMap: mutable.LinkedHashMap[Tree, StateSet] = liveVars(asyncStates, liftables)
2628

27-
var assignsOf = Map[Int, List[Tree]]()
29+
var assignsOf = mutable.LinkedHashMap[Int, List[Tree]]()
2830

2931
for ((fld, where) <- liveVarsMap) {
3032
where.foreach { new IntConsumer { def accept(state: Int): Unit = {
@@ -54,7 +56,7 @@ trait LiveVariables {
5456
* @param liftables the lifted fields
5557
* @return a map which indicates for a given field (the key) the states in which it should be nulled out
5658
*/
57-
def liveVars(asyncStates: List[AsyncState], liftables: List[Tree]): Map[Tree, StateSet] = {
59+
def liveVars(asyncStates: List[AsyncState], liftables: List[Tree]): mutable.LinkedHashMap[Tree, StateSet] = {
5860
val liftedSyms: Set[Symbol] = // include only vars
5961
liftables.iterator.filter {
6062
case ValDef(mods, _, _, _) => mods.hasFlag(MUTABLE)
@@ -262,15 +264,15 @@ trait LiveVariables {
262264
result
263265
}
264266

265-
val lastUsages: Map[Tree, StateSet] =
266-
liftables.iterator.map(fld => fld -> lastUsagesOf(fld, finalState)).toMap
267+
val lastUsages: mutable.LinkedHashMap[Tree, StateSet] =
268+
mutable.LinkedHashMap(liftables.map(fld => fld -> lastUsagesOf(fld, finalState)): _*)
267269

268270
if(AsyncUtils.verbose) {
269271
for ((fld, lastStates) <- lastUsages)
270272
AsyncUtils.vprintln(s"field ${fld.symbol.name} is last used in states ${lastStates.iterator.mkString(", ")}")
271273
}
272274

273-
val nullOutAt: Map[Tree, StateSet] =
275+
val nullOutAt: mutable.LinkedHashMap[Tree, StateSet] =
274276
for ((fld, lastStates) <- lastUsages) yield {
275277
var result = new StateSet
276278
lastStates.foreach(new IntConsumer { def accept(s: Int): Unit = {

src/main/scala/scala/async/internal/TransformUtils.scala

+11-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package scala.async.internal
66
import scala.reflect.macros.Context
77
import reflect.ClassTag
88
import scala.collection.immutable.ListMap
9+
import scala.collection.mutable
10+
import scala.collection.mutable.ListBuffer
911

1012
/**
1113
* Utilities used in both `ExprBuilder` and `AnfTransform`.
@@ -303,8 +305,15 @@ private[async] trait TransformUtils {
303305
})
304306
}
305307

306-
def toMultiMap[A, B](as: Iterable[(A, B)]): Map[A, List[B]] =
307-
as.toList.groupBy(_._1).mapValues(_.map(_._2).toList).toMap
308+
def toMultiMap[A, B](abs: Iterable[(A, B)]): mutable.LinkedHashMap[A, List[B]] = {
309+
// LinkedHashMap for stable order of results.
310+
val result = new mutable.LinkedHashMap[A, ListBuffer[B]]()
311+
for ((a, b) <- abs) {
312+
val buffer = result.getOrElseUpdate(a, new ListBuffer[B])
313+
buffer += b
314+
}
315+
result.map { case (a, b) => (a, b.toList) }
316+
}
308317

309318
// Attributed version of `TreeGen#mkCastPreservingAnnotations`
310319
def mkAttributedCastPreservingAnnotations(tree: Tree, tp: Type): Tree = {

0 commit comments

Comments
 (0)