@@ -70,36 +70,6 @@ object Semantic {
70
70
sealed abstract class Ref extends Value {
71
71
def klass : ClassSymbol
72
72
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)
103
73
}
104
74
105
75
/** A reference to the object under initialization pointed by `this` */
@@ -122,21 +92,19 @@ object Semantic {
122
92
*/
123
93
def populateParams (): Contextual [this .type ] = log(" populating parameters" , printer, (_ : Warm ).objekt.toString) {
124
94
assert(! populatingParams, " the object is already populating parameters" )
125
- // Somehow Dotty uses the one in the class parameters
126
95
populatingParams = true
127
- given Heap = state.heap
128
96
val tpl = klass.defTree.asInstanceOf [TypeDef ].rhs.asInstanceOf [Template ]
129
97
this .callConstructor(ctor, args.map(arg => ArgInfo (arg, EmptyTree )), tpl)
130
98
populatingParams = false
131
99
this
132
100
}
133
101
134
102
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()
137
105
138
106
def ensureObjectFreshAndPopulated (): Contextual [this .type ] =
139
- ensureFresh().populateParams()
107
+ this . ensureFresh().populateParams()
140
108
}
141
109
142
110
/** A function value */
@@ -167,60 +135,6 @@ object Semantic {
167
135
def hasField (f : Symbol ) = fields.contains(f)
168
136
}
169
137
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
-
224
138
/** The environment for method parameters
225
139
*
226
140
* For performance and usability, we restrict parameters to be either `Cold`
@@ -327,13 +241,26 @@ object Semantic {
327
241
328
242
object Cache {
329
243
opaque type CacheStore = mutable.Map [Value , EqHashMap [Tree , Value ]]
244
+ private type Heap = Map [Ref , Objekt ]
330
245
331
246
class Cache {
332
247
private val last : CacheStore = mutable.Map .empty
333
248
private var current : CacheStore = mutable.Map .empty
334
249
private val stable : CacheStore = mutable.Map .empty
335
250
private var changed : Boolean = false
336
251
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
+
337
264
def hasChanged = changed
338
265
339
266
def contains (value : Value , expr : Tree ) =
@@ -370,27 +297,65 @@ object Semantic {
370
297
actual
371
298
end assume
372
299
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 () =
378
302
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) =>
380
305
stable.put(v, e, res)
381
306
}
382
307
}
383
308
current = mutable.Map .empty
384
309
385
310
/** Prepare cache for the next iteration
386
311
*
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
+ *
389
324
*/
390
- def prepare () = {
325
+ def prepareForNextIteration (isStable : Boolean )(using State , Context ) =
326
+ if isStable then this .commitToStableCache()
327
+
391
328
changed = false
392
329
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)
394
359
}
395
360
396
361
extension (cache : CacheStore )
@@ -408,7 +373,6 @@ object Semantic {
408
373
409
374
inline def cache (using c : Cache ): Cache = c
410
375
411
-
412
376
/** Result of abstract interpretation */
413
377
case class Result (value : Value , errors : Seq [Error ]) {
414
378
def show (using Context ) = value.show + " , errors = " + errors.map(_.toString)
@@ -435,9 +399,8 @@ object Semantic {
435
399
436
400
// ----- State --------------------------------------------
437
401
/** 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 )
439
403
440
- given (using s : State ): Heap = s.heap
441
404
given (using s : State ): Cache = s.cache
442
405
given (using s : State ): WorkList = s.workList
443
406
@@ -496,6 +459,40 @@ object Semantic {
496
459
497
460
def widenArgs : List [Value ] = values.map(_.widenArg).toList
498
461
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
+
499
496
extension (value : Value )
500
497
def select (field : Symbol , source : Tree , needResolve : Boolean = true ): Contextual [Result ] = log(" select " + field.show + " , this = " + value, printer, (_ : Result ).show) {
501
498
if promoted.isCurrentObjectPromoted then Result (Hot , Nil )
@@ -913,18 +910,14 @@ object Semantic {
913
910
checkedTasks = checkedTasks + task
914
911
915
912
task.value.ensureFresh()
916
- val heapBefore = heap.snapshot()
917
913
val res = doTask(task)
918
914
res.errors.foreach(_.issue)
919
915
920
916
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 )
924
918
else
925
- cache.commit( )
919
+ cache.prepareForNextIteration(isStable = true )
926
920
pendingTasks = rest
927
- cache.prepare()
928
921
929
922
work()
930
923
case _ =>
@@ -968,7 +961,7 @@ object Semantic {
968
961
* }
969
962
*/
970
963
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 )
972
965
work(using initialState)
973
966
}
974
967
0 commit comments