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

Commits:

5 changed files:

Changes:

  • compiler/GHC/Data/Word64Map/Internal.hs
    ... ... @@ -170,6 +170,7 @@ module GHC.Data.Word64Map.Internal (
    170 170
         , map
    
    171 171
         , mapWithKey
    
    172 172
         , traverseWithKey
    
    173
    +    , traverseWithKey_
    
    173 174
         , traverseMaybeWithKey
    
    174 175
         , mapAccum
    
    175 176
         , mapAccumWithKey
    
    ... ... @@ -2520,6 +2521,16 @@ traverseWithKey f = go
    2520 2521
           | otherwise = liftA2 (Bin p m) (go l) (go r)
    
    2521 2522
     {-# INLINE traverseWithKey #-}
    
    2522 2523
     
    
    2524
    +-- | \(O(n)\). Visit each key\/value pair in ascending key order, discarding
    
    2525
    +-- the results.
    
    2526
    +traverseWithKey_ :: Applicative t => (Key -> a -> t ()) -> Word64Map a -> t ()
    
    2527
    +traverseWithKey_ f = go
    
    2528
    +  where
    
    2529
    +    go Nil = pure ()
    
    2530
    +    go (Tip k v) = f k v
    
    2531
    +    go (Bin _ _ l r) = go l *> go r
    
    2532
    +{-# INLINE traverseWithKey_ #-}
    
    2533
    +
    
    2523 2534
     -- | \(O(n)\). The function @'mapAccum'@ threads an accumulating
    
    2524 2535
     -- argument through the map in ascending order of keys.
    
    2525 2536
     --
    

  • compiler/GHC/Data/Word64Map/Lazy.hs
    ... ... @@ -149,6 +149,7 @@ module GHC.Data.Word64Map.Lazy (
    149 149
         , WM.map
    
    150 150
         , mapWithKey
    
    151 151
         , traverseWithKey
    
    152
    +    , traverseWithKey_
    
    152 153
         , traverseMaybeWithKey
    
    153 154
         , mapAccum
    
    154 155
         , mapAccumWithKey
    

  • compiler/GHC/Data/Word64Map/Strict.hs
    ... ... @@ -166,6 +166,7 @@ module GHC.Data.Word64Map.Strict (
    166 166
         , map
    
    167 167
         , mapWithKey
    
    168 168
         , traverseWithKey
    
    169
    +    , traverseWithKey_
    
    169 170
         , traverseMaybeWithKey
    
    170 171
         , mapAccum
    
    171 172
         , mapAccumWithKey
    

  • compiler/GHC/Data/Word64Map/Strict/Internal.hs
    ... ... @@ -168,6 +168,7 @@ module GHC.Data.Word64Map.Strict.Internal (
    168 168
         , map
    
    169 169
         , mapWithKey
    
    170 170
         , traverseWithKey
    
    171
    +    , traverseWithKey_
    
    171 172
         , traverseMaybeWithKey
    
    172 173
         , mapAccum
    
    173 174
         , mapAccumWithKey
    
    ... ... @@ -330,6 +331,7 @@ import GHC.Data.Word64Map.Internal
    330 331
       , toAscList
    
    331 332
       , toDescList
    
    332 333
       , toList
    
    334
    +  , traverseWithKey_
    
    333 335
       , union
    
    334 336
       , unions
    
    335 337
       , withoutKeys
    

  • compiler/GHC/Types/Unique/DFM.hs
    ... ... @@ -14,6 +14,9 @@ See Note [Unique Determinism] in GHC.Types.Unique for explanation why @Unique@ o
    14 14
     is not deterministic.
    
    15 15
     -}
    
    16 16
     
    
    17
    +{-# LANGUAGE MagicHash #-}
    
    18
    +{-# LANGUAGE UnboxedTuples #-}
    
    19
    +
    
    17 20
     {-# OPTIONS_GHC -Wall #-}
    
    18 21
     
    
    19 22
     module GHC.Types.Unique.DFM (
    
    ... ... @@ -79,6 +82,9 @@ import Data.Functor.Classes (Eq1 (..))
    79 82
     import Data.List (sortBy)
    
    80 83
     import Data.Function (on)
    
    81 84
     import GHC.Types.Unique.FM (UniqFM, nonDetUFMToList, ufmToIntMap, unsafeIntMapToUFM)
    
    85
    +import GHC.Data.SmallArray
    
    86
    +import GHC.Exts (State#, build)
    
    87
    +import GHC.ST (ST(..), runST)
    
    82 88
     import Unsafe.Coerce
    
    83 89
     import qualified GHC.Data.Word64Set as W
    
    84 90
     
    
    ... ... @@ -96,10 +102,10 @@ import qualified GHC.Data.Word64Set as W
    96 102
     -- This means `alterUDFM` consistent with `addToUDFM` and `adjustUDFM`,
    
    97 103
     -- so that for example `alterUDFM id k = id` and `alterUDFM (fmap f) k = adjustUDFM f k`
    
    98 104
     --
    
    99
    --- There is an implementation cost: each element is given a serial number
    
    100
    --- as it is added, and `udfmToList` sorts its result by this serial
    
    101
    --- number. So you should only use `UniqDFM` if you need the deterministic
    
    102
    --- property.
    
    105
    +-- There is an implementation cost: each element is given an insertion tag
    
    106
    +-- as it is added, and functions like `udfmToList` or `eltsUDFM` order their
    
    107
    +-- results by this tag (see Note [Cost of deterministic iteration]). So you
    
    108
    +-- should only use `UniqDFM` if you need the deterministic property.
    
    103 109
     --
    
    104 110
     -- `foldUDFM` also preserves determinism.
    
    105 111
     --
    
    ... ... @@ -112,7 +118,7 @@ import qualified GHC.Data.Word64Set as W
    112 118
     --
    
    113 119
     --
    
    114 120
     -- There's more than one way to implement this. The implementation here tags
    
    115
    --- every value with the insertion time that can later be used to sort the
    
    121
    +-- every value with its insertion tag that can later be used to sort the
    
    116 122
     -- values when asked to convert to a list.
    
    117 123
     --
    
    118 124
     -- Updating an existing key keeps the old tag. This keeps the order stable for
    
    ... ... @@ -125,7 +131,7 @@ import qualified GHC.Data.Word64Set as W
    125 131
     --
    
    126 132
     -- An alternative would be to have
    
    127 133
     --
    
    128
    ---   data UniqDFM ele = UDFM (M.IntMap ele) [ele]
    
    134
    +--   data UniqDFM ele = UDFM (Word64Map ele) [ele]
    
    129 135
     --
    
    130 136
     -- where the list determines the order. This makes deletion tricky as we'd
    
    131 137
     -- only accumulate elements in that list, but makes merging easier as you
    
    ... ... @@ -133,11 +139,11 @@ import qualified GHC.Data.Word64Set as W
    133 139
     -- Deletion can probably be done in amortized fashion when the size of the
    
    134 140
     -- list is twice the size of the set.
    
    135 141
     
    
    136
    --- | A type of values tagged with insertion time
    
    142
    +-- | A type of values carrying an insertion tag
    
    137 143
     data TaggedVal val =
    
    138 144
       TaggedVal
    
    139 145
         !val
    
    140
    -    {-# UNPACK #-} !Int -- ^ insertion time
    
    146
    +    {-# UNPACK #-} !Int -- ^ insertion tag
    
    141 147
       deriving stock (Data, Functor, Foldable, Traversable)
    
    142 148
     
    
    143 149
     taggedFst :: TaggedVal val -> val
    
    ... ... @@ -159,18 +165,30 @@ instance Eq val => Eq (TaggedVal val) where
    159 165
     data UniqDFM key ele =
    
    160 166
       UDFM
    
    161 167
         !(M.Word64Map (TaggedVal ele)) -- A map where keys are Unique's values and
    
    162
    -                                -- values are tagged with insertion time.
    
    163
    -                                -- The invariant is that all the tags will
    
    164
    -                                -- be distinct within a single map
    
    165
    -    {-# UNPACK #-} !Int         -- Upper bound on the values' insertion
    
    166
    -                                -- time. See Note [Overflow on plusUDFM]
    
    168
    +                                   -- values carry an insertion tag.
    
    169
    +    {-# UNPACK #-} !Int            -- Upper bound on the values' insertion
    
    170
    +                                   -- tags. See Note [Overflow on plusUDFM]
    
    171
    +  -- See Note [UDFM invariants]
    
    167 172
       deriving (Data, Functor)
    
    168 173
     
    
    169
    --- | Deterministic, in O(n log n).
    
    174
    +{- Note [UDFM invariants]
    
    175
    +~~~~~~~~~~~~~~~~~~~~~~~~~
    
    176
    +In a map (UDFM m ub):
    
    177
    +
    
    178
    + (a) The insertion tags of the elements of m are distinct.
    
    179
    + (b) Every tag lies in [0, ub).
    
    180
    +
    
    181
    +Consequently ub >= size m.
    
    182
    +
    
    183
    +The tags determine the order of deterministic iteration (eltsUDFM,
    
    184
    +udfmToList). See Note [Sorting a UDFM].
    
    185
    +-}
    
    186
    +
    
    187
    +-- | Deterministic. See Note [Cost of deterministic iteration].
    
    170 188
     instance Foldable (UniqDFM key) where
    
    171 189
       foldr = foldUDFM
    
    172 190
     
    
    173
    --- | Deterministic, in O(n log n).
    
    191
    +-- | Deterministic. See Note [Cost of deterministic iteration].
    
    174 192
     instance Traversable (UniqDFM key) where
    
    175 193
       traverse f = fmap listToUDFM_Directly
    
    176 194
                  . traverse (\(u,a) -> (u,) <$> f a)
    
    ... ... @@ -264,8 +282,8 @@ plusUDFM_CK f udfml@(UDFM _ i) udfmr@(UDFM _ j)
    264 282
     -- Note [Overflow on plusUDFM]
    
    265 283
     -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    266 284
     -- There are multiple ways of implementing plusUDFM.
    
    267
    --- The main problem that needs to be solved is overlap on times of
    
    268
    --- insertion between different keys in two maps.
    
    285
    +-- The main problem that needs to be solved is overlap on insertion
    
    286
    +-- tags between different keys in two maps.
    
    269 287
     -- Consider:
    
    270 288
     --
    
    271 289
     -- A = fromList [(a, (x, 1))]
    
    ... ... @@ -325,13 +343,27 @@ elemUDFM :: Uniquable key => key -> UniqDFM key elt -> Bool
    325 343
     elemUDFM k (UDFM m _i) = M.member (getKey $ getUnique k) m
    
    326 344
     
    
    327 345
     -- | Performs a deterministic fold over the UniqDFM.
    
    328
    --- It's O(n log n) while the corresponding function on `UniqFM` is O(n).
    
    346
    +--
    
    347
    +-- O(n) in the common case, with an O(n log n) fallback.
    
    348
    +--
    
    349
    +-- See Note [Cost of deterministic iteration].
    
    329 350
     foldUDFM :: (elt -> a -> a) -> a -> UniqDFM key elt -> a
    
    330 351
     {-# INLINE foldUDFM #-}
    
    331
    --- This INLINE prevents a regression in !10568
    
    332
    -foldUDFM k z m = foldr k z (eltsUDFM m)
    
    333
    -
    
    334
    --- | Like 'foldUDFM' but the function also receives a key
    
    352
    +-- Specialises k and z into M.foldr on the small-map path.
    
    353
    +foldUDFM k z (UDFM m ub)
    
    354
    +  | M.compareSize m 1 /= GT = M.foldr (k . taggedFst) z m
    
    355
    +  | otherwise               = fold_udfm k z m ub
    
    356
    +
    
    357
    +fold_udfm :: (elt -> a -> a) -> a -> M.Word64Map (TaggedVal elt) -> Int -> a
    
    358
    +{-# NOINLINE fold_udfm #-}
    
    359
    +-- Kept out of line so that foldUDFM's consumers don't inline the sort machinery.
    
    360
    +fold_udfm k z m ub
    
    361
    +  | usePigeonholeSort m ub = foldr k z (pigeonholeSort ub (\_ tv -> tv) m)
    
    362
    +  | otherwise              = foldr k z (map taggedFst (sort_it m))
    
    363
    +
    
    364
    +-- | Like 'foldUDFM' but the function also receives a key.
    
    365
    +--
    
    366
    +-- See Note [Cost of deterministic iteration].
    
    335 367
     foldWithKeyUDFM :: (Unique -> elt -> a -> a) -> a -> UniqDFM key elt -> a
    
    336 368
     {-# INLINE foldWithKeyUDFM #-}
    
    337 369
     -- This INLINE was copied from foldUDFM
    
    ... ... @@ -346,14 +378,113 @@ nonDetStrictFoldUDFM k z (UDFM m _i) = foldl' k' z m
    346 378
       where
    
    347 379
         k' acc (TaggedVal v _) = k v acc
    
    348 380
     
    
    381
    +{- Note [Cost of deterministic iteration]
    
    382
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    383
    +Deterministic iteration -- foldUDFM, eltsUDFM, udfmToList, and everything
    
    384
    +built on them -- orders elements by insertion tag. The element with the
    
    385
    +smallest tag can sit anywhere in the map, so every tag must be inspected,
    
    386
    +and, given a @UDFM m ub@ on the pigeonhole-sort path, an array with ub slots
    
    387
    +must be filled, before the first element can be emitted (see
    
    388
    +Note [Sorting a UDFM]). So beyond maps of a single element, deterministic
    
    389
    +iteration cannot stream: demanding any of the result processes the whole
    
    390
    +map. #27459 shows that cost hitting a consumer that only needed to know
    
    391
    +whether the result was non-empty.
    
    392
    +
    
    393
    +So: to test for emptiness, use isNullUDFM rather than null on eltsUDFM;
    
    394
    +for order-oblivious queries, prefer short-circuiting anyUDFM/allUDFM; and
    
    395
    +if you don't need the deterministic order at all, use nonDetStrictFoldUDFM.
    
    396
    +-}
    
    397
    +
    
    398
    +-- | Deterministic, in order of insertion.
    
    399
    +--
    
    400
    +-- See Note [Sorting a UDFM] and Note [Cost of deterministic iteration].
    
    349 401
     eltsUDFM :: UniqDFM key elt -> [elt]
    
    350
    -{-# INLINE eltsUDFM #-}
    
    351
    --- The INLINE makes it a good producer (from the map)
    
    352
    -eltsUDFM (UDFM m _i) = map taggedFst (sort_it m)
    
    402
    +{-# INLINE eltsUDFM #-}  -- so the small case is a good producer
    
    403
    +                         -- This matters for T13719.
    
    404
    +eltsUDFM (UDFM m ub)
    
    405
    +  | M.compareSize m 1 /= GT = build (\c n -> M.foldr (c . taggedFst) n m)
    
    406
    +  | otherwise               = elts_udfm m ub
    
    407
    +
    
    408
    +elts_udfm :: M.Word64Map (TaggedVal elt) -> Int -> [elt]
    
    409
    +{-# NOINLINE elts_udfm #-}
    
    410
    +-- Kept out of line so that eltsUDFM's consumers don't inline the sort machinery.
    
    411
    +elts_udfm m ub
    
    412
    +  | usePigeonholeSort m ub = pigeonholeSort ub (\_ tv -> tv) m
    
    413
    +  | otherwise              = map taggedFst (sort_it m)
    
    353 414
     
    
    354 415
     sort_it :: M.Word64Map (TaggedVal elt) -> [TaggedVal elt]
    
    355 416
     sort_it m = sortBy (compare `on` taggedSnd) (M.elems m)
    
    356 417
     
    
    418
    +
    
    419
    +{- Note [Sorting a UDFM]
    
    420
    +~~~~~~~~~~~~~~~~~~~~~~~~
    
    421
    +Deterministic iteration must yield a map's elements in order of their
    
    422
    +insertion tags. The obvious way is to sort on the tags, but we can do better:
    
    423
    +in (UDFM m ub) the tags are distinct indices into [0, ub) (see
    
    424
    +Note [UDFM invariants]), so each element can simply be placed at its own
    
    425
    +tag in an ub-slot array, which is then read back in index order. This is
    
    426
    +pigeonhole sort, with one element per hole.
    
    427
    +
    
    428
    +Cost: writing the elements is O(n) for n = M.size m, while allocating the
    
    429
    +array and reading it back are O(ub). Since n <= ub the total is O(ub). No
    
    430
    +comparisons are made.
    
    431
    +
    
    432
    +So the method wins only while the array stays dense, and ub never shrinks
    
    433
    +(overwrites keep bumping it, delete/filter shrink n but not ub).
    
    434
    +usePigeonholeSort therefore takes this path only when ub <= 4 * n, which
    
    435
    +bounds its cost at O(n), and falls back to the O(n log n) comparison sort
    
    436
    +otherwise.
    
    437
    +
    
    438
    +Unfilled slots contain a TaggedVal with tag -1 and value
    
    439
    +@unsafeCoerce () :: r@. This is safe because the value is never used: only
    
    440
    +slots with non-negative tags are read.
    
    441
    +
    
    442
    +pigeonholeSort also avoids intermediate lists: it fills the array by
    
    443
    +traversing the map directly, and emits its readout with 'build', so the foldr
    
    444
    +in fold_udfm fuses with it. This contributes significantly to the allocation
    
    445
    +reductions in InstanceMatching1 in !16292.
    
    446
    +-}
    
    447
    +
    
    448
    +-- | @ub <= 4 * size m@, computed without a full 'M.size' traversal.
    
    449
    +usePigeonholeSort :: M.Word64Map a -> Int -> Bool
    
    450
    +usePigeonholeSort m ub = M.compareSize m ceil_ub_div_4 /= LT
    
    451
    +  where
    
    452
    +    ceil_ub_div_4 = (ub + 3) `div` 4  -- ceil(ub/4): ub <= 4*n iff n >= ceil(ub/4)
    
    453
    +
    
    454
    +-- | Order the map's elements by tag. The tags must be distinct and in
    
    455
    +-- @[0, ub)@, and @mk@ must preserve them. See Note [Sorting a UDFM].
    
    456
    +pigeonholeSort :: forall e r. Int
    
    457
    +              -> (M.Key -> TaggedVal e -> TaggedVal r)
    
    458
    +              -> M.Word64Map (TaggedVal e)
    
    459
    +              -> [r]
    
    460
    +{-# INLINE pigeonholeSort #-}  -- Specialise mk and enable foldr/build fusion.
    
    461
    +pigeonholeSort ub mk m = build gen
    
    462
    +  where
    
    463
    +    -- The tag -1 marks unfilled slots; the value field is never read, but it
    
    464
    +    -- is strict, so it needs a WHNF value of type r. See Note [Sorting a UDFM].
    
    465
    +    hole :: TaggedVal r
    
    466
    +    hole = TaggedVal (unsafeCoerce ()) (-1)
    
    467
    +
    
    468
    +    fill :: SmallMutableArray s (TaggedVal r) -> State# s -> (# State# s, () #)
    
    469
    +    fill marr s = case M.traverseWithKey_ write m of ST st -> st s
    
    470
    +      where
    
    471
    +        write k tv = ST (\s' ->
    
    472
    +          (# writeSmallArray marr (taggedSnd tv) (mk k tv) s', () #))
    
    473
    +
    
    474
    +    gen :: forall b. (r -> b -> b) -> b -> b
    
    475
    +    gen cons nil = runST (ST (\s0 ->
    
    476
    +      case newSmallArray ub hole s0 of
    
    477
    +        (# s1, marr #) -> case fill marr s1 of
    
    478
    +          (# s2, () #) -> case unsafeFreezeSmallArray marr s2 of
    
    479
    +            (# s3, arr #) -> (# s3, readout arr 0 #)))
    
    480
    +      where
    
    481
    +        readout :: SmallArray (TaggedVal r) -> Int -> b
    
    482
    +        readout arr j
    
    483
    +          | j >= ub   = nil
    
    484
    +          | t < 0     = readout arr (j + 1)
    
    485
    +          | otherwise = cons v (readout arr (j + 1))
    
    486
    +          where TaggedVal v t = indexSmallArray arr j
    
    487
    +
    
    357 488
     filterUDFM :: (elt -> Bool) -> UniqDFM key elt -> UniqDFM key elt
    
    358 489
     filterUDFM p (UDFM m i) = UDFM (M.filter (\(TaggedVal v _) -> p v) m) i
    
    359 490
     
    
    ... ... @@ -371,11 +502,22 @@ udfmRestrictKeysSet (UDFM val_set i) set =
    371 502
       in UDFM (M.restrictKeys val_set key_set) i
    
    372 503
     
    
    373 504
     -- | Converts `UniqDFM` to a list, with elements in deterministic order.
    
    374
    --- It's O(n log n) while the corresponding function on `UniqFM` is O(n).
    
    505
    +--
    
    506
    +-- O(n) in the common case, with an O(n log n) fallback.
    
    507
    +--
    
    508
    +-- See Note [Cost of deterministic iteration].
    
    375 509
     udfmToList :: UniqDFM key elt -> [(Unique, elt)]
    
    376
    -udfmToList (UDFM m _i) =
    
    377
    -  [ (mkUniqueGrimily k, taggedFst v)
    
    378
    -  | (k, v) <- sortBy (compare `on` (taggedSnd . snd)) $ M.toList m ]
    
    510
    +-- NB: no INLINE, unlike eltsUDFM. udfmToList's one hot consumer is
    
    511
    +-- traverseUSDFM in the pattern-match checker, which doesn't fuse. Inlining
    
    512
    +-- the size dispatch into it regresses T17836.
    
    513
    +udfmToList (UDFM m ub)
    
    514
    +  | M.compareSize m 1 /= GT =
    
    515
    +      M.foldrWithKey (\k tv r -> (mkUniqueGrimily k, taggedFst tv) : r) [] m
    
    516
    +  | usePigeonholeSort m ub = pigeonholeSort ub
    
    517
    +      (\k tv -> TaggedVal (mkUniqueGrimily k, taggedFst tv) (taggedSnd tv)) m
    
    518
    +  | otherwise =
    
    519
    +      [ (mkUniqueGrimily k, taggedFst v)
    
    520
    +      | (k, v) <- sortBy (compare `on` (taggedSnd . snd)) $ M.toList m ]
    
    379 521
     
    
    380 522
     -- Determines whether two 'UniqDFM's contain the same keys.
    
    381 523
     equalKeysUDFM :: UniqDFM key a -> UniqDFM key b -> Bool