Skip to content

Commit 5f86b1d

Browse files
committed
Drive bitmap computation from field, not accessor
Since bitmaps are only needed if there is a field, and the field carries the transient annotation, don't go around via the accessor (we'd miss the annotation). Before we had a fields phase, this was tricky to do, but now it's pretty straightforward to operate on field symbols, as we synthesize the relevant ones, along with their bitmaps, during the fields phase instead of waiting until mixin. As an extra bonus, the "artifact" fields such as outer pointers etc don't exist yet, so we don't have to exclude them.
1 parent ff57fa8 commit 5f86b1d

File tree

5 files changed

+145
-152
lines changed

5 files changed

+145
-152
lines changed

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

Lines changed: 102 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
/* NSC -- new Scala compiler
2-
* Copyright 2005-2013 LAMP/EPFL and Lightbend, Inc
3-
*/
1+
// Copyright 2005-2017 LAMP/EPFL and Lightbend, Inc
42

53
package scala.tools.nsc
64
package transform
@@ -95,75 +93,77 @@ trait AccessorSynthesis extends Transform with ast.TreeDSL {
9593
}
9694

9795
case class BitmapInfo(symbol: Symbol, mask: Literal) {
98-
def storageClass: ClassSymbol = symbol.info.typeSymbol.asClass
96+
def select(on: This): Tree = Select(on, symbol)
97+
def applyToMask(on: This, op: Name): Tree = Apply(member(select(on), op), List(mask))
98+
def member(bitmapRef: Tree, name: Name): Tree = Select(bitmapRef, getMember(storageClass, name))
99+
def convert(bitmapRef: Tree): Tree = Apply(member(bitmapRef, newTermName("to" + storageClass.name)), Nil)
100+
101+
def isLong: Boolean = storageClass == LongClass
102+
def isBoolean: Boolean = storageClass == BooleanClass
103+
104+
lazy val storageClass: ClassSymbol = symbol.info.typeSymbol.asClass
99105
}
100106

101107

102108
// TODO: better way to communicate from info transform to tree transform?
103109
private[this] val _bitmapInfo = perRunCaches.newMap[Symbol, BitmapInfo]
104110
private[this] val _slowPathFor = perRunCaches.newMap[Symbol, Symbol]()
105111

