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 diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index 74874843b4f6..7f3a5d911994 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -23,9 +23,15 @@ 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] override def phaseName: String = LazyVals.name @@ -52,6 +58,16 @@ 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) + 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 +78,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,12 +116,12 @@ 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 { case None => template case Some(data) => @@ -115,16 +130,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 +148,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 +201,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,171 +288,218 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { } } - /** 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, ) - * } - * } - * } - * ``` - */ + /** + * Create a threadsafe lazy accessor equivalent to the following code: + * ``` + * private @volatile var _x: AnyRef = null + * @tailrec def x: A = + * _x match + * 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 + * 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 + * 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 + * return null + * else if current.isInstanceOf[Waiting] then + * current.asInstanceOf[Waiting].awaitRelease() + * else + * return 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 mkThreadSafeDef(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))) + 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 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), + val current = newSymbol(methodSymbol, lazyNme.current, Synthetic, defn.ObjectType) + val ifNotNull = If( - stateRef.equal(initState), - initialize, - waitOnLock.appliedTo(thiz, offset, flagRef, fieldId) + 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 loop = WhileDo(EmptyTree, Block(List(flagDef, stateDef), condition)) - DefDef(methodSymbol, loop) + 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 transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket = { - assert(!(x.symbol is Mutable)) + 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 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) + val helperModule = requiredModule(runtimeModule) 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 - appendOffsetDefs.get(claz) match { + 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) + val getOffset = + if stat then + Select(ref(helperModule), lazyNme.RLazyVals.getStaticFieldOffset) + else + Select(ref(helperModule), lazyNme.RLazyVals.getOffsetStatic) + val containerTree = ValDef(containerSymbol, nullLiteral) + + // create an offset for this lazy val + appendOffsetDefs.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 - } - + 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.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 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 = mkThreadSafeDef(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) + 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))) + + 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 = mkThreadSafeDef(methodSymbol, claz, containerSymbol, x.rhs, tpe, offset, objCas, + ref(waiting), evaluating, nullValued, swapOver) + Thicket(containerTree, accessor) } } @@ -450,14 +511,22 @@ 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 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 + } val flag: TermName = "flag".toTermName val state: TermName = "state".toTermName @@ -466,5 +535,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/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index d21cf929b840..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(88, instructionsFromMethod(method).size) + assertEquals(113, instructionsFromMethod(method).size) } } diff --git a/library/src/scala/runtime/LazyVals.scala b/library/src/scala/runtime/LazyVals.scala index f3fe52beb9ee..3a3800576147 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)") @@ -99,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) @@ -106,6 +149,13 @@ object LazyVals { 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) @@ -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" } } diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index e8f82f61de9c..f02026f440e8 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.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"), ) } 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 diff --git a/tests/run/serialization-new.check b/tests/run/serialization-new.check index 93dd1dc4fcce..65b6f809d51f 100644 --- a/tests/run/serialization-new.check +++ b/tests/run/serialization-new.check @@ -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 \ No newline at end of file diff --git a/tests/run/serialization-new.scala b/tests/run/serialization-new.scala index 85706c27a04d..0f6cba7cfd0f 100644 --- a/tests/run/serialization-new.scala +++ b/tests/run/serialization-new.scala @@ -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") }