-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Make object fields static, and move post-super init to <clinit> #7270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,7 +70,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { | |
var claszSymbol: Symbol = null | ||
var isCZParcelable = false | ||
var isCZStaticModule = false | ||
var initModuleInClinit = false | ||
|
||
/* ---------------- idiomatic way to ask questions to typer ---------------- */ | ||
|
||
|
@@ -102,26 +101,51 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { | |
|
||
/* ---------------- helper utils for generating classes and fields ---------------- */ | ||
|
||
def genPlainClass(cd: ClassDef): Unit = { | ||
def genPlainClass(cd0: ClassDef): Unit = { | ||
assert(cnode == null, "GenBCode detected nested methods.") | ||
|
||
claszSymbol = cd.symbol | ||
claszSymbol = cd0.symbol | ||
isCZParcelable = isAndroidParcelableClass(claszSymbol) | ||
isCZStaticModule = isStaticModuleClass(claszSymbol) | ||
thisBType = classBTypeFromSymbol(claszSymbol) | ||
initModuleInClinit = isCZStaticModule && canAssignModuleInClinit(cd, claszSymbol) | ||
|
||
cnode = new ClassNode1() | ||
|
||
initJClass(cnode) | ||
val cd = if (isCZStaticModule) { | ||
// Move statements from the primary constructor following the superclass constructor call to | ||
// a newly synthesised tree representing the "<clinit>", which also assigns the MODULE$ field. | ||
// Because the assigments to both the module instance fields, and the fields of the module itself | ||
// are in the <clinit>, these fields can be static + final. | ||
|
||
val hasStaticCtor = methodSymbols(cd) exists (_.isStaticConstructor) | ||
if (!hasStaticCtor) { | ||
// but needs one ... | ||
if (isCZStaticModule || isCZParcelable) { | ||
fabricateStaticInit() | ||
// TODO should we do this transformation earlier, say in Constructors? Or would that just cause | ||
// pain for scala-{js, native}? | ||
|
||
for (f <- fieldSymbols(claszSymbol)) { | ||
f.setFlag(Flags.STATIC) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a little bit funny that this is enough to make code gen emit |
||
} | ||
} | ||
val constructorDefDef = treeInfo.firstConstructor(cd0.impl.body).asInstanceOf[DefDef] | ||
val (uptoSuperStats, remainingConstrStats) = treeInfo.splitAtSuper(constructorDefDef.rhs.asInstanceOf[Block].stats, classOnly = true) | ||
val clInitSymbol = claszSymbol.newMethod(nme.CLASS_CONSTRUCTOR, claszSymbol.pos, Flags.STATIC).setInfo(NullaryMethodType(definitions.UnitTpe)) | ||
|
||
// We don't need to enter this field into the decls of claszSymbol.info as this is added manually to the generated class | ||
// in addModuleInstanceField. TODO: try adding it to the decls and making the usual field generation do the right thing. | ||
val moduleField = claszSymbol.newValue(nme.MODULE_INSTANCE_FIELD, claszSymbol.pos, Flags.STATIC | Flags.PRIVATE).setInfo(claszSymbol.tpeHK) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's maybe a little confusing to create the field symbol but not enter it in the decls and rely on (Class fields are generated based on |
||
|
||
val callConstructor = NewFromConstructor(claszSymbol.primaryConstructor).setType(claszSymbol.tpeHK) | ||
val assignModuleField = Assign(global.gen.mkAttributedRef(moduleField).setType(claszSymbol.tpeHK), callConstructor).setType(definitions.UnitTpe) | ||
val remainingConstrStatsSubst = remainingConstrStats.map(_.substituteThis(claszSymbol, global.gen.mkAttributedRef(claszSymbol.sourceModule)).changeOwner(claszSymbol.primaryConstructor -> clInitSymbol)) | ||
val clinit = DefDef(clInitSymbol, Block(assignModuleField :: remainingConstrStatsSubst, Literal(Constant(())).setType(definitions.UnitTpe)).setType(definitions.UnitTpe)) | ||
deriveClassDef(cd0)(tmpl => deriveTemplate(tmpl)(body => | ||
clinit :: body.map { | ||
case `constructorDefDef` => copyDefDef(constructorDefDef)(rhs = Block(uptoSuperStats, constructorDefDef.rhs.asInstanceOf[Block].expr)) | ||
case tree => tree | ||
} | ||
)) | ||
} else cd0 | ||
|
||
val hasStaticCtor = methodSymbols(cd) exists (_.isStaticConstructor) | ||
if (!hasStaticCtor && isCZParcelable) fabricateStaticInitAndroid() | ||
|
||
val optSerial: Option[Long] = serialVUID(claszSymbol) | ||
/* serialVersionUID can't be put on interfaces (it's a private field). | ||
|
@@ -204,17 +228,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { | |
*/ | ||
private def addModuleInstanceField(): Unit = { | ||
// TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED | ||
// scala/scala-dev#194: | ||
// This can't be FINAL on JVM 1.9+ because we assign it from within the | ||
// instance constructor, not from <clinit> directly. Assignment from <clinit>, | ||
// after the constructor has completely finished, seems like the principled | ||
// thing to do, but it would change behaviour when "benign" cyclic references | ||
// between modules exist. | ||
// | ||
// We special case modules with parents that we know don't (and won't ever) refer to | ||
// the module during their construction. These can use a final field, and defer the assigment | ||
// to <clinit>. | ||
val mods = if (initModuleInClinit) GenBCode.PublicStaticFinal else GenBCode.PublicStatic | ||
val mods = GenBCode.PublicStaticFinal | ||
val fv = | ||
cnode.visitField(mods, | ||
strMODULE_INSTANCE_FIELD, | ||
|
@@ -232,7 +246,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { | |
/* | ||
* must-single-thread | ||
*/ | ||
private def fabricateStaticInit(): Unit = { | ||
private def fabricateStaticInitAndroid(): Unit = { | ||
|
||
val clinit: asm.MethodVisitor = cnode.visitMethod( | ||
GenBCode.PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED | ||
|
@@ -243,20 +257,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { | |
) | ||
clinit.visitCode() | ||
|
||
/* "legacy static initialization" */ | ||
if (isCZStaticModule) { | ||
clinit.visitTypeInsn(asm.Opcodes.NEW, thisBType.internalName) | ||
if (initModuleInClinit) clinit.visitInsn(asm.Opcodes.DUP) | ||
|
||
clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, | ||
thisBType.internalName, INSTANCE_CONSTRUCTOR_NAME, "()V", false) | ||
if (initModuleInClinit) { | ||
assignModuleInstanceField(clinit) | ||
} | ||
} | ||
if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisBType.internalName) } | ||
clinit.visitInsn(asm.Opcodes.RETURN) | ||
|
||
clinit.visitInsn(asm.Opcodes.RETURN) | ||
clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments | ||
clinit.visitEnd() | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1085,4 +1085,16 @@ trait MacroAnnotionTreeInfo { self: TreeInfo => | |
SyntacticClassDef(mods, name, tparams, constrMods, vparamss.map(_.map(_.duplicate)), earlyDefs, parents, selfType, body) | ||
} | ||
} | ||
|
||
// Return a pair consisting of (all statements up to and including superclass and trait constr calls, rest) | ||
final def splitAtSuper(stats: List[Tree], classOnly: Boolean): (List[Tree], List[Tree]) = { | ||
def isConstr(tree: Tree): Boolean = tree match { | ||
case Block(_, expr) => isConstr(expr) // scala/bug#6481 account for named argument blocks | ||
case _ => (tree.symbol ne null) && (if (classOnly) tree.symbol.isClassConstructor else tree.symbol.isConstructor) | ||
} | ||
val (pre, rest0) = stats span (!isConstr(_)) | ||
val (supercalls, rest) = rest0 span (isConstr(_)) | ||
(pre ::: supercalls, rest) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like a lot of iteration and a lot of allocation. Would this not be possible to solve with a custom Ordering + a sort? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it could be done with a bit less garbage, but this isn't a hotspot based on profiles I've collected. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's your call :) I always err on the side of efficiency :) |
||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,7 +37,7 @@ trait TestBase { | |
} | ||
|
||
|
||
trait FutureCallbacks extends TestBase { | ||
class FutureCallbacks extends TestBase { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @retronym Out of curiosity, why did these need to change? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As written, the tests ran during the After this PR, pattern leads to a deadlock: the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @retronym That makes me feel skeptical about this encoding. It would be interesting to see the community build running on this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it's unfortunate. But code that publishes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @retronym Let's see what community-build says :) |
||
import ExecutionContext.Implicits._ | ||
|
||
def testOnSuccess(): Unit = once { | ||
|
@@ -137,7 +137,7 @@ trait FutureCallbacks extends TestBase { | |
} | ||
|
||
|
||
trait FutureCombinators extends TestBase { | ||
class FutureCombinators extends TestBase { | ||
import ExecutionContext.Implicits._ | ||
|
||
def testMapSuccess(): Unit = once { | ||
|
@@ -604,7 +604,7 @@ def testTransformFailure(): Unit = once { | |
} | ||
|
||
|
||
trait FutureProjections extends TestBase { | ||
class FutureProjections extends TestBase { | ||
import ExecutionContext.Implicits._ | ||
|
||
def testFailedFailureOnComplete(): Unit = once { | ||
|
@@ -696,7 +696,7 @@ trait FutureProjections extends TestBase { | |
} | ||
|
||
|
||
trait Blocking extends TestBase { | ||
class Blocking extends TestBase { | ||
import ExecutionContext.Implicits._ | ||
|
||
def testAwaitSuccess(): Unit = once { | ||
|
@@ -728,7 +728,7 @@ trait Blocking extends TestBase { | |
test("testFQCNForAwaitAPI")(testFQCNForAwaitAPI()) | ||
} | ||
|
||
trait BlockContexts extends TestBase { | ||
class BlockContexts extends TestBase { | ||
import ExecutionContext.Implicits._ | ||
import scala.concurrent.{ Await, Awaitable, BlockContext } | ||
|
||
|
@@ -785,7 +785,7 @@ trait BlockContexts extends TestBase { | |
test("testPopCustom")(testPopCustom()) | ||
} | ||
|
||
trait Promises extends TestBase { | ||
class Promises extends TestBase { | ||
import ExecutionContext.Implicits._ | ||
|
||
def testSuccess(): Unit = once { | ||
|
@@ -820,7 +820,7 @@ trait Promises extends TestBase { | |
} | ||
|
||
|
||
trait Exceptions extends TestBase { | ||
class Exceptions extends TestBase { | ||
import java.util.concurrent.{Executors, RejectedExecutionException} | ||
def interruptHandling(): Unit = { | ||
implicit val e = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(1)) | ||
|
@@ -846,7 +846,7 @@ trait Exceptions extends TestBase { | |
test("rejectedExecutionException")(rejectedExecutionException()) | ||
} | ||
|
||
trait GlobalExecutionContext extends TestBase { | ||
class GlobalExecutionContext extends TestBase { | ||
import ExecutionContext.Implicits._ | ||
|
||
def testNameOfGlobalECThreads(): Unit = once { | ||
|
@@ -859,7 +859,7 @@ trait GlobalExecutionContext extends TestBase { | |
test("testNameOfGlobalECThreads")(testNameOfGlobalECThreads()) | ||
} | ||
|
||
trait CustomExecutionContext extends TestBase { | ||
class CustomExecutionContext extends TestBase { | ||
import scala.concurrent.{ ExecutionContext, Awaitable } | ||
|
||
def defaultEC = ExecutionContext.global | ||
|
@@ -1008,7 +1008,7 @@ trait CustomExecutionContext extends TestBase { | |
test("testCallbackChainCustomEC")(testCallbackChainCustomEC()) | ||
} | ||
|
||
trait ExecutionContextPrepare extends TestBase { | ||
class ExecutionContextPrepare extends TestBase { | ||
val theLocal = new ThreadLocal[String] { | ||
override protected def initialValue(): String = "" | ||
} | ||
|
@@ -1062,17 +1062,17 @@ trait ExecutionContextPrepare extends TestBase { | |
} | ||
|
||
object Test | ||
extends App | ||
with FutureCallbacks | ||
with FutureCombinators | ||
with FutureProjections | ||
with Promises | ||
with BlockContexts | ||
with Exceptions | ||
with GlobalExecutionContext | ||
with CustomExecutionContext | ||
with ExecutionContextPrepare | ||
{ | ||
extends App { | ||
new FutureCallbacks | ||
new FutureCombinators | ||
new FutureProjections | ||
new Promises | ||
new BlockContexts | ||
new Exceptions | ||
new GlobalExecutionContext | ||
new CustomExecutionContext | ||
new ExecutionContextPrepare | ||
|
||
System.exit(0) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
c | ||
t | ||
o1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sjrd Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't made my mind about it yet. I'll think about it.
Can my input wait until next week? I'm currently at the Scala Symposium.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing.