Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/NameKinds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/NameTags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
69 changes: 40 additions & 29 deletions compiler/src/dotty/tools/dotc/transform/LazyVals.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -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

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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
)
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
}

Expand Down Expand Up @@ -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
Expand All @@ -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
}
}
9 changes: 6 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/MoveStatics.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.*

Expand All @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down
9 changes: 9 additions & 0 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.<clinit>"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.ArraySeq.<clinit>"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.concurrent.ExecutionContext.<clinit>"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.io.Codec.<clinit>"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.math.BigDecimal.<clinit>"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.sys.SystemProperties.<clinit>"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.sys.process.Process.<clinit>"),

// Change `experimental` annotation to a final class
ProblemFilters.exclude[FinalClassProblem]("scala.annotation.experimental"),

Expand Down
21 changes: 10 additions & 11 deletions tests/printing/transformed/lazy-vals-new.check
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ package <empty> {
}
@static private def <clinit>(): 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
Expand All @@ -33,7 +33,7 @@ package <empty> {
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
{
Expand All @@ -49,15 +49,14 @@ package <empty> {
}
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 ()
Expand All @@ -71,7 +70,7 @@ package <empty> {
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())
()
}
Expand Down
Loading