| /* |
| * Copyright (c) 2000, 2002, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| * |
| */ |
| |
| package sun.jvm.hotspot.debugger; |
| |
| /** This class implements an LRU page-level cache of configurable page |
| size and number of pages. It is configured with a PageFetcher |
| which enables it to transparently satisfy requests which span |
| multiple pages when one or more of those pages is not in the |
| cache. It is generic enough to be sharable among debugger |
| implementations. */ |
| |
| import sun.jvm.hotspot.utilities.*; |
| |
| public class PageCache { |
| /** The pageSize must be a power of two and implicitly specifies the |
| alignment of pages. numPages specifies how many pages should be |
| cached. */ |
| public PageCache(long pageSize, |
| long maxNumPages, |
| PageFetcher fetcher) { |
| checkPageInfo(pageSize, maxNumPages); |
| this.pageSize = pageSize; |
| this.maxNumPages = maxNumPages; |
| this.fetcher = fetcher; |
| addressToPageMap = new LongHashMap(); |
| enabled = true; |
| } |
| |
| /** This handles fetches which span multiple pages by virtue of the |
| presence of the PageFetcher. Throws UnmappedAddressException if |
| a page on which data was requested was unmapped. This can not |
| really handle numBytes > 32 bits. */ |
| public synchronized byte[] getData(long startAddress, long numBytes) |
| throws UnmappedAddressException { |
| byte[] data = new byte[(int) numBytes]; |
| long numRead = 0; |
| |
| while (numBytes > 0) { |
| long pageBaseAddress = startAddress & pageMask; |
| // Look up this page |
| Page page = checkPage(getPage(pageBaseAddress), startAddress); |
| // Figure out how many bytes to read from this page |
| long pageOffset = startAddress - pageBaseAddress; |
| long numBytesFromPage = Math.min(pageSize - pageOffset, numBytes); |
| // Read them starting at the appropriate offset in the |
| // destination buffer |
| page.getDataAsBytes(startAddress, numBytesFromPage, data, numRead); |
| // Increment offsets |
| numRead += numBytesFromPage; |
| numBytes -= numBytesFromPage; |
| startAddress += numBytesFromPage; |
| } |
| |
| return data; |
| } |
| |
| public synchronized boolean getBoolean(long address) { |
| return (getByte(address) != 0); |
| } |
| |
| public synchronized byte getByte(long address) { |
| return checkPage(getPage(address & pageMask), address).getByte(address); |
| } |
| |
| public synchronized short getShort(long address, boolean bigEndian) { |
| return checkPage(getPage(address & pageMask), address).getShort(address, bigEndian); |
| } |
| |
| public synchronized char getChar(long address, boolean bigEndian) { |
| return checkPage(getPage(address & pageMask), address).getChar(address, bigEndian); |
| } |
| |
| public synchronized int getInt(long address, boolean bigEndian) { |
| return checkPage(getPage(address & pageMask), address).getInt(address, bigEndian); |
| } |
| |
| public synchronized long getLong(long address, boolean bigEndian) { |
| return checkPage(getPage(address & pageMask), address).getLong(address, bigEndian); |
| } |
| |
| public synchronized float getFloat(long address, boolean bigEndian) { |
| return checkPage(getPage(address & pageMask), address).getFloat(address, bigEndian); |
| } |
| |
| public synchronized double getDouble(long address, boolean bigEndian) { |
| return checkPage(getPage(address & pageMask), address).getDouble(address, bigEndian); |
| } |
| |
| /** A mechanism for clearing cached data covering the given region */ |
| public synchronized void clear(long startAddress, long numBytes) { |
| long pageBaseAddress = startAddress & pageMask; |
| long endAddress = startAddress + numBytes; |
| while (pageBaseAddress < endAddress) { |
| flushPage(pageBaseAddress); |
| pageBaseAddress += pageSize; |
| } |
| } |
| |
| /** A mechanism for clearing out the cache is necessary to handle |
| detaching and reattaching */ |
| public synchronized void clear() { |
| // Should probably break next/prev links in list as well |
| addressToPageMap.clear(); |
| lruList = null; |
| numPages = 0; |
| } |
| |
| /** Disables the page cache; no further pages will be added to the |
| cache and all existing pages will be flushed. Call this when the |
| target process has been resumed. */ |
| public synchronized void disable() { |
| enabled = false; |
| clear(); |
| } |
| |
| /** Enables the page cache; fetched pages will be added to the |
| cache. Call this when the target process has been suspended. */ |
| public synchronized void enable() { |
| enabled = true; |
| } |
| |
| |
| //-------------------------------------------------------------------------------- |
| // Internals only below this point |
| // |
| |
| // This is implemented with two data structures: a hash table for |
| // fast lookup by a page's base address and a circular doubly-linked |
| // list for implementing LRU behavior. |
| |
| private boolean enabled; |
| private long pageSize; |
| private long maxNumPages; |
| private long pageMask; |
| private long numPages; |
| private PageFetcher fetcher; |
| private LongHashMap addressToPageMap; // Map<long, Page> |
| private Page lruList; // Most recently fetched page, or null |
| |
| /** Page fetcher plus LRU functionality */ |
| private Page getPage(long pageBaseAddress) { |
| // Check head of LRU list first to avoid hash table lookup and |
| // extra list work if possible |
| if (lruList != null) { |
| if (lruList.getBaseAddress() == pageBaseAddress) { |
| // Hit. Return it. |
| return lruList; |
| } |
| } |
| // Long key = new Long(pageBaseAddress); |
| long key = pageBaseAddress; |
| Page page = (Page) addressToPageMap.get(key); |
| if (page == null) { |
| // System.err.println("** Cache miss at address 0x" + Long.toHexString(pageBaseAddress) + " **"); |
| // Fetch new page |
| page = fetcher.fetchPage(pageBaseAddress, pageSize); |
| if (enabled) { |
| // Add to cache, evicting last element if necessary |
| addressToPageMap.put(key, page); |
| if (Assert.ASSERTS_ENABLED) { |
| Assert.that(page == (Page) addressToPageMap.get(pageBaseAddress), |
| "must have found page in cache!"); |
| } |
| addPageToList(page); |
| // See whether eviction of oldest is necessary |
| if (numPages == maxNumPages) { |
| Page evictedPage = lruList.getPrev(); |
| // System.err.println("-> Evicting page at 0x" + Long.toHexString(evictedPage.getBaseAddress()) + |
| // "; " + countPages() + " pages left (expect " + numPages + ")"); |
| removePageFromList(evictedPage); |
| addressToPageMap.remove(evictedPage.getBaseAddress()); |
| } else { |
| ++numPages; |
| } |
| } |
| } else { |
| // Page already in cache, move to front of list |
| removePageFromList(page); |
| addPageToList(page); |
| } |
| return page; |
| } |
| |
| private Page checkPage(Page page, long startAddress) { |
| if (!page.isMapped()) { |
| throw new UnmappedAddressException(startAddress); |
| } |
| return page; |
| } |
| |
| private int countPages() { |
| Page page = lruList; |
| int num = 0; |
| if (page == null) { |
| return num; |
| } |
| do { |
| ++num; |
| page = page.getNext(); |
| } while (page != lruList); |
| return num; |
| } |
| |
| private void flushPage(long pageBaseAddress) { |
| long key = pageBaseAddress; |
| Page page = (Page) addressToPageMap.remove(key); |
| if (page != null) { |
| removePageFromList(page); |
| } |
| } |
| |
| // Adds given page to head of list |
| private void addPageToList(Page page) { |
| if (lruList == null) { |
| lruList = page; |
| page.setNext(page); |
| page.setPrev(page); |
| } else { |
| // Add to front of list |
| page.setNext(lruList); |
| page.setPrev(lruList.getPrev()); |
| lruList.getPrev().setNext(page); |
| lruList.setPrev(page); |
| lruList = page; |
| } |
| } |
| |
| // Removes given page from list |
| private void removePageFromList(Page page) { |
| if (page.getNext() == page) { |
| lruList = null; |
| } else { |
| if (lruList == page) { |
| lruList = page.getNext(); |
| } |
| page.getPrev().setNext(page.getNext()); |
| page.getNext().setPrev(page.getPrev()); |
| } |
| page.setPrev(null); |
| page.setNext(null); |
| } |
| |
| /** Ensure that page size fits within 32 bits and is a power of two, and that maxNumPages > 0 */ |
| private void checkPageInfo(long pageSize, long maxNumPages) { |
| if ((pageSize <= 0) || maxNumPages <= 0) { |
| throw new IllegalArgumentException("pageSize and maxNumPages must both be greater than zero"); |
| } |
| long tmpPageSize = pageSize >>> 32; |
| if (tmpPageSize != 0) { |
| throw new IllegalArgumentException("pageSize " + pageSize + " too big (must fit within 32 bits)"); |
| } |
| int numNonZeroBits = 0; |
| for (int i = 0; i < 32; ++i) { |
| if ((pageSize & 1L) != 0) { |
| ++numNonZeroBits; |
| if ((numNonZeroBits > 1) || (i == 0)) { |
| throw new IllegalArgumentException("pageSize " + pageSize + " must be a power of two"); |
| } |
| } |
| pageSize >>>= 1; |
| if (numNonZeroBits == 0) { |
| pageMask = (pageMask << 1) | 1L; |
| } |
| } |
| pageMask = ~pageMask; |
| } |
| } |