Skip to content

Commit db34814

Browse files
authored
Merge pull request #14205 from som-snytt/issue/14198
Clear temp var for captured var expr to permit GC
2 parents 1e7ad75 + b93b940 commit db34814

File tree

4 files changed

+123
-29
lines changed

4 files changed

+123
-29
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
206206
def ValDef(sym: TermSymbol, rhs: LazyTree = EmptyTree)(using Context): ValDef =
207207
ta.assignType(untpd.ValDef(sym.name, TypeTree(sym.info), rhs), sym)
208208

209-
def SyntheticValDef(name: TermName, rhs: Tree)(using Context): ValDef =
210-
ValDef(newSymbol(ctx.owner, name, Synthetic, rhs.tpe.widen, coord = rhs.span), rhs)
209+
def SyntheticValDef(name: TermName, rhs: Tree, flags: FlagSet = EmptyFlags)(using Context): ValDef =
210+
ValDef(newSymbol(ctx.owner, name, Synthetic | flags, rhs.tpe.widen, coord = rhs.span), rhs)
211211

212212
def DefDef(sym: TermSymbol, paramss: List[List[Symbol]],
213213
resultType: Type, rhs: Tree)(using Context): DefDef =

compiler/src/dotty/tools/dotc/transform/CapturedVars.scala

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ import core.Decorators._
1010
import core.StdNames.nme
1111
import core.Names._
1212
import core.NameKinds.TempResultName
13+
import core.Constants._
1314
import ast.Trees._
1415
import util.Store
1516
import collection.mutable
1617

1718
/** This phase translates variables that are captured in closures to
1819
* heap-allocated refs.
1920
*/
20-
class CapturedVars extends MiniPhase with IdentityDenotTransformer { thisPhase =>
21+
class CapturedVars extends MiniPhase with IdentityDenotTransformer:
22+
thisPhase =>
2123
import ast.tpd._
2224

2325
/** the following two members override abstract members in Transform */
@@ -45,6 +47,9 @@ class CapturedVars extends MiniPhase with IdentityDenotTransformer { thisPhase =
4547

4648
val boxedRefClasses: collection.Set[Symbol] =
4749
refClassKeys.flatMap(k => Set(refClass(k), volatileRefClass(k)))
50+
51+
val objectRefClasses: collection.Set[Symbol] =
52+
Set(refClass(defn.ObjectClass), volatileRefClass(defn.ObjectClass))
4853
}
4954

5055
private var myRefInfo: RefInfo = null
@@ -123,32 +128,42 @@ class CapturedVars extends MiniPhase with IdentityDenotTransformer { thisPhase =
123128
}
124129

125130
/** If assignment is to a boxed ref type, e.g.
126-
*
127-
* intRef.elem = expr
128-
*
129-
* rewrite using a temporary var to
130-
*
131-
* val ev$n = expr
132-
* intRef.elem = ev$n
133-
*
134-
* That way, we avoid the problem that `expr` might contain a `try` that would
135-
* run on a non-empty stack (which is illegal under JVM rules). Note that LiftTry
136-
* has already run before, so such `try`s would not be eliminated.
137-
*
138-
* Also: If the ref type lhs is followed by a cast (can be an artifact of nested translation),
139-
* drop the cast.
140-
*/
141-
override def transformAssign(tree: Assign)(using Context): Tree = {
142-
def recur(lhs: Tree): Tree = lhs match {
143-
case TypeApply(Select(qual, nme.asInstanceOf_), _) =>
144-
val Select(_, nme.elem) = qual
131+
*
132+
* intRef.elem = expr
133+
*
134+
* rewrite using a temporary var to
135+
*
136+
* val ev$n = expr
137+
* intRef.elem = ev$n
138+
*
139+
* That way, we avoid the problem that `expr` might contain a `try` that would
140+
* run on a non-empty stack (which is illegal under JVM rules). Note that LiftTry
141+
* has already run before, so such `try`s would not be eliminated.
142+
*
143+
* If the ref type lhs is followed by a cast (can be an artifact of nested translation),
144+
* drop the cast.
145+
*
146+
* If the ref type is `ObjectRef` or `VolatileObjectRef`, immediately assign `null`
147+
* to the temporary to make the underlying target of the reference available for
148+
* garbage collection. Nullification is omitted if the `expr` is already `null`.
149+
*
150+
* var ev$n: RHS = expr
151+
* objRef.elem = ev$n
152+
* ev$n = null.asInstanceOf[RHS]
153+
*/
154+
override def transformAssign(tree: Assign)(using Context): Tree =
155+
def absolved: Boolean = tree.rhs match
156+
case Literal(Constant(null)) | Typed(Literal(Constant(null)), _) => true
157+
case _ => false
158+
def recur(lhs: Tree): Tree = lhs match
159+
case TypeApply(Select(qual@Select(_, nme.elem), nme.asInstanceOf_), _) =>
145160
recur(qual)
146161
case Select(_, nme.elem) if refInfo.boxedRefClasses.contains(lhs.symbol.maybeOwner) =>
147-
val tempDef = transformFollowing(SyntheticValDef(TempResultName.fresh(), tree.rhs))
148-
transformFollowing(Block(tempDef :: Nil, cpy.Assign(tree)(lhs, ref(tempDef.symbol))))
162+
val tempDef = transformFollowing(SyntheticValDef(TempResultName.fresh(), tree.rhs, flags = Mutable))
163+
val update = cpy.Assign(tree)(lhs, ref(tempDef.symbol))
164+
def reset = cpy.Assign(tree)(ref(tempDef.symbol), nullLiteral.cast(tempDef.symbol.info))
165+
val res = if refInfo.objectRefClasses(lhs.symbol.maybeOwner) && !absolved then reset else unitLiteral
166+
transformFollowing(Block(tempDef :: update :: Nil, res))
149167
case _ =>
150168
tree
151-
}
152169
recur(tree.lhs)
153-
}
154-
}

