It also make sense to cache memory accesses when the CPU is stalled. The same locations are repeatedly accessed as the stack is analyzed.
It is not generally feasible (nor efficient) to cache all of
memory. Instead a small hash table is used. In this case the hash
table is represented by three private arrays, each of the same size
(specified in the constructor and dynamically
allocated). tabIsValid
is a Boolean array
indicating if the corresponding hash table slot is in
use. tabKeyAddr
holds the memory address being
used to key a particular hash table
slot. tabValue
holds the associated cached
value. The hash table can be cleared by using
memset
to set all the elements of
tabIsValid
to false
.
The hash table provides for no retry function if a lookup clashes. The new key address replaces any existing entry. In practice clashes are very unlikely, so this makes lookup efficient.
The use of MemCache
within the Debug Unit is
discussed in the chapter on optimization (Section 5.2).
The public interface to MemCache
is as
follows:
MemCache
. Constructor, which takes the size
of the hash table as an optional argument. The default if no
size is specified is 1009. The hash table arrays
(tabIsValid
, tabKeyAddr
and tabValue
) are allocated. The table is
cleared by calling clear
.
~MemCache
. The destructor, which frees the
hash table arrays.
clear
. Clears the hash table by using
memset
to set all elements of
tabIsValid
to false
.
write
. Writes a hash table entry for a
specified address and value. Any existing entry at that location
is overwritten.
read
. Returns true
if
the given memory address is in the cache. The cached value is
returned by reference through the second argument.