Skip to content

Commit 191877a

Browse files
committed
Make heap as part of cache
Two motivations: First, it's close to the essence of its functionality. Second, it's easier to update the cache state in fixed-pointed computation.
1 parent fc76549 commit 191877a

File tree

1 file changed

+102
-109
lines changed

1 file changed

+102
-109
lines changed

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

Lines changed: 102 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -70,36 +70,6 @@ object Semantic {
7070
sealed abstract class Ref extends Value {
7171
def klass: ClassSymbol
7272
def outer: Value
73-
def objekt(using Heap): Objekt = heap(this)
74-
75-
def ensureObjectExists()(using Heap): this.type =
76-
if heap.contains(this) then this
77-
else ensureFresh()
78-
79-
def ensureFresh()(using Heap): this.type =
80-
val obj = Objekt(this.klass, fields = Map.empty, outers = Map(this.klass -> this.outer))
81-
heap.update(this, obj)
82-
this
83-
84-
/** Update field value of the abstract object
85-
*
86-
* Invariant: fields are immutable and only set once
87-
*/
88-
def updateField(field: Symbol, value: Value)(using Heap, Context): Unit =
89-
val obj = objekt
90-
assert(!obj.hasField(field), field.show + " already init, new = " + value + ", old = " + obj.field(field) + ", ref = " + this)
91-
val obj2 = obj.copy(fields = obj.fields.updated(field, value))
92-
heap.update(this, obj2)
93-
94-
/** Update the immediate outer of the given `klass` of the abstract object
95-
*
96-
* Invariant: outers are immutable and only set once
97-
*/
98-
def updateOuter(klass: ClassSymbol, value: Value)(using Heap, Context): Unit =
99-
val obj = objekt
100-
assert(!obj.hasOuter(klass), klass.show + " already has outer, new = " + value + ", old = " + obj.outer(klass) + ", ref = " + this)
101-
val obj2 = obj.copy(outers = obj.outers.updated(klass, value))
102-
heap.update(this, obj2)
10373
}
10474

10575
/** A reference to the object under initialization pointed by `this` */
@@ -122,21 +92,19 @@ object Semantic {
12292
*/
12393
def populateParams(): Contextual[this.type] = log("populating parameters", printer, (_: Warm).objekt.toString) {
12494
assert(!populatingParams, "the object is already populating parameters")
125-
// Somehow Dotty uses the one in the class parameters
12695
populatingParams = true
127-
given Heap = state.heap
12896
val tpl = klass.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template]
12997
this.callConstructor(ctor, args.map(arg => ArgInfo(arg, EmptyTree)), tpl)
13098
populatingParams = false
13199
this
132100
}
133101

134102
def ensureObjectExistsAndPopulated(): Contextual[this.type] =
135-
if heap.contains(this) then this
136-
else ensureFresh().populateParams()
103+
if cache.containsObject(this) then this
104+
else this.ensureFresh().populateParams()
137105

138106
def ensureObjectFreshAndPopulated(): Contextual[this.type] =
139-
ensureFresh().populateParams()
107+
this.ensureFresh().populateParams()
140108
}
141109

142110
/** A function value */
@@ -167,60 +135,6 @@ object Semantic {
167135
def hasField(f: Symbol) = fields.contains(f)
168136
}
169137

