Skip to content

Commit 90a661b

Browse files
committed
Drop ephemeral
Changing the interpolation scheme uncovered several cache invalidation problems with - the asSeenFrom cache in Denotations - the superType cache in AppliedType - the lastDenotation cache in NamedType The new denotation scheme performed essentially same operations as the old one, but sometimes in a different order. I am still not quite sure how the differences made the cache invalidations fail. On the other hand, it is quite plausible (obvious even, in retrospect) that the previous invalidation schemes are incomplete. So this commit replaces them with a common algorithm that does not rely on the previous global state represented by `ephemeral`.
1 parent bdfe740 commit 90a661b

File tree

4 files changed

+72
-84
lines changed

4 files changed

+72
-84
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ object Denotations {
130130
if ((cachedPrefix ne pre) || ctx.period != validAsSeenFrom) {
131131
cachedAsSeenFrom = computeAsSeenFrom(pre)
132132
cachedPrefix = pre
133-
validAsSeenFrom = ctx.period
133+
validAsSeenFrom = if (pre.isProvisional) Nowhere else ctx.period
134134
}
135135
cachedAsSeenFrom
136136
} else computeAsSeenFrom(pre)

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,6 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab
4242
private val previousConstraint =
4343
if (previous == null) constraint else previous.constraint
4444

45-
private[this] var myEphemeral: Boolean =
46-
if (previous == null) false else previous.ephemeral
47-
48-
/** The ephemeral flag is set as a side effect if an operation accesses
49-
* the underlying type of a type variable. The reason we need this flag is
50-
* that any such operation is not referentially transparent; it might logically change
51-
* its value at the moment the type variable is instantiated. Caching code needs to
52-
* check the ephemeral flag; If the flag is set during an operation, the result
53-
* of that operation should not be cached.
54-
*/
55-
def ephemeral = myEphemeral
56-
def ephemeral_=(x: Boolean): Unit = { myEphemeral = x }
57-
5845
private[this] var myIsCommittable = true
5946

6047
def isCommittable = myIsCommittable
@@ -159,7 +146,6 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab
159146
constraint foreachTypeVar { tvar =>
160147
if (tvar.owningState.get eq this) tvar.owningState = new WeakReference(targetState)
161148
}
162-
targetState.ephemeral |= ephemeral
163149
targetState.gc()
164150
reporter.flush()
165151
isCommitted = true

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

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,41 @@ object Types {
9696
nextId
9797
}
9898

99+
/** A cache indicating whether the type was still provisional, last time we checked */
100+
@sharable private var mightBeProvisional = true
101+
102+
/** Is this type still provisional? This is the case if the type contains, or depends on,
103+
* uninstantiated type variables or type symbols that have the Provisional flag set.
104+
* This is an antimonotonic property - once a type is not provisional, it stays so forever.
105+
*/
106+
def isProvisional(implicit ctx: Context) = mightBeProvisional && testProvisional
107+
108+
private def testProvisional(implicit ctx: Context) = {
109+
val accu = new TypeAccumulator[Boolean] {
110+
override def apply(x: Boolean, t: Type) =
111+
x || t.mightBeProvisional && {
112+
t.mightBeProvisional = t match {
113+
case t: TypeVar =>
114+
!t.inst.exists
115+
case t: TypeRef =>
116+
(t: Type).mightBeProvisional = false // break cycles
117+
t.symbol.is(Provisional) ||
118+
apply(x, t.prefix) || {
119+
t.info match {
120+
case TypeAlias(alias) => apply(x, alias)
121+
case TypeBounds(lo, hi) => apply(apply(x, lo), hi)
122+
case _ => false
123+
}
124+
}
125+
case _ =>
126+
foldOver(x, t)
127+
}
128+
t.mightBeProvisional
129+
}
130+
}
131+
accu.apply(false, this)
132+
}
133+
99134
/** Is this type different from NoType? */
100135
def exists: Boolean = true
101136

