Skip to content

Commit

Permalink
Further clean-up of new lazy vals
Browse files Browse the repository at this point in the history
  • Loading branch information
szymon-rd committed Oct 22, 2022
1 parent ab52310 commit 28abd42
Show file tree
Hide file tree
Showing 10 changed files with 39 additions and 93 deletions.
23 changes: 5 additions & 18 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,20 @@ package dotty.tools
package dotc
package core

import scala.annotation.threadUnsafe as tu
import Types.*
import Contexts.*
import Symbols.*
import SymDenotations.*
import StdNames.*
import Names.*
import Phases.*
import Flags.*
import Scopes.*
import Decorators.*
import NameOps.*
import Periods.*
import NullOpsDecorator.*
import scala.annotation.{threadUnsafe => tu}
import Types._, Contexts._, Symbols._, SymDenotations._, StdNames._, Names._, Phases._
import Flags._, Scopes._, Decorators._, NameOps._, Periods._, NullOpsDecorator._
import unpickleScala2.Scala2Unpickler.ensureConstructor

import scala.collection.mutable
import collection.mutable
import Denotations.{SingleDenotation, staticRef}
import util.{NoSource, SimpleIdentityMap, SourceFile}
import util.{SimpleIdentityMap, SourceFile, NoSource}
import typer.ImportInfo.RootRef
import Comments.CommentsContext
import Comments.Comment
import util.Spans.NoSpan
import Symbols.requiredModuleRef
import cc.{CaptureSet, CapturingType, EventuallyCapturingType}
import dotty.tools.dotc.transform.LazyVals.lazyNme
import cc.{CapturingType, CaptureSet, EventuallyCapturingType}

import scala.annotation.tailrec

Expand Down
85 changes: 28 additions & 57 deletions compiler/src/dotty/tools/dotc/transform/LazyVals.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
}
}

/**
* Append offset fields to companion objects.

/** Append offset fields to companion objects
*/
override def transformTemplate(template: Template)(using Context): Tree = {
val cls = ctx.owner.asClass
Expand All @@ -122,10 +122,10 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
case _ => prefix ::: stats
}

