Skip to content

New lazy vals implementation #15207

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
386 changes: 229 additions & 157 deletions compiler/src/dotty/tools/dotc/transform/LazyVals.scala

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ class DottyBytecodeTests extends DottyBytecodeTest {
val clsIn = dir.lookupName("Test.class", directory = false).input
val clsNode = loadClassNode(clsIn)
val method = getMethod(clsNode, "test")
assertEquals(88, instructionsFromMethod(method).size)
assertEquals(113, instructionsFromMethod(method).size)
}
}

Expand Down
58 changes: 58 additions & 0 deletions library/src/scala/runtime/LazyVals.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ object LazyVals {
val processors = java.lang.Runtime.getRuntime.nn.availableProcessors()
8 * processors * processors
}

private[this] val monitors: Array[Object] =
Array.tabulate(base)(_ => new Object)

Expand All @@ -37,6 +38,41 @@ object LazyVals {

/* ------------- Start of public API ------------- */

/**
* Used to indicate the state of a lazy val that is being
* evaluated and of which other threads await the result.
*/
final class Waiting:
private var done = false

/**
* Wakes up waiting threads. Called on completion of the evaluation
* of lazy val's right-hand side.
*/
def release(): Unit = synchronized {
done = true
notifyAll()
}

/**
* Awaits the completion of the evaluation of lazy val's right-hand side.
*/
def awaitRelease(): Unit = synchronized {
while !done do wait()
}

/**
* Used to indicate the state of a lazy val that is currently being
* evaluated with no other thread awaiting its result.
*/
object Evaluating

/**
* Used to indicate the state of a lazy val that has been evaluated to
* `null`.
*/
object NULL

final val BITS_PER_LAZY_VAL = 2L

def STATE(cur: Long, ord: Int): Long = {
Expand All @@ -54,6 +90,12 @@ object LazyVals {
unsafe.compareAndSwapLong(t, offset, e, n)
}

def objCAS(t: Object, offset: Long, exp: Object, n: Object): Boolean = {
if (debug)
println(s"objCAS($t, $exp, $n)")
unsafe.compareAndSwapObject(t, offset, exp, n)
}

def setFlag(t: Object, offset: Long, v: Int, ord: Int): Unit = {
if (debug)
println(s"setFlag($t, $offset, $v, $ord)")
Expand Down Expand Up @@ -99,13 +141,21 @@ object LazyVals {
unsafe.getLongVolatile(t, off)
}

// kept for backward compatibility
def getOffset(clz: Class[_], name: String): Long = {
val r = unsafe.objectFieldOffset(clz.getDeclaredField(name))
if (debug)
println(s"getOffset($clz, $name) = $r")
r
}

def getStaticFieldOffset(field: java.lang.reflect.Field): Long = {
val r = unsafe.staticFieldOffset(field)
if (debug)
println(s"getStaticFieldOffset(${field.getDeclaringClass}, ${field.getName}) = $r")
r
}

def getOffsetStatic(field: java.lang.reflect.Field) =
val r = unsafe.objectFieldOffset(field)
if (debug)
Expand All @@ -114,11 +164,19 @@ object LazyVals {


object Names {
final val waiting = "Waiting"
final val evaluating = "Evaluating"
final val nullValued = "NULL"
final val waitingAwaitRelease = "awaitRelease"
final val waitingRelease = "release"
final val state = "STATE"
final val cas = "CAS"
final val objCas = "objCAS"
final val setFlag = "setFlag"
final val wait4Notification = "wait4Notification"
final val get = "get"
final val getOffset = "getOffset"
final val getOffsetStatic = "getOffsetStatic"
final val getStaticFieldOffset = "getStaticFieldOffset"
}
}
24 changes: 24 additions & 0 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,35 @@ object MiMaFilters {

ProblemFilters.exclude[MissingClassProblem]("scala.annotation.since"),


// APIs will be added in 3.2.0
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#AppliedTypeModule.apply"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.asQuotes"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"),

ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.getStaticFieldOffset"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.getOffsetStatic"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.objCAS"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.evaluating"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.getStaticFieldOffset"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.getOffsetStatic"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.nullValued"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.objCas"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.waiting"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.waitingAwaitRelease"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.waitingRelease"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$Evaluating$"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$NULL$"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$Waiting"),
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.LazyVals.Evaluating"),
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.LazyVals.NULL"),

// Experimental `MainAnnotation` APIs. Can be added in 3.3.0 or later.
// MiMa bug: classes nested in an experimental object should be ignored
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$Info"),
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$Parameter"),
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$ParameterAnnotation"),
)
}
4 changes: 4 additions & 0 deletions tests/run/lazyVals_c3.0.0.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
computing x
x
computing y
y
13 changes: 13 additions & 0 deletions tests/run/lazyVals_c3.0.0.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Compiled with 3.0.0 and run with current compiler
class Foo:
lazy val x =
println("computing x")
"x"
lazy val y =
println("computing y")
"y"

@main def Test =
val foo = new Foo
println(foo.x)
println(foo.y)
4 changes: 4 additions & 0 deletions tests/run/lazyVals_c3.1.0.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
computing x
x
computing y
y
13 changes: 13 additions & 0 deletions tests/run/lazyVals_c3.1.0.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Compiled with 3.1.0 and run with current compiler
class Foo:
lazy val x =
println("computing x")
"x"
lazy val y =
println("computing y")
"y"

@main def Test =
val foo = new Foo
println(foo.x)
println(foo.y)
7 changes: 7 additions & 0 deletions tests/run/serialization-new.check
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,14 @@ x = Paul
y = Paul
x equals y: true, y equals x: true

Calculating a1
1
Calculating a2
2
Calculating a3
3
Calculating a1
1
Calculating a2
2
3
16 changes: 14 additions & 2 deletions tests/run/serialization-new.scala
Original file line number Diff line number Diff line change
Expand Up @@ -468,14 +468,26 @@ object Test7 {

// Verify that transient lazy vals don't get serialized
class WithTransient extends Serializable {
@transient lazy val a1 = 1
@transient private lazy val a2 = 2
@transient lazy val a1 = {
println("Calculating a1")
1
}
@transient private lazy val a2 = {
println("Calculating a2")
2
}
private lazy val a3 = {
println("Calculating a3")
3
}

@transient object B extends Serializable
@transient private object C extends Serializable

def test = {
println(a1)
println(a2)
println(a3)
if (B == null || C == null)
println("Transient nested object failed to serialize properly")
}
Expand Down