-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Compiler performance improvements around Symbols and Types #5823
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
5d07122
f717ee9
4a22452
9f954e9
43f3d85
fc80f86
aa2ccb9
d64765e
d85a087
8a53dd0
b47c989
f985f1f
67ea707
1517573
9b28b01
3b9feea
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 |
---|---|---|
|
@@ -1089,7 +1089,8 @@ abstract class ClassfileParser { | |
cls setInfo completer | ||
mod setInfo completer | ||
mod.moduleClass setInfo loaders.moduleClassLoader | ||
List(cls, mod.moduleClass) foreach (_.associatedFile = file) | ||
cls.associatedFile = file | ||
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. if you want to retain the previous structure, you could probably (cls.associatedFile, mod.moduleClass.associatedFile) = (file, file) |
||
mod.moduleClass.associatedFile = file | ||
(cls, mod) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2323,8 +2323,9 @@ trait Symbols extends api.Symbols { self: SymbolTable => | |
|
||
private def matchingSymbolInternal(site: Type, candidate: Symbol): Symbol = { | ||
def qualifies(sym: Symbol) = !sym.isTerm || ((site memberType this) matches (site memberType sym)) | ||
//OPT cut down on #closures by special casing non-overloaded case | ||
if (candidate.isOverloaded) candidate filter qualifies | ||
//OPT Fast past for NoSymbol. Cut down on #closures by special casing non-overloaded case | ||
if (candidate == NoSymbol) NoSymbol | ||
else if (candidate.isOverloaded) candidate filter qualifies | ||
else if (qualifies(candidate)) candidate | ||
else NoSymbol | ||
} | ||
|
@@ -2374,15 +2375,16 @@ trait Symbols extends api.Symbols { self: SymbolTable => | |
|
||
/** Returns all symbols overridden by this symbol. */ | ||
final def allOverriddenSymbols: List[Symbol] = { | ||
def loop(xs: List[Symbol]): List[Symbol] = xs match { | ||
case Nil => Nil | ||
@tailrec | ||
def loop(xs: List[Symbol], result: List[Symbol]): List[Symbol] = xs match { | ||
case Nil => result | ||
case x :: xs => | ||
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. minor: could also be |
||
overriddenSymbol(x) match { | ||
case NoSymbol => loop(xs) | ||
case sym => sym :: loop(xs) | ||
case NoSymbol => loop(xs, result) | ||
case sym => loop(xs, sym :: result) | ||
} | ||
} | ||
if (isOverridingSymbol) loop(owner.ancestors) else Nil | ||
if (isOverridingSymbol) loop(owner.ancestors, Nil) else Nil | ||
} | ||
|
||
/** Equivalent to allOverriddenSymbols.nonEmpty, but more efficient. */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1087,7 +1087,7 @@ trait Types | |
override def baseTypeSeq: BaseTypeSeq = supertype.baseTypeSeq | ||
override def baseTypeSeqDepth: Depth = supertype.baseTypeSeqDepth | ||
override def baseClasses: List[Symbol] = supertype.baseClasses | ||
} | ||
override def boundSyms: Set[Symbol] = emptySymbolSet} | ||
|
||
/** A base class for types that represent a single value | ||
* (single-types and this-types). | ||
|
@@ -2129,15 +2129,27 @@ trait Types | |
//OPT specialize hashCode | ||
override final def computeHashCode = { | ||
import scala.util.hashing.MurmurHash3._ | ||
val hasArgs = args ne Nil | ||
var h = productSeed | ||
h = mix(h, pre.hashCode) | ||
h = mix(h, sym.hashCode) | ||
if (hasArgs) | ||
finalizeHash(mix(h, args.hashCode()), 3) | ||
else | ||
finalizeHash(h, 2) | ||
var length = 2 | ||
var elems = args | ||
while (elems ne Nil) { | ||
h = mix(h, elems.head.hashCode()) | ||
elems = elems.tail | ||
length += 1 | ||
} | ||
finalizeHash(h, length) | ||
} | ||
//OPT specialize equals | ||
override final def equals(other: Any): Boolean = { | ||
other match { | ||
case otherTypeRef: TypeRef => | ||
pre.equals(otherTypeRef.pre) && sym.eq(otherTypeRef.sym) && sameElementsEquals(args, otherTypeRef.args) | ||
case _ => false | ||
} | ||
} | ||
|
||
|
||
// interpret symbol's info in terms of the type's prefix and type args | ||
protected def relativeInfo: Type = appliedType(sym.info.asSeenFrom(pre, sym.owner), argsOrDummies) | ||
|
@@ -3605,7 +3617,10 @@ trait Types | |
if (sym.isAliasType && sameLength(sym.info.typeParams, args) && !sym.lockOK) | ||
throw new RecoverableCyclicReference(sym) | ||
|
||
TypeRef(pre, sym, args) | ||
if ((args eq Nil) && (pre eq NoPrefix)) | ||
sym.tpeHK // opt lean on TypeSymbol#tyconCache, rather than interning a type ref. | ||
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. Could this be pushed further down into 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. IIRC, we can't (easily) do that because 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. OK, I see.. We could clean up TypeRef construction one day. |
||
else | ||
TypeRef(pre, sym, args) | ||
case _ => | ||
typeRef(pre, sym, args) | ||
} | ||
|
@@ -3786,7 +3801,7 @@ trait Types | |
private var uniques: util.WeakHashSet[Type] = _ | ||
private var uniqueRunId = NoRunId | ||
|
||
protected def unique[T <: Type](tp: T): T = { | ||
protected def unique[T <: Type](tp: T): T = { | ||
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.
|
||
if (Statistics.canEnable) Statistics.incCounter(rawTypeCount) | ||
if (uniqueRunId != currentRunId) { | ||
uniques = util.WeakHashSet[Type](initialUniquesCapacity) | ||
|
@@ -3901,7 +3916,7 @@ trait Types | |
&& isRawIfWithoutArgs(sym) | ||
) | ||
|
||
def singletonBounds(hi: Type) = TypeBounds.upper(intersectionType(List(hi, SingletonClass.tpe))) | ||
def singletonBounds(hi: Type) = TypeBounds.upper(intersectionType(hi :: SingletonClass.tpe :: Nil)) | ||
|
||
/** | ||
* A more persistent version of `Type#memberType` which does not require | ||
|
@@ -4241,38 +4256,44 @@ trait Types | |
*/ | ||
protected[internal] def specializesSym(preLo: Type, symLo: Symbol, preHi: Type, symHi: Symbol, depth: Depth): Boolean = | ||
(symHi.isAliasType || symHi.isTerm || symHi.isAbstractType) && { | ||
// only now that we know symHi is a viable candidate ^^^^^^^, do the expensive checks: ----V | ||
require((symLo ne NoSymbol) && (symHi ne NoSymbol), ((preLo, symLo, preHi, symHi, depth))) | ||
|
||
val tpHi = preHi.memberInfo(symHi).substThis(preHi.typeSymbol, preLo) | ||
|
||
// Should we use memberType or memberInfo? | ||
// memberType transforms (using `asSeenFrom`) `sym.tpe`, | ||
// whereas memberInfo performs the same transform on `sym.info`. | ||
// For term symbols, this ends up being the same thing (`sym.tpe == sym.info`). | ||
// For type symbols, however, the `.info` of an abstract type member | ||
// is defined by its bounds, whereas its `.tpe` is a `TypeRef` to that type symbol, | ||
// so that `sym.tpe <:< sym.info`, but not the other way around. | ||
// | ||
// Thus, for the strongest (correct) result, | ||
// we should use `memberType` on the low side. | ||
// | ||
// On the high side, we should use the result appropriate | ||
// for the right side of the `<:<` above (`memberInfo`). | ||
val tpLo = preLo.memberType(symLo) | ||
|
||
debuglog(s"specializesSymHi: $preHi . $symHi : $tpHi") | ||
debuglog(s"specializesSymLo: $preLo . $symLo : $tpLo") | ||
|
||
if (symHi.isTerm) | ||
(isSubType(tpLo, tpHi, depth) && | ||
(!symHi.isStable || symLo.isStable) && // sub-member must remain stable | ||
(!symLo.hasVolatileType || symHi.hasVolatileType || tpHi.isWildcard)) // sub-member must not introduce volatility | ||
else if (symHi.isAbstractType) | ||
((tpHi.bounds containsType tpLo) && | ||
kindsConform(symHi :: Nil, tpLo :: Nil, preLo, symLo.owner)) | ||
else // we know `symHi.isAliasType` (see above) | ||
tpLo =:= tpHi | ||
val symHiInfo = symHi.info | ||
if (symHi.isTerm && symHiInfo == WildcardType) { | ||
// OPT fast path (avoiding tpLo.mmeberType) for wildcards which appear here frequently in the search for implicit views. | ||
!symHi.isStable || symLo.isStable // sub-member must remain stable | ||
} else { | ||
// only now that we know symHi is a viable candidate, do the expensive checks: ----V | ||
require((symLo ne NoSymbol) && (symHi ne NoSymbol), ((preLo, symLo, preHi, symHi, depth))) | ||
|
||
val tpHi = symHiInfo.asSeenFrom(preHi, symHi.owner).substThis(preHi.typeSymbol, preLo) | ||
|
||
// Should we use memberType or memberInfo? | ||
// memberType transforms (using `asSeenFrom`) `sym.tpe`, | ||
// whereas memberInfo performs the same transform on `sym.info`. | ||
// For term symbols, this ends up being the same thing (`sym.tpe == sym.info`). | ||
// For type symbols, however, the `.info` of an abstract type member | ||
// is defined by its bounds, whereas its `.tpe` is a `TypeRef` to that type symbol, | ||
// so that `sym.tpe <:< sym.info`, but not the other way around. | ||
// | ||
// Thus, for the strongest (correct) result, | ||
// we should use `memberType` on the low side. | ||
// | ||
// On the high side, we should use the result appropriate | ||
// for the right side of the `<:<` above (`memberInfo`). | ||
val tpLo = preLo.memberType(symLo) | ||
|
||
debuglog(s"specializesSymHi: $preHi . $symHi : $tpHi") | ||
debuglog(s"specializesSymLo: $preLo . $symLo : $tpLo") | ||
|
||
if (symHi.isTerm) | ||
(isSubType(tpLo, tpHi, depth) && | ||
(!symHi.isStable || symLo.isStable) && // sub-member must remain stable | ||
(!symLo.hasVolatileType || symHi.hasVolatileType || tpHi.isWildcard)) // sub-member must not introduce volatility | ||
else if (symHi.isAbstractType) | ||
((tpHi.bounds containsType tpLo) && | ||
kindsConform(symHi :: Nil, tpLo :: Nil, preLo, symLo.owner)) | ||
else // we know `symHi.isAliasType` (see above) | ||
tpLo =:= tpHi | ||
} | ||
} | ||
|
||
/** A function implementing `tp1` matches `tp2`. */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -184,8 +184,7 @@ private[internal] trait GlbLubs { | |
/** Eliminate from list of types all elements which are a supertype | ||
* of some other element of the list. */ | ||
private def elimSuper(ts: List[Type]): List[Type] = ts match { | ||
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. I don't claim to understand it, but I have a question: 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.
|
||
case List() => List() | ||
case List(t) => List(t) | ||
case List() | List(_) => ts | ||
case t :: ts1 => | ||
val rest = elimSuper(ts1 filter (t1 => !(t <:< t1))) | ||
if (rest exists (t1 => t1 <:< t)) rest else t :: rest | ||
|
@@ -195,8 +194,8 @@ private[internal] trait GlbLubs { | |
* of some other element of the list. */ | ||
private def elimSub(ts: List[Type], depth: Depth): List[Type] = { | ||
def elimSub0(ts: List[Type]): List[Type] = ts match { | ||
case List() => List() | ||
case List(t) => List(t) | ||
case List() => ts | ||
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. could probably be: case List() | List(_) => ts instead |
||
case List(t) => ts | ||
case t :: ts1 => | ||
val rest = elimSub0(ts1 filter (t1 => !isSubType(t1, t, depth.decr))) | ||
if (rest exists (t1 => isSubType(t, t1, depth.decr))) rest else t :: rest | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,11 +101,16 @@ private[internal] trait TypeMaps { | |
case tr @ TypeRef(pre, sym, args) => | ||
val pre1 = this(pre) | ||
val args1 = ( | ||
if (trackVariance && args.nonEmpty && !variance.isInvariant && sym.typeParams.nonEmpty) | ||
mapOverArgs(args, sym.typeParams) | ||
else | ||
if (trackVariance && args.nonEmpty && !variance.isInvariant) { | ||
val tparams = sym.typeParams | ||
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. minor: to make it more obvious what happened to the if (sym.typeParams.nonEmpty) mapOverArgs(args, sym.typeParams)
else args mapConserve this |
||
if (tparams.isEmpty) | ||
args mapConserve this | ||
else | ||
mapOverArgs(args, tparams) | ||
} else { | ||
args mapConserve this | ||
) | ||
} | ||
) | ||
if ((pre1 eq pre) && (args1 eq args)) tp | ||
else copyTypeRef(tp, pre1, tr.coevolveSym(pre1), args1) | ||
case ThisType(_) => tp | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,19 @@ trait Collections { | |
head | ||
} | ||
|
||
final def sameElementsEquals(thiss: List[AnyRef], that: List[AnyRef]): Boolean = { | ||
// Probably immutable, so check reference identity first (it's quick anyway) | ||
(thiss eq that) || { | ||
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. question: this inlining of 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. The key difference is using |
||
var these = thiss | ||
var those = that | ||
while (!these.isEmpty && !those.isEmpty && these.head.equals(those.head)) { | ||
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. minor: could probably be 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. I've got a bit of an aversion to |
||
these = these.tail | ||
those = those.tail | ||
} | ||
these.isEmpty && those.isEmpty | ||
} | ||
} | ||
|
||
final def collectFirst[A, B](as: List[A])(pf: PartialFunction[A, B]): Option[B] = { | ||
@tailrec | ||
def loop(rest: List[A]): Option[B] = rest match { | ||
|
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.
the first is more readable, is the second that much faster?
Uh oh!
There was an error while loading. Please reload this page.
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.
Yeah, we're lacking an optimization on
List.apply
to avoid boxing an array to pass as varargs. We've got a ticket open for that improvement, but in the meantime I'm happy enough with the readability of::
to use it in the few places where this makes a material difference.