///|
pub struct HotCacheEntry[T] {
  built_at_revision : Int32
  value : T
} derive(Eq, Debug)

///|
pub struct HotCfgOrders {
  preorder : Array[BlockId]
  postorder : Array[BlockId]
  reverse_postorder : Array[BlockId]
  reverse_postorder_with_exceptions : Array[BlockId]
  reverse_exit_postorder : Array[BlockId]
  reverse_exit_postorder_with_exceptions : Array[BlockId]
  worklist_seed : Array[BlockId]
} derive(Eq, Debug)

///|
pub struct HotEffectsSummary {
  node_masks : Array[EffectMask]
  block_masks : Array[EffectMask]
  root_region_mask : EffectMask
} derive(Eq, Debug)

///|
pub struct HotAnalysisCache {
  // Every owned HotFunc has one stable root array. Use its physical identity,
  // without retaining the whole function or comparing its mutable contents.
  mut owner_roots : Array[NodeId]?
  mut cfg : HotCacheEntry[HotCfg]?
  mut orders : HotCacheEntry[HotCfgOrders]?
  mut dom : HotCacheEntry[HotDominators]?
  mut postdom : HotCacheEntry[HotPostDominators]?
  mut loop_info : HotCacheEntry[HotLoopInfo]?
  mut use_def : HotCacheEntry[HotUseDef]?
  mut liveness : HotCacheEntry[HotLiveness]?
  mut effects : HotCacheEntry[HotEffectsSummary]?
  mut ssa : HotCacheEntry[HotLocalSsa]?
} derive(Eq, Debug)

///|
pub fn HotAnalysisCache::new() -> HotAnalysisCache {
  {
    owner_roots: None,
    cfg: None,
    orders: None,
    dom: None,
    postdom: None,
    loop_info: None,
    use_def: None,
    liveness: None,
    effects: None,
    ssa: None,
  }
}

///|
fn[T] cache_entry_is_current(
  entry : HotCacheEntry[T]?,
  revision : Int32,
) -> Bool {
  match entry {
    Some(item) => item.built_at_revision == revision
    None => false
  }
}

///|
fn[T] cache_entry_value(entry : HotCacheEntry[T]?) -> T {
  match entry {
    Some(item) => item.value
    None => abort("analysis cache entry must exist before reading its value")
  }
}

///|
fn cache_effects_build(func : HotFunc, cfg : HotCfg) -> HotEffectsSummary {
  let node_masks = effects_build_node_masks(func)
  let block_masks : Array[EffectMask] = []
  for block_id = 0; block_id < cfg_block_count(cfg); block_id = block_id + 1 {
    block_masks.push(
      effects_for_block_with_node_masks(cfg, block_id, node_masks),
    )
  }
  {
    node_masks,
    block_masks,
    root_region_mask: effects_for_region_with_node_masks(
      func,
      HotRegionRef::root(),
      node_masks,
    ),
  }
}

///|
pub fn cache_invalidate_all(cache : HotAnalysisCache) -> Unit {
  cache.owner_roots = None
  cache.cfg = None
  cache.orders = None
  cache.dom = None
  cache.postdom = None
  cache.loop_info = None
  cache.use_def = None
  cache.liveness = None
  cache.effects = None
  cache.ssa = None
}

///|
pub fn cache_invalidate_analyses(
  cache : HotAnalysisCache,
  analyses : Array[HotAnalysis],
) -> Unit {
  for analysis in analyses {
    match analysis {
      HotAnalysis::Cfg => {
        cache.cfg = None
        cache.orders = None
        cache.dom = None
        cache.postdom = None
        cache.loop_info = None
        cache.use_def = None
        cache.liveness = None
        cache.effects = None
        cache.ssa = None
      }
      HotAnalysis::Dominance => {
        cache.dom = None
        cache.loop_info = None
        cache.ssa = None
      }
      HotAnalysis::Liveness => {
        cache.liveness = None
        cache.ssa = None
      }
      HotAnalysis::UseDef => {
        cache.use_def = None
        cache.liveness = None
        cache.ssa = None
      }
      HotAnalysis::Effects => cache.effects = None
      HotAnalysis::LoopInfo => cache.loop_info = None
      HotAnalysis::Ssa => cache.ssa = None
    }
  }
}

///|
fn cache_bind_func(cache : HotAnalysisCache, func : HotFunc) -> Unit {
  match cache.owner_roots {
    Some(roots) if physical_equal(roots, func.roots) => return
    _ => ()
  }
  cache_invalidate_all(cache)
  cache.owner_roots = Some(func.roots)
}

///|
pub fn cache_get_or_build_cfg(
  cache : HotAnalysisCache,
  func : HotFunc,
) -> HotCfg {
  cache_bind_func(cache, func)
  let revision = hot_revision_current(func)
  if cache_entry_is_current(cache.cfg, revision) {
    return cache_entry_value(cache.cfg)
  }
  let value = cfg_build(func)
  cache.cfg = Some({ built_at_revision: revision, value, })
  value
}

