Simon Jakobi pushed to branch wip/sjakobi/udfm-placement at Glasgow Haskell Compiler / GHC

Commits:

1 changed file:

Changes:

  • compiler/GHC/Types/Unique/DFM.hs
    ... ... @@ -347,8 +347,21 @@ elemUDFM k (UDFM m _i) = M.member (getKey $ getUnique k) m
    347 347
     -- See Note [Cost of deterministic iteration].
    
    348 348
     foldUDFM :: (elt -> a -> a) -> a -> UniqDFM key elt -> a
    
    349 349
     {-# INLINE foldUDFM #-}
    
    350
    --- This INLINE prevents a regression in !10568
    
    351
    -foldUDFM k z m = foldr k z (eltsUDFM m)
    
    350
    +-- The INLINE prevents a regression in !10568 and, together with the placement
    
    351
    +-- sort's build form, lets the non-empty path fold directly over the sorted
    
    352
    +-- readout with no intermediate list. See Note [Sorting a UDFM].
    
    353
    +foldUDFM k z (UDFM m ub)
    
    354
    +  -- n <= 1: any order is trivially tag order, so fold straight over the map
    
    355
    +  | M.compareSize m 1 /= GT = M.foldr (k . taggedFst) z m
    
    356
    +  | otherwise               = fold_elts_nonempty k z m ub
    
    357
    +
    
    358
    +-- Precondition: m is non-empty. Out of line (like elts_nonempty) so foldUDFM's
    
    359
    +-- consumers don't inline the sort machinery; but placementSort's build fuses
    
    360
    +-- with the foldr here, so the placement path folds without building a list.
    
    361
    +fold_elts_nonempty :: (elt -> a -> a) -> a -> M.Word64Map (TaggedVal elt) -> Int -> a
    
    362
    +fold_elts_nonempty k z m ub
    
    363
    +  | usePlacement m ub = foldr k z (placementSort ub (\_ tv -> tv) m)
    
    364
    +  | otherwise         = foldr k z (map taggedFst (sort_it m))
    
    352 365
     
    
    353 366
     -- | Like 'foldUDFM' but the function also receives a key.
    
    354 367
     --
    
    ... ... @@ -420,13 +433,29 @@ O(ub) -- with no comparisons. The readout is lazy, so consumers that demand
    420 433
     only a prefix pay almost nothing beyond the fill (but the fill itself is
    
    421 434
     unavoidable; see Note [Cost of deterministic iteration]).
    
    422 435
     
    
    436
    +placementSort emits the readout as a 'build', so foldr/build fusion applies.
    
    437
    +An eltsUDFM consumer materialises the list (build reduces to cons/nil); a
    
    438
    +foldUDFM consumer instead has its combiner threaded straight into the readout,
    
    439
    +folding the sorted elements with no intermediate list. foldUDFM is INLINE and
    
    440
    +dispatches through the out-of-line fold_elts_nonempty, where the fusion happens
    
    441
    +once -- so the fold is list-free without inlining the sort into every consumer.
    
    442
    +
    
    423 443
     Holes: slots whose tag never occurs keep the initial sentinel, a TaggedVal
    
    424
    -with tag -1. Real tags are non-negative, so the readout skips on tag < 0.
    
    425
    -The sentinel's value field is never read, but something must fill it: not a
    
    426
    -panic thunk -- TaggedVal's strict value field would force it as soon as the
    
    427
    -readout inspects a hole's tag -- so we borrow the value of an arbitrary map
    
    428
    -element. (Safe: the callers' guards send maps of size < 2 down a different
    
    429
    -path, so the map is never empty here.)
    
    444
    +with tag -1. Real tags are non-negative, so the readout skips on tag < 0 and
    
    445
    +never reads the sentinel's value field. But that field is strict, so the
    
    446
    +sentinel still needs a value in WHNF: a panic thunk is out (it would be forced
    
    447
    +-- and crash -- the moment the readout inspects a hole's tag). We use
    
    448
    +@unsafeCoerce ()@: () is a static, already-evaluated nullary constructor, so
    
    449
    +the sentinel is one shared top-level value -- no per-call allocation, and it
    
    450
    +retains nothing.
    
    451
    +
    
    452
    +The unsafeCoerce is safe here because r is always a lifted, boxed (pointer)
    
    453
    +type -- it comes from the map's elements -- so a pointer to () has the right
    
    454
    +representation. The GC only ever traces that pointer (() is a valid closure);
    
    455
    +the value is never evaluated or used as an r, since the readout takes values
    
    456
    +only from filled slots. Borrowing a real element instead would also type-check
    
    457
    +but costs a per-call thunk that retains the whole source map until the holes
    
    458
    +are read.
    
    430 459
     
    
    431 460
     This sorting method loses when ub is much larger than n = M.size m: ub never
    
    432 461
     shrinks (overwrites keep bumping it, delete/filter shrink n but not ub). We
    
    ... ... @@ -453,19 +482,15 @@ placementSort :: forall e r. Int
    453 482
                   -> (M.Key -> TaggedVal e -> TaggedVal r)
    
    454 483
                   -> M.Word64Map (TaggedVal e)
    
    455 484
                   -> [r]
    
    456
    -{-# INLINE placementSort #-}  -- specializes mk into the fill loop
    
    457
    -placementSort ub mk m = runST (ST (\s0 ->
    
    458
    -  case newSmallArray ub hole s0 of
    
    459
    -    (# s1, marr #) -> case fill marr s1 of
    
    460
    -      (# s2, () #) -> case unsafeFreezeSmallArray marr s2 of
    
    461
    -        (# s3, arr #) -> (# s3, readout arr 0 #)))
    
    485
    +{-# INLINE placementSort #-}  -- specializes mk into the fill loop; makes the
    
    486
    +                              -- readout a good producer that fuses with a
    
    487
    +                              -- consuming fold. See Note [Sorting a UDFM].
    
    488
    +placementSort ub mk m = build gen
    
    462 489
       where
    
    463
    -    -- The tag -1 marks unfilled slots; the value field is never read, but
    
    464
    -    -- it is strict, so it needs a real value of type r -- borrow one from
    
    465
    -    -- the map. See Note [Sorting a UDFM].
    
    490
    +    -- The tag -1 marks unfilled slots; the value field is never read, but it
    
    491
    +    -- is strict, so it needs a WHNF value of type r. See Note [Sorting a UDFM].
    
    466 492
         hole :: TaggedVal r
    
    467
    -    hole = case M.findMin m of
    
    468
    -      (k, tv) -> TaggedVal (taggedFst (mk k tv)) (-1)
    
    493
    +    hole = TaggedVal (unsafeCoerce ()) (-1)
    
    469 494
     
    
    470 495
         fill :: SmallMutableArray s (TaggedVal r) -> State# s -> (# State# s, () #)
    
    471 496
         fill marr s = case M.traverseWithKey_ write m of ST st -> st s
    
    ... ... @@ -473,12 +498,22 @@ placementSort ub mk m = runST (ST (\s0 ->
    473 498
             write k tv = ST (\s' ->
    
    474 499
               (# writeSmallArray marr (taggedSnd tv) (mk k tv) s', () #))
    
    475 500
     
    
    476
    -    readout :: SmallArray (TaggedVal r) -> Int -> [r]
    
    477
    -    readout arr j
    
    478
    -      | j >= ub   = []
    
    479
    -      | t < 0     = readout arr (j + 1)
    
    480
    -      | otherwise = v : readout arr (j + 1)
    
    481
    -      where TaggedVal v t = indexSmallArray arr j
    
    501
    +    -- Written as a build so that foldr/build fusion lets a consuming fold
    
    502
    +    -- (foldUDFM) apply its combiner during readout, with no cons cells.
    
    503
    +    -- Unfused (eltsUDFM), build reduces to cons/nil and yields the list.
    
    504
    +    gen :: forall b. (r -> b -> b) -> b -> b
    
    505
    +    gen cons nil = runST (ST (\s0 ->
    
    506
    +      case newSmallArray ub hole s0 of
    
    507
    +        (# s1, marr #) -> case fill marr s1 of
    
    508
    +          (# s2, () #) -> case unsafeFreezeSmallArray marr s2 of
    
    509
    +            (# s3, arr #) -> (# s3, readout arr 0 #)))
    
    510
    +      where
    
    511
    +        readout :: SmallArray (TaggedVal r) -> Int -> b
    
    512
    +        readout arr j
    
    513
    +          | j >= ub   = nil
    
    514
    +          | t < 0     = readout arr (j + 1)
    
    515
    +          | otherwise = cons v (readout arr (j + 1))
    
    516
    +          where TaggedVal v t = indexSmallArray arr j
    
    482 517
     
    
    483 518
     filterUDFM :: (elt -> Bool) -> UniqDFM key elt -> UniqDFM key elt
    
    484 519
     filterUDFM p (UDFM m i) = UDFM (M.filter (\(TaggedVal v _) -> p v) m) i