1 /*
   2  * Copyright 2000-2002 Sun Microsystems, Inc.  All Rights Reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  20  * CA 95054 USA or visit www.sun.com if you need additional information or
  21  * have any questions.
  22  *
  23  */
  24 
  25 package sun.jvm.hotspot.debugger;
  26 
  27 /** This class implements an LRU page-level cache of configurable page
  28     size and number of pages. It is configured with a PageFetcher
  29     which enables it to transparently satisfy requests which span
  30     multiple pages when one or more of those pages is not in the
  31     cache. It is generic enough to be sharable among debugger
  32     implementations. */
  33 
  34 import sun.jvm.hotspot.utilities.*;
  35 
  36 public class PageCache {
  37   /** The pageSize must be a power of two and implicitly specifies the
  38       alignment of pages. numPages specifies how many pages should be
  39       cached. */
  40   public PageCache(long pageSize,
  41                    long maxNumPages,
  42                    PageFetcher fetcher) {
  43     checkPageInfo(pageSize, maxNumPages);
  44     this.pageSize    = pageSize;
  45     this.maxNumPages = maxNumPages;
  46     this.fetcher     = fetcher;
  47     addressToPageMap = new LongHashMap();
  48     enabled = true;
  49   }
  50 
  51   /** This handles fetches which span multiple pages by virtue of the
  52       presence of the PageFetcher. Throws UnmappedAddressException if
  53       a page on which data was requested was unmapped. This can not
  54       really handle numBytes > 32 bits. */
  55   public synchronized byte[] getData(long startAddress, long numBytes)
  56     throws UnmappedAddressException {
  57     byte[] data = new byte[(int) numBytes];
  58     long numRead = 0;
  59 
  60     while (numBytes > 0) {
  61       long pageBaseAddress = startAddress & pageMask;
  62       // Look up this page
  63       Page page = checkPage(getPage(pageBaseAddress), startAddress);
  64       // Figure out how many bytes to read from this page
  65       long pageOffset = startAddress - pageBaseAddress;
  66       long numBytesFromPage = Math.min(pageSize - pageOffset, numBytes);
  67       // Read them starting at the appropriate offset in the
  68       // destination buffer
  69       page.getDataAsBytes(startAddress, numBytesFromPage, data, numRead);
  70       // Increment offsets
  71       numRead      += numBytesFromPage;
  72       numBytes     -= numBytesFromPage;
  73       startAddress += numBytesFromPage;
  74     }
  75 
  76     return data;
  77   }
  78 
  79   public synchronized boolean getBoolean(long address) {
  80     return (getByte(address) != 0);
  81   }
  82 
  83   public synchronized byte getByte(long address) {
  84     return checkPage(getPage(address & pageMask), address).getByte(address);
  85   }
  86 
  87   public synchronized short getShort(long address, boolean bigEndian) {
  88     return checkPage(getPage(address & pageMask), address).getShort(address, bigEndian);
  89   }
  90 
  91   public synchronized char getChar(long address, boolean bigEndian) {
  92     return checkPage(getPage(address & pageMask), address).getChar(address, bigEndian);
  93   }
  94 
  95   public synchronized int getInt(long address, boolean bigEndian) {
  96     return checkPage(getPage(address & pageMask), address).getInt(address, bigEndian);
  97   }
  98 
  99   public synchronized long getLong(long address, boolean bigEndian) {
 100     return checkPage(getPage(address & pageMask), address).getLong(address, bigEndian);
 101   }
 102 
 103   public synchronized float getFloat(long address, boolean bigEndian) {
 104     return checkPage(getPage(address & pageMask), address).getFloat(address, bigEndian);
 105   }
 106 
 107   public synchronized double getDouble(long address, boolean bigEndian) {
 108     return checkPage(getPage(address & pageMask), address).getDouble(address, bigEndian);
 109   }
 110 
 111   /** A mechanism for clearing cached data covering the given region */
 112   public synchronized void clear(long startAddress, long numBytes) {
 113     long pageBaseAddress = startAddress & pageMask;
 114     long endAddress      = startAddress + numBytes;
 115     while (pageBaseAddress < endAddress) {
 116       flushPage(pageBaseAddress);
 117       pageBaseAddress += pageSize;
 118     }
 119   }
 120 
 121   /** A mechanism for clearing out the cache is necessary to handle
 122       detaching and reattaching */
 123   public synchronized void clear() {
 124     // Should probably break next/prev links in list as well
 125     addressToPageMap.clear();
 126     lruList = null;
 127     numPages = 0;
 128   }
 129 
 130   /** Disables the page cache; no further pages will be added to the
 131       cache and all existing pages will be flushed. Call this when the
 132       target process has been resumed. */
 133   public synchronized void disable() {
 134     enabled = false;
 135     clear();
 136   }
 137 
 138   /** Enables the page cache; fetched pages will be added to the
 139       cache. Call this when the target process has been suspended. */
 140   public synchronized void enable() {
 141     enabled = true;
 142   }
 143 
 144 
 145   //--------------------------------------------------------------------------------
 146   // Internals only below this point
 147   //
 148 
 149   // This is implemented with two data structures: a hash table for
 150   // fast lookup by a page's base address and a circular doubly-linked
 151   // list for implementing LRU behavior.
 152 
 153   private boolean     enabled;
 154   private long        pageSize;
 155   private long        maxNumPages;
 156   private long        pageMask;
 157   private long        numPages;
 158   private PageFetcher fetcher;
 159   private LongHashMap addressToPageMap; // Map<long, Page>
 160   private Page        lruList; // Most recently fetched page, or null
 161 
 162   /** Page fetcher plus LRU functionality */
 163   private Page getPage(long pageBaseAddress) {
 164     // Check head of LRU list first to avoid hash table lookup and
 165     // extra list work if possible
 166     if (lruList != null) {
 167       if (lruList.getBaseAddress() == pageBaseAddress) {
 168         // Hit. Return it.
 169         return lruList;
 170       }
 171     }
 172     //    Long key = new Long(pageBaseAddress);
 173     long key = pageBaseAddress;
 174     Page page = (Page) addressToPageMap.get(key);
 175     if (page == null) {
 176       // System.err.println("** Cache miss at address 0x" + Long.toHexString(pageBaseAddress) + " **");
 177       // Fetch new page
 178       page = fetcher.fetchPage(pageBaseAddress, pageSize);
 179       if (enabled) {
 180         // Add to cache, evicting last element if necessary
 181         addressToPageMap.put(key, page);
 182         if (Assert.ASSERTS_ENABLED) {
 183           Assert.that(page == (Page) addressToPageMap.get(pageBaseAddress),
 184                       "must have found page in cache!");
 185         }
 186         addPageToList(page);
 187         // See whether eviction of oldest is necessary
 188         if (numPages == maxNumPages) {
 189           Page evictedPage = lruList.getPrev();
 190           // System.err.println("-> Evicting page at      0x" + Long.toHexString(evictedPage.getBaseAddress()) +
 191           //                    "; " + countPages() + " pages left (expect " + numPages + ")");
 192           removePageFromList(evictedPage);
 193           addressToPageMap.remove(evictedPage.getBaseAddress());
 194         } else {
 195           ++numPages;
 196         }
 197       }
 198     } else {
 199       // Page already in cache, move to front of list
 200       removePageFromList(page);
 201       addPageToList(page);
 202     }
 203     return page;
 204   }
 205 
 206   private Page checkPage(Page page, long startAddress) {
 207     if (!page.isMapped()) {
 208       throw new UnmappedAddressException(startAddress);
 209     }
 210     return page;
 211   }
 212 
 213   private int countPages() {
 214     Page page = lruList;
 215     int num = 0;
 216     if (page == null) {
 217       return num;
 218     }
 219     do {
 220       ++num;
 221       page = page.getNext();
 222     } while (page != lruList);
 223     return num;
 224   }
 225 
 226   private void flushPage(long pageBaseAddress) {
 227     long key = pageBaseAddress;
 228     Page page = (Page) addressToPageMap.remove(key);
 229     if (page != null) {
 230       removePageFromList(page);
 231     }
 232   }
 233 
 234   // Adds given page to head of list
 235   private void addPageToList(Page page) {
 236     if (lruList == null) {
 237       lruList = page;
 238       page.setNext(page);
 239       page.setPrev(page);
 240     } else {
 241       // Add to front of list
 242       page.setNext(lruList);
 243       page.setPrev(lruList.getPrev());
 244       lruList.getPrev().setNext(page);
 245       lruList.setPrev(page);
 246       lruList = page;
 247     }
 248   }
 249 
 250   // Removes given page from list
 251   private void removePageFromList(Page page) {
 252     if (page.getNext() == page) {
 253       lruList = null;
 254     } else {
 255       if (lruList == page) {
 256         lruList = page.getNext();
 257       }
 258       page.getPrev().setNext(page.getNext());
 259       page.getNext().setPrev(page.getPrev());
 260     }
 261     page.setPrev(null);
 262     page.setNext(null);
 263   }
 264 
 265   /** Ensure that page size fits within 32 bits and is a power of two, and that maxNumPages > 0 */
 266   private void checkPageInfo(long pageSize, long maxNumPages) {
 267     if ((pageSize <= 0) || maxNumPages <= 0) {
 268       throw new IllegalArgumentException("pageSize and maxNumPages must both be greater than zero");
 269     }
 270     long tmpPageSize = pageSize >>> 32;
 271     if (tmpPageSize != 0) {
 272       throw new IllegalArgumentException("pageSize " + pageSize + " too big (must fit within 32 bits)");
 273     }
 274     int numNonZeroBits = 0;
 275     for (int i = 0; i < 32; ++i) {
 276       if ((pageSize & 1L) != 0) {
 277         ++numNonZeroBits;
 278         if ((numNonZeroBits > 1) || (i == 0)) {
 279           throw new IllegalArgumentException("pageSize " + pageSize + " must be a power of two");
 280         }
 281       }
 282       pageSize >>>= 1;
 283       if (numNonZeroBits == 0) {
 284         pageMask = (pageMask << 1) | 1L;
 285       }
 286     }
 287     pageMask = ~pageMask;
 288   }
 289 }