Skip to content

Commit

Permalink
Revised implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
odersky committed Aug 7, 2019
1 parent a873a86 commit eb5dbca
Showing 1 changed file with 59 additions and 65 deletions.
124 changes: 59 additions & 65 deletions tests/run/lazy-impl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,55 +15,47 @@
* for the result
* otherwise Variable is initialized
*
* Note 1: This assumes that fields cannot have `null` as normal value. Once we have
* nullability checking, this should be the standard case. We can still accommodate
* fields that can be null by representing `null` with a special value (say `NULL`)
* and storing `NULL` instead of `null` in the field. The necessary tweaks are added
* as comment lines to the code below.
*
* A lazy val `x: A = rhs` is compiled to the following code scheme:
*
* private var _x: AnyRef = null
* def x: A =
* _x match
* case current: A =>
* current
* case null =>
* if CAS(_x, null, Evaluating) then
* var result = rhs
* // if result == null then result == NULL
* if !CAS(_x, Evaluating, result) then
* val lock = _x.asInstanceOf[Waiting]
* _x = result
* lock.release(result)
* x
* case Evaluating =>
* CAS(x, Evaluating, new Waiting)
* x
* case current: Waiting =>
* _x = current.awaitRelease()
* x
* // case NULL =>
* // null
*
private @volatile var _x: AnyRef = null
@tailrec def x: A =
_x match
case current: A =>
current
case null =>
if CAS(_x, null, Evaluating) then
var result: A = null
try
result = rhs
if result == null then result = NULL // drop if A is non-nullable
finally
if !CAS(_x, Evaluating, result) then
val lock = _x.asInstanceOf[Waiting]
CAS(_x, lock, result)
lock.release()
x
case Evaluating =>
CAS(x, Evaluating, new Waiting)
x
case current: Waiting =>
_x = current.awaitRelease()
x
case NULL => null // drop if A is non-nullable
* The code makes use of the following runtime class:
*
* class Waiting:
*
* private var done = false
* private var result: AnyRef = _
*
* def release(result: AnyRef): Unit = synchronized:
* this.result = result
* done = true
* notifyAll()
*
* def awaitRelease(): AnyRef = synchronized:
* while !done do wait()
* result
*
* Note 2: The code assumes that the getter result type `A` is disjoint from the type
class Waiting:
private var done = false
def release(): Unit = synchronized:
done = true
notifyAll()
def awaitRelease(): Unit = synchronized:
while !done do wait()
* Note: The code assumes that the getter result type `A` is disjoint from the type
* of `Evaluating` and the `Waiting` class. If this is not the case (e.g. `A` is AnyRef),
* then the conditions in the match have to be re-ordered so that case `_x: A` becomes
* the final default case.
Expand All @@ -75,14 +67,14 @@
* whether cache has updated
* - no synchronization operations on reads after the first one
* - If there is contention, we see in addition
* - for the initializing thread: a synchronized notifyAll
* - for the initializing thread: another CAS and a synchronized notifyAll
* - for a reading thread: 0 or 1 CAS and a synchronized wait
*
* Code sizes for getter:
*
* this scheme, if nulls are excluded in type: 72 bytes
* current Dotty scheme: 131 bytes
* Scala 2 scheme: 39 bytes + 1 exception handler
* this scheme, if nulls are excluded in type: 86 bytes
* current Dotty scheme: 125 bytes
* Scala 2 scheme: 39 bytes
*
* Advantages of the scheme:
*
Expand All @@ -95,7 +87,6 @@
*
* Disadvantages:
*
* - does not work for local lazy vals (but maybe these could be unsynchronized anyway?)
* - lazy vals of primitive types are boxed
*/
import sun.misc.Unsafe._
Expand All @@ -106,31 +97,37 @@ class C {
println(s"initialize $name"); "result"
}

private[this] var _x: AnyRef = null
@volatile private[this] var _x: AnyRef = _

// Expansion of: lazy val x: String = init
// Expansion of: lazy val x: String = init("x")

def x: String = {
val current = _x
if (current.isInstanceOf[String])
current.asInstanceOf[String]
else
x$lzy_compute
x$lzy
}

def x$lzy_compute: String = {
def x$lzy: String = {
val current = _x
if (current.isInstanceOf[String])
current.asInstanceOf[String]
else {
val offset = C.x_offset
if (current == null) {
if (LazyRuntime.isUnitialized(this, offset))
LazyRuntime.initialize(this, offset, init("x"))
if (LazyRuntime.isUnitialized(this, offset)) {
try LazyRuntime.initialize(this, offset, init("x"))
catch {
case ex: Throwable =>
LazyRuntime.initialize(this, offset, null)
throw ex
}
}
}
else
LazyRuntime.awaitInitialized(this, offset, current)
x$lzy_compute
x$lzy
}
}

Expand Down Expand Up @@ -164,13 +161,13 @@ object LazyRuntime {
def initialize(base: Object, offset: Long, result: Object): Unit =
if (!unsafe.compareAndSwapObject(base, offset, Evaluating, result)) {
val lock = unsafe.getObject(base, offset).asInstanceOf[Waiting]
unsafe.putObject(base, offset, result)
lock.release(result)
unsafe.compareAndSwapObject(base, offset, lock, result)
lock.release()
}

def awaitInitialized(base: Object, offset: Long, current: Object): Unit =
if (current.isInstanceOf[Waiting])
unsafe.putObject(base, offset, current.asInstanceOf[Waiting].awaitRelease())
current.asInstanceOf[Waiting].awaitRelease()
else
unsafe.compareAndSwapObject(base, offset, Evaluating, new Waiting)
}
Expand All @@ -180,17 +177,14 @@ class LazyControl
class Waiting extends LazyControl {

private var done = false
private var result: AnyRef = _

def release(result: AnyRef) = synchronized {
this.result = result
def release(): Unit = synchronized {
done = true
notifyAll()
}

def awaitRelease(): AnyRef = synchronized {
def awaitRelease(): Unit = synchronized {
while (!done) wait()
result
}
}

Expand Down

0 comments on commit eb5dbca

Please sign in to comment.