Skip to content

Commit

Permalink
Emit local module like lazy val
Browse files Browse the repository at this point in the history
The motivation is to use the new fine-grained lock scoping that
local lazies have since #5294.

Fixes scala/scala-dev#235

Co-Authored-By: Jason Zaugg <jzaugg@gmail.com>
  • Loading branch information
adriaanm and retronym committed Sep 28, 2016
1 parent d04cda1 commit 346b012
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 38 deletions.
42 changes: 20 additions & 22 deletions src/compiler/scala/tools/nsc/transform/Fields.scala
Original file line number Diff line number Diff line change
Expand Up @@ -193,20 +193,6 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
// not be emitted as ACC_FINAL. They are FINAL in the Scala sense, though: cannot be overridden.
private final val ModuleOrLazyFieldFlags = FINAL | PrivateLocal | SYNTHETIC | NEEDS_TREES

private def newModuleVarSymbol(owner: Symbol, module: Symbol, tp: Type): TermSymbol = {
// println(s"new module var in $site for $module of type $tp")
val flags = MODULEVAR | (if (owner.isClass) ModuleOrLazyFieldFlags else 0)

val moduleVar =
(owner.newVariable(nme.moduleVarName(module.name.toTermName), module.pos.focus, flags)
setInfo tp
addAnnotation VolatileAttr)

moduleOrLazyVarOf(module) = moduleVar

moduleVar
}

private def moduleInit(module: Symbol, moduleVar: Symbol) = {
// println(s"moduleInit for $module in ${module.ownerChain} --> ${moduleVarOf.get(module)}")
def moduleVarRef = gen.mkAttributedRef(moduleVar)
Expand Down Expand Up @@ -380,8 +366,16 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
(existingGetter ne NoSymbol) && (tp matches (site memberInfo existingGetter).resultType) // !existingGetter.isDeferred && -- see (3)
}

def newModuleVarMember(member: Symbol): TermSymbol =
newModuleVarSymbol(clazz, member, site.memberType(member).resultType)
def newModuleVarMember(module: Symbol): TermSymbol = {
val moduleVar =
(clazz.newVariable(nme.moduleVarName(module.name.toTermName), module.pos.focus, MODULEVAR | ModuleOrLazyFieldFlags)
setInfo site.memberType(module).resultType
addAnnotation VolatileAttr)

moduleOrLazyVarOf(module) = moduleVar

moduleVar
}

def newLazyVarMember(member: Symbol): TermSymbol =
Fields.this.newLazyVarMember(clazz, member, site.memberType(member).resultType)
Expand Down Expand Up @@ -531,7 +525,8 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
def mkTypedValDef(sym: Symbol, rhs: Tree = EmptyTree) = typedPos(sym.pos)(ValDef(sym, rhs)).asInstanceOf[ValDef]

