From d7136bbeecb8a39f0180eba4020422b4f71e2d08 Mon Sep 17 00:00:00 2001 From: olsdavis Date: Tue, 17 May 2022 16:57:30 +0200 Subject: [PATCH 1/9] New lazy vals implementation --- .../dotty/tools/dotc/transform/LazyVals.scala | 294 ++++++++++++++++-- .../test-resources/scripting/scriptPath.sc | 2 + .../backend/jvm/DottyBytecodeTests.scala | 2 +- library/src/scala/runtime/LazyVals.scala | 56 ++++ project/MiMaFilters.scala | 24 ++ tests/init/neg/promotion-loop.check | 4 + tests/init/neg/t3273.check | 4 +- tests/run/lazyVals_c3.0.0.check | 4 + tests/run/lazyVals_c3.0.0.scala | 13 + tests/run/lazyVals_c3.1.0.check | 4 + tests/run/lazyVals_c3.1.0.scala | 13 + 11 files changed, 395 insertions(+), 25 deletions(-) create mode 100644 tests/run/lazyVals_c3.0.0.check create mode 100644 tests/run/lazyVals_c3.0.0.scala create mode 100644 tests/run/lazyVals_c3.1.0.check create mode 100644 tests/run/lazyVals_c3.1.0.scala diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index 74874843b4f6..eb365bfa18c3 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -23,10 +23,17 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { import LazyVals._ import tpd._ - /** this map contains mutable state of transformation: OffsetDefs to be appended to companion object definitions, - * and number of bits currently used */ - class OffsetInfo(var defs: List[Tree], var ord:Int) + /** + * The map contains the list of the offset trees. + */ + class OffsetInfo(var defs: List[Tree]) + /** + * This map contains mutable state of transformation: OffsetDefs to be appended + * to companion object definitions, and number of bits currently used. + */ + class OldOffsetInfo(defs: List[Tree], var ord: Int) extends OffsetInfo(defs) private val appendOffsetDefs = mutable.Map.empty[Symbol, OffsetInfo] + private val oldAppendOffsetDefs = mutable.Map.empty[Symbol, OldOffsetInfo] override def phaseName: String = LazyVals.name @@ -52,6 +59,20 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { else nullables.toList } + private inline def isOldLazyVals(using ctx: Context): Boolean = + import dotty.tools.dotc.config.ScalaRelease._ + ctx.scalaRelease <= Release3_1 + + private def initBlock(stats: List[Tree])(using Context): Block = stats match + case Nil => throw new IllegalArgumentException("trying to create an empty Block") + case x :: Nil => Block(List(x), EmptyTree) + case x :: xs => Block(stats.init, stats.last) + + private def needsBoxing(tp: Type)(using Context): Boolean = tp != NoType && tp != defn.UnitType && tp.classSymbol.isPrimitiveValueClass + + private def boxIfCan(tp: Type)(using Context): Type = + assert(needsBoxing(tp)) + defn.boxedType(tp) override def prepareForUnit(tree: Tree)(using Context): Context = { if (lazyValNullables == null) @@ -62,7 +83,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { override def transformDefDef(tree: DefDef)(using Context): Tree = transformLazyVal(tree) - override def transformValDef(tree: ValDef)(using Context): Tree = transformLazyVal(tree) @@ -101,13 +121,13 @@ 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 - - appendOffsetDefs.get(cls) match { + + (if isOldLazyVals then oldAppendOffsetDefs else appendOffsetDefs).get(cls) match { case None => template case Some(data) => data.defs.foreach(_.symbol.addAnnotation(Annotation(defn.ScalaStaticAnnot))) @@ -115,16 +135,15 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { } } - private def addInFront(prefix: List[Tree], stats: List[Tree]) = stats match { case first :: rest if isSuperConstrCall(first) => first :: prefix ::: rest 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), @@ -134,7 +153,8 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { Thicket(field, getter) } - /** Desugar a local `lazy val x: Int = ` into: + /** + * Desugar a local `lazy val x: Int = ` into: * * ``` * val x$lzy = new scala.runtime.LazyInt() @@ -186,7 +206,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { Thicket(holderTree, initTree, accessor) } - override def transformStats(trees: List[tpd.Tree])(using Context): List[Tree] = { // backend requires field usage to be after field definition // need to bring containers to start of method @@ -274,6 +293,227 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { } } + def transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket = { + assert(!(x.symbol is Mutable)) + // generate old code for compatibility + // TODO find more meaningful names than old/new + if isOldLazyVals then + transformMemberDefThreadSafeOld(x) + else + transformMemberDefThreadSafeNew(x) + } + + /** + * Create a threadsafe lazy accessor equivalent to the following code: + * ``` + * 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: AnyRef = null // here, we need `AnyRef` to possibly assign `NULL` + * try + * result = rhs + * nullable = null // if the field is nullable; see `CollectNullableFields` + * 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 => + * current.awaitRelease() + * x + * case NULL => null + * ``` + * Where `Evaluating` and `NULL` 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. That is: + * + * ``` + * private @volatile var _x: AnyRef = null + * def x: A = + * while true do + * val current: AnyRef = _x + * if current == null then + * if CAS(_x, null, Evaluating) then + * var result: AnyRef = null + * try + * result = rhs + * nullable = null + * if result == null then result = NULL + * finally + * if !CAS(_x, Evaluating, result) then + * val lock = _x.asInstanceOf[Waiting] + * CAS(_x, lock, result) + * lock.release() + * else + * if current.isInstanceOf[Evaluating] then + * CAS(current, Evaluating, new Waiting) + * else if current.isInstanceOf[NULL] then + * null + * else if current.isInstanceOf[Waiting] then + * current.asInstanceOf[Waiting].awaitRelease() + * else + * current.asInstanceOf[A] + * end while + * ``` + * + * @param methodSymbol the symbol of the new method + * @param claz the class containing this lazy val field + * @param target the target synthetic field + * @param rhs the right-hand side expression of the lazy val + * @param tp the type of the lazy val + * @param offset the offset of the field in the bitmap + * @param getFlag a flag for the volatile get function + * @param objCasFlag a flag for the CAS function operating on objects + * @param waiting a reference to the `Waiting` runtime class + * @param evaluating a reference to the `Evaluating` runtime object + * @param nullValued a reference to the `NULL` runtime object + */ + def mkThreadSafeDefNew(methodSymbol: TermSymbol, + claz: ClassSymbol, + target: Symbol, + rhs: Tree, + tp: Type, + offset: Tree, + objCasFlag: Tree, + waiting: Tree, + evaluating: Tree, + nullValued: Tree, + thiz: Tree)(using Context): DefDef = { + val discardSymb = newSymbol(methodSymbol, lazyNme.discard, Method | Synthetic, MethodType(Nil)(_ => Nil, _ => defn.UnitType)) + val discardDef = DefDef(discardSymb, initBlock( + objCasFlag.appliedTo(thiz, offset, evaluating, Select(New(waiting), StdNames.nme.CONSTRUCTOR).ensureApplied) + :: Return(unitLiteral, discardSymb) :: Nil)) + // if observed a null value + val unevaluated = { + // var res: AnyRef + val resSymb = newSymbol(methodSymbol, lazyNme.result, Synthetic | Mutable, defn.ObjectType) + // releasing block in finally + val lockRel = { + val lockSymb = newSymbol(methodSymbol, lazyNme.lock, Synthetic, waiting.typeOpt) + initBlock(ValDef(lockSymb, ref(target).cast(waiting.typeOpt)) + :: objCasFlag.appliedTo(thiz, offset, ref(lockSymb), ref(resSymb)) + :: ref(lockSymb).select(lazyNme.RLazyVals.waitingRelease).ensureApplied :: Nil) + } + // finally block + val fin = If( + objCasFlag.appliedTo(thiz, offset, evaluating, ref(resSymb)).equal(Literal(Constant(false))), + lockRel, + EmptyTree + ).withType(defn.UnitType) + // entire try block + val evaluate = Try( + initBlock( + Assign(ref(resSymb), if needsBoxing(tp) && rhs != EmptyTree then rhs.ensureConforms(boxIfCan(tp)) else rhs) // try result = rhs + :: nullOut(nullableFor(methodSymbol)) + ::: If(ref(resSymb).equal(nullLiteral), Assign(ref(resSymb), nullValued), EmptyTree).withType(defn.UnitType) // if result == null then result = NULL + :: Nil + ), + Nil, + fin + ) + // if CAS(...) + If( + objCasFlag.appliedTo(thiz, offset, nullLiteral, evaluating), + initBlock(ValDef(resSymb, nullLiteral) // var result: AnyRef = null + :: evaluate // try ... finally ... + :: Nil), + EmptyTree + ).withType(defn.UnitType) + } + val current = newSymbol(methodSymbol, lazyNme.current, Synthetic, defn.ObjectType) + val ifNotNull = + If( + ref(current).select(defn.Any_isInstanceOf).appliedToTypeTree(evaluating), + ref(discardSymb).ensureApplied, + // not an Evaluating + If( + ref(current).select(defn.Any_isInstanceOf).appliedToTypeTree(nullValued), + Return(defaultValue(tp), methodSymbol), + // not a NULL + If( + ref(current).select(defn.Any_isInstanceOf).appliedToTypeTree(waiting), + ref(current).select(defn.Any_asInstanceOf).appliedToTypeTree(waiting).select(lazyNme.RLazyVals.waitingAwaitRelease).ensureApplied, + // not a Waiting, then is an A + Return(ref(current).ensureConforms(tp), methodSymbol) + ) + ) + ) + val body = initBlock(ValDef(current, ref(target)) :: If(ref(current).equal(nullLiteral), unevaluated, ifNotNull) :: Nil) + val mainLoop = WhileDo(EmptyTree, body) // becomes: while (true) do { body } + val ret = DefDef(methodSymbol, initBlock(discardDef :: mainLoop :: Nil)) + ret + } + + def transformMemberDefThreadSafeNew(x: ValOrDefDef)(using Context): Thicket = { + import dotty.tools.dotc.core.Types._ + import dotty.tools.dotc.core.Flags._ + + val runtimeModule = "scala.runtime.LazyVals" + val tpe = x.tpe.widen.resultType.widen + val claz = x.symbol.owner.asClass + val thizClass = Literal(Constant(claz.info)) + val helperModule = requiredModule(runtimeModule) + var offsetSymbol: TermSymbol | Null = null + + def offsetName(id: Int) = s"${StdNames.nme.LAZY_FIELD_OFFSET}${if (x.symbol.owner.is(Module)) "_m_" else ""}$id".toTermName + + val containerName = LazyLocalName.fresh(x.name.asTermName) + val containerSymbol = newSymbol(claz, containerName, containerFlags, defn.ObjectType).enteredAfter(this) + containerSymbol.addAnnotation(Annotation(defn.VolatileAnnot)) // private @volatile var _x: AnyRef + val stat = x.symbol.isStatic + if stat then + containerSymbol.setFlag(JavaStatic) + val getOffset = + if stat then + Select(ref(helperModule), lazyNme.RLazyVals.getStaticOffset) + else + Select(ref(helperModule), lazyNme.RLazyVals.getOffset) + val containerTree = ValDef(containerSymbol, nullLiteral) + def staticOrFieldOff: Tree = getOffset.appliedTo(thizClass, Literal(Constant(containerName.toString))) + + // create an offset for this lazy val + appendOffsetDefs.get(claz) match + case Some(info) => + offsetSymbol = newSymbol(claz, offsetName(info.defs.size), Synthetic, defn.LongType).enteredAfter(this) + offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot)) + val offsetTree = ValDef(offsetSymbol.nn, staticOrFieldOff) + info.defs = offsetTree :: info.defs + case None => + offsetSymbol = newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this) + offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot)) + val offsetTree = ValDef(offsetSymbol.nn, staticOrFieldOff) + appendOffsetDefs += (claz -> new OffsetInfo(List(offsetTree))) + + val waiting = requiredClass(s"$runtimeModule.${lazyNme.RLazyVals.waiting}") + val evaluating = Select(ref(helperModule), lazyNme.RLazyVals.evaluating) + val nullValued = Select(ref(helperModule), lazyNme.RLazyVals.nullValued) + val objCas = Select(ref(helperModule), lazyNme.RLazyVals.objCas) + + val offset = ref(offsetSymbol.nn) + + val swapOver = + if stat then + tpd.clsOf(x.symbol.owner.typeRef) + else + This(claz) + + val methodSymbol = x.symbol.asTerm + val accessor = mkThreadSafeDefNew(methodSymbol, claz, containerSymbol, x.rhs, tpe, offset, objCas, + ref(waiting), evaluating, nullValued, swapOver) + Thicket(containerTree, accessor) + } + /** Create a threadsafe lazy accessor equivalent to such code * ``` * def methodSymbol(): Int = { @@ -305,7 +545,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { * } * ``` */ - def mkThreadSafeDef(methodSymbol: TermSymbol, + def mkThreadSafeDefOld(methodSymbol: TermSymbol, claz: ClassSymbol, ord: Int, target: Symbol, @@ -374,9 +614,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { DefDef(methodSymbol, loop) } - def transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket = { - assert(!(x.symbol is Mutable)) - + def transformMemberDefThreadSafeOld(x: ValOrDefDef)(using Context): Thicket = { val tpe = x.tpe.widen.resultType.widen val claz = x.symbol.owner.asClass val thizClass = Literal(Constant(claz.info)) @@ -390,7 +628,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { def offsetName(id: Int) = s"${StdNames.nme.LAZY_FIELD_OFFSET}${if (x.symbol.owner.is(Module)) "_m_" else ""}$id".toTermName // compute or create appropriate offsetSymbol, bitmap and bits used by current ValDef - appendOffsetDefs.get(claz) match { + oldAppendOffsetDefs.get(claz) match { case Some(info) => val flagsPerLong = (64 / scala.runtime.LazyVals.BITS_PER_LAZY_VAL).toInt info.ord += 1 @@ -435,7 +673,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { val state = Select(ref(helperModule), lazyNme.RLazyVals.state) val cas = Select(ref(helperModule), lazyNme.RLazyVals.cas) - val accessor = mkThreadSafeDef(x.symbol.asTerm, claz, ord, containerSymbol, x.rhs, tpe, offset, getFlag, state, cas, setFlag, wait) + val accessor = mkThreadSafeDefOld(x.symbol.asTerm, claz, ord, containerSymbol, x.rhs, tpe, offset, getFlag, state, cas, setFlag, wait) if (flag eq EmptyTree) Thicket(containerTree, accessor) else Thicket(containerTree, flag, accessor) @@ -450,6 +688,14 @@ object LazyVals { import Names.TermName object RLazyVals { import scala.runtime.LazyVals.{Names => N} + val waiting: TermName = N.waiting.toTermName + val waitingAwaitRelease: TermName = N.waitingAwaitRelease.toTermName + val waitingRelease: TermName = N.waitingRelease.toTermName + val evaluating: TermName = N.evaluating.toTermName + val nullValued: TermName = N.nullValued.toTermName + val objCas: TermName = N.objCas.toTermName + val getOffset: TermName = N.getOffset.toTermName + val getStaticOffset: TermName = N.getStaticOffset.toTermName val get: TermName = N.get.toTermName val setFlag: TermName = N.setFlag.toTermName val wait4Notification: TermName = N.wait4Notification.toTermName @@ -458,6 +704,7 @@ object LazyVals { val getOffset: TermName = N.getOffset.toTermName val getOffsetStatic: TermName = "getOffsetStatic".toTermName val getDeclaredField: TermName = "getDeclaredField".toTermName + } val flag: TermName = "flag".toTermName val state: TermName = "state".toTermName @@ -466,5 +713,8 @@ object LazyVals { val initialized: TermName = "initialized".toTermName val initialize: TermName = "initialize".toTermName val retry: TermName = "retry".toTermName + val current: TermName = "current".toTermName + val lock: TermName = "lock".toTermName + val discard: TermName = "discard".toTermName } } diff --git a/compiler/test-resources/scripting/scriptPath.sc b/compiler/test-resources/scripting/scriptPath.sc index 46cd5e8a7385..837bd674aad8 100755 --- a/compiler/test-resources/scripting/scriptPath.sc +++ b/compiler/test-resources/scripting/scriptPath.sc @@ -16,6 +16,8 @@ val pathEntries = System.getenv("PATH").split(psep).toList System.err.printf("sun.java.command: %s\n", sys.props("sun.java.command")) System.err.printf("first 5 PATH entries:\n%s\n",pathEntries.take(5).mkString("\n")) + printf("script.path: %s\n",path.norm) + assert(path.endsWith("scriptPath.sc"),s"actual path [$path]") } extension(s: String) diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index d21cf929b840..a867e74f0507 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -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(122, instructionsFromMethod(method).size) } } diff --git a/library/src/scala/runtime/LazyVals.scala b/library/src/scala/runtime/LazyVals.scala index f3fe52beb9ee..0df5fab309f3 100644 --- a/library/src/scala/runtime/LazyVals.scala +++ b/library/src/scala/runtime/LazyVals.scala @@ -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) @@ -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 = { @@ -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)") @@ -106,6 +148,13 @@ object LazyVals { r } + def getStaticOffset(clz: Class[_], name: String): Long = { + val r = unsafe.staticFieldOffset(clz.getDeclaredField(name)) + if (debug) + println(s"getStaticOffset($clz, $name) = $r") + r + } + def getOffsetStatic(field: java.lang.reflect.Field) = val r = unsafe.objectFieldOffset(field) if (debug) @@ -114,11 +163,18 @@ 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 getStaticOffset = "getStaticOffset" } } diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index e8f82f61de9c..e015bf4af833 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -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.getStaticOffset"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.getStaticOffset"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.objCAS"), + 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.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"), ) } diff --git a/tests/init/neg/promotion-loop.check b/tests/init/neg/promotion-loop.check index b53dd676081f..24f63e7dcedd 100644 --- a/tests/init/neg/promotion-loop.check +++ b/tests/init/neg/promotion-loop.check @@ -1,7 +1,11 @@ -- Error: tests/init/neg/promotion-loop.scala:16:10 -------------------------------------------------------------------- 16 | println(b) // error | ^ +<<<<<<< HEAD + | Cannot prove that the value is fully-initialized. Only initialized values may be used as arguments. +======= | Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. +>>>>>>> 91c03b1f7835d574290eba1ecf236e0f59fc2bf0 | | The unsafe promotion may cause the following problem: | Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. diff --git a/tests/init/neg/t3273.check b/tests/init/neg/t3273.check index 8c7f0f32e2d8..04704b1d92ba 100644 --- a/tests/init/neg/t3273.check +++ b/tests/init/neg/t3273.check @@ -1,7 +1,7 @@ -- Error: tests/init/neg/t3273.scala:4:42 ------------------------------------------------------------------------------ 4 | val num1: LazyList[Int] = 1 #:: num1.map(_ + 1) // error | ^^^^^^^^^^^^^^^ - | Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. + | Cannot prove that the value is fully-initialized. Only initialized values may be used as arguments. | | The unsafe promotion may cause the following problem: | Access non-initialized value num1. Calling trace: @@ -10,7 +10,7 @@ -- Error: tests/init/neg/t3273.scala:5:61 ------------------------------------------------------------------------------ 5 | val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. + | Cannot prove that the value is fully-initialized. Only initialized values may be used as arguments. | | The unsafe promotion may cause the following problem: | Access non-initialized value num2. Calling trace: diff --git a/tests/run/lazyVals_c3.0.0.check b/tests/run/lazyVals_c3.0.0.check new file mode 100644 index 000000000000..a0aad90603ed --- /dev/null +++ b/tests/run/lazyVals_c3.0.0.check @@ -0,0 +1,4 @@ +computing x +x +computing y +y \ No newline at end of file diff --git a/tests/run/lazyVals_c3.0.0.scala b/tests/run/lazyVals_c3.0.0.scala new file mode 100644 index 000000000000..5df4069bc94c --- /dev/null +++ b/tests/run/lazyVals_c3.0.0.scala @@ -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) diff --git a/tests/run/lazyVals_c3.1.0.check b/tests/run/lazyVals_c3.1.0.check new file mode 100644 index 000000000000..a0aad90603ed --- /dev/null +++ b/tests/run/lazyVals_c3.1.0.check @@ -0,0 +1,4 @@ +computing x +x +computing y +y \ No newline at end of file diff --git a/tests/run/lazyVals_c3.1.0.scala b/tests/run/lazyVals_c3.1.0.scala new file mode 100644 index 000000000000..45e796ab46d3 --- /dev/null +++ b/tests/run/lazyVals_c3.1.0.scala @@ -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) \ No newline at end of file From a1af355ad71c326db627e7f5b58148c172304df5 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Wed, 18 May 2022 16:49:09 +0200 Subject: [PATCH 2/9] Porting recent changes & adding fixes --- .../dotty/tools/dotc/transform/LazyVals.scala | 202 ++---------------- library/src/scala/runtime/LazyVals.scala | 10 +- project/MiMaFilters.scala | 8 +- tests/init/neg/promotion-loop.check | 4 - tests/init/neg/t3273.check | 4 +- 5 files changed, 26 insertions(+), 202 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index eb365bfa18c3..babf030eb5e0 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -33,7 +33,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { */ class OldOffsetInfo(defs: List[Tree], var ord: Int) extends OffsetInfo(defs) private val appendOffsetDefs = mutable.Map.empty[Symbol, OffsetInfo] - private val oldAppendOffsetDefs = mutable.Map.empty[Symbol, OldOffsetInfo] override def phaseName: String = LazyVals.name @@ -59,9 +58,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { else nullables.toList } - private inline def isOldLazyVals(using ctx: Context): Boolean = - import dotty.tools.dotc.config.ScalaRelease._ - ctx.scalaRelease <= Release3_1 private def initBlock(stats: List[Tree])(using Context): Block = stats match case Nil => throw new IllegalArgumentException("trying to create an empty Block") @@ -127,7 +123,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { override def transformTemplate(template: Template)(using Context): Tree = { val cls = ctx.owner.asClass - (if isOldLazyVals then oldAppendOffsetDefs else appendOffsetDefs).get(cls) match { + appendOffsetDefs.get(cls) match { case None => template case Some(data) => data.defs.foreach(_.symbol.addAnnotation(Annotation(defn.ScalaStaticAnnot))) @@ -295,12 +291,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { def transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket = { assert(!(x.symbol is Mutable)) - // generate old code for compatibility - // TODO find more meaningful names than old/new - if isOldLazyVals then - transformMemberDefThreadSafeOld(x) - else - transformMemberDefThreadSafeNew(x) + transformMemberDefThreadSafeNew(x) } /** @@ -309,8 +300,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { * 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: AnyRef = null // here, we need `AnyRef` to possibly assign `NULL` @@ -331,6 +320,8 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { * current.awaitRelease() * x * case NULL => null + * case current: A => + * current * ``` * Where `Evaluating` and `NULL` are represented by `object`s and `Waiting` by a class that * allows awaiting the completion of the evaluation. Note that since tail-recursive @@ -359,11 +350,11 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { * if current.isInstanceOf[Evaluating] then * CAS(current, Evaluating, new Waiting) * else if current.isInstanceOf[NULL] then - * null + * return null * else if current.isInstanceOf[Waiting] then * current.asInstanceOf[Waiting].awaitRelease() * else - * current.asInstanceOf[A] + * return current.asInstanceOf[A] * end while * ``` * @@ -476,23 +467,24 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { containerSymbol.setFlag(JavaStatic) val getOffset = if stat then - Select(ref(helperModule), lazyNme.RLazyVals.getStaticOffset) + Select(ref(helperModule), lazyNme.RLazyVals.getStaticFieldOffset) else - Select(ref(helperModule), lazyNme.RLazyVals.getOffset) + Select(ref(helperModule), lazyNme.RLazyVals.getOffsetStatic) val containerTree = ValDef(containerSymbol, nullLiteral) - def staticOrFieldOff: Tree = getOffset.appliedTo(thizClass, Literal(Constant(containerName.toString))) // create an offset for this lazy val appendOffsetDefs.get(claz) match case Some(info) => offsetSymbol = newSymbol(claz, offsetName(info.defs.size), Synthetic, defn.LongType).enteredAfter(this) offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot)) - val offsetTree = ValDef(offsetSymbol.nn, staticOrFieldOff) + val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerName.toString))) + val offsetTree = ValDef(offsetSymbol.nn, getOffset.appliedTo(fieldTree)) info.defs = offsetTree :: info.defs case None => offsetSymbol = newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this) offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot)) - val offsetTree = ValDef(offsetSymbol.nn, staticOrFieldOff) + val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerName.toString))) + val offsetTree = ValDef(offsetSymbol.nn, getOffset.appliedTo(fieldTree)) appendOffsetDefs += (claz -> new OffsetInfo(List(offsetTree))) val waiting = requiredClass(s"$runtimeModule.${lazyNme.RLazyVals.waiting}") @@ -513,171 +505,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { ref(waiting), evaluating, nullValued, swapOver) Thicket(containerTree, accessor) } - - /** Create a threadsafe lazy accessor equivalent to such code - * ``` - * def methodSymbol(): Int = { - * while (true) { - * val flag = LazyVals.get(this, bitmap_offset) - * val state = LazyVals.STATE(flag, ) - * - * if (state == ) { - * return value_0 - * } else if (state == ) { - * if (LazyVals.CAS(this, bitmap_offset, flag, , )) { - * try { - * val result = - * value_0 = result - * nullable = null - * LazyVals.setFlag(this, bitmap_offset, , ) - * return result - * } - * catch { - * case ex => - * LazyVals.setFlag(this, bitmap_offset, , ) - * throw ex - * } - * } - * } else /* if (state == || state == ) */ { - * LazyVals.wait4Notification(this, bitmap_offset, flag, ) - * } - * } - * } - * ``` - */ - def mkThreadSafeDefOld(methodSymbol: TermSymbol, - claz: ClassSymbol, - ord: Int, - target: Symbol, - rhs: Tree, - tp: Type, - offset: Tree, - getFlag: Tree, - stateMask: Tree, - casFlag: Tree, - setFlagState: Tree, - waitOnLock: Tree)(using Context): DefDef = { - val initState = Literal(Constant(0)) - val computeState = Literal(Constant(1)) - val computedState = Literal(Constant(3)) - - val thiz = This(claz) - val fieldId = Literal(Constant(ord)) - - val flagSymbol = newSymbol(methodSymbol, lazyNme.flag, Synthetic, defn.LongType) - val flagDef = ValDef(flagSymbol, getFlag.appliedTo(thiz, offset)) - val flagRef = ref(flagSymbol) - - val stateSymbol = newSymbol(methodSymbol, lazyNme.state, Synthetic, defn.LongType) - val stateDef = ValDef(stateSymbol, stateMask.appliedTo(ref(flagSymbol), Literal(Constant(ord)))) - val stateRef = ref(stateSymbol) - - val compute = { - val resultSymbol = newSymbol(methodSymbol, lazyNme.result, Synthetic, tp) - val resultRef = ref(resultSymbol) - val stats = ( - ValDef(resultSymbol, rhs) :: - ref(target).becomes(resultRef) :: - (nullOut(nullableFor(methodSymbol)) :+ - setFlagState.appliedTo(thiz, offset, computedState, fieldId)) - ) - Block(stats, Return(resultRef, methodSymbol)) - } - - val retryCase = { - val caseSymbol = newSymbol(methodSymbol, nme.DEFAULT_EXCEPTION_NAME, Synthetic | Case, defn.ThrowableType) - val triggerRetry = setFlagState.appliedTo(thiz, offset, initState, fieldId) - CaseDef( - Bind(caseSymbol, ref(caseSymbol)), - EmptyTree, - Block(List(triggerRetry), Throw(ref(caseSymbol))) - ) - } - - val initialize = If( - casFlag.appliedTo(thiz, offset, flagRef, computeState, fieldId), - Try(compute, List(retryCase), EmptyTree), - unitLiteral - ) - - val condition = If( - stateRef.equal(computedState), - Return(ref(target), methodSymbol), - If( - stateRef.equal(initState), - initialize, - waitOnLock.appliedTo(thiz, offset, flagRef, fieldId) - ) - ) - - val loop = WhileDo(EmptyTree, Block(List(flagDef, stateDef), condition)) - DefDef(methodSymbol, loop) - } - - def transformMemberDefThreadSafeOld(x: ValOrDefDef)(using Context): Thicket = { - val tpe = x.tpe.widen.resultType.widen - val claz = x.symbol.owner.asClass - val thizClass = Literal(Constant(claz.info)) - val helperModule = requiredModule("scala.runtime.LazyVals") - val getOffset = Select(ref(helperModule), lazyNme.RLazyVals.getOffset) - val getOffsetStatic = Select(ref(helperModule), lazyNme.RLazyVals.getOffsetStatic) - var offsetSymbol: TermSymbol | Null = null - var flag: Tree = EmptyTree - var ord = 0 - - def offsetName(id: Int) = s"${StdNames.nme.LAZY_FIELD_OFFSET}${if (x.symbol.owner.is(Module)) "_m_" else ""}$id".toTermName - - // compute or create appropriate offsetSymbol, bitmap and bits used by current ValDef - oldAppendOffsetDefs.get(claz) match { - case Some(info) => - val flagsPerLong = (64 / scala.runtime.LazyVals.BITS_PER_LAZY_VAL).toInt - info.ord += 1 - ord = info.ord % flagsPerLong - val id = info.ord / flagsPerLong - val offsetById = offsetName(id) - if (ord != 0) // there are unused bits in already existing flag - offsetSymbol = claz.info.decl(offsetById) - .suchThat(sym => sym.is(Synthetic) && sym.isTerm) - .symbol.asTerm - else { // need to create a new flag - offsetSymbol = newSymbol(claz, offsetById, Synthetic, defn.LongType).enteredAfter(this) - offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot)) - val flagName = LazyBitMapName.fresh(id.toString.toTermName) - val flagSymbol = newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this) - flag = ValDef(flagSymbol, Literal(Constant(0L))) - val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(flagName.toString))) - val offsetTree = ValDef(offsetSymbol.nn, getOffsetStatic.appliedTo(fieldTree)) - info.defs = offsetTree :: info.defs - } - - case None => - offsetSymbol = newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this) - offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot)) - val flagName = LazyBitMapName.fresh("0".toTermName) - val flagSymbol = newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this) - flag = ValDef(flagSymbol, Literal(Constant(0L))) - val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(flagName.toString))) - val offsetTree = ValDef(offsetSymbol.nn, getOffsetStatic.appliedTo(fieldTree)) - appendOffsetDefs += (claz -> new OffsetInfo(List(offsetTree), ord)) - } - - val containerName = LazyLocalName.fresh(x.name.asTermName) - val containerSymbol = newSymbol(claz, containerName, x.symbol.flags &~ containerFlagsMask | containerFlags, tpe, coord = x.symbol.coord).enteredAfter(this) - - val containerTree = ValDef(containerSymbol, defaultValue(tpe)) - - val offset = ref(offsetSymbol.nn) - val getFlag = Select(ref(helperModule), lazyNme.RLazyVals.get) - val setFlag = Select(ref(helperModule), lazyNme.RLazyVals.setFlag) - val wait = Select(ref(helperModule), lazyNme.RLazyVals.wait4Notification) - val state = Select(ref(helperModule), lazyNme.RLazyVals.state) - val cas = Select(ref(helperModule), lazyNme.RLazyVals.cas) - - val accessor = mkThreadSafeDefOld(x.symbol.asTerm, claz, ord, containerSymbol, x.rhs, tpe, offset, getFlag, state, cas, setFlag, wait) - if (flag eq EmptyTree) - Thicket(containerTree, accessor) - else Thicket(containerTree, flag, accessor) - } } object LazyVals { @@ -694,15 +521,14 @@ object LazyVals { val evaluating: TermName = N.evaluating.toTermName val nullValued: TermName = N.nullValued.toTermName val objCas: TermName = N.objCas.toTermName - val getOffset: TermName = N.getOffset.toTermName - val getStaticOffset: TermName = N.getStaticOffset.toTermName + val getStaticFieldOffset: TermName = N.getStaticFieldOffset.toTermName val get: TermName = N.get.toTermName val setFlag: TermName = N.setFlag.toTermName val wait4Notification: TermName = N.wait4Notification.toTermName val state: TermName = N.state.toTermName val cas: TermName = N.cas.toTermName val getOffset: TermName = N.getOffset.toTermName - val getOffsetStatic: TermName = "getOffsetStatic".toTermName + val getOffsetStatic: TermName = N.getOffsetStatic.toTermName val getDeclaredField: TermName = "getDeclaredField".toTermName } diff --git a/library/src/scala/runtime/LazyVals.scala b/library/src/scala/runtime/LazyVals.scala index 0df5fab309f3..3a3800576147 100644 --- a/library/src/scala/runtime/LazyVals.scala +++ b/library/src/scala/runtime/LazyVals.scala @@ -141,6 +141,7 @@ 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) @@ -148,10 +149,10 @@ object LazyVals { r } - def getStaticOffset(clz: Class[_], name: String): Long = { - val r = unsafe.staticFieldOffset(clz.getDeclaredField(name)) + def getStaticFieldOffset(field: java.lang.reflect.Field): Long = { + val r = unsafe.staticFieldOffset(field) if (debug) - println(s"getStaticOffset($clz, $name) = $r") + println(s"getStaticFieldOffset(${field.getDeclaringClass}, ${field.getName}) = $r") r } @@ -175,6 +176,7 @@ object LazyVals { final val wait4Notification = "wait4Notification" final val get = "get" final val getOffset = "getOffset" - final val getStaticOffset = "getStaticOffset" + final val getOffsetStatic = "getOffsetStatic" + final val getStaticFieldOffset = "getStaticFieldOffset" } } diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index e015bf4af833..f02026f440e8 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -19,12 +19,12 @@ object MiMaFilters { ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.getStaticOffset"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.getStaticOffset"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.objCAS"), + 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.getStaticOffset"), + 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"), diff --git a/tests/init/neg/promotion-loop.check b/tests/init/neg/promotion-loop.check index 24f63e7dcedd..b53dd676081f 100644 --- a/tests/init/neg/promotion-loop.check +++ b/tests/init/neg/promotion-loop.check @@ -1,11 +1,7 @@ -- Error: tests/init/neg/promotion-loop.scala:16:10 -------------------------------------------------------------------- 16 | println(b) // error | ^ -<<<<<<< HEAD - | Cannot prove that the value is fully-initialized. Only initialized values may be used as arguments. -======= | Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. ->>>>>>> 91c03b1f7835d574290eba1ecf236e0f59fc2bf0 | | The unsafe promotion may cause the following problem: | Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. diff --git a/tests/init/neg/t3273.check b/tests/init/neg/t3273.check index 04704b1d92ba..8c7f0f32e2d8 100644 --- a/tests/init/neg/t3273.check +++ b/tests/init/neg/t3273.check @@ -1,7 +1,7 @@ -- Error: tests/init/neg/t3273.scala:4:42 ------------------------------------------------------------------------------ 4 | val num1: LazyList[Int] = 1 #:: num1.map(_ + 1) // error | ^^^^^^^^^^^^^^^ - | Cannot prove that the value is fully-initialized. Only initialized values may be used as arguments. + | Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. | | The unsafe promotion may cause the following problem: | Access non-initialized value num1. Calling trace: @@ -10,7 +10,7 @@ -- Error: tests/init/neg/t3273.scala:5:61 ------------------------------------------------------------------------------ 5 | val num2: LazyList[Int] = 1 #:: num2.iterator.map(_ + 1).to(LazyList) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Cannot prove that the value is fully-initialized. Only initialized values may be used as arguments. + | Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. | | The unsafe promotion may cause the following problem: | Access non-initialized value num2. Calling trace: From 4e2561c82fa41b1d0323baa90b83eb06d4edcd59 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Wed, 18 May 2022 16:58:24 +0200 Subject: [PATCH 3/9] Pass annotations and fix transient lazy vals bug --- compiler/src/dotty/tools/dotc/transform/LazyVals.scala | 1 + tests/run/serialization-new.check | 4 ++++ tests/run/serialization-new.scala | 10 ++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index babf030eb5e0..aaf88febe440 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -462,6 +462,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { val containerName = LazyLocalName.fresh(x.name.asTermName) val containerSymbol = newSymbol(claz, containerName, containerFlags, defn.ObjectType).enteredAfter(this) containerSymbol.addAnnotation(Annotation(defn.VolatileAnnot)) // private @volatile var _x: AnyRef + containerSymbol.addAnnotations(x.symbol.annotations) // pass annotations from original definition val stat = x.symbol.isStatic if stat then containerSymbol.setFlag(JavaStatic) diff --git a/tests/run/serialization-new.check b/tests/run/serialization-new.check index 93dd1dc4fcce..7fa8f9785385 100644 --- a/tests/run/serialization-new.check +++ b/tests/run/serialization-new.check @@ -224,7 +224,11 @@ x = Paul y = Paul x equals y: true, y equals x: true +Calculating a1 1 +Calculating a2 2 +Calculating a1 1 +Calculating a2 2 diff --git a/tests/run/serialization-new.scala b/tests/run/serialization-new.scala index 85706c27a04d..cb4b9f83b30b 100644 --- a/tests/run/serialization-new.scala +++ b/tests/run/serialization-new.scala @@ -468,8 +468,14 @@ 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 + } @transient object B extends Serializable @transient private object C extends Serializable From 3476fc8b34d6d3b63399da37acd5da06b23aeef5 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Wed, 18 May 2022 17:23:01 +0200 Subject: [PATCH 4/9] Renamed new methods for lazy val transform --- .../src/dotty/tools/dotc/transform/LazyVals.scala | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index aaf88febe440..f2d68d70acc4 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -289,11 +289,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { } } - def transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket = { - assert(!(x.symbol is Mutable)) - transformMemberDefThreadSafeNew(x) - } - /** * Create a threadsafe lazy accessor equivalent to the following code: * ``` @@ -370,7 +365,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { * @param evaluating a reference to the `Evaluating` runtime object * @param nullValued a reference to the `NULL` runtime object */ - def mkThreadSafeDefNew(methodSymbol: TermSymbol, + def mkThreadSafeDef(methodSymbol: TermSymbol, claz: ClassSymbol, target: Symbol, rhs: Tree, @@ -446,10 +441,12 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { ret } - def transformMemberDefThreadSafeNew(x: ValOrDefDef)(using Context): Thicket = { + def transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket = { import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.core.Flags._ + assert(!(x.symbol is Mutable)) + val runtimeModule = "scala.runtime.LazyVals" val tpe = x.tpe.widen.resultType.widen val claz = x.symbol.owner.asClass @@ -502,7 +499,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { This(claz) val methodSymbol = x.symbol.asTerm - val accessor = mkThreadSafeDefNew(methodSymbol, claz, containerSymbol, x.rhs, tpe, offset, objCas, + val accessor = mkThreadSafeDef(methodSymbol, claz, containerSymbol, x.rhs, tpe, offset, objCas, ref(waiting), evaluating, nullValued, swapOver) Thicket(containerTree, accessor) } From 68620cde25f05d21029de7e1f2de5f573f728661 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Thu, 19 May 2022 11:13:45 +0200 Subject: [PATCH 5/9] Undo scriptPath changes --- compiler/test-resources/scripting/scriptPath.sc | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/test-resources/scripting/scriptPath.sc b/compiler/test-resources/scripting/scriptPath.sc index 837bd674aad8..46cd5e8a7385 100755 --- a/compiler/test-resources/scripting/scriptPath.sc +++ b/compiler/test-resources/scripting/scriptPath.sc @@ -16,8 +16,6 @@ val pathEntries = System.getenv("PATH").split(psep).toList System.err.printf("sun.java.command: %s\n", sys.props("sun.java.command")) System.err.printf("first 5 PATH entries:\n%s\n",pathEntries.take(5).mkString("\n")) - printf("script.path: %s\n",path.norm) - assert(path.endsWith("scriptPath.sc"),s"actual path [$path]") } extension(s: String) From 00fe8dcb223d815bb44ac314d2b54acb3a7e4109 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Thu, 19 May 2022 17:19:56 +0200 Subject: [PATCH 6/9] Adjust number of bytecode insn for Lazy Vals test & extend test --- .../test/dotty/tools/backend/jvm/DottyBytecodeTests.scala | 2 +- tests/run/serialization-new.check | 3 +++ tests/run/serialization-new.scala | 6 ++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index a867e74f0507..b2f5093660aa 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -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(122, instructionsFromMethod(method).size) + assertEquals(123, instructionsFromMethod(method).size) } } diff --git a/tests/run/serialization-new.check b/tests/run/serialization-new.check index 7fa8f9785385..65b6f809d51f 100644 --- a/tests/run/serialization-new.check +++ b/tests/run/serialization-new.check @@ -228,7 +228,10 @@ Calculating a1 1 Calculating a2 2 +Calculating a3 +3 Calculating a1 1 Calculating a2 2 +3 \ No newline at end of file diff --git a/tests/run/serialization-new.scala b/tests/run/serialization-new.scala index cb4b9f83b30b..0f6cba7cfd0f 100644 --- a/tests/run/serialization-new.scala +++ b/tests/run/serialization-new.scala @@ -476,12 +476,18 @@ class WithTransient extends Serializable { 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") } From 4353ae4be8f2e666046cb0e2f486a366d2c82338 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Fri, 20 May 2022 15:17:44 +0200 Subject: [PATCH 7/9] Replace spaces in names --- compiler/src/dotty/tools/dotc/transform/LazyVals.scala | 8 ++++---- .../test/dotty/tools/backend/jvm/DottyBytecodeTests.scala | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index f2d68d70acc4..467da3b33880 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -58,7 +58,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { else nullables.toList } - private def initBlock(stats: List[Tree])(using Context): Block = stats match case Nil => throw new IllegalArgumentException("trying to create an empty Block") case x :: Nil => Block(List(x), EmptyTree) @@ -444,7 +443,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { def transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket = { import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.core.Flags._ - + assert(!(x.symbol is Mutable)) val runtimeModule = "scala.runtime.LazyVals" @@ -457,6 +456,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { def offsetName(id: Int) = s"${StdNames.nme.LAZY_FIELD_OFFSET}${if (x.symbol.owner.is(Module)) "_m_" else ""}$id".toTermName val containerName = LazyLocalName.fresh(x.name.asTermName) + val containerNameEscaped = containerName.toString.replace(" ", "$u0020") // escape spaces in names val containerSymbol = newSymbol(claz, containerName, containerFlags, defn.ObjectType).enteredAfter(this) containerSymbol.addAnnotation(Annotation(defn.VolatileAnnot)) // private @volatile var _x: AnyRef containerSymbol.addAnnotations(x.symbol.annotations) // pass annotations from original definition @@ -475,13 +475,13 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { case Some(info) => offsetSymbol = newSymbol(claz, offsetName(info.defs.size), Synthetic, defn.LongType).enteredAfter(this) offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot)) - val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerName.toString))) + val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerNameEscaped))) val offsetTree = ValDef(offsetSymbol.nn, getOffset.appliedTo(fieldTree)) info.defs = offsetTree :: info.defs case None => offsetSymbol = newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this) offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot)) - val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerName.toString))) + val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerNameEscaped))) val offsetTree = ValDef(offsetSymbol.nn, getOffset.appliedTo(fieldTree)) appendOffsetDefs += (claz -> new OffsetInfo(List(offsetTree))) diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index b2f5093660aa..f101f0522e05 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -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(123, instructionsFromMethod(method).size) + assertEquals(113, instructionsFromMethod(method).size) } } From 889d788d753ed27411ec9f7ed8e954d833127700 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Tue, 24 May 2022 13:46:09 +0200 Subject: [PATCH 8/9] Mangle string for getDeclaredField on LazyVals --- compiler/src/dotty/tools/dotc/transform/LazyVals.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index 467da3b33880..7f3a5d911994 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -454,9 +454,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { var offsetSymbol: TermSymbol | Null = null def offsetName(id: Int) = s"${StdNames.nme.LAZY_FIELD_OFFSET}${if (x.symbol.owner.is(Module)) "_m_" else ""}$id".toTermName - val containerName = LazyLocalName.fresh(x.name.asTermName) - val containerNameEscaped = containerName.toString.replace(" ", "$u0020") // escape spaces in names val containerSymbol = newSymbol(claz, containerName, containerFlags, defn.ObjectType).enteredAfter(this) containerSymbol.addAnnotation(Annotation(defn.VolatileAnnot)) // private @volatile var _x: AnyRef containerSymbol.addAnnotations(x.symbol.annotations) // pass annotations from original definition @@ -475,13 +473,13 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { case Some(info) => offsetSymbol = newSymbol(claz, offsetName(info.defs.size), Synthetic, defn.LongType).enteredAfter(this) offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot)) - val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerNameEscaped))) + val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerName.mangledString))) val offsetTree = ValDef(offsetSymbol.nn, getOffset.appliedTo(fieldTree)) info.defs = offsetTree :: info.defs case None => offsetSymbol = newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this) offsetSymbol.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot)) - val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerNameEscaped))) + val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerName.mangledString))) val offsetTree = ValDef(offsetSymbol.nn, getOffset.appliedTo(fieldTree)) appendOffsetDefs += (claz -> new OffsetInfo(List(offsetTree))) From f279f19d5dc1eb384ed66bc41339b77e13734eaa Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Tue, 24 May 2022 16:55:57 +0200 Subject: [PATCH 9/9] Update community build --- community-build/community-projects/scala-parallel-collections | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scala-parallel-collections b/community-build/community-projects/scala-parallel-collections index a6bd648bb188..1cd213661a68 160000 --- a/community-build/community-projects/scala-parallel-collections +++ b/community-build/community-projects/scala-parallel-collections @@ -1 +1 @@ -Subproject commit a6bd648bb188a65ab36be07e956e52fe25f64d67 +Subproject commit 1cd213661a682e7c9947a1d1777526f01225da56