Skip to content

Commit aa26211

Browse files
committed
Replace depracated Unsafe with VarHandles in the lazy val implementation
1 parent 903cdf9 commit aa26211

File tree

7 files changed

+71
-43
lines changed

7 files changed

+71
-43
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,10 @@ class Definitions {
756756
def JavaEnumType = JavaEnumClass.typeRef
757757

758758
@tu lazy val MethodHandleClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandle")
759+
@tu lazy val MethodHandlesClass: TermSymbol = requiredModule("java.lang.invoke.MethodHandles")
760+
@tu lazy val MethodHandles_lookup: Symbol = MethodHandlesClass.requiredMethod("lookup")
759761
@tu lazy val MethodHandlesLookupClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandles.Lookup")
762+
@tu lazy val MethodHandlesLookup_FindVarHandle: Symbol = MethodHandlesLookupClass.requiredMethod("findVarHandle")
760763
@tu lazy val VarHandleClass: ClassSymbol = requiredClass("java.lang.invoke.VarHandle")
761764

762765
@tu lazy val StringBuilderClass: ClassSymbol = requiredClass("scala.collection.mutable.StringBuilder")

compiler/src/dotty/tools/dotc/core/NameKinds.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ object NameKinds {
400400
val DirectMethName: SuffixNameKind = new SuffixNameKind(DIRECT, "$direct")
401401
val AdaptedClosureName: SuffixNameKind = new SuffixNameKind(ADAPTEDCLOSURE, "$adapted") { override def definesNewName = true }
402402
val SyntheticSetterName: SuffixNameKind = new SuffixNameKind(SETTER, "_$eq")
403+
val LazyVarHandleName: SuffixNameKind = new SuffixNameKind(LAZYVALVARHANDLE, "$lzyHandle")
403404

404405
/** A name together with a signature. Used in Tasty trees. */
405406
object SignedName extends NameKind(SIGNED) {

compiler/src/dotty/tools/dotc/core/NameTags.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ object NameTags extends TastyFormat.NameTags {
4040
inline val EXPLICITFIELD = 38 // An explicitly named field, introduce to avoid a clash
4141
// with a regular field of the underlying name
4242

43+
inline val LAZYVALVARHANDLE = 39 // A field containing a VarHandle generated for lazy vals
44+
4345
def nameTagToString(tag: Int): String = tag match {
4446
case UTF8 => "UTF8"
4547
case QUALIFIED => "QUALIFIED"

compiler/src/dotty/tools/dotc/transform/LazyVals.scala

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import core.Contexts.*
99
import core.Decorators.*
1010
import core.DenotTransformers.IdentityDenotTransformer
1111
import core.Flags.*
12-
import core.NameKinds.{ExpandedName, LazyBitMapName, LazyLocalInitName, LazyLocalName}
12+
import core.NameKinds.{ExpandedName, LazyBitMapName, LazyLocalInitName, LazyLocalName, LazyVarHandleName}
1313
import core.StdNames.nme
1414
import core.Symbols.*
1515
import core.Types.*
@@ -28,8 +28,10 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
2828
* The map contains the list of the offset trees.
2929
*/
3030
class OffsetInfo(var defs: List[Tree], var ord: Int = 0)
31+
class VarHandleInfo(var defs: List[Tree])
3132

3233
private val appendOffsetDefs = mutable.Map.empty[Symbol, OffsetInfo]
34+
private val appendVarHandleDefs = mutable.Map.empty[Symbol, VarHandleInfo]
3335

3436
override def phaseName: String = LazyVals.name
3537

@@ -109,12 +111,19 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
109111
*/
110112
override def transformTemplate(template: Template)(using Context): Tree = {
111113
val cls = ctx.owner.asClass
112-
appendOffsetDefs.get(cls) match {
113-
case None => template
114-
case Some(data) =>
115-
data.defs.foreach(defin => defin.symbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, defin.symbol.span)))
116-
cpy.Template(template)(body = addInFront(data.defs, template.body))
117-
}
114+
if ctx.settings.YlegacyLazyVals.value then
115+
appendOffsetDefs.get(cls) match {
116+
case None => template
117+
case Some(data) =>
118+
data.defs.foreach(defin => defin.symbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, defin.symbol.span)))
119+
cpy.Template(template)(body = addInFront(data.defs, template.body))
120+
}
121+
else
122+
appendVarHandleDefs.get(cls) match {
123+
case None => template
124+
case Some(data) =>
125+
cpy.Template(template)(body = addInFront(data.defs, template.body))
126+
}
118127
}
119128

120129
private def addInFront(prefix: List[Tree], stats: List[Tree]) = stats match {
@@ -328,20 +337,24 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
328337
* @param memberDef the transformed lazy field member definition
329338
* @param claz the class containing this lazy val field
330339
* @param target the target synthetic field
331-
* @param offset the offset of the field in the storage allocation of the class
340+
* @param varHandle the VarHandle of the field
332341
* @param thiz a reference to the transformed class
333342
*/
334343
def mkThreadSafeDef(memberDef: ValOrDefDef,
335344
claz: ClassSymbol,
336345
target: Symbol,
337-
offset: Tree,
346+
varHandle: Tree,
338347
thiz: Tree)(using Context): (DefDef, DefDef) = {
339348
val tp = memberDef.tpe.widenDealias.resultType.widenDealias
340349
val waiting = ref(defn.LazyValsWaitingState)
341350
val controlState = ref(defn.LazyValsControlState)
342351
val evaluating = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.evaluating)
343352
val nullValue = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.nullValue)
344-
val objCasFlag = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.objCas)
353+
val casFlag =
354+
typer.Applications.retypeSignaturePolymorphicFn( // must be retyped to avoid wrapping into Array[Object]
355+
Select(varHandle, lazyNme.compareAndSet),
356+
MethodType(List(defn.ObjectType,defn.ObjectType,defn.ObjectType), defn.BooleanType)
357+
)
345358
val accessorMethodSymbol = memberDef.symbol.asTerm
346359
val lazyInitMethodName = LazyLocalInitName.fresh(memberDef.name.asTermName)
347360
val lazyInitMethodSymbol = newSymbol(claz, lazyInitMethodName, Synthetic | Method | Private, MethodType(Nil)(_ => Nil, _ => defn.ObjectType))
@@ -383,12 +396,12 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
383396
val lockRel = {
384397
val lockSymb = newSymbol(lazyInitMethodSymbol, lazyNme.lock, Synthetic, waiting.typeOpt)
385398
Block(ValDef(lockSymb, ref(target).cast(waiting.typeOpt))
386-
:: objCasFlag.appliedTo(thiz, offset, ref(lockSymb), ref(resSymb)) :: Nil,
399+
:: casFlag.appliedTo(thiz, ref(lockSymb), ref(resSymb)) :: Nil,
387400
ref(lockSymb).select(lazyNme.RLazyVals.waitingRelease).ensureApplied)
388401
}
389402
// finally block
390403
val fin = If(
391-
objCasFlag.appliedTo(thiz, offset, evaluating, ref(resSymb)).select(nme.UNARY_!).appliedToNone,
404+
casFlag.appliedTo(thiz, evaluating, ref(resSymb)).select(nme.UNARY_!).appliedToNone,
392405
lockRel,
393406
unitLiteral
394407
)
@@ -409,7 +422,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
409422
)
410423
// if CAS(_, null, Evaluating)
411424
If(
412-
objCasFlag.appliedTo(thiz, offset, nullLiteral, evaluating),
425+
casFlag.appliedTo(thiz, nullLiteral, evaluating),
413426
Block(ValDef(resSymb, nullLiteral) :: ValDef(resSymbNullable, nullLiteral) :: evaluate :: Nil, // var result: AnyRef = null
414427
Return(ref(resSymbNullable), lazyInitMethodSymbol)),
415428
unitLiteral
@@ -425,7 +438,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
425438
ref(current).select(defn.Object_eq).appliedTo(evaluating),
426439
// if is Evaluating then CAS(_, Evaluating, new Waiting)
427440
Block(
428-
objCasFlag.appliedTo(thiz, offset, ref(current), Select(New(waiting), StdNames.nme.CONSTRUCTOR).ensureApplied) :: Nil,
441+
casFlag.appliedTo(thiz, ref(current), Select(New(waiting), StdNames.nme.CONSTRUCTOR).ensureApplied) :: Nil,
429442
unitLiteral
430443
),
431444
// if not Evaluating
@@ -461,7 +474,6 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
461474
val claz = x.symbol.owner.asClass
462475
val thizClass = Literal(Constant(claz.info))
463476

464-
def offsetName(id: Int) = s"${StdNames.nme.LAZY_FIELD_OFFSET}${if (x.symbol.owner.is(Module)) "_m_" else ""}$id".toTermName
465477
val containerName = LazyLocalName.fresh(x.name.asTermName)
466478
val containerSymbol = newSymbol(claz, containerName, x.symbol.flags &~ containerFlagsMask | containerFlags | Private, defn.ObjectType, coord = x.symbol.coord).enteredAfter(this)
467479
containerSymbol.addAnnotation(Annotation(defn.VolatileAnnot, containerSymbol.span)) // private @volatile var _x: AnyRef
@@ -471,23 +483,22 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
471483
Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.getOffsetStatic)
472484
val containerTree = ValDef(containerSymbol, nullLiteral)
473485

