Skip to content

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 function GHC.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 or a) to check that they are distinct. Ugh!

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 a UniqSupply, a first-class value that is an infinite tree of uniques -- see Note [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 is r, see above). But it doesn't have a UniqSupply. Instead it calls genSym directly, in takeUniqFromNameCache which calls uniqFromTag, which call genSym. Maybe that's ok -- but it's utterly un-documented.

  • And I have realised that monadic code we use uniqFromTag to call genSym 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 of newUniqueSupply :: 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 the NameCache (a field of HscEnv) do so, via takeUniqFromNameCache. Plugin authors would like to have newUnique :: Hsc Unique, and we should really provide it.

FastString uniques

ToDo: explain

Edited by Simon Peyton Jones
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information