Skip to content

Commit 6615cdd

Browse files
committed
Fix #5340: Improve runtime implementation of local lazy vals
Reduce the amount of code generated for local lazy val by moving some of the generated code to the runtime library. This is a port of scala/scala#5374
1 parent 39bb8bd commit 6615cdd

File tree

4 files changed

+94
-87
lines changed

4 files changed

+94
-87
lines changed

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

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -129,50 +129,69 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
129129
Thicket(field, getter)
130130
}
131131

132-
/** Replace a local lazy val inside a method,
133-
* with a LazyHolder from
134-
* dotty.runtime(eg dotty.runtime.LazyInt)
135-
*/
132+
/** Desugar a local `lazy val x: Int = <RHS>` into:
133+
*
134+
* ```
135+
* val x$lzy = new scala.runtime.LazyInt()
136+
*
137+
* def x$lzycompute(): Int = x$lzy.synchronized {
138+
* if (x$lzy.initialized()) x$lzy.value()
139+
* else x$lzy.initialize(<RHS>)
140+
* // for a Unit-typed lazy val, this becomes `{ rhs ; x$lzy.initialize() }`
141+
* // to avoid passing around BoxedUnit
142+
* }
143+
*
144+
* def x(): Int = if (x$lzy.initialized()) x$lzy.value() else x$lzycompute()
145+
* ```
146+
*
147+
* TODO: Implement Unit-typed lazy val optimization
148+
*/
136149
def transformLocalDef(x: ValOrDefDef)(implicit ctx: Context): Thicket = {
137-
val valueInitter = x.rhs
138-
val xname = x.name.asTermName
139-
val holderName = LazyLocalName.fresh(xname)
140-
val initName = LazyLocalInitName.fresh(xname)
141-
val tpe = x.tpe.widen.resultType.widen
142-
143-
val holderType =
144-
if (tpe isRef defn.IntClass) "LazyInt"
145-
else if (tpe isRef defn.LongClass) "LazyLong"
146-
else if (tpe isRef defn.BooleanClass) "LazyBoolean"
147-
else if (tpe isRef defn.FloatClass) "LazyFloat"
148-
else if (tpe isRef defn.DoubleClass) "LazyDouble"
149-
else if (tpe isRef defn.ByteClass) "LazyByte"
150-
else if (tpe isRef defn.CharClass) "LazyChar"
151-
else if (tpe isRef defn.ShortClass) "LazyShort"
152-
else "LazyRef"
153-
154-
155-
val holderImpl = ctx.requiredClass("dotty.runtime." + holderType)
156-
157-
val holderSymbol = ctx.newSymbol(x.symbol.owner, holderName, containerFlags, holderImpl.typeRef, coord = x.pos)
158-
val initSymbol = ctx.newSymbol(x.symbol.owner, initName, initFlags, MethodType(Nil, tpe), coord = x.pos)
159-
val result = ref(holderSymbol).select(lazyNme.value).withPos(x.pos)
160-
val flag = ref(holderSymbol).select(lazyNme.initialized)
161-
val initer = valueInitter.changeOwnerAfter(x.symbol, initSymbol, this)
162-
val initBody =
163-
adaptToType(
164-
ref(holderSymbol).select(defn.Object_synchronized).appliedTo(
165-
adaptToType(mkNonThreadSafeDef(result, flag, initer, nullables = Nil), defn.ObjectType)),
166-
tpe)
167-
val initTree = DefDef(initSymbol, initBody)
168-
val holderTree = ValDef(holderSymbol, New(holderImpl.typeRef, List()))
169-
val methodBody = tpd.If(flag.ensureApplied,
170-
result.ensureApplied,
171-
ref(initSymbol).ensureApplied).ensureConforms(tpe)
172-
173-
val methodTree = DefDef(x.symbol.asTerm, methodBody)
174-
ctx.debuglog(s"found a lazy val ${x.show},\nrewrote with ${holderTree.show}")
175-
Thicket(holderTree, initTree, methodTree)
150+
val xname = x.name.asTermName
151+
val holderName = LazyLocalName.fresh(xname)
152+
val initName = LazyLocalInitName.fresh(xname)
153+
val tpe = x.tpe.widen.resultType.widen
154+
155+
val holderType =
156+
if (tpe isRef defn.IntClass) "LazyInt"
157+
else if (tpe isRef defn.LongClass) "LazyLong"
158+
else if (tpe isRef defn.BooleanClass) "LazyBoolean"
159+
else if (tpe isRef defn.FloatClass) "LazyFloat"
160+
else if (tpe isRef defn.DoubleClass) "LazyDouble"
161+
else if (tpe isRef defn.ByteClass) "LazyByte"
162+
else if (tpe isRef defn.CharClass) "LazyChar"
163+
else if (tpe isRef defn.ShortClass) "LazyShort"
164+
else "LazyRef"
165+
166+
// val x$lzy = new scala.runtime.LazyInt()
167+
val holderImpl = ctx.requiredClass("scala.runtime." + holderType)
168+
val holderSymbol = ctx.newSymbol(x.symbol.owner, holderName, containerFlags, holderImpl.typeRef, coord = x.pos)
169+
val holderTree = ValDef(holderSymbol, New(holderImpl.typeRef, List()))
170+
171+
val holderRef = ref(holderSymbol)
172+
val getValue = holderRef.select(lazyNme.value).ensureApplied.withPos(x.pos)
173+
val initialized = holderRef.select(lazyNme.initialized).ensureApplied
174+
175+
// def x$lzycompute(): Int = x$lzy.synchronized {
176+
// if (x$lzy.initialized()) x$lzy.value()
177+
// else x$lzy.initialize(<RHS>)
178+
// }
179+
val initSymbol = ctx.newSymbol(x.symbol.owner, initName, initFlags, MethodType(Nil, tpe), coord = x.pos)
180+
val rhs = x.rhs.changeOwnerAfter(x.symbol, initSymbol, this)
181+
val initialize = holderRef.select(lazyNme.initialize).appliedTo(rhs)
182+
val initBody =
183+
adaptToType(
184+
holderRef.select(defn.Object_synchronized).appliedTo(
185+
adaptToType(If(initialized, getValue, initialize), defn.ObjectType)),
186+
tpe)
187+
val initTree = DefDef(initSymbol, initBody)
188+
189+
// def x(): Int = if (x$lzy.initialized()) x$lzy.value() else x$lzycompute()
190+
val accessorBody = If(initialized, getValue, ref(initSymbol).ensureApplied).ensureConforms(tpe)
191+
val accessor = DefDef(x.symbol.asTerm, accessorBody)
192+
193+
ctx.debuglog(s"found a lazy val ${x.show},\nrewrote with ${holderTree.show}")
194+
Thicket(holderTree, initTree, accessor)
176195
}
177196