///|
pub fn cache_get_or_build_orders(
  cache : HotAnalysisCache,
  func : HotFunc,
) -> HotCfgOrders {
  cache_bind_func(cache, func)
  let revision = hot_revision_current(func)
  if cache_entry_is_current(cache.orders, revision) {
    return cache_entry_value(cache.orders)
  }
  let cfg = cache_get_or_build_cfg(cache, func)
  let value = {
    preorder: cfg_preorder(cfg),
    postorder: cfg_postorder(cfg),
    reverse_postorder: cfg_reverse_postorder(cfg),
    reverse_postorder_with_exceptions: cfg_reverse_postorder_with_exceptions(
      cfg,
    ),
    reverse_exit_postorder: cfg_reverse_postorder_from_exit(cfg),
    reverse_exit_postorder_with_exceptions: cfg_reverse_postorder_from_exit_with_exceptions(
      cfg,
    ),
    worklist_seed: cfg_block_worklist_seed(cfg),
  }
  cache.orders = Some({ built_at_revision: revision, value, })
  value
}

///|
pub fn cache_get_or_build_dom(
  cache : HotAnalysisCache,
  func : HotFunc,
) -> HotDominators {
  cache_bind_func(cache, func)
  let revision = hot_revision_current(func)
  if cache_entry_is_current(cache.dom, revision) {
    return cache_entry_value(cache.dom)
  }
  let value = dom_build(cache_get_or_build_cfg(cache, func))
  cache.dom = Some({ built_at_revision: revision, value, })
  value
}

///|
pub fn cache_get_or_build_postdom(
  cache : HotAnalysisCache,
  func : HotFunc,
) -> HotPostDominators {
  cache_bind_func(cache, func)
  let revision = hot_revision_current(func)
  if cache_entry_is_current(cache.postdom, revision) {
    return cache_entry_value(cache.postdom)
  }
  let value = postdom_build(cache_get_or_build_cfg(cache, func))
  cache.postdom = Some({ built_at_revision: revision, value, })
  value
}

///|
pub fn cache_get_or_build_loop_info(
  cache : HotAnalysisCache,
  func : HotFunc,
) -> HotLoopInfo {
  cache_bind_func(cache, func)
  let revision = hot_revision_current(func)
  if cache_entry_is_current(cache.loop_info, revision) {
    return cache_entry_value(cache.loop_info)
  }
  let cfg = cache_get_or_build_cfg(cache, func)
  let dom = cache_get_or_build_dom(cache, func)
  let value = loop_info_build(cfg, dom)
  cache.loop_info = Some({ built_at_revision: revision, value, })
  value
}

///|
pub fn cache_get_or_build_use_def(
  cache : HotAnalysisCache,
  func : HotFunc,
) -> HotUseDef {
  cache_bind_func(cache, func)
  let revision = hot_revision_current(func)
  if cache_entry_is_current(cache.use_def, revision) {
    return cache_entry_value(cache.use_def)
  }
  let value = use_def_build(func, cfg=Some(cache_get_or_build_cfg(cache, func)))
  cache.use_def = Some({ built_at_revision: revision, value, })
  value
}

///|
pub fn cache_get_or_build_liveness(
  cache : HotAnalysisCache,
  func : HotFunc,
) -> HotLiveness {
  cache_bind_func(cache, func)
  let revision = hot_revision_current(func)
  if cache_entry_is_current(cache.liveness, revision) {
    return cache_entry_value(cache.liveness)
  }
  let cfg = cache_get_or_build_cfg(cache, func)
  let value = liveness_build(cfg, cache_get_or_build_use_def(cache, func))
  cache.liveness = Some({ built_at_revision: revision, value, })
  value
}

///|
pub fn cache_get_or_build_effects(
  cache : HotAnalysisCache,
  func : HotFunc,
) -> HotEffectsSummary {
  cache_bind_func(cache, func)
  let revision = hot_revision_current(func)
  if cache_entry_is_current(cache.effects, revision) {
    return cache_entry_value(cache.effects)
  }
  let value = cache_effects_build(func, cache_get_or_build_cfg(cache, func))
  cache.effects = Some({ built_at_revision: revision, value, })
  value
}

///|
pub fn cache_get_or_build_ssa(
  cache : HotAnalysisCache,
  func : HotFunc,
) -> HotLocalSsa {
  cache_bind_func(cache, func)
  let revision = hot_revision_current(func)
  if cache_entry_is_current(cache.ssa, revision) {
    return cache_entry_value(cache.ssa)
  }
  let cfg = cache_get_or_build_cfg(cache, func)
  let dom = cache_get_or_build_dom(cache, func)
  let use_def = cache_get_or_build_use_def(cache, func)
  let liveness = cache_get_or_build_liveness(cache, func)
  let value = ssa_build_local(func, cfg, dom, liveness, use_def)
  cache.ssa = Some({ built_at_revision: revision, value, })
  value
}