1 /*
2  * Copyright (c) 2000, 2002, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * 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. */
PageCache(long pageSize, long maxNumPages, PageFetcher fetcher)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. */
getData(long startAddress, long numBytes)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 
getBoolean(long address)79   public synchronized boolean getBoolean(long address) {
80     return (getByte(address) != 0);
81   }
82 
getByte(long address)83   public synchronized byte getByte(long address) {
84     return checkPage(getPage(address & pageMask), address).getByte(address);
85   }
86 
getShort(long address, boolean bigEndian)87   public synchronized short getShort(long address, boolean bigEndian) {
88     return checkPage(getPage(address & pageMask), address).getShort(address, bigEndian);
89   }
90 
getChar(long address, boolean bigEndian)91   public synchronized char getChar(long address, boolean bigEndian) {
92     return checkPage(getPage(address & pageMask), address).getChar(address, bigEndian);
93   }
94 
getInt(long address, boolean bigEndian)95   public synchronized int getInt(long address, boolean bigEndian) {
96     return checkPage(getPage(address & pageMask), address).getInt(address, bigEndian);
97   }
98 
getLong(long address, boolean bigEndian)99   public synchronized long getLong(long address, boolean bigEndian) {
100     return checkPage(getPage(address & pageMask), address).getLong(address, bigEndian);
101   }
102 
getFloat(long address, boolean bigEndian)103   public synchronized float getFloat(long address, boolean bigEndian) {
104     return checkPage(getPage(address & pageMask), address).getFloat(address, bigEndian);
105   }
106 
getDouble(long address, boolean bigEndian)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 */
clear(long startAddress, long numBytes)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 */
clear()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. */
disable()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. */
enable()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 */
getPage(long pageBaseAddress)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 
checkPage(Page page, long startAddress)206   private Page checkPage(Page page, long startAddress) {
207     if (!page.isMapped()) {
208       throw new UnmappedAddressException(startAddress);
209     }
210     return page;
211   }
212 
countPages()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 
flushPage(long pageBaseAddress)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
addPageToList(Page page)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
removePageFromList(Page page)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 */
checkPageInfo(long pageSize, long maxNumPages)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 }
290