106-
def checkedAccessorSymbolSynth(clz: Symbol) =
107-
if (settings.checkInit) new CheckInitAccessorSymbolSynth { val clazz = clz }
108-
else new CheckedAccessorSymbolSynth { val clazz = clz }
109-
110-
// base trait, with enough functionality for lazy vals -- CheckInitAccessorSymbolSynth adds logic for -Xcheckinit
111-
trait CheckedAccessorSymbolSynth {
112-
protected val clazz: Symbol
113-
114-
protected def defaultPos = clazz.pos.focus
115-
protected def isTrait = clazz.isTrait
116-
protected def hasTransientAnnot(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr
117-
118-
def needsBitmap(sym: Symbol): Boolean = !(isTrait || sym.isDeferred) && sym.isMethod && sym.isLazy && !sym.isSpecialized
119-
112+
def checkedAccessorSymbolSynth(clz: Symbol): CheckedAccessorSymbolSynth =
113+
new CheckedAccessorSymbolSynth(clz)
120114

121-
/** Examines the symbol and returns a name indicating what brand of
122-
* bitmap it requires. The possibilities are the BITMAP_* vals
123-
* defined in StdNames. If it needs no bitmap, nme.NO_NAME.
115+
// base trait, with enough functionality for generating bitmap symbols for lazy vals and -Xcheckinit fields
116+
class CheckedAccessorSymbolSynth(val clazz: Symbol) {
117+
/**
118+
* Note: fields of classes inheriting DelayedInit are not checked.
119+
* This is because they are neither initialized in the constructor
120+
* nor do they have a setter (not if they are vals anyway). The usual
121+
* logic for setting bitmaps does therefore not work for such fields.
122+
* That's why they are excluded.
124123
*
125-
* bitmaps for checkinit fields are not inherited
126124
*/
127-
protected def bitmapCategory(sym: Symbol): Name = {
128-
// ensure that nested objects are transformed TODO: still needed?
129-
sym.initialize
125+
private[this] val doCheckInit = settings.checkInit.value && !(clazz isSubClass DelayedInitClass)
130126

131-
import nme._
132-
133-
if (needsBitmap(sym) && sym.isLazy)
134-
if (hasTransientAnnot(sym)) BITMAP_TRANSIENT else BITMAP_NORMAL
135-
else NO_NAME
136-
}
137-
138-
139-
def bitmapFor(sym: Symbol): BitmapInfo = _bitmapInfo(sym)
140-
protected def hasBitmap(sym: Symbol): Boolean = _bitmapInfo isDefinedAt sym
127+
private[AccessorSynthesis] def bitmapFor(field: Symbol): BitmapInfo = _bitmapInfo(field)
128+
protected def bitmapOf(field: Symbol): Option[BitmapInfo] = _bitmapInfo.get(field)
141129

142130

143131
/** Fill the map from fields to bitmap infos.
132+
* This is called for all fields in each transformed class (by the fields info transformer),
133+
* after the fields inherited from traits have been added.
144134
*
145-
* Instead of field symbols, the map keeps their getter symbols. This makes code generation easier later.
135+
* bitmaps for checkinit fields are not inherited
146136
*/
147-
def computeBitmapInfos(decls: List[Symbol]): List[Symbol] = {
148-
def doCategory(fields: List[Symbol], category: Name) = {
149-
val nbFields = fields.length // we know it's > 0
137+
def computeBitmapInfos(fields: List[Symbol]): List[Symbol] = {
138+
def bitmapCategory(field: Symbol): Name = {
139+
import nme._
140+
141+
if (field.isLazy)
142+
if (field hasAnnotation TransientAttr) BITMAP_TRANSIENT else BITMAP_NORMAL
143+
else if (doCheckInit && !(field hasFlag DEFAULTINIT | PRESUPER))
144+
if (field hasAnnotation TransientAttr) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT
145+
else NO_NAME
146+
}
147+
148+
def allocateBitmaps(fieldsWithBitmaps: List[Symbol], category: Name) = {
149+
val nbFields = fieldsWithBitmaps.length // we know it's > 0
150150
val (bitmapClass, bitmapCapacity) =
151-
if (nbFields == 1) (BooleanClass, 1)
152-
else if (nbFields <= 8) (ByteClass, 8)
153-
else if (nbFields <= 32) (IntClass, 32)
154-
else (LongClass, 64)
151+
if (nbFields == 1) (BooleanClass, 1)
152+
else if (nbFields <= 8) (ByteClass, 8)
153+
else if (nbFields <= 32) (IntClass, 32)
154+
else (LongClass, 64)
155155

156156
// 0-based index of highest bit, divided by bits per bitmap
157157
// note that this is only ever > 0 when bitmapClass == LongClass
158158
val maxBitmapNumber = (nbFields - 1) / bitmapCapacity
159159

160160
// transient fields get their own category
161-
val isTransientCategory = fields.head hasAnnotation TransientAttr
161+
val isTransientCategory = nme.isTransientBitmap(category)
162162

163163
val bitmapSyms =
164164
(0 to maxBitmapNumber).toArray map { bitmapNumber =>
165165
val bitmapSym = (
166-
clazz.newVariable(nme.newBitmapName(category, bitmapNumber).toTermName, defaultPos)
166+
clazz.newVariable(nme.newBitmapName(category, bitmapNumber).toTermName, clazz.pos.focus)
167167
setInfo bitmapClass.tpe
168168
setFlag PrivateLocal | NEEDS_TREES
169169
)
@@ -175,7 +175,7 @@ trait AccessorSynthesis extends Transform with ast.TreeDSL {
175175
bitmapSym
176176
}
177177

178-
fields.zipWithIndex foreach { case (f, idx) =>
178+
fieldsWithBitmaps.zipWithIndex foreach { case (f, idx) =>
179179
val bitmapIdx = idx / bitmapCapacity
180180
val offsetInBitmap = idx % bitmapCapacity
181181
val mask =
@@ -188,60 +188,23 @@ trait AccessorSynthesis extends Transform with ast.TreeDSL {
188188
bitmapSyms
189189
}
190190

191-
decls groupBy bitmapCategory flatMap {
192-
case (category, fields) if category != nme.NO_NAME && fields.nonEmpty => doCategory(fields, category)
191+
fields groupBy bitmapCategory flatMap {
192+
case (category, fields) if category != nme.NO_NAME && fields.nonEmpty => allocateBitmaps(fields, category)
193193
case _ => Nil
194194
} toList
195195
}
196196

197197
def slowPathFor(lzyVal: Symbol): Symbol = _slowPathFor(lzyVal)
198198

199199
def newSlowPathSymbol(lzyVal: Symbol): Symbol = {
200-
val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else defaultPos // TODO: is the else branch ever taken?
200+
val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else clazz.pos.focus // TODO: is the else branch ever taken?
201201
val sym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfo MethodType(Nil, lzyVal.tpe.resultType)
202202
_slowPathFor(lzyVal) = sym
203203
sym
204204
}
205205

206206
}
207207

208-
trait CheckInitAccessorSymbolSynth extends CheckedAccessorSymbolSynth {
209-
/** Does this field require an initialized bit?
210-
* Note: fields of classes inheriting DelayedInit are not checked.
211-
* This is because they are neither initialized in the constructor
212-
* nor do they have a setter (not if they are vals anyway). The usual
213-
* logic for setting bitmaps does therefore not work for such fields.
214-
* That's why they are excluded.
215-
* Note: The `checkinit` option does not check if transient fields are initialized.
216-
*/
217-
protected def needsInitFlag(sym: Symbol): Boolean =
218-
sym.isGetter &&
219-
!( sym.isInitializedToDefault
220-
|| isConstantType(sym.info.finalResultType) // scala/bug#4742
221-
|| sym.hasFlag(PARAMACCESSOR | SPECIALIZED | LAZY)
222-
|| sym.accessed.hasFlag(PRESUPER)
223-
|| sym.isOuterAccessor
224-
|| (sym.owner isSubClass DelayedInitClass)
225-
|| (sym.accessed hasAnnotation TransientAttr))
226-
227-
/** Examines the symbol and returns a name indicating what brand of
228-
* bitmap it requires. The possibilities are the BITMAP_* vals
229-
* defined in StdNames. If it needs no bitmap, nme.NO_NAME.
230-
*
231-
* bitmaps for checkinit fields are not inherited
232-
*/
233-
override protected def bitmapCategory(sym: Symbol): Name = {
234-
import nme._
235-
236-
super.bitmapCategory(sym) match {
237-
case NO_NAME if needsInitFlag(sym) && !sym.isDeferred =>
238-
if (hasTransientAnnot(sym)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT
239-
case category => category
240-
}
241-
}
242-
243-
override def needsBitmap(sym: Symbol): Boolean = super.needsBitmap(sym) || !(isTrait || sym.isDeferred) && needsInitFlag(sym)
244-
}
245208

246209

247210
// synthesize trees based on info gathered during info transform
@@ -250,47 +213,34 @@ trait AccessorSynthesis extends Transform with ast.TreeDSL {
250213
// (they are persisted even between phases because the -Xcheckinit logic runs during constructors)
251214
// TODO: can we use attachments instead of _bitmapInfo and _slowPathFor?
252215
trait CheckedAccessorTreeSynthesis extends AccessorTreeSynthesis {
253-
254216
// note: we deal in getters here, not field symbols
255-
trait SynthCheckedAccessorsTreesInClass extends CheckedAccessorSymbolSynth {
217+
class SynthCheckedAccessorsTreesInClass(clazz: Symbol) extends CheckedAccessorSymbolSynth(clazz) {
256218
def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass
257219
def thisRef = gen.mkAttributedThis(clazz)
258220

221+
259222
/** Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the
260223
* precise comparison operator depending on the value of 'equalToZero'.
261224
*/
262-
def mkTest(field: Symbol, equalToZero: Boolean = true): Tree = {
263-
val bitmap = bitmapFor(field)
264-
val bitmapTree = thisRef DOT bitmap.symbol
265-
266-
if (bitmap.storageClass == BooleanClass) {
267-
if (equalToZero) NOT(bitmapTree) else bitmapTree
268-
} else {
269-
val lhs = bitmapTree GEN_&(bitmap.mask, bitmap.storageClass)
270-
if (equalToZero) lhs GEN_==(ZERO, bitmap.storageClass)
271-
else lhs GEN_!=(ZERO, bitmap.storageClass)
272-
}
273-
}
225+
def mkTest(bm: BitmapInfo, equalToZero: Boolean = true): Tree =
226+
if (bm.isBoolean)
227+
if (equalToZero) NOT(bm.select(thisRef)) else bm.select(thisRef)
228+
else
229+
Apply(bm.member(bm.applyToMask(thisRef, nme.AND), if (equalToZero) nme.EQ else nme.NE), List(ZERO))
274230

275231
/** Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */
276-
def mkSetFlag(valSym: Symbol): Tree = {
277-
val bitmap = bitmapFor(valSym)
278-
def x = thisRef DOT bitmap.symbol
279-
280-
Assign(x,
281-
if (bitmap.storageClass == BooleanClass) TRUE
232+
def mkSetFlag(bitmap: BitmapInfo): Tree =
233+
Assign(bitmap.select(thisRef),
234+
if (bitmap.isBoolean) TRUE
282235
else {
283-
val or = Apply(Select(x, getMember(bitmap.storageClass, nme.OR)), List(bitmap.mask))
284-
// NOTE: bitwise or (`|`) on two bytes yields and Int (TODO: why was this not a problem when this ran during mixins?)
285-
// TODO: need this to make it type check -- is there another way??
286-
if (bitmap.storageClass != LongClass) Apply(Select(or, newTermName("to" + bitmap.storageClass.name)), Nil)
287-
else or
288-
}
289-
)
290-
}
236+
val ored = bitmap.applyToMask(thisRef, nme.OR)
237+
// NOTE: Unless the bitmap is a Long, we must convert explicitly to avoid widening
238+
// For example, bitwise OR (`|`) on two bytes yields and Int
239+
if (bitmap.isLong) ored else bitmap.convert(ored)
240+
})
291241
}
292242

293-
class SynthLazyAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass {
243+
class SynthLazyAccessorsIn(clazz: Symbol) extends SynthCheckedAccessorsTreesInClass(clazz) {
294244
/**
295245
* The compute method (slow path) looks like:
296246
*
@@ -334,8 +284,9 @@ trait AccessorSynthesis extends Transform with ast.TreeDSL {
334284
val selectVar = if (isUnit) UNIT else Select(thisRef, lazyVar)
335285
val storeRes = if (isUnit) rhsAtSlowDef else Assign(selectVar, fields.castHack(rhsAtSlowDef, lazyVar.info))
336286

337-
def needsInit = mkTest(lazyAccessor)
338-
val doInit = Block(List(storeRes), mkSetFlag(lazyAccessor))
287+
val bitmap = bitmapFor(lazyVar)
288+
def needsInit = mkTest(bitmap)
289+
val doInit = Block(List(storeRes), mkSetFlag(bitmap))
339290
// the slow part of double-checked locking (TODO: is this the most efficient pattern? https://github.come/scala/scala-dev/issues/204)
340291
val slowPathRhs = Block(gen.mkSynchronized(thisRef)(If(needsInit, doInit, EmptyTree)) :: Nil, selectVar)
341292

@@ -349,50 +300,52 @@ trait AccessorSynthesis extends Transform with ast.TreeDSL {
349300
}
350301
}
351302

352-
class SynthInitCheckedAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass with CheckInitAccessorSymbolSynth {
303+
class SynthInitCheckedAccessorsIn(clazz: Symbol) extends SynthCheckedAccessorsTreesInClass(clazz) {
304+
305+
// Add statements to the body of a constructor to set the 'init' bit for each field initialized in the constructor
353306
private object addInitBitsTransformer extends Transformer {
354-
private def checkedGetter(lhs: Tree)(pos: Position) = {
355-
val getter = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter)
356-
if (hasBitmap(getter) && needsInitFlag(getter)) {
357-
debuglog("adding checked getter for: " + getter + " " + lhs.symbol.flagString)
358-
List(typedPos(pos)(mkSetFlag(getter)))
359-
}
360-
else Nil
361-
}
362307
override def transformStats(stats: List[Tree], exprOwner: Symbol) = {
363-
// !!! Ident(self) is never referenced, is it supposed to be confirming
364-
// that self is anything in particular?
365-
super.transformStats(
366-
stats flatMap {
367-
case stat@Assign(lhs@Select(This(_), _), rhs) => stat :: checkedGetter(lhs)(stat.pos.focus)
368-
// remove initialization for default values -- TODO is this case ever hit? constructors does not generate Assigns with EmptyTree for the rhs AFAICT
369-
case Apply(lhs@Select(Ident(self), _), EmptyTree.asList) if lhs.symbol.isSetter => Nil
370-
case stat => List(stat)
371-
},
372-
exprOwner
373-
)
308+
val checkedStats = stats flatMap {
309+
// Mark field as initialized after an assignment
310+
case stat@Assign(lhs@Select(This(_), _), _) =>
311+
stat :: bitmapOf(lhs.symbol).toList.map(bitmap => typedPos(stat.pos.focus)(mkSetFlag(bitmap)))
312+
313+
// remove initialization for default values
314+
// TODO is this case ever hit? constructors does not generate Assigns with EmptyTree for the rhs AFAICT
315+
// !!! Ident(self) is never referenced, is it supposed to be confirming
316+
// that self is anything in particular?
317+
case Apply(lhs@Select(Ident(self), _), EmptyTree.asList) if lhs.symbol.isSetter => Nil
318+
case stat => List(stat)
319+
}
320+
321+
super.transformStats(checkedStats, exprOwner)
374322
}
375323
}
376324

325+
private[this] val isTrait = clazz.isTrait
326+
// We only act on concrete methods, and traits only need to have their constructor rewritten
327+
def needsWrapping(dd: DefDef) =
328+
dd.rhs != EmptyTree && (!isTrait || dd.symbol.isConstructor)
329+
377330
/** Make getters check the initialized bit, and the class constructor & setters are changed to set the initialized bits. */
378-
def wrapRhsWithInitChecks(sym: Symbol)(rhs: Tree): Tree = {
379-
// Add statements to the body of a constructor to set the 'init' bit for each field initialized in the constructor
331+
def wrapRhsWithInitChecks(sym: Symbol)(rhs: Tree): Tree =
380332
if (sym.isConstructor) addInitBitsTransformer transform rhs
381-
else if (isTrait || rhs == EmptyTree) rhs
382-
else if (needsInitFlag(sym)) // getter
383-
mkCheckedAccessorRhs(if (isUnitGetter(sym)) UNIT else rhs, rhs.pos, sym)
384-
else if (sym.isSetter) {
385-
val getter = sym.getterIn(clazz)
386-
if (needsInitFlag(getter)) Block(List(rhs, typedPos(rhs.pos.focus)(mkSetFlag(getter))), UNIT)
387-
else rhs
333+
else if ((sym hasFlag ACCESSOR) && !(sym hasFlag LAZY)) {
334+
val field = clazz.info.decl(sym.localName)
335+
if (field == NoSymbol) rhs
336+
else bitmapOf(field) match {
337+
case Some(bitmap) =>
338+
if (sym.isGetter) mkCheckedAccessorRhs(if (isUnitGetter(sym)) UNIT else rhs, rhs.pos, bitmap) // TODO: why not always use rhs?
339+
else Block(List(rhs, typedPos(rhs.pos.focus)(mkSetFlag(bitmap))), UNIT)
340+
case _ => rhs
341+
}
388342
}
389343
else rhs
390-
}
391344

392-
private def mkCheckedAccessorRhs(retVal: Tree, pos: Position, getter: Symbol): Tree = {
345+
private def mkCheckedAccessorRhs(retVal: Tree, pos: Position, bitmap: BitmapInfo): Tree = {
393346
val msg = s"Uninitialized field: ${clazz.sourceFile}: ${pos.line}"
394347
val result =
395-
IF(mkTest(getter, equalToZero = false)).
348+
IF(mkTest(bitmap, equalToZero = false)).
396349
THEN(retVal).
397350
ELSE(Throw(NewFromConstructor(UninitializedFieldConstructor, LIT(msg))))
398351

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -782,8 +782,8 @@ abstract class Constructors extends Statics with Transform with TypingTransforme
782782
if (settings.checkInit) {
783783
val addChecks = new SynthInitCheckedAccessorsIn(currentOwner)
784784
prunedStats mapConserve {
785-
case dd: DefDef => deriveDefDef(dd)(addChecks.wrapRhsWithInitChecks(dd.symbol))
786-
case stat => stat
785+
case dd: DefDef if addChecks.needsWrapping(dd) => deriveDefDef(dd)(addChecks.wrapRhsWithInitChecks(dd.symbol))
786+
case stat => stat
787787
}
788788
} else prunedStats
789789

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor
505505
mixedInAccessorAndFields foreach enterAll
506506

507507
// both oldDecls and mixedInAccessorAndFields (a list of lists) contribute
508-
val bitmapSyms = accessorSymbolSynth.computeBitmapInfos(newDecls.toList)
508+
val bitmapSyms = accessorSymbolSynth.computeBitmapInfos(newDecls.filter(sym => sym.isValue && !sym.isMethod).toList)
509509

510510
bitmapSyms foreach enter
511511

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,7 @@ trait StdNames {
10401040
}
10411041

10421042
def newBitmapName(bitmapPrefix: Name, n: Int) = bitmapPrefix append ("" + n)
1043+
def isTransientBitmap(name: Name) = name == nme.BITMAP_TRANSIENT || name == nme.BITMAP_CHECKINIT_TRANSIENT
10431044

10441045
val BITMAP_NORMAL: NameType = BITMAP_PREFIX + "" // initialization bitmap for public/protected lazy vals
10451046
val BITMAP_TRANSIENT: NameType = BITMAP_PREFIX + "trans$" // initialization bitmap for transient lazy vals

0 commit comments

Comments
 (0)