Skip to content

Commit 0ccdb15

Browse files
committed
Clean up and document some usages of flags in the backend
1 parent 1bed39a commit 0ccdb15

File tree

9 files changed

+154
-44
lines changed

9 files changed

+154
-44
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
977977
if (!isModuleInitialized &&
978978
jMethodName == INSTANCE_CONSTRUCTOR_NAME &&
979979
jname == INSTANCE_CONSTRUCTOR_NAME &&
980-
isStaticModule(siteSymbol)) {
980+
isStaticModuleClass(siteSymbol)) {
981981
isModuleInitialized = true
982982
mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)
983983
mnode.visitFieldInsn(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
9595

9696
claszSymbol = cd.symbol
9797
isCZParcelable = isAndroidParcelableClass(claszSymbol)
98-
isCZStaticModule = isStaticModule(claszSymbol)
98+
isCZStaticModule = isStaticModuleClass(claszSymbol)
9999
isCZRemote = isRemote(claszSymbol)
100100
thisName = internalName(claszSymbol)
101101

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

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -627,20 +627,52 @@ abstract class BCodeTypes extends BCodeIdiomatic {
627627
false
628628
}
629629

630-
/*
630+
/**
631631
* must-single-thread
632+
*
633+
* True for module classes of package level objects. The backend will generate a mirror class for
634+
* such objects.
632635
*/
633-
def isTopLevelModule(sym: Symbol): Boolean = {
634-
exitingPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass }
636+
def isTopLevelModuleClass(sym: Symbol): Boolean = exitingPickler {
637+
// phase travel to pickler required for isNestedClass (looks at owner)
638+
val r = sym.isModuleClass && !sym.isNestedClass
639+
// The mixin phase adds the `lateMODULE` flag to trait implementation classes. Since the flag
640+
// is late, it should not be visible here inside the time travel. We check this.
641+
if (r) assert(!sym.isImplClass, s"isModuleClass should be false for impl class $sym")
642+
r
635643
}
636644

637-
/*
645+
/**
638646
* must-single-thread
647+
*
648+
* True for module classes of modules that are top-level or owned only by objects. Module classes
649+
* for such objects will get a MODULE$ flag and a corresponding static initializer.
639650
*/
640-
def isStaticModule(sym: Symbol): Boolean = {
641-
sym.isModuleClass && !sym.isImplClass && !sym.isLifted
651+
def isStaticModuleClass(sym: Symbol): Boolean = {
652+
/* The implementation of this method is tricky because it is a source-level property. Various
653+
* phases changed the symbol's properties in the meantime.
654+
*
655+
* (1) Phase travel to to pickler is required to exclude implementation classes; they have the
656+
* lateMODULEs after mixin, so isModuleClass would be true.
657+
*
658+
* (2) We cannot use `sym.isStatic` because lambdalift modified (destructively) the owner. For
659+
* example, in
660+
* object T { def f { object U } }
661+
* the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here.
662+
* So we basically re-implement `sym.isStaticOwner`, but using the original owner chain.
663+
*/
664+
665+
def isOriginallyStaticOwner(sym: Symbol): Boolean = {
666+
sym.isPackageClass || sym.isModuleClass && isOriginallyStaticOwner(sym.originalOwner)
667+
}
668+
669+
exitingPickler { // (1)
670+
sym.isModuleClass &&
671+
isOriginallyStaticOwner(sym.originalOwner) // (2)
672+
}
642673
}
643674

675+
644676
// ---------------------------------------------------------------------
645677
// ---------------- InnerClasses attribute (JVMS 4.7.6) ----------------
646678
// ---------------------------------------------------------------------
@@ -743,7 +775,7 @@ abstract class BCodeTypes extends BCodeIdiomatic {
743775
null
744776
else {
745777
val outerName = innerSym.rawowner.javaBinaryName
746-
if (isTopLevelModule(innerSym.rawowner)) nme.stripModuleSuffix(outerName)
778+
if (isTopLevelModuleClass(innerSym.rawowner)) nme.stripModuleSuffix(outerName)
747779
else outerName
748780
}
749781
}
@@ -825,7 +857,7 @@ abstract class BCodeTypes extends BCodeIdiomatic {
825857
// new instances via outerClassInstance.new InnerModuleClass$().
826858
// TODO: do this early, mark the symbol private.
827859
val privateFlag =
828-
sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner))
860+
sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModuleClass(sym.owner))
829861