tests/run/i14198.scala

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
2+
import java.lang.ref.WeakReference
3+
import java.util.concurrent.atomic.AtomicReference
4+
5+
final class Mark
6+
7+
object Test:
8+
9+
def main(args: Array[String]): Unit =
10+
myTest
11+
trying
12+
13+
final def myAssert(cond: => Boolean): Unit = assert(cond)
14+
15+
def terminally(cond: => Boolean): Unit =
16+
System.gc()
17+
var n = 10
18+
while (n > 0 && !cond)
19+
do
20+
System.gc()
21+
Thread.`yield`()
22+
//print(".")
23+
n -= 1
24+
assert(cond)
25+
26+
def myTest: Unit =
27+
val ref = new AtomicReference[WeakReference[AnyRef]]
28+
var mark: AnyRef = null
29+
assert(ref.compareAndSet(null, WeakReference(Mark())))
30+
mark = ref.get().get()
31+
myAssert(mark ne null) // in theory this could fail, but it isn't
32+
mark = null
33+
terminally(ref.get().get() == null)
34+
35+
def trying: Unit =
36+
def ignore[A]: (Throwable => A) = _ => null.asInstanceOf[A]
37+
var i: Int = 21
38+
var s: String = "hello"
39+
var r: WeakReference[String] = null
40+
def f(n: => Int) = n + n + 1
41+
def g(x: => String) =
42+
r = WeakReference(x + "/" + x)
43+
r.get()
44+
i = try f(i) catch ignore
45+
s = try g(s) catch ignore
46+
assert(s == "hello/hello")
47+
assert(r.get() == "hello/hello")
48+
s = null
49+
terminally(r.get() == null)
50+
s = "bye"
51+
s = try g(s) catch ignore
52+
assert(s == "bye/bye")
53+
assert(r.get() == "bye/bye")
54+
s = null.asInstanceOf[String]
55+
terminally(r.get() == null)
56+
@volatile var z: String = "whoa"
57+
z = try g(z) catch ignore
58+
assert(r.get() == "whoa/whoa")
59+
z = null
60+
terminally(r.get() == null)

tests/run/liftedTry.scala

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,34 @@ object Test {
1616
assert(x == 1)
1717
assert(foo(2) == 2)
1818
assert(foo(try raise(3) catch handle) == 3)
19-
Tr.foo
19+
assert(Tr.foo == 3)
2020
}
2121
}
2222

2323
object Tr {
2424
def fun(a: Int => Unit) = a(2)
2525
def foo: Int = {
2626
var s = 1
27-
s = try {fun(s = _); 3} catch{ case ex: Throwable => val x = 4; s = x; 5 }
27+
s = try {fun(s = _); 3} catch { case ex: Throwable => val x = 4; s = x; 5 }
2828
s
2929
}
3030
}
31+
32+
/* was:
33+
Caused by: java.lang.VerifyError: Inconsistent stackmap frames at branch target 33
34+
Exception Details:
35+
Location:
36+
Tr$.foo()I @30: goto
37+
Reason:
38+
Current frame's stack size doesn't match stackmap.
39+
Current Frame:
40+
bci: @30
41+
flags: { }
42+
locals: { 'Tr$', 'scala/runtime/IntRef', 'java/lang/Throwable', integer }
43+
stack: { integer }
44+
Stackmap Frame:
45+
bci: @33
46+
flags: { }
47+
locals: { 'Tr$', 'scala/runtime/IntRef' }
48+
stack: { top, integer }
49+
*/

0 commit comments

Comments
 (0)