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

Commits:

11 changed files:

Changes:

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

  • rts/LdvProfile.c
    ... ... @@ -177,8 +177,8 @@ processHeapForDead( bdescr *bd )
    177 177
             p = bd->start;
    
    178 178
             while (p < bd->free) {
    
    179 179
                 p += processHeapClosureForDead((StgClosure *)p);
    
    180
    -            while (p < bd->free && !*p)   // skip slop
    
    181
    -                p++;
    
    180
    +            // See Note [Skipping slop when scanning the heap] in ClosureMacros.h
    
    181
    +            p = skipSlop(p, bd->free);
    
    182 182
             }
    
    183 183
             ASSERT(p == bd->free);
    
    184 184
             bd = bd->link;
    

  • rts/PrimOps.cmm
    ... ... @@ -223,9 +223,8 @@ stg_isMutableByteArrayWeaklyPinnedzh ( gcptr mba )
    223 223
     /* Note [shrink-array slop marker]
    
    224 224
      * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    225 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.
    
    226
    + * slop at address `slop_start`, we write an O(1) marker so that linear heap
    
    227
    + * scans can skip over the slop without reading stale heap pointers.
    
    229 228
      *
    
    230 229
      * The marker scheme (let n = number of slop words):
    
    231 230
      *
    
    ... ... @@ -239,8 +238,8 @@ stg_isMutableByteArrayWeaklyPinnedzh ( gcptr mba )
    239 238
      *
    
    240 239
      * An array may be shrunk multiple times, leaving consecutive slop regions.
    
    241 240
      * 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).
    
    241
    + * to the next live closure.  See Note [Skipping slop when scanning the heap]
    
    242
    + * in ClosureMacros.h.
    
    244 243
      */
    
    245 244
     
    
    246 245
     // shrink size of MutableByteArray in-place
    

  • rts/Printer.c
    ... ... @@ -1001,19 +1001,9 @@ 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 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
    -                        }
    
    1004
    +                        // See Note [Skipping slop when scanning the heap]
    
    1005
    +                        // in ClosureMacros.h
    
    1006
    +                        r = skipSlop(r, bd->free);
    
    1017 1007
                             if (!LOOKS_LIKE_CLOSURE_PTR(r)) {
    
    1018 1008
                                 debugBelch("%p found at %p, no closure at %p\n",
    
    1019 1009
                                            p, q, r);
    

  • rts/ProfHeap.c
    ... ... @@ -1317,37 +1317,8 @@ heapCensusBlock(Census *census, bdescr *bd)
    1317 1317
     
    
    1318 1318
             p += size;
    
    1319 1319
     
    
    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
    -        }
    
    1320
    +        /* See Note [Skipping slop when scanning the heap] in ClosureMacros.h */
    
    1321
    +        p = skipSlop(p, bd->free);
    
    1351 1322
         }
    
    1352 1323
     }
    
    1353 1324
     
    
    ... ... @@ -1472,8 +1443,6 @@ heapCensusChain( Census *census, bdescr *bd )
    1472 1443
             // of the associated block descriptor, thus introducing slop at the end
    
    1473 1444
             // of the object.  This slop remains after GC, violating the assumption
    
    1474 1445
             // of the loop below that all slop has been eliminated (#11627).
    
    1475
    -        // The slop isn't always zeroed (e.g. in non-profiling mode, cf
    
    1476
    -        // OVERWRITING_CLOSURE_OFS).
    
    1477 1446
             // Consequently, we handle large ARR_WORDS objects as a special case.
    
    1478 1447
             if (bd->flags & BF_LARGE) {
    
    1479 1448
                 StgPtr p = bd->start;
    

  • rts/include/rts/storage/ClosureMacros.h
    ... ... @@ -634,6 +634,44 @@ INLINE_HEADER void writeSlopMarker(StgWord *slop, StgWord n)
    634 634
         }
    
    635 635
     }
    
    636 636
     
    
    637
    +// Note [Skipping slop when scanning the heap]
    
    638
    +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    639
    +// Every linear scan of the heap (heap census, LDV census, sanity checker,
    
    640
    +// findPtr) has to step over slop between closures.  Slop comes in two forms:
    
    641
    +//
    
    642
    +//  1. Zero words: alignment padding for large/pinned objects, zeroed
    
    643
    +//     explicitly (see MEMSET_SLOP_W in allocatePinned).
    
    644
    +//
    
    645
    +//  2. Slop markers: written by writeSlopMarker whenever a closure is
    
    646
    +//     overwritten by a smaller one.  See Note [shrink-array slop marker]
    
    647
    +//     in PrimOps.cmm for the encoding.
    
    648
    +//
    
    649
    +// A closure can be shrunk repeatedly, leaving consecutive slop regions, so we
    
    650
    +// must loop until reaching a word that can't be slop.  Info pointers are never
    
    651
    +// 0 or (StgWord)(-1), so such a word starts the next closure.
    
    652
    +//
    
    653
    +// All scanners must use skipSlop: if any one of them keeps its own copy of
    
    654
    +// this loop it will go out of sync with the encoding (#27585).
    
    655
    +INLINE_HEADER StgPtr skipSlop(StgPtr p, StgPtr end)
    
    656
    +{
    
    657
    +    while (p < end) {
    
    658
    +        if (!*p) {
    
    659
    +            // single-word slop region, or alignment padding
    
    660
    +            p++;
    
    661
    +        } else if (*p == (StgWord)(-1)) {
    
    662
    +            // Multi-word slop region: sentinel, count, then that many words.
    
    663
    +            // writeSlopMarker only writes the sentinel for n >= 2, so the count
    
    664
    +            // word is always within the region.  We assert rather than bail out
    
    665
    +            // so that a corrupt heap is reported instead of silently skipped.
    
    666
    +            ASSERT(p + 1 < end);
    
    667
    +            p += 2 + *(p + 1);
    
    668
    +        } else {
    
    669
    +            break;
    
    670
    +        }
    
    671
    +    }
    
    672
    +    return p;
    
    673
    +}
    
    674
    +
    
    637 675
     INLINE_HEADER void
    
    638 676
     markImmutableSlop (StgClosure *p,
    
    639 677
               uint32_t offset,    /*< offset to start marking at, in words */
    

  • rts/sm/Sanity.c
    ... ... @@ -605,19 +605,9 @@ void checkHeapChain (bdescr *bd)
    605 605
                     ASSERT( size >= MIN_PAYLOAD_SIZE + sizeofW(StgHeader) );
    
    606 606
                     p += size;
    
    607 607
     
    
    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
    -                }
    
    608
    +                /* See Note [Skipping slop when scanning the heap]
    
    609
    +                   in ClosureMacros.h */
    
    610
    +                p = skipSlop(p, bd->free);
    
    621 611
                 }
    
    622 612
             }
    
    623 613
         }
    

  • rts/sm/Storage.c
    ... ... @@ -1035,9 +1035,10 @@ accountAllocation(Capability *cap, W_ n)
    1035 1035
      *   leave slop behind depending on the size of the closure being
    
    1036 1036
      *   overwritten. See Note [marking slop when overwriting immutable closures].
    
    1037 1037
      *
    
    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]
    
    1038
    + * To allow the heap profiler, the LDV profiler and the sanity checker to
    
    1039
    + * linearly scan over heap blocks, slop must be identifiable without reading
    
    1040
    + * stale heap pointers.
    
    1041
    + * See Note [Skipping slop when scanning the heap] in ClosureMacros.h
    
    1041 1042
      *
    
    1042 1043
      * Shrunk-array slop has a further, concurrent reader: the non-moving GC mark
    
    1043 1044
      * thread scans SmallMutArrPtrs payloads while the mutator may be shrinking
    
    ... ... @@ -1207,7 +1208,7 @@ allocateMightFail (Capability *cap, W_ n)
    1207 1208
      * When profiling we zero the space used for alignment. This allows us to
    
    1208 1209
      * traverse pinned blocks in the heap profiler.
    
    1209 1210
      *
    
    1210
    - * See Note [skipping slop in the heap profiler]
    
    1211
    + * See Note [Skipping slop when scanning the heap] in ClosureMacros.h
    
    1211 1212
      */
    
    1212 1213
     #define MEMSET_SLOP_W(p, val, len_w) memset(p, val, (len_w) * sizeof(W_))
    
    1213 1214
     
    

  • testsuite/tests/rts/T27585.hs
    1
    +{-# LANGUAGE MagicHash, UnboxedTuples, BlockArguments #-}
    
    2
    +module Main where
    
    3
    +
    
    4
    +import GHC.Exts
    
    5
    +import GHC.IO (IO(..))
    
    6
    +import System.Mem (performMajorGC)
    
    7
    +
    
    8
    +-- Lifted wrapper so SmallMutableArray# can be passed around.
    
    9
    +data MArr = MArr (SmallMutableArray# RealWorld Integer)
    
    10
    +
    
    11
    +-- Variant of T19048 for the LDV (biographical) profiler, +RTS -hb (#27585).
    
    12
    +--
    
    13
    +-- The array is promoted to the oldest generation *before* shrinking, so the
    
    14
    +-- shrink-array slop marker is written into an old-generation block.  The next
    
    15
    +-- major GC then runs LdvCensusForDead, whose linear heap scan
    
    16
    +-- (processHeapForDead in rts/LdvProfile.c) must skip the slop correctly.
    
    17
    +--
    
    18
    +-- The array must stay below LARGE_OBJECT_THRESHOLD (409 words): large objects
    
    19
    +-- live on the large_objects chain, which the census does not scan linearly.
    
    20
    +main :: IO ()
    
    21
    +main = do
    
    22
    +  ma <- newArr
    
    23
    +  fillArr ma 299
    
    24
    +  -- Two major GCs promote the array to the oldest generation.
    
    25
    +  performMajorGC
    
    26
    +  performMajorGC
    
    27
    +  -- Shrink: writes the slop marker over slots [10..299], in place, in an
    
    28
    +  -- old-generation block.
    
    29
    +  shrinkArr ma
    
    30
    +  n <- getSize ma
    
    31
    +  putStrLn $ "size after shrink = " ++ show n
    
    32
    +  -- With -hb active, LdvCensusForDead scans the old blocks containing the
    
    33
    +  -- slop marker.
    
    34
    +  performMajorGC
    
    35
    +  x <- readElem ma 0
    
    36
    +  putStrLn $ "arr[0] = " ++ show x
    
    37
    +  putStrLn "survived"
    
    38
    +
    
    39
    +newArr :: IO MArr
    
    40
    +newArr = IO \s -> case newSmallArray# 300# (0 :: Integer) s of
    
    41
    +  (# s', ma #) -> (# s', MArr ma #)
    
    42
    +
    
    43
    +-- Overwrite every slot with a distinct Integer so each holds a unique,
    
    44
    +-- definitely non-zero heap pointer.
    
    45
    +fillArr :: MArr -> Int -> IO ()
    
    46
    +fillArr _ (-1) = pure ()
    
    47
    +fillArr arr@(MArr ma) n@(I# n#) = do
    
    48
    +  IO \s -> case writeSmallArray# ma n# (fromIntegral n :: Integer) s of
    
    49
    +    s' -> (# s', () #)
    
    50
    +  fillArr arr (n - 1)
    
    51
    +
    
    52
    +shrinkArr :: MArr -> IO ()
    
    53
    +shrinkArr (MArr ma) = IO \s ->
    
    54
    +  case shrinkSmallMutableArray# ma 10# s of s' -> (# s', () #)
    
    55
    +
    
    56
    +getSize :: MArr -> IO Int
    
    57
    +getSize (MArr ma) = IO \s ->
    
    58
    +  case getSizeofSmallMutableArray# ma s of (# s', n# #) -> (# s', I# n# #)
    
    59
    +
    
    60
    +readElem :: MArr -> Int -> IO Integer
    
    61
    +readElem (MArr ma) (I# i#) = IO \s -> readSmallArray# ma i# s

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

  • testsuite/tests/rts/all.T
    ... ... @@ -710,3 +710,13 @@ test('T19048',
    710 710
          , extra_run_opts('+RTS -hT -i0 -RTS')
    
    711 711
          ],
    
    712 712
          compile_and_run, ['-O -rtsopts'])
    
    713
    +
    
    714
    +test('T27585',
    
    715
    +     [ omit_ghci
    
    716
    +     , no_check_hp
    
    717
    +     , js_skip
    
    718
    +     , when(have_profiling(), extra_ways(['prof_hb']))
    
    719
    +     , only_ways(['prof_hb'])
    
    720
    +     , extra_run_opts('+RTS -i0 -RTS')
    
    721
    +     ],
    
    722
    +     compile_and_run, ['-O -rtsopts'])