diff --git a/src/main/scala/scala/async/internal/AnfTransform.scala b/src/main/scala/scala/async/internal/AnfTransform.scala index bb63d565..fa230999 100644 --- a/src/main/scala/scala/async/internal/AnfTransform.scala +++ b/src/main/scala/scala/async/internal/AnfTransform.scala @@ -77,7 +77,7 @@ private[async] trait AnfTransform { stats :+ expr :+ api.typecheck(atPos(expr.pos)(Throw(Apply(Select(New(gen.mkAttributedRef(defn.IllegalStateExceptionClass)), nme.CONSTRUCTOR), Nil)))) expr match { case Apply(fun, args) if isAwait(fun) => - val valDef = defineVal(name.await, expr, tree.pos) + val valDef = defineVal(name.await(), expr, tree.pos) val ref = gen.mkAttributedStableRef(valDef.symbol).setType(tree.tpe) val ref1 = if (ref.tpe =:= definitions.UnitTpe) // https://github.com/scala/async/issues/74 @@ -109,7 +109,7 @@ private[async] trait AnfTransform { } else if (expr.tpe =:= definitions.NothingTpe) { statsExprThrow } else { - val varDef = defineVar(name.ifRes, expr.tpe, tree.pos) + val varDef = defineVar(name.ifRes(), expr.tpe, tree.pos) def typedAssign(lhs: Tree) = api.typecheck(atPos(lhs.pos)(Assign(Ident(varDef.symbol), mkAttributedCastPreservingAnnotations(lhs, tpe(varDef.symbol))))) @@ -140,7 +140,7 @@ private[async] trait AnfTransform { } else if (expr.tpe =:= definitions.NothingTpe) { statsExprThrow } else { - val varDef = defineVar(name.matchRes, expr.tpe, tree.pos) + val varDef = defineVar(name.matchRes(), expr.tpe, tree.pos) def typedAssign(lhs: Tree) = api.typecheck(atPos(lhs.pos)(Assign(Ident(varDef.symbol), mkAttributedCastPreservingAnnotations(lhs, tpe(varDef.symbol))))) val casesWithAssign = cases map { @@ -163,14 +163,14 @@ private[async] trait AnfTransform { } } - def defineVar(prefix: String, tp: Type, pos: Position): ValDef = { - val sym = api.currentOwner.newTermSymbol(name.fresh(prefix), pos, MUTABLE | SYNTHETIC).setInfo(uncheckedBounds(tp)) + def defineVar(name: TermName, tp: Type, pos: Position): ValDef = { + val sym = api.currentOwner.newTermSymbol(name, pos, MUTABLE | SYNTHETIC).setInfo(uncheckedBounds(tp)) valDef(sym, mkZero(uncheckedBounds(tp))).setType(NoType).setPos(pos) } } - def defineVal(prefix: String, lhs: Tree, pos: Position): ValDef = { - val sym = api.currentOwner.newTermSymbol(name.fresh(prefix), pos, SYNTHETIC).setInfo(uncheckedBounds(lhs.tpe)) + def defineVal(name: TermName, lhs: Tree, pos: Position): ValDef = { + val sym = api.currentOwner.newTermSymbol(name, pos, SYNTHETIC).setInfo(uncheckedBounds(lhs.tpe)) internal.valDef(sym, internal.changeOwner(lhs, api.currentOwner, sym)).setType(NoType).setPos(pos) } @@ -212,7 +212,7 @@ private[async] trait AnfTransform { case Arg(expr, _, argName) => linearize.transformToList(expr) match { case stats :+ expr1 => - val valDef = defineVal(argName, expr1, expr1.pos) + val valDef = defineVal(name.freshen(argName), expr1, expr1.pos) require(valDef.tpe != null, valDef) val stats1 = stats :+ valDef (stats1, atPos(tree.pos.makeTransparent)(gen.stabilize(gen.mkAttributedIdent(valDef.symbol)))) @@ -279,8 +279,9 @@ private[async] trait AnfTransform { // TODO we can move this into ExprBuilder once we get rid of `AsyncDefinitionUseAnalyzer`. val block = linearize.transformToBlock(body) val (valDefs, mappings) = (pat collect { - case b@Bind(name, _) => - val vd = defineVal(name.toTermName + AnfTransform.this.name.bindSuffix, gen.mkAttributedStableRef(b.symbol).setPos(b.pos), b.pos) + case b@Bind(bindName, _) => + val vd = defineVal(name.freshen(bindName.toTermName), gen.mkAttributedStableRef(b.symbol).setPos(b.pos), b.pos) + vd.symbol.updateAttachment(SyntheticBindVal) (vd, (b.symbol, vd.symbol)) }).unzip val (from, to) = mappings.unzip @@ -333,7 +334,7 @@ private[async] trait AnfTransform { // Otherwise, create the matchres var. We'll callers of the label def below. // Remember: we're iterating through the statement sequence in reverse, so we'll get // to the LabelDef and mutate `matchResults` before we'll get to its callers. - val matchResult = linearize.defineVar(name.matchRes, param.tpe, ld.pos) + val matchResult = linearize.defineVar(name.matchRes(), param.tpe, ld.pos) matchResults += matchResult caseDefToMatchResult(ld.symbol) = matchResult.symbol val rhs2 = ld.rhs.substituteSymbols(param.symbol :: Nil, matchResult.symbol :: Nil) @@ -408,3 +409,5 @@ private[async] trait AnfTransform { }).asInstanceOf[Block] } } + +object SyntheticBindVal diff --git a/src/main/scala/scala/async/internal/AsyncMacro.scala b/src/main/scala/scala/async/internal/AsyncMacro.scala index 113e7a8f..65c43d00 100644 --- a/src/main/scala/scala/async/internal/AsyncMacro.scala +++ b/src/main/scala/scala/async/internal/AsyncMacro.scala @@ -1,10 +1,19 @@ package scala.async.internal +import java.util + object AsyncMacro { + private val nameCache = new util.WeakHashMap[Object, AsyncNames[_]]() def apply(c0: reflect.macros.Context, base: AsyncBase)(body0: c0.Tree): AsyncMacro { val c: c0.type } = { import language.reflectiveCalls + val asyncNames0 = nameCache.synchronized[AsyncNames[_]] { + nameCache.computeIfAbsent(c0.universe, new java.util.function.Function[Object, AsyncNames[_]] { + override def apply(t: Object): AsyncNames[_] = new AsyncNames[c0.universe.type](c0.universe) + }) + } new AsyncMacro { self => val c: c0.type = c0 + val asyncNames: AsyncNames[c.universe.type] = asyncNames0.asInstanceOf[AsyncNames[c.universe.type]] val body: c.Tree = body0 // This member is required by `AsyncTransform`: val asyncBase: AsyncBase = base @@ -23,6 +32,7 @@ private[async] trait AsyncMacro val c: scala.reflect.macros.Context val body: c.Tree var containsAwait: c.Tree => Boolean + val asyncNames: AsyncNames[c.universe.type] lazy val macroPos: c.universe.Position = c.macroApplication.pos.makeTransparent def atMacroPos(t: c.Tree): c.Tree = c.universe.atPos(macroPos)(t) diff --git a/src/main/scala/scala/async/internal/AsyncNames.scala b/src/main/scala/scala/async/internal/AsyncNames.scala new file mode 100644 index 00000000..f0e4f9b8 --- /dev/null +++ b/src/main/scala/scala/async/internal/AsyncNames.scala @@ -0,0 +1,109 @@ +package scala.async.internal + +import java.util.concurrent.atomic.AtomicInteger + +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer +import scala.reflect.api.Names + +/** + * A per-global cache of names needed by the Async macro. + */ +final class AsyncNames[U <: Names with Singleton](val u: U) { + self => + import u._ + + abstract class NameCache[N <: U#Name](base: String) { + val cached = new ArrayBuffer[N]() + protected def newName(s: String): N + def apply(i: Int): N = { + if (cached.isDefinedAt(i)) cached(i) + else { + assert(cached.length == i) + val name = newName(freshenString(base, i)) + cached += name + name + } + } + } + + final class TermNameCache(base: String) extends NameCache[U#TermName](base) { + override protected def newName(s: String): U#TermName = newTermName(s) + } + final class TypeNameCache(base: String) extends NameCache[U#TypeName](base) { + override protected def newName(s: String): U#TypeName = newTypeName(s) + } + private val matchRes: TermNameCache = new TermNameCache("match") + private val ifRes: TermNameCache = new TermNameCache("if") + private val await: TermNameCache = new TermNameCache("await") + + private val resume = newTermName("resume") + private val completed: TermName = newTermName("completed$async") + private val apply = newTermName("apply") + private val stateMachine = newTermName("stateMachine$async") + private val stateMachineT = stateMachine.toTypeName + private val state: u.TermName = newTermName("state$async") + private val execContext = newTermName("execContext$async") + private val tr: u.TermName = newTermName("tr$async") + private val t: u.TermName = newTermName("throwable$async") + + final class NameSource[N <: U#Name](cache: NameCache[N]) { + private val count = new AtomicInteger(0) + def apply(): N = cache(count.getAndIncrement()) + } + + class AsyncName { + final val matchRes = new NameSource[U#TermName](self.matchRes) + final val ifRes = new NameSource[U#TermName](self.matchRes) + final val await = new NameSource[U#TermName](self.await) + final val completed = self.completed + final val result = self.resume + final val apply = self.apply + final val stateMachine = self.stateMachine + final val stateMachineT = self.stateMachineT + final val state: u.TermName = self.state + final val execContext = self.execContext + final val tr: u.TermName = self.tr + final val t: u.TermName = self.t + + private val seenPrefixes = mutable.AnyRefMap[Name, AtomicInteger]() + private val freshened = mutable.HashSet[Name]() + + final def freshenIfNeeded(name: TermName): TermName = { + seenPrefixes.getOrNull(name) match { + case null => + seenPrefixes.put(name, new AtomicInteger()) + name + case counter => + freshen(name, counter) + } + } + final def freshenIfNeeded(name: TypeName): TypeName = { + seenPrefixes.getOrNull(name) match { + case null => + seenPrefixes.put(name, new AtomicInteger()) + name + case counter => + freshen(name, counter) + } + } + final def freshen(name: TermName): TermName = { + val counter = seenPrefixes.getOrElseUpdate(name, new AtomicInteger()) + freshen(name, counter) + } + final def freshen(name: TypeName): TypeName = { + val counter = seenPrefixes.getOrElseUpdate(name, new AtomicInteger()) + freshen(name, counter) + } + private def freshen(name: TermName, counter: AtomicInteger): TermName = { + if (freshened.contains(name)) name + else TermName(freshenString(name.toString, counter.incrementAndGet())) + } + private def freshen(name: TypeName, counter: AtomicInteger): TypeName = { + if (freshened.contains(name)) name + else TypeName(freshenString(name.toString, counter.incrementAndGet())) + } + } + + private def freshenString(name: String, counter: Int): String = name.toString + "$async$" + counter +} diff --git a/src/main/scala/scala/async/internal/ExprBuilder.scala b/src/main/scala/scala/async/internal/ExprBuilder.scala index e1ab6c86..6ca1dd5a 100644 --- a/src/main/scala/scala/async/internal/ExprBuilder.scala +++ b/src/main/scala/scala/async/internal/ExprBuilder.scala @@ -3,6 +3,8 @@ */ package scala.async.internal +import java.util.function.IntUnaryOperator + import scala.collection.mutable import scala.collection.mutable.ListBuffer import language.existentials @@ -23,7 +25,7 @@ trait ExprBuilder { trait AsyncState { def state: Int - def nextStates: List[Int] + def nextStates: Array[Int] def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef @@ -55,8 +57,8 @@ trait ExprBuilder { final class SimpleAsyncState(var stats: List[Tree], val state: Int, nextState: Int, symLookup: SymLookup) extends AsyncState { - def nextStates: List[Int] = - List(nextState) + val nextStates: Array[Int] = + Array(nextState) def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = { mkHandlerCase(state, treesThenStats(mkStateTree(nextState, symLookup) :: Nil)) @@ -69,7 +71,7 @@ trait ExprBuilder { /** A sequence of statements with a conditional transition to the next state, which will represent * a branch of an `if` or a `match`. */ - final class AsyncStateWithoutAwait(var stats: List[Tree], val state: Int, val nextStates: List[Int]) extends AsyncState { + final class AsyncStateWithoutAwait(var stats: List[Tree], val state: Int, val nextStates: Array[Int]) extends AsyncState { override def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = mkHandlerCase(state, stats) @@ -84,8 +86,8 @@ trait ExprBuilder { val awaitable: Awaitable, symLookup: SymLookup) extends AsyncState { - def nextStates: List[Int] = - List(nextState) + val nextStates: Array[Int] = + Array(nextState) override def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = { val fun = This(tpnme.EMPTY) @@ -93,7 +95,7 @@ trait ExprBuilder { c.Expr[futureSystem.Tryy[Any] => Unit](fun), c.Expr[futureSystem.ExecContext](Ident(name.execContext))).tree val tryGetOrCallOnComplete: List[Tree] = if (futureSystemOps.continueCompletedFutureOnSameThread) { - val tempName = name.fresh(name.completed) + val tempName = name.completed val initTemp = ValDef(NoMods, tempName, TypeTree(futureSystemOps.tryType[Any]), futureSystemOps.getCompleted[Any](c.Expr[futureSystem.Fut[Any]](awaitable.expr)).tree) val ifTree = If(Apply(Select(Literal(Constant(null)), TermName("ne")), Ident(tempName) :: Nil), adaptToUnit(ifIsFailureTree[T](Ident(tempName)) :: Nil), @@ -191,7 +193,7 @@ trait ExprBuilder { def resultWithIf(condTree: Tree, thenState: Int, elseState: Int): AsyncState = { def mkBranch(state: Int) = mkStateTree(state, symLookup) this += If(condTree, mkBranch(thenState), mkBranch(elseState)) - new AsyncStateWithoutAwait(stats.toList, state, List(thenState, elseState)) + new AsyncStateWithoutAwait(stats.toList, state, Array(thenState, elseState)) } /** @@ -204,7 +206,7 @@ trait ExprBuilder { * @param caseStates starting state of the right-hand side of the each case * @return an `AsyncState` representing the match expression */ - def resultWithMatch(scrutTree: Tree, cases: List[CaseDef], caseStates: List[Int], symLookup: SymLookup): AsyncState = { + def resultWithMatch(scrutTree: Tree, cases: List[CaseDef], caseStates: Array[Int], symLookup: SymLookup): AsyncState = { // 1. build list of changed cases val newCases = for ((cas, num) <- cases.zipWithIndex) yield cas match { case CaseDef(pat, guard, rhs) => @@ -218,7 +220,7 @@ trait ExprBuilder { def resultWithLabel(startLabelState: Int, symLookup: SymLookup): AsyncState = { this += mkStateTree(startLabelState, symLookup) - new AsyncStateWithoutAwait(stats.toList, state, List(startLabelState)) + new AsyncStateWithoutAwait(stats.toList, state, Array(startLabelState)) } override def toString: String = { @@ -299,7 +301,10 @@ trait ExprBuilder { case Match(scrutinee, cases) if containsAwait(stat) => checkForUnsupportedAwait(scrutinee) - val caseStates = cases.map(_ => nextState()) + val caseStates = new Array[Int](cases.length) + java.util.Arrays.setAll(caseStates, new IntUnaryOperator { + override def applyAsInt(operand: Int): Int = nextState() + }) val afterMatchState = nextState() asyncStates += @@ -515,7 +520,7 @@ trait ExprBuilder { } private def isSyntheticBindVal(tree: Tree) = tree match { - case vd@ValDef(_, lname, _, Ident(rname)) => lname.toString.contains(name.bindSuffix) + case vd@ValDef(_, lname, _, Ident(rname)) => attachments(vd.symbol).contains[SyntheticBindVal.type] case _ => false } diff --git a/src/main/scala/scala/async/internal/FutureSystem.scala b/src/main/scala/scala/async/internal/FutureSystem.scala index 3ca9c834..52f10a4f 100644 --- a/src/main/scala/scala/async/internal/FutureSystem.scala +++ b/src/main/scala/scala/async/internal/FutureSystem.scala @@ -75,6 +75,7 @@ trait FutureSystem { def mkOps(c0: Context): Ops { val c: c0.type } + @deprecated("No longer honoured by the macro, all generated names now contain $async to avoid accidental clashes with lambda lifted names", "0.9.7") def freshenAllNames: Boolean = false def emitTryCatch: Boolean = true def resultFieldName: String = "result" diff --git a/src/main/scala/scala/async/internal/Lifter.scala b/src/main/scala/scala/async/internal/Lifter.scala index 3afe6d6a..495ba44e 100644 --- a/src/main/scala/scala/async/internal/Lifter.scala +++ b/src/main/scala/scala/async/internal/Lifter.scala @@ -120,13 +120,13 @@ trait Lifter { val rhs1 = if (sym.asTerm.isLazy) rhs else EmptyTree treeCopy.ValDef(vd, Modifiers(sym.flags), sym.name, TypeTree(tpe(sym)).setPos(t.pos), rhs1) case dd@DefDef(_, _, tparams, vparamss, tpt, rhs) => - sym.setName(this.name.fresh(sym.name.toTermName)) + sym.setName(this.name.freshen(sym.name.toTermName)) sym.setFlag(PRIVATE | LOCAL) // Was `DefDef(sym, rhs)`, but this ran afoul of `ToughTypeSpec.nestedMethodWithInconsistencyTreeAndInfoParamSymbols` // due to the handling of type parameter skolems in `thisMethodType` in `Namers` treeCopy.DefDef(dd, Modifiers(sym.flags), sym.name, tparams, vparamss, tpt, rhs) case cd@ClassDef(_, _, tparams, impl) => - sym.setName(newTypeName(name.fresh(sym.name.toString).toString)) + sym.setName(name.freshen(sym.name.toTypeName)) companionship.companionOf(cd.symbol) match { case NoSymbol => case moduleSymbol => @@ -137,13 +137,13 @@ trait Lifter { case md@ModuleDef(_, _, impl) => companionship.companionOf(md.symbol) match { case NoSymbol => - sym.setName(name.fresh(sym.name.toTermName)) + sym.setName(name.freshen(sym.name.toTermName)) sym.asModule.moduleClass.setName(sym.name.toTypeName) case classSymbol => // will be renamed by `case ClassDef` above. } treeCopy.ModuleDef(md, Modifiers(sym.flags), sym.name, impl) case td@TypeDef(_, _, tparams, rhs) => - sym.setName(newTypeName(name.fresh(sym.name.toString).toString)) + sym.setName(name.freshen(sym.name.toTypeName)) treeCopy.TypeDef(td, Modifiers(sym.flags), sym.name, tparams, rhs) } atPos(t.pos)(treeLifted) diff --git a/src/main/scala/scala/async/internal/LiveVariables.scala b/src/main/scala/scala/async/internal/LiveVariables.scala index 8ae00f56..72b752ea 100644 --- a/src/main/scala/scala/async/internal/LiveVariables.scala +++ b/src/main/scala/scala/async/internal/LiveVariables.scala @@ -1,5 +1,10 @@ package scala.async.internal +import java.util +import java.util.function.{IntConsumer, IntPredicate} + +import scala.collection.immutable.IntMap + trait LiveVariables { self: AsyncMacro => import c.universe._ @@ -17,19 +22,22 @@ trait LiveVariables { def fieldsToNullOut(asyncStates: List[AsyncState], liftables: List[Tree]): Map[Int, List[Tree]] = { // live variables analysis: // the result map indicates in which states a given field should be nulled out - val liveVarsMap: Map[Tree, Set[Int]] = liveVars(asyncStates, liftables) + val liveVarsMap: Map[Tree, StateSet] = liveVars(asyncStates, liftables) var assignsOf = Map[Int, List[Tree]]() - for ((fld, where) <- liveVarsMap; state <- where) - assignsOf get state match { - case None => - assignsOf += (state -> List(fld)) - case Some(trees) if !trees.exists(_.symbol == fld.symbol) => - assignsOf += (state -> (fld +: trees)) - case _ => - /* do nothing */ - } + for ((fld, where) <- liveVarsMap) { + where.foreach { new IntConsumer { def accept(state: Int): Unit = { + assignsOf get state match { + case None => + assignsOf += (state -> List(fld)) + case Some(trees) if !trees.exists(_.symbol == fld.symbol) => + assignsOf += (state -> (fld +: trees)) + case _ => + // do nothing + } + }}} + } assignsOf } @@ -46,9 +54,9 @@ trait LiveVariables { * @param liftables the lifted fields * @return a map which indicates for a given field (the key) the states in which it should be nulled out */ - def liveVars(asyncStates: List[AsyncState], liftables: List[Tree]): Map[Tree, Set[Int]] = { + def liveVars(asyncStates: List[AsyncState], liftables: List[Tree]): Map[Tree, StateSet] = { val liftedSyms: Set[Symbol] = // include only vars - liftables.filter { + liftables.iterator.filter { case ValDef(mods, _, _, _) => mods.hasFlag(MUTABLE) case _ => false }.map(_.symbol).toSet @@ -122,20 +130,30 @@ trait LiveVariables { * A state `i` is contained in the list that is the value to which * key `j` maps iff control can flow from state `j` to state `i`. */ - val cfg: Map[Int, List[Int]] = asyncStates.map(as => as.state -> as.nextStates).toMap + val cfg: Map[Int, Array[Int]] = { + var res = IntMap.empty[Array[Int]] + + for (as <- asyncStates) res = res.updated(as.state, as.nextStates) + res + } /** Tests if `state1` is a predecessor of `state2`. */ def isPred(state1: Int, state2: Int): Boolean = { - val seen = scala.collection.mutable.HashSet[Int]() + val seen = new StateSet() def isPred0(state1: Int, state2: Int): Boolean = if(state1 == state2) false - else if (seen(state1)) false // breaks cycles in the CFG + else if (seen.contains(state1)) false // breaks cycles in the CFG else cfg get state1 match { case Some(nextStates) => seen += state1 - nextStates.contains(state2) || nextStates.exists(isPred0(_, state2)) + var i = 0 + while (i < nextStates.length) { + if (nextStates(i) == state2 || isPred0(nextStates(i), state2)) return true + i += 1 + } + false case None => false } @@ -164,8 +182,8 @@ trait LiveVariables { * 7. repeat if something has changed */ - var LVentry = Map[Int, Set[Symbol]]() withDefaultValue Set[Symbol]() - var LVexit = Map[Int, Set[Symbol]]() withDefaultValue Set[Symbol]() + var LVentry = IntMap[Set[Symbol]]() withDefaultValue Set[Symbol]() + var LVexit = IntMap[Set[Symbol]]() withDefaultValue Set[Symbol]() // All fields are declared to be dead at the exit of the final async state, except for the ones // that cannot be nulled out at all (those in noNull), because they have been captured by a nested def. @@ -174,6 +192,14 @@ trait LiveVariables { var currStates = List(finalState) // start at final state var captured: Set[Symbol] = Set() + def contains(as: Array[Int], a: Int): Boolean = { + var i = 0 + while (i < as.length) { + if (as(i) == a) return true + i += 1 + } + false + } while (!currStates.isEmpty) { var entryChanged: List[AsyncState] = Nil @@ -183,19 +209,19 @@ trait LiveVariables { captured ++= referenced.captured val LVentryNew = LVexit(cs.state) ++ referenced.used if (!LVentryNew.sameElements(LVentryOld)) { - LVentry = LVentry + (cs.state -> LVentryNew) + LVentry = LVentry.updated(cs.state, LVentryNew) entryChanged ::= cs } } - val pred = entryChanged.flatMap(cs => asyncStates.filter(_.nextStates.contains(cs.state))) + val pred = entryChanged.flatMap(cs => asyncStates.filter(state => contains(state.nextStates, cs.state))) var exitChanged: List[AsyncState] = Nil for (p <- pred) { val LVexitOld = LVexit(p.state) val LVexitNew = p.nextStates.flatMap(succ => LVentry(succ)).toSet if (!LVexitNew.sameElements(LVexitOld)) { - LVexit = LVexit + (p.state -> LVexitNew) + LVexit = LVexit.updated(p.state, LVexitNew) exitChanged ::= p } } @@ -210,53 +236,64 @@ trait LiveVariables { } } - def lastUsagesOf(field: Tree, at: AsyncState): Set[Int] = { + def lastUsagesOf(field: Tree, at: AsyncState): StateSet = { val avoid = scala.collection.mutable.HashSet[AsyncState]() - def lastUsagesOf0(field: Tree, at: AsyncState): Set[Int] = { - if (avoid(at)) Set() + val result = new StateSet + def lastUsagesOf0(field: Tree, at: AsyncState): Unit = { + if (avoid(at)) () else if (captured(field.symbol)) { - Set() + () } else LVentry get at.state match { case Some(fields) if fields.contains(field.symbol) => - Set(at.state) + result += at.state case _ => avoid += at - val preds = asyncStates.filter(_.nextStates.contains(at.state)).toSet - preds.flatMap(p => lastUsagesOf0(field, p)) + for (state <- asyncStates) { + if (contains(state.nextStates, at.state)) { + lastUsagesOf0(field, state) + } + } } } lastUsagesOf0(field, at) + result } - val lastUsages: Map[Tree, Set[Int]] = - liftables.map(fld => fld -> lastUsagesOf(fld, finalState)).toMap + val lastUsages: Map[Tree, StateSet] = + liftables.iterator.map(fld => fld -> lastUsagesOf(fld, finalState)).toMap if(AsyncUtils.verbose) { for ((fld, lastStates) <- lastUsages) - AsyncUtils.vprintln(s"field ${fld.symbol.name} is last used in states ${lastStates.mkString(", ")}") + AsyncUtils.vprintln(s"field ${fld.symbol.name} is last used in states ${lastStates.iterator.mkString(", ")}") } - val nullOutAt: Map[Tree, Set[Int]] = + val nullOutAt: Map[Tree, StateSet] = for ((fld, lastStates) <- lastUsages) yield { - val killAt = lastStates.flatMap { s => - if (s == finalState.state) Set() - else { + var result = new StateSet + lastStates.foreach(new IntConsumer { def accept(s: Int): Unit = { + if (s != finalState.state) { val lastAsyncState = asyncStates.find(_.state == s).get val succNums = lastAsyncState.nextStates // all successor states that are not indirect predecessors // filter out successor states where the field is live at the entry - succNums.filter(num => !isPred(num, s)).filterNot(num => LVentry(num).contains(fld.symbol)) + var i = 0 + while (i < succNums.length) { + val num = succNums(i) + if (!isPred(num, s) && !LVentry(num).contains(fld.symbol)) + result += num + i += 1 + } } - } - (fld, killAt) + }}) + (fld, result) } if(AsyncUtils.verbose) { for ((fld, killAt) <- nullOutAt) - AsyncUtils.vprintln(s"field ${fld.symbol.name} should be nulled out in states ${killAt.mkString(", ")}") + AsyncUtils.vprintln(s"field ${fld.symbol.name} should be nulled out in states ${killAt.iterator.mkString(", ")}") } nullOutAt diff --git a/src/main/scala/scala/async/internal/StateSet.scala b/src/main/scala/scala/async/internal/StateSet.scala new file mode 100644 index 00000000..2dc61e7c --- /dev/null +++ b/src/main/scala/scala/async/internal/StateSet.scala @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 Lightbend Inc. + */ +package scala.async.internal + +import java.util +import java.util.function.{Consumer, IntConsumer} + +import scala.collection.JavaConverters.{asScalaIteratorConverter, iterableAsScalaIterableConverter} +import scala.collection.mutable + +// Set for StateIds, which are either small positive integers or -symbolID. +final class StateSet { + private var bitSet = new java.util.BitSet() + private var caseSet = new util.HashSet[Integer]() + def +=(stateId: Int): Unit = if (stateId > 0) bitSet.set(stateId) else caseSet.add(stateId) + def contains(stateId: Int): Boolean = if (stateId > 0 && stateId < 1024) bitSet.get(stateId) else caseSet.contains(stateId) + def iterator: Iterator[Integer] = { + bitSet.stream().iterator().asScala ++ caseSet.asScala.iterator + } + def foreach(f: IntConsumer): Unit = { + bitSet.stream().forEach(f) + caseSet.stream().forEach(new Consumer[Integer] { + override def accept(value: Integer): Unit = f.accept(value) + }) + } +} diff --git a/src/main/scala/scala/async/internal/TransformUtils.scala b/src/main/scala/scala/async/internal/TransformUtils.scala index be56bb71..79e453a7 100644 --- a/src/main/scala/scala/async/internal/TransformUtils.scala +++ b/src/main/scala/scala/async/internal/TransformUtils.scala @@ -17,42 +17,8 @@ private[async] trait TransformUtils { import c.internal._ import decorators._ - private object baseNames { - - val matchRes = "matchres" - val ifRes = "ifres" - val bindSuffix = "$bind" - val completed = newTermName("completed") - - val state = newTermName("state") - val result = newTermName(self.futureSystem.resultFieldName) - val execContext = newTermName("execContext") - val tr = newTermName("tr") - val t = newTermName("throwable") - } - - object name { - def matchRes = maybeFresh(baseNames.matchRes) - def ifRes = maybeFresh(baseNames.ifRes) - def bindSuffix = maybeFresh(baseNames.bindSuffix) - def completed = maybeFresh(baseNames.completed) - - val state = maybeFresh(baseNames.state) - val result = baseNames.result - val execContext = maybeFresh(baseNames.execContext) - val tr = maybeFresh(baseNames.tr) - val t = maybeFresh(baseNames.t) - - val await = "await" - val resume = newTermName("resume") - val apply = newTermName("apply") - val stateMachine = newTermName(fresh("stateMachine")) - val stateMachineT = stateMachine.toTypeName - - def maybeFresh(name: TermName): TermName = if (self.asyncBase.futureSystem.freshenAllNames) fresh(name) else name - def maybeFresh(name: String): String = if (self.asyncBase.futureSystem.freshenAllNames) fresh(name) else name - def fresh(name: TermName): TermName = c.freshName(name) - + object name extends asyncNames.AsyncName { + def fresh(name: TermName): TermName = freshenIfNeeded(name) def fresh(name: String): String = c.freshName(name) } @@ -162,10 +128,10 @@ private[async] trait TransformUtils { (i, j) => util.Try(byNamess(i)(j)).getOrElse(false) } } - private def argName(fun: Tree): ((Int, Int) => String) = { + private def argName(fun: Tree): ((Int, Int) => TermName) = { val paramss = fun.tpe.paramss - val namess = paramss.map(_.map(_.name.toString)) - (i, j) => util.Try(namess(i)(j)).getOrElse(s"arg_${i}_${j}") + val namess = paramss.map(_.map(_.name.toTermName)) + (i, j) => util.Try(namess(i)(j)).getOrElse(TermName(s"arg_${i}_${j}")) } object defn { @@ -246,7 +212,7 @@ private[async] trait TransformUtils { } } - case class Arg(expr: Tree, isByName: Boolean, argName: String) + case class Arg(expr: Tree, isByName: Boolean, argName: TermName) /** * Transform a list of argument lists, producing the transformed lists, and lists of auxillary @@ -261,7 +227,7 @@ private[async] trait TransformUtils { */ def mapArgumentss[A](fun: Tree, argss: List[List[Tree]])(f: Arg => (A, Tree)): (List[List[A]], List[List[Tree]]) = { val isByNamess: (Int, Int) => Boolean = isByName(fun) - val argNamess: (Int, Int) => String = argName(fun) + val argNamess: (Int, Int) => TermName = argName(fun) argss.zipWithIndex.map { case (args, i) => mapArguments[A](args) { (tree, j) => f(Arg(tree, isByNamess(i, j), argNamess(i, j))) diff --git a/src/test/scala/scala/async/TreeInterrogation.scala b/src/test/scala/scala/async/TreeInterrogation.scala index 9426d1dc..4ad40360 100644 --- a/src/test/scala/scala/async/TreeInterrogation.scala +++ b/src/test/scala/scala/async/TreeInterrogation.scala @@ -38,7 +38,7 @@ class TreeInterrogation { val varDefs = tree1.collect { case vd @ ValDef(mods, name, _, _) if mods.hasFlag(Flag.MUTABLE) && vd.symbol.owner.isClass => name } - varDefs.map(_.decoded.trim).toSet.toList.sorted mustStartWith (List("await$macro$", "await$macro$", "state")) + varDefs.map(_.decoded.trim).toSet.toList.sorted mustStartWith (List("await$async$", "await$async", "state$async")) val defDefs = tree1.collect { case t: Template => @@ -49,11 +49,11 @@ class TreeInterrogation { && !dd.symbol.asTerm.isAccessor && !dd.symbol.asTerm.isSetter => dd.name } }.flatten - defDefs.map(_.decoded.trim) mustStartWith List("foo$macro$", "", "apply", "apply") + defDefs.map(_.decoded.trim) mustStartWith List("foo$async$", "", "apply", "apply") } } -object TreeInterrogation extends App { +object TreeInterrogationApp extends App { def withDebug[T](t: => T): T = { def set(level: String, value: Boolean) = System.setProperty(s"scala.async.$level", value.toString) val levels = Seq("trace", "debug") @@ -65,7 +65,7 @@ object TreeInterrogation extends App { withDebug { val cm = reflect.runtime.currentMirror - val tb = mkToolbox(s"-cp ${toolboxClasspath} -Xprint:typer -uniqid") + val tb = mkToolbox(s"-cp ${toolboxClasspath} -Xprint:typer") import scala.async.internal.AsyncId._ val tree = tb.parse( """ @@ -75,6 +75,9 @@ object TreeInterrogation extends App { | while(await(b)) { | b = false | } + | (1, 1) match { + | case (x, y) => await(2); println(x) + | } | await(b) | } | diff --git a/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala b/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala index 661b6dc2..66c3fabc 100644 --- a/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala +++ b/src/test/scala/scala/async/run/anf/AnfTransformSpec.scala @@ -403,7 +403,8 @@ class AnfTransformSpec { """.stripMargin }) val applyImplicitView = tree.collect { case x if x.getClass.getName.endsWith("ApplyImplicitView") => x } - applyImplicitView.map(_.toString) mustStartWith List("view(a$macro$") + println(applyImplicitView) + applyImplicitView.map(_.toString) mustStartWith List("view(") } @Test