Skip to content

Commit b662af6

Browse files
authored
Merge pull request #198 from retronym/topic/dot-dse
Performance improvements to the macro
2 parents 890c597 + 7fd08df commit b662af6

12 files changed

+414
-179
lines changed

src/main/scala/scala/async/internal/AnfTransform.scala

+14-11
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ private[async] trait AnfTransform {
7777
stats :+ expr :+ api.typecheck(atPos(expr.pos)(Throw(Apply(Select(New(gen.mkAttributedRef(defn.IllegalStateExceptionClass)), nme.CONSTRUCTOR), Nil))))
7878
expr match {
7979
case Apply(fun, args) if isAwait(fun) =>
80-
val valDef = defineVal(name.await, expr, tree.pos)
80+
val valDef = defineVal(name.await(), expr, tree.pos)
8181
val ref = gen.mkAttributedStableRef(valDef.symbol).setType(tree.tpe)
8282
val ref1 = if (ref.tpe =:= definitions.UnitTpe)
8383
// https://github.com/scala/async/issues/74
@@ -109,7 +109,7 @@ private[async] trait AnfTransform {
109109
} else if (expr.tpe =:= definitions.NothingTpe) {
110110
statsExprThrow
111111
} else {
112-
val varDef = defineVar(name.ifRes, expr.tpe, tree.pos)
112+
val varDef = defineVar(name.ifRes(), expr.tpe, tree.pos)
113113
def typedAssign(lhs: Tree) =
114114
api.typecheck(atPos(lhs.pos)(Assign(Ident(varDef.symbol), mkAttributedCastPreservingAnnotations(lhs, tpe(varDef.symbol)))))
115115

@@ -140,7 +140,7 @@ private[async] trait AnfTransform {
140140
} else if (expr.tpe =:= definitions.NothingTpe) {
141141
statsExprThrow
142142
} else {
143-
val varDef = defineVar(name.matchRes, expr.tpe, tree.pos)
143+
val varDef = defineVar(name.matchRes(), expr.tpe, tree.pos)
144144
def typedAssign(lhs: Tree) =
145145
api.typecheck(atPos(lhs.pos)(Assign(Ident(varDef.symbol), mkAttributedCastPreservingAnnotations(lhs, tpe(varDef.symbol)))))
146146
val casesWithAssign = cases map {
@@ -163,14 +163,14 @@ private[async] trait AnfTransform {
163163
}
164164
}
165165

166-
def defineVar(prefix: String, tp: Type, pos: Position): ValDef = {
167-
val sym = api.currentOwner.newTermSymbol(name.fresh(prefix), pos, MUTABLE | SYNTHETIC).setInfo(uncheckedBounds(tp))
166+
def defineVar(name: TermName, tp: Type, pos: Position): ValDef = {
167+
val sym = api.currentOwner.newTermSymbol(name, pos, MUTABLE | SYNTHETIC).setInfo(uncheckedBounds(tp))
168168
valDef(sym, mkZero(uncheckedBounds(tp))).setType(NoType).setPos(pos)
169169
}
170170
}
171171

172-
def defineVal(prefix: String, lhs: Tree, pos: Position): ValDef = {
173-
val sym = api.currentOwner.newTermSymbol(name.fresh(prefix), pos, SYNTHETIC).setInfo(uncheckedBounds(lhs.tpe))
172+
def defineVal(name: TermName, lhs: Tree, pos: Position): ValDef = {
173+
val sym = api.currentOwner.newTermSymbol(name, pos, SYNTHETIC).setInfo(uncheckedBounds(lhs.tpe))
174174
internal.valDef(sym, internal.changeOwner(lhs, api.currentOwner, sym)).setType(NoType).setPos(pos)
175175
}
176176

@@ -212,7 +212,7 @@ private[async] trait AnfTransform {
212212
case Arg(expr, _, argName) =>
213213
linearize.transformToList(expr) match {
214214
case stats :+ expr1 =>
215-
val valDef = defineVal(argName, expr1, expr1.pos)
215+
val valDef = defineVal(name.freshen(argName), expr1, expr1.pos)
216216
require(valDef.tpe != null, valDef)
217217
val stats1 = stats :+ valDef
218218
(stats1, atPos(tree.pos.makeTransparent)(gen.stabilize(gen.mkAttributedIdent(valDef.symbol))))
@@ -279,8 +279,9 @@ private[async] trait AnfTransform {
279279
// TODO we can move this into ExprBuilder once we get rid of `AsyncDefinitionUseAnalyzer`.
280280
val block = linearize.transformToBlock(body)
281281
val (valDefs, mappings) = (pat collect {
282-
case b@Bind(name, _) =>
283-
val vd = defineVal(name.toTermName + AnfTransform.this.name.bindSuffix, gen.mkAttributedStableRef(b.symbol).setPos(b.pos), b.pos)
282+
case b@Bind(bindName, _) =>
283+
val vd = defineVal(name.freshen(bindName.toTermName), gen.mkAttributedStableRef(b.symbol).setPos(b.pos), b.pos)
284+
vd.symbol.updateAttachment(SyntheticBindVal)
284285
(vd, (b.symbol, vd.symbol))
285286
}).unzip
286287
val (from, to) = mappings.unzip
@@ -333,7 +334,7 @@ private[async] trait AnfTransform {
333334
// Otherwise, create the matchres var. We'll callers of the label def below.
334335
// Remember: we're iterating through the statement sequence in reverse, so we'll get
335336
// to the LabelDef and mutate `matchResults` before we'll get to its callers.
336-
val matchResult = linearize.defineVar(name.matchRes, param.tpe, ld.pos)
337+
val matchResult = linearize.defineVar(name.matchRes(), param.tpe, ld.pos)
337338
matchResults += matchResult
338339
caseDefToMatchResult(ld.symbol) = matchResult.symbol
339340
val rhs2 = ld.rhs.substituteSymbols(param.symbol :: Nil, matchResult.symbol :: Nil)
@@ -408,3 +409,5 @@ private[async] trait AnfTransform {
408409
}).asInstanceOf[Block]
409410
}
410411
}
412+
413+
object SyntheticBindVal

src/main/scala/scala/async/internal/AsyncMacro.scala

+11
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,18 @@ package scala.async.internal
33
object AsyncMacro {
44
def apply(c0: reflect.macros.Context, base: AsyncBase)(body0: c0.Tree): AsyncMacro { val c: c0.type } = {
55
import language.reflectiveCalls
6+
7+
// Use an attachment on RootClass as a sneaky place for a per-Global cache
8+
val att = c0.internal.attachments(c0.universe.rootMirror.RootClass)
9+
val names = att.get[AsyncNames[_]].getOrElse {
10+
val names = new AsyncNames[c0.universe.type](c0.universe)
11+
att.update(names)
12+
names
13+
}
14+
615
new AsyncMacro { self =>
716
val c: c0.type = c0
17+
val asyncNames: AsyncNames[c.universe.type] = names.asInstanceOf[AsyncNames[c.universe.type]]
818
val body: c.Tree = body0
919
// This member is required by `AsyncTransform`:
1020
val asyncBase: AsyncBase = base
@@ -23,6 +33,7 @@ private[async] trait AsyncMacro
2333
val c: scala.reflect.macros.Context
2434
val body: c.Tree
2535
var containsAwait: c.Tree => Boolean
36+
val asyncNames: AsyncNames[c.universe.type]
2637

2738
lazy val macroPos: c.universe.Position = c.macroApplication.pos.makeTransparent
2839
def atMacroPos(t: c.Tree): c.Tree = c.universe.atPos(macroPos)(t)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package scala.async.internal
2+
3+
import java.util.concurrent.atomic.AtomicInteger
4+
5+
import scala.collection.mutable
6+
import scala.collection.mutable.ArrayBuffer
7+
import scala.reflect.api.Names
8+
9+
/**
10+
* A per-global cache of names needed by the Async macro.
11+
*/
12+
final class AsyncNames[U <: Names with Singleton](val u: U) {
13+
self =>
14+
import u._
15+
16+
abstract class NameCache[N <: U#Name](base: String) {
17+
val cached = new ArrayBuffer[N]()
18+
protected def newName(s: String): N
19+
def apply(i: Int): N = {
20+
if (cached.isDefinedAt(i)) cached(i)
21+
else {
22+
assert(cached.length == i)
23+
val name = newName(freshenString(base, i))
24+
cached += name
25+
name
26+
}
27+
}
28+
}
29+
30+
final class TermNameCache(base: String) extends NameCache[U#TermName](base) {
31+
override protected def newName(s: String): U#TermName = newTermName(s)
32+
}
33+
final class TypeNameCache(base: String) extends NameCache[U#TypeName](base) {
34+
override protected def newName(s: String): U#TypeName = newTypeName(s)
35+
}
36+
private val matchRes: TermNameCache = new TermNameCache("match")
37+
private val ifRes: TermNameCache = new TermNameCache("if")
38+
private val await: TermNameCache = new TermNameCache("await")
39+
40+
private val result = newTermName("result$async")
41+
private val completed: TermName = newTermName("completed$async")
42+
private val apply = newTermName("apply")
43+
private val stateMachine = newTermName("stateMachine$async")
44+
private val stateMachineT = stateMachine.toTypeName
45+
private val state: u.TermName = newTermName("state$async")
46+
private val execContext = newTermName("execContext$async")
47+
private val tr: u.TermName = newTermName("tr$async")
48+
private val t: u.TermName = newTermName("throwable$async")
49+
50+
final class NameSource[N <: U#Name](cache: NameCache[N]) {
51+
private val count = new AtomicInteger(0)
52+
def apply(): N = cache(count.getAndIncrement())
53+
}
54+
55+
class AsyncName {
56+
final val matchRes = new NameSource[U#TermName](self.matchRes)
57+
final val ifRes = new NameSource[U#TermName](self.matchRes)
58+
final val await = new NameSource[U#TermName](self.await)
59+
final val completed = self.completed
60+
final val result = self.result
61+
final val apply = self.apply
62+
final val stateMachine = self.stateMachine
63+
final val stateMachineT = self.stateMachineT
64+
final val state: u.TermName = self.state
65+
final val execContext = self.execContext
66+
final val tr: u.TermName = self.tr
67+
final val t: u.TermName = self.t
68+
69+
private val seenPrefixes = mutable.AnyRefMap[Name, AtomicInteger]()
70+
private val freshened = mutable.HashSet[Name]()
71+
72+
final def freshenIfNeeded(name: TermName): TermName = {
73+
seenPrefixes.getOrNull(name) match {
74+
case null =>
75+
seenPrefixes.put(name, new AtomicInteger())
76+
name
77+
case counter =>
78+
freshen(name, counter)
79+
}
80+
}
81+
final def freshenIfNeeded(name: TypeName): TypeName = {
82+
seenPrefixes.getOrNull(name) match {
83+
case null =>
84+
seenPrefixes.put(name, new AtomicInteger())
85+
name
86+
case counter =>
87+
freshen(name, counter)
88+
}
89+
}
90+
final def freshen(name: TermName): TermName = {
91+
val counter = seenPrefixes.getOrElseUpdate(name, new AtomicInteger())
92+
freshen(name, counter)
93+
}
94+
final def freshen(name: TypeName): TypeName = {
95+
val counter = seenPrefixes.getOrElseUpdate(name, new AtomicInteger())
96+
freshen(name, counter)
97+
}
98+
private def freshen(name: TermName, counter: AtomicInteger): TermName = {
99+
if (freshened.contains(name)) name
100+
else TermName(freshenString(name.toString, counter.incrementAndGet()))
101+
}
102+
private def freshen(name: TypeName, counter: AtomicInteger): TypeName = {
103+
if (freshened.contains(name)) name
104+
else TypeName(freshenString(name.toString, counter.incrementAndGet()))
105+
}
106+
}
107+
108+
private def freshenString(name: String, counter: Int): String = name.toString + "$async$" + counter
109+
}

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

+8-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,15 @@ 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+
}
118+
futureSystemOps.dot(enclosingOwner, body).foreach(f => f(asyncBlock.toDot))
117119
cleanupContainsAwaitAttachments(result)
118120
}
119121

120-
def logDiagnostics(anfTree: Tree, states: Seq[String]): Unit = {
122+
def logDiagnostics(anfTree: Tree, block: AsyncBlock, states: Seq[String]): Unit = {
121123
def location = try {
122124
macroPos.source.path
123125
} catch {
@@ -129,6 +131,8 @@ trait AsyncTransform {
129131
AsyncUtils.vprintln(s"${c.macroApplication}")
130132
AsyncUtils.vprintln(s"ANF transform expands to:\n $anfTree")
131133
states foreach (s => AsyncUtils.vprintln(s))
134+
AsyncUtils.vprintln("===== DOT =====")
135+
AsyncUtils.vprintln(block.toDot)
132136
}
133137

134138
/**

0 commit comments

Comments
 (0)