diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/ContendedInitialization.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/ContendedInitialization.scala new file mode 100644 index 000000000000..fb2cedbb7d41 --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/ContendedInitialization.scala @@ -0,0 +1,49 @@ +package dotty.tools.benchmarks.lazyvals + +import org.openjdk.jmh.annotations._ +import LazyVals.LazyHolder +import org.openjdk.jmh.infra.Blackhole +import java.util.concurrent.TimeUnit +import java.util.concurrent.{Executors, ExecutorService} + +@BenchmarkMode(Array(Mode.AverageTime)) +@Fork(2) +@Threads(1) +@Warmup(iterations = 5) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +class ContendedInitialization { + + @Param(Array("2000000", "5000000")) + var size: Int = _ + + @Param(Array("2", "4", "8")) + var nThreads: Int = _ + + var executor: ExecutorService = _ + + @Setup + def prepare: Unit = { + executor = Executors.newFixedThreadPool(nThreads) + } + + @TearDown + def cleanup: Unit = { + executor.shutdown() + executor = null + } + + @Benchmark + def measureContended(bh: Blackhole): Unit = { + val array = Array.fill(size)(new LazyHolder) + val task: Runnable = () => + for (elem <- array) bh.consume(elem.value) + + val futures = + for (_ <- 0 until nThreads) yield + executor.submit(task) + + futures.foreach(_.get()) + } +} diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccess.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccess.scala new file mode 100644 index 000000000000..a2aaf3e88570 --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccess.scala @@ -0,0 +1,30 @@ +package dotty.tools.benchmarks.lazyvals + +import org.openjdk.jmh.annotations._ +import LazyVals.LazyHolder +import org.openjdk.jmh.infra.Blackhole +import java.util.concurrent.TimeUnit + +@BenchmarkMode(Array(Mode.AverageTime)) +@Fork(2) +@Threads(1) +@Warmup(iterations = 5) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +class InitializedAccess { + + var holder: LazyHolder = _ + + @Setup + def prepare: Unit = { + holder = new LazyHolder + holder.value + } + + @Benchmark + def measureInitialized(bh: Blackhole) = { + bh.consume(holder) + bh.consume(holder.value) + } +} diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessAny.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessAny.scala new file mode 100644 index 000000000000..5a6b4ae1686d --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessAny.scala @@ -0,0 +1,30 @@ +package dotty.tools.benchmarks.lazyvals + +import org.openjdk.jmh.annotations._ +import LazyVals.LazyAnyHolder +import org.openjdk.jmh.infra.Blackhole +import java.util.concurrent.TimeUnit + +@BenchmarkMode(Array(Mode.AverageTime)) +@Fork(2) +@Threads(1) +@Warmup(iterations = 5) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +class InitializedAccessAny { + + var holder: LazyAnyHolder = _ + + @Setup + def prepare: Unit = { + holder = new LazyAnyHolder + holder.value + } + + @Benchmark + def measureInitialized(bh: Blackhole) = { + bh.consume(holder) + bh.consume(holder.value) + } +} diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessGeneric.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessGeneric.scala new file mode 100644 index 000000000000..a95cb1de2980 --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessGeneric.scala @@ -0,0 +1,30 @@ +package dotty.tools.benchmarks.lazyvals + +import org.openjdk.jmh.annotations._ +import LazyVals.LazyGenericHolder +import org.openjdk.jmh.infra.Blackhole +import java.util.concurrent.TimeUnit + +@BenchmarkMode(Array(Mode.AverageTime)) +@Fork(2) +@Threads(1) +@Warmup(iterations = 5) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +class InitializedAccessGeneric { + + var holder: LazyGenericHolder[String] = _ + + @Setup + def prepare: Unit = { + holder = new LazyGenericHolder[String]("foo") + holder.value + } + + @Benchmark + def measureInitialized(bh: Blackhole) = { + bh.consume(holder) + bh.consume(holder.value) + } +} diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessMultiple.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessMultiple.scala new file mode 100644 index 000000000000..4f3c75fd920b --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessMultiple.scala @@ -0,0 +1,34 @@ +package dotty.tools.benchmarks.lazyvals + +import org.openjdk.jmh.annotations._ +import LazyVals.LazyHolder +import org.openjdk.jmh.infra.Blackhole +import java.util.concurrent.TimeUnit + +@BenchmarkMode(Array(Mode.AverageTime)) +@Fork(2) +@Threads(1) +@Warmup(iterations = 5) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +class InitializedAccessMultiple { + + var holders: Array[LazyHolder] = _ + + @Setup + def prepare: Unit = { + holders = Array.fill(100){ new LazyHolder } + } + + @Benchmark + def measureInitialized(bh: Blackhole) = { + var i = 0 + while(i < 100) { + val currentHolder = holders(i) + bh.consume(currentHolder) + bh.consume(currentHolder.value) + i = i + 1 + } + } +} diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessString.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessString.scala new file mode 100644 index 000000000000..25cc0f9b288d --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessString.scala @@ -0,0 +1,30 @@ +package dotty.tools.benchmarks.lazyvals + +import org.openjdk.jmh.annotations._ +import LazyVals.LazyStringHolder +import org.openjdk.jmh.infra.Blackhole +import java.util.concurrent.TimeUnit + +@BenchmarkMode(Array(Mode.AverageTime)) +@Fork(2) +@Threads(1) +@Warmup(iterations = 5) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +class InitializedAccessString { + + var holder: LazyStringHolder = _ + + @Setup + def prepare: Unit = { + holder = new LazyStringHolder + holder.value + } + + @Benchmark + def measureInitialized(bh: Blackhole) = { + bh.consume(holder) + bh.consume(holder.value) + } +} diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/LazyVals.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/LazyVals.scala new file mode 100644 index 000000000000..0afd93d086be --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/LazyVals.scala @@ -0,0 +1,53 @@ +package dotty.tools.benchmarks.lazyvals +import java.util.concurrent.CountDownLatch +object LazyVals { + + trait Foo + class Bar1 extends Foo + class Bar2 extends Foo + class Bar3 extends Foo + class Bar4 extends Foo + class Bar5 extends Bar4 + + class LazyStringHolder { + + lazy val value: String = { + System.nanoTime() % 5 match { + case 0 => "abc" + case 1 => "def" + case 2 => "ghi" + case 3 => "jkl" + case 4 => "mno" + } + } + } + + class LazyHolder { + + lazy val value: List[Int] = { + System.nanoTime() % 5 match { + case 0 => 1 :: 2 :: Nil + case 1 => Nil + case 2 => 1 :: Nil + case 3 => Nil + case 4 => 1 :: 2 :: 3 :: Nil + } + } + } + + class LazyGenericHolder[A](v: => A) { + lazy val value: A = v + } + + class LazyAnyHolder { + lazy val value: Any = { + System.nanoTime() % 5 match { + case 0 => new Bar1 + case 1 => new Bar2 + case 2 => new Bar3 + case 3 => new Bar4 + case 4 => new Bar4 + } + } + } +} diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/UninitializedAccess.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/UninitializedAccess.scala new file mode 100644 index 000000000000..417d22f67d48 --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/UninitializedAccess.scala @@ -0,0 +1,25 @@ +package dotty.tools.benchmarks.lazyvals + +import org.openjdk.jmh.annotations._ +import LazyVals.LazyHolder +import org.openjdk.jmh.infra.Blackhole +import java.util.concurrent.TimeUnit + +@BenchmarkMode(Array(Mode.AverageTime)) +@Fork(2) +@Threads(1) +@Warmup(iterations = 5) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +class UninitializedAccess { + + @Benchmark + def measureInitialized(bh: Blackhole) = { + var i = 0 + val holder = new LazyHolder + bh.consume(holder) + bh.consume(holder.value) + i = i + 1 + } +} diff --git a/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/UninitializedAccessMultiple.scala b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/UninitializedAccessMultiple.scala new file mode 100644 index 000000000000..133a0932bf51 --- /dev/null +++ b/bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/UninitializedAccessMultiple.scala @@ -0,0 +1,27 @@ +package dotty.tools.benchmarks.lazyvals + +import org.openjdk.jmh.annotations._ +import LazyVals.LazyHolder +import org.openjdk.jmh.infra.Blackhole +import java.util.concurrent.TimeUnit + +@BenchmarkMode(Array(Mode.AverageTime)) +@Fork(2) +@Threads(1) +@Warmup(iterations = 5) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +class UninitializedAccessMultiple { + + @Benchmark + def measureInitialized(bh: Blackhole) = { + var i = 0 + while(i < 100) { + val holder = new LazyHolder + bh.consume(holder) + bh.consume(holder.value) + i = i + 1 + } + } +} diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index a2dba94ad9fc..09bedd3e8b35 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -330,6 +330,7 @@ private sealed trait YSettings: val YrecheckTest: Setting[Boolean] = BooleanSetting("-Yrecheck-test", "Run basic rechecking (internal test only)") val YccDebug: Setting[Boolean] = BooleanSetting("-Ycc-debug", "Used in conjunction with captureChecking language import, debug info for captured references") val YccNoAbbrev: Setting[Boolean] = BooleanSetting("-Ycc-no-abbrev", "Used in conjunction with captureChecking language import, suppress type abbreviations") + val YlightweightLazyVals: Setting[Boolean] = BooleanSetting("-Ylightweight-lazy-vals", "Use experimental lightweight implementation of lazy vals") /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b88e7c88d449..174244b4a456 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1990,6 +1990,12 @@ class Definitions { addSyntheticSymbolsComments } + /** Definitions used in Lazy Vals implementation */ + val LazyValsModuleName = "scala.runtime.LazyVals" + @tu lazy val LazyValsModule = requiredModule(LazyValsModuleName) + @tu lazy val LazyValsWaitingState = requiredClass(s"$LazyValsModuleName.Waiting") + @tu lazy val LazyValsControlState = requiredClass(s"$LazyValsModuleName.LazyValControlState") + def addSyntheticSymbolsComments(using Context): Unit = def add(sym: Symbol, doc: String) = ctx.docCtx.foreach(_.addDocstring(sym, Some(Comment(NoSpan, doc)))) diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index c32ea61cff2b..3b37ef130231 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -2,30 +2,33 @@ package dotty.tools.dotc package transform import java.util.IdentityHashMap - import ast.tpd import core.Annotations.Annotation import core.Constants.Constant -import core.Contexts._ -import core.Decorators._ +import core.Contexts.* +import core.Decorators.* import core.DenotTransformers.IdentityDenotTransformer -import core.Flags._ -import core.NameKinds.{LazyBitMapName, LazyLocalInitName, LazyLocalName, ExpandedName} +import core.Flags.* +import core.NameKinds.{ExpandedName, LazyBitMapName, LazyLocalInitName, LazyLocalName} import core.StdNames.nme -import core.Symbols._ -import core.Types._ +import core.Symbols.* +import core.Types.* import core.{Names, StdNames} +import dotty.tools.dotc.config.Feature import transform.MegaPhase.MiniPhase -import transform.SymUtils._ +import transform.SymUtils.* + import scala.collection.mutable 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], var ord: Int = 0) + private val appendOffsetDefs = mutable.Map.empty[Symbol, OffsetInfo] override def phaseName: String = LazyVals.name @@ -52,6 +55,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { else nullables.toList } + private def needsBoxing(tp: Type)(using Context): Boolean = tp.classSymbol.isPrimitiveValueClass override def prepareForUnit(tree: Tree)(using Context): Context = { if (lazyValNullables == null) @@ -62,7 +66,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) @@ -103,10 +106,9 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { /** 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,7 +117,6 @@ 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 @@ -186,7 +187,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 +274,231 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { } } + /** + * 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 + * + * def x: A = + * 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 + * x_compute() // possible unboxing applied here + * + * private def x_compute(): AnyRef = + * while do + * val current: AnyRef = _x + * if current.eq(null) then + * if CAS(_x, null, Evaluating) then + * var resultNullable: AnyRef = null + * var result: AnyRef = null + * try + * resultNullable = rhs + * nullable = null // nulls out the nullable fields used only in initialization + * if resultNullable.eq(null) then + * result = NullValue + * else + * result = resultNullable + * 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.eq(Evaluating) then // To avoid creating Waiting instance + * CAS(current, current, new Waiting) + * else if current.isInstanceOf[Waiting] then + * current.asInstanceOf[Waiting].await() + * else return null + * else + * return current + * 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 + * @param offset the offset of the field in the storage allocation of the class + * @param thiz a reference to the transformed class + */ + def mkThreadSafeDef(memberDef: ValOrDefDef, + claz: ClassSymbol, + target: Symbol, + offset: Tree, + thiz: Tree)(using Context): (DefDef, DefDef) = { + 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) + val nullValue = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.nullValue) + val objCasFlag = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.objCas) + val accessorMethodSymbol = memberDef.symbol.asTerm + val lazyInitMethodName = LazyLocalInitName.fresh(memberDef.name.asTermName) + val lazyInitMethodSymbol = newSymbol(claz, lazyInitMethodName, Synthetic | Method | Private, MethodType(Nil)(_ => Nil, _ => defn.ObjectType)) + + val rhs = memberDef.rhs + val rhsMappedOwner = rhs.changeOwnerAfter(memberDef.symbol, lazyInitMethodSymbol, this) + val valueSymbol = newSymbol(accessorMethodSymbol, lazyNme.result, Synthetic, defn.ObjectType) + + val immediateValueCondition = + if (defn.LazyValsControlState.isSubClass(tp.classSymbol)) then + ref(valueSymbol).select(defn.Any_!=).appliedTo(nullLiteral).select(nme.And).appliedTo(ref(valueSymbol) + .select(defn.Any_isInstanceOf).appliedToType(defn.LazyValsControlState.typeRef) + .select(nme.UNARY_!).appliedToNone) + else + ref(valueSymbol).select(defn.Any_isInstanceOf).appliedToType(tp) + + val accessorBody = + Block( + ValDef(valueSymbol, ref(target)) :: Nil, + If( // if _x != null && !_x.isInstanceOf[LazyValControlState] then + immediateValueCondition, + ref(valueSymbol).ensureConforms(tp), // then return _x.asInstanceOf[A] + If( + ref(valueSymbol).select(defn.Object_eq).appliedTo(nullValue), + nullLiteral.ensureConforms(tp), + ref(lazyInitMethodSymbol).ensureApplied.ensureConforms(tp) // else return x_compute() + ) + ) + ) + + val accessorDef = DefDef(accessorMethodSymbol, accessorBody) + + // if observed a null (uninitialized) value + val initialize = { + // var result: AnyRef + val resSymbNullable = newSymbol(lazyInitMethodSymbol, lazyNme.resultNullable, Synthetic | Mutable, defn.ObjectType) + val resSymb = newSymbol(lazyInitMethodSymbol, lazyNme.result, Synthetic | Mutable, defn.ObjectType) + // releasing block in finally + val lockRel = { + val lockSymb = newSymbol(lazyInitMethodSymbol, lazyNme.lock, Synthetic, waiting.typeOpt) + Block(ValDef(lockSymb, ref(target).cast(waiting.typeOpt)) + :: objCasFlag.appliedTo(thiz, offset, ref(lockSymb), ref(resSymb)) :: Nil, + ref(lockSymb).select(lazyNme.RLazyVals.waitingRelease).ensureApplied) + } + // finally block + val fin = If( + objCasFlag.appliedTo(thiz, offset, evaluating, ref(resSymb)).select(nme.UNARY_!).appliedToNone, + lockRel, + unitLiteral + ) + // entire try block + val evaluate = Try( + + Block( + (Assign(ref(resSymbNullable), if needsBoxing(tp) && rhsMappedOwner != EmptyTree then rhsMappedOwner.ensureConforms(defn.boxedType(tp)) else rhsMappedOwner) // try result = rhs + :: If( + ref(resSymbNullable).select(defn.Object_eq).appliedTo(nullLiteral), + Assign(ref(resSymb), nullValue), + Assign(ref(resSymb), ref(resSymbNullable)) + ) :: Nil) + ::: nullOut(nullableFor(accessorMethodSymbol)), + unitLiteral), + Nil, + fin + ) + // if CAS(_, null, Evaluating) + If( + objCasFlag.appliedTo(thiz, offset, nullLiteral, evaluating), + Block(ValDef(resSymb, nullLiteral) :: ValDef(resSymbNullable, nullLiteral) :: evaluate :: Nil, // var result: AnyRef = null + Return(ref(resSymbNullable), lazyInitMethodSymbol)), + unitLiteral + ).withType(defn.UnitType) + } + + val current = newSymbol(lazyInitMethodSymbol, lazyNme.current, Synthetic, defn.ObjectType) + val ifNotUninitialized = + If( + ref(current).select(defn.Any_isInstanceOf).appliedToTypeTree(controlState), + // if a control state + If( + ref(current).select(defn.Object_eq).appliedTo(evaluating), + // if is Evaluating then CAS(_, Evaluating, new Waiting) + Block( + objCasFlag.appliedTo(thiz, offset, ref(current), Select(New(waiting), StdNames.nme.CONSTRUCTOR).ensureApplied) :: Nil, + unitLiteral + ), + // if not Evaluating + If( + ref(current).select(defn.Any_isInstanceOf).appliedToTypeTree(waiting), + // if is waiting + ref(current).select(defn.Any_asInstanceOf).appliedToTypeTree(waiting).select(lazyNme.RLazyVals.waitingAwaitRelease, _.info.paramInfoss.exists(_.size == 0)).ensureApplied, + Return(nullLiteral, lazyInitMethodSymbol) + ) + ), + // if not a control state + Return(ref(current), lazyInitMethodSymbol) + ) + + 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) + } + + def transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket = { + assert(!(x.symbol is Mutable)) + if ctx.settings.YlightweightLazyVals.value then + transformMemberDefThreadSafeNew(x) + else + transformMemberDefThreadSafeLegacy(x) + } + + def transformMemberDefThreadSafeNew(x: ValOrDefDef)(using Context): Thicket = { + import dotty.tools.dotc.core.Types._ + import dotty.tools.dotc.core.Flags._ + + val claz = x.symbol.owner.asClass + val thizClass = Literal(Constant(claz.info)) + + 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, x.symbol.flags &~ containerFlagsMask | containerFlags | Private, defn.ObjectType, coord = x.symbol.coord).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(defn.LazyValsModule), lazyNme.RLazyVals.getStaticFieldOffset) + else + Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.getOffsetStatic) + val containerTree = ValDef(containerSymbol, nullLiteral) + + // create an offset for this lazy val + val offsetSymbol: TermSymbol = appendOffsetDefs.get(claz) match + case Some(info) => + newSymbol(claz, offsetName(info.defs.size), Synthetic, defn.LongType).enteredAfter(this) + case None => + 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.mangledString))) + val offsetTree = ValDef(offsetSymbol.nn, getOffset.appliedTo(fieldTree)) + val offsetInfo = appendOffsetDefs.getOrElseUpdate(claz, new OffsetInfo(Nil)) + offsetInfo.defs = offsetTree :: offsetInfo.defs + val offset = ref(offsetSymbol.nn) + + val swapOver = + if stat then + tpd.clsOf(x.symbol.owner.typeRef) + else + This(claz) + + val (accessorDef, initMethodDef) = mkThreadSafeDef(x, claz, containerSymbol, offset, swapOver) + Thicket(containerTree, accessorDef, initMethodDef) + } + /** Create a threadsafe lazy accessor equivalent to such code * ``` * def methodSymbol(): Int = { @@ -305,7 +530,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { * } * ``` */ - def mkThreadSafeDef(methodSymbol: TermSymbol, + def mkThreadSafeDefLegacy(methodSymbol: TermSymbol, claz: ClassSymbol, ord: Int, target: Symbol, @@ -374,15 +599,12 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { DefDef(methodSymbol, loop) } - def transformMemberDefThreadSafe(x: ValOrDefDef)(using Context): Thicket = { - assert(!(x.symbol is Mutable)) - + def transformMemberDefThreadSafeLegacy(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) + val getOffset = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.getOffset) + val getOffsetStatic = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.getOffsetStatic) var offsetSymbol: TermSymbol | Null = null var flag: Tree = EmptyTree var ord = 0 @@ -425,17 +647,16 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { 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 getFlag = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.get) + val setFlag = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.setFlag) + val wait = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.wait4Notification) + val state = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.state) + val cas = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.cas) - val accessor = mkThreadSafeDef(x.symbol.asTerm, claz, ord, containerSymbol, x.rhs, tpe, offset, getFlag, state, cas, setFlag, wait) + val accessor = mkThreadSafeDefLegacy(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) @@ -445,26 +666,35 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { object LazyVals { val name: String = "lazyVals" val description: String = "expand lazy vals" - object lazyNme { import Names.TermName object RLazyVals { import scala.runtime.LazyVals.{Names => N} - 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 getDeclaredField: TermName = "getDeclaredField".toTermName + val waitingAwaitRelease: TermName = "await".toTermName + val waitingRelease: TermName = "countDown".toTermName + val evaluating: TermName = "Evaluating".toTermName + val nullValue: TermName = "NullValue".toTermName + val objCas: TermName = "objCAS".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 getStaticFieldOffset: TermName = "getStaticFieldOffset".toTermName + val getDeclaredField: TermName = "getDeclaredField".toTermName } val flag: TermName = "flag".toTermName val state: TermName = "state".toTermName val result: TermName = "result".toTermName + val resultNullable: TermName = "resultNullable".toTermName val value: TermName = "value".toTermName 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/dotc/pos-lazy-vals-tests.allowlist b/compiler/test/dotc/pos-lazy-vals-tests.allowlist new file mode 100644 index 000000000000..21667a9265d7 --- /dev/null +++ b/compiler/test/dotc/pos-lazy-vals-tests.allowlist @@ -0,0 +1,37 @@ +Repeated.scala +byname-implicits-8.scala +existentials.scala +i1235.scala +i13332super.scala +i13349.scala +i13460.scala +i14626.scala +i1753.scala +i4031.scala +i4328.scala +i6450.scala +i6565.scala +i8031.scala +i8111.scala +i8900-unflip.scala +lazyvals.scala +singletons.scala +spec-traits.scala +spurious-overload.scala +t1591_pos.scala +t2910.scala +t3411.scala +t3420.scala +t3452f.scala +t3670.scala +t3927.scala +t4432.scala +t4716.scala +t4717.scala +t5099.scala +t5796.scala +t6278-synth-def.scala +t6925b.scala +t7011.scala +t8306.scala +zipped.scala diff --git a/compiler/test/dotc/run-lazy-vals-tests.allowlist b/compiler/test/dotc/run-lazy-vals-tests.allowlist new file mode 100644 index 000000000000..98973dc2893d --- /dev/null +++ b/compiler/test/dotc/run-lazy-vals-tests.allowlist @@ -0,0 +1,66 @@ +IArrayOps.scala +Lazies1.scala +Lazies2.scala +OrderingTest.scala +anon-mirror-gen-local.scala +byname-implicits-28.scala +byname-implicits-30.scala +byname-implicits-5.scala +exports.scala +i13146.scala +i13146a.scala +i13332a.scala +i13332shapeless.scala +i13358.scala +i1692.scala +i1692b.scala +i1856.scala +i2266.scala +i2275.scala +i4451.scala +i4559.scala +i5340.scala +i5350.scala +i7675.scala +i9473.scala +isInstanceOf-eval.scala +lazy-exprs.scala +lazy-impl.scala +lazy-implicit-lists.scala +lazy-override-run.scala +lazy-traits.scala +lazyVals.scala +lazyVals_c3.0.0.scala +lazyVals_c3.1.0.scala +nonLocalReturns.scala +nothing-lazy-val.scala +null-lazy-val.scala +patmatch-classtag.scala +priorityQueue.scala +serialization-new-legacy.scala +serialization-new.scala +singletons.scala +statics.scala +stream_flatmap_odds.scala +t1535.scala +t1591.scala +t2333.scala +t3038.scala +t3670.scala +t3699.scala +t3877.scala +t3895.scala +t3980.scala +t429.scala +t5552.scala +t5610a.scala +t603.scala +t6272.scala +t6443-by-name.scala +t6443-varargs.scala +t704.scala +t7406.scala +t8245.scala +unapply.scala +unit-lazy-val.scala +view-iterator-stream.scala diff --git a/compiler/test/dotty/tools/TestSources.scala b/compiler/test/dotty/tools/TestSources.scala index c4d36b16c90b..6961a61b69b6 100644 --- a/compiler/test/dotty/tools/TestSources.scala +++ b/compiler/test/dotty/tools/TestSources.scala @@ -13,21 +13,26 @@ object TestSources { def posFromTastyBlacklistFile: String = "compiler/test/dotc/pos-from-tasty.blacklist" def posTestPicklingBlacklistFile: String = "compiler/test/dotc/pos-test-pickling.blacklist" - def posTestRecheckExcludesFile = "compiler/test/dotc/pos-test-recheck.excludes" + def posTestRecheckExcludesFile: String = "compiler/test/dotc/pos-test-recheck.excludes" + def posLazyValsAllowlistFile: String = "compiler/test/dotc/pos-lazy-vals-tests.allowlist" def posFromTastyBlacklisted: List[String] = loadList(posFromTastyBlacklistFile) def posTestPicklingBlacklisted: List[String] = loadList(posTestPicklingBlacklistFile) - def posTestRecheckExcluded = loadList(posTestRecheckExcludesFile) + def posTestRecheckExcluded: List[String] = loadList(posTestRecheckExcludesFile) + def posLazyValsAllowlist: List[String] = loadList(posLazyValsAllowlistFile) // run tests lists def runFromTastyBlacklistFile: String = "compiler/test/dotc/run-from-tasty.blacklist" def runTestPicklingBlacklistFile: String = "compiler/test/dotc/run-test-pickling.blacklist" - def runTestRecheckExcludesFile = "compiler/test/dotc/run-test-recheck.excludes" + def runTestRecheckExcludesFile: String = "compiler/test/dotc/run-test-recheck.excludes" + def runLazyValsAllowlistFile: String = "compiler/test/dotc/run-lazy-vals-tests.allowlist" + def runFromTastyBlacklisted: List[String] = loadList(runFromTastyBlacklistFile) def runTestPicklingBlacklisted: List[String] = loadList(runTestPicklingBlacklistFile) - def runTestRecheckExcluded = loadList(runTestRecheckExcludesFile) + def runTestRecheckExcluded: List[String] = loadList(runTestRecheckExcludesFile) + def runLazyValsAllowlist: List[String] = loadList(runLazyValsAllowlistFile) // load lists diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 915e4e5f2e50..261e6af21927 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -43,6 +43,8 @@ class CompilationTests { compileFilesInDir("tests/pos-custom-args/captures", defaultOptions.and("-language:experimental.captureChecking")), compileFilesInDir("tests/pos-custom-args/erased", defaultOptions.and("-language:experimental.erasedDefinitions")), compileFilesInDir("tests/pos", defaultOptions.and("-Ysafe-init")), + // Run tests for experimental lightweight lazy vals + compileFilesInDir("tests/pos", defaultOptions.and("-Ysafe-init", "-Ylightweight-lazy-vals"), FileFilter.include(TestSources.posLazyValsAllowlist)), compileFilesInDir("tests/pos-deep-subtype", allowDeepSubtypes), compileFilesInDir("tests/pos-custom-args/no-experimental", defaultOptions.and("-Yno-experimental")), compileDir("tests/pos-special/java-param-names", defaultOptions.withJavacOnlyOptions("-parameters")), @@ -215,7 +217,9 @@ class CompilationTests { compileDir("tests/run-custom-args/Xmacro-settings/compileTimeEnv", defaultOptions.and("-Xmacro-settings:a,b=1,c.b.a=x.y.z=1,myLogger.level=INFO")), compileFilesInDir("tests/run-custom-args/captures", allowDeepSubtypes.and("-language:experimental.captureChecking")), compileFilesInDir("tests/run-deep-subtype", allowDeepSubtypes), - compileFilesInDir("tests/run", defaultOptions.and("-Ysafe-init")) + compileFilesInDir("tests/run", defaultOptions.and("-Ysafe-init"), FileFilter.exclude("serialization-new.scala")), + // Run tests for experimental lightweight lazy vals and stable lazy vals. + compileFilesInDir("tests/run", defaultOptions.and("-Ysafe-init", "-Ylightweight-lazy-vals"), FileFilter.include(TestSources.runLazyValsAllowlist)), ).checkRuns() } diff --git a/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala b/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala index 710ceee0a7c0..639b04089abc 100644 --- a/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala +++ b/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala @@ -19,20 +19,25 @@ import dotty.tools.io.Directory import scala.io.Source import org.junit.Test - +import scala.util.Using +import java.io.File class PrintingTest { - def options(phase: String) = - List(s"-Xprint:$phase", "-color:never", "-classpath", TestConfiguration.basicClasspath) + def options(phase: String, flags: List[String]) = + List(s"-Xprint:$phase", "-color:never", "-classpath", TestConfiguration.basicClasspath) ::: flags private def compileFile(path: JPath, phase: String): Boolean = { val baseFilePath = path.toString.stripSuffix(".scala") val checkFilePath = baseFilePath + ".check" + val flagsFilePath = baseFilePath + ".flags" val byteStream = new ByteArrayOutputStream() val reporter = TestReporter.reporter(new PrintStream(byteStream), INFO) + val flags = + if (!(new File(flagsFilePath)).exists) Nil + else Using(Source.fromFile(flagsFilePath, StandardCharsets.UTF_8.name))(_.getLines().toList).get try { - Main.process((path.toString::options(phase)).toArray, reporter, null) + Main.process((path.toString :: options(phase, flags)).toArray, reporter, null) } catch { case e: Throwable => println(s"Compile $path exception:") @@ -63,4 +68,7 @@ class PrintingTest { @Test def untypedPrinting: Unit = testIn("tests/printing/untyped", "parser") + + @Test + def transformedPrinting: Unit = testIn("tests/printing/transformed", "repeatableAnnotations") } diff --git a/library/src/scala/runtime/LazyVals.scala b/library/src/scala/runtime/LazyVals.scala index 7a66639a826a..0bb78aee94ad 100644 --- a/library/src/scala/runtime/LazyVals.scala +++ b/library/src/scala/runtime/LazyVals.scala @@ -1,5 +1,7 @@ package scala.runtime +import java.util.concurrent.CountDownLatch + import scala.annotation.* /** @@ -25,6 +27,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) @@ -40,6 +43,30 @@ object LazyVals { /* ------------- Start of public API ------------- */ + @experimental + sealed trait LazyValControlState + + /** + * Used to indicate the state of a lazy val that is being + * evaluated and of which other threads await the result. + */ + @experimental + final class Waiting extends CountDownLatch(1) with LazyValControlState + + /** + * Used to indicate the state of a lazy val that is currently being + * evaluated with no other thread awaiting its result. + */ + @experimental + object Evaluating extends LazyValControlState + + /** + * Used to indicate the state of a lazy val that has been evaluated to + * `null`. + */ + @experimental + object NullValue extends LazyValControlState + final val BITS_PER_LAZY_VAL = 2L def STATE(cur: Long, ord: Int): Long = { @@ -57,6 +84,13 @@ object LazyVals { unsafe.compareAndSwapLong(t, offset, e, n) } + @experimental + 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)") @@ -102,6 +136,7 @@ object LazyVals { unsafe.getLongVolatile(t, off) } + // kept for backward compatibility def getOffset(clz: Class[_], name: String): Long = { @nowarn val r = unsafe.objectFieldOffset(clz.getDeclaredField(name)) @@ -110,6 +145,15 @@ object LazyVals { r } + @experimental + def getStaticFieldOffset(field: java.lang.reflect.Field): Long = { + @nowarn + val r = unsafe.staticFieldOffset(field) + if (debug) + println(s"getStaticFieldOffset(${field.getDeclaringClass}, ${field.getName}) = $r") + r + } + def getOffsetStatic(field: java.lang.reflect.Field) = @nowarn val r = unsafe.objectFieldOffset(field) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 12ae3487cade..81510d22d2c2 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -4,6 +4,16 @@ import com.typesafe.tools.mima.core._ object MiMaFilters { val Library: Seq[ProblemFilter] = Seq( ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.MappedAlternative"), + + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.getStaticFieldOffset"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.LazyVals.objCAS"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$LazyValControlState"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$Evaluating$"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$NullValue$"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.LazyVals$Waiting"), + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.LazyVals.Evaluating"), + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.LazyVals.NullValue"), + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.pureFunctions"), ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.captureChecking"), ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$pureFunctions$"), diff --git a/tests/printing/transformed/lazy-vals-legacy.check b/tests/printing/transformed/lazy-vals-legacy.check new file mode 100644 index 000000000000..a08148e441d6 --- /dev/null +++ b/tests/printing/transformed/lazy-vals-legacy.check @@ -0,0 +1,59 @@ +[[syntax trees at end of MegaPhase{dropOuterAccessors, checkNoSuperThis, flatten, transformWildcards, moveStatic, expandPrivate, restoreScopes, selectStatic, Collect entry points, collectSuperCalls, repeatableAnnotations}]] // tests/printing/transformed/lazy-vals-legacy.scala +package { + @SourceFile("tests/printing/transformed/lazy-vals-legacy.scala") final module + class + A extends Object { + def (): Unit = + { + super() + () + } + @static private def (): Unit = + { + A.OFFSET$_m_0 = + scala.runtime.LazyVals.getOffsetStatic( + classOf[Object {...}].getDeclaredField("0bitmap$1") + ) + () + } + @static @static val OFFSET$_m_0: Long = + scala.runtime.LazyVals.getOffsetStatic( + classOf[Object {...}].getDeclaredField("0bitmap$1") + ) + lazy var 0bitmap$1: Long = 0L + private def writeReplace(): Object = + new scala.runtime.ModuleSerializationProxy(classOf[A]) + lazy var x$lzy1: Int = 0 + lazy def x(): Int = + while do + { + val flag: Long = scala.runtime.LazyVals.get(this, A.OFFSET$_m_0) + val state: Long = scala.runtime.LazyVals.STATE(flag, 0) + if state.==(3) then return A.x$lzy1 else + if state.==(0) then + if scala.runtime.LazyVals.CAS(this, A.OFFSET$_m_0, flag, 1, 0) + then + + try + { + val result: Int = 2 + A.x$lzy1 = result + scala.runtime.LazyVals.setFlag(this, A.OFFSET$_m_0, 3, 0) + return result + } + catch + { + case ex @ ex => + scala.runtime.LazyVals.setFlag(this, A.OFFSET$_m_0, 0, 0) + throw ex + } + else () + else + scala.runtime.LazyVals.wait4Notification(this, A.OFFSET$_m_0, flag + , + 0) + } + } + final lazy module val A: A = new A() +} + diff --git a/tests/printing/transformed/lazy-vals-legacy.scala b/tests/printing/transformed/lazy-vals-legacy.scala new file mode 100644 index 000000000000..7240286af56a --- /dev/null +++ b/tests/printing/transformed/lazy-vals-legacy.scala @@ -0,0 +1,3 @@ +object A { + lazy val x: Int = 2 +} diff --git a/tests/printing/transformed/lazy-vals-new.check b/tests/printing/transformed/lazy-vals-new.check new file mode 100644 index 000000000000..521f5a6e51a6 --- /dev/null +++ b/tests/printing/transformed/lazy-vals-new.check @@ -0,0 +1,101 @@ +[[syntax trees at end of MegaPhase{dropOuterAccessors, checkNoSuperThis, flatten, transformWildcards, moveStatic, expandPrivate, restoreScopes, selectStatic, Collect entry points, collectSuperCalls, repeatableAnnotations}]] // tests/printing/transformed/lazy-vals-new.scala +package { + @SourceFile("tests/printing/transformed/lazy-vals-new.scala") final module + class + A extends Object { + def (): Unit = + { + super() + () + } + @static private def (): Unit = + { + A.OFFSET$_m_0 = + scala.runtime.LazyVals.getStaticFieldOffset( + classOf[Object {...}].getDeclaredField("x$lzy1") + ) + () + } + @static @static val OFFSET$_m_0: Long = + scala.runtime.LazyVals.getStaticFieldOffset( + classOf[Object {...}].getDeclaredField("x$lzy1") + ) + private def writeReplace(): Object = + new scala.runtime.ModuleSerializationProxy(classOf[A]) + @volatile private lazy var x$lzy1: Object = null + lazy def x(): Int = + { + val result: Object = A#x$lzy1 + if result.isInstanceOf[Int] then scala.Int.unbox(result) else + if result.eq(scala.runtime.LazyVals.NullValue) then + scala.Int.unbox(null) + else scala.Int.unbox(A.x$lzyINIT1()) + } + private def x$lzyINIT1(): Object = + while do + { + val current: Object = A#x$lzy1 + if current.eq(null) then + if + scala.runtime.LazyVals.objCAS(classOf[A], A.OFFSET$_m_0, null, + scala.runtime.LazyVals.Evaluating + ) + then + { + var result: Object = null + var resultNullable: Object = null + try + { + resultNullable = scala.Int.box(2) + if resultNullable.eq(null) then + result = scala.runtime.LazyVals.NullValue + else result = resultNullable + () + } + finally + if + scala.runtime.LazyVals.objCAS(classOf[A], A.OFFSET$_m_0, + scala.runtime.LazyVals.Evaluating + , result).unary_!() + then + { + val lock: scala.runtime.LazyVals.LazyVals$Waiting = + A#x$lzy1.asInstanceOf[ + scala.runtime.LazyVals.LazyVals$Waiting + ] + scala.runtime.LazyVals.objCAS(classOf[A], A.OFFSET$_m_0, + lock + , result) + lock.countDown() + } + else () + return resultNullable + } + else () + else + if + current.isInstanceOf[ + scala.runtime.LazyVals.LazyVals$LazyValControlState + ] + then + if current.eq(scala.runtime.LazyVals.Evaluating) then + { + scala.runtime.LazyVals.objCAS(classOf[A], A.OFFSET$_m_0, + current + , new scala.runtime.LazyVals.LazyVals$Waiting()) + () + } + else + if current.isInstanceOf[scala.runtime.LazyVals.LazyVals$Waiting] + then + + current.asInstanceOf[scala.runtime.LazyVals.LazyVals$Waiting]. + await + () + else return null + else return current + } + } + final lazy module val A: A = new A() +} + diff --git a/tests/printing/transformed/lazy-vals-new.flags b/tests/printing/transformed/lazy-vals-new.flags new file mode 100644 index 000000000000..51b77a4da919 --- /dev/null +++ b/tests/printing/transformed/lazy-vals-new.flags @@ -0,0 +1 @@ +-Ylightweight-lazy-vals diff --git a/tests/printing/transformed/lazy-vals-new.scala b/tests/printing/transformed/lazy-vals-new.scala new file mode 100644 index 000000000000..7240286af56a --- /dev/null +++ b/tests/printing/transformed/lazy-vals-new.scala @@ -0,0 +1,3 @@ +object A { + lazy val x: Int = 2 +} diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index d6c08f78ee85..8901ba53b605 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -67,6 +67,16 @@ val experimentalDefinitionInLibrary = Set( // Need experimental annotation macros to check that design works. "scala.quoted.Quotes.reflectModule.ClassDefModule.apply", "scala.quoted.Quotes.reflectModule.SymbolModule.newClass", + + // New APIs: Lightweight lazy vals. Can be stabilized in 3.3.0 + "scala.runtime.LazyVals$.Evaluating", + "scala.runtime.LazyVals$.Evaluating$", + "scala.runtime.LazyVals$.LazyValControlState", + "scala.runtime.LazyVals$.NullValue", + "scala.runtime.LazyVals$.NullValue$", + "scala.runtime.LazyVals$.Waiting", + "scala.runtime.LazyVals$.getStaticFieldOffset", + "scala.runtime.LazyVals$.objCAS" ) 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..8d36bd4a8825 --- /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) diff --git a/tests/run/serialization-new-legacy.check b/tests/run/serialization-new-legacy.check new file mode 100644 index 000000000000..7124046aaff8 --- /dev/null +++ b/tests/run/serialization-new-legacy.check @@ -0,0 +1,226 @@ +a1 = Array[1,2,3] +_a1 = Array[1,2,3] +arrayEquals(a1, _a1): true + +e1 = Left(1) +_e1 = Left(1) +e1 eq _e1: false, _e1 eq e1: false +e1 equals _e1: true, _e1 equals e1: true + +x7 = RoundingMode +y7 = RoundingMode +x7 eq y7: true, y7 eq x7: true +x7 equals y7: true, y7 equals x7: true + +x8 = WeekDay +y8 = WeekDay +x8 eq y8: true, y8 eq x8: true +x8 equals y8: true, y8 equals x8: true + +x9 = UP +y9 = UP +x9 eq y9: true, y9 eq x9: true +x9 equals y9: true, y9 equals x9: true + +x10 = Monday +y10 = Monday +x10 eq y10: true, y10 eq x10: true +x10 equals y10: true, y10 equals x10: true + +x9 eq x10: false, x10 eq x9: false +x9 equals x10: false, x10 equals x9: false +x9 eq y10: false, y10 eq x9: false +x9 equals y10: false, y10 equals x9: false + +f1 = +_f1 = +f1(2): 4, _f1(2): 4 + +xs0 = List(1, 2, 3) +_xs0 = List(1, 2, 3) +xs0 eq _xs0: false, _xs0 eq xs0: false +xs0 equals _xs0: true, _xs0 equals xs0: true + +xs1 = List() +_xs1 = List() +xs1 eq _xs1: true, _xs1 eq xs1: true + +o1 = None +_o1 = None +o1 eq _o1: true, _o1 eq o1: true + +o2 = Some(1) +_o2 = Some(1) +o2 eq _o2: false, _o2 eq o2: false +o2 equals _o2: true, _o2 equals o2: true + +s1 = Symbol(hello) +_s1 = Symbol(hello) +s1 eq _s1: true, _s1 eq s1: true +s1 equals _s1: true, _s1 equals s1: true + +t1 = (BannerLimit,12345) +_t1 = (BannerLimit,12345) +t1 eq _t1: false, _t1 eq t1: false +t1 equals _t1: true, _t1 equals t1: true + +x = BitSet(1, 2) +y = BitSet(1, 2) +x equals y: true, y equals x: true + +x = BitSet(2, 3) +y = BitSet(2, 3) +x equals y: true, y equals x: true + +x = HashMap(1 -> A, 2 -> B, 3 -> C) +y = HashMap(1 -> A, 2 -> B, 3 -> C) +x equals y: true, y equals x: true + +x = HashSet(1, 2) +y = HashSet(1, 2) +x equals y: true, y equals x: true + +x = List((buffers,20), (layers,2), (title,3)) +y = List((buffers,20), (layers,2), (title,3)) +x equals y: true, y equals x: true + +x = ListMap(buffers -> 20, layers -> 2, title -> 3) +y = ListMap(buffers -> 20, layers -> 2, title -> 3) +x equals y: true, y equals x: true + +x = ListSet(3, 5) +y = ListSet(3, 5) +x equals y: true, y equals x: true + +x = Queue(a, b, c) +y = Queue(a, b, c) +x equals y: true, y equals x: true + +x = Range 0 until 10 +y = Range 0 until 10 +x equals y: true, y equals x: true + +x = NumericRange 0 until 10 +y = NumericRange 0 until 10 +x equals y: true, y equals x: true + +x = TreeMap(1 -> A, 2 -> B, 3 -> C) +y = TreeMap(1 -> A, 2 -> B, 3 -> C) +x equals y: true, y equals x: true + +x = TreeSet(1, 2, 3) +y = TreeSet(1, 2, 3) +x equals y: true, y equals x: true + +x = LazyList() +y = LazyList() +x equals y: true, y equals x: true + +x = TreeMap(42 -> FortyTwo) +y = TreeMap(42 -> FortyTwo) +x equals y: true, y equals x: true + +x = TreeSet(0, 2) +y = TreeSet(0, 2) +x equals y: true, y equals x: true + +x = Vector(Symbol(a), Symbol(b), Symbol(c)) +y = Vector(Symbol(a), Symbol(b), Symbol(c)) +x equals y: true, y equals x: true + +x = ArrayBuffer(one, two) +y = ArrayBuffer(one, two) +x equals y: true, y equals x: true + +x = ArrayBuilder.ofLong +y = ArrayBuilder.ofLong +x equals y: true, y equals x: true + +x = ArrayBuilder.ofFloat +y = ArrayBuilder.ofFloat +x equals y: true, y equals x: true + +x = BitSet(0, 8, 9) +y = BitSet(0, 8, 9) +x equals y: true, y equals x: true + +x = HashMap(A -> 1, B -> 2, C -> 3) +y = HashMap(A -> 1, B -> 2, C -> 3) +x equals y: true, y equals x: true + +x = HashSet(buffers, layers, title) +y = HashSet(buffers, layers, title) +x equals y: true, y equals x: true + +x = LinkedHashMap(Linked -> 1, Hash -> 2, Map -> 3) +y = LinkedHashMap(Linked -> 1, Hash -> 2, Map -> 3) +x equals y: true, y equals x: true + +x = List((Linked,1), (Hash,2), (Map,3)) +y = List((Linked,1), (Hash,2), (Map,3)) +x equals y: true, y equals x: true + +x = List((Linked,1), (Hash,2), (Map,3)) +y = List((Linked,1), (Hash,2), (Map,3)) +x equals y: true, y equals x: true + +x = LinkedHashSet(layers, buffers, title) +y = LinkedHashSet(layers, buffers, title) +x equals y: true, y equals x: true + +x = List(layers, buffers, title) +y = List(layers, buffers, title) +x equals y: true, y equals x: true + +x = List(layers, buffers, title) +y = List(layers, buffers, title) +x equals y: true, y equals x: true + +x = ListBuffer(white, black) +y = ListBuffer(white, black) +x equals y: true, y equals x: true + +x = Queue(20, 2, 3) +y = Queue(20, 2, 3) +x equals y: true, y equals x: true + +x = Stack(3, 2, 20) +y = Stack(3, 2, 20) +x equals y: true, y equals x: true + +x = abc +y = abc +x equals y: true, y equals x: true + +x = ArraySeq(1, 2, 3) +y = ArraySeq(1, 2, 3) +x equals y: true, y equals x: true + +x = TreeSet(1, 2, 3) +y = TreeSet(1, 2, 3) +x equals y: true, y equals x: true + +x = TrieMap(1 -> one, 2 -> two, 3 -> three) +y = TrieMap(1 -> one, 2 -> two, 3 -> three) +x equals y: true, y equals x: true + +x = Tim +y = Tim +x equals y: true, y equals x: true + +x = Bob +y = Bob +x equals y: true, y equals x: true + +x = John +y = John +x equals y: true, y equals x: true + +x = Bill +y = Bill +x equals y: true, y equals x: true + +x = Paul +y = Paul +x equals y: true, y equals x: true + diff --git a/tests/run/serialization-new-legacy.scala b/tests/run/serialization-new-legacy.scala new file mode 100644 index 000000000000..ac7c52a31797 --- /dev/null +++ b/tests/run/serialization-new-legacy.scala @@ -0,0 +1,483 @@ +// scalajs: --skip + +//############################################################################ +// Serialization +//############################################################################ + +object Serialize { + @throws(classOf[java.io.IOException]) + def write[A](o: A): Array[Byte] = { + val ba = new java.io.ByteArrayOutputStream(512) + val out = new java.io.ObjectOutputStream(ba) + out.writeObject(o) + out.close() + ba.toByteArray() + } + @throws(classOf[java.io.IOException]) + @throws(classOf[ClassNotFoundException]) + def read[A](buffer: Array[Byte]): A = { + val in = + new java.io.ObjectInputStream(new java.io.ByteArrayInputStream(buffer)) + in.readObject().asInstanceOf[A] + } + def check[A, B](x: A, y: B): Unit = { + println("x = " + x) + println("y = " + y) + println("x equals y: " + (x equals y) + ", y equals x: " + (y equals x)) + assert((x equals y) && (y equals x)) + println() + } +} +import Serialize.* + +//############################################################################ +// Test classes in package "scala" + +object Test1_scala { + + private def arrayToString[A](arr: Array[A]): String = + arr.mkString("Array[",",","]") + + private def arrayEquals[A, B](a1: Array[A], a2: Array[B]): Boolean = + (a1.length == a2.length) && + (Iterator.range(0, a1.length) forall { i => a1(i) == a2(i) }) + + object WeekDay extends Enumeration { + type WeekDay = Value + val Monday, Tuesday, Wednesday, Thusday, Friday, Saturday, Sunday = Value + } + import WeekDay._, BigDecimal._, RoundingMode.* + + // in alphabetic order + try { + // Array + val a1 = Array(1, 2, 3) + val _a1: Array[Int] = read(write(a1)) + println("a1 = " + arrayToString(a1)) + println("_a1 = " + arrayToString(_a1)) + println("arrayEquals(a1, _a1): " + arrayEquals(a1, _a1)) + println() + + // Either + val e1 = Left(1) + val _e1: Either[Int, String] = read(write(e1)) + println("e1 = " + e1) + println("_e1 = " + _e1) + println("e1 eq _e1: " + (e1 eq _e1) + ", _e1 eq e1: " + (_e1 eq e1)) + println("e1 equals _e1: " + (e1 equals _e1) + ", _e1 equals e1: " + (_e1 equals e1)) + println() + + // Enumeration + val x7 = BigDecimal.RoundingMode + val y7: RoundingMode.type = read(write(x7)) + println("x7 = " + x7) + println("y7 = " + y7) + println("x7 eq y7: " + (x7 eq y7) + ", y7 eq x7: " + (y7 eq x7)) + println("x7 equals y7: " + (x7 equals y7) + ", y7 equals x7: " + (y7 equals x7)) + println() + + val x8 = WeekDay + val y8: WeekDay.type = read(write(x8)) + println("x8 = " + x8) + println("y8 = " + y8) + println("x8 eq y8: " + (x8 eq y8) + ", y8 eq x8: " + (y8 eq x8)) + println("x8 equals y8: " + (x8 equals y8) + ", y8 equals x8: " + (y8 equals x8)) + println() + + val x9 = UP + val y9: RoundingMode = read(write(x9)) + println("x9 = " + x9) + println("y9 = " + y9) + println("x9 eq y9: " + (x9 eq y9) + ", y9 eq x9: " + (y9 eq x9)) + println("x9 equals y9: " + (x9 equals y9) + ", y9 equals x9: " + (y9 equals x9)) + println() + + val x10 = Monday + val y10: WeekDay = read(write(x10)) + println("x10 = " + x10) + println("y10 = " + y10) + println("x10 eq y10: " + (x10 eq y10) + ", y10 eq x10: " + (y10 eq x10)) + println("x10 equals y10: " + (x10 equals y10) + ", y10 equals x10: " + (y10 equals x10)) + println() + + println("x9 eq x10: " + (x9 eq x10) + ", x10 eq x9: " + (x10 eq x9)) + println("x9 equals x10: " + (x9 equals x10) + ", x10 equals x9: " + (x10 equals x9)) + println("x9 eq y10: " + (x9 eq y10) + ", y10 eq x9: " + (y10 eq x9)) + println("x9 equals y10: " + (x9 equals y10) + ", y10 equals x9: " + (y10 equals x9)) + println() + + // Function + val f1 = { (x: Int) => 2 * x } + val _f1: Function[Int, Int] = read(write(f1)) + println("f1 = ") + println("_f1 = ") + println("f1(2): " + f1(2) + ", _f1(2): " + _f1(2)) + println() + + // List + val xs0 = List(1, 2, 3) + val _xs0: List[Int] = read(write(xs0)) + println("xs0 = " + xs0) + println("_xs0 = " + _xs0) + println("xs0 eq _xs0: " + (xs0 eq _xs0) + ", _xs0 eq xs0: " + (_xs0 eq xs0)) + println("xs0 equals _xs0: " + (xs0 equals _xs0) + ", _xs0 equals xs0: " + (_xs0 equals xs0)) + println() + + val xs1 = Nil + val _xs1: List[Nothing] = read(write(xs1)) + println("xs1 = " + xs1) + println("_xs1 = " + _xs1) + println("xs1 eq _xs1: " + (xs1 eq _xs1) + ", _xs1 eq xs1: " + (_xs1 eq xs1)) + println() + + // Option + val o1 = None + val _o1: Option[Nothing] = read(write(o1)) + println("o1 = " + o1) + println("_o1 = " + _o1) + println("o1 eq _o1: " + (o1 eq _o1) + ", _o1 eq o1: " + (_o1 eq o1)) + println() + + val o2 = Some(1) + val _o2: Option[Int] = read(write(o2)) + println("o2 = " + o2) + println("_o2 = " + _o2) + println("o2 eq _o2: " + (o2 eq _o2) + ", _o2 eq o2: " + (_o2 eq o2)) + println("o2 equals _o2: " + (o2 equals _o2) + ", _o2 equals o2: " + (_o2 equals o2)) + println() +/* + // Responder + val r1 = Responder.constant("xyz") + val _r1: Responder[String] = read(write(r1)) + check(r1, _r1) +*/ + // Symbol + val s1 = Symbol("hello") + val _s1: Symbol = read(write(s1)) + println("s1 = " + s1) + println("_s1 = " + _s1) + println("s1 eq _s1: " + (s1 eq _s1) + ", _s1 eq s1: " + (_s1 eq s1)) + println("s1 equals _s1: " + (s1 equals _s1) + ", _s1 equals s1: " + (_s1 equals s1)) + println() + + // Tuple + val t1 = ("BannerLimit", 12345) + val _t1: (String, Int) = read(write(t1)) + println("t1 = " + t1) + println("_t1 = " + _t1) + println("t1 eq _t1: " + (t1 eq _t1) + ", _t1 eq t1: " + (_t1 eq t1)) + println("t1 equals _t1: " + (t1 equals _t1) + ", _t1 equals t1: " + (_t1 equals t1)) + println() + } + catch { + case e: Exception => + println("Error in Test1_scala: " + e) + throw e + } +} + +//############################################################################ +// Test classes in package "scala.collection.immutable" + +object Test2_immutable { + import scala.collection.immutable.{ + BitSet, HashMap, HashSet, ListMap, ListSet, Queue, Range, SortedMap, + SortedSet, LazyList, TreeMap, TreeSet, Vector} + + // in alphabetic order + try { + // BitSet + val bs1 = BitSet.empty + 1 + 2 + val _bs1: BitSet = read(write(bs1)) + check(bs1, _bs1) + + val bs2 = { + val bs = new collection.mutable.BitSet() + bs += 2; bs += 3 + bs.toImmutable + } + val _bs2: BitSet = read(write(bs2)) + check(bs2, _bs2) + + // HashMap + val hm1 = HashMap.empty[Int, String] ++ List(1 -> "A", 2 -> "B", 3 -> "C") + val _hm1: HashMap[Int, String] = read(write(hm1)) + check(hm1, _hm1) + + // HashSet + val hs1 = HashSet.empty[Int] + 1 + 2 + val _hs1: HashSet[Int] = read(write(hs1)) + check(hs1, _hs1) + + // List + val xs1 = List(("buffers", 20), ("layers", 2), ("title", 3)) + val _xs1: List[(String, Int)] = read(write(xs1)) + check(xs1, _xs1) + + // ListMap + val lm1 = new ListMap[String, Int] ++ List("buffers" -> 20, "layers" -> 2, "title" -> 3) + val _lm1: ListMap[String, Int] = read(write(lm1)) + check(lm1, _lm1) + + // ListSet + val ls1 = new ListSet[Int] + 3 + 5 + val _ls1: ListSet[Int] = read(write(ls1)) + check(ls1, _ls1) + + // Queue + val q1 = Queue("a", "b", "c") + val _q1: Queue[String] = read(write(q1)) + check(q1, _q1) + + // Range + val r1 = 0 until 10 + val _r1: Range = read(write(r1)) + check(r1, _r1) + + val r2 = Range.Long(0L, 10L, 1) + val _r2: r2.type = read(write(r2)) + check(r2, _r2) + + // SortedMap + val sm1 = SortedMap.empty[Int, String] ++ List(2 -> "B", 3 -> "C", 1 -> "A") + val _sm1: SortedMap[Int, String] = read(write(sm1)) + check(sm1, _sm1) + + // SortedSet + val ss1 = SortedSet.empty[Int] + 2 + 3 + 1 + val _ss1: SortedSet[Int] = read(write(ss1)) + check(ss1, _ss1) + + // LazyList + val st1 = LazyList.range(0, 10) + val _st1: LazyList[Int] = read(write(st1)) + check(st1, _st1) + + // TreeMap + val tm1 = new TreeMap[Int, String] + (42 -> "FortyTwo") + val _tm1: TreeMap[Int, String] = read(write(tm1)) + check(tm1, _tm1) + + // TreeSet + val ts1 = new TreeSet[Int]() + 2 + 0 + val _ts1: TreeSet[Int] = read(write(ts1)) + check(ts1, _ts1) + + // Vector + val v1 = Vector(Symbol("a"), Symbol("b"), Symbol("c")) + val _v1: Vector[Symbol] = read(write(v1)) + check(v1, _v1) + } + catch { + case e: Exception => + println("Error in Test2_immutable: " + e) + throw e + } +} + +//############################################################################ +// Test classes in package "scala.collection.mutable" + +object Test3_mutable { + import scala.reflect.ClassTag + import scala.collection.mutable.{ + ArrayBuffer, ArrayBuilder, BitSet, + HashMap, HashSet, LinkedHashMap, LinkedHashSet, ListBuffer, + Queue, Stack, StringBuilder, ArraySeq, TreeSet} + import scala.collection.concurrent.TrieMap + + // in alphabetic order + try { + // ArrayBuffer + val ab1 = new ArrayBuffer[String] + ab1 ++= List("one", "two") + val _ab1: ArrayBuffer[String] = read(write(ab1)) + check(ab1, _ab1) + + // ArrayBuilder + val abu1 = ArrayBuilder.make[Long] + val _abu1: ArrayBuilder[ClassTag[Long]] = read(write(abu1)) + check(abu1, _abu1) + + val abu2 = ArrayBuilder.make[Float] + val _abu2: ArrayBuilder[ClassTag[Float]] = read(write(abu2)) + check(abu2, _abu2) + + // BitSet + val bs1 = new BitSet() + bs1 += 0 + bs1 += 8 + bs1 += 9 + val _bs1: BitSet = read(write(bs1)) + check(bs1, _bs1) + + // HashMap + val hm1 = new HashMap[String, Int] + hm1 ++= List(("A", 1), ("B", 2), ("C", 3)).iterator + val _hm1: HashMap[String, Int] = read(write(hm1)) + check(hm1, _hm1) + + // HashSet + val hs1 = new HashSet[String] + hs1 ++= List("layers", "buffers", "title").iterator + val _hs1: HashSet[String] = read(write(hs1)) + check(hs1, _hs1) + + // LinkedHashMap + { val lhm1 = new LinkedHashMap[String, Int] + val list = List(("Linked", 1), ("Hash", 2), ("Map", 3)) + lhm1 ++= list.iterator + val _lhm1: LinkedHashMap[String, Int] = read(write(lhm1)) + check(lhm1, _lhm1) + check(lhm1.toSeq, _lhm1.toSeq) // check elements order + check(lhm1.toSeq, list) // check elements order + } + + // LinkedHashSet + { val lhs1 = new LinkedHashSet[String] + val list = List("layers", "buffers", "title") + lhs1 ++= list.iterator + val _lhs1: LinkedHashSet[String] = read(write(lhs1)) + check(lhs1, _lhs1) + check(lhs1.toSeq, _lhs1.toSeq) // check elements order + check(lhs1.toSeq, list) // check elements order + } + + // ListBuffer + val lb1 = new ListBuffer[String] + lb1 ++= List("white", "black") + val _lb1: ListBuffer[String] = read(write(lb1)) + check(lb1, _lb1) + + // Queue + val q1 = new Queue[Int] + q1 ++= List(20, 2, 3).iterator + val _q1: Queue[Int] = read(write(q1)) + check(q1, _q1) + + // Stack + val s1 = new Stack[Int] + s1 pushAll q1 + val _s1: Stack[Int] = read(write(s1)) + check(s1, _s1) + + // StringBuilder + val sb1 = new StringBuilder + sb1 append "abc" + val _sb1: StringBuilder = read(write(sb1)) + check(sb1, _sb1) + + // ArraySeq + val wa1 = ArraySeq.make(Array(1, 2, 3)) + val _wa1: ArraySeq[Int] = read(write(wa1)) + check(wa1, _wa1) + + // TreeSet + val ts1 = TreeSet[Int]() ++= Array(1, 2, 3) + val _ts1: TreeSet[Int] = read(write(ts1)) + check(ts1, _ts1) + + // concurrent.TrieMap + val ct1 = TrieMap[Int, String]() ++= Array(1 -> "one", 2 -> "two", 3 -> "three") + val _ct1: TrieMap[Int, String] = read(write(ct1)) + check(ct1, _ct1) + } + catch { + case e: Exception => + println("Error in Test3_mutable: " + e) + throw e + } +} + +//############################################################################ +// Test user-defined classes WITHOUT nesting + +class Person(_name: String) extends Serializable { + private var name = _name + override def toString() = name + override def equals(that: Any): Boolean = + that.isInstanceOf[Person] && + (name == that.asInstanceOf[Person].name) +} + +class Employee(_name: String) extends Serializable { + private var name = _name + override def toString() = name +} + +object bob extends Employee("Bob") + +object Test5 { + val x1 = new Person("Tim") + val x2 = bob + + try { + val y1: Person = read(write(x1)) + val y2: Employee = read(write(x2)) + + check(x1, y1) + check(x2, y2) + } + catch { + case e: Exception => + println("Error in Test5: " + e) + } +} + +//############################################################################ +// Test user-defined classes WITH nesting + +object Test6 { + object bill extends Employee("Bill") { + val x = paul + } + object paul extends Person("Paul") { + val x = 4 // bill; => StackOverflowException !!! + } + val x1 = new Person("John") + val x2 = bill + val x3 = paul + + try { + val y1: Person = read(write(x1)) + val y2: Employee = read(write(x2)) + val y3: Person = read(write(x3)) + + check(x1, y1) + check(x2, y2) + check(x3, y3) + } + catch { + case e: Exception => + println("Error in Test6: " + e) + } +} + +//############################################################################ +// Nested objects cannot get readresolve automatically because after deserialization +// they would be null (they are treated as lazy vals) +class Outer extends Serializable { + object Inner extends Serializable +} + +object Test7 { + val x = new Outer + x.Inner // initialize + val y:Outer = read(write(x)) + if (y.Inner == null) + println("Inner object is null") +} + +//############################################################################ +// Test code + +object Test { + def main(args: Array[String]): Unit = { + Test1_scala + Test2_immutable + Test3_mutable + Test5 + Test6 + Test7 + } +} 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 786fc12bd066..202920964479 100644 --- a/tests/run/serialization-new.scala +++ b/tests/run/serialization-new.scala @@ -470,14 +470,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") }