@@ -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