474-
// create an offset for this lazy val
475-
val offsetSymbol: TermSymbol = appendOffsetDefs.get(claz) match
476-
case Some(info) =>
477-
newSymbol(claz, offsetName(info.defs.size), Synthetic, defn.LongType).enteredAfter(this)
478-
case None =>
479-
newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this)
480-
offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, offsetSymbol.span))
481-
val fieldTree = thizClass.select(lazyNme.RLazyVals.getDeclaredField).appliedTo(Literal(Constant(containerName.mangledString)))
482-
val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(fieldTree))
483-
val offsetInfo = appendOffsetDefs.getOrElseUpdate(claz, new OffsetInfo(Nil))
484-
offsetInfo.defs = offsetTree :: offsetInfo.defs
485-
val offset = ref(offsetSymbol)
486+
// create a VarHandle for this lazy val
487+
val varHandleSymbol: TermSymbol = newSymbol(claz, LazyVarHandleName(containerName), Private | Synthetic, defn.VarHandleClass.typeRef).enteredAfter(this)
488+
varHandleSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot, varHandleSymbol.span))
489+
val getVarHandle = Apply(
490+
Select(Apply(Select(ref(defn.MethodHandlesClass), defn.MethodHandles_lookup.name), Nil), defn.MethodHandlesLookup_FindVarHandle.name),
491+
List(thizClass, Literal(Constant(containerName.mangledString)), Literal(Constant(defn.ObjectType)))
492+
)
493+
val varHandleTree = ValDef(varHandleSymbol, getVarHandle)
494+
val varHandle = ref(varHandleSymbol)
486495

