Skip to content

Commit e2608cf

Browse files
committed
Fuse isCachable and computeBaseType
The previous logic tended to test things several times, which made it harder to understand and possibly less efficient. We now combine the cachability decisions in the main `recur` method of `baseTypeOf`.
1 parent 25c720f commit e2608cf

File tree

2 files changed

+119
-97
lines changed

2 files changed

+119
-97
lines changed

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

Lines changed: 109 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,126 +1621,140 @@ object SymDenotations {
16211621

16221622
/** Compute tp.baseType(this) */
16231623
final def baseTypeOf(tp: Type)(implicit ctx: Context): Type = {
1624+
val btrCache = baseTypeCache
1625+
def inCache(tp: Type) = btrCache.get(tp) != null
16241626

1625-
def foldGlb(bt: Type, ps: List[Type]): Type = ps match {
1626-
case p :: ps1 => foldGlb(bt & baseTypeOf(p), ps1)
1627-
case _ => bt
1627+
def ensureAcyclic(baseTp: Type) = {
1628+
if (baseTp `eq` NoPrefix) throw CyclicReference(this)
1629+
baseTp
16281630
}
16291631

1630-
/** We cannot cache:
1631-
* - type variables which are uninstantiated or whose instances can
1632-
* change, depending on typerstate.
1633-
* - types where the underlying type is an ErasedValueType, because
1634-
* this underlying type will change after ElimErasedValueType,
1635-
* and this changes subtyping relations. As a shortcut, we do not
1636-
* cache ErasedValueType at all.
1637-
*/
1638-
def isCachable(tp: Type, btrCache: BaseTypeMap): Boolean = {
1639-
def inCache(tp: Type) = btrCache.containsKey(tp) && isCachable(tp, btrCache)
1632+
def recur(tp: Type): Type = try {
16401633
tp match {
1641-
case tp: TypeRef if tp.symbol.isClass => true
1642-
case tp: TypeVar => tp.inst.exists && inCache(tp.inst)
1643-
case tp: TypeParamRef if ctx.typerState.constraint.contains(tp) => false
1644-
//case tp: TypeProxy => inCache(tp.underlying) // disabled, can re-enable insyead of last two lines for performance testing
1645-
case tp: TypeProxy => isCachable(tp.underlying, btrCache)
1646-
case tp: AndType => isCachable(tp.tp1, btrCache) && isCachable(tp.tp2, btrCache)
1647-
case tp: OrType => isCachable(tp.tp1, btrCache) && isCachable(tp.tp2, btrCache)
1648-
case _: TypeErasure.ErasedValueType => false
1649-
case _ => true
1634+
case tp: CachedType =>
1635+
val baseTp = btrCache.get(tp)
1636+
if (baseTp != null) return ensureAcyclic(baseTp)
1637+
case _ =>
16501638
}
1651-
}
1652-
1653-
def computeBaseTypeOf(tp: Type): Type = {
16541639
if (Stats.monitored) {
16551640
Stats.record("computeBaseType, total")
16561641
Stats.record(s"computeBaseType, ${tp.getClass}")
16571642
}
1658-
if (symbol.isStatic && tp.derivesFrom(symbol) && symbol.typeParams.isEmpty)
1659-
symbol.typeRef
1660-
else tp match {
1643+
tp match {
16611644
case tp @ TypeRef(prefix, _) =>
1662-
val subsym = tp.symbol
1663-
if (subsym eq symbol) tp
1664-
else subsym.denot match {
1665-
case clsd: ClassDenotation =>
1666-
val owner = clsd.owner
1667-
val isOwnThis = prefix match {
1668-
case prefix: ThisType => prefix.cls eq owner
1669-
case NoPrefix => true
1670-
case _ => false
1671-
}
1672-
if (isOwnThis)
1673-
if (clsd.baseClassSet.contains(symbol)) foldGlb(NoType, clsd.classParents)
1674-
else NoType
1675-
else
1676-
baseTypeOf(clsd.typeRef).asSeenFrom(prefix, owner)
1677-
case _ =>
1678-
baseTypeOf(tp.superType)
1645+
1646+
def foldGlb(bt: Type, ps: List[Type]): Type = ps match {
1647+
case p :: ps1 => foldGlb(bt & recur(p), ps1)
1648+
case _ => bt
16791649
}
1650+
1651+
def computeTypeRef = {
1652+
btrCache.put(tp, NoPrefix)
1653+
tp.symbol.denot match {
1654+
case clsd: ClassDenotation =>
1655+
def isOwnThis = prefix match {
1656+
case prefix: ThisType => prefix.cls `eq` clsd.owner
1657+
case NoPrefix => true
1658+
case _ => false
1659+
}
1660+
val baseTp =
1661+
if (tp.symbol eq symbol)
1662+
tp
1663+
else if (isOwnThis)
1664+
if (clsd.baseClassSet.contains(symbol))
1665+
if (symbol.isStatic && symbol.typeParams.isEmpty) symbol.typeRef
1666+
else foldGlb(NoType, clsd.classParents)
1667+
else NoType
1668+
else
1669+
recur(clsd.typeRef).asSeenFrom(prefix, clsd.owner)
1670+
if (baseTp.exists) btrCache.put(tp, baseTp) else btrCache.remove(tp)
1671+
baseTp
1672+
case _ =>
1673+
val superTp = tp.superType
1674+
val baseTp = recur(superTp)
1675+
if (baseTp.exists && inCache(superTp) && tp.symbol.maybeOwner.isType)
1676+
btrCache.put(tp, baseTp) // typeref cannot be a GADT, so cache is stable
1677+
else
1678+
btrCache.remove(tp)
1679+
baseTp
1680+
}
1681+
}
1682+
computeTypeRef
1683+
16801684
case tp @ AppliedType(tycon, args) =>
1681-
val subsym = tycon.typeSymbol
1682-
if (subsym eq symbol) tp
1683-
else (tycon.typeParams: @unchecked) match {
1684-
case LambdaParam(_, _) :: _ =>
1685-
baseTypeOf(tp.superType)
1686-
case tparams: List[Symbol @unchecked] =>
1687-
baseTypeOf(tycon).subst(tparams, args)
1685+
1686+
def computeApplied = {
1687+
btrCache.put(tp, NoPrefix)
1688+
val baseTp =
1689+
if (tycon.typeSymbol eq symbol) tp
1690+
else (tycon.typeParams: @unchecked) match {
1691+
case LambdaParam(_, _) :: _ =>
1692+
recur(tp.superType)
1693+
case tparams: List[Symbol @unchecked] =>
1694+
recur(tycon).subst(tparams, args)
1695+
}
1696+
if (baseTp.exists) btrCache.put(tp, baseTp) else btrCache.remove(tp)
1697+
baseTp
16881698
}
1689-
case tp: TypeParamRef =>
1690-
baseTypeOf(ctx.typeComparer.bounds(tp).hi)
1699+
computeApplied
1700+
1701+
case tp: TypeParamRef => // uncachable, since baseType depends on context bounds
1702+
recur(ctx.typeComparer.bounds(tp).hi)
16911703
case tp: TypeProxy =>
1692-
baseTypeOf(tp.superType)
1693-
case AndType(tp1, tp2) =>
1694-
baseTypeOf(tp1) & baseTypeOf(tp2) match {
1695-
case AndType(tp1a, tp2a) if (tp1a eq tp1) && (tp2a eq tp2) => tp
1696-
case res => res
1704+
1705+
def computeTypeProxy = {
1706+
val superTp = tp.superType
1707+
val baseTp = recur(superTp)
1708+
tp match {
1709+
case tp: CachedType if baseTp.exists && inCache(superTp) =>
1710+
// Note: This also works for TypeVars: If they are not instantiated, their supertype
1711+
// is a TypeParamRef, which is never cached. So uninstantiated TypeVars are not cached either.
1712+
btrCache.put(tp, baseTp)
1713+
case _ =>
1714+
}
1715+
baseTp
16971716
}
1698-
case OrType(tp1, tp2) =>
1699-
baseTypeOf(tp1) | baseTypeOf(tp2) match {
1700-
case OrType(tp1a, tp2a) if (tp1a eq tp1) && (tp2a eq tp2) => tp
1701-
case res => res
1717+
computeTypeProxy
1718+
1719+
case tp: AndOrType =>
1720+
1721+
def computeAndOrType = {
1722+
val tp1 = tp.tp1
1723+
val tp2 = tp.tp2
1724+
val baseTp =
1725+
if (symbol.isStatic && tp.derivesFrom(symbol) && symbol.typeParams.isEmpty)
1726+
symbol.typeRef
1727+
else {
1728+
val baseTp1 = recur(tp1)
1729+
val baseTp2 = recur(tp2)
1730+
val combined = if (tp.isAnd) baseTp1 & baseTp2 else baseTp1 | baseTp2
1731+
combined match {
1732+
case combined: AndOrType
1733+
if (combined.tp1 eq tp1) && (combined.tp2 eq tp2) && (combined.isAnd == tp.isAnd) => tp
1734+
case _ => combined
1735+
}
1736+
}
1737+
if (baseTp.exists && inCache(tp1) && inCache(tp2)) btrCache.put(tp, baseTp)
1738+
baseTp
17021739
}
1740+
computeAndOrType
1741+
17031742
case JavaArrayType(_) if symbol == defn.ObjectClass =>
17041743
this.typeRef
17051744
case _ =>
17061745
NoType
17071746
}
17081747
}
1748+
catch {
1749+
case ex: Throwable =>
1750+
btrCache.remove(tp)
1751+
throw ex
1752+
}
1753+
17091754

17101755
/*>|>*/ trace.onDebug(s"$tp.baseType($this)") /*<|<*/ {
17111756
Stats.record("baseTypeOf")
1712-
tp.stripTypeVar match {
1713-
case tp: CachedType =>
1714-
val btrCache = baseTypeCache
1715-
if (!isCachable(tp, btrCache))
1716-
computeBaseTypeOf(tp)
1717-
else
1718-
try {
1719-
var basetp = btrCache.get(tp)
1720-
if (basetp == null) {
1721-
btrCache.put(tp, NoPrefix)
1722-
basetp = computeBaseTypeOf(tp)
1723-
if (basetp.exists) {
1724-
Stats.record("cached base type exists")
1725-
btrCache.put(tp, basetp)
1726-
}
1727-
else {
1728-
Stats.record("cached base type missing")
1729-
btrCache.remove(tp)
1730-
}
1731-
}
1732-
else if (basetp `eq` NoPrefix)
1733-
throw CyclicReference(this)
1734-
basetp
1735-
}
1736-
catch {
1737-
case ex: Throwable =>
1738-
btrCache.put(tp, null)
1739-
throw ex
1740-
}
1741-
case tp =>
1742-
computeBaseTypeOf(tp)
1743-
}
1757+
recur(tp)
17441758
}
17451759
}
17461760

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2438,7 +2438,14 @@ object Types {
24382438

24392439
// --- AndType/OrType ---------------------------------------------------------------
24402440

2441-
abstract case class AndType(tp1: Type, tp2: Type) extends CachedGroundType with ValueType {
2441+
abstract class AndOrType extends CachedGroundType with ValueType {
2442+
def isAnd: Boolean
2443+
def tp1: Type
2444+
def tp2: Type
2445+
}
2446+
2447+
abstract case class AndType(tp1: Type, tp2: Type) extends AndOrType {
2448+
def isAnd = true
24422449
private[this] var myBaseClassesPeriod: Period = Nowhere
24432450
private[this] var myBaseClasses: List[ClassSymbol] = _
24442451
/** Base classes of are the merge of the operand base classes. */
@@ -2502,7 +2509,8 @@ object Types {
25022509
if (checkValid) apply(tp1, tp2) else unchecked(tp1, tp2)
25032510
}
25042511

2505-
abstract case class OrType(tp1: Type, tp2: Type) extends CachedGroundType with ValueType {
2512+
abstract case class OrType(tp1: Type, tp2: Type) extends AndOrType {
2513+
def isAnd = false
25062514
private[this] var myBaseClassesPeriod: Period = Nowhere
25072515
private[this] var myBaseClasses: List[ClassSymbol] = _
25082516
/** Base classes of are the intersection of the operand base classes. */

0 commit comments

Comments
 (0)