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