Skip to content

Commit 920fa58

Browse files
committed
Further optimize name freshening
- Introduce a per-Global store of names - Avoid double freshening names - Use $async$ in all names to avoid clashes with, e.g. lambda lifted methods.
1 parent e95be2c commit 920fa58

File tree

9 files changed

+151
-58
lines changed

9 files changed

+151
-58
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: TermName, 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: TermName, 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

+10
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
package scala.async.internal
22

3+
import java.util
4+
35
object AsyncMacro {
6+
private val nameCache = new util.WeakHashMap[Object, AsyncNames[_]]()
47
def apply(c0: reflect.macros.Context, base: AsyncBase)(body0: c0.Tree): AsyncMacro { val c: c0.type } = {
58
import language.reflectiveCalls
9+
val asyncNames0 = nameCache.synchronized[AsyncNames[_]] {
10+
nameCache.computeIfAbsent(c0.universe, new java.util.function.Function[Object, AsyncNames[_]] {
11+
override def apply(t: Object): AsyncNames[_] = new AsyncNames[c0.universe.type](c0.universe)
12+
})
13+
}
614
new AsyncMacro { self =>
715
val c: c0.type = c0
16+
val asyncNames: AsyncNames[c.universe.type] = asyncNames0.asInstanceOf[AsyncNames[c.universe.type]]
817
val body: c.Tree = body0
918
// This member is required by `AsyncTransform`:
1019
val asyncBase: AsyncBase = base
@@ -23,6 +32,7 @@ private[async] trait AsyncMacro
2332
val c: scala.reflect.macros.Context
2433
val body: c.Tree
2534
var containsAwait: c.Tree => Boolean
35+
val asyncNames: AsyncNames[c.universe.type]
2636

2737
lazy val macroPos: c.universe.Position = c.macroApplication.pos.makeTransparent
2838
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 resume = newTermName("resume")
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.resume
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/ExprBuilder.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ trait ExprBuilder {
9595
c.Expr[futureSystem.Tryy[Any] => Unit](fun), c.Expr[futureSystem.ExecContext](Ident(name.execContext))).tree
9696
val tryGetOrCallOnComplete: List[Tree] =
9797
if (futureSystemOps.continueCompletedFutureOnSameThread) {
98-
val tempName = name.fresh(name.completed)
98+
val tempName = name.completed
9999
val initTemp = ValDef(NoMods, tempName, TypeTree(futureSystemOps.tryType[Any]), futureSystemOps.getCompleted[Any](c.Expr[futureSystem.Fut[Any]](awaitable.expr)).tree)
100100
val ifTree = If(Apply(Select(Literal(Constant(null)), TermName("ne")), Ident(tempName) :: Nil),
101101
adaptToUnit(ifIsFailureTree[T](Ident(tempName)) :: Nil),
@@ -520,7 +520,7 @@ trait ExprBuilder {
520520
}
521521

522522
private def isSyntheticBindVal(tree: Tree) = tree match {
523-
case vd@ValDef(_, lname, _, Ident(rname)) => lname.toString.contains(name.bindSuffix)
523+
case vd@ValDef(_, lname, _, Ident(rname)) => attachments(vd.symbol).contains[SyntheticBindVal.type]
524524
case _ => false
525525
}
526526

src/main/scala/scala/async/internal/FutureSystem.scala

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ trait FutureSystem {
7575

7676
def mkOps(c0: Context): Ops { val c: c0.type }
7777

78+
@deprecated("No longer honoured by the macro, all generated names now contain $async to avoid accidental clashes with lambda lifted names", "0.9.7")
7879
def freshenAllNames: Boolean = false
7980
def emitTryCatch: Boolean = true
8081
def resultFieldName: String = "result"

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,13 @@ trait Lifter {
120120
val rhs1 = if (sym.asTerm.isLazy) rhs else EmptyTree
121121
treeCopy.ValDef(vd, Modifiers(sym.flags), sym.name, TypeTree(tpe(sym)).setPos(t.pos), rhs1)
122122
case dd@DefDef(_, _, tparams, vparamss, tpt, rhs) =>
123-
sym.setName(this.name.fresh(sym.name.toTermName))
123+
sym.setName(this.name.freshen(sym.name.toTermName))
124124
sym.setFlag(PRIVATE | LOCAL)
125125
// Was `DefDef(sym, rhs)`, but this ran afoul of `ToughTypeSpec.nestedMethodWithInconsistencyTreeAndInfoParamSymbols`
126126
// due to the handling of type parameter skolems in `thisMethodType` in `Namers`
127127
treeCopy.DefDef(dd, Modifiers(sym.flags), sym.name, tparams, vparamss, tpt, rhs)
128128
case cd@ClassDef(_, _, tparams, impl) =>
129-
sym.setName(newTypeName(name.fresh(sym.name.toString).toString))
129+
sym.setName(name.freshen(sym.name.toTypeName))
130130
companionship.companionOf(cd.symbol) match {
131131
case NoSymbol =>
132132
case moduleSymbol =>
@@ -137,13 +137,13 @@ trait Lifter {
137137
case md@ModuleDef(_, _, impl) =>
138138
companionship.companionOf(md.symbol) match {
139139
case NoSymbol =>
140-
sym.setName(name.fresh(sym.name.toTermName))
140+
sym.setName(name.freshen(sym.name.toTermName))
141141
sym.asModule.moduleClass.setName(sym.name.toTypeName)
142142
case classSymbol => // will be renamed by `case ClassDef` above.
143143
}
144144
treeCopy.ModuleDef(md, Modifiers(sym.flags), sym.name, impl)
145145
case td@TypeDef(_, _, tparams, rhs) =>
146-
sym.setName(newTypeName(name.fresh(sym.name.toString).toString))
146+
sym.setName(name.freshen(sym.name.toTypeName))
147147
treeCopy.TypeDef(td, Modifiers(sym.flags), sym.name, tparams, rhs)
148148
}
149149
atPos(t.pos)(treeLifted)

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

+2-36
Original file line numberDiff line numberDiff line change
@@ -17,42 +17,8 @@ private[async] trait TransformUtils {
1717
import c.internal._
1818
import decorators._
1919

20-
private object baseNames {
21-
22-
val matchRes = "matchres"
23-
val ifRes = "ifres"
24-
val bindSuffix = "$bind"
25-
val completed = newTermName("completed")
26-
27-
val state = newTermName("state")
28-
val result = newTermName(self.futureSystem.resultFieldName)
29-
val execContext = newTermName("execContext")
30-
val tr = newTermName("tr")
31-
val t = newTermName("throwable")
32-
}
33-
34-
object name {
35-
val matchRes = newTermName(baseNames.matchRes)
36-
val ifRes = newTermName(baseNames.ifRes)
37-
def bindSuffix = baseNames.bindSuffix
38-
def completed = baseNames.completed
39-
40-
val state = maybeFresh(baseNames.state)
41-
val result = baseNames.result
42-
val execContext = maybeFresh(baseNames.execContext)
43-
val tr = maybeFresh(baseNames.tr)
44-
val t = maybeFresh(baseNames.t)
45-
46-
val await = newTermName("await")
47-
val resume = newTermName("resume")
48-
val apply = newTermName("apply")
49-
val stateMachine = newTermName(fresh("stateMachine"))
50-
val stateMachineT = stateMachine.toTypeName
51-
52-
def maybeFresh(name: TermName): TermName = if (self.asyncBase.futureSystem.freshenAllNames) fresh(name) else name
53-
def maybeFresh(name: String): String = if (self.asyncBase.futureSystem.freshenAllNames) fresh(name) else name
54-
def fresh(name: TermName): TermName = c.freshName(name)
55-
20+
object name extends asyncNames.AsyncName {
21+
def fresh(name: TermName): TermName = freshenIfNeeded(name)
5622
def fresh(name: String): String = c.freshName(name)
5723
}
5824

src/test/scala/scala/async/TreeInterrogation.scala

+7-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class TreeInterrogation {
3838
val varDefs = tree1.collect {
3939
case vd @ ValDef(mods, name, _, _) if mods.hasFlag(Flag.MUTABLE) && vd.symbol.owner.isClass => name
4040
}
41-
varDefs.map(_.decoded.trim).toSet.toList.sorted mustStartWith (List("await$macro$", "await$macro$", "state"))
41+
varDefs.map(_.decoded.trim).toSet.toList.sorted mustStartWith (List("await$async$", "await$async", "state$async"))
4242

4343
val defDefs = tree1.collect {
4444
case t: Template =>
@@ -49,11 +49,11 @@ class TreeInterrogation {
4949
&& !dd.symbol.asTerm.isAccessor && !dd.symbol.asTerm.isSetter => dd.name
5050
}
5151
}.flatten
52-
defDefs.map(_.decoded.trim) mustStartWith List("foo$macro$", "<init>", "apply", "apply")
52+
defDefs.map(_.decoded.trim) mustStartWith List("foo$async$", "<init>", "apply", "apply")
5353
}
5454
}
5555

56-
object TreeInterrogation extends App {
56+
object TreeInterrogationApp extends App {
5757
def withDebug[T](t: => T): T = {
5858
def set(level: String, value: Boolean) = System.setProperty(s"scala.async.$level", value.toString)
5959
val levels = Seq("trace", "debug")
@@ -65,7 +65,7 @@ object TreeInterrogation extends App {
6565

6666
withDebug {
6767
val cm = reflect.runtime.currentMirror
68-
val tb = mkToolbox(s"-cp ${toolboxClasspath} -Xprint:typer -uniqid")
68+
val tb = mkToolbox(s"-cp ${toolboxClasspath} -Xprint:typer")
6969
import scala.async.internal.AsyncId._
7070
val tree = tb.parse(
7171
"""
@@ -75,6 +75,9 @@ object TreeInterrogation extends App {
7575
| while(await(b)) {
7676
| b = false
7777
| }
78+
| (1, 1) match {
79+
| case (x, y) => await(2); println(x)
80+
| }
7881
| await(b)
7982
| }
8083
|

src/test/scala/scala/async/run/anf/AnfTransformSpec.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ class AnfTransformSpec {
403403
""".stripMargin
404404
})
405405
val applyImplicitView = tree.collect { case x if x.getClass.getName.endsWith("ApplyImplicitView") => x }
406-
applyImplicitView.map(_.toString) mustStartWith List("view(a$macro$")
406+
println(applyImplicitView)
407+
applyImplicitView.map(_.toString) mustStartWith List("view(")
407408
}
408409

409410
@Test

0 commit comments

Comments
 (0)