Skip to content

Commit 1d01d7c

Browse files
committed
Introduce provisional state for better cache reuse
1 parent 40b8c7a commit 1d01d7c

File tree

2 files changed

+189
-31
lines changed

2 files changed

+189
-31
lines changed

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

Lines changed: 147 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -118,39 +118,126 @@ object Types extends TypeUtils {
118118
* a call to `resetInst`. This means all caches that rely on `isProvisional`
119119
* can likely end up returning stale results.
120120
*/
121-
def isProvisional(using Context): Boolean = mightBeProvisional && testProvisional
121+
// def isProvisional(using Context): Boolean = mightBeProvisional && testProvisional
122+
def isProvisional(using Context): Boolean = mightBeProvisional && !currentProvisionalState.isEmpty
122123

123-
private def testProvisional(using Context): Boolean =
124+
type ProvisionalState = util.HashMap[Type, Type]
125+
126+
def currentProvisionalState(using Context): ProvisionalState =
127+
val state: ProvisionalState = util.HashMap()
128+
// Compared to `testProvisional`, we don't use short-circuiting or,
129+
// because we want to collect all provisional types.
124130
class ProAcc extends TypeAccumulator[Boolean]:
125-
override def apply(x: Boolean, t: Type) = x || test(t, this)
131+
override def apply(x: Boolean, t: Type) = x | test(t, this)
126132
def test(t: Type, theAcc: TypeAccumulator[Boolean] | Null): Boolean =
127133
if t.mightBeProvisional then
128134
t.mightBeProvisional = t match
129135
case t: TypeRef =>
130-
t.currentSymbol.isProvisional || !t.currentSymbol.isStatic && {
136+
if t.currentSymbol.isProvisional then
137+
// When t is a TypeRef and its symbol is provisional,
138+
// t will be considered provisional and its state is always updating.
139+
// We store itself as info.
140+
state(t) = t
141+
true
142+
else if !t.currentSymbol.isStatic then
131143
(t: Type).mightBeProvisional = false // break cycles
132-
test(t.prefix, theAcc)
133-
|| t.denot.infoOrCompleter.match
134-
case info: LazyType => true
135-
case info: AliasingBounds => test(info.alias, theAcc)
136-
case TypeBounds(lo, hi) => test(lo, theAcc) || test(hi, theAcc)
137-
case _ => false
138-
}
144+
if test(t.prefix, theAcc) then
145+
// If the prefix is provisional, some provisional type from it
146+
// must have been added to state, so we don't need to add t.
147+
true
148+
else t.denot.infoOrCompleter.match
149+
case info: LazyType =>
150+
state(t) = info
151+
true
152+
case info: AliasingBounds =>
153+
test(info.alias, theAcc)
154+
case TypeBounds(lo, hi) =>
155+
test(lo, theAcc) | test(hi, theAcc)
156+
case _ =>
157+
// If a TypeRef has been fully completed, it is no longer provisional,
158+
// so we don't need to traverse its info.
159+
false
160+
else false
139161
case t: TermRef =>
140162
!t.currentSymbol.isStatic && test(t.prefix, theAcc)
141163
case t: AppliedType =>
142-
t.fold(false, (x, tp) => x || test(tp, theAcc))
164+
t.fold(false, (x, tp) => x | test(tp, theAcc))
143165
case t: TypeVar =>
144-
!t.isPermanentlyInstantiated || test(t.permanentInst, theAcc)
166+
if t.isPermanentlyInstantiated then
167+
test(t.permanentInst, theAcc)
168+
else
169+
val inst = t.instanceOpt
170+
if inst.exists then
171+
// We want to store the temporary instance to the state
172+
// in order to reuse the cache when possible.
173+
state(t) = inst
174+
test(inst, theAcc)
175+
else
176+
// When t is a TypeVar and does not have an instancication,
177+
// we store itself as info.
178+
state(t) = t
179+
true
145180
case t: LazyRef =>
146-
!t.completed || test(t.ref, theAcc)
181+
if !t.completed then
182+
// When t is a LazyRef and is not completed,
183+
// we store itself as info.
184+
state(t) = t
185+
true
186+
else
187+
test(t.ref, theAcc)
147188
case _ =>
148189
(if theAcc != null then theAcc else ProAcc()).foldOver(false, t)
149190
end if
150191
t.mightBeProvisional
151192
end test
152193
test(this, null)
153-
end testProvisional
194+
state
195+
end currentProvisionalState
196+
197+
def isStateUpToDate(
198+
currentState: ProvisionalState,
199+
lastState: ProvisionalState | Null)
200+
(using Context): Boolean =
201+
lastState != null
202+
&& currentState.size == lastState.size
203+
&& currentState.iterator.forall: (tp, info) =>
204+
lastState.contains(tp) && {
205+
tp match
206+
case tp: TypeRef => (info ne tp) && (info eq lastState(tp))
207+
case _=> info eq lastState(tp)
208+
}
209+
210+
// private def testProvisional(using Context): Boolean =
211+
// class ProAcc extends TypeAccumulator[Boolean]:
212+
// override def apply(x: Boolean, t: Type) = x || test(t, this)
213+
// def test(t: Type, theAcc: TypeAccumulator[Boolean] | Null): Boolean =
214+
// if t.mightBeProvisional then
215+
// t.mightBeProvisional = t match
216+
// case t: TypeRef =>
217+
// t.currentSymbol.isProvisional || !t.currentSymbol.isStatic && {
218+
// (t: Type).mightBeProvisional = false // break cycles
219+
// test(t.prefix, theAcc)
220+
// || t.denot.infoOrCompleter.match
221+
// case info: LazyType => true
222+
// case info: AliasingBounds => test(info.alias, theAcc)
223+
// case TypeBounds(lo, hi) => test(lo, theAcc) || test(hi, theAcc)
224+
// case _ => false
225+
// }
226+
// case t: TermRef =>
227+
// !t.currentSymbol.isStatic && test(t.prefix, theAcc)
228+
// case t: AppliedType =>
229+
// t.fold(false, (x, tp) => x || test(tp, theAcc))
230+
// case t: TypeVar =>
231+
// !t.isPermanentlyInstantiated || test(t.permanentInst, theAcc)
232+
// case t: LazyRef =>
233+
// !t.completed || test(t.ref, theAcc)
234+
// case _ =>
235+
// (if theAcc != null then theAcc else ProAcc()).foldOver(false, t)
236+
// end if
237+
// t.mightBeProvisional
238+
// end test
239+
// test(this, null)
240+
// end testProvisional
154241

155242
/** Is this type different from NoType? */
156243
final def exists: Boolean = this.ne(NoType)
@@ -1309,7 +1396,10 @@ object Types extends TypeUtils {
13091396
final def widen(using Context): Type = this match
13101397
case _: TypeRef | _: MethodOrPoly => this // fast path for most frequent cases
13111398
case tp: TermRef => // fast path for next most frequent case
1312-
if tp.isOverloaded then tp else tp.underlying.widen
1399+
// Don't call `isOverloaded` and `underlying` on `tp` directly,
1400+
// to avoid computing provisional state twice.
1401+
val denot = tp.denot
1402+
if denot.isOverloaded then tp else denot.info.widen
13131403
case tp: SingletonType => tp.underlying.widen
13141404
case tp: ExprType => tp.resultType.widen
13151405
case tp =>
@@ -2303,10 +2393,12 @@ object Types extends TypeUtils {
23032393

23042394
private var myName: Name | Null = null
23052395
private var lastDenotation: Denotation | Null = null
2396+
private var lastDenotationProvState: ProvisionalState | Null = null
23062397
private var lastSymbol: Symbol | Null = null
23072398
private var checkedPeriod: Period = Nowhere
23082399
private var myStableHash: Byte = 0
23092400
private var mySignature: Signature = uninitialized
2401+
private var mySignatureProvState: ProvisionalState | Null = null
23102402
private var mySignatureRunId: Int = NoRunId
23112403

23122404
// Invariants:
@@ -2341,9 +2433,12 @@ object Types extends TypeUtils {
23412433
else if ctx.erasedTypes then atPhase(erasurePhase)(computeSignature)
23422434
else symbol.asSeenFrom(prefix).signature
23432435

2344-
if ctx.runId != mySignatureRunId then
2436+
val currentState = currentProvisionalState
2437+
if ctx.runId != mySignatureRunId
2438+
|| !isStateUpToDate(currentState, mySignatureProvState) then
23452439
mySignature = computeSignature
2346-
if !mySignature.isUnderDefined && !isProvisional then mySignatureRunId = ctx.runId
2440+
mySignatureProvState = currentState
2441+
if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId
23472442
mySignature
23482443
end signature
23492444

@@ -2354,7 +2449,9 @@ object Types extends TypeUtils {
23542449
* some symbols change their signature at erasure.
23552450
*/
23562451
private def currentSignature(using Context): Signature =
2357-
if ctx.runId == mySignatureRunId then mySignature
2452+
if ctx.runId == mySignatureRunId
2453+
&& isStateUpToDate(currentProvisionalState, mySignatureProvState)
2454+
then mySignature
23582455
else
23592456
val lastd = lastDenotation
23602457
if lastd != null then sigFromDenot(lastd)
@@ -2432,7 +2529,10 @@ object Types extends TypeUtils {
24322529
val lastd = lastDenotation.asInstanceOf[Denotation]
24332530
// Even if checkedPeriod == now we still need to recheck lastDenotation.validFor
24342531
// as it may have been mutated by SymDenotation#installAfter
2435-
if checkedPeriod.code != NowhereCode && lastd.validFor.contains(ctx.period) then lastd
2532+
if checkedPeriod.code != NowhereCode
2533+
&& isStateUpToDate(prefix.currentProvisionalState, lastDenotationProvState)
2534+
&& lastd.validFor.contains(ctx.period)
2535+
then lastd
24362536
else computeDenot
24372537

24382538
private def computeDenot(using Context): Denotation = {
@@ -2466,14 +2566,18 @@ object Types extends TypeUtils {
24662566
finish(symd.current)
24672567
}
24682568

2569+
def isLastDenotValid =
2570+
checkedPeriod.code != NowhereCode
2571+
&& isStateUpToDate(prefix.currentProvisionalState, lastDenotationProvState)
2572+
24692573
lastDenotation match {
24702574
case lastd0: SingleDenotation =>
24712575
val lastd = lastd0.skipRemoved
2472-
if lastd.validFor.runId == ctx.runId && checkedPeriod.code != NowhereCode then
2576+
if lastd.validFor.runId == ctx.runId && isLastDenotValid then
24732577
finish(lastd.current)
24742578
else lastd match {
24752579
case lastd: SymDenotation =>
2476-
if stillValid(lastd) && checkedPeriod.code != NowhereCode then finish(lastd.current)
2580+
if stillValid(lastd) && isLastDenotValid then finish(lastd.current)
24772581
else finish(memberDenot(lastd.initial.name, allowPrivate = false))
24782582
case _ =>
24792583
fromDesignator
@@ -2564,7 +2668,8 @@ object Types extends TypeUtils {
25642668

25652669
lastDenotation = denot
25662670
lastSymbol = denot.symbol
2567-
checkedPeriod = if (prefix.isProvisional) Nowhere else ctx.period
2671+
lastDenotationProvState = prefix.currentProvisionalState
2672+
checkedPeriod = ctx.period
25682673
designator match {
25692674
case sym: Symbol if designator ne lastSymbol.nn =>
25702675
designator = lastSymbol.asInstanceOf[Designator{ type ThisName = self.ThisName }]
@@ -3847,14 +3952,18 @@ object Types extends TypeUtils {
38473952
sealed abstract class MethodOrPoly extends UncachedGroundType with LambdaType with MethodicType {
38483953

38493954
// Invariants:
3850-
// (1) mySignatureRunId != NoRunId => mySignature != null
3851-
// (2) myJavaSignatureRunId != NoRunId => myJavaSignature != null
3955+
// (1) mySignatureRunId != NoRunId => mySignature != null
3956+
// (2) myJavaSignatureRunId != NoRunId => myJavaSignature != null
3957+
// (3) myScala2SignatureRunId != NoRunId => myScala2Signature != null
38523958

38533959
private var mySignature: Signature = uninitialized
3960+
private var mySignatureProvState: ProvisionalState | Null = null
38543961
private var mySignatureRunId: Int = NoRunId
38553962
private var myJavaSignature: Signature = uninitialized
3963+
private var myJavaSignatureProvState: ProvisionalState | Null = null
38563964
private var myJavaSignatureRunId: Int = NoRunId
38573965
private var myScala2Signature: Signature = uninitialized
3966+
private var myScala2SignatureProvState: ProvisionalState | Null = null
38583967
private var myScala2SignatureRunId: Int = NoRunId
38593968

38603969
/** If `isJava` is false, the Scala signature of this method. Otherwise, its Java signature.
@@ -3890,21 +3999,28 @@ object Types extends TypeUtils {
38903999
case tp: PolyType =>
38914000
resultSignature.prependTypeParams(tp.paramNames.length)
38924001

4002+
val currentState = currentProvisionalState
38934003
sourceLanguage match
38944004
case SourceLanguage.Java =>
3895-
if ctx.runId != myJavaSignatureRunId then
4005+
if ctx.runId != myJavaSignatureRunId
4006+
|| !isStateUpToDate(currentState, myJavaSignatureProvState) then
38964007
myJavaSignature = computeSignature
3897-
if !myJavaSignature.isUnderDefined && !isProvisional then myJavaSignatureRunId = ctx.runId
4008+
myJavaSignatureProvState = currentState
4009+
if !myJavaSignature.isUnderDefined then myJavaSignatureRunId = ctx.runId
38984010
myJavaSignature
38994011
case SourceLanguage.Scala2 =>
3900-
if ctx.runId != myScala2SignatureRunId then
4012+
if ctx.runId != myScala2SignatureRunId
4013+
|| !isStateUpToDate(currentState, myScala2SignatureProvState) then
39014014
myScala2Signature = computeSignature
3902-
if !myScala2Signature.isUnderDefined && !isProvisional then myScala2SignatureRunId = ctx.runId
4015+
myScala2SignatureProvState = currentState
4016+
if !myScala2Signature.isUnderDefined then myScala2SignatureRunId = ctx.runId
39034017
myScala2Signature
39044018
case SourceLanguage.Scala3 =>
3905-
if ctx.runId != mySignatureRunId then
4019+
if ctx.runId != mySignatureRunId
4020+
|| !isStateUpToDate(currentState, mySignatureProvState) then
39064021
mySignature = computeSignature
3907-
if !mySignature.isUnderDefined && !isProvisional then mySignatureRunId = ctx.runId
4022+
mySignatureProvState = currentState
4023+
if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId
39084024
mySignature
39094025
end signature
39104026

0 commit comments

Comments
 (0)