496+
val varHandleInfo = appendVarHandleDefs.getOrElseUpdate(claz, new VarHandleInfo(Nil))
497+
varHandleInfo.defs = varHandleTree :: varHandleInfo.defs
487498
val swapOver =
488499
This(claz)
489500

490-
val (accessorDef, initMethodDef) = mkThreadSafeDef(x, claz, containerSymbol, offset, swapOver)
501+
val (accessorDef, initMethodDef) = mkThreadSafeDef(x, claz, containerSymbol, varHandle, swapOver)
491502
Thicket(containerTree, accessorDef, initMethodDef)
492503
}
493504

@@ -666,7 +677,6 @@ object LazyVals {
666677
val waitingRelease: TermName = "countDown".toTermName
667678
val evaluating: TermName = "Evaluating".toTermName
668679
val nullValue: TermName = "NullValue".toTermName
669-
val objCas: TermName = "objCAS".toTermName
670680
val get: TermName = N.get.toTermName
671681
val setFlag: TermName = N.setFlag.toTermName
672682
val wait4Notification: TermName = N.wait4Notification.toTermName
@@ -687,5 +697,6 @@ object LazyVals {
687697
val current: TermName = "current".toTermName
688698
val lock: TermName = "lock".toTermName
689699
val discard: TermName = "discard".toTermName
700+
val compareAndSet: TermName = "compareAndSet".toTermName
690701
}
691702
}

compiler/src/dotty/tools/dotc/transform/MoveStatics.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ import SymDenotations.SymDenotation
1212
import Names.Name
1313
import StdNames.nme
1414
import NameOps.*
15+
import NameKinds.LazyVarHandleName
1516

1617
import ast.*
1718

1819

1920
import MegaPhase.*
2021

