Skip to content

Commit eb5dbca

Browse files
committed
Revised implementation
1 parent a873a86 commit eb5dbca

File tree

1 file changed

+59
-65
lines changed

1 file changed

+59
-65
lines changed

Diff for: tests/run/lazy-impl.scala

+59-65
Original file line numberDiff line numberDiff line change
@@ -15,55 +15,47 @@
1515
* for the result
1616
* otherwise Variable is initialized
1717
*
18-
* Note 1: This assumes that fields cannot have `null` as normal value. Once we have
19-
* nullability checking, this should be the standard case. We can still accommodate
20-
* fields that can be null by representing `null` with a special value (say `NULL`)
21-
* and storing `NULL` instead of `null` in the field. The necessary tweaks are added
22-
* as comment lines to the code below.
2318
*
2419
* A lazy val `x: A = rhs` is compiled to the following code scheme:
25-
*
26-
* private var _x: AnyRef = null
27-
* def x: A =
28-
* _x match
29-
* case current: A =>
30-
* current
31-
* case null =>
32-
* if CAS(_x, null, Evaluating) then
33-
* var result = rhs
34-
* // if result == null then result == NULL
35-
* if !CAS(_x, Evaluating, result) then
36-
* val lock = _x.asInstanceOf[Waiting]
37-
* _x = result
38-
* lock.release(result)
39-
* x
40-
* case Evaluating =>
41-
* CAS(x, Evaluating, new Waiting)
42-
* x
43-
* case current: Waiting =>
44-
* _x = current.awaitRelease()
45-
* x
46-
* // case NULL =>
47-
* // null
48-
*
20+
21+
private @volatile var _x: AnyRef = null
22+
23+
@tailrec def x: A =
24+
_x match
25+
case current: A =>
26+
current
27+
case null =>
28+
if CAS(_x, null, Evaluating) then
29+
var result: A = null
30+
try
31+
result = rhs
32+
if result == null then result = NULL // drop if A is non-nullable
33+
finally
34+
if !CAS(_x, Evaluating, result) then
35+
val lock = _x.asInstanceOf[Waiting]
36+
CAS(_x, lock, result)
37+
lock.release()
38+
x
39+
case Evaluating =>
40+
CAS(x, Evaluating, new Waiting)
41+
x
42+
case current: Waiting =>
43+
_x = current.awaitRelease()
44+
x
45+
case NULL => null // drop if A is non-nullable
4946
5047
* The code makes use of the following runtime class:
51-
*
52-
* class Waiting:
53-
*
54-
* private var done = false
55-
* private var result: AnyRef = _
56-
*
57-
* def release(result: AnyRef): Unit = synchronized:
58-
* this.result = result
59-
* done = true
60-
* notifyAll()
61-
*
62-
* def awaitRelease(): AnyRef = synchronized:
63-
* while !done do wait()
64-
* result
65-
*
66-
* Note 2: The code assumes that the getter result type `A` is disjoint from the type
48+
49+
class Waiting:
50+
private var done = false
51+
def release(): Unit = synchronized:
52+
done = true
53+
notifyAll()
54+
55+
def awaitRelease(): Unit = synchronized:
56+
while !done do wait()
57+
58+
* Note: The code assumes that the getter result type `A` is disjoint from the type
6759
* of `Evaluating` and the `Waiting` class. If this is not the case (e.g. `A` is AnyRef),
6860
* then the conditions in the match have to be re-ordered so that case `_x: A` becomes
6961
* the final default case.
@@ -75,14 +67,14 @@
7567
* whether cache has updated
7668
* - no synchronization operations on reads after the first one
7769
* - If there is contention, we see in addition
78-
* - for the initializing thread: a synchronized notifyAll
70+
* - for the initializing thread: another CAS and a synchronized notifyAll
7971
* - for a reading thread: 0 or 1 CAS and a synchronized wait
8072
*
8173
* Code sizes for getter:
8274
*
83-
* this scheme, if nulls are excluded in type: 72 bytes
84-
* current Dotty scheme: 131 bytes
85-
* Scala 2 scheme: 39 bytes + 1 exception handler
75+
* this scheme, if nulls are excluded in type: 86 bytes
76+
* current Dotty scheme: 125 bytes
77+
* Scala 2 scheme: 39 bytes
8678
*
8779
* Advantages of the scheme:
8880
*
@@ -95,7 +87,6 @@
9587
*
9688
* Disadvantages:
9789
*
98-
* - does not work for local lazy vals (but maybe these could be unsynchronized anyway?)
9990
* - lazy vals of primitive types are boxed
10091
*/
10192
import sun.misc.Unsafe._
@@ -106,31 +97,37 @@ class C {
10697
println(s"initialize $name"); "result"
10798
}
10899

109-
private[this] var _x: AnyRef = null
100+
@volatile private[this] var _x: AnyRef = _
110101

111-
// Expansion of: lazy val x: String = init
102+
// Expansion of: lazy val x: String = init("x")
112103

113104
def x: String = {
114105
val current = _x
115106
if (current.isInstanceOf[String])
116107
current.asInstanceOf[String]
117108
else
118-
x$lzy_compute
109+
x$lzy
119110
}
120111

121-
def x$lzy_compute: String = {
112+
def x$lzy: String = {
122113
val current = _x
123114
if (current.isInstanceOf[String])
124115
current.asInstanceOf[String]
125116
else {
126117
val offset = C.x_offset
127118
if (current == null) {
128-
if (LazyRuntime.isUnitialized(this, offset))
129-
LazyRuntime.initialize(this, offset, init("x"))
119+
if (LazyRuntime.isUnitialized(this, offset)) {
120+
try LazyRuntime.initialize(this, offset, init("x"))
121+
catch {
122+
case ex: Throwable =>
123+
LazyRuntime.initialize(this, offset, null)
124+
throw ex
125+
}
126+
}
130127
}
131128
else
132129
LazyRuntime.awaitInitialized(this, offset, current)
133-
x$lzy_compute
130+
x$lzy
134131
}
135132
}
136133

@@ -164,13 +161,13 @@ object LazyRuntime {
164161
def initialize(base: Object, offset: Long, result: Object): Unit =
165162
if (!unsafe.compareAndSwapObject(base, offset, Evaluating, result)) {
166163
val lock = unsafe.getObject(base, offset).asInstanceOf[Waiting]
167-
unsafe.putObject(base, offset, result)
168-
lock.release(result)
164+
unsafe.compareAndSwapObject(base, offset, lock, result)
165+
lock.release()
169166
}
170167

171168
def awaitInitialized(base: Object, offset: Long, current: Object): Unit =
172169
if (current.isInstanceOf[Waiting])
173-
unsafe.putObject(base, offset, current.asInstanceOf[Waiting].awaitRelease())
170+
current.asInstanceOf[Waiting].awaitRelease()
174171
else
175172
unsafe.compareAndSwapObject(base, offset, Evaluating, new Waiting)
176173
}
@@ -180,17 +177,14 @@ class LazyControl
180177
class Waiting extends LazyControl {
181178

182179
private var done = false
183-
private var result: AnyRef = _
184180

185-
def release(result: AnyRef) = synchronized {
186-
this.result = result
181+
def release(): Unit = synchronized {
187182
done = true
188183
notifyAll()
189184
}
190185

191-
def awaitRelease(): AnyRef = synchronized {
186+
def awaitRelease(): Unit = synchronized {
192187
while (!done) wait()
193-
result
194188
}
195189
}
196190

0 commit comments

Comments
 (0)