| ... |
... |
@@ -10,13 +10,12 @@ Basically, the things need to be in class @Uniquable@, and we use the |
|
10
|
10
|
|
|
11
|
11
|
(A similar thing to @UniqSet@, as opposed to @Set@.)
|
|
12
|
12
|
|
|
13
|
|
-The interface is based on @FiniteMap@s, but the implementation uses
|
|
14
|
|
-@Data.IntMap@, which is both maintained and faster than the past
|
|
15
|
|
-implementation (see commit log).
|
|
|
13
|
+The implementation is a 'Word64Map' keyed by each element's 'Unique';
|
|
|
14
|
+see Note [Uniques and UniqFM shape].
|
|
16
|
15
|
|
|
17
|
|
-The @UniqFM@ interface maps directly to Data.IntMap, only
|
|
18
|
|
-``Data.IntMap.union'' is left-biased and ``plusUFM'' right-biased
|
|
19
|
|
-and ``addToUFM\_C'' and ``Data.IntMap.insertWith'' differ in the order
|
|
|
16
|
+The @UniqFM@ interface maps directly to Word64Map, only
|
|
|
17
|
+``Word64Map.union'' is left-biased and ``plusUFM'' right-biased
|
|
|
18
|
+and ``addToUFM\_C'' and ``Word64Map.insertWith'' differ in the order
|
|
20
|
19
|
of arguments of combining function.
|
|
21
|
20
|
-}
|
|
22
|
21
|
|
| ... |
... |
@@ -100,6 +99,55 @@ import qualified Data.Semigroup as Semi |
|
100
|
99
|
import Data.Functor.Classes (Eq1 (..))
|
|
101
|
100
|
import Data.Coerce
|
|
102
|
101
|
|
|
|
102
|
+{- Note [Uniques and UniqFM shape]
|
|
|
103
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
104
|
+A 'UniqFM' is a 'Word64Map' -- a big-endian PATRICIA trie -- keyed by the
|
|
|
105
|
+'Word64' underlying each 'Unique'. A 'Unique' splits into a tag in the top 8
|
|
|
106
|
+bits (UNIQUE_TAG_BITS), identifying the supply or namespace that minted it, and
|
|
|
107
|
+a running number in the low bits; see Note [Uniques and tags] in
|
|
|
108
|
+GHC.Types.Unique. Those numbers are allocated sequentially -- from the global
|
|
|
109
|
+'genSym' counter, or by 'uniqAway' from a set's largest local unique -- so they
|
|
|
110
|
+stay far below the full 'Word64' range. Within a tag the keys are thus dense and
|
|
|
111
|
+sequential, not scattered hashes, which keeps the trie shallow and is what makes
|
|
|
112
|
+a 'Word64Map' a good fit. And because those numbers only grow, inserts within a
|
|
|
113
|
+cluster tend to land above its existing keys -- a near-monotonic pattern that
|
|
|
114
|
+big-endian PATRICIA tries service superoptimally, well below the O(log n)
|
|
|
115
|
+worst case (see #24137).
|
|
|
116
|
+
|
|
|
117
|
+A PATRICIA trie branches on the most-significant bit on which its keys disagree,
|
|
|
118
|
+so the tag bits are tested first, nearest the root. Uniques are therefore
|
|
|
119
|
+partitioned by tag near the root: all keys with a given tag occupy a single
|
|
|
120
|
+subtree (the largest one whose prefix already fixes every tag bit), and two
|
|
|
121
|
+keys with different tags diverge closer to the root, where the trie switches on
|
|
|
122
|
+a tag bit. In other words the high-order tag clusters together the keys minted
|
|
|
123
|
+by each part of the compiler.
|
|
|
124
|
+
|
|
|
125
|
+This clustering would make tag-keyed bulk operations cheap -- collecting the
|
|
|
126
|
+distinct tags, filtering to a set of tags, or splitting into per-tag submaps
|
|
|
127
|
+could each reuse whole subtrees and touch O(number of distinct tags) nodes
|
|
|
128
|
+rather than O(size). That is only potential, though: GHC has no tag-aware bulk
|
|
|
129
|
+operations yet.
|
|
|
130
|
+
|
|
|
131
|
+The flip side is that the tag bits lengthen the path every per-key operation
|
|
|
132
|
+walks: a lookup, insert, or delete descends past the tag-distinguishing nodes
|
|
|
133
|
+near the root before reaching the number bits. Mixing tags thus adds branch
|
|
|
134
|
+levels above each entry -- at most 7, since the tag is 8 bits with its top bit
|
|
|
135
|
+clear for every ASCII tag. In-scope sets and 'VarSet's are the heavy case:
|
|
|
136
|
+keyed by 'Var' uniques, they gather ~8-11 supply tags (desugarer, simplifier,
|
|
|
137
|
+'uniqAway', wired-in, typechecker), spanning most of the tag bits, so a lookup
|
|
|
138
|
+descends roughly 3-5 extra levels. A 'NameEnv'/'TypeEnv' is milder: external
|
|
|
139
|
+names all share HscTag, so it is one big cluster plus a few wired-in and local
|
|
|
140
|
+tags, ~2-4 levels. A map holding a single tag collapses the tag bits into the
|
|
|
141
|
+root prefix and is correspondingly shallower, so keeping a map tag-homogeneous
|
|
|
142
|
+(or splitting it by tag) speeds up its lookups.
|
|
|
143
|
+
|
|
|
144
|
+The trie's key order is exploited the other way too, to mint uniques: 'uniqAway'
|
|
|
145
|
+derives a fresh unique not in an 'InScopeSet' from the largest key already in
|
|
|
146
|
+that set's local-unique region (an O(depth) 'lookupLT') and increments it,
|
|
|
147
|
+staying in that dedicated tag region (the 'X' tag), which cannot clash with
|
|
|
148
|
+counter-allocated ones. See Note [Local uniques] in GHC.Types.Var.Env.
|
|
|
149
|
+-}
|
|
|
150
|
+
|
|
103
|
151
|
-- | A finite map from @uniques@ of one type to
|
|
104
|
152
|
-- elements in another type.
|
|
105
|
153
|
--
|