From aa26211a4b7f6749d05567ff692d293c7d8947f8 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Mon, 29 Sep 2025 19:11:28 +0200 Subject: [PATCH] Replace depracated Unsafe with VarHandles in the lazy val implementation --- .../dotty/tools/dotc/core/Definitions.scala | 3 + .../src/dotty/tools/dotc/core/NameKinds.scala | 1 + .../src/dotty/tools/dotc/core/NameTags.scala | 2 + .../dotty/tools/dotc/transform/LazyVals.scala | 69 +++++++++++-------- .../tools/dotc/transform/MoveStatics.scala | 9 ++- project/MiMaFilters.scala | 9 +++ .../printing/transformed/lazy-vals-new.check | 21 +++--- 7 files changed, 71 insertions(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index dbe1602e2d82..1b3f2a1e0a8f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -756,7 +756,10 @@ class Definitions { def JavaEnumType = JavaEnumClass.typeRef @tu lazy val MethodHandleClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandle") + @tu lazy val MethodHandlesClass: TermSymbol = requiredModule("java.lang.invoke.MethodHandles") + @tu lazy val MethodHandles_lookup: Symbol = MethodHandlesClass.requiredMethod("lookup") @tu lazy val MethodHandlesLookupClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandles.Lookup") + @tu lazy val MethodHandlesLookup_FindVarHandle: Symbol = MethodHandlesLookupClass.requiredMethod("findVarHandle") @tu lazy val VarHandleClass: ClassSymbol = requiredClass("java.lang.invoke.VarHandle") @tu lazy val StringBuilderClass: ClassSymbol = requiredClass("scala.collection.mutable.StringBuilder") diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index f41062d908e9..6738c73113f9 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -400,6 +400,7 @@ object NameKinds { val DirectMethName: SuffixNameKind = new SuffixNameKind(DIRECT, "$direct") val AdaptedClosureName: SuffixNameKind = new SuffixNameKind(ADAPTEDCLOSURE, "$adapted") { override def definesNewName = true } val SyntheticSetterName: SuffixNameKind = new SuffixNameKind(SETTER, "_$eq") + val LazyVarHandleName: SuffixNameKind = new SuffixNameKind(LAZYVALVARHANDLE, "$lzyHandle") /** A name together with a signature. Used in Tasty trees. */ object SignedName extends NameKind(SIGNED) { diff --git a/compiler/src/dotty/tools/dotc/core/NameTags.scala b/compiler/src/dotty/tools/dotc/core/NameTags.scala index 59dfaa3d437b..1e45ceac30cd 100644 --- a/compiler/src/dotty/tools/dotc/core/NameTags.scala +++ b/compiler/src/dotty/tools/dotc/core/NameTags.scala @@ -40,6 +40,8 @@ object NameTags extends TastyFormat.NameTags { inline val EXPLICITFIELD = 38 // An explicitly named field, introduce to avoid a clash // with a regular field of the underlying name + inline val LAZYVALVARHANDLE = 39 // A field containing a VarHandle generated for lazy vals + def nameTagToString(tag: Int): String = tag match { case UTF8 => "UTF8" case QUALIFIED => "QUALIFIED" diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index 76bc09e07540..103f058609a9 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -9,7 +9,7 @@ import core.Contexts.* import core.Decorators.* import core.DenotTransformers.IdentityDenotTransformer import core.Flags.* -import core.NameKinds.{ExpandedName, LazyBitMapName, LazyLocalInitName, LazyLocalName} +import core.NameKinds.{ExpandedName, LazyBitMapName, LazyLocalInitName, LazyLocalName, LazyVarHandleName} import core.StdNames.nme import core.Symbols.* import core.Types.* @@ -28,8 +28,10 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { * The map contains the list of the offset trees. */ class OffsetInfo(var defs: List[Tree], var ord: Int = 0) + class VarHandleInfo(var defs: List[Tree]) private val appendOffsetDefs = mutable.Map.empty[Symbol, OffsetInfo] + private val appendVarHandleDefs = mutable.Map.empty[Symbol, VarHandleInfo] override def phaseName: String = LazyVals.name @@ -109,12 +111,19 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { */ override def transformTemplate(template: Template)(using Context): Tree = { val cls = ctx.owner.asClass - appendOffsetDefs.get(cls) match { - case None => template - case Some(data) => - data.defs.foreach(defin => defin.symbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, defin.symbol.span))) - cpy.Template(template)(body = addInFront(data.defs, template.body)) - } + if ctx.settings.YlegacyLazyVals.value then + appendOffsetDefs.get(cls) match { + case None => template + case Some(data) => + data.defs.foreach(defin => defin.symbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, defin.symbol.span))) + cpy.Template(template)(body = addInFront(data.defs, template.body)) + } + else + appendVarHandleDefs.get(cls) match { + case None => template + case Some(data) => + cpy.Template(template)(body = addInFront(data.defs, template.body)) + } } private def addInFront(prefix: List[Tree], stats: List[Tree]) = stats match { @@ -328,20 +337,24 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { * @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 varHandle the VarHandle of the field * @param thiz a reference to the transformed class */ def mkThreadSafeDef(memberDef: ValOrDefDef, claz: ClassSymbol, target: Symbol, - offset: Tree, + varHandle: 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 casFlag = + typer.Applications.retypeSignaturePolymorphicFn( // must be retyped to avoid wrapping into Array[Object] + Select(varHandle, lazyNme.compareAndSet), + MethodType(List(defn.ObjectType,defn.ObjectType,defn.ObjectType), defn.BooleanType) + ) 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)) @@ -383,12 +396,12 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { 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, + :: casFlag.appliedTo(thiz, 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, + casFlag.appliedTo(thiz, evaluating, ref(resSymb)).select(nme.UNARY_!).appliedToNone, lockRel, unitLiteral ) @@ -409,7 +422,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { ) // if CAS(_, null, Evaluating) If( - objCasFlag.appliedTo(thiz, offset, nullLiteral, evaluating), + casFlag.appliedTo(thiz, nullLiteral, evaluating), Block(ValDef(resSymb, nullLiteral) :: ValDef(resSymbNullable, nullLiteral) :: evaluate :: Nil, // var result: AnyRef = null Return(ref(resSymbNullable), lazyInitMethodSymbol)), unitLiteral @@ -425,7 +438,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { 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, + casFlag.appliedTo(thiz, ref(current), Select(New(waiting), StdNames.nme.CONSTRUCTOR).ensureApplied) :: Nil, unitLiteral ), // if not Evaluating @@ -461,7 +474,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { 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, containerSymbol.span)) // private @volatile var _x: AnyRef @@ -471,23 +483,22 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { 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.addAnnotation(Annotation(defn.ScalaStaticAnnot, offsetSymbol.span)) - val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerName.mangledString))) - val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(fieldTree)) - val offsetInfo = appendOffsetDefs.getOrElseUpdate(claz, new OffsetInfo(Nil)) - offsetInfo.defs = offsetTree :: offsetInfo.defs - val offset = ref(offsetSymbol) + // create a VarHandle for this lazy val + val varHandleSymbol: TermSymbol = newSymbol(claz, LazyVarHandleName(containerName), Private | Synthetic, defn.VarHandleClass.typeRef).enteredAfter(this) + varHandleSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, varHandleSymbol.span)) + val getVarHandle = Apply( + Select(Apply(Select(ref(defn.MethodHandlesClass), defn.MethodHandles_lookup.name), Nil), defn.MethodHandlesLookup_FindVarHandle.name), + List(thizClass, Literal(Constant(containerName.mangledString)), Literal(Constant(defn.ObjectType))) + ) + val varHandleTree = ValDef(varHandleSymbol, getVarHandle) + val varHandle = ref(varHandleSymbol) + val varHandleInfo = appendVarHandleDefs.getOrElseUpdate(claz, new VarHandleInfo(Nil)) + varHandleInfo.defs = varHandleTree :: varHandleInfo.defs val swapOver = This(claz) - val (accessorDef, initMethodDef) = mkThreadSafeDef(x, claz, containerSymbol, offset, swapOver) + val (accessorDef, initMethodDef) = mkThreadSafeDef(x, claz, containerSymbol, varHandle, swapOver) Thicket(containerTree, accessorDef, initMethodDef) } @@ -666,7 +677,6 @@ object LazyVals { 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 @@ -687,5 +697,6 @@ object LazyVals { val current: TermName = "current".toTermName val lock: TermName = "lock".toTermName val discard: TermName = "discard".toTermName + val compareAndSet: TermName = "compareAndSet".toTermName } } diff --git a/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala b/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala index b3ec05501b5b..13741c24d415 100644 --- a/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala +++ b/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala @@ -12,13 +12,16 @@ import SymDenotations.SymDenotation import Names.Name import StdNames.nme import NameOps.* +import NameKinds.LazyVarHandleName import ast.* import MegaPhase.* -/** Move static methods from companion to the class itself */ +/** Move static methods from companion to the class itself. Also create the static constructor. + * VarHandles generated by the compiler for lazy vals are left in the original class. + */ class MoveStatics extends MiniPhase with SymTransformer { import ast.tpd.* @@ -28,7 +31,7 @@ class MoveStatics extends MiniPhase with SymTransformer { def transformSym(sym: SymDenotation)(using Context): SymDenotation = if (sym.hasAnnotation(defn.ScalaStaticAnnot) && sym.owner.is(Flags.Module) && sym.owner.companionClass.exists && - (sym.is(Flags.Method) || !(sym.isMutableVarOrAccessor && sym.owner.companionClass.is(Flags.Trait)))) { + (sym.is(Flags.Method) || !(sym.isMutableVarOrAccessor && sym.owner.companionClass.is(Flags.Trait)) && !sym.symbol.name.is(LazyVarHandleName))) { sym.owner.asClass.delete(sym.symbol) sym.owner.companionClass.asClass.enter(sym.symbol) sym.copySymDenotation(owner = sym.owner.companionClass) @@ -65,7 +68,7 @@ class MoveStatics extends MiniPhase with SymTransformer { val moduleTmpl = module.rhs.asInstanceOf[Template] val companionTmpl = companion.rhs.asInstanceOf[Template] val (staticDefs, remainingDefs) = moduleTmpl.body.partition { - case memberDef: MemberDef => memberDef.symbol.isScalaStatic + case memberDef: MemberDef => memberDef.symbol.isScalaStatic && !memberDef.symbol.name.is(LazyVarHandleName) case _ => false } diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index bc842ae86039..d351c517b41f 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -141,6 +141,15 @@ object MiMaFilters { ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newModule"), + // Changes to lazy vals (added static constructors) + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.Tuple."), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.ArraySeq."), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.concurrent.ExecutionContext."), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.io.Codec."), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.math.BigDecimal."), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.sys.SystemProperties."), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.sys.process.Process."), + // Change `experimental` annotation to a final class ProblemFilters.exclude[FinalClassProblem]("scala.annotation.experimental"), diff --git a/tests/printing/transformed/lazy-vals-new.check b/tests/printing/transformed/lazy-vals-new.check index 75ae8885f68c..26fc3ec21ddb 100644 --- a/tests/printing/transformed/lazy-vals-new.check +++ b/tests/printing/transformed/lazy-vals-new.check @@ -9,14 +9,14 @@ package { } @static private def (): Unit = { - A.OFFSET$_m_0 = - scala.runtime.LazyVals.getOffsetStatic( - classOf[Object {...}].getDeclaredField("x$lzy1")) + A.x$lzy1$lzyHandle = + java.lang.invoke.MethodHandles.lookup().findVarHandle( + classOf[Object {...}], "x$lzy1", classOf[Object]) () } - @static @static val OFFSET$_m_0: Long = - scala.runtime.LazyVals.getOffsetStatic( - classOf[Object {...}].getDeclaredField("x$lzy1")) + @static private val x$lzy1$lzyHandle: java.lang.invoke.VarHandle = + java.lang.invoke.MethodHandles.lookup().findVarHandle( + classOf[Object {...}], "x$lzy1", classOf[Object]) private def writeReplace(): Object = new scala.runtime.ModuleSerializationProxy(classOf[A]) @volatile private lazy var x$lzy1: Object = null @@ -33,7 +33,7 @@ package { val current: Object = A.x$lzy1 if current eq null then if - scala.runtime.LazyVals.objCAS(this, A.OFFSET$_m_0, null, + A.x$lzy1$lzyHandle.compareAndSet(this, null, scala.runtime.LazyVals.Evaluating) then { @@ -49,15 +49,14 @@ package { } finally if - scala.runtime.LazyVals.objCAS(this, A.OFFSET$_m_0, + A.x$lzy1$lzyHandle.compareAndSet(this, 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(this, A.OFFSET$_m_0, lock, - result) + A.x$lzy1$lzyHandle.compareAndSet(this, lock, result) lock.countDown() } else () @@ -71,7 +70,7 @@ package { then if current eq scala.runtime.LazyVals.Evaluating then { - scala.runtime.LazyVals.objCAS(this, A.OFFSET$_m_0, current, + A.x$lzy1$lzyHandle.compareAndSet(this, current, new scala.runtime.LazyVals.LazyVals$Waiting()) () }