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.alloc;
014    
015    import org.mmtk.policy.Space;
016    import org.mmtk.utility.Constants;
017    import org.mmtk.utility.Conversions;
018    import org.mmtk.utility.Log;
019    import org.mmtk.utility.gcspy.drivers.LinearSpaceDriver;
020    import org.mmtk.vm.VM;
021    import org.vmmagic.pragma.Inline;
022    import org.vmmagic.pragma.NoInline;
023    import org.vmmagic.pragma.Uninterruptible;
024    import org.vmmagic.unboxed.Address;
025    import org.vmmagic.unboxed.Extent;
026    import org.vmmagic.unboxed.ObjectReference;
027    import org.vmmagic.unboxed.Offset;
028    import org.vmmagic.unboxed.Word;
029    
030    /**
031     * This class implements a bump pointer allocator that allows linearly
032     * scanning through the allocated objects. In order to achieve this in the
033     * face of parallelism it maintains a header at a region (1 or more blocks)
034     * granularity.<p>
035     *
036     * Intra-block allocation is fast, requiring only a load, addition comparison
037     * and store.  If a block boundary is encountered the allocator will
038     * request more memory (virtual and actual).<p>
039     *
040     * In the current implementation the scanned objects maintain affinity
041     * with the thread that allocated the objects in the region. In the future
042     * it is anticipated that subclasses should be allowed to choose to improve
043     * load balancing during the parallel scan.<p>
044     *
045     * Each region is laid out as follows:
046     * <pre>
047     *
048     *  +-------------+-------------+-------------+---------------
049     *  | Region  End | Next Region |  Data  End  | Data -->
050     * +-------------+-------------+-------------+---------------
051     *
052     * </pre>
053     *
054     * The minimum region size is 32768 bytes, so the 3 or 4 word overhead is
055     * less than 0.05% of all space.<p>
056     *
057     * An intended enhancement is to facilitate a reallocation operation
058     * where a second cursor is maintained over earlier regions (and at the
059     * limit a lower location in the same region). This would be accompianied
060     * with an alternative slow path that would allow reuse of empty regions.<p>
061     *
062     * This class relies on the supporting virtual machine implementing the
063     * getNextObject and related operations.
064     */
065    @Uninterruptible public class BumpPointer extends Allocator
066      implements Constants {
067    
068      /****************************************************************************
069       *
070       * Class variables
071       */
072    
073      // Block size defines slow path periodicity.
074    
075      /**
076       *
077       */
078      private static final int LOG_DEFAULT_STEP_SIZE = 30; // 1G: let the external slow path dominate
079      private static final int STEP_SIZE = 1<<(SUPPORT_CARD_SCANNING ? LOG_CARD_BYTES : LOG_DEFAULT_STEP_SIZE);
080      protected static final int LOG_BLOCK_SIZE = LOG_BYTES_IN_PAGE + 3;
081      protected static final Word BLOCK_MASK = Word.one().lsh(LOG_BLOCK_SIZE).minus(Word.one());
082      private static final int BLOCK_SIZE = (1<<LOG_BLOCK_SIZE);
083    
084    
085      // Offsets into header
086      protected static final Offset REGION_LIMIT_OFFSET = Offset.zero();
087      protected static final Offset NEXT_REGION_OFFSET = REGION_LIMIT_OFFSET.plus(BYTES_IN_ADDRESS);
088      protected static final Offset DATA_END_OFFSET = NEXT_REGION_OFFSET.plus(BYTES_IN_ADDRESS);
089    
090      // Data must start particle-aligned.
091      protected static final Offset DATA_START_OFFSET = alignAllocationNoFill(
092          Address.zero().plus(DATA_END_OFFSET.plus(BYTES_IN_ADDRESS)),
093          MIN_ALIGNMENT, 0).toWord().toOffset();
094      protected static final Offset MAX_DATA_START_OFFSET = alignAllocationNoFill(
095          Address.zero().plus(DATA_END_OFFSET.plus(BYTES_IN_ADDRESS)),
096          MAX_ALIGNMENT, 0).toWord().toOffset();
097    
098      public static final int MINIMUM_DATA_SIZE = (1 << LOG_BLOCK_SIZE) - MAX_DATA_START_OFFSET.toInt();
099    
100      private static final int SIZE_OF_TWO_X86_CACHE_LINES_IN_BYTES = 128;
101    
102      private static final boolean VERBOSE = false;
103    
104      /****************************************************************************
105       *
106       * Instance variables
107       */
108    
109      /** insertion point */
110      protected Address cursor;
111      /** current internal slow-path sentinel for bump pointer */
112      private Address internalLimit;
113      /** current external slow-path sentinel for bump pointer */
114      private Address limit;
115      /**  space this bump pointer is associated with */
116      protected Space space;
117      /** first contiguous region */
118      protected Address initialRegion;
119      /** linear scanning is permitted if true */
120      protected final boolean allowScanning;
121      /** current contiguous region */
122      protected Address region;
123    
124    
125      /**
126       * Constructor.
127       *
128       * @param space The space to bump point into.
129       * @param allowScanning Allow linear scanning of this region of memory.
130       */
131      protected BumpPointer(Space space, boolean allowScanning) {
132        this.space = space;
133        this.allowScanning = allowScanning;
134        reset();
135      }
136    
137      /**
138       * Reset the allocator. Note that this does not reset the space.
139       * This is must be done by the caller.
140       */
141      public final void reset() {
142        cursor = Address.zero();
143        limit = Address.zero();
144        internalLimit = Address.zero();
145        initialRegion = Address.zero();
146        region = Address.zero();
147      }
148    
149      /**
150       * Re-associate this bump pointer with a different space. Also
151       * reset the bump pointer so that it will use the new space
152       * on the next call to <code>alloc</code>.
153       *
154       * @param space The space to associate the bump pointer with.
155       */
156      public final void rebind(Space space) {
157        reset();
158        this.space = space;
159      }
160    
161      /**
162       * Allocate space for a new object.  This is frequently executed code and
163       * the coding is deliberately sensitive to the optimizing compiler.
164       * After changing this, always check the IR/MC that is generated.
165       *
166       * @param bytes The number of bytes allocated
167       * @param align The requested alignment
168       * @param offset The offset from the alignment
169       * @return The address of the first byte of the allocated region
170       */
171      @Inline
172      public final Address alloc(int bytes, int align, int offset) {
173        Address start = alignAllocationNoFill(cursor, align, offset);
174        Address end = start.plus(bytes);
175        if (end.GT(internalLimit))
176          return allocSlow(start, end, align, offset);
177        fillAlignmentGap(cursor, start);
178        cursor = end;
179        end.plus(SIZE_OF_TWO_X86_CACHE_LINES_IN_BYTES).prefetch();
180        return start;
181      }
182    
183     /**
184      * Internal allocation slow path.  This is called whenever the bump
185      * pointer reaches the internal limit.  The code is forced out of
186      * line.  If required we perform an external slow path take, which
187      * we inline into this method since this is already out of line.
188      *
189      * @param start The start address for the pending allocation
190     * @param end The end address for the pending allocation
191     * @param align The requested alignment
192     * @param offset The offset from the alignment
193      * @return The address of the first byte of the allocated region
194      */
195      @NoInline
196      private Address allocSlow(Address start, Address end, int align,
197          int offset) {
198        Address rtn = null;
199        Address card = null;
200        if (SUPPORT_CARD_SCANNING)
201          card = getCard(start.plus(CARD_MASK)); // round up
202        if (end.GT(limit)) { /* external slow path */
203          rtn = allocSlowInline(end.diff(start).toInt(), align, offset);
204          if (SUPPORT_CARD_SCANNING && card.NE(getCard(rtn.plus(CARD_MASK))))
205            card = getCard(rtn); // round down
206        } else {             /* internal slow path */
207          while (internalLimit.LE(end))
208            internalLimit = internalLimit.plus(STEP_SIZE);
209          if (internalLimit.GT(limit))
210            internalLimit = limit;
211          fillAlignmentGap(cursor, start);
212          cursor = end;
213          rtn = start;
214        }
215        if (SUPPORT_CARD_SCANNING && !rtn.isZero())
216          createCardAnchor(card, rtn, end.diff(start).toInt());
217        return rtn;
218      }
219    
220      /**
221       * Given an allocation which starts a new card, create a record of
222       * where the start of the object is relative to the start of the
223       * card.
224       *
225       * @param card An address that lies within the card to be marked
226       * @param start The address of an object which creates a new card.
227       * @param bytes The size of the pending allocation in bytes (used for debugging)
228       */
229      private void createCardAnchor(Address card, Address start, int bytes) {
230        if (VM.VERIFY_ASSERTIONS) {
231          VM.assertions._assert(allowScanning);
232          VM.assertions._assert(card.EQ(getCard(card)));
233          VM.assertions._assert(start.diff(card).sLE(MAX_DATA_START_OFFSET));
234          VM.assertions._assert(start.diff(card).toInt() >= -CARD_MASK);
235        }
236        while (bytes > 0) {
237          int offset = start.diff(card).toInt();
238          getCardMetaData(card).store(offset);
239          card = card.plus(1 << LOG_CARD_BYTES);
240          bytes -= (1 << LOG_CARD_BYTES);
241        }
242      }
243    
244      /**
245       * Return the start of the card corresponding to a given address.
246       *
247       * @param address The address for which the card start is required
248       * @return The start of the card containing the address
249       */
250      private static Address getCard(Address address) {
251        return address.toWord().and(Word.fromIntSignExtend(CARD_MASK).not()).toAddress();
252      }
253    
254      /**
255       * Return the address of the metadata for a card, given the address of the card.
256       * @param card The address of some card
257       * @return The address of the metadata associated with that card
258       */
259      private static Address getCardMetaData(Address card) {
260        Address metadata = EmbeddedMetaData.getMetaDataBase(card);
261        return metadata.plus(EmbeddedMetaData.getMetaDataOffset(card, LOG_CARD_BYTES-LOG_CARD_META_SIZE, LOG_CARD_META_SIZE));
262      }
263    
264      /**
265       * External allocation slow path (called by superclass when slow path is
266       * actually taken.  This is necessary (rather than a direct call
267       * from the fast path) because of the possibility of a thread switch
268       * and corresponding re-association of bump pointers to kernel
269       * threads.
270       *
271       * @param bytes The number of bytes allocated
272       * @param offset The offset from the alignment
273       * @param align The requested alignment
274       * @return The address of the first byte of the allocated region or
275       * zero on failure
276       */
277      @Override
278      protected final Address allocSlowOnce(int bytes, int align, int offset) {
279        /* Check we have been bound to a space */
280        if (space == null) {
281          VM.assertions.fail("Allocation on unbound bump pointer.");
282        }
283    
284        /* Check if we already have a block to use */
285        if (allowScanning && !region.isZero()) {
286          Address nextRegion = getNextRegion(region);
287          if (!nextRegion.isZero()) {
288            return consumeNextRegion(nextRegion, bytes, align, offset);
289          }
290        }
291    
292        /* Acquire space, block aligned, that can accommodate the request */
293        Extent blockSize = Word.fromIntZeroExtend(bytes).plus(BLOCK_MASK)
294                           .and(BLOCK_MASK.not()).toExtent();
295        Address start = space.acquire(Conversions.bytesToPages(blockSize));
296    
297        if (start.isZero()) return start; // failed allocation
298    
299        if (!allowScanning) { // simple allocator
300          if (start.NE(limit)) cursor = start;  // discontiguous
301          updateLimit(start.plus(blockSize), start, bytes);
302        } else                // scannable allocator
303          updateMetaData(start, blockSize, bytes);
304        return alloc(bytes, align, offset);
305      }
306    
307      /**
308       * Update the limit pointer.  As a side effect update the internal limit
309       * pointer appropriately.
310       *
311       * @param newLimit The new value for the limit pointer
312       * @param start The start of the region to be allocated into
313       * @param bytes The size of the pending allocation (if any).
314       */
315      @Inline
316      protected final void updateLimit(Address newLimit, Address start, int bytes) {
317        limit = newLimit;
318        internalLimit = start.plus(STEP_SIZE);
319        if (internalLimit.GT(limit))
320          internalLimit = limit;
321        else {
322          while (internalLimit.LT(cursor.plus(bytes)))
323            internalLimit = internalLimit.plus(STEP_SIZE);
324          if (VM.VERIFY_ASSERTIONS)
325            VM.assertions._assert(internalLimit.LE(limit));
326        }
327      }
328    
329      /**
330       * A bump pointer chunk/region has been consumed but the contiguous region
331       * is available, so consume it and then return the address of the start
332       * of a memory region satisfying the outstanding allocation request.  This
333       * is relevant when re-using memory, as in a mark-compact collector.
334       *
335       * @param nextRegion The region to be consumed
336       * @param bytes The number of bytes allocated
337       * @param align The requested alignment
338       * @param offset The offset from the alignment
339       * @return The address of the first byte of the allocated region or
340       * zero on failure
341       */
342      private Address consumeNextRegion(Address nextRegion, int bytes, int align,
343            int offset) {
344        setNextRegion(region,cursor);
345        region = nextRegion;
346        cursor = getDataStart(nextRegion);
347        updateLimit(getRegionLimit(nextRegion), nextRegion, bytes);
348        setDataEnd(nextRegion,Address.zero());
349        VM.memory.zero(false, cursor, limit.diff(cursor).toWord().toExtent());
350        reusePages(Conversions.bytesToPages(limit.diff(region)));
351    
352        return alloc(bytes, align, offset);
353      }
354    
355      /******************************************************************************
356       *
357       *   Accessor methods for the region metadata fields.
358       *
359       */
360    
361      /**
362       * The first offset in a region after the header
363       * @param region The region
364       * @return The lowest address at which data can be stored
365       */
366      @Inline
367      public static Address getDataStart(Address region) {
368        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!region.isZero());
369        return region.plus(DATA_START_OFFSET);
370      }
371    
372      /**
373       * The next region in the linked-list of regions
374       * @param region The region
375       * @return The next region in the list
376       */
377      @Inline
378      public static Address getNextRegion(Address region) {
379        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!region.isZero());
380        Address result = region.plus(NEXT_REGION_OFFSET).loadAddress();
381        return result;
382      }
383    
384      /**
385       * Set the next region in the linked-list of regions
386       * @param region The region
387       * @param nextRegion the next region in the list
388       */
389      @Inline
390      public static void setNextRegion(Address region, Address nextRegion) {
391        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!region.isZero());
392        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!nextRegion.EQ(Address.fromIntZeroExtend(0xdeadbeef)));
393        region.store(nextRegion,NEXT_REGION_OFFSET);
394      }
395    
396      /**
397       * Clear the next region pointer in the linked-list of regions
398       * @param region The region
399       */
400      @Inline
401      public static void clearNextRegion(Address region) {
402        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!region.isZero());
403        region.store(Address.zero(),NEXT_REGION_OFFSET);
404      }
405    
406      /**
407       * @param region The bump-pointer region
408       * @return The DATA_END address from the region header
409       */
410      @Inline
411      public static Address getDataEnd(Address region) {
412        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!region.isZero());
413        return region.plus(DATA_END_OFFSET).loadAddress();
414      }
415    
416      /**
417       * @param region The bump-pointer region
418       * @param endAddress The new DATA_END address from the region header
419       */
420      public static void setDataEnd(Address region, Address endAddress) {
421        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!region.isZero());
422        region.store(endAddress, DATA_END_OFFSET);
423        if (VERBOSE) {
424          Log.write("setDataEnd(");
425          Log.write(region);
426          Log.write(",");
427          Log.write(endAddress);
428          Log.writeln(")");
429        }
430      }
431    
432      /**
433       * Return the end address of the given region.
434       * @param region The region.
435       * @return the allocation limit of the region.
436       */
437      public static Address getRegionLimit(Address region) {
438        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!region.isZero());
439        return region.plus(REGION_LIMIT_OFFSET).loadAddress();
440      }
441    
442      /**
443       * Store the limit value at the end of the region.
444       */
445      public static void setRegionLimit(Address region, Address limit) {
446        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!region.isZero());
447        region.plus(REGION_LIMIT_OFFSET).store(limit);
448      }
449    
450      /**
451       * @param region The region.
452       * @return {@code true} if the address is region-aligned
453       */
454      public static boolean isRegionAligned(Address region) {
455        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!region.isZero());
456        return region.toWord().and(BLOCK_MASK).isZero();
457      }
458    
459      /**
460       * Sanity check a region header
461       * @param region Region to check
462       */
463      public static void checkRegionMetadata(Address region) {
464        if (VM.VERIFY_ASSERTIONS) {
465          Address nextRegion = getNextRegion(region);
466          Address dataStart = getDataStart(region);
467          Address dataEnd = getDataEnd(region);
468          Address regionLimit = getRegionLimit(region);
469    
470          VM.assertions._assert(nextRegion.isZero() || isRegionAligned(nextRegion));
471          VM.assertions._assert(dataEnd.GE(dataStart));
472          if (dataEnd.GT(regionLimit)) {
473            Log.write("dataEnd="); Log.write(dataEnd);
474            Log.write(", regionLimit="); Log.writeln(regionLimit);
475          }
476          VM.assertions._assert(dataEnd.LE(regionLimit));
477          VM.assertions._assert(regionLimit.EQ(region.plus(BLOCK_SIZE)));
478        }
479    
480      }
481      /**
482       * Update the metadata to reflect the addition of a new region.
483       *
484       * @param start The start of the new region
485       * @param size The size of the new region (rounded up to block-alignment)
486       */
487      @Inline
488      private void updateMetaData(Address start, Extent size, int bytes) {
489        if (initialRegion.isZero()) {
490          /* this is the first allocation */
491          initialRegion = start;
492          region = start;
493          cursor = region.plus(DATA_START_OFFSET);
494        } else if (limit.NE(start) ||
495                   region.diff(start.plus(size)).toWord().toExtent().GT(maximumRegionSize())) {
496          /* non contiguous or over-size, initialize new region */
497          setNextRegion(region,start);
498          setDataEnd(region,cursor);
499          region = start;
500          cursor = start.plus(DATA_START_OFFSET);
501        }
502        updateLimit(start.plus(size), start, bytes);
503        setRegionLimit(region,limit);
504      }
505    
506      /**
507       * Gather data for GCspy. <p>
508       * This method calls the drivers linear scanner to scan through
509       * the objects allocated by this bump pointer.
510       *
511       * @param driver The GCspy driver for this space.
512       */
513      public void gcspyGatherData(LinearSpaceDriver driver) {
514        //driver.setRange(space.getStart(), cursor);
515        driver.setRange(space.getStart(), limit);
516        this.linearScan(driver.getScanner());
517      }
518    
519      /**
520       * Gather data for GCspy. <p>
521       * This method calls the drivers linear scanner to scan through
522       * the objects allocated by this bump pointer.
523       *
524       * @param driver The GCspy driver for this space.
525       * @param scanSpace The space to scan
526       */
527      public void gcspyGatherData(LinearSpaceDriver driver, Space scanSpace) {
528        //TODO can scanSpace ever be different to this.space?
529        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(scanSpace == space, "scanSpace != space");
530    
531        //driver.setRange(scanSpace.getStart(), cursor);
532        Address start = scanSpace.getStart();
533        driver.setRange(start, limit);
534    
535        if (false) {
536          Log.write("\nBumpPointer.gcspyGatherData set Range "); Log.write(scanSpace.getStart());
537          Log.write(" to "); Log.writeln(limit);
538          Log.write("BumpPointergcspyGatherData scan from "); Log.writeln(initialRegion);
539        }
540    
541        linearScan(driver.getScanner());
542      }
543    
544    
545      /**
546       * Perform a linear scan through the objects allocated by this bump pointer.
547       *
548       * @param scanner The scan object to delegate scanning to.
549       */
550      @Inline
551      public final void linearScan(LinearScan scanner) {
552        if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(allowScanning);
553        /* Has this allocator ever allocated anything? */
554        if (initialRegion.isZero()) return;
555    
556        /* Loop through active regions or until the last region */
557        Address start = initialRegion;
558        while (!start.isZero()) {
559          scanRegion(scanner, start); // Scan this region
560          start = getNextRegion(start); // Move on to next
561        }
562      }
563    
564      /**
565       * Perform a linear scan through a single contiguous region
566       *
567       * @param scanner The scan object to delegate to.
568       * @param start The start of this region
569       */
570      @Inline
571      private void scanRegion(LinearScan scanner, Address start) {
572        /* Get the end of this region */
573        Address dataEnd = start.plus(DATA_END_OFFSET).loadAddress();
574    
575        /* dataEnd = zero represents the current region. */
576        Address currentLimit = (dataEnd.isZero() ? cursor : dataEnd);
577        if (currentLimit.EQ(start.plus(DATA_END_OFFSET).plus(BYTES_IN_ADDRESS))) {
578          /* Empty region, so we can not call getObjectFromStartAddress() */
579          return;
580        }
581    
582        ObjectReference current = VM.objectModel.getObjectFromStartAddress(start.plus(DATA_START_OFFSET));
583    
584        /* Loop through each object up to the limit */
585        do {
586          /* Read end address first, as scan may be destructive */
587          Address currentObjectEnd = VM.objectModel.getObjectEndAddress(current);
588          scanner.scan(current);
589          if (currentObjectEnd.GE(currentLimit)) {
590            /* We have scanned the last object */
591            break;
592          }
593          /* Find the next object from the start address (dealing with alignment gaps, etc.) */
594          ObjectReference next = VM.objectModel.getObjectFromStartAddress(currentObjectEnd);
595          if (VM.VERIFY_ASSERTIONS) {
596            /* Must be monotonically increasing */
597            VM.assertions._assert(next.toAddress().GT(current.toAddress()));
598          }
599          current = next;
600        } while (true);
601      }
602    
603      /**
604       * Some pages are about to be re-used to satisfy a slow path request.
605       * @param pages The number of pages.
606       */
607      protected void reusePages(int pages) {
608        VM.assertions.fail("Subclasses that reuse regions must override this method.");
609      }
610    
611      /**
612       * Maximum size of a single region. Important for children that implement
613       * load balancing or increments based on region size.
614       * @return the maximum region size
615       */
616      protected Extent maximumRegionSize() { return Extent.max(); }
617    
618      /** @return the current cursor value */
619      public final Address getCursor() { return cursor; }
620      /** @return the space associated with this bump pointer */
621      @Override
622      public final Space getSpace() { return space; }
623    
624      /**
625       * Print out the status of the allocator (for debugging)
626       */
627      public final void show() {
628        Log.write("cursor = "); Log.write(cursor);
629        if (allowScanning) {
630          Log.write(" region = "); Log.write(region);
631        }
632        Log.write(" limit = "); Log.writeln(limit);
633      }
634    }