From 110ee3fd87aed77c6770b5b1606f21dcd1c488bf Mon Sep 17 00:00:00 2001 From: Allan Renucci Date: Tue, 30 Oct 2018 17:55:05 +0100 Subject: [PATCH 1/2] More details on Lazy Vals --- docs/docs/reference/changed/lazy-vals-spec.md | 76 +++++++++++++++++++ docs/docs/reference/changed/lazy-vals.md | 22 ++++-- 2 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 docs/docs/reference/changed/lazy-vals-spec.md diff --git a/docs/docs/reference/changed/lazy-vals-spec.md b/docs/docs/reference/changed/lazy-vals-spec.md new file mode 100644 index 000000000000..26ab42425f5b --- /dev/null +++ b/docs/docs/reference/changed/lazy-vals-spec.md @@ -0,0 +1,76 @@ +--- +layout: doc-page +title: Changed: Lazy Vals - More Details +--- + +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) +of the [SIP-20] improved lazy vals initialization proposal. + +## Motivation + +The newly proposed lazy val initialization mechanism aims to eliminate the acquisition of resources +during the execution of the lazy val initializer block, thus reducing the possibility of a deadlock. +The concrete deadlock scenarios that the new lazy val initialization scheme eliminates are +summarized in the [SIP-20] document. + +## Implementation + +Given a lazy field of the form: + +```scala +class Foo { + @volatile lazy val bar = +} +``` + +The Dotty compiler will generate code equivalent to: + +```scala +class Foo { + import dotty.runtime.LazyVals + var value_0: Int = _ + var bitmap = 0 + val bitmap_offset = LazyVals.getOffset(classOf[LazyCell], "bitmap") + + def bar(): Int = { + var result: Int = 0 + var retry: Boolean = true + var flag: Long = 0L + while (retry) { + flag = LazyVals.get(this, bitmap_offset) + LazyVals.STATE(flag, ) match { + case => + if (LazyVals.CAS(this, bitmap_offset, flag, )) { + try result = + catch { + case ex => + LazyVals.setFlag(this, bitmap_offset, , ) + throw ex + } + value_0 = result + LazyVals.setFlag(this, bitmap_offset, , ) + retry = false + } + case | => + LazyVals.wait4Notification(this, bitmap_offset, flag, ) + case => + retry = false + result = $target + } + } + result + } +} +``` + +The state of the lazy val `` is represented with 4 values: 0, 1, 2 and 3. The state 0 +represents a non-initialized lazy val. The state 1 represents a lazy val that is currently being +initialized by some thread. The state 2 denotes that there are concurrent readers of the lazy val. +The state 3 represents a lazy val that has been initialized. `` is the id of the lazy +val. This id grows with the number of volatile lazy vals defined in the class. + +## Reference + +* [SIP-20] + +[SIP-20]: https://docs.scala-lang.org/sips/improved-lazy-val-initialization.html diff --git a/docs/docs/reference/changed/lazy-vals.md b/docs/docs/reference/changed/lazy-vals.md index 49228d97c429..0d9d74d2f3e2 100644 --- a/docs/docs/reference/changed/lazy-vals.md +++ b/docs/docs/reference/changed/lazy-vals.md @@ -1,19 +1,27 @@ --- layout: doc-page -title: Changed: Lazy Vals and @volatile +title: Changed: Lazy Vals --- -Lazy val initialization no longer guarantees safe publishing. This change was done -to avoid the performance overhead of thread synchonization because many lazy vals are only used in a single-threaded context. +Lazy field initialization no longer guarantees safe publishing. This change was done +to avoid the performance overhead of thread synchonization because most lazy vals are only used in +a single-threaded context. -To get back safe publishing you need to annotate a lazy val with `@volatile`. In +## Compatibility considerations - @volatile lazy val x = expr +To get back safe publishing (Scala 2's default) you need to annotate a lazy val with `@volatile`. In -it is guaranteed that readers of a lazy val will see the value of `x` -once it is assigned. +```scala +@volatile lazy val x = expr +``` + +it is guaranteed that readers of a lazy val will see the value of `x` once it is assigned. + +Local lazy vals (i.e. defined inside methods) are left unchanged. The [ScalaFix](https://scalacenter.github.io/scalafix/) rewrite tool as well as Dotty's own `-language:Scala2 -rewrite` option will rewrite all normal lazy vals to volatile ones in order to keep their semantics compatible with current Scala's. + +[More details](./lazy-vals-spec.html) From 215491cd478a489694afd3d8ec5dd60fd3f97d03 Mon Sep 17 00:00:00 2001 From: Allan Renucci Date: Wed, 31 Oct 2018 14:06:37 +0100 Subject: [PATCH 2/2] Add note on recursive lazy vals --- docs/docs/reference/changed/lazy-vals-spec.md | 6 +++++ tests/run/i1856.check | 22 +++++++++++++++ tests/run/i1856.scala | 27 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 tests/run/i1856.check create mode 100644 tests/run/i1856.scala diff --git a/docs/docs/reference/changed/lazy-vals-spec.md b/docs/docs/reference/changed/lazy-vals-spec.md index 26ab42425f5b..cb7cf9b0f4c5 100644 --- a/docs/docs/reference/changed/lazy-vals-spec.md +++ b/docs/docs/reference/changed/lazy-vals-spec.md @@ -69,6 +69,12 @@ initialized by some thread. The state 2 denotes that there are concurrent reader The state 3 represents a lazy val that has been initialized. `` is the id of the lazy val. This id grows with the number of volatile lazy vals defined in the class. +## Note on recursive lazy vals + +Ideally recursive lazy vals should be flagged as an error. The current behavior for `@volatile` +recursive lazy vals is undefined (initialization may result in a deadlock). Non `@volatile` lazy +vals behave as in Scala 2. + ## Reference * [SIP-20] diff --git a/tests/run/i1856.check b/tests/run/i1856.check new file mode 100644 index 000000000000..914b00f5b803 --- /dev/null +++ b/tests/run/i1856.check @@ -0,0 +1,22 @@ +Iteration 0 +Iteration 1 +Iteration 2 +Iteration 3 +Iteration 4 +Iteration 5 +Iteration 6 +Iteration 7 +Iteration 8 +Iteration 9 +42 +Iteration 0 +Iteration 1 +Iteration 2 +Iteration 3 +Iteration 4 +Iteration 5 +Iteration 6 +Iteration 7 +Iteration 8 +Iteration 9 +42 diff --git a/tests/run/i1856.scala b/tests/run/i1856.scala new file mode 100644 index 000000000000..9dafc1c1b693 --- /dev/null +++ b/tests/run/i1856.scala @@ -0,0 +1,27 @@ +object Test { + var count = 0 + lazy val lzy: Int = { + if (count < 10) { + println(s"Iteration $count") + count += 1 + lzy + } else 42 + } + + def lzy2 = { + var countLocal = 0 + lazy val lzyLocal: Int = { + if (countLocal < 10) { + println(s"Iteration $countLocal") + countLocal += 1 + lzyLocal + } else 42 + } + lzyLocal + } + + def main(args: Array[String]): Unit = { + println(lzy) + println(lzy2) + } +}