| /* | |
| * Copyright (c) 2000, 2022, 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 shareable 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; | |
| } | |
| } |