Cache

dotty.tools.dotc.transform.init.Cache
See theCache companion object
class Cache[Config, Res]

The co-inductive cache used for analysis

The cache contains two maps from (Config, Tree) to Res:

  • input cache (this.last)
  • output cache (this.current)

The two caches are required because we want to make sure in a new iteration, an expression is evaluated exactly once. The monotonicity of the analysis ensures that the cache state goes up the lattice of the abstract domain, consequently the algorithm terminates.

The general skeleton for usage of the cache is as follows

def analysis(entryExp: Expr) = {
  def iterate(entryExp: Expr)(using Cache) =
     eval(entryExp, initConfig)
     if cache.hasChanged && noErrors then
       cache.last = cache.current
       cache.current = Empty
       cache.changed = false
       iterate(entryExp)
     else
       reportErrors


  def eval(expr: Expr, config: Config)(using Cache) =
    cache.cachedEval(config, expr) {
      // Actual recursive evaluation of expression.
      //
      // Only executed if the entry `(exp, config)` is not in the output cache.
    }

  iterate(entryExp)(using new Cache)
}

See the documentation for the method Cache.cachedEval for more information.

What goes to the configuration (Config) and what goes to the result (Res) need to be decided by the specific analysis and justified by reasoning about soundness.

Type parameters

Config

The analysis state that matters for evaluating an expression.

Res

The result from the evaluation the given expression.

Attributes

Companion
object
Graph
Supertypes
class Object
trait Matchable
class Any
Known subtypes
class Data
class Data

Members list

Value members

Concrete methods

def cachedEval(config: Config, expr: Tree, cacheResult: Boolean, default: Res)(eval: Tree => Res): Res

Evaluate an expression with cache

Evaluate an expression with cache

The algorithmic skeleton is as follows:

if don't cache result then
  return eval(expr)
if this.current.contains(config, expr) then
  return cached value
else
  val assumed = this.last(config, expr) or bottom value if absent
  this.current(config, expr) = assumed
  val actual = eval(expr)

  if assumed != actual then
    this.changed = true
    this.current(config, expr) = actual

Attributes

def get(config: Config, expr: Tree): Option[Res]

Prepare cache for the next iteration

Prepare cache for the next iteration

  1. Reset changed flag.

  2. Use current cache as last cache and set current cache to be empty.

Attributes