Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC

Commits:

17 changed files:

Changes:

  • changelog.d/fix-heap-census-large-arrays-19048
    1
    +section: rts
    
    2
    +synopsis: Correctly mark slop bytes when shrinking large arrays.
    
    3
    +  Heap census no longer traverses garbage-collected closures when profiling
    
    4
    +  is off.
    
    5
    +issues: #19048
    
    6
    +mrs: !15685

  • rts/Apply.cmm
    ... ... @@ -736,8 +736,8 @@ for:
    736 736
       R1 = StgAP_STACK_fun(ap);
    
    737 737
     
    
    738 738
       // Because of eager blackholing the closure no longer has correct size so
    
    739
    -  // threadPaused() can't correctly zero the slop, so we do it here. See #15571
    
    740
    -  // and Note [zeroing slop when overwriting closures].
    
    739
    +  // threadPaused() can't correctly mark the slop, so we do it here. See #15571
    
    740
    +  // and Note [marking slop when overwriting immutable closures].
    
    741 741
       OVERWRITING_CLOSURE_SIZE(ap, BYTES_TO_WDS(SIZEOF_StgThunkHeader) + 2 + Words);
    
    742 742
     
    
    743 743
       ENTER_R1();
    

  • rts/ZeroSlop.c โ†’ rts/MarkSlop.c
    ... ... @@ -2,7 +2,7 @@
    2 2
      *
    
    3 3
      * (c) The GHC Team, 1998-2012
    
    4 4
      *
    
    5
    - * Utilities for zeroing slop callable from Cmm
    
    5
    + * Utilities for marking slop callable from Cmm
    
    6 6
      *
    
    7 7
      * N.B. If you are in C you should rather using the inlineable utilities
    
    8 8
      * (e.g. overwritingClosure) defined in ClosureMacros.h.
    
    ... ... @@ -11,14 +11,14 @@
    11 11
     
    
    12 12
     #include "Rts.h"
    
    13 13
     
    
    14
    -void stg_overwritingClosure (StgClosure *p)
    
    14
    +void stg_writeSlopMarker (StgWord *slop, StgWord n)
    
    15 15
     {
    
    16
    -    overwritingClosure(p);
    
    16
    +    writeSlopMarker(slop, n);
    
    17 17
     }
    
    18 18
     
    
    19
    -void stg_overwritingMutableClosureOfs (StgClosure *p, uint32_t offset)
    
    19
    +void stg_overwritingClosure (StgClosure *p)
    
    20 20
     {
    
    21
    -    overwritingMutableClosureOfs(p, offset);
    
    21
    +    overwritingClosure(p);
    
    22 22
     }
    
    23 23
     
    
    24 24
     void stg_overwritingClosureSize (StgClosure *p, uint32_t size /* in words */)
    

  • rts/PrimOps.cmm
    ... ... @@ -216,10 +216,33 @@ stg_isMutableByteArrayWeaklyPinnedzh ( gcptr mba )
    216 216
      *    point if it's an older generation block, the mutator won't
    
    217 217
      *    allocate into those blocks anyway.
    
    218 218
      *
    
    219
    - * If check fails, fall back to the conservative code path: just zero the slop
    
    219
    + * If check fails, fall back to the conservative code path: just mark the slop
    
    220 220
      * and return when shrinking, or allocate a new array when growing.
    
    221 221
      */
    
    222 222
     
    
    223
    +/* Note [shrink-array slop marker]
    
    224
    + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    225
    + * When shrinkSmallMutableArray# or shrinkMutableByteArray# creates n words of
    
    226
    + * slop at address `slop_start`, we write an O(1) marker so that the heap
    
    227
    + * census (heapCensus in ProfHeap.c) and the sanity checker (checkHeapChain in
    
    228
    + * Sanity.c) can skip over the slop without reading stale heap pointers.
    
    229
    + *
    
    230
    + * The marker scheme (let n = number of slop words):
    
    231
    + *
    
    232
    + *   n == 0  nothing written (no slop)
    
    233
    + *   n == 1  slop[0] = 0
    
    234
    + *   n >= 2  slop[0] = (StgWord)(-1)   -- sentinel (0xFFFF...FFFF)
    
    235
    + *           slop[1] = n - 2           -- words remaining after the two-word header
    
    236
    + *
    
    237
    + * The sentinel value (StgWord)(-1) is safe because info-table pointers always
    
    238
    + * live in the text segment; 0xFFFF...FFFF is never a valid info pointer.
    
    239
    + *
    
    240
    + * An array may be shrunk multiple times, leaving consecutive slop regions.
    
    241
    + * Traversal code must therefore loop over all slop regions before advancing
    
    242
    + * to the next live closure.  See the while-loops in heapCensusBlock (ProfHeap.c)
    
    243
    + * and checkHeapChain (Sanity.c).
    
    244
    + */
    
    245
    +
    
    223 246
     // shrink size of MutableByteArray in-place
    
    224 247
     stg_shrinkMutableByteArrayzh ( gcptr mba, W_ new_size )
    
    225 248
     // MutableByteArray# s -> Int# -> State# s -> State# s
    
    ... ... @@ -236,7 +259,11 @@ stg_shrinkMutableByteArrayzh ( gcptr mba, W_ new_size )
    236 259
        bd = Bdescr(mba);
    
    237 260
        if (bdescr_free(bd) != mba + WDS(old_wds) ||
    
    238 261
            (bd != StgRegTable_rCurrentAlloc(BaseReg) && bd != Capability_pinned_object_block(MyCapability()))) {
    
    239
    -       OVERWRITING_CLOSURE_MUTABLE(mba, new_wds);
    
    262
    +       /* fall-back: not at end of nursery block; write slop marker.
    
    263
    +          See Note [shrink-array slop marker] */
    
    264
    +       W_ n;
    
    265
    +       n = old_wds - new_wds;
    
    266
    +       foreign "C" stg_writeSlopMarker(mba + WDS(new_wds) "ptr", n);
    
    240 267
            StgArrBytes_bytes(mba) = new_size;
    
    241 268
            // No need to call PROF_HEADER_CREATE. See Note [LDV profiling and resizing arrays]
    
    242 269
            return ();
    
    ... ... @@ -321,8 +348,16 @@ again:
    321 348
          }
    
    322 349
        }
    
    323 350
     
    
    324
    -   OVERWRITING_CLOSURE_MUTABLE(mba, (BYTES_TO_WDS(SIZEOF_StgSmallMutArrPtrs) +
    
    325
    -                                     new_size));
    
    351
    +   /* Write slop marker. See Note [shrink-array slop marker] */
    
    352
    +   W_ old_size_sma, n_sma, slop_start;
    
    353
    +   old_size_sma = StgSmallMutArrPtrs_ptrs(mba);
    
    354
    +   n_sma = old_size_sma - new_size;
    
    355
    +   slop_start = mba + SIZEOF_StgSmallMutArrPtrs + WDS(new_size);
    
    356
    +   foreign "C" stg_writeSlopMarker(slop_start "ptr", n_sma);
    
    357
    +
    
    358
    +   // No ordering needed: the concurrent mark thread bounds the slop with the
    
    359
    +   // marker's own release/acquire (Note [Slop marker memory ordering]), not
    
    360
    +   // with this store, and reading the new size it never scans the slop.
    
    326 361
        StgSmallMutArrPtrs_ptrs(mba) = new_size;
    
    327 362
        // No need to call PROF_HEADER_CREATE. See Note [LDV profiling and resizing arrays]
    
    328 363
     
    

  • rts/Printer.c
    ... ... @@ -1001,8 +1001,19 @@ findPtrBlocks (StgPtr p, bdescr *bd, StgPtr arr[], int arr_size, int i)
    1001 1001
                 if (UNTAG_CONST_CLOSURE((StgClosure*)*q) == (const StgClosure *)p) {
    
    1002 1002
                     if (i < arr_size) {
    
    1003 1003
                         for (r = bd->start; r < bd->free; r = end) {
    
    1004
    -                        // skip over zeroed-out slop
    
    1005
    -                        while (*r == 0) r++;
    
    1004
    +                        // skip over marked slop; loop because an array
    
    1005
    +                        // may have been shrunk multiple times.
    
    1006
    +                        // See Note [shrink-array slop marker] in PrimOps.cmm.
    
    1007
    +                        while (r < bd->free) {
    
    1008
    +                            if (!*r) {
    
    1009
    +                                r++;
    
    1010
    +                            } else if (*r == (StgWord)(-1)) {
    
    1011
    +                                StgWord skip = *(r + 1);
    
    1012
    +                                r += 2 + skip;
    
    1013
    +                            } else {
    
    1014
    +                                break;
    
    1015
    +                            }
    
    1016
    +                        }
    
    1006 1017
                             if (!LOOKS_LIKE_CLOSURE_PTR(r)) {
    
    1007 1018
                                 debugBelch("%p found at %p, no closure at %p\n",
    
    1008 1019
                                            p, q, r);
    

  • rts/ProfHeap.c
    ... ... @@ -1317,20 +1317,37 @@ heapCensusBlock(Census *census, bdescr *bd)
    1317 1317
     
    
    1318 1318
             p += size;
    
    1319 1319
     
    
    1320
    -        /* skip over slop, see Note [slop on the heap] */
    
    1321
    -        while (p < bd->free && !*p) p++;
    
    1322
    -        /* Note [skipping slop in the heap profiler]
    
    1323
    -         * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    1324
    -         * We make sure to zero slop that can remain after a major GC so
    
    1325
    -         * here we can assume any slop words we see until the block's free
    
    1326
    -         * pointer are zero. Since info pointers are always nonzero we can
    
    1327
    -         * use this to scan for the next valid heap closure.
    
    1328
    -         *
    
    1329
    -         * Note that not all types of slop are relevant here, only the ones
    
    1330
    -         * that can remain after major GC. So essentially just large objects
    
    1331
    -         * and pinned objects. All other closures will have been packed nice
    
    1332
    -         * and tight into fresh blocks.
    
    1333
    -         */
    
    1320
    +        /* skip over slop (zero words from large/pinned objects, or
    
    1321
    +           shrink-array slop markers); loop because an array may have been
    
    1322
    +           shrunk multiple times, leaving consecutive slop regions.
    
    1323
    +           See Note [slop on the heap] and Note [shrink-array slop marker]
    
    1324
    +           in PrimOps.cmm.
    
    1325
    +
    
    1326
    +           Note [skipping slop in the heap profiler]
    
    1327
    +           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    1328
    +           Slop left behind after major GC comes in two forms:
    
    1329
    +
    
    1330
    +            1. Zero words: alignment padding for large/pinned objects.
    
    1331
    +               We zero these explicitly (see MEMSET_SLOP_W in allocatePinned).
    
    1332
    +
    
    1333
    +            2. Shrink-array slop markers: written by stg_shrinkMutableByteArrayzh
    
    1334
    +               and stg_shrinkSmallMutableArrayzh in all build modes.  A single-word
    
    1335
    +               slop region is represented as a zero word; a multi-word region begins
    
    1336
    +               with the sentinel (StgWord)(-1) followed by a count of additional
    
    1337
    +               words.  See Note [shrink-array slop marker] in PrimOps.cmm.
    
    1338
    +
    
    1339
    +           Because an array can be shrunk multiple times, we loop until we
    
    1340
    +           see a word that looks like a valid info pointer. */
    
    1341
    +        while (p < bd->free) {
    
    1342
    +            if (!*p) {
    
    1343
    +                p++;
    
    1344
    +            } else if (*p == (StgWord)(-1)) {
    
    1345
    +                StgWord skip = *(p + 1);
    
    1346
    +                p += 2 + skip;
    
    1347
    +            } else {
    
    1348
    +                break;
    
    1349
    +            }
    
    1350
    +        }
    
    1334 1351
         }
    
    1335 1352
     }
    
    1336 1353
     
    

  • rts/RtsFlags.c
    ... ... @@ -2026,7 +2026,7 @@ static void normaliseRtsOpts (void)
    2026 2026
     
    
    2027 2027
     #if !defined(PROFILING) && !defined(DEBUG)
    
    2028 2028
         // The mark-region collector is incompatible with heap census unless
    
    2029
    -    // we zero slop of blackhole'd thunks, which doesn't happen in the
    
    2029
    +    // we mark slop of blackhole'd thunks, which doesn't happen in the
    
    2030 2030
         // vanilla way. See #9666.
    
    2031 2031
         if (RtsFlags.ProfFlags.doHeapProfile && RtsFlags.GcFlags.sweep) {
    
    2032 2032
             barf("The mark-region collector can only be used with profiling\n"
    

  • rts/ThreadPaused.c
    ... ... @@ -383,7 +383,7 @@ threadPaused(Capability *cap, StgTSO *tso)
    383 383
                         }
    
    384 384
                     }
    
    385 385
     
    
    386
    -                // zero out the slop so that the sanity checker can tell
    
    386
    +                // mark the slop so that the sanity checker can tell
    
    387 387
                     // where the next closure is. N.B. We mustn't do this until we have
    
    388 388
                     // pushed the free variables to the update remembered set above.
    
    389 389
                     OVERWRITING_CLOSURE_SIZE(bh, closure_sizeW_(bh, INFO_PTR_TO_STRUCT(bh_info)));
    

  • rts/include/Cmm.h
    ... ... @@ -659,15 +659,9 @@
    659 659
     #if defined(PROFILING) || defined(DEBUG)
    
    660 660
     #define OVERWRITING_CLOSURE_SIZE(c, size) foreign "C" stg_overwritingClosureSize(c "ptr", size)
    
    661 661
     #define OVERWRITING_CLOSURE(c) foreign "C" stg_overwritingClosure(c "ptr")
    
    662
    -#define OVERWRITING_CLOSURE_MUTABLE(c, off) foreign "C" stg_overwritingMutableClosureOfs(c "ptr", off)
    
    663 662
     #else
    
    664 663
     #define OVERWRITING_CLOSURE_SIZE(c, size) /* nothing */
    
    665 664
     #define OVERWRITING_CLOSURE(c) /* nothing */
    
    666
    -/* This is used to zero slop after shrunk arrays. It is important that we do
    
    667
    - * this whenever profiling is enabled as described in Note [slop on the heap]
    
    668
    - * in Storage.c. */
    
    669
    -#define OVERWRITING_CLOSURE_MUTABLE(c, off) \
    
    670
    -    if (TO_W_(RtsFlags_ProfFlags_doHeapProfile(RtsFlags)) != 0) { foreign "C" stg_overwritingMutableClosureOfs(c "ptr", off); }
    
    671 665
     #endif
    
    672 666
     
    
    673 667
     #define IS_STACK_CLEAN(stack) \
    

  • rts/include/rts/storage/ClosureMacros.h
    ... ... @@ -533,29 +533,34 @@ EXTERN_INLINE StgWord8 *mutArrPtrsCard (StgMutArrPtrs *a, W_ n)
    533 533
      */
    
    534 534
     
    
    535 535
      /*
    
    536
    -   Note [zeroing slop when overwriting closures]
    
    537
    -   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    538
    -   When we overwrite a closure in the heap with a smaller one, in some scenarios
    
    539
    -   we need to write zero words into "slop"; the memory that is left
    
    540
    -   unoccupied. See Note [slop on the heap]
    
    536
    +   Note [marking slop when overwriting immutable closures]
    
    537
    +   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    538
    +   When we overwrite a closure in the heap with a smaller one, we need to mark
    
    539
    +   the "slop" -- the memory that is left unoccupied -- so that the heap can be
    
    540
    +   linearly scanned. See Note [slop on the heap]
    
    541 541
     
    
    542
    -   Zeroing slop is required for:
    
    542
    +   For mutable closures (e.g. shrinking arrays), slop is always marked
    
    543
    +   unconditionally via writeSlopMarker, in all build modes.
    
    544
    +   See Note [shrink-array slop marker] in PrimOps.cmm.
    
    545
    +
    
    546
    +   For immutable closures (e.g. thunks overwritten with indirections), slop
    
    547
    +   marking is only needed for:
    
    543 548
     
    
    544 549
         - full-heap sanity checks (DEBUG, and +RTS -DS),
    
    545 550
     
    
    546
    -    - LDV profiling (PROFILING, and +RTS -hb) and
    
    551
    +    - LDV profiling (PROFILING, and +RTS -hb)
    
    547 552
     
    
    548
    -   However we can get into trouble if we're zeroing slop for ordinarily
    
    549
    -   immutable closures when using multiple threads, since there is nothing
    
    550
    -   preventing another thread from still being in the process of reading the
    
    551
    -   memory we're about to zero.
    
    553
    +   However we can get into trouble if we're marking slop for immutable closures
    
    554
    +   when using multiple threads, since there is nothing preventing another thread
    
    555
    +   from still being in the process of reading the memory we're about to
    
    556
    +   overwrite.
    
    552 557
     
    
    553
    -   Thus, with the THREADED RTS and +RTS -N2 or greater we must not zero
    
    558
    +   Thus, with the THREADED RTS and +RTS -N2 or greater we must not mark
    
    554 559
        immutable closure's slop. Similarly, the concurrent GC's mark thread
    
    555
    -   may race when a mutator during slop-zeroing. Consequently, we also disable
    
    556
    -   zeroing when the non-moving GC is in use.
    
    560
    +   may race with a mutator during slop marking. Consequently, we also disable
    
    561
    +   marking of immutable closures when the non-moving GC is in use.
    
    557 562
     
    
    558
    -   Hence, an immutable closure's slop is zeroed when either:
    
    563
    +   Hence, an immutable closure's slop is marked when either:
    
    559 564
     
    
    560 565
         - PROFILING && era > 0 (LDV is on) && !nonmoving-gc-enabled or
    
    561 566
         - !THREADED && DEBUG
    
    ... ... @@ -575,15 +580,11 @@ EXTERN_INLINE StgWord8 *mutArrPtrsCard (StgMutArrPtrs *a, W_ n)
    575 580
         overwritingClosure(c)
    
    576 581
     #define OVERWRITING_CLOSURE_SIZE(c, size) \
    
    577 582
         overwritingClosureSize(c, size)
    
    578
    -#define OVERWRITING_CLOSURE_MUTABLE(c, off) \
    
    579
    -    overwritingMutableClosureOfs(c, off)
    
    580 583
     #else
    
    581 584
     #define OVERWRITING_CLOSURE(c) \
    
    582 585
         do { (void) sizeof(c); } while(0)
    
    583 586
     #define OVERWRITING_CLOSURE_SIZE(c, size) \
    
    584 587
         do { (void) sizeof(c); (void) sizeof(size); } while(0)
    
    585
    -#define OVERWRITING_CLOSURE_MUTABLE(c, off) \
    
    586
    -    do { (void) sizeof(c); (void) sizeof(off); } while(0)
    
    587 588
     #endif
    
    588 589
     
    
    589 590
     #if defined(PROFILING)
    
    ... ... @@ -591,16 +592,57 @@ void LDV_recordDead (const StgClosure *c, uint32_t size);
    591 592
     RTS_PRIVATE bool isInherentlyUsed ( StgHalfWord closure_type );
    
    592 593
     #endif
    
    593 594
     
    
    595
    +// Note [Slop marker memory ordering]
    
    596
    +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    597
    +// The non-moving GC mark thread reads SmallMutArrPtrs payload elements
    
    598
    +// concurrently with the mutator, which may shrink the array via
    
    599
    +// stg_shrinkSmallMutableArrayzh.  Shrinking writes a slop marker over the
    
    600
    +// vacated elements (see Note [shrink-array slop marker] in PrimOps.cmm):
    
    601
    +//
    
    602
    +//   n == 1:  slop[0] = 0
    
    603
    +//   n >= 2:  slop[0] = -1   (sentinel)
    
    604
    +//            slop[1] = n-2  (count of further slop words)
    
    605
    +//
    
    606
    +// The mark thread reads elements with ACQUIRE_LOAD and, at each position i>0,
    
    607
    +// re-reads element i-1 after reading i to detect a concurrently written -1
    
    608
    +// sentinel (see rts/sm/NonMovingMark.c).  For this check to be sound the
    
    609
    +// store of -1 to slop[0] must be visible to the reader when it observes the
    
    610
    +// skip count at slop[1].  This is guaranteed by the release-acquire pairing:
    
    611
    +// the RELEASE_STORE of n-2 to slop[1] ensures that the prior RELAXED_STORE
    
    612
    +// of -1 to slop[0] is visible to any thread that performs an ACQUIRE_LOAD of
    
    613
    +// slop[1] and sees the skip count.
    
    614
    +//
    
    615
    +// The re-read of element i-1 is only performed when the value c just read at
    
    616
    +// position i could plausibly be a skip count, i.e. when (StgWord)c < n-i
    
    617
    +// (a skip count at position i must satisfy i + skip <= n-1, so skip < n-i).
    
    618
    +// Values at or above that bound are valid closure pointers, so no re-read is
    
    619
    +// needed.  In practice closure pointers are word-aligned kernel addresses and
    
    620
    +// far exceed any plausible skip count, so this eliminates virtually all of
    
    621
    +// the redundant re-reads.
    
    622
    +//
    
    623
    +// The non-moving GC only runs in the threaded RTS, where RELEASE_STORE and
    
    624
    +// ACQUIRE_LOAD are both __atomic_* operations.  The _ALWAYS variants are not
    
    625
    +// needed here.
    
    626
    +INLINE_HEADER void writeSlopMarker(StgWord *slop, StgWord n)
    
    627
    +{
    
    628
    +    // See Note [Slop marker memory ordering]
    
    629
    +    if (n == 1) {
    
    630
    +        RELAXED_STORE(&slop[0], (StgWord)0);
    
    631
    +    } else if (n >= 2) {
    
    632
    +        RELAXED_STORE(&slop[0], (StgWord)(-1));
    
    633
    +        RELEASE_STORE(&slop[1], n - 2);
    
    634
    +    }
    
    635
    +}
    
    636
    +
    
    594 637
     INLINE_HEADER void
    
    595
    -zeroSlop (StgClosure *p,
    
    596
    -          uint32_t offset,    /*< offset to start zeroing at, in words */
    
    597
    -          uint32_t size,      /*< total closure size, in words */
    
    598
    -          bool known_mutable  /*< is this a closure who's slop we can always zero? */
    
    638
    +markImmutableSlop (StgClosure *p,
    
    639
    +          uint32_t offset,    /*< offset to start marking at, in words */
    
    640
    +          uint32_t size       /*< total closure size, in words */
    
    599 641
              )
    
    600 642
     {
    
    601
    -    // see Note [zeroing slop when overwriting closures], also #8402
    
    643
    +    // see Note [marking slop when overwriting immutable closures], also #8402
    
    602 644
     
    
    603
    -    const bool want_to_zero_immutable_slop = false
    
    645
    +    const bool want_to_mark = false
    
    604 646
             // Sanity checking (-DS) is enabled
    
    605 647
             || RTS_DEREF(RtsFlags).DebugFlags.sanity
    
    606 648
     #if defined(PROFILING)
    
    ... ... @@ -609,44 +651,23 @@ zeroSlop (StgClosure *p,
    609 651
     #endif
    
    610 652
             ;
    
    611 653
     
    
    612
    -    const bool can_zero_immutable_slop =
    
    654
    +    const bool can_mark =
    
    613 655
             // Only if we're running single threaded.
    
    614 656
             getNumCapabilities() == 1
    
    615 657
             && !RTS_DEREF(RtsFlags).GcFlags.useNonmoving; // see #23170
    
    616 658
     
    
    617
    -    const bool zero_slop_immutable =
    
    618
    -        want_to_zero_immutable_slop && can_zero_immutable_slop;
    
    619
    -
    
    620
    -    const bool zero_slop_mutable =
    
    621
    -#if defined(PROFILING)
    
    622
    -        // Always zero mutable closure slop when profiling. We do this to cover
    
    623
    -        // the case of shrinking mutable arrays in pinned blocks for the heap
    
    624
    -        // profiler, see Note [skipping slop in the heap profiler]
    
    625
    -        //
    
    626
    -        // TODO: We could make this check more specific and only zero if the
    
    627
    -        // object is in a BF_PINNED bdescr here. Update Note [slop on the heap]
    
    628
    -        // and [zeroing slop when overwriting closures] if you change this.
    
    629
    -        true
    
    630
    -#else
    
    631
    -        zero_slop_immutable
    
    632
    -#endif
    
    633
    -        ;
    
    634
    -
    
    635
    -    const bool zero_slop =
    
    636
    -        // If we're not sure this is a mutable closure treat it like an
    
    637
    -        // immutable one.
    
    638
    -        known_mutable ? zero_slop_mutable : zero_slop_immutable;
    
    639
    -
    
    640
    -    if(!zero_slop)
    
    659
    +    if(!(want_to_mark && can_mark))
    
    641 660
             return;
    
    642 661
     
    
    643
    -    for (uint32_t i = offset; i < size; i++) {
    
    644
    -        ((StgWord *)p)[i] = 0;
    
    645
    -    }
    
    662
    +    // Write a slop marker so that the heap profiler and sanity checker can skip
    
    663
    +    // over the slop without reading stale heap pointers.
    
    664
    +    // See Note [shrink-array slop marker] in PrimOps.cmm for the encoding.
    
    665
    +    writeSlopMarker((StgWord *)p + offset, size - offset);
    
    646 666
     }
    
    647 667
     
    
    648 668
     // N.B. the stg_* variants of the utilities below are only for calling from
    
    649 669
     // Cmm. The INLINE_HEADER functions should be used when in C.
    
    670
    +void stg_writeSlopMarker (StgWord *slop, StgWord n);
    
    650 671
     void stg_overwritingClosure (StgClosure *p);
    
    651 672
     INLINE_HEADER void overwritingClosure (StgClosure *p)
    
    652 673
     {
    
    ... ... @@ -655,31 +676,10 @@ INLINE_HEADER void overwritingClosure (StgClosure *p)
    655 676
         if(era > 0 && !isInherentlyUsed(get_itbl(p)->type))
    
    656 677
             LDV_recordDead(p, size);
    
    657 678
     #endif
    
    658
    -    zeroSlop(p, sizeofW(StgThunkHeader), size, /*known_mutable=*/false);
    
    679
    +    markImmutableSlop(p, sizeofW(StgThunkHeader), size);
    
    659 680
     }
    
    660 681
     
    
    661 682
     
    
    662
    -// Version of 'overwritingClosure' which overwrites only a suffix of a
    
    663
    -// closure.  The offset is expressed in words relative to 'p' and shall
    
    664
    -// be less than or equal to closure_sizeW(p), and usually at least as
    
    665
    -// large as the respective thunk header.
    
    666
    -void stg_overwritingMutableClosureOfs (StgClosure *p, uint32_t offset);
    
    667
    -INLINE_HEADER void overwritingMutableClosureOfs (StgClosure *p, uint32_t offset)
    
    668
    -{
    
    669
    -    // Since overwritingClosureOfs is only ever called by:
    
    670
    -    //
    
    671
    -    //   - shrinkMutableByteArray# (ARR_WORDS) and
    
    672
    -    //
    
    673
    -    //   - shrinkSmallMutableArray# (SMALL_MUT_ARR_PTRS)
    
    674
    -    //
    
    675
    -    // we can safely omit the Ldv_recordDead call. Since these closures are
    
    676
    -    // considered inherently used we don't need to track their destruction.
    
    677
    -#if defined(PROFILING)
    
    678
    -    ASSERT(isInherentlyUsed(get_itbl(p)->type) == true);
    
    679
    -#endif
    
    680
    -    zeroSlop(p, offset, closure_sizeW(p), /*known_mutable=*/true);
    
    681
    -}
    
    682
    -
    
    683 683
     // Version of 'overwritingClosure' which takes closure size as argument.
    
    684 684
     void stg_overwritingClosureSize (StgClosure *p, uint32_t size /* in words */);
    
    685 685
     INLINE_HEADER void overwritingClosureSize (StgClosure *p, uint32_t size)
    
    ... ... @@ -691,5 +691,5 @@ INLINE_HEADER void overwritingClosureSize (StgClosure *p, uint32_t size)
    691 691
         if(era > 0)
    
    692 692
             LDV_recordDead(p, size);
    
    693 693
     #endif
    
    694
    -    zeroSlop(p, sizeofW(StgThunkHeader), size, /*known_mutable=*/false);
    
    694
    +    markImmutableSlop(p, sizeofW(StgThunkHeader), size);
    
    695 695
     }

  • rts/rts.cabal
    ... ... @@ -473,7 +473,7 @@ library
    473 473
                      TSANUtils.c
    
    474 474
                      WSDeque.c
    
    475 475
                      Weak.c
    
    476
    -                 ZeroSlop.c
    
    476
    +                 MarkSlop.c
    
    477 477
                      eventlog/EventLog.c
    
    478 478
                      eventlog/EventLogWriter.c
    
    479 479
                      hooks/FlagDefaults.c
    

  • rts/sm/NonMovingMark.c
    ... ... @@ -1663,9 +1663,42 @@ mark_closure (MarkQueue *queue, const StgClosure *p0, StgClosure **origin)
    1663 1663
         case SMALL_MUT_ARR_PTRS_FROZEN_CLEAN:
    
    1664 1664
         case SMALL_MUT_ARR_PTRS_FROZEN_DIRTY: {
    
    1665 1665
             StgSmallMutArrPtrs *arr = (StgSmallMutArrPtrs *) p;
    
    1666
    -        for (StgWord i = 0; i < arr->ptrs; i++) {
    
    1667
    -            StgClosure **field = &arr->payload[i];
    
    1668
    -            markQueuePushClosure(queue, ACQUIRE_LOAD(field), field);
    
    1666
    +        StgWord n = arr->ptrs;
    
    1667
    +        if (n == 0) break;
    
    1668
    +
    
    1669
    +        for (StgWord i = 0; i < n; i++) {
    
    1670
    +            StgClosure *c = ACQUIRE_LOAD(&arr->payload[i]);
    
    1671
    +            // If NULL or -1, we know the rest is slop
    
    1672
    +            if (c == NULL || c == (StgClosure *)(-1)) break;
    
    1673
    +            // A valid skip count at position i must satisfy
    
    1674
    +            //   i + skip <= n-1  (sentinel at i-1, count at i, skip more words)
    
    1675
    +            // i.e. skip < n-i.  If c is out of that range it cannot be a skip
    
    1676
    +            // count, so we must have read a valid closure pointer.
    
    1677
    +            bool maybe_slop_count = (StgWord)c < n - i;
    
    1678
    +            if (maybe_slop_count && i != 0) {
    
    1679
    +                // Otherwise re-read the previous element: the mutator may have
    
    1680
    +                // written -1 there after we last saw it, making the current
    
    1681
    +                // word the skip count rather than a valid closure pointer.
    
    1682
    +                //
    
    1683
    +                // The ACQUIRE_LOAD of payload[i] above synchronizes with the
    
    1684
    +                // RELEASE_STORE in writeSlopMarker, so a RELAXED_LOAD suffices
    
    1685
    +                // here; see Note [Slop marker memory ordering] in
    
    1686
    +                // rts/include/rts/storage/ClosureMacros.h.
    
    1687
    +                if (RELAXED_LOAD(&arr->payload[i-1]) == (StgClosure *)(-1)) break;
    
    1688
    +            }
    
    1689
    +
    
    1690
    +            // Track origin so indirections reached through array elements get
    
    1691
    +            // short-cut (see Note [Origin references in the nonmoving
    
    1692
    +            // collector] in NonMovingMark.h), but only when c cannot be a skip
    
    1693
    +            // count, i.e. c >= n-i.
    
    1694
    +            // The collapse rewrites the cell with a CAS that fires only if it
    
    1695
    +            // still holds c; restricting to c >= n-i guarantees c differs from
    
    1696
    +            // any skip count the mutator could write at this cell while
    
    1697
    +            // concurrently shrinking the array, so the CAS can never clobber a
    
    1698
    +            // slop marker. Real heap addresses are far above n, so in practice
    
    1699
    +            // every element is still short-cut.
    
    1700
    +            StgClosure **origin = maybe_slop_count ? NULL : &arr->payload[i];
    
    1701
    +            markQueuePushClosure(queue, c, origin);
    
    1669 1702
             }
    
    1670 1703
             break;
    
    1671 1704
         }
    

  • rts/sm/Sanity.c
    ... ... @@ -605,9 +605,19 @@ void checkHeapChain (bdescr *bd)
    605 605
                     ASSERT( size >= MIN_PAYLOAD_SIZE + sizeofW(StgHeader) );
    
    606 606
                     p += size;
    
    607 607
     
    
    608
    -                /* skip over slop, see Note [slop on the heap] */
    
    609
    -                while (p < bd->free &&
    
    610
    -                       (*p < 0x1000 || !LOOKS_LIKE_INFO_PTR(*p))) { p++; }
    
    608
    +                /* skip slop; loop because an array may have been shrunk
    
    609
    +                   multiple times.  See Note [slop on the heap] in Storage.c
    
    610
    +                   and Note [shrink-array slop marker] in PrimOps.cmm. */
    
    611
    +                while (p < bd->free) {
    
    612
    +                    if (!*p) {
    
    613
    +                        p++;
    
    614
    +                    } else if (*p == (StgWord)(-1)) {
    
    615
    +                        StgWord skip = *(p + 1);
    
    616
    +                        p += 2 + skip;
    
    617
    +                    } else {
    
    618
    +                        break;
    
    619
    +                    }
    
    620
    +                }
    
    611 621
                 }
    
    612 622
             }
    
    613 623
         }
    
    ... ... @@ -1013,9 +1023,9 @@ static void checkGeneration (generation *gen,
    1013 1023
         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    1014 1024
         // heap sanity checking doesn't work with SMP for two reasons:
    
    1015 1025
         //
    
    1016
    -    //   * We can't zero the slop. However, we can sanity-check the heap after a
    
    1026
    +    //   * We can't mark the slop. However, we can sanity-check the heap after a
    
    1017 1027
         //     major gc, because there is no slop. See also Updates.h and Note
    
    1018
    -    //     [zeroing slop when overwriting closures].
    
    1028
    +    //     [marking slop when overwriting immutable closures].
    
    1019 1029
         //
    
    1020 1030
         //   * The nonmoving collector may be mutating its large object lists,
    
    1021 1031
         //     unless we were in fact called by the nonmoving collector.
    

  • rts/sm/Storage.c
    ... ... @@ -1033,29 +1033,30 @@ accountAllocation(Capability *cap, W_ n)
    1033 1033
      *
    
    1034 1034
      *   During GC the RTS overwrites closures with forwarding pointers, this can
    
    1035 1035
      *   leave slop behind depending on the size of the closure being
    
    1036
    - *   overwritten. See Note [zeroing slop when overwriting closures].
    
    1036
    + *   overwritten. See Note [marking slop when overwriting immutable closures].
    
    1037 1037
      *
    
    1038
    - * Under various ways we actually zero slop so we can linearly scan over blocks
    
    1039
    - * of closures. This trick is used by the sanity checking code and the heap
    
    1040
    - * profiler, see Note [skipping slop in the heap profiler].
    
    1038
    + * To allow the heap profiler and sanity checker to linearly scan over heap
    
    1039
    + * blocks, slop must be identifiable without reading stale heap pointers.
    
    1040
    + * See Note [skipping slop in the heap profiler]
    
    1041 1041
      *
    
    1042
    - * In general we zero:
    
    1042
    + * Shrunk-array slop has a further, concurrent reader: the non-moving GC mark
    
    1043
    + * thread scans SmallMutArrPtrs payloads while the mutator may be shrinking
    
    1044
    + * them, so it must identify the slop with the right memory ordering. See Note
    
    1045
    + * [Slop marker memory ordering] in ClosureMacros.h.
    
    1043 1046
      *
    
    1047
    + * For pinned/large-object alignment slop we use explicit zeroing:
    
    1044 1048
      *  - Pinned object alignment slop, see MEMSET_SLOP_W in allocatePinned.
    
    1045 1049
      *  - Large object alignment slop, see MEMSET_SLOP_W in allocatePinned.
    
    1046
    - *  - Shrunk array slop, see OVERWRITING_CLOSURE_MUTABLE.
    
    1047 1050
      *
    
    1048
    - * Note that this is necessary even in the vanilla (e.g. non-profiling) RTS
    
    1049
    - * since the user may trigger a heap census via +RTS -hT, which can be used
    
    1050
    - * even when not linking against the profiled RTS. Failing to zero slop
    
    1051
    - * due to array shrinking has resulted in a few nasty bugs (#17572, #9666).
    
    1052
    - * However, since array shrink may result in large amounts of slop (unlike
    
    1053
    - * alignment), we take care to only zero such slop when heap profiling or DEBUG
    
    1054
    - * are enabled.
    
    1051
    + * For shrunk-array slop we write an O(1) marker in all build modes.
    
    1052
    + * See Note [shrink-array slop marker] in PrimOps.cmm for the encoding.
    
    1053
    + * This replaces the old approach of zeroing the entire slop region, which was a
    
    1054
    + * no-op in vanilla (non-profiling, non-debug) builds and caused heap-census
    
    1055
    + * crashes (#19048, #17572, #9666).
    
    1055 1056
      *
    
    1056
    - * When performing LDV profiling or using a (single threaded) debug RTS we zero
    
    1057
    - * slop even when overwriting immutable closures, see Note [zeroing slop when
    
    1058
    - * overwriting closures].
    
    1057
    + * When performing LDV profiling or using a (single threaded) debug RTS we mark
    
    1058
    + * slop even when overwriting immutable closures, see Note [marking slop when
    
    1059
    + * overwriting immutable closures].
    
    1059 1060
      */
    
    1060 1061
     
    
    1061 1062
     /*
    

  • testsuite/tests/rts/T19048.hs
    1
    +{-# LANGUAGE MagicHash, UnboxedTuples, BlockArguments #-}
    
    2
    +module Main where
    
    3
    +
    
    4
    +import GHC.Exts
    
    5
    +import GHC.ST   (ST(..), runST)
    
    6
    +import System.Mem (performMajorGC)
    
    7
    +
    
    8
    +-- Lifted wrapper so SmallArray# can appear in non-unlifted positions.
    
    9
    +data SmallArr a = SmallArr (SmallArray# a)
    
    10
    +
    
    11
    +main :: IO ()
    
    12
    +main = do
    
    13
    +  let arr = buildArr
    
    14
    +  let n = case arr of SmallArr a -> I# (sizeofSmallArray# a)
    
    15
    +  putStrLn $ "size after shrink = " ++ show n
    
    16
    +  -- With +RTS -hT -i0 this triggers heapCensus.
    
    17
    +  -- The census advances past the 10 live elements, then hits the 490
    
    18
    +  -- stale heap pointers in the slop and crashes.
    
    19
    +  performMajorGC
    
    20
    +  -- Keep arr alive across the GC by reading from it after.
    
    21
    +  let v = case arr of SmallArr a -> case indexSmallArray# a 0# of (# x #) -> x
    
    22
    +  putStrLn $ "arr[0] = " ++ show (v :: Integer)
    
    23
    +  putStrLn "survived"
    
    24
    +
    
    25
    +-- Allocate 500 slots, write a DISTINCT Integer to every slot, then
    
    26
    +-- shrink to 10.  Slots [10..499] become slop: they still hold live,
    
    27
    +-- non-zero heap pointers in the raw memory, but the ptrs header field
    
    28
    +-- says there are only 10 elements.  zeroSlop is a no-op in non-PROFILING
    
    29
    +-- builds (even when -hT is active), so the slop is never cleared.
    
    30
    +buildArr :: SmallArr Integer
    
    31
    +buildArr = runST $ ST \s0 ->
    
    32
    +  -- All 500 slots start with a non-null initial value.
    
    33
    +  case newSmallArray# 500# (0 :: Integer) s0 of { (# s1, ma #) ->
    
    34
    +  -- Overwrite every slot with a distinct Integer so each holds a
    
    35
    +  -- unique heap pointer (no sharing, definitely non-zero).
    
    36
    +  case fill 499 ma s1 of { s2 ->
    
    37
    +  -- Shrink: ptrs = 10, but physical slots [10..499] are NOT zeroed.
    
    38
    +  case shrinkSmallMutableArray# ma 10# s2 of { s3 ->
    
    39
    +  case unsafeFreezeSmallArray# ma s3 of { (# s4, a #) ->
    
    40
    +  (# s4, SmallArr a #) }}}}
    
    41
    +
    
    42
    +-- Fill slots [0..n] each with a distinct Integer value (n, n-1, ..., 0).
    
    43
    +-- 'Integer' guarantees a genuine heap object for every value.
    
    44
    +fill :: Int -> SmallMutableArray# s Integer -> State# s -> State# s
    
    45
    +fill 0 ma s = writeSmallArray# ma 0# (0 :: Integer) s
    
    46
    +fill n ma s =
    
    47
    +  let I# n# = n
    
    48
    +  in case writeSmallArray# ma n# (fromIntegral n :: Integer) s of
    
    49
    +       s' -> fill (n - 1) ma s'

  • testsuite/tests/rts/T19048.stdout
    1
    +size after shrink = 10
    
    2
    +arr[0] = 0
    
    3
    +survived

  • testsuite/tests/rts/all.T
    ... ... @@ -702,3 +702,11 @@ test('T27123', [when(have_profiling(), extra_ways(['prof']))], compile_and_run,
    702 702
     test('T27434',
    
    703 703
          extra_ways(['compacting_gc']),
    
    704 704
          compile_and_run, [''])
    
    705
    +
    
    706
    +test('T19048',
    
    707
    +     [ omit_ghci
    
    708
    +     , no_check_hp
    
    709
    +     , js_skip
    
    710
    +     , extra_run_opts('+RTS -hT -i0 -RTS')
    
    711
    +     ],
    
    712
    +     compile_and_run, ['-O -rtsopts'])