Skip to content

Commit 2ec0c21

Browse files
committed
Merge pull request #35 from phaller/issue/memory-retention
Liveness analysis to avoid memory retention issues
2 parents 9ecbb7a + 9ac022c commit 2ec0c21

File tree

8 files changed

+444
-25
lines changed

8 files changed

+444
-25
lines changed

src/main/scala/scala/async/continuations/AsyncBaseWithCPSFallback.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ trait AsyncBaseWithCPSFallback extends internal.AsyncBase {
9292
(execContext: c.Expr[futureSystem.ExecContext]): c.Expr[futureSystem.Fut[T]] = {
9393
AsyncUtils.vprintln("AsyncBaseWithCPSFallback.asyncImpl")
9494

95-
val asyncMacro = AsyncMacro(c, futureSystem)
95+
val asyncMacro = AsyncMacro(c, this)
9696

9797
if (!asyncMacro.reportUnsupportedAwaits(body.tree.asInstanceOf[asyncMacro.global.Tree], report = fallbackEnabled))
9898
super.asyncImpl[T](c)(body)(execContext) // no unsupported awaits

src/main/scala/scala/async/internal/AsyncBase.scala

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package scala.async.internal
66

77
import scala.reflect.internal.annotations.compileTimeOnly
88
import scala.reflect.macros.Context
9+
import scala.reflect.api.Universe
910

1011
/**
1112
* A base class for the `async` macro. Subclasses must provide:
@@ -45,7 +46,7 @@ abstract class AsyncBase {
4546
(execContext: c.Expr[futureSystem.ExecContext]): c.Expr[futureSystem.Fut[T]] = {
4647
import c.universe._
4748

48-
val asyncMacro = AsyncMacro(c, futureSystem)
49+
val asyncMacro = AsyncMacro(c, self)
4950

5051
val code = asyncMacro.asyncTransform[T](
5152
body.tree.asInstanceOf[asyncMacro.global.Tree],
@@ -59,4 +60,7 @@ abstract class AsyncBase {
5960
AsyncUtils.vprintln(s"async state machine transform expands to:\n ${code}")
6061
c.Expr[futureSystem.Fut[T]](code)
6162
}
63+
64+
protected[async] def nullOut(u: Universe)(name: u.Expr[String], v: u.Expr[Any]): u.Expr[Unit] =
65+
u.reify { () }
6266
}

src/main/scala/scala/async/internal/AsyncId.scala

+18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package scala.async.internal
66

77
import language.experimental.macros
88
import scala.reflect.macros.Context
9+
import scala.reflect.api.Universe
910
import scala.reflect.internal.SymbolTable
1011

1112
object AsyncId extends AsyncBase {
@@ -17,6 +18,23 @@ object AsyncId extends AsyncBase {
1718
def asyncIdImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[T] = asyncImpl[T](c)(body)(c.literalUnit)
1819
}
1920

21+
object AsyncTestLV extends AsyncBase {
22+
lazy val futureSystem = IdentityFutureSystem
23+
type FS = IdentityFutureSystem.type
24+
25+
def async[T](body: T) = macro asyncIdImpl[T]
26+
27+
def asyncIdImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[T] = asyncImpl[T](c)(body)(c.literalUnit)
28+
29+
var log: List[(String, Any)] = List()
30+
31+
def apply(name: String, v: Any): Unit =
32+
log ::= (name -> v)
33+
34+
protected[async] override def nullOut(u: Universe)(name: u.Expr[String], v: u.Expr[Any]): u.Expr[Unit] =
35+
u.reify { scala.async.internal.AsyncTestLV(name.splice, v.splice) }
36+
}
37+
2038
/**
2139
* A trivial implementation of [[FutureSystem]] that performs computations
2240
* on the current thread. Useful for testing.

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

+9-6
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,26 @@ import scala.tools.nsc.Global
44
import scala.tools.nsc.transform.TypingTransformers
55

66
object AsyncMacro {
7-
def apply(c: reflect.macros.Context, futureSystem0: FutureSystem): AsyncMacro = {
7+
def apply(c: reflect.macros.Context, base: AsyncBase): AsyncMacro = {
88
import language.reflectiveCalls
99
val powerContext = c.asInstanceOf[c.type { val universe: Global; val callsiteTyper: universe.analyzer.Typer }]
1010
new AsyncMacro {
11-
val global: powerContext.universe.type = powerContext.universe
11+
val global: powerContext.universe.type = powerContext.universe
1212
val callSiteTyper: global.analyzer.Typer = powerContext.callsiteTyper
13-
val futureSystem: futureSystem0.type = futureSystem0
14-
val futureSystemOps: futureSystem.Ops {val universe: global.type} = futureSystem0.mkOps(global)
15-
val macroApplication: global.Tree = c.macroApplication.asInstanceOf[global.Tree]
13+
val macroApplication: global.Tree = c.macroApplication.asInstanceOf[global.Tree]
14+
// This member is required by `AsyncTransform`:
15+
val asyncBase: AsyncBase = base
16+
// These members are required by `ExprBuilder`:
17+
val futureSystem: FutureSystem = base.futureSystem
18+
val futureSystemOps: futureSystem.Ops {val universe: global.type} = futureSystem.mkOps(global)
1619
}
1720
}
1821
}
1922

2023
private[async] trait AsyncMacro
2124
extends TypingTransformers
2225
with AnfTransform with TransformUtils with Lifter
23-
with ExprBuilder with AsyncTransform with AsyncAnalysis {
26+
with ExprBuilder with AsyncTransform with AsyncAnalysis with LiveVariables {
2427

2528
val global: Global
2629
val callSiteTyper: global.analyzer.Typer

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

+42-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ trait AsyncTransform {
55

66
import global._
77

8+
val asyncBase: AsyncBase
9+
810
def asyncTransform[T](body: Tree, execContext: Tree, cpsFallbackEnabled: Boolean)
911
(resultType: WeakTypeTag[T]): Tree = {
1012

@@ -29,6 +31,7 @@ trait AsyncTransform {
2931

3032
val stateMachineType = applied("scala.async.StateMachine", List(futureSystemOps.promType[T](uncheckedBoundsResultTag), futureSystemOps.execContextType))
3133

34+
// Create `ClassDef` of state machine with empty method bodies for `resume` and `apply`.
3235
val stateMachine: ClassDef = {
3336
val body: List[Tree] = {
3437
val stateVar = ValDef(Modifiers(Flag.MUTABLE | Flag.PRIVATE | Flag.LOCAL), name.state, TypeTree(definitions.IntTpe), Literal(Constant(0)))
@@ -42,24 +45,45 @@ trait AsyncTransform {
4245
}
4346
List(emptyConstructor, stateVar, result, execContextValDef) ++ List(resumeFunTreeDummyBody, applyDefDefDummyBody, apply0DefDef)
4447
}
45-
val template = {
46-
Template(List(stateMachineType), emptyValDef, body)
47-
}
48+
49+
val template = Template(List(stateMachineType), emptyValDef, body)
50+
4851
val t = ClassDef(NoMods, name.stateMachineT, Nil, template)
4952
callSiteTyper.typedPos(macroPos)(Block(t :: Nil, Literal(Constant(()))))
5053
t
5154
}
5255

56+
val stateMachineClass = stateMachine.symbol
5357
val asyncBlock: AsyncBlock = {
54-
val symLookup = new SymLookup(stateMachine.symbol, applyDefDefDummyBody.vparamss.head.head.symbol)
58+
val symLookup = new SymLookup(stateMachineClass, applyDefDefDummyBody.vparamss.head.head.symbol)
5559
buildAsyncBlock(anfTree, symLookup)
5660
}
5761

5862
logDiagnostics(anfTree, asyncBlock.asyncStates.map(_.toString))
5963

64+
val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates)
65+
66+
// live variables analysis
67+
// the result map indicates in which states a given field should be nulled out
68+
val assignsOf = fieldsToNullOut(asyncBlock.asyncStates, liftedFields)
69+
70+
for ((state, flds) <- assignsOf) {
71+
val assigns = flds.map { fld =>
72+
val fieldSym = fld.symbol
73+
Block(
74+
List(
75+
asyncBase.nullOut(global)(Expr[String](Literal(Constant(fieldSym.name.toString))), Expr[Any](Ident(fieldSym))).tree
76+
),
77+
Assign(gen.mkAttributedStableRef(fieldSym.owner.thisType, fieldSym), gen.mkZero(fieldSym.info))
78+
)
79+
}
80+
val asyncState = asyncBlock.asyncStates.find(_.state == state).get
81+
asyncState.stats = assigns ++ asyncState.stats
82+
}
83+
6084
def startStateMachine: Tree = {
6185
val stateMachineSpliced: Tree = spliceMethodBodies(
62-
liftables(asyncBlock.asyncStates),
86+
liftedFields,
6387
stateMachine,
6488
atMacroPos(asyncBlock.onCompleteHandler[T]),
6589
atMacroPos(asyncBlock.resumeFunTree[T].rhs)
@@ -96,9 +120,16 @@ trait AsyncTransform {
96120
states foreach (s => AsyncUtils.vprintln(s))
97121
}
98122

99-
def spliceMethodBodies(liftables: List[Tree], tree: Tree, applyBody: Tree,
100-
resumeBody: Tree): Tree = {
101-
123+
/**
124+
* Build final `ClassDef` tree of state machine class.
125+
*
126+
* @param liftables trees of definitions that are lifted to fields of the state machine class
127+
* @param tree `ClassDef` tree of the state machine class
128+
* @param applyBody tree of onComplete handler (`apply` method)
129+
* @param resumeBody RHS of definition tree of `resume` method
130+
* @return transformed `ClassDef` tree of the state machine class
131+
*/
132+
def spliceMethodBodies(liftables: List[Tree], tree: ClassDef, applyBody: Tree, resumeBody: Tree): Tree = {
102133
val liftedSyms = liftables.map(_.symbol).toSet
103134
val stateMachineClass = tree.symbol
104135
liftedSyms.foreach {
@@ -112,7 +143,7 @@ trait AsyncTransform {
112143
// Replace the ValDefs in the splicee with Assigns to the corresponding lifted
113144
// fields. Similarly, replace references to them with references to the field.
114145
//
115-
// This transform will be only be run on the RHS of `def foo`.
146+
// This transform will only be run on the RHS of `def foo`.
116147
class UseFields extends MacroTypingTransformer {
117148
override def transform(tree: Tree): Tree = tree match {
118149
case _ if currentOwner == stateMachineClass =>
@@ -150,6 +181,7 @@ trait AsyncTransform {
150181
}
151182
val treeSubst = tree
152183

184+
/* Fixes up DefDef: use lifted fields in `body` */
153185
def fixup(dd: DefDef, body: Tree, ctx: analyzer.Context): Tree = {
154186
val spliceeAnfFixedOwnerSyms = body
155187
val useField = new UseFields()
@@ -171,6 +203,7 @@ trait AsyncTransform {
171203
(ctx: analyzer.Context) =>
172204
val typedTree = fixup(dd, changeOwner(applyBody, callSiteTyper.context.owner, dd.symbol), ctx)
173205
typedTree
206+
174207
case dd@DefDef(_, name.resume, _, _, _, _) if dd.symbol.owner == stateMachineClass =>
175208
(ctx: analyzer.Context) =>
176209
val changed = changeOwner(resumeBody, callSiteTyper.context.owner, dd.symbol)

src/main/scala/scala/async/internal/ExprBuilder.scala

+16-8
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ trait ExprBuilder {
2525
trait AsyncState {
2626
def state: Int
2727

28+
def nextStates: List[Int]
29+
2830
def mkHandlerCaseForState: CaseDef
2931

3032
def mkOnCompleteHandler[T: WeakTypeTag]: Option[CaseDef] = None
3133

32-
def stats: List[Tree]
34+
var stats: List[Tree]
3335

3436
final def allStats: List[Tree] = this match {
3537
case a: AsyncStateWithAwait => stats :+ a.awaitable.resultValDef
@@ -43,9 +45,12 @@ trait ExprBuilder {
4345
}
4446

4547
/** A sequence of statements that concludes with a unconditional transition to `nextState` */
46-
final class SimpleAsyncState(val stats: List[Tree], val state: Int, nextState: Int, symLookup: SymLookup)
48+
final class SimpleAsyncState(var stats: List[Tree], val state: Int, nextState: Int, symLookup: SymLookup)
4749
extends AsyncState {
4850

51+
def nextStates: List[Int] =
52+
List(nextState)
53+
4954
def mkHandlerCaseForState: CaseDef =
5055
mkHandlerCase(state, stats :+ mkStateTree(nextState, symLookup) :+ mkResumeApply(symLookup))
5156

@@ -56,21 +61,24 @@ trait ExprBuilder {
5661
/** A sequence of statements with a conditional transition to the next state, which will represent
5762
* a branch of an `if` or a `match`.
5863
*/
59-
final class AsyncStateWithoutAwait(val stats: List[Tree], val state: Int) extends AsyncState {
64+
final class AsyncStateWithoutAwait(var stats: List[Tree], val state: Int, val nextStates: List[Int]) extends AsyncState {
6065
override def mkHandlerCaseForState: CaseDef =
6166
mkHandlerCase(state, stats)
6267

6368
override val toString: String =
64-
s"AsyncStateWithoutAwait #$state"
69+
s"AsyncStateWithoutAwait #$state, nextStates = $nextStates"
6570
}
6671

6772
/** A sequence of statements that concludes with an `await` call. The `onComplete`
6873
* handler will unconditionally transition to `nextState`.
6974
*/
70-
final class AsyncStateWithAwait(val stats: List[Tree], val state: Int, nextState: Int,
75+
final class AsyncStateWithAwait(var stats: List[Tree], val state: Int, nextState: Int,
7176
val awaitable: Awaitable, symLookup: SymLookup)
7277
extends AsyncState {
7378

79+
def nextStates: List[Int] =
80+
List(nextState)
81+
7482
override def mkHandlerCaseForState: CaseDef = {
7583
val callOnComplete = futureSystemOps.onComplete(Expr(awaitable.expr),
7684
Expr(This(tpnme.EMPTY)), Expr(Ident(name.execContext))).tree
@@ -147,7 +155,7 @@ trait ExprBuilder {
147155
def resultWithIf(condTree: Tree, thenState: Int, elseState: Int): AsyncState = {
148156
def mkBranch(state: Int) = Block(mkStateTree(state, symLookup) :: Nil, mkResumeApply(symLookup))
149157
this += If(condTree, mkBranch(thenState), mkBranch(elseState))
150-
new AsyncStateWithoutAwait(stats.toList, state)
158+
new AsyncStateWithoutAwait(stats.toList, state, List(thenState, elseState))
151159
}
152160

153161
/**
@@ -169,12 +177,12 @@ trait ExprBuilder {
169177
}
170178
// 2. insert changed match tree at the end of the current state
171179
this += Match(scrutTree, newCases)
172-
new AsyncStateWithoutAwait(stats.toList, state)
180+
new AsyncStateWithoutAwait(stats.toList, state, caseStates)
173181
}
174182

175183
def resultWithLabel(startLabelState: Int, symLookup: SymLookup): AsyncState = {
176184
this += Block(mkStateTree(startLabelState, symLookup) :: Nil, mkResumeApply(symLookup))
177-
new AsyncStateWithoutAwait(stats.toList, state)
185+
new AsyncStateWithoutAwait(stats.toList, state, List(startLabelState))
178186
}
179187

180188
override def toString: String = {

0 commit comments

Comments
 (0)