001/*
002 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003 *
004 *  This file is licensed to You under the Eclipse Public License (EPL);
005 *  You may not use this file except in compliance with the License. You
006 *  may obtain a copy of the License at
007 *
008 *      http://www.opensource.org/licenses/eclipse-1.0.php
009 *
010 *  See the COPYRIGHT.txt file distributed with this work for information
011 *  regarding copyright ownership.
012 */
013package org.mmtk.utility.heap;
014
015import static org.mmtk.utility.Constants.*;
016
017import org.mmtk.policy.Space;
018import org.mmtk.utility.Conversions;
019import org.mmtk.utility.alloc.EmbeddedMetaData;
020import org.mmtk.utility.options.Options;
021
022import org.mmtk.vm.VM;
023
024import org.vmmagic.pragma.Inline;
025import org.vmmagic.pragma.Uninterruptible;
026import org.vmmagic.unboxed.Address;
027import org.vmmagic.unboxed.Extent;
028import org.vmmagic.unboxed.Offset;
029import org.vmmagic.unboxed.Word;
030
031/**
032 * This class manages the allocation of pages for a space.  When a
033 * page is requested by the space both a page budget and the use of
034 * virtual address space are checked.  If the request for space can't
035 * be satisfied (for either reason) a GC may be triggered.
036 */
037@Uninterruptible
038public final class MonotonePageResource extends PageResource {
039
040  /****************************************************************************
041   *
042   * Instance variables
043   */
044
045  /**
046   *
047   */
048  private Address cursor;
049  private Address sentinel;
050  private final int metaDataPagesPerRegion;
051  private Address currentChunk = Address.zero();
052  private volatile Address zeroingCursor;
053  private Address zeroingSentinel;
054
055  /**
056   * Constructor
057   *
058   * Contiguous monotone resource. The address range is pre-defined at
059   * initialization time and is immutable.
060   *
061   * @param space The space to which this resource is attached
062   * @param start The start of the address range allocated to this resource
063   * @param bytes The size of the address rage allocated to this resource
064   * @param metaDataPagesPerRegion The number of pages of meta data
065   * that are embedded in each region.
066   */
067  public MonotonePageResource(Space space, Address start, Extent bytes, int metaDataPagesPerRegion) {
068    super(space, start);
069    this.cursor = start;
070    this.sentinel = start.plus(bytes);
071    this.zeroingCursor = this.sentinel;
072    this.zeroingSentinel = start;
073    this.metaDataPagesPerRegion = metaDataPagesPerRegion;
074  }
075
076  /**
077   * Constructor
078   *
079   * Discontiguous monotone resource. The address range is <i>not</i>
080   * pre-defined at initialization time and is dynamically defined to
081   * be some set of pages, according to demand and availability.
082   *
083   * @param space The space to which this resource is attached
084   * @param metaDataPagesPerRegion The number of pages of meta data
085   * that are embedded in each region.
086   */
087  public MonotonePageResource(Space space, int metaDataPagesPerRegion) {
088    super(space);
089    this.cursor = Address.zero();
090    this.sentinel = Address.zero();
091    this.metaDataPagesPerRegion = metaDataPagesPerRegion;
092  }
093
094
095  @Override
096  public int getAvailablePhysicalPages() {
097    int rtn = Conversions.bytesToPages(sentinel.diff(cursor));
098    if (!contiguous)
099      rtn += Map.getAvailableDiscontiguousChunks() * Space.PAGES_IN_CHUNK;
100    return rtn;
101  }
102
103  /**
104   * Allocate <code>pages</code> pages from this resource.  Simply
105   * bump the cursor, and fail if we hit the sentinel.<p>
106   *
107   * If the request can be satisfied, then ensure the pages are
108   * mmpapped and zeroed before returning the address of the start of
109   * the region.  If the request cannot be satisfied, return zero.
110   *
111   * @param reservedPages The number of pages reserved due to the initial request.
112   * @param requiredPages The number of pages required to be allocated.
113   * @return The start of the first page if successful, zero on
114   * failure.
115   */
116  @Override
117  @Inline
118  protected Address allocPages(int reservedPages, int requiredPages, boolean zeroed) {
119    boolean newChunk = false;
120    lock();
121    Address rtn = cursor;
122    if (Space.chunkAlign(rtn, true).NE(currentChunk)) {
123      newChunk = true;
124      currentChunk = Space.chunkAlign(rtn, true);
125    }
126
127    if (metaDataPagesPerRegion != 0) {
128      /* adjust allocation for metadata */
129      Address regionStart = getRegionStart(cursor.plus(Conversions.pagesToBytes(requiredPages)));
130      Offset regionDelta = regionStart.diff(cursor);
131      if (regionDelta.sGE(Offset.zero())) {
132        /* start new region, so adjust pages and return address accordingly */
133        requiredPages += Conversions.bytesToPages(regionDelta) + metaDataPagesPerRegion;
134        rtn = regionStart.plus(Conversions.pagesToBytes(metaDataPagesPerRegion));
135      }
136    }
137    Extent bytes = Conversions.pagesToBytes(requiredPages);
138    Address tmp = cursor.plus(bytes);
139
140    if (!contiguous && tmp.GT(sentinel)) {
141      /* we're out of virtual memory within our discontiguous region, so ask for more */
142      int requiredChunks = Space.requiredChunks(requiredPages);
143      Address chunk = space.growDiscontiguousSpace(requiredChunks); // Returns zero on failure
144      cursor = chunk;
145      sentinel = cursor.plus(chunk.isZero() ? 0 : requiredChunks << Space.LOG_BYTES_IN_CHUNK);
146      rtn = cursor;
147      tmp = cursor.plus(bytes);
148      newChunk = true;
149    }
150    if (VM.VERIFY_ASSERTIONS)
151      VM.assertions._assert(rtn.GE(cursor) && rtn.LT(cursor.plus(bytes)));
152    if (tmp.GT(sentinel)) {
153      unlock();
154      return Address.zero();
155    } else {
156      Address old = cursor;
157      cursor = tmp;
158      commitPages(reservedPages, requiredPages);
159      space.growSpace(old, bytes, newChunk);
160      unlock();
161      Mmapper.ensureMapped(old, requiredPages);
162      if (zeroed) {
163        if (!zeroConcurrent) {
164          VM.memory.zero(zeroNT, old, bytes);
165        } else {
166          while (cursor.GT(zeroingCursor));
167        }
168      }
169      VM.events.tracePageAcquired(space, rtn, requiredPages);
170      return rtn;
171    }
172  }
173
174  /**
175   * {@inheritDoc}<p>
176   *
177   * In this case we simply report the expected page cost. We can't use
178   * worst case here because we would exhaust our budget every time.
179   */
180  @Override
181  public int adjustForMetaData(int pages) {
182    return pages + ((pages + EmbeddedMetaData.PAGES_IN_REGION - 1) >> EmbeddedMetaData.LOG_PAGES_IN_REGION) * metaDataPagesPerRegion;
183  }
184
185  /**
186   * Adjust a page request to include metadata requirements, if any.<p>
187   *
188   * Note that there could be a race here, with multiple threads each
189   * adjusting their request on account of the same single metadata
190   * region.  This should not be harmful, as the failing requests will
191   * just retry, and if multiple requests succeed, only one of them
192   * will actually have the metadata accounted against it, the others
193   * will simply have more space than they originally requested.
194   *
195   * @param pages The size of the pending allocation in pages
196   * @param begin The start address of the region assigned to this pending
197   * request
198   * @return The number of required pages, inclusive of any metadata
199   */
200  public int adjustForMetaData(int pages, Address begin) {
201    if (getRegionStart(begin).plus(metaDataPagesPerRegion << LOG_BYTES_IN_PAGE).EQ(begin)) {
202      pages += metaDataPagesPerRegion;
203    }
204    return pages;
205  }
206
207  private static Address getRegionStart(Address addr) {
208    return addr.toWord().and(Word.fromIntSignExtend(EmbeddedMetaData.BYTES_IN_REGION - 1).not()).toAddress();
209  }
210
211  /**
212   * Reset this page resource, freeing all pages and resetting
213   * reserved and committed pages appropriately.
214   */
215  @Inline
216  public void reset() {
217    lock();
218    reserved = 0;
219    committed = 0;
220    releasePages();
221    unlock();
222  }
223
224  /**
225   * Notify that several pages are no longer in use.
226   *
227   * @param pages The number of pages
228   */
229  public void unusePages(int pages) {
230    lock();
231    reserved -= pages;
232    committed -= pages;
233    unlock();
234  }
235
236  /**
237   * Notify that previously unused pages are in use again.
238   *
239   * @param pages The number of pages
240   */
241  public void reusePages(int pages) {
242    lock();
243    reserved += pages;
244    committed += pages;
245    unlock();
246  }
247
248  /**
249   * Release all pages associated with this page resource, optionally
250   * zeroing on release and optionally memory protecting on release.
251   */
252  @Inline
253  private void releasePages() {
254    if (contiguous) {
255      // TODO: We will perform unnecessary zeroing if the nursery size has decreased.
256      if (zeroConcurrent) {
257        // Wait for current zeroing to finish.
258        while (zeroingCursor.LT(zeroingSentinel)) { }
259      }
260      // Reset zeroing region.
261      if (cursor.GT(zeroingSentinel)) {
262        zeroingSentinel = cursor;
263      }
264      zeroingCursor = start;
265      cursor = start;
266    } else { /* Not contiguous */
267      if (!cursor.isZero()) {
268        do {
269          Extent bytes = cursor.diff(currentChunk).toWord().toExtent();
270          releasePages(currentChunk, bytes);
271        } while (moveToNextChunk());
272
273        currentChunk = Address.zero();
274        sentinel = Address.zero();
275        cursor = Address.zero();
276        space.releaseAllChunks();
277      }
278    }
279  }
280
281  /**
282   * Adjust the currentChunk and cursor fields to point to the next chunk
283   * in the linked list of chunks tied down by this page resource.
284   *
285   * @return {@code true} if we moved to the next chunk; {@code false} if we hit the
286   * end of the linked list.
287   */
288  private boolean moveToNextChunk() {
289    currentChunk = Map.getNextContiguousRegion(currentChunk);
290    if (currentChunk.isZero())
291      return false;
292    else {
293      cursor = currentChunk.plus(Map.getContiguousRegionSize(currentChunk));
294      return true;
295    }
296  }
297
298  /**
299   * Releases a range of pages associated with this page resource, optionally
300   * zeroing on release and optionally memory protecting on release.
301   *
302   * @param first start address of memory to be released
303   * @param bytes number of bytes in the memory region
304   */
305  @Inline
306  private void releasePages(Address first, Extent bytes) {
307    int pages = Conversions.bytesToPages(bytes);
308    if (VM.VERIFY_ASSERTIONS)
309      VM.assertions._assert(bytes.EQ(Conversions.pagesToBytes(pages)));
310    if (VM.config.ZERO_PAGES_ON_RELEASE)
311      VM.memory.zero(false, first, bytes);
312    if (Options.protectOnRelease.getValue())
313      Mmapper.protect(first, pages);
314    VM.events.tracePageReleased(space, first, pages);
315  }
316
317  private static int CONCURRENT_ZEROING_BLOCKSIZE = 1 << 16;
318
319  @Override
320  public void concurrentZeroing() {
321    if (VM.VERIFY_ASSERTIONS) {
322      VM.assertions._assert(zeroConcurrent);
323    }
324    Address first = start;
325    while (first.LT(zeroingSentinel)) {
326      Address last = first.plus(CONCURRENT_ZEROING_BLOCKSIZE);
327      if (last.GT(zeroingSentinel)) last = zeroingSentinel;
328      VM.memory.zero(zeroNT, first, Extent.fromIntSignExtend(last.diff(first).toInt()));
329      zeroingCursor = last;
330      first = first.plus(CONCURRENT_ZEROING_BLOCKSIZE);
331    }
332    zeroingCursor = sentinel;
333  }
334}