Skip to content

Commit ec23c65

Browse files
committed
Lazy entering of names with internal $'s in package scopes
Names with internal $'s are entered in package scopes only if - we look for a name with internal $'s. - we want to know all the members of a package scope This optimization seems to be fairly effective. The typical range of package scopes that need $-names is between 0 and 20%. The optimization seems to improve execution time of all unit tests by about 3%. Also. drop the inheritance from Iterable to Scope. The reason is that we now need a context parameter for toList and other Iterable operations which makes them impossible to fit into the Iterable framework.
1 parent 94787c8 commit ec23c65

13 files changed

+113
-66
lines changed

compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
794794

795795
def memberInfo(s: Symbol): Type = tp.memberInfo(s)
796796

797-
def decls: List[Symbol] = tp.decls.map(_.symbol).toList
797+
def decls: List[Symbol] = tp.decls.toList
798798

799799
def members: List[Symbol] =
800800
tp.memberDenots(takeAllFilter, (name, buf) => buf ++= tp.member(name).alternatives).map(_.symbol).toList

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ object Contexts {
294294

295295
/** Is this a context that introduces a non-empty scope? */
296296
def isNonEmptyScopeContext: Boolean =
297-
(this.scope ne outer.scope) && this.scope.nonEmpty
297+
(this.scope ne outer.scope) && !this.scope.isEmpty
298298

299299
/** Leave message in diagnostics buffer if it exists */
300300
def diagnose(str: => String) =

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

+37-22
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ object Scopes {
6060
* or to delete them. These methods are provided by subclass
6161
* MutableScope.
6262
*/
63-
abstract class Scope extends DotClass with printing.Showable with Iterable[Symbol] {
63+
abstract class Scope extends DotClass with printing.Showable {
6464

6565
/** The last scope-entry from which all others are reachable via `prev` */
6666
private[dotc] def lastEntry: ScopeEntry
@@ -76,18 +76,37 @@ object Scopes {
7676
/** The symbols in this scope in the order they were entered;
7777
* inherited from outer ones first.
7878
*/
79-
def toList: List[Symbol]
79+
def toList(implicit ctx: Context): List[Symbol]
8080

8181
/** Return all symbols as an iterator in the order they were entered in this scope.
8282
*/
83-
def iterator: Iterator[Symbol] = toList.iterator
83+
def iterator(implicit ctx: Context): Iterator[Symbol] = toList.iterator
84+
85+
/** Is the scope empty? */
86+
def isEmpty: Boolean = lastEntry eq null
87+
88+
def foreach[U](p: Symbol => U)(implicit ctx: Context): Unit = toList foreach p
89+
90+
def filter(p: Symbol => Boolean)(implicit ctx: Context): List[Symbol] = {
91+
ensureComplete()
92+
var syms: List[Symbol] = Nil
93+
var e = lastEntry
94+
while ((e ne null) && e.owner == this) {
95+
val sym = e.sym
96+
if (p(sym)) syms = sym :: syms
97+
e = e.prev
98+
}
99+
syms
100+
}
101+
102+
def find(p: Symbol => Boolean)(implicit ctx: Context): Symbol = filter(p) match {
103+
case sym :: _ => sym
104+
case _ => NoSymbol
105+
}
84106

85107
/** Returns a new mutable scope with the same content as this one. */
86108
def cloneScope(implicit ctx: Context): MutableScope
87109

88-
/** Is the scope empty? */
89-
override def isEmpty: Boolean = lastEntry eq null
90-
91110
/** Lookup a symbol entry matching given name. */
92111
def lookupEntry(name: Name)(implicit ctx: Context): ScopeEntry
93112

@@ -144,6 +163,12 @@ object Scopes {
144163
final def toText(printer: Printer): Text = printer.toText(this)
145164

146165
def checkConsistent()(implicit ctx: Context) = ()
166+
167+
/** Ensure that all elements of this scope have been entered.
168+
* Overridden by SymbolLoaders.PackageLoader#PackageScope, where it
169+
* makes sure that all names with `$`'s have been added.
170+
*/
171+
protected def ensureComplete()(implicit ctx: Context): Unit = ()
147172
}
148173

149174
/** A subclass of Scope that defines methods for entering and
@@ -341,8 +366,9 @@ object Scopes {
341366
/** Returns all symbols as a list in the order they were entered in this scope.
342367
* Does _not_ include the elements of inherited scopes.
343368
*/
344-
override final def toList: List[Symbol] = {
369+
override final def toList(implicit ctx: Context): List[Symbol] = {
345370
if (elemsCache eq null) {
371+
ensureComplete()
346372
elemsCache = Nil
347373
var e = lastEntry
348374
while ((e ne null) && e.owner == this) {
@@ -354,6 +380,7 @@ object Scopes {
354380
}
355381

356382
override def implicitDecls(implicit ctx: Context): List[TermRef] = {
383+
ensureComplete()
357384
var irefs = new mutable.ListBuffer[TermRef]
358385
var e = lastEntry
359386
while (e ne null) {
@@ -368,25 +395,13 @@ object Scopes {
368395

369396
/** Vanilla scope - symbols are stored in declaration order.
370397
*/
371-
final def sorted: List[Symbol] = toList
372-
373-
override def foreach[U](p: Symbol => U): Unit = toList foreach p
374-
375-
override def filter(p: Symbol => Boolean): List[Symbol] = {
376-
var syms: List[Symbol] = Nil
377-
var e = lastEntry
378-
while ((e ne null) && e.owner == this) {
379-
val sym = e.sym
380-
if (p(sym)) syms = sym :: syms
381-
e = e.prev
382-
}
383-
syms
384-
}
398+
final def sorted(implicit ctx: Context): List[Symbol] = toList
385399

386400
override def openForMutations: MutableScope = this
387401

388402
/** Check that all symbols in this scope are in their correct hashtable buckets. */
389403
override def checkConsistent()(implicit ctx: Context) = {
404+
ensureComplete()
390405
var e = lastEntry
391406
while (e != null) {
392407
var e1 = lookupEntry(e.name)
@@ -425,7 +440,7 @@ object Scopes {
425440
override private[dotc] def lastEntry = null
426441
override def size = 0
427442
override def nestingLevel = 0
428-
override def toList = Nil
443+
override def toList(implicit ctx: Context) = Nil
429444
override def cloneScope(implicit ctx: Context): MutableScope = unsupported("cloneScope")
430445
override def lookupEntry(name: Name)(implicit ctx: Context): ScopeEntry = null
431446
override def lookupNextEntry(entry: ScopeEntry)(implicit ctx: Context): ScopeEntry = null

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

+11-10
Original file line numberDiff line numberDiff line change
@@ -930,14 +930,15 @@ object SymDenotations {
930930
* and which is also defined in the same scope and compilation unit.
931931
* NoSymbol if this class does not exist.
932932
*/
933-
final def companionClass(implicit ctx: Context): Symbol = {
934-
val companionMethod = info.decls.denotsNamed(nme.COMPANION_CLASS_METHOD, selectPrivate).first
935-
936-
if (companionMethod.exists)
937-
companionMethod.info.resultType.classSymbol
938-
else
939-
NoSymbol
940-
}
933+
final def companionClass(implicit ctx: Context): Symbol =
934+
if (is(Package)) NoSymbol
935+
else {
936+
val companionMethod = info.decls.denotsNamed(nme.COMPANION_CLASS_METHOD, selectPrivate).first
937+
if (companionMethod.exists)
938+
companionMethod.info.resultType.classSymbol
939+
else
940+
NoSymbol
941+
}
941942

942943
final def scalacLinkedClass(implicit ctx: Context): Symbol =
943944
if (this is ModuleClass) companionNamed(effectiveName.toTypeName)
@@ -1777,8 +1778,8 @@ object SymDenotations {
17771778
def constrNamed(cname: TermName) = info.decls.denotsNamed(cname).last.symbol
17781779
// denotsNamed returns Symbols in reverse order of occurrence
17791780
if (this.is(ImplClass)) constrNamed(nme.TRAIT_CONSTRUCTOR) // ignore normal constructor
1780-
else
1781-
constrNamed(nme.CONSTRUCTOR).orElse(constrNamed(nme.TRAIT_CONSTRUCTOR))
1781+
else if (this.is(Package)) NoSymbol
1782+
else constrNamed(nme.CONSTRUCTOR).orElse(constrNamed(nme.TRAIT_CONSTRUCTOR))
17821783
}
17831784

17841785
/** The parameter accessors of this class. Term and type accessors,

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

+51-17
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Contexts._, Symbols._, Flags._, SymDenotations._, Types._, Scopes._, util
1414
import StdNames._, NameOps._
1515
import Decorators.{PreNamedString, StringInterpolators}
1616
import classfile.ClassfileParser
17+
import util.Stats
1718
import scala.util.control.NonFatal
1819

1920
object SymbolLoaders {
@@ -148,46 +149,79 @@ class SymbolLoaders {
148149
override def sourceModule(implicit ctx: Context) = _sourceModule
149150
def description = "package loader " + classpath.name
150151

152+
private var enterFlatClasses: Option[Context => Unit] = None
153+
154+
Stats.record("package scopes")
155+
151156
/** The scope of a package. This is different from a normal scope
152-
* in three aspects:
153-
*
154-
* 1. Names of scope entries are kept in mangled form.
155-
* 2. Some function types in the `scala` package are synthesized.
157+
* in three aspects:
158+
*
159+
* 1. Names of scope entries are kept in mangled form.
160+
* 2. Some function types in the `scala` package are synthesized.
156161
*/
157162
final class PackageScope extends MutableScope {
158163
override def newScopeEntry(name: Name, sym: Symbol)(implicit ctx: Context): ScopeEntry =
159164
super.newScopeEntry(name.mangled, sym)
160165

161166
override def lookupEntry(name: Name)(implicit ctx: Context): ScopeEntry = {
162-
val e = super.lookupEntry(name.mangled)
163-
if (e == null &&
164-
_sourceModule.name == nme.scala_ && _sourceModule == defn.ScalaPackageVal &&
165-
name.isTypeName && name.isSyntheticFunction)
167+
val mangled = name.mangled
168+
val e = super.lookupEntry(mangled)
169+
if (e != null) e
170+
else if (_sourceModule.initialDenot.name == nme.scala_ && _sourceModule == defn.ScalaPackageVal &&
171+
name.isTypeName && name.isSyntheticFunction)
166172
newScopeEntry(defn.newFunctionNTrait(name.asTypeName))
173+
else if (isFlatName(mangled.toSimpleName) && enterFlatClasses.isDefined) {
174+
Stats.record("package scopes with flatnames entered")
175+
enterFlatClasses.get(ctx)
176+
lookupEntry(name)
177+
}
167178
else e
168179
}
169180

181+
override def ensureComplete()(implicit ctx: Context) =
182+
for (enter <- enterFlatClasses) enter(ctx)
183+
170184
override def newScopeLikeThis() = new PackageScope
171185
}
172186

173187
private[core] val currentDecls: MutableScope = new PackageScope()
174188

189+
def isFlatName(name: SimpleTermName) = name.lastIndexOf('$', name.length - 2) >= 0
190+
191+
def isFlatName(classRep: ClassPath#ClassRep) = {
192+
val idx = classRep.name.indexOf('$')
193+
idx >= 0 && idx < classRep.name.length - 1
194+
}
195+
196+
def maybeModuleClass(classRep: ClassPath#ClassRep) = classRep.name.last == '$'
197+
198+
private def enterClasses(root: SymDenotation, flat: Boolean)(implicit ctx: Context) = {
199+
def isAbsent(classRep: ClassPath#ClassRep) =
200+
!root.unforcedDecls.lookup(classRep.name.toTypeName).exists
201+
202+
if (!root.isRoot) {
203+
for (classRep <- classpath.classes)
204+
if (!maybeModuleClass(classRep) && isFlatName(classRep) == flat &&
205+
(!flat || isAbsent(classRep))) // on 2nd enter of flat names, check that the name has not been entered before
206+
initializeFromClassPath(root.symbol, classRep)
207+
for (classRep <- classpath.classes)
208+
if (maybeModuleClass(classRep) && isFlatName(classRep) == flat &&
209+
isAbsent(classRep))
210+
initializeFromClassPath(root.symbol, classRep)
211+
}
212+
}
213+
175214
def doComplete(root: SymDenotation)(implicit ctx: Context): Unit = {
176215
assert(root is PackageClass, root)
177-
def maybeModuleClass(classRep: ClassPath#ClassRep) = classRep.name.last == '$'
178216
val pre = root.owner.thisType
179217
root.info = ClassInfo(pre, root.symbol.asClass, Nil, currentDecls, pre select sourceModule)
180218
if (!sourceModule.isCompleted)
181219
sourceModule.completer.complete(sourceModule)
182-
if (!root.isRoot) {
183-
for (classRep <- classpath.classes)
184-
if (!maybeModuleClass(classRep))
185-
initializeFromClassPath(root.symbol, classRep)
186-
for (classRep <- classpath.classes)
187-
if (maybeModuleClass(classRep) &&
188-
!root.unforcedDecls.lookup(classRep.name.toTypeName).exists)
189-
initializeFromClassPath(root.symbol, classRep)
220+
enterFlatClasses = Some { ctx =>
221+
enterFlatClasses = None
222+
enterClasses(root, flat = true)(ctx)
190223
}
224+
enterClasses(root, flat = false)
191225
if (!root.isEmptyPackage)
192226
for (pkg <- classpath.packages)
193227
enterPackage(root.symbol, pkg)

compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
363363
}
364364

365365
def slowSearch(name: Name): Symbol =
366-
owner.info.decls.find(_.name == name).getOrElse(NoSymbol)
366+
owner.info.decls.find(_.name == name)
367367

368368
def nestedObjectSymbol: Symbol = {
369369
// If the owner is overloaded (i.e. a method), it's not possible to select the

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ object messages {
237237
val msg = {
238238
import core.Flags._
239239
val maxDist = 3
240-
val decls = site.decls.flatMap { sym =>
240+
val decls = site.decls.toList.flatMap { sym =>
241241
if (sym.flagsUNSAFE.is(Synthetic | PrivateOrLocal) || sym.isConstructor) Nil
242242
else List((sym.name.show, sym))
243243
}

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder
214214
// and can therefore be ignored.
215215
def alwaysPresent(s: Symbol) =
216216
s.isCompanionMethod || (csym.is(ModuleClass) && s.isConstructor)
217-
val decls = cinfo.decls.filterNot(alwaysPresent).toList
217+
val decls = cinfo.decls.filter(!alwaysPresent(_)).toList
218218
val apiDecls = apiDefinitions(decls)
219219

220220
val declSet = decls.toSet
@@ -224,7 +224,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder
224224
// We cannot filter out `LegacyApp` because it contains the main method,
225225
// see the comment about main class discovery in `computeType`.
226226
.filter(bc => !bc.is(Scala2x) || bc.eq(LegacyAppClass))
227-
.flatMap(_.classInfo.decls.filterNot(s => s.is(Private) || declSet.contains(s)))
227+
.flatMap(_.classInfo.decls.filter(s => !(s.is(Private) || declSet.contains(s))))
228228
// Inherited members need to be computed lazily because a class might contain
229229
// itself as an inherited member, like in `class A { class B extends A }`,
230230
// this works because of `classLikeCache`

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ object ExplicitOuter {
217217
def outerAccessor(cls: ClassSymbol)(implicit ctx: Context): Symbol =
218218
if (cls.isStatic) NoSymbol // fast return to avoid scanning package decls
219219
else cls.info.member(outerAccName(cls)).suchThat(_ is OuterAccessor).symbol orElse
220-
cls.info.decls.find(_ is OuterAccessor).getOrElse(NoSymbol)
220+
cls.info.decls.find(_ is OuterAccessor)
221221

222222
/** Class has an outer accessor. Can be called only after phase ExplicitOuter. */
223223
private def hasOuter(cls: ClassSymbol)(implicit ctx: Context): Boolean =

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class PrimitiveForwarders extends MiniPhaseTransform with IdentityDenotTransform
4343
import ops._
4444

4545
def methodPrimitiveForwarders: List[Tree] =
46-
for (meth <- mixins.flatMap(_.info.decls.flatMap(needsPrimitiveForwarderTo)).distinct)
46+
for (meth <- mixins.flatMap(_.info.decls.toList.flatMap(needsPrimitiveForwarderTo)).distinct)
4747
yield polyDefDef(implementation(meth.asTerm), forwarder(meth))
4848

4949
cpy.Template(impl)(body = methodPrimitiveForwarders ::: impl.body)

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -391,11 +391,11 @@ class TreeChecker extends Phase with SymTransformer {
391391
!x.isCompanionMethod &&
392392
!x.isValueClassConvertMethod
393393

394-
val symbolsNotDefined = cls.classInfo.decls.toSet.filter(isNonMagicalMethod) -- impl.body.map(_.symbol) - constr.symbol
394+
val symbolsNotDefined = cls.classInfo.decls.toList.toSet.filter(isNonMagicalMethod) -- impl.body.map(_.symbol) - constr.symbol
395395

396396
assert(symbolsNotDefined.isEmpty,
397397
i" $cls tree does not define methods: ${symbolsNotDefined.toList}%, %\n" +
398-
i"expected: ${cls.classInfo.decls.toSet.filter(isNonMagicalMethod).toList}%, %\n" +
398+
i"expected: ${cls.classInfo.decls.toList.toSet.filter(isNonMagicalMethod)}%, %\n" +
399399
i"defined: ${impl.body.map(_.symbol)}%, %")
400400

401401
super.typedClassDef(cdef, cls)

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

+2-5
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,10 @@ object ValueClasses {
2828
!d.isSuperAccessor &&
2929
!d.is(Macro)
3030

31-
/** The member that of a derived value class that unboxes it. */
31+
/** The member of a derived value class that unboxes it. */
3232
def valueClassUnbox(d: ClassDenotation)(implicit ctx: Context): Symbol =
3333
// (info.decl(nme.unbox)).orElse(...) uncomment once we accept unbox methods
34-
d.classInfo.decls
35-
.find(d => d.isTerm && d.symbol.is(ParamAccessor))
36-
.map(_.symbol)
37-
.getOrElse(NoSymbol)
34+
d.classInfo.decls.find(_.is(TermParamAccessor))
3835

3936
/** For a value class `d`, this returns the synthetic cast from the underlying type to
4037
* ErasedValueType defined in the companion module. This method is added to the module

compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ trait TypeAssigner {
9191
else
9292
parent
9393
}
94-
val refinableDecls = info.decls.filterNot(
95-
sym => sym.is(TypeParamAccessor | Private) || sym.isConstructor)
94+
val refinableDecls = info.decls.filter(
95+
sym => !(sym.is(TypeParamAccessor | Private) || sym.isConstructor))
9696
val fullType = (parentType /: refinableDecls)(addRefinement)
9797
mapOver(fullType)
9898
case TypeBounds(lo, hi) if variance > 0 =>

0 commit comments

Comments
 (0)