Skip to content

PrimOpIds and PrimOpWrappers

I discussed with @bgamari two related issues

  1. Use the primop wrappers, instead of always eta-expanding. The strange module GHC.PrimopWrappers is generated by utils/genprimopcode from primops.txt.pp. It contains bindings like

    (+#) :: Int# -> Int# -> Int#
    (+#) x y = (+#) x y

    This defines a curried function GHC.PrimopWrappers.(+#), whose implementation is an unboxed machine add. The code generator spots that saturated (+#) x y in the right hand side, and generates a machine add.

    Why do we need these curried functions? So that in a higher order call like f (+#), we can pass GHC.PrimopWrappers.(+#).

    An alternative, is to eta-expand, so that we have f (\x y. (+#) x y); now we don't need that wrapper.

    We must eta-expand for levity-polymorphic primops, because we can't compile levity-polymorphic code in GHC.PrimopWrappers. The hasNoBinding predicate is true for functions that have no binding -- and that must include levity-polymorphic primops.

    In !6197 (closed) I tried making hasNoBinding return False for all non-levity-polymorphic primops. But that caused

    ghci> data A
    <interactive>: internal error: stg_ap_v_ret

    This error comes from somewhere near this code in GHC.UI.Monad

    runDecls' :: GhciMonad m => [LHsDecl GhcPs] -> m (Maybe [GHC.Name])
    runDecls' decls = do
      st <- getGHCiState
      liftIO (print "hello2")
      reifyGHCi $ \x ->
      withProgName (progname st) $
       withArgs (args st) $
        reflectGHCi x $
          GHC.handleSourceError
            (\e -> do GHC.printException e;
                      return Nothing)
            (Just <$> GHC.runParsedDecls decls)

    If you comment out both the withProgName and withArgs calls, it works; if not, you crash.

    I'm not sure why; @bgamari may try to find out.

  2. De-duplicate primop wrappers. GHC currently builds two different Ids for PrimOps. In GHC.Types.Id.Make:

    mkPrimOpId :: PrimOp -> Id
    mkPrimOpId prim_op

    In GHC.Builtin.PrimOps:

    primOpWrapperId :: PrimOp -> Id
    primOpWrapperId op = mkVanillaGlobalWithInfo name ty info

    We put both into the symbol table.

    We also currently generate (using genprimopcode) two modules:

    • GHC.Prim.hs which contains accurate type signatures and Haddock docs; all RHS are bottom. Also data Char# etc (no RHS) for primitive types. This module is used only for Haddock to generate docs.
    • GHC.Primopwrappers.hs which contains no Haddock, just wrappers. Used for unsaturated applications.

    There seems no reason for all this duplication. Instead:

    • Kill off GHC.PrimopWrappers.hs; instead generate one file, but with module name GHC.Prim, with accurate types, Haddock, and (for rep-mono primops) wrappers.

      • This module is generated by genprimopcode from primops.txt.pp.
      • It contains Haddock docs for the primops; this documentation comes form primops.txt.pp
      • Even representation-polymorphic primops (for which a wrapper makes no sense) get a defn in GHC.Prim, solely to carry the Haddock documentation.
      • Primitive types get data Char# with Haddock docs in GHC.Prim.
      • Pseudo-ops like nullAddr# have a type and Haddock, but a dummy (bottom) RHS. They get compulsory unfoldings, so we will never call them.
    • Kill off primOpWrapperId; use primOpId instead. (Kill mkPrimOpWrapperUnique too.)

    Ben is going to look into this. Slightly fiddly, but makes things simpler; and the symbol table will be smaller.

NB: three kinds of things are defined in primops.txt.pp:

  • Primitive types. genprimopcode ignores them.
  • Primops. genprimopcode generates a wrapper function.
  • Pseudo-ops. genprimopcode ....

Things to watch out for: #18079 (closed), #19982

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