Skip to content

Commit faedca8

Browse files
authored
Cache substitutions (#954)
Substitutions are used multiple times but computed over and over again. This PR caches them until any modification happens. On my machine, this brings down typer from `1150ms` to `720ms` with ``` effekt --time text examples/casestudies/anf.effekt.md ``` This shaves of 25% of the total compile time, which is reduced from 2.5s to 2s.
1 parent 4bf7668 commit faedca8

File tree

2 files changed

+17
-11
lines changed

2 files changed

+17
-11
lines changed

effekt/shared/src/main/scala/effekt/typer/Constraints.scala

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,25 @@ class Constraints(
121121
* Unification variables which are not in scope anymore, but also haven't been solved, yet.
122122
*/
123123
private var pendingInactive: Set[CNode] = Set.empty
124-
125124
)(using C: ErrorReporter) {
126125

126+
/**
127+
* Caches type substitutions, which are only invalidated if the mapping from node to typevar changes.
128+
*
129+
* This significantly improves the performance of Typer (see https://github.com/effekt-lang/effekt/pull/954)
130+
*/
131+
private var _typeSubstitution: Map[TypeVar, ValueType] = _
132+
private def invalidate(): Unit = _typeSubstitution = null
133+
127134
/**
128135
* The currently known substitutions
129136
*/
130137
def subst: Substitutions =
131-
val types = classes.flatMap[TypeVar, ValueType] { case (k, v) => typeSubstitution.get(v).map { k -> _ } }
138+
if _typeSubstitution == null then {
139+
_typeSubstitution = classes.flatMap[TypeVar, ValueType] { case (k, v) => typeSubstitution.get(v).map { k -> _ } }
140+
}
132141
val captures = captSubstitution.asInstanceOf[Map[CaptVar, Captures]]
133-
Substitutions(types, captures)
142+
Substitutions(_typeSubstitution, captures)
134143

135144
/**
136145
* Should only be called on unification variables where we do not know any types, yet
@@ -308,16 +317,18 @@ class Constraints(
308317
private [typer] def upperNodes: Map[CNode, Filter] = getData(x).upperNodes
309318
private def lower_=(bounds: Set[Capture]): Unit =
310319
captureConstraints = captureConstraints.updated(x, getData(x).copy(lower = Some(bounds)))
320+
311321
private def upper_=(bounds: Set[Capture]): Unit =
312322
captureConstraints = captureConstraints.updated(x, getData(x).copy(upper = Some(bounds)))
323+
313324
private def addLower(other: CNode, exclude: Filter): Unit =
314325
val oldData = getData(x)
315326

316327
// compute the intersection of filters
317328
val oldFilter = oldData.lowerNodes.get(other)
318329
val newFilter = oldFilter.map { _ intersect exclude }.getOrElse { exclude }
319-
320330
captureConstraints = captureConstraints.updated(x, oldData.copy(lowerNodes = oldData.lowerNodes + (other -> newFilter)))
331+
321332
private def addUpper(other: CNode, exclude: Filter): Unit =
322333
val oldData = getData(x)
323334

@@ -462,6 +473,7 @@ class Constraints(
462473
*/
463474
private def updateSubstitution(): Unit =
464475
val substitution = subst
476+
invalidate()
465477
typeSubstitution = typeSubstitution.map { case (node, tpe) => node -> substitution.substitute(tpe) }
466478

467479
private def getNode(x: UnificationVar): Node =

effekt/shared/src/main/scala/effekt/typer/Substitution.scala

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ case class Substitutions(
3131
}
3232
def get(x: CaptUnificationVar): Option[Captures] = captures.get(x)
3333

34-
// amounts to first substituting this, then other
35-
def updateWith(other: Substitutions): Substitutions =
36-
Substitutions(
37-
values.view.mapValues { t => other.substitute(t) }.toMap,
38-
captures.view.mapValues { t => other.substitute(t) }.toMap) ++ other
39-
4034
// amounts to parallel substitution
4135
def ++(other: Substitutions): Substitutions = Substitutions(values ++ other.values, captures ++ other.captures)
4236

@@ -95,4 +89,4 @@ object Substitutions {
9589
def apply(values: List[(TypeVar, ValueType)], captures: List[(CaptVar, Captures)]): Substitutions = Substitutions(values.toMap, captures.toMap)
9690
def types(keys: List[TypeVar], values: List[ValueType]): Substitutions = Substitutions((keys zip values).toMap, Map.empty)
9791
def captures(keys: List[CaptVar], values: List[Captures]): Substitutions = Substitutions(Map.empty, (keys zip values).toMap)
98-
}
92+
}

0 commit comments

Comments
 (0)