Skip to content

Commit f256aa8

Browse files
committed
Fix #9424: Add releaseFence() call when mixing in a trait val.
This is a forward port of relevant parts of the upstream PR scala/scala#7028
1 parent e593e85 commit f256aa8

File tree

3 files changed

+118
-1
lines changed

3 files changed

+118
-1
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,7 @@ object StdNames {
562562
val reflect: N = "reflect"
563563
val reflectiveSelectable: N = "reflectiveSelectable"
564564
val reify : N = "reify"
565+
val releaseFence : N = "releaseFence"
565566
val rootMirror : N = "rootMirror"
566567
val run: N = "run"
567568
val runOrElse: N = "runOrElse"

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

+30-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,16 @@ import NameKinds.TraitSetterName
1616
import NameOps._
1717
import Flags._
1818
import Decorators._
19+
import StdNames.nme
20+
21+
import util.Store
1922

2023
object Memoize {
2124
val name: String = "memoize"
25+
26+
private final class MyState {
27+
val classesThatNeedReleaseFence = new util.HashSet[Symbol]
28+
}
2229
}
2330

2431
/** Provides the implementations of all getters and setters, introducing
@@ -37,10 +44,17 @@ object Memoize {
3744
* --> <accessor> <mods> def x_=(y: T): Unit = x = y
3845
*/
3946
class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
47+
import Memoize.MyState
4048
import ast.tpd._
4149

4250
override def phaseName: String = Memoize.name
4351

52+
private var MyState: Store.Location[MyState] = _
53+
private def myState(using Context): MyState = ctx.store(MyState)
54+
55+
override def initContext(ctx: FreshContext): Unit =
56+
MyState = ctx.addLocation[MyState]()
57+
4458
/* Makes sure that, after getters and constructors gen, there doesn't
4559
* exist non-deferred definitions that are not implemented. */
4660
override def checkPostCondition(tree: Tree)(using Context): Unit = {
@@ -69,6 +83,17 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
6983
*/
7084
override def runsAfter: Set[String] = Set(Mixin.name)
7185

86+
override def prepareForUnit(tree: Tree)(using Context): Context =
87+
ctx.fresh.updateStore(MyState, new MyState())
88+
89+
override def transformTemplate(tree: Template)(using Context): Tree =
90+
val cls = ctx.owner.asClass
91+
if myState.classesThatNeedReleaseFence.contains(cls) then
92+
val releaseFenceCall = ref(defn.staticsMethodRef(nme.releaseFence)).appliedToNone
93+
cpy.Template(tree)(tree.constr, tree.parents, Nil, tree.self, tree.body :+ releaseFenceCall)
94+
else
95+
tree
96+
7297
override def transformDefDef(tree: DefDef)(using Context): Tree = {
7398
val sym = tree.symbol
7499

@@ -154,7 +179,11 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
154179
// See tests/run/traitValOverriddenByParamAccessor.scala
155180
tree
156181
else
157-
field.setFlag(Mutable) // Necessary for vals mixed in from traits
182+
if !field.is(Mutable) then
183+
// This is a val mixed in from a trait.
184+
// We make it mutable, and mark the class as needing a releaseFence() in the constructor
185+
field.setFlag(Mutable)
186+
myState.classesThatNeedReleaseFence += sym.owner
158187
val initializer =
159188
if (isErasableBottomField(field, tree.vparamss.head.head.tpt.tpe.classSymbol)) Literal(Constant(()))
160189
else Assign(ref(field), adaptToField(field, ref(tree.vparamss.head.head.symbol)))

compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala

+87
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,93 @@ class TestBCode extends DottyBytecodeTest {
561561
}
562562
}
563563

564+
/* Test that vals in traits cause appropriate `releaseFence()` calls to be emitted. */
565+
566+
private def checkReleaseFence(releaseFenceExpected: Boolean, outputClassName: String, source: String): Unit = {
567+
checkBCode(source) { dir =>
568+
val clsIn = dir.lookupName(outputClassName, directory = false)
569+
val clsNode = loadClassNode(clsIn.input)
570+
val method = getMethod(clsNode, "<init>")
571+
572+
val hasReleaseFence = instructionsFromMethod(method).exists {
573+
case Invoke(_, _, "releaseFence", _, _) => true
574+
case _ => false
575+
}
576+
577+
assertEquals(source, releaseFenceExpected, hasReleaseFence)
578+
}
579+
}
580+
581+
@Test def testInsertReleaseFence(): Unit = {
582+
// An empty trait does not cause a releaseFence.
583+
checkReleaseFence(false, "Bar.class",
584+
"""trait Foo {
585+
|}
586+
|class Bar extends Foo
587+
""".stripMargin)
588+
589+
// A val in a class does not cause a releaseFence.
590+
checkReleaseFence(false, "Bar.class",
591+
"""trait Foo {
592+
|}
593+
|class Bar extends Foo {
594+
| val x: Int = 5
595+
|}
596+
""".stripMargin)
597+
598+
// A val in a trait causes a releaseFence.
599+
checkReleaseFence(true, "Bar.class",
600+
"""trait Foo {
601+
| val x: Int = 5
602+
|}
603+
|class Bar extends Foo
604+
""".stripMargin)
605+
606+
// The presence of a var in the trait does not invalidate the need for a releaseFence.
607+
// Also, indirect mixin.
608+
checkReleaseFence(true, "Bar.class",
609+
"""trait Parent {
610+
| val x: Int = 5
611+
| var y: Int = 6
612+
|}
613+
|trait Foo extends Parent
614+
|class Bar extends Foo
615+
""".stripMargin)
616+
617+
// The presence of a var in the class does not invalidate the need for a releaseFence.
618+
checkReleaseFence(true, "Bar.class",
619+
"""trait Foo {
620+
| val x: Int = 5
621+
|}
622+
|class Bar extends Foo {
623+
| var y: Int = 6
624+
|}
625+
""".stripMargin)
626+
627+
// When inheriting trait vals through a superclass, no releaseFence is inserted.
628+
checkReleaseFence(false, "Bar.class",
629+
"""trait Parent {
630+
| val x: Int = 5
631+
| var y: Int = 6
632+
|}
633+
|class Foo extends Parent // releaseFence in Foo, but not in Bar
634+
|class Bar extends Foo
635+
""".stripMargin)
636+
637+
// Various other stuff that do not cause a releaseFence.
638+
checkReleaseFence(false, "Bar.class",
639+
"""trait Foo {
640+
| var w: Int = 1
641+
| final val x = 2
642+
| def y: Int = 3
643+
| lazy val z: Int = 4
644+
|
645+
| def add(a: Int, b: Int): Int = a + b
646+
|}
647+
|class Bar extends Foo
648+
""".stripMargin)
649+
}
650+
564651
/* Test that objects compile to *final* classes. */
565652

566653
private def checkFinalClass(outputClassName: String, source: String) = {

0 commit comments

Comments
 (0)