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
25package 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
34import sun.jvm.hotspot.utilities.*;
35
36public 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}
290