blob: 957cb70bbd8ff52563cd52014f8b00150c766d19 [file] [log] [blame]
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
from typing import Dict, Generic, Optional, List, TypeVar
K = TypeVar('K')
V = TypeVar('V')
I = TypeVar('I')
class CacheEntry(Generic[K,V]):
'''Represents a single entry in a cache.
The entry must hold two pieces of information:
- value, the cached result to be returned on a matching lookup
- key, data needed to determine if the entry matches a lookup (e.g. input
parameters to the function whose result has been cached)
Note that this is not a simple key/value store, because a key might match a
lookup even if it's not an exact match. Determining what exactly needs to
match is implementation-specific and implemented by subclasses.
'''
def __init__(self, key: K, value: V):
self.key = key
self.value = value
def is_match(self, key: K) -> bool:
'''Returns whether this entry is a match for the key.
In the simplest case, this could be just self.key == key; however, the
implementer might choose to ignore certain information when evaluating
the match, depending on the use-case.
'''
raise NotImplementedError()
class Cache(Generic[I,K,V]):
'''Represents a cache to speed up recursive functions.
The cache is structured with two layers:
- The first layer is a dictionary that maps some hashable index type to the
second layer, a list for each dictionary entry.
- The second layer is a list of CacheEntry instances to be checked.
The purpose of the two-layer structure is to speed up lookups; the index
type should be used to quickly narrow things down to a limited number of
potentially matching entries (for instance, it could be an input parameter
to the function that absolutely must match for the cache entries to match).
'''
def __init__(self) -> None:
self.entries : Dict[I,List[CacheEntry[K,V]]] = {}
def add(self, index: I, entry: CacheEntry[K,V]) -> None:
# Only add if there's no matching entry already
if self.lookup(index, entry.key) is None:
self.entries.setdefault(index, []).append(entry)
def lookup(self, index: I, key: K) -> Optional[V]:
for entry in self.entries.get(index, []):
if entry.is_match(key):
return entry.value
return None