| ... |
... |
@@ -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
|