178197

@@ -458,6 +477,7 @@ object LazyVals {
458477
val result: TermName = "result".toTermName
459478
val value: TermName = "value".toTermName
460479
val initialized: TermName = "initialized".toTermName
480+
val initialize: TermName = "initialize".toTermName
461481
val retry: TermName = "retry".toTermName
462482
}
463483
}

library/src/dotty/runtime/LazyHolders.scala

Lines changed: 0 additions & 44 deletions
This file was deleted.

tests/run/i5340.check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Int
2+
Long
3+
Float
4+
Double
5+
Byte
6+
Char
7+
Short
8+
String

tests/run/i5340.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
object Test {
2+
def main(args: Array[String]): Unit = {
3+
lazy val a: Int = { println("Int"); 1 }
4+
lazy val b: Long = { println("Long"); 1L }
5+
lazy val c: Float = { println("Float"); 1F }
6+
lazy val d: Double = { println("Double"); 1.0 }
7+
lazy val e: Byte = { println("Byte"); 1 }
8+
lazy val f: Char = { println("Char"); '1' }
9+
lazy val g: Short = { println("Short"); 1 }
10+
lazy val h: String = { println("String"); "1" }
11+
lazy val i: Unit = { println("Unit"); () }
12+
13+
a
14+
b
15+
c
16+
d
17+
e
18+
f
19+
g
20+
h
21+
// i // FIXME: See #5350
22+
}
23+
}

0 commit comments

Comments
 (0)