Skip to content

Commit 40c714b

Browse files
authored
Merge pull request #5342 from dotty-staging/lazy-val-sip
More details on Lazy Vals
2 parents 4b50294 + 215491c commit 40c714b

File tree

4 files changed

+146
-7
lines changed

4 files changed

+146
-7
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
layout: doc-page
3+
title: Changed: Lazy Vals - More Details
4+
---
5+
6+
Dotty implements [Version 6](https://docs.scala-lang.org/sips/improved-lazy-val-initialization.html#version-6---no-synchronization-on-this-and-concurrent-initialization-of-fields)
7+
of the [SIP-20] improved lazy vals initialization proposal.
8+
9+
## Motivation
10+
11+
The newly proposed lazy val initialization mechanism aims to eliminate the acquisition of resources
12+
during the execution of the lazy val initializer block, thus reducing the possibility of a deadlock.
13+
The concrete deadlock scenarios that the new lazy val initialization scheme eliminates are
14+
summarized in the [SIP-20] document.
15+
16+
## Implementation
17+
18+
Given a lazy field of the form:
19+
20+
```scala
21+
class Foo {
22+
@volatile lazy val bar = <RHS>
23+
}
24+
```
25+
26+
The Dotty compiler will generate code equivalent to:
27+
28+
```scala
29+
class Foo {
30+
import dotty.runtime.LazyVals
31+
var value_0: Int = _
32+
var bitmap = 0
33+
val bitmap_offset = LazyVals.getOffset(classOf[LazyCell], "bitmap")
34+
35+
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>)) {
44+
try result = <RHS>
45+
catch {
46+
case ex =>
47+
LazyVals.setFlag(this, bitmap_offset, <state-0>, <field-id>)
48+
throw ex
49+
}
50+
value_0 = result
51+
LazyVals.setFlag(this, bitmap_offset, <state-3>, <field-id>)
52+
retry = false
53+
}
54+
case <state-1> | <state-2> =>
55+
LazyVals.wait4Notification(this, bitmap_offset, flag, <field-id>)
56+
case <state-3> =>
57+
retry = false
58+
result = $target
59+
}
60+
}
61+
result
62+
}
63+
}
64+
```
65+
66+
The state of the lazy val `<state-i>` is represented with 4 values: 0, 1, 2 and 3. The state 0
67+
represents a non-initialized lazy val. The state 1 represents a lazy val that is currently being
68+
initialized by some thread. The state 2 denotes that there are concurrent readers of the lazy val.
69+
The state 3 represents a lazy val that has been initialized. `<field-id>` is the id of the lazy
70+
val. This id grows with the number of volatile lazy vals defined in the class.
71+
72+
## Note on recursive lazy vals
73+
74+
Ideally recursive lazy vals should be flagged as an error. The current behavior for `@volatile`
75+
recursive lazy vals is undefined (initialization may result in a deadlock). Non `@volatile` lazy
76+
vals behave as in Scala 2.
77+
78+
## Reference
79+
80+
* [SIP-20]
81+
82+
[SIP-20]: https://docs.scala-lang.org/sips/improved-lazy-val-initialization.html
Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
---
22
layout: doc-page
3-
title: Changed: Lazy Vals and @volatile
3+
title: Changed: Lazy Vals
44
---
55

6-
Lazy val initialization no longer guarantees safe publishing. This change was done
7-
to avoid the performance overhead of thread synchonization because many lazy vals are only used in a single-threaded context.
6+
Lazy field initialization no longer guarantees safe publishing. This change was done
7+
to avoid the performance overhead of thread synchonization because most lazy vals are only used in
8+
a single-threaded context.
89

9-
To get back safe publishing you need to annotate a lazy val with `@volatile`. In
10+
## Compatibility considerations
1011

11-
@volatile lazy val x = expr
12+
To get back safe publishing (Scala 2's default) you need to annotate a lazy val with `@volatile`. In
1213

13-
it is guaranteed that readers of a lazy val will see the value of `x`
14-
once it is assigned.
14+
```scala
15+
@volatile lazy val x = expr
16+
```
17+
18+
it is guaranteed that readers of a lazy val will see the value of `x` once it is assigned.
19+
20+
Local lazy vals (i.e. defined inside methods) are left unchanged.
1521

1622
The [ScalaFix](https://scalacenter.github.io/scalafix/) rewrite tool
1723
as well as Dotty's own `-language:Scala2 -rewrite` option will rewrite all normal
1824
lazy vals to volatile ones in order to keep their semantics compatible
1925
with current Scala's.
26+
27+
[More details](./lazy-vals-spec.html)

tests/run/i1856.check

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Iteration 0
2+
Iteration 1
3+
Iteration 2
4+
Iteration 3
5+
Iteration 4
6+
Iteration 5
7+
Iteration 6
8+
Iteration 7
9+
Iteration 8
10+
Iteration 9
11+
42
12+
Iteration 0
13+
Iteration 1
14+
Iteration 2
15+
Iteration 3
16+
Iteration 4
17+
Iteration 5
18+
Iteration 6
19+
Iteration 7
20+
Iteration 8
21+
Iteration 9
22+
42

tests/run/i1856.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
object Test {
2+
var count = 0
3+
lazy val lzy: Int = {
4+
if (count < 10) {
5+
println(s"Iteration $count")
6+
count += 1
7+
lzy
8+
} else 42
9+
}
10+
11+
def lzy2 = {
12+
var countLocal = 0
13+
lazy val lzyLocal: Int = {
14+
if (countLocal < 10) {
15+
println(s"Iteration $countLocal")
16+
countLocal += 1
17+
lzyLocal
18+
} else 42
19+
}
20+
lzyLocal
21+
}
22+
23+
def main(args: Array[String]): Unit = {
24+
println(lzy)
25+
println(lzy2)
26+
}
27+
}

0 commit comments

Comments
 (0)