@@ -590,7 +625,6 @@ object Types {
590625
val next = tp.underlying
591626
ctx.typerState.constraint.entry(tp) match {
592627
case bounds: TypeBounds if bounds ne next =>
593-
ctx.typerState.ephemeral = true
594628
go(bounds.hi)
595629
case _ =>
596630
go(next)
@@ -1667,9 +1701,7 @@ object Types {
16671701
private def computeDenot(implicit ctx: Context): Denotation = {
16681702

16691703
def finish(d: Denotation) = {
1670-
if (ctx.typerState.ephemeral)
1671-
record("ephemeral cache miss: memberDenot")
1672-
else if (d.exists)
1704+
if (d.exists)
16731705
// Avoid storing NoDenotations in the cache - we will not be able to recover from
16741706
// them. The situation might arise that a type has NoDenotation in some later
16751707
// phase but a defined denotation earlier (e.g. a TypeRef to an abstract type
@@ -1696,23 +1728,19 @@ object Types {
16961728
finish(symd.current)
16971729
}
16981730

1699-
val savedEphemeral = ctx.typerState.ephemeral
1700-
ctx.typerState.ephemeral = false
1701-
try
1702-
lastDenotation match {
1703-
case lastd0: SingleDenotation =>
1704-
val lastd = lastd0.skipRemoved
1705-
if (lastd.validFor.runId == ctx.runId) finish(lastd.current)
1706-
else lastd match {
1707-
case lastd: SymDenotation =>
1708-
if (ctx.stillValid(lastd)) finish(lastd.current)
1709-
else finish(memberDenot(lastd.initial.name, allowPrivate = false))
1710-
case _ =>
1711-
fromDesignator
1712-
}
1713-
case _ => fromDesignator
1714-
}
1715-
finally ctx.typerState.ephemeral |= savedEphemeral
1731+
lastDenotation match {
1732+
case lastd0: SingleDenotation =>
1733+
val lastd = lastd0.skipRemoved
1734+
if (lastd.validFor.runId == ctx.runId) finish(lastd.current)
1735+
else lastd match {
1736+
case lastd: SymDenotation =>
1737+
if (ctx.stillValid(lastd)) finish(lastd.current)
1738+
else finish(memberDenot(lastd.initial.name, allowPrivate = false))
1739+
case _ =>
1740+
fromDesignator
1741+
}
1742+
case _ => fromDesignator
1743+
}
17161744
}
17171745

17181746
private def disambiguate(d: Denotation)(implicit ctx: Context): Denotation =
@@ -1797,7 +1825,7 @@ object Types {
17971825

17981826
lastDenotation = denot
17991827
lastSymbol = denot.symbol
1800-
checkedPeriod = ctx.period
1828+
checkedPeriod = if (prefix.isProvisional) Nowhere else ctx.period
18011829
designator match {
18021830
case sym: Symbol if designator ne lastSymbol =>
18031831
designator = lastSymbol.asInstanceOf[Designator{ type ThisName = self.ThisName }]
@@ -3138,20 +3166,13 @@ object Types {
31383166

31393167
override def superType(implicit ctx: Context): Type = {
31403168
if (ctx.period != validSuper) {
3141-
validSuper = ctx.period
31423169
cachedSuper = tycon match {
31433170
case tycon: HKTypeLambda => defn.AnyType
3144-
case tycon: TypeVar if !tycon.inst.exists =>
3145-
// supertype not stable, since underlying might change
3146-
validSuper = Nowhere
3147-
tycon.underlying.applyIfParameterized(args)
3148-
case tycon: TypeRef if tycon.symbol.isClass =>
3149-
tycon
3150-
case tycon: TypeProxy =>
3151-
if (tycon.typeSymbol.is(Provisional)) validSuper = Nowhere
3152-
tycon.superType.applyIfParameterized(args)
3171+
case tycon: TypeRef if tycon.symbol.isClass => tycon
3172+
case tycon: TypeProxy => tycon.superType.applyIfParameterized(args)
31533173
case _ => defn.AnyType
31543174
}
3175+
validSuper = if (tycon.isProvisional) Nowhere else ctx.period
31553176
}
31563177
cachedSuper
31573178
}
@@ -3360,10 +3381,7 @@ object Types {
33603381
* uninstantiated
33613382
*/
33623383
def instanceOpt(implicit ctx: Context): Type =
3363-
if (inst.exists) inst else {
3364-
ctx.typerState.ephemeral = true
3365-
ctx.typerState.instType(this)
3366-
}
3384+
if (inst.exists) inst else ctx.typerState.instType(this)
33673385

33683386
/** Is the variable already instantiated? */
33693387
def isInstantiated(implicit ctx: Context) = instanceOpt.exists
@@ -3403,11 +3421,7 @@ object Types {
34033421
/** If the variable is instantiated, its instance, otherwise its origin */
34043422
override def underlying(implicit ctx: Context): Type = {
34053423
val inst = instanceOpt
3406-
if (inst.exists) inst
3407-
else {
3408-
ctx.typerState.ephemeral = true
3409-
origin
3410-
}
3424+
if (inst.exists) inst else origin
34113425
}
34123426

34133427
override def computeHash(bs: Binders): Int = identityHash(bs)

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

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -224,14 +224,9 @@ object Implicits {
224224
}
225225
else if (ctx eq NoContext) Nil
226226
else {
227-
val savedEphemeral = ctx.typerState.ephemeral
228-
ctx.typerState.ephemeral = false
229-
try {
230-
val result = computeEligible(tp)
231-
if (ctx.typerState.ephemeral) record("ephemeral cache miss: eligible")
232-
else eligibleCache.put(tp, result)
233-
result
234-
} finally ctx.typerState.ephemeral |= savedEphemeral
227+
val result = computeEligible(tp)
228+
eligibleCache.put(tp, result)
229+
result
235230
}
236231
}
237232
}
@@ -470,27 +465,20 @@ trait ImplicitRunInfo { self: Run =>
470465
* @param isLifted Type `tp` is the result of a `liftToClasses` application
471466
*/
472467
def iscope(tp: Type, isLifted: Boolean = false): OfTypeImplicits = {
473-
val canCache = Config.cacheImplicitScopes && tp.hash != NotCached
468+
val canCache = Config.cacheImplicitScopes && tp.hash != NotCached && !tp.isProvisional
474469
def computeIScope() = {
475-
val savedEphemeral = ctx.typerState.ephemeral
476-
ctx.typerState.ephemeral = false
477-
try {
478-
val liftedTp = if (isLifted) tp else liftToClasses(tp)
479-
val refs =
480-
if (liftedTp ne tp)
481-
iscope(liftedTp, isLifted = true).companionRefs
482-
else
483-
collectCompanions(tp)
484-
val result = new OfTypeImplicits(tp, refs)(ctx)
485-
if (ctx.typerState.ephemeral)
486-
record("ephemeral cache miss: implicitScope")
487-
else if (canCache &&
488-
((tp eq rootTp) || // first type traversed is always cached
489-
!incomplete.contains(tp))) // other types are cached if they are not incomplete
490-
implicitScopeCache(tp) = result
491-
result
492-
}
493-
finally ctx.typerState.ephemeral |= savedEphemeral
470+
val liftedTp = if (isLifted) tp else liftToClasses(tp)
471+
val refs =
472+
if (liftedTp ne tp)
473+
iscope(liftedTp, isLifted = true).companionRefs
474+
else
475+
collectCompanions(tp)
476+
val result = new OfTypeImplicits(tp, refs)(ctx)
477+
if (canCache &&
478+
((tp eq rootTp) || // first type traversed is always cached
479+
!incomplete.contains(tp))) // other types are cached if they are not incomplete
480+
implicitScopeCache(tp) = result
481+
result
494482
}
495483
if (canCache) implicitScopeCache.getOrElse(tp, computeIScope())
496484
else computeIScope()

0 commit comments

Comments
 (0)