/**
* Make an eager val that would implement synthetic module.
* Eager val ensures thread safety and has less code generated.
*/
/** Make an eager val that would implement synthetic module.
* Eager val ensures thread safety and has less code generated.
*
*/
def transformSyntheticModule(tree: ValOrDefDef)(using Context): Thicket = {
val sym = tree.symbol
val holderSymbol = newSymbol(sym.owner, LazyLocalName.fresh(sym.asTerm.name),
Expand All @@ -135,8 +135,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
Thicket(field, getter)
}

/**
* Desugar a local `lazy val x: Int = <RHS>` into:
/** Desugar a local `lazy val x: Int = <RHS>` into:
*
* ```
* val x$lzy = new scala.runtime.LazyInt()
Expand Down Expand Up @@ -276,75 +275,47 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
}

/**
* Create a threadsafe lazy accessor equivalent to the following code:
* Create a threadsafe lazy accessor and function that computes the field's value. `Evaluating` and
* `NullValue` are represented by `object`s and `Waiting` by a class that allows awaiting the completion
* of the evaluation. Note that since tail-recursive functions are transformed *before* lazy-vals,
* this implementation does involve explicit while loop. `PatternMatcher` is coming before `LazyVals`,
* therefore the pattern matching is implemented using if-s.
*
* ```
* private @volatile var _x: AnyRef = null
* @tailrec def x: A =
* _x match
* case current: A =>
* current
* case NullValue => null
* case null =>
* if CAS(_x, null, Evaluating) then
* var result: AnyRef = null // here, we need `AnyRef` to possibly assign `NullValue`
* try
* result = rhs
* nullable = null // if the field is nullable; see `CollectNullableFields`
* finally
* if result == null then result = NullValue // drop if A is non-nullable
* 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 =>
* current.awaitRelease()
* x
* ```
* Where `Evaluating` and `NullValue` are represented by `object`s and `Waiting` by a class that
* allows awaiting the completion of the evaluation. Note that since tail-recursive
* functions are transformed *before* lazy-vals, this implementation directly implements
* the resulting loop. `PatternMatcher` coming before `LazyVals`, the pattern matching block
* is implemented using if-s. Additionally, the code can be optimized to be better inlined by
* introducing separate function for computing the lazy value and awaiting it. That is:
*
* ```
* private var _x: AnyRef = null
*
* def x: A =
* if !(_x == null || _x.isInstanceOf[LazyValControlState]) then
* return _x.asInstanceOf[A]
* else if _x == NullValue then
* return null
* val result = _x
* if result.isInstanceOf[A] then
* result // possible unboxing applied here
* else if result.eq(NullValue) then
* null // possible unboxing applied here
* else
* return x_compute()
* return x_compute() // possible unboxing applied here
*
* private def x_compute() =
* while <EmptyTree> do
* val current: AnyRef = _x
* if current == null then
* if current.eq(null) then
* if CAS(_x, null, Evaluating) then
* var resultNullable: AnyRef = null
* var result: AnyRef = null
* try
* resultNullable = rhs
* nullable = null
* if resultNullable == null then
* nullable = null // nulls out the nullable fields used only in initialization
* if resultNullable.eq(null) then
* result = NullValue
* else
* result = resultNullable
* return result
* finally
* if !CAS(_x, Evaluating, result) then
* val lock = _x.asInstanceOf[Waiting]
* CAS(_x, lock, result)
* lock.release()
* return resultNullable
* else
* if current.isInstanceOf[LazyValControlState] then
* if current.isInstanceOf[Evaluating] then // To avoid creating Waiting instance
* if current.eq(Evaluating) then // To avoid creating Waiting instance
* CAS(current, Evaluating, new Waiting)
* else if current.isInstanceOf[Waiting] then
* current.asInstanceOf[Waiting].await()
Expand All @@ -353,7 +324,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
* return current.asInstanceOf[A]
* end while
* * ```
*
*
* @param memberDef the transformed lazy field member definition
* @param claz the class containing this lazy val field
* @param target the target synthetic field
Expand All @@ -365,7 +336,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
target: Symbol,
offset: Tree,
thiz: Tree)(using Context): (DefDef, DefDef) = {
val tp = memberDef.tpe.widen.resultType.widen
val tp = memberDef.tpe.widenDealias.resultType.widenDealias
val waiting = ref(defn.LazyValsWaitingState)
val controlState = ref(defn.LazyValsControlState)
val evaluating = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.evaluating)
Expand Down Expand Up @@ -469,7 +440,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
Return(ref(current), lazyInitMethodSymbol)
)

val initBody = Block(ValDef(current, ref(target)) :: Nil, If(ref(current).equal(nullLiteral), initialize, ifNotUninitialized).withType(defn.UnitType))
val initBody = Block(ValDef(current, ref(target)) :: Nil, If(ref(current).select(defn.Object_eq).appliedTo(nullLiteral), initialize, ifNotUninitialized).withType(defn.UnitType))
val initMainLoop = WhileDo(EmptyTree, initBody) // becomes: while (true) do { body }
val initMethodDef = DefDef(lazyInitMethodSymbol, initMainLoop)
(accessorDef, initMethodDef)
Expand Down Expand Up @@ -629,7 +600,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
}

def transformMemberDefThreadSafeLegacy(x: ValOrDefDef)(using Context): Thicket = {
val tpe = x.tpe.widenDealias.resultType.widenDealias
val tpe = x.tpe.widen.resultType.widen
val claz = x.symbol.owner.asClass
val thizClass = Literal(Constant(claz.info))
val getOffset = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.getOffset)
Expand Down
2 changes: 1 addition & 1 deletion compiler/test/dotc/pos-lazy-vals-tests.allowlist
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ t6278-synth-def.scala
t6925b.scala
t7011.scala
t8306.scala
zipped.scala
zipped.scala
2 changes: 1 addition & 1 deletion compiler/test/dotc/run-lazy-vals-tests.allowlist
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ t7406.scala
t8245.scala
unapply.scala
unit-lazy-val.scala
view-iterator-stream.scala
view-iterator-stream.scala
1 change: 0 additions & 1 deletion library/src/scala/runtime/LazyVals.scala
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,5 @@ object LazyVals {
final val wait4Notification = "wait4Notification"
final val get = "get"
final val getOffset = "getOffset"
final val getOffsetStatic = "getOffsetStatic"
}
}
11 changes: 0 additions & 11 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,9 @@ import com.typesafe.tools.mima.core._

object MiMaFilters {
val Library: Seq[ProblemFilter] = Seq(
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.getStaticOffset"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.getOffsetStatic"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.getStaticFieldOffset"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.getStaticFieldOffset"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.objCAS"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.evaluating"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.getStaticOffset"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.nullValue"),
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$LazyValControlState"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals#Names.controlState"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$Evaluating$"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$NullValue$"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$Waiting"),
Expand Down
2 changes: 1 addition & 1 deletion tests/printing/transformed/lazy-vals-legacy.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
object A {
lazy val x: Int = 2
}
}
2 changes: 1 addition & 1 deletion tests/printing/transformed/lazy-vals-new.check
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ package <empty> {
while <empty> do
{
val current: Object = A#x$lzy1
if current.==(null) then
if current.eq(null) then
if
scala.runtime.LazyVals.objCAS(classOf[A], A.OFFSET$_m_0, null,
scala.runtime.LazyVals.Evaluating
Expand Down
2 changes: 1 addition & 1 deletion tests/printing/transformed/lazy-vals-new.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
object A {
lazy val x: Int = 2
}
}
2 changes: 1 addition & 1 deletion tests/run/lazyVals_c3.1.0.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ class Foo:
@main def Test =
val foo = new Foo
println(foo.x)
println(foo.y)
println(foo.y)

0 comments on commit 28abd42

Please sign in to comment.