aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/transform/LazyVals.scala
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/src/dotty/tools/dotc/transform/LazyVals.scala')
-rw-r--r--compiler/src/dotty/tools/dotc/transform/LazyVals.scala418
1 files changed, 418 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala
new file mode 100644
index 000000000..e63a7c3a7
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala
@@ -0,0 +1,418 @@
+package dotty.tools.dotc
+package transform
+
+import dotty.tools.dotc.core.Annotations.Annotation
+import dotty.tools.dotc.core.Phases.NeedsCompanions
+
+import scala.collection.mutable
+import core._
+import Contexts._
+import Symbols._
+import Decorators._
+import NameOps._
+import StdNames.nme
+import rewrite.Rewrites.patch
+import util.Positions.Position
+import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransformer, MiniPhaseTransform}
+import dotty.tools.dotc.ast.Trees._
+import dotty.tools.dotc.ast.{untpd, tpd}
+import dotty.tools.dotc.core.Constants.Constant
+import dotty.tools.dotc.core.Types.{ExprType, NoType, MethodType}
+import dotty.tools.dotc.core.Names.Name
+import SymUtils._
+import scala.collection.mutable.ListBuffer
+import dotty.tools.dotc.core.Denotations.SingleDenotation
+import dotty.tools.dotc.core.SymDenotations.SymDenotation
+import dotty.tools.dotc.core.DenotTransformers.{SymTransformer, IdentityDenotTransformer, DenotTransformer}
+import Erasure.Boxing.adaptToType
+
+class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer {
+ import LazyVals._
+
+ import tpd._
+
+ def transformer = new LazyVals
+
+ val containerFlags = Flags.Synthetic | Flags.Mutable | Flags.Lazy
+ val initFlags = Flags.Synthetic | Flags.Method
+
+ val containerFlagsMask = Flags.Method | Flags.Lazy | Flags.Accessor | Flags.Module
+
+ /** 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)
+ val appendOffsetDefs = mutable.Map.empty[Symbol, OffsetInfo]
+
+ override def phaseName: String = "LazyVals"
+
+ /** List of names of phases that should have finished processing of tree
+ * before this phase starts processing same tree */
+ override def runsAfter = Set(classOf[Mixin])
+
+ override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree =
+ transformLazyVal(tree)
+
+
+ override def transformValDef(tree: tpd.ValDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
+ transformLazyVal(tree)
+ }
+
+ def transformLazyVal(tree: ValOrDefDef)(implicit ctx: Context, info: TransformerInfo): Tree = {
+ val sym = tree.symbol
+ if (!(sym is Flags.Lazy) || sym.owner.is(Flags.Trait) || (sym.isStatic && sym.is(Flags.Module))) tree
+ else {
+ val isField = sym.owner.isClass
+ if (isField) {
+ if (sym.isVolatile ||
+ (sym.is(Flags.Module)/* || ctx.scala2Mode*/) &&
+ // TODO assume @volatile once LazyVals uses static helper constructs instead of
+ // ones in the companion object.
+ !sym.is(Flags.Synthetic))
+ // module class is user-defined.
+ // Should be threadsafe, to mimic safety guaranteed by global object
+ transformMemberDefVolatile(tree)
+ else if (sym.is(Flags.Module)) // synthetic module
+ transformSyntheticModule(tree)
+ else
+ transformMemberDefNonVolatile(tree)
+ }
+ else transformLocalDef(tree)
+ }
+ }
+
+
+ /** Append offset fields to companion objects
+ */
+ override def transformTemplate(template: tpd.Template)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
+ val cls = ctx.owner.asClass
+
+ appendOffsetDefs.get(cls) match {
+ case None => template
+ case Some(data) =>
+ data.defs.foreach(_.symbol.addAnnotation(Annotation(defn.ScalaStaticAnnot)))
+ cpy.Template(template)(body = addInFront(data.defs, template.body))
+ }
+
+ }
+
+ private def addInFront(prefix: List[Tree], stats: List[Tree]) = stats match {
+ case first :: rest if isSuperConstrCall(first) => first :: prefix ::: rest
+ case _ => prefix ::: stats
+ }
+
+ /** Make an eager val that would implement synthetic module.
+ * Eager val ensures thread safety and has less code generated.
+ *
+ */
+ def transformSyntheticModule(tree: ValOrDefDef)(implicit ctx: Context) = {
+ val sym = tree.symbol
+ val holderSymbol = ctx.newSymbol(sym.owner, sym.asTerm.name.lazyLocalName,
+ Flags.Synthetic, sym.info.widen.resultType).enteredAfter(this)
+ val field = ValDef(holderSymbol, tree.rhs.changeOwnerAfter(sym, holderSymbol, this))
+ val getter = DefDef(sym.asTerm, ref(holderSymbol))
+ Thicket(field, getter)
+ }
+
+ /** Replace a local lazy val inside a method,
+ * with a LazyHolder from
+ * dotty.runtime(eg dotty.runtime.LazyInt)
+ */
+ def transformLocalDef(x: ValOrDefDef)(implicit ctx: Context) = {
+ val valueInitter = x.rhs
+ val holderName = ctx.freshName(x.name.asTermName.lazyLocalName).toTermName
+ val initName = ctx.freshName(x.name ++ StdNames.nme.LAZY_LOCAL_INIT).toTermName
+ val tpe = x.tpe.widen.resultType.widen
+
+ val holderType =
+ if (tpe isRef defn.IntClass) "LazyInt"
+ else if (tpe isRef defn.LongClass) "LazyLong"
+ else if (tpe isRef defn.BooleanClass) "LazyBoolean"
+ else if (tpe isRef defn.FloatClass) "LazyFloat"
+ else if (tpe isRef defn.DoubleClass) "LazyDouble"
+ else if (tpe isRef defn.ByteClass) "LazyByte"
+ else if (tpe isRef defn.CharClass) "LazyChar"
+ else if (tpe isRef defn.ShortClass) "LazyShort"
+ else "LazyRef"
+
+
+ val holderImpl = ctx.requiredClass("dotty.runtime." + holderType)
+
+ val holderSymbol = ctx.newSymbol(x.symbol.owner, holderName, containerFlags, holderImpl.typeRef, coord = x.pos)
+ val initSymbol = ctx.newSymbol(x.symbol.owner, initName, initFlags, MethodType(Nil, tpe), coord = x.pos)
+ val result = ref(holderSymbol).select(lazyNme.value)
+ val flag = ref(holderSymbol).select(lazyNme.initialized)
+ val initer = valueInitter.changeOwner(x.symbol, initSymbol)
+ val initBody =
+ adaptToType(
+ ref(holderSymbol).select(defn.Object_synchronized).appliedTo(
+ adaptToType(mkNonThreadSafeDef(result, flag, initer), defn.ObjectType)),
+ tpe)
+ val initTree = DefDef(initSymbol, initBody)
+ val holderTree = ValDef(holderSymbol, New(holderImpl.typeRef, List()))
+ val methodBody = tpd.If(flag.ensureApplied,
+ result.ensureApplied,
+ ref(initSymbol).ensureApplied).ensureConforms(tpe)
+
+ val methodTree = DefDef(x.symbol.asTerm, methodBody)
+ ctx.debuglog(s"found a lazy val ${x.show},\n rewrote with ${holderTree.show}")
+ Thicket(holderTree, initTree, methodTree)
+ }
+
+
+ override def transformStats(trees: List[tpd.Tree])(implicit ctx: Context, info: TransformerInfo): List[tpd.Tree] = {
+ // backend requires field usage to be after field definition
+ // need to bring containers to start of method
+ val (holders, stats) =
+ atGroupEnd { implicit ctx: Context =>
+ trees.partition {
+ _.symbol.flags.&~(Flags.Touched) == containerFlags
+ // Filtering out Flags.Touched is not required currently, as there are no LazyTypes involved here
+ // but just to be more safe
+ }
+ }
+ holders:::stats
+ }
+
+ /** Create non-threadsafe lazy accessor equivalent to such code
+ * def methodSymbol() = {
+ * if (flag) target
+ * else {
+ * target = rhs
+ * flag = true
+ * target
+ * }
+ * }
+ */
+
+ def mkNonThreadSafeDef(target: Tree, flag: Tree, rhs: Tree)(implicit ctx: Context) = {
+ val setFlag = flag.becomes(Literal(Constants.Constant(true)))
+ val setTargets = if (isWildcardArg(rhs)) Nil else target.becomes(rhs) :: Nil
+ val init = Block(setFlag :: setTargets, target.ensureApplied)
+ If(flag.ensureApplied, target.ensureApplied, init)
+ }
+
+ /** Create non-threadsafe lazy accessor for not-nullable types equivalent to such code
+ * def methodSymbol() = {
+ * if (target eq null) {
+ * target = rhs
+ * target
+ * } else target
+ * }
+ */
+ def mkDefNonThreadSafeNonNullable(target: Symbol, rhs: Tree)(implicit ctx: Context) = {
+ val cond = ref(target).select(nme.eq).appliedTo(Literal(Constant(null)))
+ val exp = ref(target)
+ val setTarget = exp.becomes(rhs)
+ val init = Block(List(setTarget), exp)
+ If(cond, init, exp)
+ }
+
+ def transformMemberDefNonVolatile(x: ValOrDefDef)(implicit ctx: Context) = {
+ val claz = x.symbol.owner.asClass
+ val tpe = x.tpe.widen.resultType.widen
+ assert(!(x.symbol is Flags.Mutable))
+ val containerName = ctx.freshName(x.name.asTermName.lazyLocalName).toTermName
+ val containerSymbol = ctx.newSymbol(claz, containerName,
+ x.symbol.flags &~ containerFlagsMask | containerFlags | Flags.Private,
+ tpe, coord = x.symbol.coord
+ ).enteredAfter(this)
+
+ val containerTree = ValDef(containerSymbol, defaultValue(tpe))
+ if (x.tpe.isNotNull && tpe <:< defn.ObjectType) { // can use 'null' value instead of flag
+ val slowPath = DefDef(x.symbol.asTerm, mkDefNonThreadSafeNonNullable(containerSymbol, x.rhs))
+ Thicket(containerTree, slowPath)
+ }
+ else {
+ val flagName = ctx.freshName(x.name ++ StdNames.nme.BITMAP_PREFIX).toTermName
+ val flagSymbol = ctx.newSymbol(x.symbol.owner, flagName, containerFlags | Flags.Private, defn.BooleanType).enteredAfter(this)
+ val flag = ValDef(flagSymbol, Literal(Constants.Constant(false)))
+ val slowPath = DefDef(x.symbol.asTerm, mkNonThreadSafeDef(ref(containerSymbol), ref(flagSymbol), x.rhs))
+ Thicket(containerTree, flag, slowPath)
+ }
+ }
+
+ /** Create a threadsafe lazy accessor equivalent to such code
+ *
+ * def methodSymbol(): Int = {
+ * val result: Int = 0
+ * val retry: Boolean = true
+ * var flag: Long = 0L
+ * while retry do {
+ * flag = dotty.runtime.LazyVals.get(this, $claz.$OFFSET)
+ * dotty.runtime.LazyVals.STATE(flag, 0) match {
+ * case 0 =>
+ * if dotty.runtime.LazyVals.CAS(this, $claz.$OFFSET, flag, 1, $ord) {
+ * try {result = rhs} catch {
+ * case x: Throwable =>
+ * dotty.runtime.LazyVals.setFlag(this, $claz.$OFFSET, 0, $ord)
+ * throw x
+ * }
+ * $target = result
+ * dotty.runtime.LazyVals.setFlag(this, $claz.$OFFSET, 3, $ord)
+ * retry = false
+ * }
+ * case 1 =>
+ * dotty.runtime.LazyVals.wait4Notification(this, $claz.$OFFSET, flag, $ord)
+ * case 2 =>
+ * dotty.runtime.LazyVals.wait4Notification(this, $claz.$OFFSET, flag, $ord)
+ * case 3 =>
+ * retry = false
+ * result = $target
+ * }
+ * }
+ * result
+ * }
+ */
+ def mkThreadSafeDef(methodSymbol: TermSymbol, claz: ClassSymbol, ord: Int, target: Symbol, rhs: Tree, tp: Types.Type, offset: Tree, getFlag: Tree, stateMask: Tree, casFlag: Tree, setFlagState: Tree, waitOnLock: Tree)(implicit ctx: Context) = {
+ val initState = Literal(Constants.Constant(0))
+ val computeState = Literal(Constants.Constant(1))
+ val notifyState = Literal(Constants.Constant(2))
+ val computedState = Literal(Constants.Constant(3))
+ val flagSymbol = ctx.newSymbol(methodSymbol, lazyNme.flag, containerFlags, defn.LongType)
+ val flagDef = ValDef(flagSymbol, Literal(Constant(0L)))
+
+ val thiz = This(claz)(ctx.fresh.setOwner(claz))
+
+ val resultSymbol = ctx.newSymbol(methodSymbol, lazyNme.result, containerFlags, tp)
+ val resultDef = ValDef(resultSymbol, defaultValue(tp))
+
+ val retrySymbol = ctx.newSymbol(methodSymbol, lazyNme.retry, containerFlags, defn.BooleanType)
+ val retryDef = ValDef(retrySymbol, Literal(Constants.Constant(true)))
+
+ val whileCond = ref(retrySymbol)
+
+ val compute = {
+ val handlerSymbol = ctx.newSymbol(methodSymbol, nme.ANON_FUN, Flags.Synthetic,
+ MethodType(List(nme.x_1), List(defn.ThrowableType), defn.IntType))
+ val caseSymbol = ctx.newSymbol(methodSymbol, nme.DEFAULT_EXCEPTION_NAME, Flags.Synthetic, defn.ThrowableType)
+ val triggerRetry = setFlagState.appliedTo(thiz, offset, initState, Literal(Constant(ord)))
+ val complete = setFlagState.appliedTo(thiz, offset, computedState, Literal(Constant(ord)))
+
+ val handler = CaseDef(Bind(caseSymbol, ref(caseSymbol)), EmptyTree,
+ Block(List(triggerRetry), Throw(ref(caseSymbol))
+ ))
+
+ val compute = ref(resultSymbol).becomes(rhs)
+ val tr = Try(compute, List(handler), EmptyTree)
+ val assign = ref(target).becomes(ref(resultSymbol))
+ val noRetry = ref(retrySymbol).becomes(Literal(Constants.Constant(false)))
+ val body = If(casFlag.appliedTo(thiz, offset, ref(flagSymbol), computeState, Literal(Constant(ord))),
+ Block(tr :: assign :: complete :: noRetry :: Nil, Literal(Constant(()))),
+ Literal(Constant(())))
+
+ CaseDef(initState, EmptyTree, body)
+ }
+
+ val waitFirst = {
+ val wait = waitOnLock.appliedTo(thiz, offset, ref(flagSymbol), Literal(Constant(ord)))
+ CaseDef(computeState, EmptyTree, wait)
+ }
+
+ val waitSecond = {
+ val wait = waitOnLock.appliedTo(thiz, offset, ref(flagSymbol), Literal(Constant(ord)))
+ CaseDef(notifyState, EmptyTree, wait)
+ }
+
+ val computed = {
+ val noRetry = ref(retrySymbol).becomes(Literal(Constants.Constant(false)))
+ val result = ref(resultSymbol).becomes(ref(target))
+ val body = Block(noRetry :: result :: Nil, Literal(Constant(())))
+ CaseDef(computedState, EmptyTree, body)
+ }
+
+ val default = CaseDef(Underscore(defn.LongType), EmptyTree, Literal(Constant(())))
+
+ val cases = Match(stateMask.appliedTo(ref(flagSymbol), Literal(Constant(ord))),
+ List(compute, waitFirst, waitSecond, computed, default)) //todo: annotate with @switch
+
+ val whileBody = List(ref(flagSymbol).becomes(getFlag.appliedTo(thiz, offset)), cases)
+ val cycle = WhileDo(methodSymbol, whileCond, whileBody)
+ DefDef(methodSymbol, Block(resultDef :: retryDef :: flagDef :: cycle :: Nil, ref(resultSymbol)))
+ }
+
+ def transformMemberDefVolatile(x: ValOrDefDef)(implicit ctx: Context) = {
+ assert(!(x.symbol is Flags.Mutable))
+
+ val tpe = x.tpe.widen.resultType.widen
+ val claz = x.symbol.owner.asClass
+ val thizClass = Literal(Constant(claz.info))
+ val helperModule = ctx.requiredModule("dotty.runtime.LazyVals")
+ val getOffset = Select(ref(helperModule), lazyNme.RLazyVals.getOffset)
+ var offsetSymbol: TermSymbol = null
+ var flag: Tree = EmptyTree
+ var ord = 0
+
+ def offsetName(id: Int) = (StdNames.nme.LAZY_FIELD_OFFSET + (if(x.symbol.owner.is(Flags.Module)) "_m_" else "") + id.toString).toTermName
+
+ // compute or create appropriate offsetSymol, bitmap and bits used by current ValDef
+ appendOffsetDefs.get(claz) match {
+ case Some(info) =>
+ val flagsPerLong = (64 / dotty.runtime.LazyVals.BITS_PER_LAZY_VAL).toInt
+ info.ord += 1
+ ord = info.ord % flagsPerLong
+ val id = info.ord / flagsPerLong
+ val offsetById = offsetName(id)
+ if (ord != 0) { // there are unused bits in already existing flag
+ offsetSymbol = claz.info.decl(offsetById)
+ .suchThat(sym => (sym is Flags.Synthetic) && sym.isTerm)
+ .symbol.asTerm
+ } else { // need to create a new flag
+ offsetSymbol = ctx.newSymbol(claz, offsetById, Flags.Synthetic, defn.LongType).enteredAfter(this)
+ offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot))
+ val flagName = (StdNames.nme.BITMAP_PREFIX + id.toString).toTermName
+ val flagSymbol = ctx.newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this)
+ flag = ValDef(flagSymbol, Literal(Constants.Constant(0L)))
+ val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thizClass, Literal(Constant(flagName.toString))))
+ info.defs = offsetTree :: info.defs
+ }
+
+ case None =>
+ offsetSymbol = ctx.newSymbol(claz, offsetName(0), Flags.Synthetic, defn.LongType).enteredAfter(this)
+ offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot))
+ val flagName = (StdNames.nme.BITMAP_PREFIX + "0").toTermName
+ val flagSymbol = ctx.newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this)
+ flag = ValDef(flagSymbol, Literal(Constants.Constant(0L)))
+ val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thizClass, Literal(Constant(flagName.toString))))
+ appendOffsetDefs += (claz -> new OffsetInfo(List(offsetTree), ord))
+ }
+
+ val containerName = ctx.freshName(x.name.asTermName.lazyLocalName).toTermName
+ val containerSymbol = ctx.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)
+ 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 accessor = mkThreadSafeDef(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)
+ }
+}
+
+object LazyVals {
+ object lazyNme {
+ object RLazyVals {
+ import dotty.runtime.LazyVals._
+ val get = Names.get.toTermName
+ val setFlag = Names.setFlag.toTermName
+ val wait4Notification = Names.wait4Notification.toTermName
+ val state = Names.state.toTermName
+ val cas = Names.cas.toTermName
+ val getOffset = Names.getOffset.toTermName
+ }
+ val flag = "flag".toTermName
+ val result = "result".toTermName
+ val value = "value".toTermName
+ val initialized = "initialized".toTermName
+ val retry = "retry".toTermName
+ }
+}
+
+
+