Improve the Unique-supply story
Prompted by this ghc-devs thread I looked a bit at the question of how we generate unique supplies. It's not great!
Unique "tags"
-
Each
Uniquehas an 8-bit tag in its high-order bits. But I think the lower 56 bits are still globally unique (via the single functionGHC.Types.Unique.Supply.genSym :: IO Word64) so the tag is just for documentation. I am not 100% sure about this though. -
The 8-bit tag is a character that is provided literally in various
init-like calls. E.g.- GHC.Driver.Main:
newNameCache 'r' knownKeysOrigNameCache - GHC.Tc.Utils.Monad:
initTcRnIf 'a' hsc_env gbl_env lcl_env - etc
There is no way to grep for all the places that choose a tag (here
rora) to check that they are distinct. Ugh! - GHC.Driver.Main:
There is this Note in GHC.Builtin.Uniques
Note [Uniques for wired-in prelude things and known tags]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allocation of unique supply characters:
v,u: for renumbering value-, and usage- vars.
B: builtin
C-E: pseudo uniques (used in native-code generator)
I: GHCi evaluation
X: uniques from mkLocalUnique
_: unifiable tyvars (above)
0-9: prelude things below
(no numbers left any more..)
:: (prelude) parallel array data constructors
other a-z: lower case chars for unique supplies. Used so far:
a TypeChecking?
b Boxing tycons & datacons
c StgToCmm/Renamer
d desugarer
f AbsC flattener
i TypeChecking interface files
j constraint tuple superclass selectors
k constraint tuple tycons
m constraint tuple datacons
n Native/LLVM codegen
r Hsc name cache
s simplifier
u Cmm pipeline
y GHCi bytecode generator
z anonymous sums
But it does not say where these various unique supplies are created. There should be a
newtype UniqueTag = UT Char
so we'd see thing like initTc (UniqueTag 'a'). Now we can find all those tags by grepping for UniqueTag.
Allocating fresh uniques
-
In pure code,
Uniques come from aUniqSupply, a first-class value that is an infinite tree of uniques -- seeNote [How the unique supply works]in GHC.Types.Unique.Supply. -
Internally that uses a side-effecting IO call to
GHC.Types.Unique.Supply.genSym :: IO Word64. -
The global
NameCachehas a tag (actually it isr, see above). But it doesn't have aUniqSupply. Instead it callsgenSymdirectly, intakeUniqFromNameCachewhich callsuniqFromTag, which callgenSym. Maybe that's ok -- but it's utterly un-documented. -
And I have realised that monadic code we use
uniqFromTagto callgenSymdirectly. For example in the typehcecker monad:newUnique :: TcRnIf gbl lcl Unique newUnique = do { env <- getEnv ; let tag = env_ut env ; liftIO $! uniqFromTag tag }Doing this, rather than using a monad-carried splittable unique supply, was introduced by this commit:
commit 88013b784d77c069b7c083244d04a59ac2da2895 Author: nineonine <mail4chemik@gmail.com> Date: Fri Oct 11 00:31:58 2019 -0700 Optimize MonadUnique instances based on IO (#16843)So in fact a
UniqSupplyis now used much more rarely, for example as a result ofnewUniqueSupply :: TcRnIf gbl lcl UniqSupply.This is is a good change ... but one that is ill-documented.
-
The top-level monad
Hscdoesn't have a very obvious way to generate uniques. And yet theNameCache(a field ofHscEnv) do so, viatakeUniqFromNameCache. Plugin authors would like to havenewUnique :: Hsc Unique, and we should really provide it.
FastString uniques
ToDo: explain