Skip to content

Commit 8d7e31f

Browse files
committed
Micro optimise @volatile lazy vals
1 parent bcdc431 commit 8d7e31f

File tree

2 files changed

+86
-100
lines changed

2 files changed

+86
-100
lines changed

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

Lines changed: 65 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Symbols._
1010
import Decorators._
1111
import NameKinds._
1212
import Types._
13-
import Flags.FlagSet
13+
import Flags._
1414
import StdNames.nme
1515
import dotty.tools.dotc.transform.MegaPhase._
1616
import dotty.tools.dotc.ast.tpd
@@ -275,34 +275,31 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
275275
/** Create a threadsafe lazy accessor equivalent to such code
276276
* ```
277277
* 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
278+
* while (true) {
279+
* val flag = LazyVals.get(this, bitmap_offset)
280+
* val state = LazyVals.STATE(flag, <field-id>)
281+
*
282+
* if (state == <state-3>) {
283+
* return value_0
284+
* } else if (state == <state-0>) {
285+
* if (LazyVals.CAS(this, bitmap_offset, flag, <state-1>, <field-id>)) {
286+
* try {
287+
* val result = <RHS>
288+
* value_0 = result
289+
* nullable = null
290+
* LazyVals.setFlag(this, bitmap_offset, <state-3>, <field-id>)
291+
* return result
292+
* }
293+
* catch {
294+
* case ex =>
295+
* LazyVals.setFlag(this, bitmap_offset, <state-0>, <field-id>)
296+
* throw ex
297+
* }
302298
* }
299+
* } else /* if (state == <state-1> || state == <state-2>) */ {
300+
* LazyVals.wait4Notification(this, bitmap_offset, flag, <field-id>)
303301
* }
304-
* nullable = null
305-
* result
302+
* }
306303
* }
307304
* ```
308305
*/
@@ -321,69 +318,59 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
321318
nullables: List[Symbol])(implicit ctx: Context): DefDef = {
322319
val initState = Literal(Constant(0))
323320
val computeState = Literal(Constant(1))
324-
val notifyState = Literal(Constant(2))
325321
val computedState = Literal(Constant(3))
326-
val flagSymbol = ctx.newSymbol(methodSymbol, lazyNme.flag, containerFlags, defn.LongType)
327-
val flagDef = ValDef(flagSymbol, Literal(Constant(0L)))
328322

329323
val thiz = This(claz)(ctx.fresh.setOwner(claz))
324+
val fieldId = Literal(Constant(ord))
330325

331-
val resultSymbol = ctx.newSymbol(methodSymbol, lazyNme.result, containerFlags, tp)
332-
val resultDef = ValDef(resultSymbol, defaultValue(tp))
333-
334-
val retrySymbol = ctx.newSymbol(methodSymbol, lazyNme.retry, containerFlags, defn.BooleanType)
335-
val retryDef = ValDef(retrySymbol, Literal(Constant(true)))
326+
val flagSymbol = ctx.newSymbol(methodSymbol, lazyNme.flag, Synthetic, defn.LongType)
327+
val flagDef = ValDef(flagSymbol, getFlag.appliedTo(thiz, offset))
328+
val flagRef = ref(flagSymbol)
336329

337-
val whileCond = ref(retrySymbol)
330+
val stateSymbol = ctx.newSymbol(methodSymbol, lazyNme.state, Synthetic, defn.LongType)
331+
val stateDef = ValDef(stateSymbol, stateMask.appliedTo(ref(flagSymbol), Literal(Constant(ord))))
332+
val stateRef = ref(stateSymbol)
338333

339334
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)
335+
val resultSymbol = ctx.newSymbol(methodSymbol, lazyNme.result, Synthetic, tp)
336+
val resultRef = ref(resultSymbol)
337+
val stats = (
338+
ValDef(resultSymbol, rhs) ::
339+
ref(target).becomes(resultRef) ::
340+
(nullOut(nullableFor(methodSymbol)) :+
341+
setFlagState.appliedTo(thiz, offset, computedState, fieldId))
342+
)
343+
Block(stats, Return(resultRef, methodSymbol))
364344
}
365345

366-
val waitSecond = {
367-
val wait = waitOnLock.appliedTo(thiz, offset, ref(flagSymbol), Literal(Constant(ord)))
368-
CaseDef(notifyState, EmptyTree, wait)
369-
}
370-
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)
346+
val retryCase = {
347+
val caseSymbol = ctx.newSymbol(methodSymbol, nme.DEFAULT_EXCEPTION_NAME, Flags.Synthetic, defn.ThrowableType)
348+
val triggerRetry = setFlagState.appliedTo(thiz, offset, initState, fieldId)
349+
CaseDef(
350+
Bind(caseSymbol, ref(caseSymbol)),
351+
EmptyTree,
352+
Block(List(triggerRetry), Throw(ref(caseSymbol)))
353+
)
376354
}
377355

378-
val default = CaseDef(Underscore(defn.LongType), EmptyTree, Literal(Constant(())))
356+
val initialize = If(
357+
casFlag.appliedTo(thiz, offset, flagRef, computeState, fieldId),
358+
Try(compute, List(retryCase), EmptyTree),
359+
unitLiteral
360+
)
379361

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

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)))
372+
val loop = WhileDo(EmptyTree, Block(List(flagDef, stateDef), condition))
373+
DefDef(methodSymbol, loop)
387374
}
388375

389376
def transformMemberDefVolatile(x: ValOrDefDef)(implicit ctx: Context): Thicket = {
@@ -465,6 +452,7 @@ object LazyVals {
465452
val getOffset: TermName = N.getOffset.toTermName
466453
}
467454
val flag: TermName = "flag".toTermName
455+
val state: TermName = "state".toTermName
468456
val result: TermName = "result".toTermName
469457
val value: TermName = "value".toTermName
470458
val initialized: TermName = "initialized".toTermName

docs/docs/reference/changed/lazy-vals-spec.md

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,36 +29,34 @@ The Dotty compiler will generate code equivalent to:
2929
class Foo {
3030
import dotty.runtime.LazyVals
3131
var value_0: Int = _
32-
var bitmap = 0
33-
val bitmap_offset = LazyVals.getOffset(classOf[LazyCell], "bitmap")
32+
var bitmap: Long = 0L
33+
val bitmap_offset: Long = LazyVals.getOffset(classOf[LazyCell], "bitmap")
3434

3535
def bar(): Int = {
36-
var result: Int = 0
37-
var retry: Boolean = true
38-
var flag: Long = 0L
39-
while (retry) {
40-
flag = LazyVals.get(this, bitmap_offset)
41-
LazyVals.STATE(flag, <field-id>) match {
42-
case <state-0> =>
43-
if (LazyVals.CAS(this, bitmap_offset, flag, <state-1>, <field-id>)) {
44-
try result = <RHS>
45-
catch {
46-
case ex =>
47-
LazyVals.setFlag(this, bitmap_offset, <state-0>, <field-id>)
48-
throw ex
49-
}
36+
while (true) {
37+
val flag = LazyVals.get(this, bitmap_offset)
38+
val state = LazyVals.STATE(flag, <field-id>)
39+
40+
if (state == <state-3>) {
41+
return value_0
42+
} else if (state == <state-0>) {
43+
if (LazyVals.CAS(this, bitmap_offset, flag, <state-1>, <field-id>)) {
44+
try {
45+
val result = <RHS>
5046
value_0 = result
5147
LazyVals.setFlag(this, bitmap_offset, <state-3>, <field-id>)
52-
retry = false
48+
return result
49+
}
50+
catch {
51+
case ex =>
52+
LazyVals.setFlag(this, bitmap_offset, <state-0>, <field-id>)
53+
throw ex
5354
}
54-
case <state-1> | <state-2> =>
55-
LazyVals.wait4Notification(this, bitmap_offset, flag, <field-id>)
56-
case <state-3> =>
57-
retry = false
58-
result = value_0
5955
}
56+
} else /* if (state == <state-1> || state == <state-2>) */ {
57+
LazyVals.wait4Notification(this, bitmap_offset, flag, <field-id>)
6058
}
61-
result
59+
}
6260
}
6361
}
6462
```

0 commit comments

Comments
 (0)