| /* |
| * This file is part of the Jikes RVM project (http://jikesrvm.org). |
| * |
| * This file is licensed to You under the Eclipse Public License (EPL); |
| * You may not use this file except in compliance with the License. You |
| * may obtain a copy of the License at |
| * |
| * http://www.opensource.org/licenses/eclipse-1.0.php |
| * |
| * See the COPYRIGHT.txt file distributed with this work for information |
| * regarding copyright ownership. |
| */ |
| package org.mmtk.utility.heap; |
| |
| import org.mmtk.utility.alloc.EmbeddedMetaData; |
| import org.mmtk.utility.options.Options; |
| import org.mmtk.policy.Space; |
| import org.mmtk.utility.Conversions; |
| import org.mmtk.utility.Constants; |
| |
| import org.mmtk.vm.VM; |
| |
| import org.vmmagic.pragma.*; |
| import org.vmmagic.unboxed.*; |
| |
| /** |
| * This class manages the allocation of pages for a space. When a |
| * page is requested by the space both a page budget and the use of |
| * virtual address space are checked. If the request for space can't |
| * be satisfied (for either reason) a GC may be triggered.<p> |
| */ |
| @Uninterruptible |
| public final class MonotonePageResource extends PageResource |
| implements Constants { |
| |
| /**************************************************************************** |
| * |
| * Instance variables |
| */ |
| private Address cursor; |
| private Address sentinel; |
| private final int metaDataPagesPerRegion; |
| private Address currentChunk = Address.zero(); |
| |
| /** |
| * Constructor |
| * |
| * Contiguous monotone resource. The address range is pre-defined at |
| * initialization time and is immutable. |
| * |
| * @param pageBudget The budget of pages available to this memory |
| * manager before it must poll the collector. |
| * @param space The space to which this resource is attached |
| * @param start The start of the address range allocated to this resource |
| * @param bytes The size of the address rage allocated to this resource |
| * @param metaDataPagesPerRegion The number of pages of meta data |
| * that are embedded in each region. |
| */ |
| public MonotonePageResource(int pageBudget, Space space, Address start, |
| Extent bytes, int metaDataPagesPerRegion) { |
| super(pageBudget, space, start); |
| this.cursor = start; |
| this.sentinel = start.plus(bytes); |
| this.metaDataPagesPerRegion = metaDataPagesPerRegion; |
| } |
| |
| /** |
| * Constructor |
| * |
| * Discontiguous monotone resource. The address range is <i>not</i> |
| * pre-defined at initialization time and is dynamically defined to |
| * be some set of pages, according to demand and availability. |
| * |
| * CURRENTLY UNIMPLEMENTED |
| * |
| * @param pageBudget The budget of pages available to this memory |
| * manager before it must poll the collector. |
| * @param space The space to which this resource is attached |
| * @param metaDataPagesPerRegion The number of pages of meta data |
| * that are embedded in each region. |
| */ |
| public MonotonePageResource(int pageBudget, Space space, int metaDataPagesPerRegion) { |
| super(pageBudget, space); |
| /* unimplemented */ |
| this.start = Address.zero(); |
| this.cursor = Address.zero(); |
| this.sentinel = Address.zero(); |
| this.metaDataPagesPerRegion = metaDataPagesPerRegion; |
| } |
| |
| /** |
| * Return the number of available physical pages for this resource. |
| * This includes all pages currently unused by this resource's page |
| * cursor. If the resource is using discontiguous space it also includes |
| * currently unassigned discontiguous space.<p> |
| * |
| * Note: This just considers physical pages (ie virtual memory pages |
| * allocated for use by this resource). This calculation is orthogonal |
| * to and does not consider any restrictions on the number of pages |
| * this resource may actually use at any time (ie the number of |
| * committed and reserved pages).<p> |
| * |
| * Note: The calculation is made on the assumption that all space that |
| * could be assigned to this resource would be assigned to this resource |
| * (ie the unused discontiguous space could just as likely be assigned |
| * to another competing resource). |
| * |
| * @return The number of available physical pages for this resource. |
| */ |
| @Override |
| public int getAvailablePhysicalPages() { |
| int rtn = Conversions.bytesToPages(sentinel.diff(cursor)); |
| if (!contiguous) |
| rtn += Map.getAvailableDiscontiguousChunks()*Space.PAGES_IN_CHUNK; |
| return rtn; |
| } |
| |
| /** |
| * Allocate <code>pages</code> pages from this resource. Simply |
| * bump the cursor, and fail if we hit the sentinel.<p> |
| * |
| * If the request can be satisfied, then ensure the pages are |
| * mmpapped and zeroed before returning the address of the start of |
| * the region. If the request cannot be satisfied, return zero. |
| * |
| * @param requestPages The number of pages to be allocated. |
| * @return The start of the first page if successful, zero on |
| * failure. |
| */ |
| @Inline |
| protected Address allocPages(int requestPages) { |
| int pages = requestPages; |
| boolean newChunk = false; |
| lock(); |
| Address rtn = cursor; |
| if (Space.chunkAlign(rtn, true).NE(currentChunk)) { |
| newChunk = true; |
| currentChunk = Space.chunkAlign(rtn, true); |
| } |
| |
| if (metaDataPagesPerRegion != 0) { |
| /* adjust allocation for metadata */ |
| Address regionStart = getRegionStart(cursor.plus(Conversions.pagesToBytes(pages))); |
| Offset regionDelta = regionStart.diff(cursor); |
| if (regionDelta.sGE(Offset.zero())) { |
| /* start new region, so adjust pages and return address accordingly */ |
| pages += Conversions.bytesToPages(regionDelta) + metaDataPagesPerRegion; |
| rtn = regionStart.plus(Conversions.pagesToBytes(metaDataPagesPerRegion)); |
| } |
| } |
| Extent bytes = Conversions.pagesToBytes(pages); |
| Address tmp = cursor.plus(bytes); |
| |
| if (!contiguous && tmp.GT(sentinel)) { |
| /* we're out of virtual memory within our discontiguous region, so ask for more */ |
| int requiredChunks = Space.requiredChunks(pages); |
| start = space.growDiscontiguousSpace(requiredChunks); |
| cursor = start; |
| sentinel = cursor.plus(start.isZero() ? 0 : requiredChunks<<Space.LOG_BYTES_IN_CHUNK); |
| rtn = cursor; |
| tmp = cursor.plus(bytes); |
| newChunk = true; |
| } |
| if (VM.VERIFY_ASSERTIONS) |
| VM.assertions._assert(rtn.GE(cursor) && rtn.LT(cursor.plus(bytes))); |
| if (tmp.GT(sentinel)) { |
| unlock(); |
| return Address.zero(); |
| } else { |
| Address old = cursor; |
| cursor = tmp; |
| commitPages(requestPages, pages); |
| space.growSpace(old, bytes, newChunk); |
| unlock(); |
| Mmapper.ensureMapped(old, pages); |
| VM.memory.zero(old, bytes); |
| VM.events.tracePageAcquired(space, rtn, pages); |
| return rtn; |
| } |
| } |
| |
| /** |
| * Adjust a page request to include metadata requirements, if any.<p> |
| * |
| * In this case we simply report the expected page cost. We can't use |
| * worst case here because we would exhaust our budget every time. |
| * |
| * @param pages The size of the pending allocation in pages |
| * @return The number of required pages, inclusive of any metadata |
| */ |
| public int adjustForMetaData(int pages) { |
| return (metaDataPagesPerRegion * pages) / EmbeddedMetaData.PAGES_IN_REGION; |
| } |
| |
| /** |
| * Adjust a page request to include metadata requirements, if any.<p> |
| * |
| * Note that there could be a race here, with multiple threads each |
| * adjusting their request on account of the same single metadata |
| * region. This should not be harmful, as the failing requests will |
| * just retry, and if multiple requests succeed, only one of them |
| * will actually have the metadata accounted against it, the others |
| * will simply have more space than they originally requested. |
| * |
| * @param pages The size of the pending allocation in pages |
| * @param begin The start address of the region assigned to this pending |
| * request |
| * @return The number of required pages, inclusive of any metadata |
| */ |
| public int adjustForMetaData(int pages, Address begin) { |
| if (getRegionStart(begin).plus(metaDataPagesPerRegion<<LOG_BYTES_IN_PAGE).EQ(begin)) |
| pages += metaDataPagesPerRegion; |
| return pages; |
| } |
| |
| private static Address getRegionStart(Address addr) { |
| return addr.toWord().and(Word.fromIntSignExtend(EmbeddedMetaData.BYTES_IN_REGION - 1).not()).toAddress(); |
| } |
| |
| /** |
| * Reset this page resource, freeing all pages and resetting |
| * reserved and committed pages appropriately. |
| */ |
| @Inline |
| public void reset() { |
| lock(); |
| reserved = 0; |
| committed = 0; |
| releasePages(); |
| unlock(); |
| } |
| |
| /** |
| * Notify that several pages are no longer in use. |
| * |
| * @param pages The number of pages |
| */ |
| public void unusePages(int pages) { |
| lock(); |
| reserved -= pages; |
| committed -= pages; |
| unlock(); |
| } |
| |
| /** |
| * Notify that previously unused pages are in use again. |
| * |
| * @param pages The number of pages |
| */ |
| public void reusePages(int pages) { |
| lock(); |
| reserved += pages; |
| committed += pages; |
| unlock(); |
| } |
| |
| /** |
| * Release all pages associated with this page resource, optionally |
| * zeroing on release and optionally memory protecting on release. |
| */ |
| @Inline |
| private void releasePages() { |
| Address first = start; |
| do { |
| Extent bytes = cursor.diff(start).toWord().toExtent(); |
| releasePages(start, bytes); |
| cursor = start; |
| } while (!contiguous && moveToNextChunk()); |
| if (!contiguous) { |
| sentinel = Address.zero(); |
| Map.freeAllChunks(first); |
| } |
| } |
| |
| /** |
| * Adjust the start and cursor fields to point to the next chunk |
| * in the linked list of chunks tied down by this page resource. |
| * |
| * @return True if we moved to the next chunk; false if we hit the |
| * end of the linked list. |
| */ |
| private boolean moveToNextChunk() { |
| start = Map.getNextContiguousRegion(start); |
| if (start.isZero()) |
| return false; |
| else { |
| cursor = start.plus(Map.getContiguousRegionSize(start)); |
| return true; |
| } |
| } |
| |
| /** |
| * Release a range of pages associated with this page resource, optionally |
| * zeroing on release and optionally memory protecting on release. |
| */ |
| @Inline |
| private void releasePages(Address first, Extent bytes) { |
| int pages = Conversions.bytesToPages(bytes); |
| if (VM.VERIFY_ASSERTIONS) |
| VM.assertions._assert(bytes.EQ(Conversions.pagesToBytes(pages))); |
| if (ZERO_ON_RELEASE) |
| VM.memory.zero(first, bytes); |
| if (Options.protectOnRelease.getValue()) |
| Mmapper.protect(first, pages); |
| VM.events.tracePageReleased(space, first, pages); |
| } |
| } |