@@ -168,44 +168,56 @@ object Semantic:
168
168
import Promoted .*
169
169
inline def promoted (using p : Promoted ): Promoted = p
170
170
171
- /** Interpreter configuration
171
+ /** Cache used in fixed point computation
172
172
*
173
- * The (abstract) interpreter can be seen as a push-down automaton
174
- * that transits between the configurations where the stack is the
175
- * implicit call stack of the meta-language.
173
+ * The analysis computes the least fixed point for the cache (see doc for
174
+ * `ExprValueCache`).
176
175
*
177
- * It's important that the configuration is finite for the analysis
178
- * to terminate.
176
+ * For the fixed point computation to terminate, we need to make sure that
177
+ * the domain of the cache, i.e. the key pair (Ref, Tree) is finite. As the
178
+ * code is finite, we only need to carefully design the abstract domain to
179
+ * be finitary.
179
180
*
180
- * For soundness, we need to compute fixed point of the cache, which
181
- * maps configuration to evaluation result.
181
+ * We also need to make sure that the computing function (i.e. the abstract
182
+ * interpreter) is monotone. Error handling breaks monotonicity of the
183
+ * abstract interpreter, because when an error happens, we always return
184
+ * the bottom value `Hot` for an expression. It is not a threat for
185
+ * termination because when an error happens, we stop the fixed point
186
+ * computation at the end of the iteration where the error happens.
182
187
*
183
- * Thanks to heap monotonicity, heap is not part of the configuration.
184
- *
185
- * This class is only used for the purpose of documentation.
186
- */
187
- case class Config (thisV : Value , expr : Tree )
188
-
189
- /** Cache used to terminate the analysis
190
- *
191
- * A finitary configuration is not enough for the analysis to
192
- * terminate. We need to use cache to let the interpreter "know"
193
- * that it can terminate.
194
- *
195
- * For performance reasons we use curried key.
196
- *
197
- * Note: It's tempting to use location of trees as key. That should
198
- * be avoided as a template may have the same location as its single
199
- * statement body. Macros may also create incorrect locations.
188
+ * Note: It's tempting to use location of trees as key. That should
189
+ * be avoided as a template may have the same location as its single
190
+ * statement body. Macros may also create incorrect locations.
200
191
*/
201
-
202
192
object Cache :
203
193
/** Cache for expressions
204
194
*
205
195
* Ref -> Tree -> Value
206
196
*
207
197
* The first key is the value of `this` for the expression. We do not need
208
- * environment, because the environment is always hot.
198
+ * environment in the key, because the environment is always hot.
199
+ *
200
+ * We do not need the heap in the key, because the value of an expression
201
+ * is only determined by the value of `this`. The heap is immutable: the
202
+ * abstract values for object fields never change within one iteration.
203
+ * The initial abstraction of a field is always a safe over-approximation
204
+ * thanks to monotonicity of initialization states.
205
+ *
206
+ * If the heap is unstable in an iteration, the cache should also be
207
+ * unstable. This is because all values stored in the heap are also present
208
+ * in the cache. Therefore, we only need to track whether the cache is
209
+ * stable between two iterations.
210
+ *
211
+ * The heap is not part of the fixed point computation -- we throw the
212
+ * unstable heap from last iteration away. In contrast, we use the unstable
213
+ * output cache from the last iteration as input for the next iteration.
214
+ * This is safe because the heap is determined by the cache -- it is a
215
+ * "local" data to the computing function, conceptually. Local data is
216
+ * always safe be discarded.
217
+ *
218
+ * Now, if a fixed point is reached, the local data contains stable data
219
+ * that could be reused to check other classes. We employ this trick to
220
+ * improve performance of the analysis.
209
221
*/
210
222
private type ExprValueCache = Map [Value , Map [TreeWrapper , Value ]]
211
223
@@ -323,7 +335,7 @@ object Semantic:
323
335
*
324
336
* It assumes the value is `Hot` if it doesn't exist in the last cache.
325
337
*
326
- * It update the current caches if the values change.
338
+ * It updates the current caches if the values change.
327
339
*
328
340
* The two caches are required because we want to make sure in a new iteration, an expression is evaluated once.
329
341
*/
0 commit comments