Skip to content

Commit d3383f6

Browse files
committed
Refactor cache: don't use location as cache key
1 parent 7717c5b commit d3383f6

File tree

1 file changed

+15
-11
lines changed

1 file changed

+15
-11
lines changed

compiler/src/dotty/tools/dotc/transform/init/Semantic.scala

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Types._
99
import StdNames._
1010

1111
import ast.tpd._
12-
import util.SourcePosition
12+
import util.EqHashMap
1313
import config.Printers.init as printer
1414
import reporting.trace as log
1515

@@ -22,9 +22,6 @@ class Semantic {
2222

2323
// ----- Domain definitions --------------------------------
2424

25-
/** Locations are finite for any givn program */
26-
type Loc = SourcePosition
27-
2825
/** Abstract values
2926
*
3027
* Value = Hot | Cold | Warm | ThisRef | Fun | RefSet
@@ -79,16 +76,23 @@ class Semantic {
7976
* Which also avoid computing fix-point on the cache, as the cache is
8077
* immutable.
8178
*/
82-
case class Config(thisV: Value, loc: Loc)
79+
case class Config(thisV: Value, expr: Tree)
8380

8481
/** Cache used to terminate the analysis
8582
*
8683
* A finitary configuration is not enough for the analysis to
8784
* terminate. We need to use cache to let the interpreter "know"
8885
* that it can terminate.
86+
*
87+
* For performance reasons we use curried key.
88+
*
89+
* Note: It's tempting to use location of trees as key. That should
90+
* be avoided as a template may have the same location as its single
91+
* statement body. Macros may also create incorrect locations.
92+
*
8993
*/
90-
type Cache = mutable.Map[Config, Value]
91-
val cache: Cache = mutable.Map.empty[Config, Value]
94+
type Cache = mutable.Map[Value, EqHashMap[Tree, Value]]
95+
val cache: Cache = mutable.Map.empty[Value, EqHashMap[Tree, Value]]
9296

9397
/** Result of abstract interpretation */
9498
case class Result(value: Value, errors: Seq[Error]) {
@@ -346,16 +350,16 @@ class Semantic {
346350
* This method only handles cache logic and delegates the work to `cases`.
347351
*/
348352
def eval(expr: Tree, thisV: Value, klass: ClassSymbol, cacheResult: Boolean = false)(using Context, Trace): Result = log("evaluating " + expr.show + ", this = " + thisV.show, printer, res => res.asInstanceOf[Result].show) {
349-
val cfg = Config(thisV, expr.sourcePos)
350-
if (cache.contains(cfg)) Result(cache(cfg), noErrors)
353+
val innerMap = cache.getOrElseUpdate(thisV, new EqHashMap[Tree, Value])
354+
if (innerMap.contains(expr)) Result(innerMap(expr), noErrors)
351355
else {
352356
// no need to compute fix-point, because
353357
// 1. the result is decided by `cfg` for a legal program
354358
// (heap change is irrelevant thanks to monotonicity)
355359
// 2. errors will have been reported for an illegal program
356-
if cacheResult then cache(cfg) = Hot
360+
innerMap(expr) = Hot
357361
val res = cases(expr, thisV, klass)
358-
if cacheResult then cache(cfg) = res.value
362+
if cacheResult then innerMap(expr) = res.value else innerMap.remove(expr)
359363
res
360364
}
361365
}

0 commit comments

Comments
 (0)