830862
// Symbols marked in source as `final` have the FINAL flag. (In the past, the flag was also
831863
// added to modules and module classes, not anymore since 296b706).
@@ -854,7 +886,7 @@ abstract class BCodeTypes extends BCodeIdiomatic {
854886
// emit ACC_FINAL.
855887

856888
val finalFlag = (
857-
(((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModule(sym))
889+
(((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModuleClass(sym))
858890
&& !sym.enclClass.isInterface
859891
&& !sym.isClassConstructor
860892
&& !sym.isMutable // lazy vals and vars both

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ abstract class GenBCode extends BCodeSyncAndTry {
165165

166166
// -------------- mirror class, if needed --------------
167167
val mirrorC =
168-
if (isStaticModule(claszSymbol) && isTopLevelModule(claszSymbol)) {
168+
if (isTopLevelModuleClass(claszSymbol)) {
169169
if (claszSymbol.companionClass == NoSymbol) {
170170
mirrorCodeGen.genMirrorClass(claszSymbol, cunit)
171171
} else {

src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,12 @@ abstract class SymbolLoaders {
240240
}
241241
}
242242

243+
private def phaseBeforeRefchecks: Phase = {
244+
var resPhase = phase
245+
while (resPhase.refChecked) resPhase = resPhase.prev
246+
resPhase
247+
}
248+
243249
/**
244250
* Load contents of a package
245251
*/
@@ -248,19 +254,24 @@ abstract class SymbolLoaders {
248254

249255
protected def doComplete(root: Symbol) {
250256
assert(root.isPackageClass, root)
251-
root.setInfo(new PackageClassInfoType(newScope, root))
252-
253-
if (!root.isRoot) {
254-
for (classRep <- classpath.classes) {
255-
initializeFromClassPath(root, classRep)
256-
}
257-
}
258-
if (!root.isEmptyPackageClass) {
259-
for (pkg <- classpath.packages) {
260-
enterPackage(root, pkg.name, new PackageLoader(pkg))
257+
// Time travel to a phase before refchecks avoids an initialization issue. `openPackageModule`
258+
// creates a module symbol and invokes invokes `companionModule` while the `infos` field is
259+
// still null. This calls `isModuleNotMethod`, which forces the `info` if run after refchecks.
260+
enteringPhase(phaseBeforeRefchecks) {
261+
root.setInfo(new PackageClassInfoType(newScope, root))
262+
263+
if (!root.isRoot) {
264+
for (classRep <- classpath.classes) {
265+
initializeFromClassPath(root, classRep)
266+
}
261267
}
268+
if (!root.isEmptyPackageClass) {
269+
for (pkg <- classpath.packages) {
270+
enterPackage(root, pkg.name, new PackageLoader(pkg))
271+
}
262272

263-
openPackageModule(root)
273+
openPackageModule(root)
274+
}
264275
}
265276
}
266277
}
@@ -290,7 +301,13 @@ abstract class SymbolLoaders {
290301

291302
protected def doComplete(root: Symbol) {
292303
val start = if (Statistics.canEnable) Statistics.startTimer(classReadNanos) else null
293-
classfileParser.parse(classfile, root)
304+
305+
// Running the classfile parser after refchecks can lead to "illegal class file dependency"
306+
// errors. More concretely, the classfile parser calls "sym.companionModule", which calls
307+
// "isModuleNotMethod" on the companion. After refchecks, this method forces the info, which
308+
// may run the classfile parser. This produces the error.
309+
enteringPhase(phaseBeforeRefchecks)(classfileParser.parse(classfile, root))
310+
294311
if (root.associatedFile eq NoAbstractFile) {
295312
root match {
296313
// In fact, the ModuleSymbol forwards its setter to the module class

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,17 @@ abstract class Flatten extends InfoTransform {
7676
for (sym <- decls) {
7777
if (sym.isTerm && !sym.isStaticModule) {
7878
decls1 enter sym
79-
if (sym.isModule)
79+
if (sym.isModule) {
80+
// Nested, non-static moduls are transformed to methods.
81+
assert(sym.isMethod, s"Non-static $sym should have the lateMETHOD flag from RefChecks")
82+
// Note that module classes are not entered into the 'decls' of the ClassInfoType
83+
// of the outer class, only the module symbols are. So the current loop does
84+
// not visit module classes. Therefore we set the LIFTED flag here for module
85+
// classes.
86+
// TODO: should we also set the LIFTED flag for static, nested module classes?
87+
// currently they don't get the flag, even though they are lifted to the package
8088
sym.moduleClass setFlag LIFTED
89+
}
8190
} else if (sym.isClass)
8291
liftSymbol(sym)
8392
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,15 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD
192192

193193
def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
194194
stats: List[Tree], retVal: Tree): Tree = {
195+
// Q: is there a reason to first set owner to `clazz` (by using clazz.newMethod), and then
196+
// changing it to lzyVal.owner very soon after? Could we just do lzyVal.owner.newMethod?
195197
val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE)
196198
defSym setInfo MethodType(List(), lzyVal.tpe.resultType)
197199
defSym.owner = lzyVal.owner
198200
debuglog(s"crete slow compute path $defSym with owner ${defSym.owner} for lazy val $lzyVal")
199201
if (bitmaps.contains(lzyVal))
200202
bitmaps(lzyVal).map(_.owner = defSym)
201-
val rhs: Tree = (gen.mkSynchronizedCheck(clazz, cond, syncBody, stats)).changeOwner(currentOwner -> defSym)
203+
val rhs: Tree = gen.mkSynchronizedCheck(clazz, cond, syncBody, stats).changeOwner(currentOwner -> defSym)
202204

203205
DefDef(defSym, addBitmapDefs(lzyVal, BLOCK(rhs, retVal)))
204206
}

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,29 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
5555
def newFreeTypeSymbol(name: TypeName, flags: Long = 0L, origin: String): FreeTypeSymbol =
5656
new FreeTypeSymbol(name, origin) initFlags flags
5757

58-
/** The original owner of a class. Used by the backend to generate
59-
* EnclosingMethod attributes.
58+
/**
59+
* This map stores the original owner the the first time the owner of a symbol is re-assigned.
60+
* The original owner of a symbol is needed in some places in the backend. Ideally, owners should
61+
* be versioned like the type history.
6062
*/
61-
val originalOwner = perRunCaches.newMap[Symbol, Symbol]()
63+
private val originalOwnerMap = perRunCaches.newMap[Symbol, Symbol]()
6264

6365
// TODO - don't allow the owner to be changed without checking invariants, at least
6466
// when under some flag. Define per-phase invariants for owner/owned relationships,
6567
// e.g. after flatten all classes are owned by package classes, there are lots and
6668
// lots of these to be declared (or more realistically, discovered.)
67-
protected def saveOriginalOwner(sym: Symbol) {
68-
if (originalOwner contains sym) ()
69-
else originalOwner(sym) = sym.rawowner
69+
protected def saveOriginalOwner(sym: Symbol): Unit = {
70+
// some synthetic symbols have NoSymbol as owner initially
71+
if (sym.owner != NoSymbol) {
72+
if (originalOwnerMap contains sym) ()
73+
else originalOwnerMap(sym) = sym.rawowner
74+
}
7075
}
76+
7177
protected def originalEnclosingMethod(sym: Symbol): Symbol = {
7278
if (sym.isMethod || sym == NoSymbol) sym
7379
else {
74-
val owner = originalOwner.getOrElse(sym, sym.rawowner)
80+
val owner = sym.originalOwner
7581
if (sym.isLocalDummy) owner.enclClass.primaryConstructor
7682
else originalEnclosingMethod(owner)
7783
}
@@ -757,8 +763,22 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
757763
* So "isModuleNotMethod" exists not for its achievement in
758764
* brevity, but to encapsulate the relevant condition.
759765
*/
760-
def isModuleNotMethod = isModule && !isMethod
761-
def isStaticModule = isModuleNotMethod && isStatic
766+
def isModuleNotMethod = {
767+
if (isModule) {
768+
if (phase.refChecked) this.info // force completion to make sure lateMETHOD is there.
769+
!isMethod
770+
} else false
771+
}
772+
773+
// After RefChecks, the `isStatic` check is mostly redundant: all non-static modules should
774+
// be methods (and vice versa). There's a corner case on the vice-versa with mixed-in module
775+
// symbols:
776+
// trait T { object A }
777+
// object O extends T
778+
// The module symbol A is cloned into T$impl (addInterfaces), and then cloned into O (mixin).
779+
// Since the original A is not static, it's turned into a method. The clone in O however is
780+
// static (owned by a module), but it's also a method.
781+
def isStaticModule = isModuleNotMethod && isStatic
762782

763783
final def isInitializedToDefault = !isType && hasAllFlags(DEFAULTINIT | ACCESSOR)
764784
final def isThisSym = isTerm && owner.thisSym == this
@@ -909,10 +929,31 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
909929
)
910930
final def isModuleVar = hasFlag(MODULEVAR)
911931

912-
/** Is this symbol static (i.e. with no outer instance)?
913-
* Q: When exactly is a sym marked as STATIC?
914-
* A: If it's a member of a toplevel object, or of an object contained in a toplevel object, or any number of levels deep.
915-
* http://groups.google.com/group/scala-internals/browse_thread/thread/d385bcd60b08faf6
932+
/**
933+
* Is this symbol static (i.e. with no outer instance)?
934+
* Q: When exactly is a sym marked as STATIC?
935+
* A: If it's a member of a toplevel object, or of an object contained in a toplevel object, or
936+
* any number of levels deep.
937+
* http://groups.google.com/group/scala-internals/browse_thread/thread/d385bcd60b08faf6
938+
*
939+
* TODO: should this only be invoked on class / module symbols? because there's also `isStaticMember`.
940+
*
941+
* Note: the result of `isStatic` changes over time.
942+
* - Lambdalift local definitions to the class level, the `owner` field is modified.
943+
* object T { def foo { object O } }
944+
* After lambdalift, the OModule.isStatic is true.
945+
*
946+
* - After flatten, nested classes are moved to the package level. Invoking `owner` on a
947+
* class returns a package class, for which `isStaticOwner` is true. For example,
948+
* class C { object O }
949+
* OModuleClass.isStatic is true after flatten. Using phase travel to get before flatten,
950+
* method `owner` returns the class C.
951+
*
952+
* Why not make a stable version of `isStatic`? Maybe some parts of the compiler depend on the
953+
* current implementation. For example
954+
* trait T { def foo = 1 }
955+
* The method `foo` in the implementation class T$impl will be `isStatic`, because trait
956+
* impl classes get the `lateMODULE` flag (T$impl.isStaticOwner is true).
916957
*/
917958
def isStatic = (this hasFlag STATIC) || owner.isStaticOwner
918959

@@ -1106,7 +1147,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
11061147
* - After lambdalift, all local method and class definitions (those not owned by a class
11071148
* or package class) change their owner to the enclosing class. This is done through
11081149
* a destructive "sym.owner = sym.owner.enclClass". The old owner is saved by
1109-
* saveOriginalOwner for the backend (needed to generate the EnclosingMethod attribute).
1150+
* saveOriginalOwner.
11101151
* - After flatten, all classes are owned by a PackageClass. This is done through a
11111152
* phase check (if after flatten) in the (overridden) method "def owner" in
11121153
* ModuleSymbol / ClassSymbol. The `rawowner` field is not modified.
@@ -1129,6 +1170,11 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
11291170
final def safeOwner: Symbol = if (this eq NoSymbol) NoSymbol else owner
11301171
final def assertOwner: Symbol = if (this eq NoSymbol) abort("no-symbol does not have an owner") else owner
11311172

1173+
/**
1174+
* The initial owner of this symbol.
1175+
*/
1176+
def originalOwner: Symbol = originalOwnerMap.getOrElse(this, rawowner)
1177+
11321178
// TODO - don't allow the owner to be changed without checking invariants, at least
11331179
// when under some flag. Define per-phase invariants for owner/owned relationships,
11341180
// e.g. after flatten all classes are owned by package classes, there are lots and
@@ -1142,7 +1188,6 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
11421188
}
11431189

11441190
def ownerChain: List[Symbol] = this :: owner.ownerChain
1145-
def originalOwnerChain: List[Symbol] = this :: originalOwner.getOrElse(this, rawowner).originalOwnerChain
11461191

11471192
// Non-classes skip self and return rest of owner chain; overridden in ClassSymbol.
11481193
def enclClassChain: List[Symbol] = owner.enclClassChain

src/reflect/scala/reflect/runtime/SymbolLoaders.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,15 @@ private[reflect] trait SymbolLoaders { self: SymbolTable =>
6565
class LazyPackageType extends LazyType with FlagAgnosticCompleter {
6666
override def complete(sym: Symbol) {
6767
assert(sym.isPackageClass)
68-
sym setInfo new ClassInfoType(List(), new PackageScope(sym), sym)
68+
// Time travel to a phase before refchecks avoids an initialization issue. `openPackageModule`
69+
// creates a module symbol and invokes invokes `companionModule` while the `infos` field is
70+
// still null. This calls `isModuleNotMethod`, which forces the `info` if run after refchecks.
71+
slowButSafeEnteringPhaseNotLaterThan(picklerPhase) {
72+
sym setInfo new ClassInfoType(List(), new PackageScope(sym), sym)
6973
// override def safeToString = pkgClass.toString
70-
openPackageModule(sym)
71-
markAllCompleted(sym)
74+
openPackageModule(sym)
75+
markAllCompleted(sym)
76+
}
7277
}
7378
}
7479

0 commit comments

Comments
 (0)