Skip to content

Commit 6b49b26

Browse files
committed
Refactor cache to use immutable maps
1 parent 0edd835 commit 6b49b26

File tree

1 file changed

+76
-29
lines changed

1 file changed

+76
-29
lines changed

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

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import StdNames.*
1010
import NameKinds.OuterSelectName
1111

1212
import ast.tpd.*
13-
import util.EqHashMap
1413
import config.Printers.init as printer
1514
import reporting.trace as log
1615

@@ -198,17 +197,51 @@ object Semantic:
198197
* Note: It's tempting to use location of trees as key. That should
199198
* be avoided as a template may have the same location as its single
200199
* statement body. Macros may also create incorrect locations.
201-
*
202200
*/
203201

204202
object Cache:
205-
opaque type CacheStore = mutable.Map[Value, EqHashMap[Tree, Value]]
203+
/** Cache for expressions
204+
*
205+
* Ref -> Tree -> Value
206+
*
207+
* The first key is the value of `this` for the expression. We do not need
208+
* environment, because the environment is always hot.
209+
*/
210+
private type ExprValueCache = Map[Value, Map[TreeWrapper, Value]]
211+
212+
/** The heap for abstract objects
213+
*
214+
* The heap objects are immutable.
215+
*/
206216
private type Heap = Map[Ref, Objekt]
207217

218+
/** A wrapper for trees for storage in maps based on referential equality of trees. */
219+
private abstract class TreeWrapper:
220+
def tree: Tree
221+
222+
override final def equals(other: Any): Boolean =
223+
other match
224+
case that: TreeWrapper => this.tree eq that.tree
225+
case _ => false
226+
227+
override final def hashCode = tree.hashCode
228+
229+
/** The immutable wrapper is intended to be stored as key in the heap. */
230+
private class ImmutableTreeWrapper(val tree: Tree) extends TreeWrapper
231+
232+
/** For queries on the heap, reuse the same wrapper to avoid unnecessary allocation. */
233+
private class MutableTreeWrapper extends TreeWrapper:
234+
var queryTree: Tree | Null = null
235+
def tree: Tree = queryTree match
236+
case tree: Tree => tree
237+
case null => ???
238+
239+
private val queryTreeMapper: MutableTreeWrapper = new MutableTreeWrapper
240+
208241
class Cache:
209-
private var last: CacheStore = mutable.Map.empty
210-
private var current: CacheStore = mutable.Map.empty
211-
private val stable: CacheStore = mutable.Map.empty
242+
private var last: ExprValueCache = Map.empty
243+
private var current: ExprValueCache = Map.empty
244+
private var stable: ExprValueCache = Map.empty
212245
private var changed: Boolean = false
213246

214247
/** Abstract heap stores abstract objects
@@ -243,8 +276,8 @@ object Semantic:
243276
current.contains(value, expr) || stable.contains(value, expr)
244277

245278
def apply(value: Value, expr: Tree) =
246-
if current.contains(value, expr) then current(value)(expr)
247-
else stable(value)(expr)
279+
if current.contains(value, expr) then current.get(value, expr)
280+
else stable.get(value, expr)
248281

249282
/** Copy the value of `(value, expr)` from the last cache to the current cache
250283
* (assuming it's `Hot` if it doesn't exist in the cache).
@@ -256,16 +289,16 @@ object Semantic:
256289
if last.contains(value, expr) then
257290
last.get(value, expr)
258291
else
259-
last.put(value, expr, Hot)
292+
this.last = last.updatedNested(value, expr, Hot)
260293
Hot
261294
end if
262-
current.put(value, expr, assumeValue)
295+
this.current = current.updatedNested(value, expr, assumeValue)
263296

264297
val actual = fun
265298
if actual != assumeValue then
266299
this.changed = true
267-
last.put(value, expr, actual)
268-
current.put(value, expr, actual)
300+
this.last = this.last.updatedNested(value, expr, actual)
301+
this.current = this.current.updatedNested(value, expr, actual)
269302
else
270303
// It's tempting to cache the value in stable, but it's unsound.
271304
// The reason is that the current value may depend on other values
@@ -280,12 +313,12 @@ object Semantic:
280313

281314
/** Commit current cache to stable cache. */
282315
private def commitToStableCache() =
283-
current.foreach { (v, m) =>
284-
// It's useless to cache value for ThisRef.
285-
if v.isWarm then m.iterator.foreach { (e, res) =>
286-
stable.put(v, e, res)
287-
}
288-
}
316+
for
317+
(v, m) <- current
318+
if v.isWarm // It's useless to cache value for ThisRef.
319+
(wrapper, res) <- m
320+
do
321+
this.stable = stable.updatedNestedWrapper(v, wrapper.asInstanceOf[ImmutableTreeWrapper], res)
289322

290323
/** Prepare cache for the next iteration
291324
*
@@ -298,7 +331,7 @@ object Semantic:
298331
*/
299332
def prepareForNextIteration()(using Context) =
300333
this.changed = false
301-
this.current = mutable.Map.empty
334+
this.current = Map.empty
302335
this.heap = this.heapStable
303336

304337
/** Prepare for checking next class
@@ -319,8 +352,8 @@ object Semantic:
319352
this.commitToStableCache()
320353
this.heapStable = this.heap
321354

322-
this.last = mutable.Map.empty
323-
this.current = mutable.Map.empty
355+
this.last = Map.empty
356+
this.current = Map.empty
324357

325358
def updateObject(ref: Ref, obj: Objekt) =
326359
assert(!this.heapStable.contains(ref))
@@ -331,14 +364,28 @@ object Semantic:
331364
def getObject(ref: Ref) = heap(ref)
332365
end Cache
333366

334-
extension (cache: CacheStore)
335-
def contains(value: Value, expr: Tree) = cache.contains(value) && cache(value).contains(expr)
336-
def get(value: Value, expr: Tree): Value = cache(value)(expr)
337-
def remove(value: Value, expr: Tree) = cache(value).remove(expr)
338-
def put(value: Value, expr: Tree, result: Value): Unit = {
339-
val innerMap = cache.getOrElseUpdate(value, new EqHashMap[Tree, Value])
340-
innerMap(expr) = result
341-
}
367+
extension (cache: ExprValueCache)
368+
private def contains(value: Value, expr: Tree) =
369+
queryTreeMapper.queryTree = expr
370+
cache.contains(value) && cache(value).contains(queryTreeMapper)
371+
372+
private def get(value: Value, expr: Tree): Value =
373+
queryTreeMapper.queryTree = expr
374+
cache(value)(queryTreeMapper)
375+
376+
private def removed(value: Value, expr: Tree) =
377+
queryTreeMapper.queryTree = expr
378+
val innerMap2 = cache(value).removed(queryTreeMapper)
379+
cache.updated(value, innerMap2)
380+
381+
private def updatedNested(value: Value, expr: Tree, result: Value): ExprValueCache =
382+
val wrapper = new ImmutableTreeWrapper(expr)
383+
updatedNestedWrapper(value, wrapper, result)
384+
385+
private def updatedNestedWrapper(value: Value, wrapper: ImmutableTreeWrapper, result: Value): ExprValueCache =
386+
val innerMap = cache.getOrElse(value, Map.empty[TreeWrapper, Value])
387+
val innerMap2 = innerMap.updated(wrapper, result)
388+
cache.updated(value, innerMap2)
342389
end extension
343390
end Cache
344391

0 commit comments

Comments
 (0)