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 @@ -731,7 +731,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")
Comment on lines +734 to +735
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to explicitlly guard these on JDK8: their usage is not reachable on JDK8

@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 @@ -398,6 +398,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
133 changes: 106 additions & 27 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 @@ -27,8 +27,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 @@ -108,12 +110,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 !selectImpl.useVarHandles then //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 @@ -273,6 +282,33 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
}
}

private object selectImpl {
inline def apply[T](usingOffset: => T, usingVarHandle: => T)(using Context): T =
if useVarHandles(using ctx) then usingVarHandle else usingOffset

private def checkVarHandlesAllowed(using Context) =
!ctx.settings.YlegacyLazyVals.value && {
val releaseVersion = ctx.settings.javaOutputVersion.value
if releaseVersion.nonEmpty then
releaseVersion.toInt >= 9
else
scala.util.Properties.isJavaAtLeast("9")
}

// Check if environment allows VarHandles, cached per run
private var canUseVarHandles: Boolean = compiletime.uninitialized
private var cachedContext: Context = compiletime.uninitialized
def useVarHandles(using Context) = {
Comment on lines +298 to +301
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's plenty of usages, the check might allocate on each usage mostly due to .toInt or .toIntOption conversions, so let's skip redundant calculations - unlikely to change

if ctx eq cachedContext then
canUseVarHandles
else
canUseVarHandles = checkVarHandlesAllowed
cachedContext = ctx
canUseVarHandles
}
}


/**
* 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
Expand Down Expand Up @@ -327,20 +363,30 @@ 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 offsetOrVarHandle the offset of the field in the storage allocation or its VarHandle
* @param thiz a reference to the transformed class
*/
def mkThreadSafeDef(memberDef: ValOrDefDef,
claz: ClassSymbol,
target: Symbol,
offset: Tree,
offsetOrVarHandle: Tree,
thiz: Tree)(using Context): (DefDef, DefDef) = {
def varHandle = offsetOrVarHandle.ensuring(selectImpl.useVarHandles, "Unexpected VarHandle usage")
def offset = offsetOrVarHandle.ensuring(!selectImpl.useVarHandles, "Unexpected Offset usage")

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 = selectImpl(
usingOffset = Select(ref(defn.LazyValsModule), lazyNme.RLazyVals.objCas),
usingVarHandle = 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 @@ -382,12 +428,18 @@ 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,
:: selectImpl(
usingOffset = casFlag.appliedTo(thiz, offset, ref(lockSymb), ref(resSymb)),
usingVarHandle = 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,
selectImpl(
usingOffset = casFlag.appliedTo(thiz, offset, evaluating, ref(resSymb)),
usingVarHandle = casFlag.appliedTo(thiz, evaluating, ref(resSymb))
).select(nme.UNARY_!).appliedToNone,
lockRel,
unitLiteral
)
Expand All @@ -408,7 +460,10 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
)
// if CAS(_, null, Evaluating)
If(
objCasFlag.appliedTo(thiz, offset, nullLiteral, evaluating),
selectImpl(
usingOffset = casFlag.appliedTo(thiz, offset, nullLiteral, evaluating),
usingVarHandle = 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 @@ -424,7 +479,10 @@ 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,
selectImpl(
usingOffset = casFlag.appliedTo(thiz, offset, ref(current), Select(New(waiting), StdNames.nme.CONSTRUCTOR).ensureApplied),
usingVarHandle = casFlag.appliedTo(thiz, ref(current), Select(New(waiting), StdNames.nme.CONSTRUCTOR).ensureApplied)
) :: Nil,
unitLiteral
),
// if not Evaluating
Expand Down Expand Up @@ -470,23 +528,43 @@ 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.nn.addAnnotation(Annotation(defn.ScalaStaticAnnot, offsetSymbol.nn.span))
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 offsetOrVarHandle = selectImpl(
usingOffset = {
// 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, offsetSymbol.nn.span))
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
ref(offsetSymbol.nn)
},
usingVarHandle = {
// 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 =
ref(defn.MethodHandlesClass).select(defn.MethodHandles_lookup).appliedToNone
.select(defn.MethodHandlesLookup_FindVarHandle).appliedTo(
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
varHandle
}
)

val swapOver =
This(claz)

val (accessorDef, initMethodDef) = mkThreadSafeDef(x, claz, containerSymbol, offset, swapOver)
val (accessorDef, initMethodDef) = mkThreadSafeDef(x, claz, containerSymbol, offsetOrVarHandle, swapOver)
Thicket(containerTree, accessorDef, initMethodDef)
}

Expand Down Expand Up @@ -686,5 +764,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.is(Flags.Mutable) && sym.owner.companionClass.is(Flags.Trait)))) {
(sym.is(Flags.Method) || !(sym.is(Flags.Mutable) && 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
11 changes: 9 additions & 2 deletions compiler/test/dotty/tools/dotc/printing/PrintingTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ class PrintingTest {
FileDiff.checkAndDumpOrUpdate(path.toString, actualLines.toIndexedSeq, checkFilePath)
}

def testIn(testsDir: String, phase: String) =
def testIn(testsDir: String, phase: String, exclude: io.Path => Boolean = _ => false) =
val res = Directory(testsDir).list.toList
.filter(f => f.extension == "scala")
.filterNot(exclude)
.map { f => compileFile(f.jpath, phase) }

val failed = res.filter(!_)
Expand All @@ -66,12 +67,18 @@ class PrintingTest {

end testIn

private val isAtLeastJDK9 = scala.util.Properties.isJavaAtLeast("9")

@Test
def printing: Unit = testIn("tests/printing", "typer")

@Test
def untypedPrinting: Unit = testIn("tests/printing/untyped", "parser")

@Test
def transformedPrinting: Unit = testIn("tests/printing/transformed", "repeatableAnnotations")
def transformedPrinting: Unit = testIn("tests/printing/transformed", "repeatableAnnotations",
exclude = path =>
if isAtLeastJDK9 then false // test all
else Seq("lazy-vals-new-varhandle.scala").contains(path.name)
)
}
3 changes: 3 additions & 0 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ object MiMaFilters {
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.relaxedExtensionImports"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$relaxedExtensionImports$"),
// end of New experimental features in 3.3.X

// Changes to lazy vals (added static constructors)
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.Tuple.<clinit>"),
)
val TastyCore: Seq[ProblemFilter] = Seq(
// Backported in 3.3.6
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[[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
[[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-offset.scala
package <empty> {
@SourceFile("tests/printing/transformed/lazy-vals-new.scala") final module
class A extends Object {
@SourceFile("tests/printing/transformed/lazy-vals-new-offset.scala") final
module class A extends Object {
def <init>(): Unit =
{
super()
Expand Down
2 changes: 2 additions & 0 deletions tests/printing/transformed/lazy-vals-new-offset.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-java-output-version:8
-Ylegacy-lazy-vals:false
Loading
Loading