/**
* Desugar a local `lazy val x: Int = rhs` into
* Desugar a local `lazy val x: Int = rhs` or a local object into
*
* ```
* val x$lzy = new scala.runtime.LazyInt()
* def x$lzycompute(): Int =
Expand All @@ -541,10 +536,13 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
* }
* def x(): Int = if (x$lzy.initialized()) x$lzy.value() else x$lzycompute()
* ```
*
* The expansion is the same for local lazy vals and local objects,
* except for the name of the val ($lzy or
*/
private def mkLazyLocalDef(lazyVal: Symbol, rhs: Tree): Tree = {
import CODE._
import scala.reflect.NameTransformer.LAZY_LOCAL_SUFFIX_STRING
import scala.reflect.{NameTransformer => nx}
val owner = lazyVal.owner

val lazyValType = lazyVal.tpe.resultType
Expand All @@ -555,8 +553,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
val lazyName = lazyVal.name.toTermName
val pos = lazyVal.pos.focus

// used twice: once in the same owner as the lazy val, another time inside the compute method
val localLazyName = lazyName append LAZY_LOCAL_SUFFIX_STRING
val localLazyName = lazyName append (if (lazyVal.isModule) nx.MODULE_VAR_SUFFIX_STRING else nx.LAZY_LOCAL_SUFFIX_STRING)

// The lazy holder val need not be mutable, as we write to its field.
// In fact, it MUST not be mutable to avoid capturing it as an ObjectRef in lambdalift
Expand Down Expand Up @@ -730,8 +727,9 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
val cd = super.transform(ClassDef(statSym.moduleClass, impl) setType NoType)
if (currOwner.isClass) cd
else { // local module -- symbols cannot be generated by info transformer, so do it all here
val moduleVar = newModuleVarSymbol(currOwner, statSym, statSym.info.resultType)
Thicket(cd :: mkTypedValDef(moduleVar) :: mkAccessor(statSym)(moduleInit(statSym, moduleVar)) :: Nil)
val Block(stats, _) = mkLazyLocalDef(statSym, gen.newModule(statSym, statSym.info.resultType))

Thicket(cd :: stats)
}

case tree =>
Expand Down
39 changes: 39 additions & 0 deletions test/files/run/SD-235.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
class C {
var ORef: Object = null
def test = {
object O {
assert(!Thread.holdsLock(C.this))
assert(Thread.holdsLock(ORef))
}
val captor = new { def oh = O }
val refField = captor.getClass.getDeclaredFields.last
refField.setAccessible(true)
assert(refField.getType.toString.contains("LazyRef"), refField)
ORef = refField.get(captor)
O
}
}

class D {
var ORef: Object = null
def test = {
lazy val O = {
assert(!Thread.holdsLock(D.this))
assert(Thread.holdsLock(ORef))
"O"
}
val captor = new { def oh = O }
val refField = captor.getClass.getDeclaredFields.last
refField.setAccessible(true)
assert(refField.getType.toString.contains("LazyRef"), refField)
ORef = refField.get(captor)
O
}
}

object Test {
def main(args: Array[String]): Unit = {
new C().test
new D().test
}
}
17 changes: 9 additions & 8 deletions test/files/run/delambdafy_t6028.check
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ package <empty> {
}
};
def bar(barParam: String): Object = {
@volatile var MethodLocalObject$module: runtime.VolatileObjectRef = scala.runtime.VolatileObjectRef.zero();
lazy <artifact> val MethodLocalObject$module: scala.runtime.LazyRef = new scala.runtime.LazyRef();
T.this.MethodLocalObject$1(barParam, MethodLocalObject$module)
};
def tryy(tryyParam: String): Function0 = {
Expand All @@ -42,13 +42,14 @@ package <empty> {
<synthetic> <stable> <artifact> def $outer(): T = MethodLocalObject$2.this.$outer;
<synthetic> <stable> <artifact> def $outer(): T = MethodLocalObject$2.this.$outer
};
final private[this] def MethodLocalObject$lzycompute$1(barParam$1: String, MethodLocalObject$module$1: runtime.VolatileObjectRef): Unit = T.this.synchronized[Unit](if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null))
MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1));
final <stable> private[this] def MethodLocalObject$1(barParam$1: String, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = {
if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null))
T.this.MethodLocalObject$lzycompute$1(barParam$1, MethodLocalObject$module$1);
(MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type](): T#MethodLocalObject$2.type)
};
final <artifact> private[this] def MethodLocalObject$lzycompute$1(barParam$1: String, MethodLocalObject$module$1: scala.runtime.LazyRef): T#MethodLocalObject$2.type = MethodLocalObject$module$1.synchronized[T#MethodLocalObject$2.type](if (MethodLocalObject$module$1.initialized())
MethodLocalObject$module$1.value().$asInstanceOf[T#MethodLocalObject$2.type]()
else
MethodLocalObject$module$1.initialize(new T#MethodLocalObject$2.type(T.this, barParam$1)).$asInstanceOf[T#MethodLocalObject$2.type]());
final private[this] def MethodLocalObject$1(barParam$1: String, MethodLocalObject$module$1: scala.runtime.LazyRef): T#MethodLocalObject$2.type = if (MethodLocalObject$module$1.initialized())
MethodLocalObject$module$1.value().$asInstanceOf[T#MethodLocalObject$2.type]()
else
T.this.MethodLocalObject$lzycompute$1(barParam$1, MethodLocalObject$module$1);
final <artifact> private[this] def $anonfun$tryy$1(tryyParam$1: String, tryyLocal$1: runtime.ObjectRef): Unit = try {
tryyLocal$1.elem = tryyParam$1
} finally ()
Expand Down
9 changes: 9 additions & 0 deletions test/files/run/local_obj.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class C {
val z = 2
def mod = { object x { val y = z } ; x.y }
}

object Test extends App {
val c = new C
assert(c.mod == c.z, s"${c.mod} != ${c.z}")
}
17 changes: 9 additions & 8 deletions test/files/run/t6028.check
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ package <empty> {
}
};
def bar(barParam: Int): Object = {
@volatile var MethodLocalObject$module: runtime.VolatileObjectRef = scala.runtime.VolatileObjectRef.zero();
lazy <artifact> val MethodLocalObject$module: scala.runtime.LazyRef = new scala.runtime.LazyRef();
T.this.MethodLocalObject$1(barParam, MethodLocalObject$module)
};
def tryy(tryyParam: Int): Function0 = {
Expand Down Expand Up @@ -54,13 +54,14 @@ package <empty> {
<synthetic> <stable> <artifact> def $outer(): T = MethodLocalObject$2.this.$outer;
<synthetic> <stable> <artifact> def $outer(): T = MethodLocalObject$2.this.$outer
};
final private[this] def MethodLocalObject$lzycompute$1(barParam$1: Int, MethodLocalObject$module$1: runtime.VolatileObjectRef): Unit = T.this.synchronized[Unit](if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null))
MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1));
final <stable> private[this] def MethodLocalObject$1(barParam$1: Int, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = {
if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null))
T.this.MethodLocalObject$lzycompute$1(barParam$1, MethodLocalObject$module$1);
(MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type](): T#MethodLocalObject$2.type)
};
final <artifact> private[this] def MethodLocalObject$lzycompute$1(barParam$1: Int, MethodLocalObject$module$1: scala.runtime.LazyRef): T#MethodLocalObject$2.type = MethodLocalObject$module$1.synchronized[T#MethodLocalObject$2.type](if (MethodLocalObject$module$1.initialized())
MethodLocalObject$module$1.value().$asInstanceOf[T#MethodLocalObject$2.type]()
else
MethodLocalObject$module$1.initialize(new T#MethodLocalObject$2.type(T.this, barParam$1)).$asInstanceOf[T#MethodLocalObject$2.type]());
final private[this] def MethodLocalObject$1(barParam$1: Int, MethodLocalObject$module$1: scala.runtime.LazyRef): T#MethodLocalObject$2.type = if (MethodLocalObject$module$1.initialized())
MethodLocalObject$module$1.value().$asInstanceOf[T#MethodLocalObject$2.type]()
else
T.this.MethodLocalObject$lzycompute$1(barParam$1, MethodLocalObject$module$1);
@SerialVersionUID(value = 0) final <synthetic> class $anonfun$tryy$1 extends scala.runtime.AbstractFunction0$mcV$sp with Serializable {
def <init>($outer: T, tryyParam$1: Int, tryyLocal$1: runtime.IntRef): <$anon: Function0> = {
$anonfun$tryy$1.super.<init>();
Expand Down

0 comments on commit 346b012

Please sign in to comment.