170-
/** Abstract heap stores abstract objects
171-
*
172-
* The heap serves as cache of summaries for warm objects and is shared for checking all classes.
173-
*
174-
* The fact that objects of `ThisRef` are stored in heap is just an engineering convenience.
175-
* Technically, we can also store the object directly in `ThisRef`.
176-
*/
177-
object Heap {
178-
class Heap(private var map: Map[Ref, Objekt]) {
179-
def contains(ref: Ref): Boolean = map.contains(ref)
180-
def apply(ref: Ref): Objekt = map(ref)
181-
def update(ref: Ref, obj: Objekt): Unit =
182-
map = map.updated(ref, obj)
183-
184-
def snapshot(): Heap = new Heap(map)
185-
186-
/** Recompute the newly created warm objects with the updated cache.
187-
*
188-
* The computation only covers class parameters and outers. Class fields are ignored and
189-
* are lazily evaluated and cached.
190-
*
191-
* The method must be called after the call `Cache.prepare()`.
192-
*/
193-
def prepare(heapBefore: Heap)(using State, Context) =
194-
this.map.keys.foreach {
195-
case warm: Warm =>
196-
if heapBefore.contains(warm) then
197-
assert(heapBefore(warm) == this(warm))
198-
else
199-
// We cannot simply remove the object, as the values in the
200-
// updated cache may refer to the warm object.
201-
given Env = Env.empty
202-
given Trace = Trace.empty
203-
given Promoted = Promoted.empty
204-
warm.ensureObjectFreshAndPopulated()
205-
case _ =>
206-
}
207-
208-
// ThisRef might be used in `populateParams`
209-
this.map.keys.foreach {
210-
case thisRef: ThisRef =>
211-
this.map = this.map - thisRef
212-
case _ =>
213-
}
214-
215-
override def toString() = map.toString()
216-
}
217-
}
218-
type Heap = Heap.Heap
219-
220-
inline def heap(using h: Heap): Heap = h
221-
222-
import Heap._
223-
224138
/** The environment for method parameters
225139
*
226140
* For performance and usability, we restrict parameters to be either `Cold`
@@ -327,13 +241,26 @@ object Semantic {
327241

328242
object Cache {
329243
opaque type CacheStore = mutable.Map[Value, EqHashMap[Tree, Value]]
244+
private type Heap = Map[Ref, Objekt]
330245

331246
class Cache {
332247
private val last: CacheStore = mutable.Map.empty
333248
private var current: CacheStore = mutable.Map.empty
334249
private val stable: CacheStore = mutable.Map.empty
335250
private var changed: Boolean = false
336251

252+
/** Abstract heap stores abstract objects
253+
*
254+
* The heap serves as cache of summaries for warm objects and is shared for checking all classes.
255+
*
256+
* The fact that objects of `ThisRef` are stored in heap is just an engineering convenience.
257+
* Technically, we can also store the object directly in `ThisRef`.
258+
*/
259+
private var heap: Heap = Map.empty
260+
261+
/** Used to easily revert heap changes. */
262+
private var heapBefore: Heap = Map.empty
263+
337264
def hasChanged = changed
338265

