From 67b1a6025c88ae55b49ecd8af0948c3f8faa8ab9 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 10 Aug 2017 16:31:53 +1000 Subject: Allow future system to enable more name freshening --- .../scala/scala/async/internal/FutureSystem.scala | 2 + .../scala/async/internal/TransformUtils.scala | 43 +++++++++++++++------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/main/scala/scala/async/internal/FutureSystem.scala b/src/main/scala/scala/async/internal/FutureSystem.scala index f330cbf..aad5b92 100644 --- a/src/main/scala/scala/async/internal/FutureSystem.scala +++ b/src/main/scala/scala/async/internal/FutureSystem.scala @@ -74,6 +74,8 @@ trait FutureSystem { } def mkOps(c0: Context): Ops { val c: c0.type } + + def freshenAllNames: Boolean = false } object ScalaConcurrentFutureSystem extends FutureSystem { diff --git a/src/main/scala/scala/async/internal/TransformUtils.scala b/src/main/scala/scala/async/internal/TransformUtils.scala index c86540b..4c16dd7 100644 --- a/src/main/scala/scala/async/internal/TransformUtils.scala +++ b/src/main/scala/scala/async/internal/TransformUtils.scala @@ -17,23 +17,40 @@ 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("result") + val execContext = newTermName("execContext") + val tr = newTermName("tr") + val t = newTermName("throwable") + } + object name { - val resume = newTermName("resume") - val apply = newTermName("apply") - val matchRes = "matchres" - val ifRes = "ifres" - val await = "await" - val bindSuffix = "$bind" - val completed = newTermName("completed") - - val state = newTermName("state") - val result = newTermName("result") - val execContext = newTermName("execContext") + 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 = maybeFresh(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 - val tr = newTermName("tr") - val t = newTermName("throwable") + 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) def fresh(name: String): String = c.freshName(name) -- cgit v1.2.3 From 447288060a3d057a220ed4f53b7a164c651271f1 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 10 Aug 2017 16:31:53 +1000 Subject: Support future systems that perform external failure handling --- .../scala/scala/async/internal/ExprBuilder.scala | 28 +++++++++++++--------- .../scala/scala/async/internal/FutureSystem.scala | 1 + .../scala/async/internal/TransformUtils.scala | 2 ++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/main/scala/scala/async/internal/ExprBuilder.scala b/src/main/scala/scala/async/internal/ExprBuilder.scala index 002e5cc..bb5e06a 100644 --- a/src/main/scala/scala/async/internal/ExprBuilder.scala +++ b/src/main/scala/scala/async/internal/ExprBuilder.scala @@ -117,16 +117,22 @@ trait ExprBuilder { * * } */ - def ifIsFailureTree[T: WeakTypeTag](tryReference: => Tree) = - If(futureSystemOps.tryyIsFailure(c.Expr[futureSystem.Tryy[T]](tryReference)).tree, - Block(toList(futureSystemOps.completeProm[T]( - c.Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)), - c.Expr[futureSystem.Tryy[T]]( - TypeApply(Select(tryReference, newTermName("asInstanceOf")), - List(TypeTree(futureSystemOps.tryType[T]))))).tree), - Return(literalUnit)), - Block(List(tryGetTree(tryReference)), mkStateTree(nextState, symLookup)) - ) + def ifIsFailureTree[T: WeakTypeTag](tryReference: => Tree) = { + val getAndUpdateState = Block(List(tryGetTree(tryReference)), mkStateTree(nextState, symLookup)) + if (asyncBase.futureSystem.emitTryCatch) { + If(futureSystemOps.tryyIsFailure(c.Expr[futureSystem.Tryy[T]](tryReference)).tree, + Block(toList(futureSystemOps.completeProm[T]( + c.Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)), + c.Expr[futureSystem.Tryy[T]]( + TypeApply(Select(tryReference, newTermName("asInstanceOf")), + List(TypeTree(futureSystemOps.tryType[T]))))).tree), + Return(literalUnit)), + getAndUpdateState + ) + } else { + getAndUpdateState + } + } override def mkOnCompleteHandler[T: WeakTypeTag]: Option[CaseDef] = { Some(mkHandlerCase(onCompleteState, List(ifIsFailureTree[T](Ident(symLookup.applyTrParam))))) @@ -402,7 +408,7 @@ trait ExprBuilder { val stateMemberRef = symLookup.memberRef(name.state) val body = Match(stateMemberRef, mkCombinedHandlerCases[T] ++ initStates.flatMap(_.mkOnCompleteHandler[T]) ++ List(CaseDef(Ident(nme.WILDCARD), EmptyTree, Throw(Apply(Select(New(Ident(defn.IllegalStateExceptionClass)), termNames.CONSTRUCTOR), List()))))) - Try( + maybeTry( body, List( CaseDef( diff --git a/src/main/scala/scala/async/internal/FutureSystem.scala b/src/main/scala/scala/async/internal/FutureSystem.scala index aad5b92..b248744 100644 --- a/src/main/scala/scala/async/internal/FutureSystem.scala +++ b/src/main/scala/scala/async/internal/FutureSystem.scala @@ -76,6 +76,7 @@ trait FutureSystem { def mkOps(c0: Context): Ops { val c: c0.type } def freshenAllNames: Boolean = false + def emitTryCatch: Boolean = true } object ScalaConcurrentFutureSystem extends FutureSystem { diff --git a/src/main/scala/scala/async/internal/TransformUtils.scala b/src/main/scala/scala/async/internal/TransformUtils.scala index 4c16dd7..1c0b625 100644 --- a/src/main/scala/scala/async/internal/TransformUtils.scala +++ b/src/main/scala/scala/async/internal/TransformUtils.scala @@ -56,6 +56,8 @@ private[async] trait TransformUtils { def fresh(name: String): String = c.freshName(name) } + def maybeTry(block: Tree, catches: List[CaseDef], finalizer: Tree) = if (asyncBase.futureSystem.emitTryCatch) Try(block, catches, finalizer) else block + def isAsync(fun: Tree) = fun.symbol == defn.Async_async -- cgit v1.2.3 From ff6bb1f41f8cdf2de469a161acdd8365ad6ae1f3 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 14 Sep 2017 09:11:01 +1000 Subject: Allow result field name to be externally specified --- src/main/scala/scala/async/internal/FutureSystem.scala | 1 + src/main/scala/scala/async/internal/TransformUtils.scala | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/scala/scala/async/internal/FutureSystem.scala b/src/main/scala/scala/async/internal/FutureSystem.scala index b248744..c1d72f2 100644 --- a/src/main/scala/scala/async/internal/FutureSystem.scala +++ b/src/main/scala/scala/async/internal/FutureSystem.scala @@ -77,6 +77,7 @@ trait FutureSystem { def freshenAllNames: Boolean = false def emitTryCatch: Boolean = true + def resultFieldName: String = "result" } object ScalaConcurrentFutureSystem extends FutureSystem { diff --git a/src/main/scala/scala/async/internal/TransformUtils.scala b/src/main/scala/scala/async/internal/TransformUtils.scala index 1c0b625..1720815 100644 --- a/src/main/scala/scala/async/internal/TransformUtils.scala +++ b/src/main/scala/scala/async/internal/TransformUtils.scala @@ -25,7 +25,7 @@ private[async] trait TransformUtils { val completed = newTermName("completed") val state = newTermName("state") - val result = newTermName("result") + val result = newTermName(self.futureSystem.resultFieldName) val execContext = newTermName("execContext") val tr = newTermName("tr") val t = newTermName("throwable") @@ -38,7 +38,7 @@ private[async] trait TransformUtils { def completed = maybeFresh(baseNames.completed) val state = maybeFresh(baseNames.state) - val result = maybeFresh(baseNames.result) + val result = baseNames.result val execContext = maybeFresh(baseNames.execContext) val tr = maybeFresh(baseNames.tr) val t = maybeFresh(baseNames.t) -- cgit v1.2.3 From 2e381166b0983c3713ceadd15ab9cad390d65684 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 10 Aug 2017 16:31:53 +1000 Subject: Eliminate dead states If a state does nothing but unconditionally transition to the next state, remove it and rewrite predecessors to directly jump to the successor state (or to the first non-dead successor.) While we're doing this, compact the remaining state IDs to be contiguous, which will allow use of a tableswitch in bytecode. Sample bytecode demonstrating a tableswitch: https://gist.github.com/retronym/6880c35b501fc1c91bed7f30c0f2c045 --- .../scala/scala/async/internal/ExprBuilder.scala | 65 +++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/src/main/scala/scala/async/internal/ExprBuilder.scala b/src/main/scala/scala/async/internal/ExprBuilder.scala index bb5e06a..86d5422 100644 --- a/src/main/scala/scala/async/internal/ExprBuilder.scala +++ b/src/main/scala/scala/async/internal/ExprBuilder.scala @@ -3,6 +3,7 @@ */ package scala.async.internal +import scala.collection.mutable import scala.collection.mutable.ListBuffer import language.existentials @@ -407,9 +408,10 @@ trait ExprBuilder { val stateMemberSymbol = symLookup.stateMachineMember(name.state) val stateMemberRef = symLookup.memberRef(name.state) val body = Match(stateMemberRef, mkCombinedHandlerCases[T] ++ initStates.flatMap(_.mkOnCompleteHandler[T]) ++ List(CaseDef(Ident(nme.WILDCARD), EmptyTree, Throw(Apply(Select(New(Ident(defn.IllegalStateExceptionClass)), termNames.CONSTRUCTOR), List()))))) + val body1 = eliminateDeadStates(body) maybeTry( - body, + body1, List( CaseDef( Bind(name.t, Typed(Ident(nme.WILDCARD), Ident(defn.ThrowableClass))), @@ -423,8 +425,67 @@ trait ExprBuilder { If(Apply(Ident(defn.NonFatalClass), List(Ident(name.t))), then, Throw(Ident(name.t))) then })), EmptyTree) + } - //body + // Identify dead states: `case => { state = nextId; (); (); ... }, eliminated, and compact state ids to + // enable emission of a tableswitch. + private def eliminateDeadStates(m: Match): Tree = { + object DeadState { + private val liveStates = mutable.AnyRefMap[Integer, Integer]() + private val deadStates = mutable.AnyRefMap[Integer, Integer]() + private var compactedStateId = 1 + for (CaseDef(Literal(Constant(stateId: Integer)), EmptyTree, body) <- m.cases) { + body match { + case _ if (stateId == 0) => liveStates(stateId) = stateId + case Block(Assign(_, Literal(Constant(nextState: Integer))) :: rest, expr) if (expr :: rest).forall(t => isLiteralUnit(t)) => + deadStates(stateId) = nextState + case _ => + liveStates(stateId) = compactedStateId + compactedStateId += 1 + } + } + if (deadStates.nonEmpty) + AsyncUtils.vprintln(s"${deadStates.size} dead states eliminated") + def isDead(i: Integer) = deadStates.contains(i) + def translatedStateId(i: Integer, tree: Tree): Integer = { + def chaseDead(i: Integer): Integer = { + val replacement = deadStates.getOrNull(i) + if (replacement == null) i + else chaseDead(replacement) + } + + val live = chaseDead(i) + liveStates.get(live) match { + case Some(x) => x + case None => sys.error(s"$live, $liveStates \n$deadStates\n$m\n\n====\n$tree") + } + } + } + val stateMemberSymbol = symLookup.stateMachineMember(name.state) + // - remove CaseDef-s for dead states + // - rewrite state transitions to dead states to instead transition to the + // non-dead successor. + val elimDeadStateTransform = new Transformer { + override def transform(tree: Tree): Tree = tree match { + case as @ Assign(lhs, Literal(Constant(i: Integer))) if lhs.symbol == stateMemberSymbol => + val replacement = DeadState.translatedStateId(i, as) + treeCopy.Assign(tree, lhs, Literal(Constant(replacement))) + case _: Match | _: CaseDef | _: Block | _: If => + super.transform(tree) + case _ => tree + } + } + val cases1 = m.cases.flatMap { + case cd @ CaseDef(Literal(Constant(i: Integer)), EmptyTree, rhs) => + if (DeadState.isDead(i)) Nil + else { + val replacement = DeadState.translatedStateId(i, cd) + val rhs1 = elimDeadStateTransform.transform(rhs) + treeCopy.CaseDef(cd, Literal(Constant(replacement)), EmptyTree, rhs1) :: Nil + } + case x => x :: Nil + } + treeCopy.Match(m, m.selector, cases1) } def forever(t: Tree): Tree = { -- cgit v1.2.3