Skip to content

Commit 6e0cba7

Browse files
authored
Merge pull request #7270 from retronym/topic/static-object-fields
Make object fields static, and move post-super init to <clinit>
2 parents b44d01e + 5e109ff commit 6e0cba7

File tree

17 files changed

+191
-171
lines changed

17 files changed

+191
-171
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala

-13
Original file line numberDiff line numberDiff line change
@@ -563,23 +563,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
563563
generatedType = genTypeApply()
564564

565565
case Apply(fun @ Select(sup @ Super(superQual, _), _), args) =>
566-
def initModule(): Unit = {
567-
// we initialize the MODULE$ field immediately after the super ctor
568-
if (!initModuleInClinit && !isModuleInitialized &&
569-
jMethodName == INSTANCE_CONSTRUCTOR_NAME &&
570-
fun.symbol.javaSimpleName.toString == INSTANCE_CONSTRUCTOR_NAME &&
571-
isStaticModuleClass(claszSymbol)) {
572-
isModuleInitialized = true
573-
mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
574-
assignModuleInstanceField(mnode)
575-
}
576-
}
577-
578566
// scala/bug#10290: qual can be `this.$outer()` (not just `this`), so we call genLoad (not just ALOAD_0)
579567
genLoad(superQual)
580568
genLoadArguments(args, paramTKs(app))
581569
generatedType = genCallMethod(fun.symbol, InvokeStyle.Super, app.pos, sup.tpe.typeSymbol)
582-
initModule()
583570

584571
// 'new' constructor call: Note: since constructors are
585572
// thought to return an instance of what they construct,

src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala

+37-34
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
7070
var claszSymbol: Symbol = null
7171
var isCZParcelable = false
7272
var isCZStaticModule = false
73-
var initModuleInClinit = false
7473

7574
/* ---------------- idiomatic way to ask questions to typer ---------------- */
7675

@@ -102,26 +101,51 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
102101

103102
/* ---------------- helper utils for generating classes and fields ---------------- */
104103