339266
def contains(value: Value, expr: Tree) =
@@ -370,27 +297,65 @@ object Semantic {
370297
actual
371298
end assume
372299

373-
/** Commit current cache to stable cache.
374-
*
375-
* TODO: It's useless to cache value for ThisRef.
376-
*/
377-
def commit() =
300+
/** Commit current cache to stable cache. */
301+
private def commitToStableCache() =
378302
current.foreach { (v, m) =>
379-
m.iterator.foreach { (e, res) =>
303+
// It's useless to cache value for ThisRef.
304+
if v.isWarm then m.iterator.foreach { (e, res) =>
380305
stable.put(v, e, res)
381306
}
382307
}
383308
current = mutable.Map.empty
384309

385310
/** Prepare cache for the next iteration
386311
*
387-
* - Reset changed flag
388-
* - Reset current cache (last cache already synced in `assume`)
312+
* 1. Reset changed flag
313+
*
314+
* 2. Reset current cache (last cache already synced in `assume`)
315+
*
316+
* 3. Recompute the newly created warm objects with the updated cache.
317+
*
318+
* The computation only covers class parameters and outers. Class
319+
* fields are ignored and are lazily evaluated and cached.
320+
*
321+
* This step should be after the first two steps so that the populated
322+
* parameter are re-computed from the updated input cache.
323+
*
389324
*/
390-
def prepare() = {
325+
def prepareForNextIteration(isStable: Boolean)(using State, Context) =
326+
if isStable then this.commitToStableCache()
327+
391328
changed = false
392329
current = mutable.Map.empty
393-
}
330+
331+
if !isStable then revertHeapChanges()
332+
heapBefore = this.heap
333+
334+
def revertHeapChanges()(using State, Context) =
335+
this.heap.keys.foreach {
336+
case warm: Warm =>
337+
if heapBefore.contains(warm) then
338+
this.heap = heap.updated(warm, heapBefore(warm))
339+
else
340+
// We cannot simply remove the object, as the values in the
341+
// updated cache may refer to the warm object.
342+
given Env = Env.empty
343+
given Trace = Trace.empty
344+
given Promoted = Promoted.empty
345+
warm.ensureObjectFreshAndPopulated()
346+
case _ =>
347+
}
348+
349+
// ThisRef objects are not reachable, thus it's fine to leave them in
350+
// the heap
351+
end revertHeapChanges
352+
353+
def updateObject(ref: Ref, obj: Objekt) =
354+
this.heap = this.heap.updated(ref, obj)
355+
356+
def containsObject(ref: Ref) = heap.contains(ref)
357+
358+
def getObject(ref: Ref) = heap(ref)
394359
}
395360

396361
extension (cache: CacheStore)
@@ -408,7 +373,6 @@ object Semantic {
408373

409374
inline def cache(using c: Cache): Cache = c
410375

411-
412376
/** Result of abstract interpretation */
413377
case class Result(value: Value, errors: Seq[Error]) {
414378
def show(using Context) = value.show + ", errors = " + errors.map(_.toString)
@@ -435,9 +399,8 @@ object Semantic {
435399

436400
// ----- State --------------------------------------------
437401
/** Global state of the checker */
438-
class State(val cache: Cache, val heap: Heap, val workList: WorkList)
402+
class State(val cache: Cache, val workList: WorkList)
439403

440-
given (using s: State): Heap = s.heap
441404
given (using s: State): Cache = s.cache
442405
given (using s: State): WorkList = s.workList
443406

@@ -496,6 +459,40 @@ object Semantic {
496459

497460
def widenArgs: List[Value] = values.map(_.widenArg).toList
498461

462+
463+
extension (ref: Ref)
464+
def objekt(using Cache): Objekt = cache.getObject(ref)
465+
466+
def ensureObjectExists()(using Cache): ref.type =
467+
if cache.containsObject(ref) then ref
468+
else ensureFresh()
469+
470+
def ensureFresh()(using Cache): ref.type =
471+
val obj = Objekt(ref.klass, fields = Map.empty, outers = Map(ref.klass -> ref.outer))
472+
cache.updateObject(ref, obj)
473+
ref
474+
475+
/** Update field value of the abstract object
476+
*
477+
* Invariant: fields are immutable and only set once
478+
*/
479+
def updateField(field: Symbol, value: Value)(using Cache, Context): Unit =
480+
val obj = objekt
481+
assert(!obj.hasField(field), field.show + " already init, new = " + value + ", old = " + obj.field(field) + ", ref = " + ref)
482+
val obj2 = obj.copy(fields = obj.fields.updated(field, value))
483+
cache.updateObject(ref, obj2)
484+
485+
/** Update the immediate outer of the given `klass` of the abstract object
486+
*
487+
* Invariant: outers are immutable and only set once
488+
*/
489+
def updateOuter(klass: ClassSymbol, value: Value)(using Cache, Context): Unit =
490+
val obj = objekt
491+
assert(!obj.hasOuter(klass), klass.show + " already has outer, new = " + value + ", old = " + obj.outer(klass) + ", ref = " + ref)
492+
val obj2 = obj.copy(outers = obj.outers.updated(klass, value))
493+
cache.updateObject(ref, obj2)
494+
end extension
495+
499496
extension (value: Value)
500497
def select(field: Symbol, source: Tree, needResolve: Boolean = true): Contextual[Result] = log("select " + field.show + ", this = " + value, printer, (_: Result).show) {
501498
if promoted.isCurrentObjectPromoted then Result(Hot, Nil)
@@ -913,18 +910,14 @@ object Semantic {
913910
checkedTasks = checkedTasks + task
914911

915912
task.value.ensureFresh()
916-
val heapBefore = heap.snapshot()
917913
val res = doTask(task)
918914
res.errors.foreach(_.issue)
919915

920916
if cache.hasChanged && res.errors.isEmpty then
921-
// must call cache.prepare() first
922-
cache.prepare()
923-
heap.prepare(heapBefore)
917+
cache.prepareForNextIteration(isStable = false)
924918
else
925-
cache.commit()
919+
cache.prepareForNextIteration(isStable = true)
926920
pendingTasks = rest
927-
cache.prepare()
928921

929922
work()
930923
case _ =>
@@ -968,7 +961,7 @@ object Semantic {
968961
* }
969962
*/
970963
def withInitialState[T](work: State ?=> T): T = {
971-
val initialState = State(new Cache, new Heap(Map.empty), new WorkList)
964+
val initialState = State(new Cache, new WorkList)
972965
work(using initialState)
973966
}
974967

0 commit comments

Comments
 (0)