Skip to content

Commit d866916

Browse files
committed
Optimize replace using reverse dependencies
1 parent 7bda186 commit d866916

File tree

2 files changed

+66
-42
lines changed

2 files changed

+66
-42
lines changed

compiler/src/dotty/tools/dotc/config/Config.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ object Config {
2222
*/
2323
inline val checkConstraintsNonCyclic = false
2424

25+
/** Check that reverse dependencies in constraints are correct and complete.
26+
* Can also be enabled using -Ycheck-constraint-deps.
27+
*/
2528
inline val checkConstraintDeps = false
2629

2730
/** Check that each constraint resulting from a subtype test

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

Lines changed: 63 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,16 @@ import cc.{CapturingType, derivedCapturingType}
1818

1919
object OrderingConstraint {
2020

21-
@sharable private var id = 0
22-
private def nextId =
23-
id += 1
24-
id
21+
/** If true, use reverse dependencies in `replace` to avoid checking the bounds
22+
* of all parameters in the constraint. This can speed things up, but there are some
23+
* rare corner cases where reverse dependencies miss a parameter. Specifically,
24+
* if a constraint contains a free reference to TypeParam P and afterwards the
25+
* same P is added as a bound variable to the constraint, a backwards link would
26+
* then become necessary at this point but is missing. This causes two CB projects
27+
* to fail when reverse dependencies are checked (parboiled2 and perspective).
28+
* In these rare cases `replace` would behave differently when optimized.
29+
*/
30+
final val optimizeReplace = true
2531

2632
type ArrayValuedMap[T] = SimpleIdentityMap[TypeLambda, Array[T]]
2733

@@ -145,10 +151,6 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
145151

146152
type This = OrderingConstraint
147153

148-
var id = nextId
149-
//if id == 118 then
150-
// new Error(s"at $id").printStackTrace()
151-
152154
/** A new constraint with given maps and given set of hard typevars */
153155
def newConstraint( // !!! Dotty problem: Making newConstraint `private` causes -Ytest-pickler failure.
154156
boundsMap: ParamBounds = this.boundsMap,
@@ -294,7 +296,9 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
294296

295297
def update(deps: ReverseDeps, referenced: TypeParamRef): ReverseDeps =
296298
val prev = deps.at(referenced)
297-
deps.updated(referenced, if add then prev + srcParam else prev - srcParam)
299+
val newSet = if add then prev + srcParam else prev - srcParam
300+
if newSet.isEmpty then deps.remove(referenced)
301+
else deps.updated(referenced, newSet)
298302

299303
def traverse(t: Type) = t match
300304
case param: TypeParamRef =>
@@ -651,41 +655,58 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
651655

652656
var current = this
653657

654-
def removeParam(ps: List[TypeParamRef]) = ps.filterConserve(param ne _)
658+
def removeParamFrom(ps: List[TypeParamRef]) =
659+
ps.filterConserve(param ne _)
660+
655661
for lo <- lower(param) do
656-
current = upperLens.map(this, current, lo, removeParam)
662+
current = upperLens.map(this, current, lo, removeParamFrom)
657663
for hi <- upper(param) do
658-
current = lowerLens.map(this, current, hi, removeParam)
659-
660-
current.foreachParam { (p, i) =>
661-
val other = p.paramRefs(i)
662-
if other != param then
663-
val oldEntry = current.entry(other)
664-
val newEntry = current.ensureNonCyclic(other, oldEntry.substParam(param, replacement))
665-
current = boundsLens.update(this, current, other, newEntry)
666-
var oldDepEntry = oldEntry
667-
var newDepEntry = newEntry
668-
replacedTypeVar match
669-
case tvar: TypeVar =>
670-
if tvar.isInstantiated
671-
then
672-
// replace is called from TypeVar's instantiateWith,
673-
// forget about instantiation for old dependencies
674-
oldDepEntry = mapReplacedTypeVarTo(param)(oldDepEntry)
675-
else
676-
// replace is called from unify,
677-
// assume parameter has been replaced for new dependencies
678-
// (the actual replacement is done below)
679-
newDepEntry = mapReplacedTypeVarTo(replacement)(newDepEntry)
680-
case _ =>
681-
if oldDepEntry ne newDepEntry then
682-
if current eq this then
683-
// We can end up here if oldEntry eq newEntry, so posssibly no new constraint
684-
// was created, but oldDepEntry ne newDepEntry. In that case we must make
685-
// sure we have a new constraint before updating dependencies.
686-
current = newConstraint()
687-
current.adjustDeps(newDepEntry, oldDepEntry, other)
688-
}
664+
current = lowerLens.map(this, current, hi, removeParamFrom)
665+
666+
def replaceParamIn(other: TypeParamRef) =
667+
val oldEntry = current.entry(other)
668+
val newEntry = current.ensureNonCyclic(other, oldEntry.substParam(param, replacement))
669+
current = boundsLens.update(this, current, other, newEntry)
670+
var oldDepEntry = oldEntry
671+
var newDepEntry = newEntry
672+
replacedTypeVar match
673+
case tvar: TypeVar =>
674+
if tvar.isInstantiated
675+
then
676+
// replace is called from TypeVar's instantiateWith,
677+
// forget about instantiation for old dependencies
678+
oldDepEntry = mapReplacedTypeVarTo(param)(oldDepEntry)
679+
else
680+
// replace is called from unify,
681+
// assume parameter has been replaced for new dependencies
682+
// (the actual replacement is done below)
683+
newDepEntry = mapReplacedTypeVarTo(replacement)(newDepEntry)
684+
case _ =>
685+
if oldDepEntry ne newDepEntry then
686+
if current eq this then
687+
// We can end up here if oldEntry eq newEntry, so posssibly no new constraint
688+
// was created, but oldDepEntry ne newDepEntry. In that case we must make
689+
// sure we have a new constraint before updating dependencies.
690+
current = newConstraint()
691+
current.adjustDeps(newDepEntry, oldDepEntry, other)
692+
end replaceParamIn
693+
694+
if optimizeReplace then
695+
val co = current.coDeps.at(param)
696+
val contra = current.contraDeps.at(param)
697+
current.foreachParam { (p, i) =>
698+
val other = p.paramRefs(i)
699+
entry(other) match
700+
case _: TypeBounds =>
701+
if co.contains(other) || contra.contains(other) then
702+
replaceParamIn(other)
703+
case _ => replaceParamIn(other)
704+
}
705+
else
706+
current.foreachParam { (p, i) =>
707+
val other = p.paramRefs(i)
708+
if other != param then replaceParamIn(other)
709+
}
689710

690711
current =
691712
if isRemovable(param.binder) then current.remove(param.binder)

0 commit comments

Comments
 (0)