Skip to content

Commit 3730250

Browse files
authored
Merge pull request #5478 from dotty-staging/lazy-vals-micro-opt
Micro optimise @volatile lazy vals
2 parents eb175cb + e8bd565 commit 3730250

File tree

3 files changed

+156
-172
lines changed

3 files changed

+156
-172
lines changed

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

Lines changed: 103 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
1-
package dotty.tools.dotc
2-
package transform
1+
package dotty.tools.dotc.transform
32

4-
import dotty.tools.dotc.core.Annotations.Annotation
3+
import java.util.IdentityHashMap
54

6-
import scala.collection.mutable
7-
import core._
8-
import Contexts._
9-
import Symbols._
10-
import Decorators._
11-
import NameKinds._
12-
import Types._
13-
import Flags.FlagSet
14-
import StdNames.nme
15-
import dotty.tools.dotc.transform.MegaPhase._
165
import dotty.tools.dotc.ast.tpd
6+
import dotty.tools.dotc.core.Annotations.Annotation
177
import dotty.tools.dotc.core.Constants.Constant
18-
import dotty.tools.dotc.core.Types.MethodType
19-
import SymUtils._
8+
import dotty.tools.dotc.core.Contexts.Context
9+
import dotty.tools.dotc.core.Decorators._
2010
import dotty.tools.dotc.core.DenotTransformers.IdentityDenotTransformer
21-
import Erasure.Boxing.adaptToType
11+
import dotty.tools.dotc.core.Flags._
12+
import dotty.tools.dotc.core.NameKinds.{LazyBitMapName, LazyLocalInitName, LazyLocalName}
13+
import dotty.tools.dotc.core.StdNames.nme
14+
import dotty.tools.dotc.core.Symbols._
15+
import dotty.tools.dotc.core.Types._
16+
import dotty.tools.dotc.core.{Names, StdNames}
17+
import dotty.tools.dotc.transform.MegaPhase.MiniPhase
18+
import dotty.tools.dotc.transform.SymUtils._
2219

23-
import java.util.IdentityHashMap
20+
import scala.collection.mutable
2421

