/* Caching code for GDB, the GNU debugger.
Copyright (C) 1992, 1993, 1995, 1996, 1998, 1999, 2000, 2001, 2003, 2007,
- 2008, 2009 Free Software Foundation, Inc.
+ 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
This file is part of GDB.
#include "gdb_string.h"
#include "gdbcore.h"
#include "target.h"
+#include "inferior.h"
#include "splay-tree.h"
+/* Commands with a prefix of `{set,show} dcache'. */
+static struct cmd_list_element *dcache_set_list = NULL;
+static struct cmd_list_element *dcache_show_list = NULL;
+
/* The data cache could lead to incorrect results because it doesn't
know about volatile variables, thus making it impossible to debug
functions which use memory mapped I/O devices. Set the nocache
of data, such as when performing a backtrace.
The cache is a splay tree along with a linked list for replacement.
- Each block caches a LINE_SIZE area of memory. Wtihin each line we remember
- the address of the line (which must be a multiple of LINE_SIZE) and the
- actual data block.
+ Each block caches a LINE_SIZE area of memory. Within each line we
+ remember the address of the line (which must be a multiple of
+ LINE_SIZE) and the actual data block.
Lines are only allocated as needed, so DCACHE_SIZE really specifies the
*maximum* number of lines in the cache.
/* The maximum number of lines stored. The total size of the cache is
equal to DCACHE_SIZE times LINE_SIZE. */
-#define DCACHE_SIZE 4096
+#define DCACHE_DEFAULT_SIZE 4096
+static unsigned dcache_size = DCACHE_DEFAULT_SIZE;
-/* The size of a cache line. Smaller values reduce the time taken to
+/* The default size of a cache line. Smaller values reduce the time taken to
read a single byte and make the cache more granular, but increase
overhead and reduce the effectiveness of the cache as a prefetcher. */
-#define LINE_SIZE_POWER 6
-#define LINE_SIZE (1 << LINE_SIZE_POWER)
+#define DCACHE_DEFAULT_LINE_SIZE 64
+static unsigned dcache_line_size = DCACHE_DEFAULT_LINE_SIZE;
/* Each cache block holds LINE_SIZE bytes of data
starting at a multiple-of-LINE_SIZE address. */
-#define LINE_SIZE_MASK ((LINE_SIZE - 1))
-#define XFORM(x) ((x) & LINE_SIZE_MASK)
-#define MASK(x) ((x) & ~LINE_SIZE_MASK)
+#define LINE_SIZE_MASK(dcache) ((dcache->line_size - 1))
+#define XFORM(dcache, x) ((x) & LINE_SIZE_MASK (dcache))
+#define MASK(dcache, x) ((x) & ~LINE_SIZE_MASK (dcache))
struct dcache_block
{
- struct dcache_block *newer; /* for LRU and free list */
+ /* For least-recently-allocated and free lists. */
+ struct dcache_block *prev;
+ struct dcache_block *next;
+
CORE_ADDR addr; /* address of data */
- gdb_byte data[LINE_SIZE]; /* bytes at given address */
int refs; /* # hits */
+ gdb_byte data[1]; /* line_size bytes at given address */
};
struct dcache_struct
{
splay_tree tree;
- struct dcache_block *oldest;
- struct dcache_block *newest;
+ struct dcache_block *oldest; /* least-recently-allocated list. */
+ /* The free list is maintained identically to OLDEST to simplify
+ the code: we only need one set of accessors. */
struct dcache_block *freelist;
/* The number of in-use lines in the cache. */
int size;
+ CORE_ADDR line_size; /* current line_size. */
+
+ /* The ptid of last inferior to use cache or null_ptid. */
+ ptid_t ptid;
};
-static struct dcache_block *dcache_hit (DCACHE *dcache, CORE_ADDR addr);
+typedef void (block_func) (struct dcache_block *block, void *param);
-static int dcache_write_line (DCACHE *dcache, struct dcache_block *db);
+static struct dcache_block *dcache_hit (DCACHE *dcache, CORE_ADDR addr);
static int dcache_read_line (DCACHE *dcache, struct dcache_block *db);
void _initialize_dcache (void);
-static int dcache_enabled_p = 0;
+static int dcache_enabled_p = 0; /* OBSOLETE */
static void
show_dcache_enabled_p (struct ui_file *file, int from_tty,
struct cmd_list_element *c, const char *value)
{
- fprintf_filtered (file, _("Cache use for remote targets is %s.\n"), value);
+ fprintf_filtered (file, _("Deprecated remotecache flag is %s.\n"), value);
+}
+
+static DCACHE *last_cache; /* Used by info dcache. */
+
+/* Add BLOCK to circular block list BLIST, behind the block at *BLIST.
+ *BLIST is not updated (unless it was previously NULL of course).
+ This is for the least-recently-allocated list's sake:
+ BLIST points to the oldest block.
+ ??? This makes for poor cache usage of the free list,
+ but is it measurable? */
+
+static void
+append_block (struct dcache_block **blist, struct dcache_block *block)
+{
+ if (*blist)
+ {
+ block->next = *blist;
+ block->prev = (*blist)->prev;
+ block->prev->next = block;
+ (*blist)->prev = block;
+ /* We don't update *BLIST here to maintain the invariant that for the
+ least-recently-allocated list *BLIST points to the oldest block. */
+ }
+ else
+ {
+ block->next = block;
+ block->prev = block;
+ *blist = block;
+ }
+}
+
+/* Remove BLOCK from circular block list BLIST. */
+
+static void
+remove_block (struct dcache_block **blist, struct dcache_block *block)
+{
+ if (block->next == block)
+ {
+ *blist = NULL;
+ }
+ else
+ {
+ block->next->prev = block->prev;
+ block->prev->next = block->next;
+ /* If we removed the block *BLIST points to, shift it to the next block
+ to maintain the invariant that for the least-recently-allocated list
+ *BLIST points to the oldest block. */
+ if (*blist == block)
+ *blist = block->next;
+ }
+}
+
+/* Iterate over all elements in BLIST, calling FUNC.
+ PARAM is passed to FUNC.
+ FUNC may remove the block it's passed, but only that block. */
+
+static void
+for_each_block (struct dcache_block **blist, block_func *func, void *param)
+{
+ struct dcache_block *db;
+
+ if (*blist == NULL)
+ return;
+
+ db = *blist;
+ do
+ {
+ struct dcache_block *next = db->next;
+
+ func (db, param);
+ db = next;
+ }
+ while (*blist && db != *blist);
+}
+
+/* BLOCK_FUNC routine for dcache_free. */
+
+static void
+free_block (struct dcache_block *block, void *param)
+{
+ xfree (block);
}
+/* Free a data cache. */
-static DCACHE *last_cache; /* Used by info dcache */
+void
+dcache_free (DCACHE *dcache)
+{
+ if (last_cache == dcache)
+ last_cache = NULL;
+
+ splay_tree_delete (dcache->tree);
+ for_each_block (&dcache->oldest, free_block, NULL);
+ for_each_block (&dcache->freelist, free_block, NULL);
+ xfree (dcache);
+}
+
+
+/* BLOCK_FUNC function for dcache_invalidate.
+ This doesn't remove the block from the oldest list on purpose.
+ dcache_invalidate will do it later. */
+
+static void
+invalidate_block (struct dcache_block *block, void *param)
+{
+ DCACHE *dcache = (DCACHE *) param;
+
+ splay_tree_remove (dcache->tree, (splay_tree_key) block->addr);
+ append_block (&dcache->freelist, block);
+}
/* Free all the data cache blocks, thus discarding all cached data. */
void
dcache_invalidate (DCACHE *dcache)
{
- struct dcache_block *block, *next;
+ for_each_block (&dcache->oldest, invalidate_block, dcache);
- block = dcache->oldest;
+ dcache->oldest = NULL;
+ dcache->size = 0;
+ dcache->ptid = null_ptid;
- while (block)
+ if (dcache->line_size != dcache_line_size)
{
- splay_tree_remove (dcache->tree, (splay_tree_key) block->addr);
- next = block->newer;
-
- block->newer = dcache->freelist;
- dcache->freelist = block;
+ /* We've been asked to use a different line size.
+ All of our freelist blocks are now the wrong size, so free them. */
- block = next;
+ for_each_block (&dcache->freelist, free_block, dcache);
+ dcache->freelist = NULL;
+ dcache->line_size = dcache_line_size;
}
+}
- dcache->oldest = NULL;
- dcache->newest = NULL;
- dcache->size = 0;
+/* Invalidate the line associated with ADDR. */
+
+static void
+dcache_invalidate_line (DCACHE *dcache, CORE_ADDR addr)
+{
+ struct dcache_block *db = dcache_hit (dcache, addr);
+
+ if (db)
+ {
+ splay_tree_remove (dcache->tree, (splay_tree_key) db->addr);
+ remove_block (&dcache->oldest, db);
+ append_block (&dcache->freelist, db);
+ --dcache->size;
+ }
}
/* If addr is present in the dcache, return the address of the block
- containing it. */
+ containing it. Otherwise return NULL. */
static struct dcache_block *
dcache_hit (DCACHE *dcache, CORE_ADDR addr)
struct dcache_block *db;
splay_tree_node node = splay_tree_lookup (dcache->tree,
- (splay_tree_key) MASK (addr));
+ (splay_tree_key) MASK (dcache, addr));
if (!node)
return NULL;
return db;
}
-/* Fill a cache line from target memory. */
+/* Fill a cache line from target memory.
+ The result is 1 for success, 0 if the (entire) cache line
+ wasn't readable. */
static int
dcache_read_line (DCACHE *dcache, struct dcache_block *db)
int reg_len;
struct mem_region *region;
- len = LINE_SIZE;
+ len = dcache->line_size;
memaddr = db->addr;
myaddr = db->data;
else
reg_len = region->hi - memaddr;
- /* Skip non-cacheable/non-readable regions. */
- if (!region->attrib.cache || region->attrib.mode == MEM_WO)
+ /* Skip non-readable regions. The cache attribute can be ignored,
+ since we may be loading this for a stack access. */
+ if (region->attrib.mode == MEM_WO)
{
memaddr += reg_len;
myaddr += reg_len;
{
struct dcache_block *db;
- if (dcache->size >= DCACHE_SIZE)
+ if (dcache->size >= dcache_size)
{
- /* Evict the least recently used line. */
+ /* Evict the least recently allocated line. */
db = dcache->oldest;
- dcache->oldest = db->newer;
+ remove_block (&dcache->oldest, db);
splay_tree_remove (dcache->tree, (splay_tree_key) db->addr);
}
{
db = dcache->freelist;
if (db)
- dcache->freelist = db->newer;
+ remove_block (&dcache->freelist, db);
else
- db = xmalloc (sizeof (struct dcache_block));
+ db = xmalloc (offsetof (struct dcache_block, data) +
+ dcache->line_size);
dcache->size++;
}
- db->addr = MASK (addr);
- db->newer = NULL;
+ db->addr = MASK (dcache, addr);
db->refs = 0;
- if (dcache->newest)
- dcache->newest->newer = db;
-
- dcache->newest = db;
-
- if (!dcache->oldest)
- dcache->oldest = db;
+ /* Put DB at the end of the list, it's the newest. */
+ append_block (&dcache->oldest, db);
splay_tree_insert (dcache->tree, (splay_tree_key) db->addr,
(splay_tree_value) db);
return db;
}
-/* Using the data cache DCACHE return the contents of the byte at
+/* Using the data cache DCACHE, store in *PTR the contents of the byte at
address ADDR in the remote machine.
Returns 1 for success, 0 for error. */
return 0;
}
- *ptr = db->data[XFORM (addr)];
+ *ptr = db->data[XFORM (dcache, addr)];
return 1;
}
an area of memory which wasn't present in the cache doesn't cause
it to be loaded in.
- Always return 1 to simplify dcache_xfer_memory. */
+ Always return 1 (meaning success) to simplify dcache_xfer_memory. */
static int
dcache_poke_byte (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr)
struct dcache_block *db = dcache_hit (dcache, addr);
if (db)
- db->data[XFORM (addr)] = *ptr;
+ db->data[XFORM (dcache, addr)] = *ptr;
return 1;
}
return -1;
}
-/* Initialize the data cache. */
+/* Allocate and initialize a data cache. */
DCACHE *
dcache_init (void)
{
DCACHE *dcache;
- int i;
dcache = (DCACHE *) xmalloc (sizeof (*dcache));
NULL);
dcache->oldest = NULL;
- dcache->newest = NULL;
dcache->freelist = NULL;
dcache->size = 0;
+ dcache->line_size = dcache_line_size;
+ dcache->ptid = null_ptid;
last_cache = dcache;
return dcache;
}
-/* Free a data cache. */
-
-void
-dcache_free (DCACHE *dcache)
-{
- struct dcache_block *db, *next;
-
- if (last_cache == dcache)
- last_cache = NULL;
-
- splay_tree_delete (dcache->tree);
- for (db = dcache->freelist; db != NULL; db = next)
- {
- next = db->newer;
- xfree (db);
- }
- xfree (dcache);
-}
/* Read or write LEN bytes from inferior memory at MEMADDR, transferring
to or from debugger address MYADDR. Write to inferior if SHOULD_WRITE is
nonzero.
- Returns length of data written or read; 0 for error. */
+ Return the number of bytes actually transfered, or -1 if the
+ transfer is not supported or otherwise fails. Return of a non-negative
+ value less than LEN indicates that no further transfer is possible.
+ NOTE: This is different than the to_xfer_partial interface, in which
+ positive values less than LEN mean further transfers may be possible. */
int
dcache_xfer_memory (struct target_ops *ops, DCACHE *dcache,
int i;
int res;
int (*xfunc) (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr);
+
xfunc = should_write ? dcache_poke_byte : dcache_peek_byte;
+ /* If this is a different inferior from what we've recorded,
+ flush the cache. */
+
+ if (! ptid_equal (inferior_ptid, dcache->ptid))
+ {
+ dcache_invalidate (dcache);
+ dcache->ptid = inferior_ptid;
+ }
+
/* Do write-through first, so that if it fails, we don't write to
the cache at all. */
{
res = target_write (ops, TARGET_OBJECT_RAW_MEMORY,
NULL, myaddr, memaddr, len);
- if (res < len)
- return 0;
+ if (res <= 0)
+ return res;
+ /* Update LEN to what was actually written. */
+ len = res;
}
for (i = 0; i < len; i++)
{
if (!xfunc (dcache, memaddr + i, myaddr + i))
- return 0;
+ {
+ /* That failed. Discard its cache line so we don't have a
+ partially read line. */
+ dcache_invalidate_line (dcache, memaddr + i);
+ /* If we're writing, we still wrote LEN bytes. */
+ if (should_write)
+ return len;
+ else
+ return i;
+ }
}
return len;
"logically" connected but not actually a single call to one of the
memory transfer functions. */
+/* Just update any cache lines which are already present. This is called
+ by memory_xfer_partial in cases where the access would otherwise not go
+ through the cache. */
+
+void
+dcache_update (DCACHE *dcache, CORE_ADDR memaddr, gdb_byte *myaddr, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ dcache_poke_byte (dcache, memaddr + i, myaddr + i);
+}
+
static void
dcache_print_line (int index)
{
printf_filtered (_("Line %d: address %s [%d hits]\n"),
index, paddress (target_gdbarch, db->addr), db->refs);
- for (j = 0; j < LINE_SIZE; j++)
+ for (j = 0; j < last_cache->line_size; j++)
{
printf_filtered ("%02x ", db->data[j]);
- /* Print a newline every 16 bytes (48 characters) */
- if ((j % 16 == 15) && (j != LINE_SIZE - 1))
+ /* Print a newline every 16 bytes (48 characters). */
+ if ((j % 16 == 15) && (j != last_cache->line_size - 1))
printf_filtered ("\n");
}
printf_filtered ("\n");
dcache_info (char *exp, int tty)
{
splay_tree_node n;
- int i, refcount, lineno;
+ int i, refcount;
if (exp)
{
char *linestart;
+
i = strtol (exp, &linestart, 10);
if (linestart == exp || i < 0)
{
return;
}
- printf_filtered (_("Dcache line width %d, maximum size %d\n"),
- LINE_SIZE, DCACHE_SIZE);
+ printf_filtered (_("Dcache %u lines of %u bytes each.\n"),
+ dcache_size,
+ last_cache ? (unsigned) last_cache->line_size
+ : dcache_line_size);
- if (!last_cache)
+ if (!last_cache || ptid_equal (last_cache->ptid, null_ptid))
{
printf_filtered (_("No data cache available.\n"));
return;
}
+ printf_filtered (_("Contains data for %s\n"),
+ target_pid_to_str (last_cache->ptid));
+
refcount = 0;
n = splay_tree_min (last_cache->tree);
printf_filtered (_("Cache state: %d active lines, %d hits\n"), i, refcount);
}
+static void
+set_dcache_size (char *args, int from_tty,
+ struct cmd_list_element *c)
+{
+ if (dcache_size == 0)
+ {
+ dcache_size = DCACHE_DEFAULT_SIZE;
+ error (_("Dcache size must be greater than 0."));
+ }
+ if (last_cache)
+ dcache_invalidate (last_cache);
+}
+
+static void
+set_dcache_line_size (char *args, int from_tty,
+ struct cmd_list_element *c)
+{
+ if (dcache_line_size < 2
+ || (dcache_line_size & (dcache_line_size - 1)) != 0)
+ {
+ unsigned d = dcache_line_size;
+ dcache_line_size = DCACHE_DEFAULT_LINE_SIZE;
+ error (_("Invalid dcache line size: %u (must be power of 2)."), d);
+ }
+ if (last_cache)
+ dcache_invalidate (last_cache);
+}
+
+static void
+set_dcache_command (char *arg, int from_tty)
+{
+ printf_unfiltered (
+ "\"set dcache\" must be followed by the name of a subcommand.\n");
+ help_list (dcache_set_list, "set dcache ", -1, gdb_stdout);
+}
+
+static void
+show_dcache_command (char *args, int from_tty)
+{
+ cmd_show_list (dcache_show_list, from_tty, "");
+}
+
void
_initialize_dcache (void)
{
&dcache_enabled_p, _("\
Set cache use for remote targets."), _("\
Show cache use for remote targets."), _("\
-When on, use data caching for remote targets. For many remote targets\n\
-this option can offer better throughput for reading target memory.\n\
-Unfortunately, gdb does not currently know anything about volatile\n\
-registers and thus data caching will produce incorrect results with\n\
-volatile registers are in use. By default, this option is off."),
+This used to enable the data cache for remote targets. The cache\n\
+functionality is now controlled by the memory region system and the\n\
+\"stack-cache\" flag; \"remotecache\" now does nothing and\n\
+exists only for compatibility reasons."),
NULL,
show_dcache_enabled_p,
&setlist, &showlist);
With no arguments, this command prints the cache configuration and a\n\
summary of each line in the cache. Use \"info dcache <lineno> to dump\"\n\
the contents of a given line."));
+
+ add_prefix_cmd ("dcache", class_obscure, set_dcache_command, _("\
+Use this command to set number of lines in dcache and line-size."),
+ &dcache_set_list, "set dcache ", /*allow_unknown*/0, &setlist);
+ add_prefix_cmd ("dcache", class_obscure, show_dcache_command, _("\
+Show dcachesettings."),
+ &dcache_show_list, "show dcache ", /*allow_unknown*/0, &showlist);
+
+ add_setshow_uinteger_cmd ("line-size", class_obscure,
+ &dcache_line_size, _("\
+Set dcache line size in bytes (must be power of 2)."), _("\
+Show dcache line size."),
+ NULL,
+ set_dcache_line_size,
+ NULL,
+ &dcache_set_list, &dcache_show_list);
+ add_setshow_uinteger_cmd ("size", class_obscure,
+ &dcache_size, _("\
+Set number of dcache lines."), _("\
+Show number of dcache lines."),
+ NULL,
+ set_dcache_size,
+ NULL,
+ &dcache_set_list, &dcache_show_list);
}