packages feed

ghc-9.14.1: GHC/Iface/Recomp/Types.hs

module GHC.Iface.Recomp.Types (
  IfaceSelfRecomp(..),
  IfaceDynFlags(..),
  pprIfaceDynFlags,
  missingExtraFlagInfo,
) where

import GHC.Prelude
import GHC.Fingerprint
import GHC.Utils.Outputable
import GHC.Iface.Flags
import GHC.Types.SafeHaskell
import GHC.Unit.Module.Deps
import GHC.Unit.Module

import GHC.Utils.Binary

import Control.DeepSeq

{-
Note [Self recompilation information in interface files]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The flag -fwrite-if-self-recomp controls whether
interface files contain the information necessary to answer the
question:

  Is the interface file up-to-date, relative to:
    * the source file it corresponds to,
    * the flags passed to the GHC invocation to compile it,
    * its dependencies (e.g. imported items, watched files added by addDependentFile, ...)

If there is no self-recompilation information stored, then we always re-generate
the interface file from scratch.

Why? Most packages are only built once either by a distribution or cabal
and then placed into an immutable store, after which we will never ask
this question. Therefore we can derive two benefits from omitting this
information.

* Primary motivation: It vastly reduces the surface area for creating
  non-deterministic interface files. See issue #10424 which motivated a
  proper fix to that issue. Distributions have long contained versions
  of GHC which just have broken self-recompilation checking (in order to
  get deterministic interface files).

* Secondary motivation: This reduces the size of interface files
  slightly.. the `mi_usages` field can be quite big but probably this
  isn't such a great benefit.

* Third motivation: Conceptually clarity about which parts of an
  interface file are used in order to **communicate** with subsequent
  packages about the **interface** for a module. And which parts are
  used to self-communicate during recompilation checking.

The main tracking issue is #22188 but fixes issues such as #10424 in a
proper way.

-}

-- | The information for a module which is only used when deciding whether to recompile
-- itself.
--
-- See Note [Self recompilation information in interface files]
data IfaceSelfRecomp =
    IfaceSelfRecomp { mi_sr_src_hash :: !Fingerprint
                       -- ^ Hash of the .hs source, used for recompilation checking.
                       , mi_sr_usages   :: [Usage]
                       -- ^ Usages; kept sorted so that it's easy to decide
                       -- whether to write a new iface file (changing usages
                       -- doesn't affect the hash of this module)
                       -- NOT STRICT!  we read this field lazily from the interface file
                       -- It is *only* consulted by the recompilation checker

                       , mi_sr_flag_hash :: !(FingerprintWithValue IfaceDynFlags)
                       -- ^ Hash of the important flags used when compiling the module, excluding
                       -- optimisation flags
                       , mi_sr_opt_hash :: !Fingerprint
                       -- ^ Hash of optimisation flags
                       , mi_sr_hpc_hash :: !Fingerprint
                       -- ^ Hash of hpc flags
                       , mi_sr_plugin_hash :: !Fingerprint
                       -- ^ Hash of plugins
                       }


instance Binary IfaceSelfRecomp where
  put_ bh (IfaceSelfRecomp{mi_sr_src_hash, mi_sr_usages, mi_sr_flag_hash, mi_sr_opt_hash, mi_sr_hpc_hash, mi_sr_plugin_hash}) = do
    put_ bh mi_sr_src_hash
    lazyPut bh mi_sr_usages
    put_ bh mi_sr_flag_hash
    put_ bh mi_sr_opt_hash
    put_ bh mi_sr_hpc_hash
    put_ bh mi_sr_plugin_hash

  get bh = do
    src_hash    <- get bh
    usages      <- lazyGet bh
    flag_hash   <- get bh
    opt_hash    <- get bh
    hpc_hash    <- get bh
    plugin_hash <- get bh
    return $ IfaceSelfRecomp { mi_sr_src_hash = src_hash, mi_sr_usages = usages, mi_sr_flag_hash = flag_hash, mi_sr_opt_hash = opt_hash, mi_sr_hpc_hash = hpc_hash, mi_sr_plugin_hash = plugin_hash }

instance Outputable IfaceSelfRecomp where
  ppr (IfaceSelfRecomp{mi_sr_src_hash, mi_sr_usages, mi_sr_flag_hash, mi_sr_opt_hash, mi_sr_hpc_hash, mi_sr_plugin_hash})
    = vcat [text "Self-Recomp"
            , nest 2 (vcat [ text "src hash:" <+> ppr mi_sr_src_hash
                           , text "flags:" <+> pprFingerprintWithValue missingExtraFlagInfo (fmap pprIfaceDynFlags mi_sr_flag_hash)
                           , text "opt hash:" <+> ppr mi_sr_opt_hash
                           , text "hpc hash:" <+> ppr mi_sr_hpc_hash
                           , text "plugin hash:" <+> ppr mi_sr_plugin_hash
                           , text "usages:" <+> ppr (map pprUsage mi_sr_usages)
                           ])]

instance NFData IfaceSelfRecomp where
  rnf (IfaceSelfRecomp src_hash usages flag_hash opt_hash hpc_hash plugin_hash)
    = rnf src_hash `seq` rnf usages `seq` rnf flag_hash `seq` rnf opt_hash `seq` rnf hpc_hash `seq` rnf plugin_hash `seq` ()

pprFingerprintWithValue :: SDoc -> FingerprintWithValue SDoc -> SDoc
pprFingerprintWithValue missingInfo (FingerprintWithValue fp mflags)
  = vcat $
    [ text "fingerprint:" <+> (ppr fp)
    ]
    ++ case mflags of
        Nothing -> [missingInfo]
        Just doc -> [doc]

pprUsage :: Usage -> SDoc
pprUsage UsagePackageModule{ usg_mod = mod, usg_mod_hash = hash, usg_safe = safe }
  = pprUsageImport mod hash safe
pprUsage UsageHomeModule{ usg_unit_id = unit_id, usg_mod_name = mod_name
                              , usg_mod_hash = hash, usg_safe = safe
                              , usg_exports = exports, usg_entities = entities }
  = pprUsageImport (mkModule unit_id mod_name) hash safe $$
    nest 2 (
        maybe empty (\v -> text "exports: " <> ppr v) exports $$
        vcat [ ppr n <+> ppr v | (n,v) <- entities ]
        )
pprUsage usage@UsageFile{}
  = hsep [text "addDependentFile",
          doubleQuotes (ftext (usg_file_path usage)),
          ppr (usg_file_hash usage)]
pprUsage usage@UsageMergedRequirement{}
  = hsep [text "merged", ppr (usg_mod usage), ppr (usg_mod_hash usage)]
pprUsage usage@UsageHomeModuleInterface{}
  = hsep [text "implementation", ppr (usg_mod_name usage)
                               , ppr (usg_unit_id usage)
                               , ppr (usg_iface_hash usage)]

pprUsageImport :: Outputable mod => mod -> Fingerprint -> IsSafeImport -> SDoc
pprUsageImport mod hash safe
  = hsep [ text "import", pp_safe, ppr mod
         , ppr hash ]
    where
        pp_safe | safe      = text "safe"
                | otherwise = text " -/ "