2522
class LazyVals extends MiniPhase with IdentityDenotTransformer {
2623
import LazyVals._
@@ -41,10 +38,10 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
4138

4239
def transformer: LazyVals = new LazyVals
4340

44-
val containerFlags: FlagSet = Flags.Synthetic | Flags.Mutable | Flags.Lazy
45-
val initFlags: FlagSet = Flags.Synthetic | Flags.Method
41+
val containerFlags: FlagSet = Synthetic | Mutable | Lazy
42+
val initFlags: FlagSet = Synthetic | Method
4643

47-
val containerFlagsMask: FlagSet = Flags.Method | Flags.Lazy | Flags.Accessor | Flags.Module
44+
val containerFlagsMask: FlagSet = Method | Lazy | Accessor | Module
4845

4946
/** A map of lazy values to the fields they should null after initialization. */
5047
private[this] var lazyValNullables: IdentityHashMap[Symbol, mutable.ListBuffer[Symbol]] = _
@@ -72,22 +69,22 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
7269

7370
def transformLazyVal(tree: ValOrDefDef)(implicit ctx: Context): Tree = {
7471
val sym = tree.symbol
75-
if (!(sym is Flags.Lazy) ||
76-
sym.owner.is(Flags.Trait) || // val is accessor, lazy field will be implemented in subclass
77-
(sym.isStatic && sym.is(Flags.Module, butNot = Flags.Method))) // static module vals are implemented in the JVM by lazy loading
72+
if (!(sym is Lazy) ||
73+
sym.owner.is(Trait) || // val is accessor, lazy field will be implemented in subclass
74+
(sym.isStatic && sym.is(Module, butNot = Method))) // static module vals are implemented in the JVM by lazy loading
7875
tree
7976
else {
8077
val isField = sym.owner.isClass
8178
if (isField) {
8279
if (sym.isVolatile ||
83-
(sym.is(Flags.Module)/* || ctx.scala2Mode*/) &&
80+
(sym.is(Module)/* || ctx.scala2Mode*/) &&
8481
// TODO assume @volatile once LazyVals uses static helper constructs instead of
8582
// ones in the companion object.
86-
!sym.is(Flags.Synthetic))
83+
!sym.is(Synthetic))
8784
// module class is user-defined.
8885
// Should be threadsafe, to mimic safety guaranteed by global object
8986
transformMemberDefVolatile(tree)
90-
else if (sym.is(Flags.Module)) // synthetic module
87+
else if (sym.is(Module)) // synthetic module
9188
transformSyntheticModule(tree)
9289
else
9390
transformMemberDefNonVolatile(tree)
@@ -123,7 +120,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
123120
def transformSyntheticModule(tree: ValOrDefDef)(implicit ctx: Context): Thicket = {
124121
val sym = tree.symbol
125122
val holderSymbol = ctx.newSymbol(sym.owner, LazyLocalName.fresh(sym.asTerm.name),
126-
Flags.Synthetic, sym.info.widen.resultType).enteredAfter(this)
123+
Synthetic, sym.info.widen.resultType).enteredAfter(this)
127124
val field = ValDef(holderSymbol, tree.rhs.changeOwnerAfter(sym, holderSymbol, this))
128125
val getter = DefDef(sym.asTerm, ref(holderSymbol))
129126
Thicket(field, getter)
@@ -187,8 +184,8 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
187184
// need to bring containers to start of method
188185
val (holders, stats) =
189186
trees.partition {
190-
_.symbol.flags.&~(Flags.Touched) == containerFlags
191-
// Filtering out Flags.Touched is not required currently, as there are no LazyTypes involved here
187+
_.symbol.flags.&~(Touched) == containerFlags
188+
// Filtering out Touched is not required currently, as there are no LazyTypes involved here
192189
// but just to be more safe
193190
}
194191
holders:::stats
@@ -198,7 +195,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
198195
val nullConst = Literal(Constant(null))
199196
nullables.map { field =>
200197
assert(field.isField)
201-
field.setFlag(Flags.Mutable)
198+
field.setFlag(Mutable)
202199
ref(field).becomes(nullConst)
203200
}
204201
}
@@ -252,10 +249,10 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
252249
def transformMemberDefNonVolatile(x: ValOrDefDef)(implicit ctx: Context): Thicket = {
253250
val claz = x.symbol.owner.asClass
254251
val tpe = x.tpe.widen.resultType.widen
255-
assert(!(x.symbol is Flags.Mutable))
252+
assert(!(x.symbol is Mutable))
256253
val containerName = LazyLocalName.fresh(x.name.asTermName)
257254
val containerSymbol = ctx.newSymbol(claz, containerName,
258-
x.symbol.flags &~ containerFlagsMask | containerFlags | Flags.Private,
255+
x.symbol.flags &~ containerFlagsMask | containerFlags | Private,
259256
tpe, coord = x.symbol.coord
260257
).enteredAfter(this)
261258

@@ -266,7 +263,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
266263
}
267264
else {
268265
val flagName = LazyBitMapName.fresh(x.name.asTermName)
269-
val flagSymbol = ctx.newSymbol(x.symbol.owner, flagName, containerFlags | Flags.Private, defn.BooleanType).enteredAfter(this)
266+
val flagSymbol = ctx.newSymbol(x.symbol.owner, flagName, containerFlags | Private, defn.BooleanType).enteredAfter(this)
270267
val flag = ValDef(flagSymbol, Literal(Constant(false)))
271268
Thicket(containerTree, flag, mkNonThreadSafeDef(x.symbol, flagSymbol, containerSymbol, x.rhs))
272269
}
@@ -275,34 +272,31 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
275272
/** Create a threadsafe lazy accessor equivalent to such code
276273
* ```
277274
* def methodSymbol(): Int = {
278-
* val result: Int = 0
279-
* val retry: Boolean = true
280-
* var flag: Long = 0L
281-
* while retry do {
282-
* flag = dotty.runtime.LazyVals.get(this, $claz.$OFFSET)
283-
* dotty.runtime.LazyVals.STATE(flag, 0) match {
284-
* case 0 =>
285-
* if dotty.runtime.LazyVals.CAS(this, $claz.$OFFSET, flag, 1, $ord) {
286-
* try {result = rhs} catch {
287-
* case x: Throwable =>
288-
* dotty.runtime.LazyVals.setFlag(this, $claz.$OFFSET, 0, $ord)
289-
* throw x
290-
* }
291-
* $target = result
292-
* dotty.runtime.LazyVals.setFlag(this, $claz.$OFFSET, 3, $ord)
293-
* retry = false
294-
* }
295-
* case 1 =>
296-
* dotty.runtime.LazyVals.wait4Notification(this, $claz.$OFFSET, flag, $ord)
297-
* case 2 =>
298-
* dotty.runtime.LazyVals.wait4Notification(this, $claz.$OFFSET, flag, $ord)
299-
* case 3 =>
300-
* retry = false
301-
* result = $target
275+
* while (true) {
276+
* val flag = LazyVals.get(this, bitmap_offset)
277+
* val state = LazyVals.STATE(flag, <field-id>)
278+
*
279+
* if (state == <state-3>) {
280+
* return value_0
281+
* } else if (state == <state-0>) {
282+
* if (LazyVals.CAS(this, bitmap_offset, flag, <state-1>, <field-id>)) {
283+
* try {
284+
* val result = <RHS>
285+
* value_0 = result
286+
* nullable = null
287+
* LazyVals.setFlag(this, bitmap_offset, <state-3>, <field-id>)
288+
* return result
289+
* }
290+
* catch {
291+
* case ex =>
292+
* LazyVals.setFlag(this, bitmap_offset, <state-0>, <field-id>)
293+
* throw ex
294+
* }
302295
* }
296+
* } else /* if (state == <state-1> || state == <state-2>) */ {
297+
* LazyVals.wait4Notification(this, bitmap_offset, flag, <field-id>)
303298
* }
304-
* nullable = null
305-
* result
299+
* }
306300
* }
307301
* ```
308302
*/
@@ -317,77 +311,66 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
317311
stateMask: Tree,
318312
casFlag: Tree,
319313
setFlagState: Tree,
320-
waitOnLock: Tree,
321-
nullables: List[Symbol])(implicit ctx: Context): DefDef = {
314+
waitOnLock: Tree)(implicit ctx: Context): DefDef = {
322315
val initState = Literal(Constant(0))
323316
val computeState = Literal(Constant(1))
324-
val notifyState = Literal(Constant(2))
325317
val computedState = Literal(Constant(3))
326-
val flagSymbol = ctx.newSymbol(methodSymbol, lazyNme.flag, containerFlags, defn.LongType)
327-
val flagDef = ValDef(flagSymbol, Literal(Constant(0L)))
328318

329-
val thiz = This(claz)(ctx.fresh.setOwner(claz))
319+
val thiz = This(claz)
320+
val fieldId = Literal(Constant(ord))
330321

331-
val resultSymbol = ctx.newSymbol(methodSymbol, lazyNme.result, containerFlags, tp)
332-
val resultDef = ValDef(resultSymbol, defaultValue(tp))
322+
val flagSymbol = ctx.newSymbol(methodSymbol, lazyNme.flag, Synthetic, defn.LongType)
323+
val flagDef = ValDef(flagSymbol, getFlag.appliedTo(thiz, offset))
324+
val flagRef = ref(flagSymbol)
333325

334-
val retrySymbol = ctx.newSymbol(methodSymbol, lazyNme.retry, containerFlags, defn.BooleanType)
335-
val retryDef = ValDef(retrySymbol, Literal(Constant(true)))
336-
337-
val whileCond = ref(retrySymbol)
326+
val stateSymbol = ctx.newSymbol(methodSymbol, lazyNme.state, Synthetic, defn.LongType)
327+
val stateDef = ValDef(stateSymbol, stateMask.appliedTo(ref(flagSymbol), Literal(Constant(ord))))
328+
val stateRef = ref(stateSymbol)
338329

339330
val compute = {
340-
val handlerSymbol = ctx.newSymbol(methodSymbol, nme.ANON_FUN, Flags.Synthetic,
341-
MethodType(List(nme.x_1), List(defn.ThrowableType), defn.IntType))
342-
val caseSymbol = ctx.newSymbol(methodSymbol, nme.DEFAULT_EXCEPTION_NAME, Flags.Synthetic, defn.ThrowableType)
343-
val triggerRetry = setFlagState.appliedTo(thiz, offset, initState, Literal(Constant(ord)))
344-
val complete = setFlagState.appliedTo(thiz, offset, computedState, Literal(Constant(ord)))
345-
346-
val handler = CaseDef(Bind(caseSymbol, ref(caseSymbol)), EmptyTree,
347-
Block(List(triggerRetry), Throw(ref(caseSymbol))
348-
))
349-
350-
val compute = ref(resultSymbol).becomes(rhs)
351-
val tr = Try(compute, List(handler), EmptyTree)
352-
val assign = ref(target).becomes(ref(resultSymbol))
353-
val noRetry = ref(retrySymbol).becomes(Literal(Constant(false)))
354-
val body = If(casFlag.appliedTo(thiz, offset, ref(flagSymbol), computeState, Literal(Constant(ord))),
355-
Block(tr :: assign :: complete :: noRetry :: Nil, Literal(Constant(()))),
356-
Literal(Constant(())))
357-
358-
CaseDef(initState, EmptyTree, body)
359-
}
360-
361-
val waitFirst = {
362-
val wait = waitOnLock.appliedTo(thiz, offset, ref(flagSymbol), Literal(Constant(ord)))
363-
CaseDef(computeState, EmptyTree, wait)
364-
}
365-
366-
val waitSecond = {
367-
val wait = waitOnLock.appliedTo(thiz, offset, ref(flagSymbol), Literal(Constant(ord)))
368-
CaseDef(notifyState, EmptyTree, wait)
331+
val resultSymbol = ctx.newSymbol(methodSymbol, lazyNme.result, Synthetic, tp)
332+
val resultRef = ref(resultSymbol)
333+
val stats = (
334+
ValDef(resultSymbol, rhs) ::
335+
ref(target).becomes(resultRef) ::
336+
(nullOut(nullableFor(methodSymbol)) :+
337+
setFlagState.appliedTo(thiz, offset, computedState, fieldId))
338+
)
339+
Block(stats, Return(resultRef, methodSymbol))
369340
}
370341

371-
val computed = {
372-
val noRetry = ref(retrySymbol).becomes(Literal(Constant(false)))
373-
val result = ref(resultSymbol).becomes(ref(target))
374-
val body = Block(noRetry :: result :: Nil, Literal(Constant(())))
375-
CaseDef(computedState, EmptyTree, body)
342+
val retryCase = {
343+
val caseSymbol = ctx.newSymbol(methodSymbol, nme.DEFAULT_EXCEPTION_NAME, Synthetic, defn.ThrowableType)
344+
val triggerRetry = setFlagState.appliedTo(thiz, offset, initState, fieldId)
345+
CaseDef(
346+
Bind(caseSymbol, ref(caseSymbol)),
347+
EmptyTree,
348+
Block(List(triggerRetry), Throw(ref(caseSymbol)))
349+
)
376350
}
377351

378-
val default = CaseDef(Underscore(defn.LongType), EmptyTree, Literal(Constant(())))
352+
val initialize = If(
353+
casFlag.appliedTo(thiz, offset, flagRef, computeState, fieldId),
354+
Try(compute, List(retryCase), EmptyTree),
355+
unitLiteral
356+
)
379357

380-
val cases = Match(stateMask.appliedTo(ref(flagSymbol), Literal(Constant(ord))),
381-
List(compute, waitFirst, waitSecond, computed, default)) //todo: annotate with @switch
358+
val condition = If(
359+
stateRef.equal(computedState),
360+
Return(ref(target), methodSymbol),
361+
If(
362+
stateRef.equal(initState),
363+
initialize,
364+
waitOnLock.appliedTo(thiz, offset, flagRef, fieldId)
365+
)
366+
)
382367

383-
val whileBody = Block(ref(flagSymbol).becomes(getFlag.appliedTo(thiz, offset)) :: Nil, cases)
384-
val cycle = WhileDo(whileCond, whileBody)
385-
val setNullables = nullOut(nullables)
386-
DefDef(methodSymbol, Block(resultDef :: retryDef :: flagDef :: cycle :: setNullables, ref(resultSymbol)))
368+
val loop = WhileDo(EmptyTree, Block(List(flagDef, stateDef), condition))
369+
DefDef(methodSymbol, loop)
387370
}
388371

389372
def transformMemberDefVolatile(x: ValOrDefDef)(implicit ctx: Context): Thicket = {
390-
assert(!(x.symbol is Flags.Mutable))
373+
assert(!(x.symbol is Mutable))
391374

392375
val tpe = x.tpe.widen.resultType.widen
393376
val claz = x.symbol.owner.asClass
@@ -398,9 +381,9 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
398381
var flag: Tree = EmptyTree
399382
var ord = 0
400383

401-
def offsetName(id: Int) = (StdNames.nme.LAZY_FIELD_OFFSET + (if(x.symbol.owner.is(Flags.Module)) "_m_" else "") + id.toString).toTermName
384+
def offsetName(id: Int) = (StdNames.nme.LAZY_FIELD_OFFSET + (if (x.symbol.owner.is(Module)) "_m_" else "") + id.toString).toTermName
402385

403-
// compute or create appropriate offsetSymol, bitmap and bits used by current ValDef
386+
// compute or create appropriate offsetSymbol, bitmap and bits used by current ValDef
404387
appendOffsetDefs.get(claz) match {
405388
case Some(info) =>
406389
val flagsPerLong = (64 / dotty.runtime.LazyVals.BITS_PER_LAZY_VAL).toInt
@@ -410,10 +393,10 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
410393
val offsetById = offsetName(id)
411394
if (ord != 0) { // there are unused bits in already existing flag
412395
offsetSymbol = claz.info.decl(offsetById)
413-
.suchThat(sym => (sym is Flags.Synthetic) && sym.isTerm)
396+
.suchThat(sym => (sym is Synthetic) && sym.isTerm)
414397
.symbol.asTerm
415398
} else { // need to create a new flag
416-
offsetSymbol = ctx.newSymbol(claz, offsetById, Flags.Synthetic, defn.LongType).enteredAfter(this)
399+
offsetSymbol = ctx.newSymbol(claz, offsetById, Synthetic, defn.LongType).enteredAfter(this)
417400
offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot))
418401
val flagName = (StdNames.nme.BITMAP_PREFIX + id.toString).toTermName
419402
val flagSymbol = ctx.newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this)
@@ -423,7 +406,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
423406
}
424407

425408
case None =>
426-
offsetSymbol = ctx.newSymbol(claz, offsetName(0), Flags.Synthetic, defn.LongType).enteredAfter(this)
409+
offsetSymbol = ctx.newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this)
427410
offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot))
428411
val flagName = (StdNames.nme.BITMAP_PREFIX + "0").toTermName
429412
val flagSymbol = ctx.newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this)
@@ -443,9 +426,8 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
443426
val wait = Select(ref(helperModule), lazyNme.RLazyVals.wait4Notification)
444427
val state = Select(ref(helperModule), lazyNme.RLazyVals.state)
445428
val cas = Select(ref(helperModule), lazyNme.RLazyVals.cas)
446-
val nullables = nullableFor(x.symbol)
447429

448-
val accessor = mkThreadSafeDef(x.symbol.asTerm, claz, ord, containerSymbol, x.rhs, tpe, offset, getFlag, state, cas, setFlag, wait, nullables)
430+
val accessor = mkThreadSafeDef(x.symbol.asTerm, claz, ord, containerSymbol, x.rhs, tpe, offset, getFlag, state, cas, setFlag, wait)
449431
if (flag eq EmptyTree)
450432
Thicket(containerTree, accessor)
451433
else Thicket(containerTree, flag, accessor)
@@ -465,6 +447,7 @@ object LazyVals {
465447
val getOffset: TermName = N.getOffset.toTermName
466448
}
467449
val flag: TermName = "flag".toTermName
450+
val state: TermName = "state".toTermName
468451
val result: TermName = "result".toTermName
469452
val value: TermName = "value".toTermName
470453
val initialized: TermName = "initialized".toTermName

0 commit comments

Comments
 (0)