Skip to content

Commit 4b81eda

Browse files
authored
Merge pull request #9578 from dotty-staging/optimize-current
Optimize Denotation#current
2 parents 64a239f + e9b7054 commit 4b81eda

File tree

3 files changed

+103
-82
lines changed

3 files changed

+103
-82
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,8 @@ object Contexts {
343343
final def runId = period.runId
344344
final def phaseId = period.phaseId
345345

346+
final def lastPhaseId = base.phases.length - 1
347+
346348
/** Does current phase use an erased types interpretation? */
347349
final def erasedTypes = phase.erasedTypes
348350

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

Lines changed: 96 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,10 @@ object Denotations {
716716
this match {
717717
case symd: SymDenotation =>
718718
if (stillValid(symd)) return updateValidity()
719-
if (acceptStale(symd)) return symd.currentSymbol.denot.orElse(symd).updateValidity()
719+
if acceptStale(symd) && symd.initial.validFor.firstPhaseId <= ctx.lastPhaseId then
720+
// New run might have fewer phases than old, so symbol might no longer be
721+
// visible at all. TabCompleteTests have examples where this happens.
722+
return symd.currentSymbol.denot.orElse(symd).updateValidity()
720723
case _ =>
721724
}
722725
if (!symbol.exists) return updateValidity()
@@ -757,89 +760,102 @@ object Denotations {
757760
* is brought forward to be valid in the new runId. Otherwise
758761
* the symbol is stale, which constitutes an internal error.
759762
*/
760-
def current(using Context): SingleDenotation = {
763+
def current(using Context): SingleDenotation =
764+
util.Stats.record("current")
761765
val currentPeriod = ctx.period
762766
val valid = myValidFor
763-
if (valid.code <= 0) {
764-
// can happen if we sit on a stale denotation which has been replaced
765-
// wholesale by an installAfter; in this case, proceed to the next
766-
// denotation and try again.
767-
val nxt = nextDefined
768-
if (nxt.validFor != Nowhere) return nxt
769-
assert(false, this)
770-
}
771767

772-
if (valid.runId != currentPeriod.runId)
773-
if (exists) initial.bringForward().current
774-
else this
775-
else {
768+
def signalError() = println(s"error while transforming $this")
769+
770+
def assertNotPackage(d: SingleDenotation, transformer: DenotTransformer) = d match
771+
case d: ClassDenotation =>
772+
assert(!d.is(Package), s"illegal transformation of package denotation by transformer $transformer")
773+
case _ =>
774+
775+
def escapeToNext = nextDefined.ensuring(_.validFor != Nowhere)
776+
777+
def toNewRun =
778+
util.Stats.record("current.bringForward")
779+
if exists then initial.bringForward().current else this
780+
781+
def goForward =
776782
var cur = this
777-
if (currentPeriod.code > valid.code) {
778-
// search for containing period as long as nextInRun increases.
779-
var next = nextInRun
780-
while (next.validFor.code > valid.code && !(next.validFor contains currentPeriod)) {
781-
cur = next
782-
next = next.nextInRun
783-
}
784-
if (next.validFor.code > valid.code) {
785-
// in this case, next.validFor contains currentPeriod
786-
cur = next
787-
cur
788-
}
789-
else {
790-
//println(s"might need new denot for $cur, valid for ${cur.validFor} at $currentPeriod")
791-
// not found, cur points to highest existing variant
792-
val nextTransformerId = ctx.base.nextDenotTransformerId(cur.validFor.lastPhaseId)
793-
if (currentPeriod.lastPhaseId <= nextTransformerId)
794-
cur.validFor = Period(currentPeriod.runId, cur.validFor.firstPhaseId, nextTransformerId)
795-
else {
796-
var startPid = nextTransformerId + 1
797-
val transformer = ctx.base.denotTransformers(nextTransformerId)
798-
//println(s"transforming $this with $transformer")
799-
try
800-
next = atPhase(transformer)(transformer.transform(cur))
801-
catch {
802-
case ex: CyclicReference =>
803-
println(s"error while transforming $this") // DEBUG
804-
throw ex
805-
}
806-
if (next eq cur)
807-
startPid = cur.validFor.firstPhaseId
808-
else {
809-
next match {
810-
case next: ClassDenotation =>
811-
assert(!next.is(Package), s"illegal transformation of package denotation by transformer $transformer")
812-
case _ =>
813-
}
814-
next.insertAfter(cur)
815-
cur = next
816-
}
817-
cur.validFor = Period(currentPeriod.runId, startPid, transformer.lastPhaseId)
818-
//printPeriods(cur)
819-
//println(s"new denot: $cur, valid for ${cur.validFor}")
820-
}
821-
cur.current // multiple transformations could be required
822-
}
823-
}
824-
else {
825-
// currentPeriod < end of valid; in this case a version must exist
826-
// but to be defensive we check for infinite loop anyway
827-
var cnt = 0
828-
while (!(cur.validFor contains currentPeriod)) {
829-
//println(s"searching: $cur at $currentPeriod, valid for ${cur.validFor}")
830-
cur = cur.nextInRun
831-
// Note: One might be tempted to add a `prev` field to get to the new denotation
832-
// more directly here. I tried that, but it degrades rather than improves
833-
// performance: Test setup: Compile everything in dotc and immediate subdirectories
834-
// 10 times. Best out of 10: 18154ms with `prev` field, 17777ms without.
835-
cnt += 1
836-
if (cnt > MaxPossiblePhaseId)
837-
return atPhase(coveredInterval.firstPhaseId)(current)
838-
}
783+
// search for containing period as long as nextInRun increases.
784+
var next = nextInRun
785+
while next.validFor.code > valid.code && !(next.validFor contains currentPeriod) do
786+
cur = next
787+
next = next.nextInRun
788+
if next.validFor.code > valid.code then
789+
// in this case, next.validFor contains currentPeriod
790+
cur = next
839791
cur
840-
}
841-
}
842-
}
792+
else
793+
//println(s"might need new denot for $cur, valid for ${cur.validFor} at $currentPeriod")
794+
// not found, cur points to highest existing variant
795+
val nextTransformerId = ctx.base.nextDenotTransformerId(cur.validFor.lastPhaseId)
796+
if currentPeriod.lastPhaseId <= nextTransformerId then
797+
cur.validFor = Period(currentPeriod.runId, cur.validFor.firstPhaseId, nextTransformerId)
798+
else
799+
var startPid = nextTransformerId + 1
800+
val transformer = ctx.base.denotTransformers(nextTransformerId)
801+
//println(s"transforming $this with $transformer")
802+
val savedPeriod = ctx.period
803+
val mutCtx = ctx.asInstanceOf[FreshContext]
804+
try
805+
mutCtx.setPhase(transformer)
806+
next = transformer.transform(cur)
807+
// We temporarily update the context with the new phase instead of creating a
808+
// new one. This is done for performance. We cut down on about 30% of context
809+
// creations that way, and also avoid phase caches in contexts to get large.
810+
// To work correctly, we need to demand that the context with the new phase
811+
// is not retained in the result.
812+
catch case ex: CyclicReference =>
813+
signalError()
814+
throw ex
815+
finally
816+
mutCtx.setPeriod(savedPeriod)
817+
if next eq cur then
818+
startPid = cur.validFor.firstPhaseId
819+
else
820+
assertNotPackage(next, transformer)
821+
next.insertAfter(cur)
822+
cur = next
823+
cur.validFor = Period(currentPeriod.runId, startPid, transformer.lastPhaseId)
824+
//printPeriods(cur)
825+
//println(s"new denot: $cur, valid for ${cur.validFor}")
826+
cur.current // multiple transformations could be required
827+
end goForward
828+
829+
def goBack: SingleDenotation =
830+
// currentPeriod < end of valid; in this case a version must exist
831+
// but to be defensive we check for infinite loop anyway
832+
var cur = this
833+
var cnt = 0
834+
while !(cur.validFor contains currentPeriod) do
835+
//println(s"searching: $cur at $currentPeriod, valid for ${cur.validFor}")
836+
cur = cur.nextInRun
837+
// Note: One might be tempted to add a `prev` field to get to the new denotation
838+
// more directly here. I tried that, but it degrades rather than improves
839+
// performance: Test setup: Compile everything in dotc and immediate subdirectories
840+
// 10 times. Best out of 10: 18154ms with `prev` field, 17777ms without.
841+
cnt += 1
842+
if cnt > MaxPossiblePhaseId then
843+
return atPhase(coveredInterval.firstPhaseId)(current)
844+
cur
845+
end goBack
846+
847+
if valid.code <= 0 then
848+
// can happen if we sit on a stale denotation which has been replaced
849+
// wholesale by an installAfter; in this case, proceed to the next
850+
// denotation and try again.
851+
escapeToNext
852+
else if valid.runId != currentPeriod.runId then
853+
toNewRun
854+
else if currentPeriod.code > valid.code then
855+
goForward
856+
else
857+
goBack
858+
end current
843859

844860
private def demandOutsideDefinedMsg(using Context): String =
845861
s"demanding denotation of $this at phase ${ctx.phase}(${ctx.phaseId}) outside defined interval: defined periods are${definedPeriodsString}"
@@ -894,7 +910,7 @@ object Denotations {
894910

895911
/** Insert this denotation instead of `old`.
896912
* Also ensure that `old` refers with `nextInRun` to this denotation
897-
* and set its `validFor` field to `NoWhere`. This is necessary so that
913+
* and set its `validFor` field to `Nowhere`. This is necessary so that
898914
* references to the old denotation can be brought forward via `current`
899915
* to a valid denotation.
900916
*

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2351,8 +2351,11 @@ object SymDenotations {
23512351
if (denot.isOneOf(ValidForeverFlags) || denot.isRefinementClass || denot.isImport) true
23522352
else {
23532353
val initial = denot.initial
2354-
val firstPhaseId = initial.validFor.firstPhaseId.max(typerPhase.id)
2355-
if ((initial ne denot) || ctx.phaseId != firstPhaseId)
2354+
val firstPhaseId =
2355+
initial.validFor.firstPhaseId.max(typerPhase.id)
2356+
if firstPhaseId > ctx.lastPhaseId then
2357+
false
2358+
else if (initial ne denot) || ctx.phaseId != firstPhaseId then
23562359
atPhase(firstPhaseId)(stillValidInOwner(initial))
23572360
else
23582361
stillValidInOwner(denot)

0 commit comments

Comments
 (0)