Skip to content

Commit 4c6e80f

Browse files
committed
Introduce provisional state for better cache reuse
1 parent 5fa69ad commit 4c6e80f

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)
@@ -1311,7 +1398,10 @@ object Types extends TypeUtils {
13111398
final def widen(using Context): Type = this match
13121399
case _: TypeRef | _: MethodOrPoly => this // fast path for most frequent cases
13131400
case tp: TermRef => // fast path for next most frequent case
1314-
if tp.isOverloaded then tp else tp.underlying.widen
1401+
// Don't call `isOverloaded` and `underlying` on `tp` directly,
1402+
// to avoid computing provisional state twice.
1403+
val denot = tp.denot
1404+
if denot.isOverloaded then tp else denot.info.widen
13151405
case tp: SingletonType => tp.underlying.widen
13161406
case tp: ExprType => tp.resultType.widen
13171407
case tp =>
@@ -2305,10 +2395,12 @@ object Types extends TypeUtils {
23052395

23062396
private var myName: Name | Null = null
23072397
private var lastDenotation: Denotation | Null = null
2398+
private var lastDenotationProvState: ProvisionalState | Null = null
23082399
private var lastSymbol: Symbol | Null = null
23092400
private var checkedPeriod: Period = Nowhere
23102401
private var myStableHash: Byte = 0
23112402
private var mySignature: Signature = uninitialized
2403+
private var mySignatureProvState: ProvisionalState | Null = null
23122404
private var mySignatureRunId: Int = NoRunId
23132405

23142406
// Invariants:
@@ -2343,9 +2435,12 @@ object Types extends TypeUtils {
23432435
else if ctx.erasedTypes then atPhase(erasurePhase)(computeSignature)
23442436
else symbol.asSeenFrom(prefix).signature
23452437

2346-
if ctx.runId != mySignatureRunId then
2438+
val currentState = currentProvisionalState
2439+
if ctx.runId != mySignatureRunId
2440+
|| !isStateUpToDate(currentState, mySignatureProvState) then
23472441
mySignature = computeSignature
2348-
if !mySignature.isUnderDefined && !isProvisional then mySignatureRunId = ctx.runId
2442+
mySignatureProvState = currentState
2443+
if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId
23492444
mySignature
23502445
end signature
23512446

@@ -2356,7 +2451,9 @@ object Types extends TypeUtils {
23562451
* some symbols change their signature at erasure.
23572452
*/
23582453
private def currentSignature(using Context): Signature =
2359-
if ctx.runId == mySignatureRunId then mySignature
2454+
if ctx.runId == mySignatureRunId
2455+
&& isStateUpToDate(currentProvisionalState, mySignatureProvState)
2456+
then mySignature
23602457
else
23612458
val lastd = lastDenotation
23622459
if lastd != null then sigFromDenot(lastd)
@@ -2434,7 +2531,10 @@ object Types extends TypeUtils {
24342531
val lastd = lastDenotation.asInstanceOf[Denotation]
24352532
// Even if checkedPeriod == now we still need to recheck lastDenotation.validFor
24362533
// as it may have been mutated by SymDenotation#installAfter
2437-
if checkedPeriod.code != NowhereCode && lastd.validFor.contains(ctx.period) then lastd
2534+
if checkedPeriod.code != NowhereCode
2535+
&& isStateUpToDate(prefix.currentProvisionalState, lastDenotationProvState)
2536+
&& lastd.validFor.contains(ctx.period)
2537+
then lastd
24382538
else computeDenot
24392539

24402540
private def computeDenot(using Context): Denotation = {
@@ -2468,14 +2568,18 @@ object Types extends TypeUtils {
24682568
finish(symd.current)
24692569
}
24702570

2571+
def isLastDenotValid =
2572+
checkedPeriod.code != NowhereCode
2573+
&& isStateUpToDate(prefix.currentProvisionalState, lastDenotationProvState)
2574+
24712575
lastDenotation match {
24722576
case lastd0: SingleDenotation =>
24732577
val lastd = lastd0.skipRemoved
2474-
if lastd.validFor.runId == ctx.runId && checkedPeriod.code != NowhereCode then
2578+
if lastd.validFor.runId == ctx.runId && isLastDenotValid then
24752579
finish(lastd.current)
24762580
else lastd match {
24772581
case lastd: SymDenotation =>
2478-
if stillValid(lastd) && checkedPeriod.code != NowhereCode then finish(lastd.current)
2582+
if stillValid(lastd) && isLastDenotValid then finish(lastd.current)
24792583
else finish(memberDenot(lastd.initial.name, allowPrivate = false))
24802584
case _ =>
24812585
fromDesignator
@@ -2566,7 +2670,8 @@ object Types extends TypeUtils {
25662670

25672671
lastDenotation = denot
25682672
lastSymbol = denot.symbol
2569-
checkedPeriod = if (prefix.isProvisional) Nowhere else ctx.period
2673+
lastDenotationProvState = prefix.currentProvisionalState
2674+
checkedPeriod = ctx.period
25702675
designator match {
25712676
case sym: Symbol if designator ne lastSymbol.nn =>
25722677
designator = lastSymbol.asInstanceOf[Designator{ type ThisName = self.ThisName }]
@@ -3849,14 +3954,18 @@ object Types extends TypeUtils {
38493954
sealed abstract class MethodOrPoly extends UncachedGroundType with LambdaType with MethodicType {
38503955

38513956
// Invariants:
3852-
// (1) mySignatureRunId != NoRunId => mySignature != null
3853-
// (2) myJavaSignatureRunId != NoRunId => myJavaSignature != null
3957+
// (1) mySignatureRunId != NoRunId => mySignature != null
3958+
// (2) myJavaSignatureRunId != NoRunId => myJavaSignature != null
3959+
// (3) myScala2SignatureRunId != NoRunId => myScala2Signature != null
38543960

38553961
private var mySignature: Signature = uninitialized
3962+
private var mySignatureProvState: ProvisionalState | Null = null
38563963
private var mySignatureRunId: Int = NoRunId
38573964
private var myJavaSignature: Signature = uninitialized
3965+
private var myJavaSignatureProvState: ProvisionalState | Null = null
38583966
private var myJavaSignatureRunId: Int = NoRunId
38593967
private var myScala2Signature: Signature = uninitialized
3968+
private var myScala2SignatureProvState: ProvisionalState | Null = null
38603969
private var myScala2SignatureRunId: Int = NoRunId
38613970

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

4004+
val currentState = currentProvisionalState
38954005
sourceLanguage match
38964006
case SourceLanguage.Java =>
3897-
if ctx.runId != myJavaSignatureRunId then
4007+
if ctx.runId != myJavaSignatureRunId
4008+
|| !isStateUpToDate(currentState, myJavaSignatureProvState) then
38984009
myJavaSignature = computeSignature
3899-
if !myJavaSignature.isUnderDefined && !isProvisional then myJavaSignatureRunId = ctx.runId
4010+
myJavaSignatureProvState = currentState
4011+
if !myJavaSignature.isUnderDefined then myJavaSignatureRunId = ctx.runId
39004012
myJavaSignature
39014013
case SourceLanguage.Scala2 =>
3902-
if ctx.runId != myScala2SignatureRunId then
4014+
if ctx.runId != myScala2SignatureRunId
4015+
|| !isStateUpToDate(currentState, myScala2SignatureProvState) then
39034016
myScala2Signature = computeSignature
3904-
if !myScala2Signature.isUnderDefined && !isProvisional then myScala2SignatureRunId = ctx.runId
4017+
myScala2SignatureProvState = currentState
4018+
if !myScala2Signature.isUnderDefined then myScala2SignatureRunId = ctx.runId
39054019
myScala2Signature
39064020
case SourceLanguage.Scala3 =>
3907-
if ctx.runId != mySignatureRunId then
4021+
if ctx.runId != mySignatureRunId
4022+
|| !isStateUpToDate(currentState, mySignatureProvState) then
39084023
mySignature = computeSignature
3909-
if !mySignature.isUnderDefined && !isProvisional then mySignatureRunId = ctx.runId
4024+
mySignatureProvState = currentState
4025+
if !mySignature.isUnderDefined then mySignatureRunId = ctx.runId
39104026
mySignature
39114027
end signature
39124028

0 commit comments

Comments
 (0)