Skip to content

Commit 75ef24d

Browse files
committed
Merge pull request #82 from retronym/topic/live-variable-speedup
Fix asymptotic performance issues in live variables analysis.
2 parents 37ef2a7 + 9d1246d commit 75ef24d

File tree

2 files changed

+120
-28
lines changed

2 files changed

+120
-28
lines changed

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

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,22 @@ trait LiveVariables {
126126

127127
/** Tests if `state1` is a predecessor of `state2`.
128128
*/
129-
def isPred(state1: Int, state2: Int, seen: Set[Int] = Set()): Boolean =
130-
if (seen(state1)) false // breaks cycles in the CFG
131-
else cfg get state1 match {
132-
case Some(nextStates) =>
133-
nextStates.contains(state2) || nextStates.exists(isPred(_, state2, seen + state1))
134-
case None =>
135-
false
136-
}
129+
def isPred(state1: Int, state2: Int): Boolean = {
130+
val seen = scala.collection.mutable.HashSet[Int]()
131+
132+
def isPred0(state1: Int, state2: Int): Boolean =
133+
if(state1 == state2) false
134+
else if (seen(state1)) false // breaks cycles in the CFG
135+
else cfg get state1 match {
136+
case Some(nextStates) =>
137+
seen += state1
138+
nextStates.contains(state2) || nextStates.exists(isPred0(_, state2))
139+
case None =>
140+
false
141+
}
142+
143+
isPred0(state1, state2)
144+
}
137145

138146
val finalState = asyncStates.find(as => !asyncStates.exists(other => isPred(as.state, other.state))).get
139147

@@ -162,12 +170,10 @@ trait LiveVariables {
162170
LVexit = LVexit + (finalState.state -> noNull)
163171

164172
var currStates = List(finalState) // start at final state
165-
var pred = List[AsyncState]() // current predecessor states
166-
var hasChanged = true // if something has changed we need to continue iterating
167173
var captured: Set[Symbol] = Set()
168174

169-
while (hasChanged) {
170-
hasChanged = false
175+
while (!currStates.isEmpty) {
176+
var entryChanged: List[AsyncState] = Nil
171177

172178
for (cs <- currStates) {
173179
val LVentryOld = LVentry(cs.state)
@@ -176,44 +182,53 @@ trait LiveVariables {
176182
val LVentryNew = LVexit(cs.state) ++ referenced.used
177183
if (!LVentryNew.sameElements(LVentryOld)) {
178184
LVentry = LVentry + (cs.state -> LVentryNew)
179-
hasChanged = true
185+
entryChanged ::= cs
180186
}
181187
}
182188

183-
pred = currStates.flatMap(cs => asyncStates.filter(_.nextStates.contains(cs.state)))
189+
val pred = entryChanged.flatMap(cs => asyncStates.filter(_.nextStates.contains(cs.state)))
190+
var exitChanged: List[AsyncState] = Nil
184191

185192
for (p <- pred) {
186193
val LVexitOld = LVexit(p.state)
187194
val LVexitNew = p.nextStates.flatMap(succ => LVentry(succ)).toSet
188195
if (!LVexitNew.sameElements(LVexitOld)) {
189196
LVexit = LVexit + (p.state -> LVexitNew)
190-
hasChanged = true
197+
exitChanged ::= p
191198
}
192199
}
193200

194-
currStates = pred
201+
currStates = exitChanged
195202
}
196203

197204
for (as <- asyncStates) {
198205
AsyncUtils.vprintln(s"LVentry at state #${as.state}: ${LVentry(as.state).mkString(", ")}")
199206
AsyncUtils.vprintln(s"LVexit at state #${as.state}: ${LVexit(as.state).mkString(", ")}")
200207
}
201208

202-
def lastUsagesOf(field: Tree, at: AsyncState, avoid: Set[AsyncState]): Set[Int] =
203-
if (avoid(at)) Set()
204-
else if (captured(field.symbol)) {
205-
Set()
206-
}
207-
else LVentry get at.state match {
208-
case Some(fields) if fields.exists(_ == field.symbol) =>
209-
Set(at.state)
210-
case _ =>
211-
val preds = asyncStates.filter(_.nextStates.contains(at.state)).toSet
212-
preds.flatMap(p => lastUsagesOf(field, p, avoid + at))
209+
def lastUsagesOf(field: Tree, at: AsyncState): Set[Int] = {
210+
val avoid = scala.collection.mutable.HashSet[AsyncState]()
211+
212+
def lastUsagesOf0(field: Tree, at: AsyncState): Set[Int] = {
213+
if (avoid(at)) Set()
214+
else if (captured(field.symbol)) {
215+
Set()
216+
}
217+
else LVentry get at.state match {
218+
case Some(fields) if fields.exists(_ == field.symbol) =>
219+
Set(at.state)
220+
case _ =>
221+
avoid += at
222+
val preds = asyncStates.filter(_.nextStates.contains(at.state)).toSet
223+
preds.flatMap(p => lastUsagesOf0(field, p))
224+
}
213225
}
214226

227+
lastUsagesOf0(field, at)
228+
}
229+
215230
val lastUsages: Map[Tree, Set[Int]] =
216-
liftables.map(fld => (fld -> lastUsagesOf(fld, finalState, Set()))).toMap
231+
liftables.map(fld => (fld -> lastUsagesOf(fld, finalState))).toMap
217232

218233
for ((fld, lastStates) <- lastUsages)
219234
AsyncUtils.vprintln(s"field ${fld.symbol.name} is last used in states ${lastStates.mkString(", ")}")

src/test/scala/scala/async/run/ifelse1/IfElse1.scala

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,75 @@ class TestIfElse1Class {
8787
}
8888
z
8989
}
90+
91+
def pred: Future[Boolean] = async(true)
92+
93+
def m5: Future[Boolean] = async {
94+
if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(await(pred))
95+
await(pred)
96+
else
97+
false)
98+
await(pred)
99+
else
100+
false)
101+
await(pred)
102+
else
103+
false)
104+
await(pred)
105+
else
106+
false)
107+
await(pred)
108+
else
109+
false)
110+
await(pred)
111+
else
112+
false)
113+
await(pred)
114+
else
115+
false)
116+
await(pred)
117+
else
118+
false)
119+
await(pred)
120+
else
121+
false)
122+
await(pred)
123+
else
124+
false)
125+
await(pred)
126+
else
127+
false)
128+
await(pred)
129+
else
130+
false)
131+
await(pred)
132+
else
133+
false)
134+
await(pred)
135+
else
136+
false)
137+
await(pred)
138+
else
139+
false)
140+
await(pred)
141+
else
142+
false)
143+
await(pred)
144+
else
145+
false)
146+
await(pred)
147+
else
148+
false)
149+
await(pred)
150+
else
151+
false)
152+
await(pred)
153+
else
154+
false)
155+
await(pred)
156+
else
157+
false
158+
}
90159
}
91160

92161
class IfElse1Spec {
@@ -124,4 +193,12 @@ class IfElse1Spec {
124193
val res = Await.result(fut, 2 seconds)
125194
res mustBe (14)
126195
}
196+
197+
@Test
198+
def `await in deeply-nested if-else conditions`() {
199+
val o = new TestIfElse1Class
200+
val fut = o.m5
201+
val res = Await.result(fut, 2 seconds)
202+
res mustBe true
203+
}
127204
}

0 commit comments

Comments
 (0)