21-
/** Move static methods from companion to the class itself */
22+
/** Move static methods from companion to the class itself. Also create the static constructor.
23+
* VarHandles generated by the compiler for lazy vals are left in the original class.
24+
*/
2225
class MoveStatics extends MiniPhase with SymTransformer {
2326
import ast.tpd.*
2427

@@ -28,7 +31,7 @@ class MoveStatics extends MiniPhase with SymTransformer {
2831

2932
def transformSym(sym: SymDenotation)(using Context): SymDenotation =
3033
if (sym.hasAnnotation(defn.ScalaStaticAnnot) && sym.owner.is(Flags.Module) && sym.owner.companionClass.exists &&
31-
(sym.is(Flags.Method) || !(sym.isMutableVarOrAccessor && sym.owner.companionClass.is(Flags.Trait)))) {
34+
(sym.is(Flags.Method) || !(sym.isMutableVarOrAccessor && sym.owner.companionClass.is(Flags.Trait)) && !sym.symbol.name.is(LazyVarHandleName))) {
3235
sym.owner.asClass.delete(sym.symbol)
3336
sym.owner.companionClass.asClass.enter(sym.symbol)
3437
sym.copySymDenotation(owner = sym.owner.companionClass)
@@ -65,7 +68,7 @@ class MoveStatics extends MiniPhase with SymTransformer {
6568
val moduleTmpl = module.rhs.asInstanceOf[Template]
6669
val companionTmpl = companion.rhs.asInstanceOf[Template]
6770
val (staticDefs, remainingDefs) = moduleTmpl.body.partition {
68-
case memberDef: MemberDef => memberDef.symbol.isScalaStatic
71+
case memberDef: MemberDef => memberDef.symbol.isScalaStatic && !memberDef.symbol.name.is(LazyVarHandleName)
6972
case _ => false
7073
}
7174

project/MiMaFilters.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,15 @@ object MiMaFilters {
141141
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"),
142142
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newModule"),
143143

144+
// Changes to lazy vals (added static constructors)
145+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.Tuple.<clinit>"),
146+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.ArraySeq.<clinit>"),
147+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.concurrent.ExecutionContext.<clinit>"),
148+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.io.Codec.<clinit>"),
149+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.math.BigDecimal.<clinit>"),
150+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.sys.SystemProperties.<clinit>"),
151+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.sys.process.Process.<clinit>"),
152+
144153
// Change `experimental` annotation to a final class
145154
ProblemFilters.exclude[FinalClassProblem]("scala.annotation.experimental"),
146155

tests/printing/transformed/lazy-vals-new.check

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ package <empty> {
99
}
1010
@static private def <clinit>(): Unit =
1111
{
12-
A.OFFSET$_m_0 =
13-
scala.runtime.LazyVals.getOffsetStatic(
14-
classOf[Object {...}].getDeclaredField("x$lzy1"))
12+
A.x$lzy1$lzyHandle =
13+
java.lang.invoke.MethodHandles.lookup().findVarHandle(
14+
classOf[Object {...}], "x$lzy1", classOf[Object])
1515
()
1616
}
17-
@static @static val OFFSET$_m_0: Long =
18-
scala.runtime.LazyVals.getOffsetStatic(
19-
classOf[Object {...}].getDeclaredField("x$lzy1"))
17+
@static private val x$lzy1$lzyHandle: java.lang.invoke.VarHandle =
18+
java.lang.invoke.MethodHandles.lookup().findVarHandle(
19+
classOf[Object {...}], "x$lzy1", classOf[Object])
2020
private def writeReplace(): Object =
2121
new scala.runtime.ModuleSerializationProxy(classOf[A])
2222
@volatile private lazy var x$lzy1: Object = null
@@ -33,7 +33,7 @@ package <empty> {
3333
val current: Object = A.x$lzy1
3434
if current eq null then
3535
if
36-
scala.runtime.LazyVals.objCAS(this, A.OFFSET$_m_0, null,
36+
A.x$lzy1$lzyHandle.compareAndSet(this, null,
3737
scala.runtime.LazyVals.Evaluating)
3838
then
3939
{
@@ -49,15 +49,14 @@ package <empty> {
4949
}
5050
finally
5151
if
52-
scala.runtime.LazyVals.objCAS(this, A.OFFSET$_m_0,
52+
A.x$lzy1$lzyHandle.compareAndSet(this,
5353
scala.runtime.LazyVals.Evaluating, result).unary_!()
5454
then
5555
{
5656
val lock: scala.runtime.LazyVals.LazyVals$Waiting =
5757
A.x$lzy1.asInstanceOf[
5858
scala.runtime.LazyVals.LazyVals$Waiting]
59-
scala.runtime.LazyVals.objCAS(this, A.OFFSET$_m_0, lock,
60-
result)
59+
A.x$lzy1$lzyHandle.compareAndSet(this, lock, result)
6160
lock.countDown()
6261
}
6362
else ()
@@ -71,7 +70,7 @@ package <empty> {
7170
then
7271
if current eq scala.runtime.LazyVals.Evaluating then
7372
{
74-
scala.runtime.LazyVals.objCAS(this, A.OFFSET$_m_0, current,
73+
A.x$lzy1$lzyHandle.compareAndSet(this, current,
7574
new scala.runtime.LazyVals.LazyVals$Waiting())
7675
()
7776
}

0 commit comments

Comments
 (0)