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
Unique
has 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
r
ora
) 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,
Unique
s 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
NameCache
has a tag (actually it isr
, see above). But it doesn't have aUniqSupply
. Instead it callsgenSym
directly, intakeUniqFromNameCache
which callsuniqFromTag
, which callgenSym
. Maybe that's ok -- but it's utterly un-documented. -
And I have realised that monadic code we use
uniqFromTag
to callgenSym
directly. 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
UniqSupply
is 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
Hsc
doesn'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