From 9638633905e3a2f70432fa12caad965932bc889e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 6 Apr 2014 18:05:27 +0200 Subject: 0.9.1 --- README.md | 2 +- build.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f456d8..66c6f43 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Add a dependency: ```scala // SBT -libraryDependencies += "org.scala-lang.modules" %% "scala-async" % "0.9.0" +libraryDependencies += "org.scala-lang.modules" %% "scala-async" % "0.9.1" ``` Write your first `async` block: diff --git a/build.sbt b/build.sbt index 245d335..bd0f4e8 100644 --- a/build.sbt +++ b/build.sbt @@ -8,7 +8,7 @@ organization := "org.scala-lang.modules" name := "scala-async" -version := "0.9.1-SNAPSHOT" +version := "0.9.1" libraryDependencies <++= (scalaVersion) { sv => Seq( -- cgit v1.2.3 From f77d11962a3bf73c813a42a05e842ce710588c3f Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 6 Apr 2014 18:08:14 +0200 Subject: 0.9.2-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index bd0f4e8..0940d60 100644 --- a/build.sbt +++ b/build.sbt @@ -8,7 +8,7 @@ organization := "org.scala-lang.modules" name := "scala-async" -version := "0.9.1" +version := "0.9.2-SNAPSHOT" libraryDependencies <++= (scalaVersion) { sv => Seq( -- cgit v1.2.3 From f9eb27daf750010556d49dd1411ac2433ac29f17 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 14 Jun 2014 12:51:24 +0200 Subject: Avoid NotImplementedError awaiting a Future[Nothing] `gen.mkZero(NothingTpe)` gives the tree `Predef.???`. Instead, we should leave the `await` field uninitialized with `ValDef(..., rhs = EmptyTree)`. Fixes #66 --- src/main/scala/scala/async/internal/Lifter.scala | 3 +-- src/test/scala/scala/async/run/toughtype/ToughType.scala | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/scala/scala/async/internal/Lifter.scala b/src/main/scala/scala/async/internal/Lifter.scala index 7b102d1..403eae9 100644 --- a/src/main/scala/scala/async/internal/Lifter.scala +++ b/src/main/scala/scala/async/internal/Lifter.scala @@ -113,8 +113,7 @@ trait Lifter { sym.setFlag(MUTABLE | STABLE | PRIVATE | LOCAL) sym.name = name.fresh(sym.name.toTermName) sym.modifyInfo(_.deconst) - val zeroRhs = atPos(t.pos)(gen.mkZero(vd.symbol.info)) - treeCopy.ValDef(vd, Modifiers(sym.flags), sym.name, TypeTree(sym.tpe).setPos(t.pos), zeroRhs) + treeCopy.ValDef(vd, Modifiers(sym.flags), sym.name, TypeTree(sym.tpe).setPos(t.pos), EmptyTree) case dd@DefDef(_, _, tparams, vparamss, tpt, rhs) => sym.name = this.name.fresh(sym.name.toTermName) sym.setFlag(PRIVATE | LOCAL) diff --git a/src/test/scala/scala/async/run/toughtype/ToughType.scala b/src/test/scala/scala/async/run/toughtype/ToughType.scala index 458157c..54a53c8 100644 --- a/src/test/scala/scala/async/run/toughtype/ToughType.scala +++ b/src/test/scala/scala/async/run/toughtype/ToughType.scala @@ -211,6 +211,22 @@ class ToughTypeSpec { }(SomeExecutionContext) } } + + } + + @Test def ticket66Nothing() { + import scala.concurrent.Future + import scala.concurrent.ExecutionContext.Implicits.global + val e = new Exception() + val f: Future[Nothing] = Future.failed(e) + val f1 = async { + await(f) + } + try { + Await.result(f1, 5.seconds) + } catch { + case `e` => + } } } -- cgit v1.2.3 From 9d1246d799419a8e7d96302c5787ce252e86b68d Mon Sep 17 00:00:00 2001 From: Gene Novark Date: Fri, 4 Jul 2014 15:01:11 -0400 Subject: Fix asymptotic performance issues in live variables analysis. Fix possibly-exponential runtime for DFS graph searches. Improve DFA fixpoint algorithm to correctly compute worklist of only changed nodes for each iteration. Added test that takes > 2 minutes to compile without these improvements. --- .../scala/scala/async/internal/LiveVariables.scala | 71 ++++++++++++-------- .../scala/scala/async/run/ifelse1/IfElse1.scala | 77 ++++++++++++++++++++++ 2 files changed, 120 insertions(+), 28 deletions(-) diff --git a/src/main/scala/scala/async/internal/LiveVariables.scala b/src/main/scala/scala/async/internal/LiveVariables.scala index 8753b3d..23063ba 100644 --- a/src/main/scala/scala/async/internal/LiveVariables.scala +++ b/src/main/scala/scala/async/internal/LiveVariables.scala @@ -126,14 +126,22 @@ trait LiveVariables { /** Tests if `state1` is a predecessor of `state2`. */ - def isPred(state1: Int, state2: Int, seen: Set[Int] = Set()): Boolean = - if (seen(state1)) false // breaks cycles in the CFG - else cfg get state1 match { - case Some(nextStates) => - nextStates.contains(state2) || nextStates.exists(isPred(_, state2, seen + state1)) - case None => - false - } + def isPred(state1: Int, state2: Int): Boolean = { + val seen = scala.collection.mutable.HashSet[Int]() + + def isPred0(state1: Int, state2: Int): Boolean = + if(state1 == state2) false + else if (seen(state1)) false // breaks cycles in the CFG + else cfg get state1 match { + case Some(nextStates) => + seen += state1 + nextStates.contains(state2) || nextStates.exists(isPred0(_, state2)) + case None => + false + } + + isPred0(state1, state2) + } val finalState = asyncStates.find(as => !asyncStates.exists(other => isPred(as.state, other.state))).get @@ -162,12 +170,10 @@ trait LiveVariables { LVexit = LVexit + (finalState.state -> noNull) var currStates = List(finalState) // start at final state - var pred = List[AsyncState]() // current predecessor states - var hasChanged = true // if something has changed we need to continue iterating var captured: Set[Symbol] = Set() - while (hasChanged) { - hasChanged = false + while (!currStates.isEmpty) { + var entryChanged: List[AsyncState] = Nil for (cs <- currStates) { val LVentryOld = LVentry(cs.state) @@ -176,22 +182,23 @@ trait LiveVariables { val LVentryNew = LVexit(cs.state) ++ referenced.used if (!LVentryNew.sameElements(LVentryOld)) { LVentry = LVentry + (cs.state -> LVentryNew) - hasChanged = true + entryChanged ::= cs } } - pred = currStates.flatMap(cs => asyncStates.filter(_.nextStates.contains(cs.state))) + val pred = entryChanged.flatMap(cs => asyncStates.filter(_.nextStates.contains(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) - hasChanged = true + exitChanged ::= p } } - currStates = pred + currStates = exitChanged } for (as <- asyncStates) { @@ -199,21 +206,29 @@ trait LiveVariables { AsyncUtils.vprintln(s"LVexit at state #${as.state}: ${LVexit(as.state).mkString(", ")}") } - def lastUsagesOf(field: Tree, at: AsyncState, avoid: Set[AsyncState]): Set[Int] = - if (avoid(at)) Set() - else if (captured(field.symbol)) { - Set() - } - else LVentry get at.state match { - case Some(fields) if fields.exists(_ == field.symbol) => - Set(at.state) - case _ => - val preds = asyncStates.filter(_.nextStates.contains(at.state)).toSet - preds.flatMap(p => lastUsagesOf(field, p, avoid + at)) + def lastUsagesOf(field: Tree, at: AsyncState): Set[Int] = { + val avoid = scala.collection.mutable.HashSet[AsyncState]() + + def lastUsagesOf0(field: Tree, at: AsyncState): Set[Int] = { + if (avoid(at)) Set() + else if (captured(field.symbol)) { + Set() + } + else LVentry get at.state match { + case Some(fields) if fields.exists(_ == field.symbol) => + Set(at.state) + case _ => + avoid += at + val preds = asyncStates.filter(_.nextStates.contains(at.state)).toSet + preds.flatMap(p => lastUsagesOf0(field, p)) + } } + lastUsagesOf0(field, at) + } + val lastUsages: Map[Tree, Set[Int]] = - liftables.map(fld => (fld -> lastUsagesOf(fld, finalState, Set()))).toMap + liftables.map(fld => (fld -> lastUsagesOf(fld, finalState))).toMap for ((fld, lastStates) <- lastUsages) AsyncUtils.vprintln(s"field ${fld.symbol.name} is last used in states ${lastStates.mkString(", ")}") diff --git a/src/test/scala/scala/async/run/ifelse1/IfElse1.scala b/src/test/scala/scala/async/run/ifelse1/IfElse1.scala index 587aaac..6cbe910 100644 --- a/src/test/scala/scala/async/run/ifelse1/IfElse1.scala +++ b/src/test/scala/scala/async/run/ifelse1/IfElse1.scala @@ -87,6 +87,75 @@ class TestIfElse1Class { } z } + + def pred: Future[Boolean] = async(true) + + def m5: Future[Boolean] = async { + if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(await(pred)) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false + } } class IfElse1Spec { @@ -124,4 +193,12 @@ class IfElse1Spec { val res = Await.result(fut, 2 seconds) res mustBe (14) } + + @Test + def `await in deeply-nested if-else conditions`() { + val o = new TestIfElse1Class + val fut = o.m5 + val res = Await.result(fut, 2 seconds) + res mustBe true + } } -- cgit v1.2.3 From 1df849070694756533003c9c331cd04d0c3136e8 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 1 Jul 2014 16:45:33 -0400 Subject: Fix regression around type skolems and if exprs. If we start with: async({ val res = await[S[_$1 with String]](s); if (true) await[Int](0) res }) Typechecking the application (before macro expansion) yields (where the trees are printed in the form `expr{tpe}`): async[S[_$1#5738 with String#137]]({ val res: S[_$1#5490 with String] forSome { type _$1#5490 } = await[S[_$1#5487 with String]]( s{S[_$1#5487 with String]} ){S[_$1#5487 with String]}; if (true) await(0) else () res{S[_$1#5738 with String]} }{S[_$1#5738 with String]}){S[_$1#5738 with String]} Note that the type of the second last line contains a skolemized symbol `_$1#5738` of the existential `_$1#5490`. This is created by this case in `Typer#adapt`: case et @ ExistentialType(_, _) if ((mode & (EXPRmode | LHSmode)) == EXPRmode) => adapt(tree setType et.skolemizeExistential(context.owner, tree), mode, pt, original) Our ANF rewrites part of this code to: val await$1: S[_$1#5487 with String] = await[S[_$1#5487 with String]](awaitable$1); val res: S[_$1#5490 with String] forSome { type _$1 } = await$1; And later, the state machine transformation splits the last line into a blank field and an assignment. Typechecking the `Assign` node led to the an type error. This commit manually attributes the types to the `Assign` node so as to avoid these problem. It also reigns in an overeager rewriting of `If` nodes in the ANF transform, which was due to a bug in the label detection logic introduced in 4fc5463538. Thanks to @gnovark for yet another devilish test case and analysis of the problem with label detection. I worked on a more principled fix on: https://github.com/retronym/async/compare/ticket/79-2?expand=1 in which I try to use `repackExistential` to convert skolemized types to existentials for use as the types of synthetic vals introduced by the ANF transform. This ran into a deeper problem with existential subtyping in the compiler itself though. --- .../scala/async/internal/AsyncTransform.scala | 9 ++-- .../scala/async/internal/TransformUtils.scala | 2 +- .../scala/scala/async/run/ifelse4/IfElse4.scala | 63 ++++++++++++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 src/test/scala/scala/async/run/ifelse4/IfElse4.scala diff --git a/src/main/scala/scala/async/internal/AsyncTransform.scala b/src/main/scala/scala/async/internal/AsyncTransform.scala index c8d2234..a24a823 100644 --- a/src/main/scala/scala/async/internal/AsyncTransform.scala +++ b/src/main/scala/scala/async/internal/AsyncTransform.scala @@ -156,9 +156,12 @@ trait AsyncTransform { case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) => atOwner(currentOwner) { val fieldSym = tree.symbol - val set = Assign(gen.mkAttributedStableRef(fieldSym.owner.thisType, fieldSym), transform(rhs)) - changeOwner(set, tree.symbol, currentOwner) - localTyper.typedPos(tree.pos)(set) + val lhs = atPos(tree.pos) { + gen.mkAttributedStableRef(fieldSym.owner.thisType, fieldSym) + } + val assign = treeCopy.Assign(tree, lhs, transform(rhs)).setType(definitions.UnitTpe) + changeOwner(assign, tree.symbol, currentOwner) + assign } case _: DefTree if liftedSyms(tree.symbol) => EmptyTree diff --git a/src/main/scala/scala/async/internal/TransformUtils.scala b/src/main/scala/scala/async/internal/TransformUtils.scala index f228e1d..bef52f1 100644 --- a/src/main/scala/scala/async/internal/TransformUtils.scala +++ b/src/main/scala/scala/async/internal/TransformUtils.scala @@ -100,7 +100,7 @@ private[async] trait TransformUtils { case ld: LabelDef => ld.symbol }.toSet t.exists { - case rt: RefTree => !(labelDefs contains rt.symbol) + case rt: RefTree => rt.symbol != null && rt.symbol.isLabel && !(labelDefs contains rt.symbol) case _ => false } } diff --git a/src/test/scala/scala/async/run/ifelse4/IfElse4.scala b/src/test/scala/scala/async/run/ifelse4/IfElse4.scala new file mode 100644 index 0000000..b0ecf13 --- /dev/null +++ b/src/test/scala/scala/async/run/ifelse4/IfElse4.scala @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2012-2014 Typesafe Inc. + */ + +package scala.async +package run +package ifelse4 + +import language.{reflectiveCalls, postfixOps} +import scala.concurrent.{Future, ExecutionContext, future, Await} +import scala.concurrent.duration._ +import scala.async.Async.{async, await} +import org.junit.Test + + +class TestIfElse4Class { + + import ExecutionContext.Implicits.global + + class F[A] + class S[A](val id: String) + trait P + + case class K(f: F[_]) + + def result[A](f: F[A]) = async { + new S[A with P]("foo") + } + + def run(k: K) = async { + val res = await(result(k.f)) + // these triggered a crash with mismatched existential skolems + // found : S#10272[_$1#10308 with String#137] where type _$1#10308 + // required: S#10272[_$1#10311 with String#137] forSome { type _$1#10311 } + + // This variation of the crash could be avoided by fixing the over-eager + // generation of states in `If` nodes, which was caused by a bug in label + // detection code. + if(true) { + identity(res) + } + + // This variation remained after the aforementioned fix, however. + // It was fixed by manually typing the `Assign(liftedField, rhs)` AST, + // which is how we avoid these problems through the rest of the ANF transform. + if(true) { + identity(res) + await(result(k.f)) + } + res + } +} + +class IfElse4Spec { + + @Test + def `await result with complex type containing skolem`() { + val o = new TestIfElse4Class + val fut = o.run(new o.K(null)) + val res = Await.result(fut, 2 seconds) + res.id mustBe ("foo") + } +} -- cgit v1.2.3