105-
def genPlainClass(cd: ClassDef): Unit = {
104+
def genPlainClass(cd0: ClassDef): Unit = {
106105
assert(cnode == null, "GenBCode detected nested methods.")
107106

108-
claszSymbol = cd.symbol
107+
claszSymbol = cd0.symbol
109108
isCZParcelable = isAndroidParcelableClass(claszSymbol)
110109
isCZStaticModule = isStaticModuleClass(claszSymbol)
111110
thisBType = classBTypeFromSymbol(claszSymbol)
112-
initModuleInClinit = isCZStaticModule && canAssignModuleInClinit(cd, claszSymbol)
113111

114112
cnode = new ClassNode1()
115113

116114
initJClass(cnode)
115+
val cd = if (isCZStaticModule) {
116+
// Move statements from the primary constructor following the superclass constructor call to
117+
// a newly synthesised tree representing the "<clinit>", which also assigns the MODULE$ field.
118+
// Because the assigments to both the module instance fields, and the fields of the module itself
119+
// are in the <clinit>, these fields can be static + final.
117120

118-
val hasStaticCtor = methodSymbols(cd) exists (_.isStaticConstructor)
119-
if (!hasStaticCtor) {
120-
// but needs one ...
121-
if (isCZStaticModule || isCZParcelable) {
122-
fabricateStaticInit()
121+
// TODO should we do this transformation earlier, say in Constructors? Or would that just cause
122+
// pain for scala-{js, native}?
123+
124+
for (f <- fieldSymbols(claszSymbol)) {
125+
f.setFlag(Flags.STATIC)
123126
}
124-
}
127+
val constructorDefDef = treeInfo.firstConstructor(cd0.impl.body).asInstanceOf[DefDef]
128+
val (uptoSuperStats, remainingConstrStats) = treeInfo.splitAtSuper(constructorDefDef.rhs.asInstanceOf[Block].stats, classOnly = true)
129+
val clInitSymbol = claszSymbol.newMethod(nme.CLASS_CONSTRUCTOR, claszSymbol.pos, Flags.STATIC).setInfo(NullaryMethodType(definitions.UnitTpe))
130+
131+
// We don't need to enter this field into the decls of claszSymbol.info as this is added manually to the generated class
132+
// in addModuleInstanceField. TODO: try adding it to the decls and making the usual field generation do the right thing.
133+
val moduleField = claszSymbol.newValue(nme.MODULE_INSTANCE_FIELD, claszSymbol.pos, Flags.STATIC | Flags.PRIVATE).setInfo(claszSymbol.tpeHK)
134+
135+
val callConstructor = NewFromConstructor(claszSymbol.primaryConstructor).setType(claszSymbol.tpeHK)
136+
val assignModuleField = Assign(global.gen.mkAttributedRef(moduleField).setType(claszSymbol.tpeHK), callConstructor).setType(definitions.UnitTpe)
137+
val remainingConstrStatsSubst = remainingConstrStats.map(_.substituteThis(claszSymbol, global.gen.mkAttributedRef(claszSymbol.sourceModule)).changeOwner(claszSymbol.primaryConstructor -> clInitSymbol))
138+
val clinit = DefDef(clInitSymbol, Block(assignModuleField :: remainingConstrStatsSubst, Literal(Constant(())).setType(definitions.UnitTpe)).setType(definitions.UnitTpe))
139+
deriveClassDef(cd0)(tmpl => deriveTemplate(tmpl)(body =>
140+
clinit :: body.map {
141+
case `constructorDefDef` => copyDefDef(constructorDefDef)(rhs = Block(uptoSuperStats, constructorDefDef.rhs.asInstanceOf[Block].expr))
142+
case tree => tree
143+
}
144+
))
145+
} else cd0
146+
147+
val hasStaticCtor = methodSymbols(cd) exists (_.isStaticConstructor)
148+
if (!hasStaticCtor && isCZParcelable) fabricateStaticInitAndroid()
125149

126150
val optSerial: Option[Long] = serialVUID(claszSymbol)
127151
/* serialVersionUID can't be put on interfaces (it's a private field).
@@ -204,17 +228,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
204228
*/
205229
private def addModuleInstanceField(): Unit = {
206230
// TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
207-
// scala/scala-dev#194:
208-
// This can't be FINAL on JVM 1.9+ because we assign it from within the
209-
// instance constructor, not from <clinit> directly. Assignment from <clinit>,
210-
// after the constructor has completely finished, seems like the principled
211-
// thing to do, but it would change behaviour when "benign" cyclic references
212-
// between modules exist.
213-
//
214-
// We special case modules with parents that we know don't (and won't ever) refer to
215-
// the module during their construction. These can use a final field, and defer the assigment
216-
// to <clinit>.
217-
val mods = if (initModuleInClinit) GenBCode.PublicStaticFinal else GenBCode.PublicStatic
231+
val mods = GenBCode.PublicStaticFinal
218232
val fv =
219233
cnode.visitField(mods,
220234
strMODULE_INSTANCE_FIELD,
@@ -232,7 +246,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
232246
/*
233247
* must-single-thread
234248
*/
235-
private def fabricateStaticInit(): Unit = {
249+
private def fabricateStaticInitAndroid(): Unit = {
236250

237251
val clinit: asm.MethodVisitor = cnode.visitMethod(
238252
GenBCode.PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
@@ -243,20 +257,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
243257
)
244258
clinit.visitCode()
245259

246-
/* "legacy static initialization" */
247-
if (isCZStaticModule) {
248-
clinit.visitTypeInsn(asm.Opcodes.NEW, thisBType.internalName)
249-
if (initModuleInClinit) clinit.visitInsn(asm.Opcodes.DUP)
250-
251-
clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL,
252-
thisBType.internalName, INSTANCE_CONSTRUCTOR_NAME, "()V", false)
253-
if (initModuleInClinit) {
254-
assignModuleInstanceField(clinit)
255-
}
256-
}
257260
if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisBType.internalName) }
258-
clinit.visitInsn(asm.Opcodes.RETURN)
259261

262+
clinit.visitInsn(asm.Opcodes.RETURN)
260263
clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments
261264
clinit.visitEnd()
262265
}

src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala

+9-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import scala.annotation.{switch, tailrec}
2121
import scala.collection.JavaConverters._
2222
import scala.collection.immutable.BitSet
2323
import scala.collection.mutable
24-
import scala.reflect.ClassTag
2524
import scala.reflect.internal.util.Position
2625
import scala.tools.asm
2726
import scala.tools.asm.Opcodes._
@@ -348,7 +347,7 @@ abstract class BackendUtils extends PerRunInit {
348347
* - methods accessing a public field could be inlined. on the other hand, methods accessing a private
349348
* static field should not be inlined.
350349
*/
351-
def looksLikeForwarderOrFactoryOrTrivial(method: MethodNode, allowPrivateCalls: Boolean): Int = {
350+
def looksLikeForwarderOrFactoryOrTrivial(method: MethodNode, owner: InternalName, allowPrivateCalls: Boolean): Int = {
352351
val paramTypes = Type.getArgumentTypes(method.desc)
353352
val numPrimitives = paramTypes.count(_.getSort < Type.ARRAY) + (if (Type.getReturnType(method.desc).getSort < Type.ARRAY) 1 else 0)
354353

@@ -379,8 +378,14 @@ abstract class BackendUtils extends PerRunInit {
379378
callMi = mi
380379
}
381380
}
382-
} else if (i.getOpcode != GETSTATIC && nonForwarderInstructionTypes(t))
383-
numCallsOrNew = 2 // stop here: not forwarder or trivial
381+
} else if (nonForwarderInstructionTypes(t)) {
382+
if (i.getOpcode == GETSTATIC) {
383+
if (!allowPrivateCalls && owner == i.asInstanceOf[FieldInsnNode].owner)
384+
numCallsOrNew = 2 // stop here: not forwarder or trivial
385+
} else {
386+
numCallsOrNew = 2 // stop here: not forwarder or trivial
387+
}
388+
}
384389
}
385390
if (numCallsOrNew > 1 || numBoxConv > paramTypes.length + 1) -1
386391
else if (numCallsOrNew == 0) if (numBoxConv == 0) 1 else 3

src/compiler/scala/tools/nsc/backend/jvm/opt/InlinerHeuristics.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ abstract class InlinerHeuristics extends PerRunInit {
202202
// or aliases, because otherwise it's too confusing for users looking at generated code, they will
203203
// write a small test method and think the inliner doesn't work correctly.
204204
val isGeneratedForwarder =
205-
BytecodeUtils.isSyntheticMethod(callsite.callsiteMethod) && backendUtils.looksLikeForwarderOrFactoryOrTrivial(callsite.callsiteMethod, allowPrivateCalls = true) > 0 ||
205+
BytecodeUtils.isSyntheticMethod(callsite.callsiteMethod) && backendUtils.looksLikeForwarderOrFactoryOrTrivial(callsite.callsiteMethod, callsite.callsiteClass.internalName, allowPrivateCalls = true) > 0 ||
206206
backendUtils.isMixinForwarder(callsite.callsiteMethod, callsite.callsiteClass) // seems mixin forwarders are not synthetic...
207207

208208
if (isGeneratedForwarder) None
@@ -248,7 +248,7 @@ abstract class InlinerHeuristics extends PerRunInit {
248248
val isTraitSuperAccessor = backendUtils.isTraitSuperAccessor(callee.callee, callee.calleeDeclarationClass)
249249
if (isTraitSuperAccessor) null
250250
else {
251-
val forwarderKind = backendUtils.looksLikeForwarderOrFactoryOrTrivial(callee.callee, allowPrivateCalls = false)
251+
val forwarderKind = backendUtils.looksLikeForwarderOrFactoryOrTrivial(callee.callee, callee.calleeDeclarationClass.internalName, allowPrivateCalls = false)
252252
if (forwarderKind < 0)
253253
null
254254
else if (BytecodeUtils.isSyntheticMethod(callee.callee) || backendUtils.isMixinForwarder(callee.callee, callee.calleeDeclarationClass))

src/compiler/scala/tools/nsc/transform/Constructors.scala

+1-12
Original file line numberDiff line numberDiff line change
@@ -738,18 +738,7 @@ abstract class Constructors extends Statics with Transform with TypingTransforme
738738
copyParam(accSetter, parameter(acc))
739739
}
740740

741-
// Return a pair consisting of (all statements up to and including superclass and trait constr calls, rest)
742-
def splitAtSuper(stats: List[Tree]) = {
743-
def isConstr(tree: Tree): Boolean = tree match {
744-
case Block(_, expr) => isConstr(expr) // scala/bug#6481 account for named argument blocks
745-
case _ => (tree.symbol ne null) && tree.symbol.isConstructor
746-
}
747-
val (pre, rest0) = stats span (!isConstr(_))
748-
val (supercalls, rest) = rest0 span (isConstr(_))
749-
(pre ::: supercalls, rest)
750-
}
751-
752-
val (uptoSuperStats, remainingConstrStats) = splitAtSuper(constructorStats)
741+
val (uptoSuperStats, remainingConstrStats) = treeInfo.splitAtSuper(constructorStats, classOnly = false)
753742

754743
/* TODO: XXX This condition (`isDelayedInitSubclass && remainingConstrStats.nonEmpty`) is not correct:
755744
* remainingConstrStats.nonEmpty excludes too much,

src/compiler/scala/tools/nsc/transform/Fields.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,9 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
674674
// we have to make the field mutable starting with classfile format 53
675675
// (it was never allowed, but the verifier enforces this now).
676676
fieldSel.setFlag(MUTABLE)
677-
fieldSel.owner.primaryConstructor.updateAttachment(ConstructorNeedsFence)
677+
val isInStaticModule = fieldSel.owner.isModuleClass && fieldSel.owner.sourceModule.isStaticModule
678+
if (!isInStaticModule) // the <clinit> lock is enough.
679+
fieldSel.owner.primaryConstructor.updateAttachment(ConstructorNeedsFence)
678680
}
679681

680682
afterOwnPhase { // the assign only type checks after our phase (assignment to val)

src/reflect/scala/reflect/internal/StdNames.scala

+1
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ trait StdNames {
353353
// Compiler internal names
354354
val ANYname: NameType = "<anyname>"
355355
val CONSTRUCTOR: NameType = "<init>"
356+
val CLASS_CONSTRUCTOR: NameType = "<clinit>"
356357
val DEFAULT_CASE: NameType = "defaultCase$"
357358
val EQEQ_LOCAL_VAR: NameType = "eqEqTemp$"
358359
val FAKE_LOCAL_THIS: NameType = "this$"

src/reflect/scala/reflect/internal/TreeInfo.scala

+12
Original file line numberDiff line numberDiff line change
@@ -1085,4 +1085,16 @@ trait MacroAnnotionTreeInfo { self: TreeInfo =>
10851085
SyntacticClassDef(mods, name, tparams, constrMods, vparamss.map(_.map(_.duplicate)), earlyDefs, parents, selfType, body)
10861086
}
10871087
}
1088+
1089+
// Return a pair consisting of (all statements up to and including superclass and trait constr calls, rest)
1090+
final def splitAtSuper(stats: List[Tree], classOnly: Boolean): (List[Tree], List[Tree]) = {
1091+
def isConstr(tree: Tree): Boolean = tree match {
1092+
case Block(_, expr) => isConstr(expr) // scala/bug#6481 account for named argument blocks
1093+
case _ => (tree.symbol ne null) && (if (classOnly) tree.symbol.isClassConstructor else tree.symbol.isConstructor)
1094+
}
1095+
val (pre, rest0) = stats span (!isConstr(_))
1096+
val (supercalls, rest) = rest0 span (isConstr(_))
1097+
(pre ::: supercalls, rest)
1098+
}
1099+
10881100
}

src/repl/scala/tools/nsc/interpreter/IMain.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ class IMain(val settings: Settings, parentClassLoaderOverride: Option[ClassLoade
607607
val classNameRegex = s"$lineRegex.*".r
608608
def isWrapperCode(x: StackTraceElement) = cond(x.getClassName) {
609609
case classNameRegex() =>
610-
x.getMethodName == nme.CONSTRUCTOR.decoded || x.getMethodName == printName
610+
x.getMethodName == nme.CONSTRUCTOR.decoded || x.getMethodName == "<clinit>" || x.getMethodName == printName
611611
}
612612
val stackTrace = unwrapped.stackTracePrefixString(!isWrapperCode(_))
613613

test/files/jvm/scala-concurrent-tck.scala

+21-21
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ trait TestBase {
3737
}
3838

3939

40-
trait FutureCallbacks extends TestBase {
40+
class FutureCallbacks extends TestBase {
4141
import ExecutionContext.Implicits._
4242

4343
def testOnSuccess(): Unit = once {
@@ -137,7 +137,7 @@ trait FutureCallbacks extends TestBase {
137137
}
138138

139139

140-
trait FutureCombinators extends TestBase {
140+
class FutureCombinators extends TestBase {
141141
import ExecutionContext.Implicits._
142142

143143
def testMapSuccess(): Unit = once {
@@ -604,7 +604,7 @@ def testTransformFailure(): Unit = once {
604604
}
605605

606606

607-
trait FutureProjections extends TestBase {
607+
class FutureProjections extends TestBase {
608608
import ExecutionContext.Implicits._
609609

610610
def testFailedFailureOnComplete(): Unit = once {
@@ -696,7 +696,7 @@ trait FutureProjections extends TestBase {
696696
}
697697

698698

699-
trait Blocking extends TestBase {
699+
class Blocking extends TestBase {
700700
import ExecutionContext.Implicits._
701701

702702
def testAwaitSuccess(): Unit = once {
@@ -728,7 +728,7 @@ trait Blocking extends TestBase {
728728
test("testFQCNForAwaitAPI")(testFQCNForAwaitAPI())
729729
}
730730

731-
trait BlockContexts extends TestBase {
731+
class BlockContexts extends TestBase {
732732
import ExecutionContext.Implicits._
733733
import scala.concurrent.{ Await, Awaitable, BlockContext }
734734

@@ -785,7 +785,7 @@ trait BlockContexts extends TestBase {
785785
test("testPopCustom")(testPopCustom())
786786
}
787787

788-
trait Promises extends TestBase {
788+
class Promises extends TestBase {
789789
import ExecutionContext.Implicits._
790790

791791
def testSuccess(): Unit = once {
@@ -820,7 +820,7 @@ trait Promises extends TestBase {
820820
}
821821

822822

823-
trait Exceptions extends TestBase {
823+
class Exceptions extends TestBase {
824824
import java.util.concurrent.{Executors, RejectedExecutionException}
825825
def interruptHandling(): Unit = {
826826
implicit val e = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(1))
@@ -846,7 +846,7 @@ trait Exceptions extends TestBase {
846846
test("rejectedExecutionException")(rejectedExecutionException())
847847
}
848848

849-
trait GlobalExecutionContext extends TestBase {
849+
class GlobalExecutionContext extends TestBase {
850850
import ExecutionContext.Implicits._
851851

852852
def testNameOfGlobalECThreads(): Unit = once {
@@ -859,7 +859,7 @@ trait GlobalExecutionContext extends TestBase {
859859
test("testNameOfGlobalECThreads")(testNameOfGlobalECThreads())
860860
}
861861

862-
trait CustomExecutionContext extends TestBase {
862+
class CustomExecutionContext extends TestBase {
863863
import scala.concurrent.{ ExecutionContext, Awaitable }
864864

865865
def defaultEC = ExecutionContext.global
@@ -1008,7 +1008,7 @@ trait CustomExecutionContext extends TestBase {
10081008
test("testCallbackChainCustomEC")(testCallbackChainCustomEC())
10091009
}
10101010

1011-
trait ExecutionContextPrepare extends TestBase {
1011+
class ExecutionContextPrepare extends TestBase {
10121012
val theLocal = new ThreadLocal[String] {
10131013
override protected def initialValue(): String = ""
10141014
}
@@ -1062,17 +1062,17 @@ trait ExecutionContextPrepare extends TestBase {
10621062
}
10631063

10641064
object Test
1065-
extends App
1066-
with FutureCallbacks
1067-
with FutureCombinators
1068-
with FutureProjections
1069-
with Promises
1070-
with BlockContexts
1071-
with Exceptions
1072-
with GlobalExecutionContext
1073-
with CustomExecutionContext
1074-
with ExecutionContextPrepare
1075-
{
1065+
extends App {
1066+
new FutureCallbacks
1067+
new FutureCombinators
1068+
new FutureProjections
1069+
new Promises
1070+
new BlockContexts
1071+
new Exceptions
1072+
new GlobalExecutionContext
1073+
new CustomExecutionContext
1074+
new ExecutionContextPrepare
1075+
10761076
System.exit(0)
10771077
}
10781078

test/files/run/module-static.check

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
c
2+
t
3+
o1

0 commit comments

Comments
 (0)