From 5a6699efb16dcb6e9ac222abba7efb1e56b507b2 Mon Sep 17 00:00:00 2001
From: Duncan Coutts <duncan@community.haskell.org>
Date: Fri, 3 Jul 2015 13:46:34 +0100
Subject: [PATCH] Make the profiling detail level configurable with a flag

New flags: --profiling-detail and --library-profiling-detail.
When profiling is enabled (by the existing flags) then these flags
are taken into account to set the profiling detail level.

The levels are:
 none
 default
 exported-functions
 toplevel-functions
 all-functions

The default value for ghc for libraries is exported-functions and
for exes is toplevel-functions.

On GHC these levels correspond to the -fprof-auto* flags. The
ghc-prof-options will override this (just because it's passed to
ghc at the end).
---
 Cabal/Distribution/Simple/Compiler.hs       | 49 ++++++++++++++++++++-
 Cabal/Distribution/Simple/Configure.hs      | 42 ++++++++++++++----
 Cabal/Distribution/Simple/GHC.hs            |  9 ++--
 Cabal/Distribution/Simple/GHC/Internal.hs   | 19 ++++++--
 Cabal/Distribution/Simple/LocalBuildInfo.hs |  4 +-
 Cabal/Distribution/Simple/Program/GHC.hs    |  2 +
 Cabal/Distribution/Simple/Setup.hs          | 37 +++++++++++++++-
 cabal-install/Distribution/Client/Config.hs |  2 +
 cabal-install/Distribution/Client/Setup.hs  |  9 +++-
 9 files changed, 154 insertions(+), 19 deletions(-)

diff --git a/Cabal/Distribution/Simple/Compiler.hs b/Cabal/Distribution/Simple/Compiler.hs
index faad14328a..f217d73b40 100644
--- a/Cabal/Distribution/Simple/Compiler.hs
+++ b/Cabal/Distribution/Simple/Compiler.hs
@@ -53,13 +53,19 @@ module Distribution.Simple.Compiler (
         parmakeSupported,
         reexportedModulesSupported,
         renamingPackageFlagsSupported,
-        packageKeySupported
+        packageKeySupported,
+
+        -- * Support for profiling detail levels
+        ProfDetailLevel(..),
+        knownProfDetailLevels,
+        flagToProfDetailLevel,
   ) where
 
 import Distribution.Compiler
 import Distribution.Version (Version(..))
 import Distribution.Text (display)
 import Language.Haskell.Extension (Language(Haskell98), Extension)
+import Distribution.Simple.Utils (lowercase)
 
 import Control.Monad (liftM)
 import Distribution.Compat.Binary (Binary)
@@ -285,3 +291,44 @@ ghcSupported key comp =
           case M.lookup key (compilerProperties comp) of
             Just "YES" -> True
             _          -> False
+
+-- ------------------------------------------------------------
+-- * Profiling detail level
+-- ------------------------------------------------------------
+
+-- | Some compilers (notably GHC) support profiling and can instrument
+-- programs so the system can account costs to different functions. There are
+-- different levels of detail that can be used for this accounting.
+-- For compilers that do not support this notion or the particular detail
+-- levels, this is either ignored or just capped to some similar level
+-- they do support.
+--
+data ProfDetailLevel = ProfDetailNone
+                     | ProfDetailDefault
+                     | ProfDetailExportedFunctions
+                     | ProfDetailToplevelFunctions
+                     | ProfDetailAllFunctions
+                     | ProfDetailOther String
+    deriving (Eq, Generic, Read, Show)
+
+instance Binary ProfDetailLevel
+
+flagToProfDetailLevel :: String -> ProfDetailLevel
+flagToProfDetailLevel "" = ProfDetailDefault
+flagToProfDetailLevel s  =
+    case lookup (lowercase s)
+                [ (name, value)
+                | (primary, aliases, value) <- knownProfDetailLevels
+                , name <- primary : aliases ]
+      of Just value -> value
+         Nothing    -> ProfDetailOther s
+
+knownProfDetailLevels :: [(String, [String], ProfDetailLevel)]
+knownProfDetailLevels =
+  [ ("default",            [],                  ProfDetailDefault)
+  , ("none",               [],                  ProfDetailNone)
+  , ("exported-functions", ["exported"],        ProfDetailExportedFunctions)
+  , ("toplevel-functions", ["toplevel", "top"], ProfDetailToplevelFunctions)
+  , ("all-functions",      ["all"],             ProfDetailAllFunctions)
+  ]
+
diff --git a/Cabal/Distribution/Simple/Configure.hs b/Cabal/Distribution/Simple/Configure.hs
index a94df2148e..861f8df00a 100644
--- a/Cabal/Distribution/Simple/Configure.hs
+++ b/Cabal/Distribution/Simple/Configure.hs
@@ -52,7 +52,7 @@ import Distribution.Compiler
 import Distribution.Utils.NubList
 import Distribution.Simple.Compiler
     ( CompilerFlavor(..), Compiler(..), compilerFlavor, compilerVersion
-    , compilerInfo
+    , compilerInfo, ProfDetailLevel(..), knownProfDetailLevels
     , showCompilerId, unsupportedLanguages, unsupportedExtensions
     , PackageDB(..), PackageDBStack, reexportedModulesSupported
     , packageKeySupported, renamingPackageFlagsSupported )
@@ -345,7 +345,6 @@ configure :: (GenericPackageDescription, HookedBuildInfo)
 configure (pkg_descr0, pbi) cfg
   = do  let distPref = fromFlag (configDistPref cfg)
             buildDir' = distPref </> "build"
-            verbosity = fromFlag (configVerbosity cfg)
 
         setupMessage verbosity "Configuring" (packageId pkg_descr0)
 
@@ -682,10 +681,24 @@ configure (pkg_descr0, pbi) cfg
             ++ "is not being built. Linking will fail if any executables "
             ++ "depend on the library."
 
-        let withProf_ = fromFlagOrDefault False (configProf cfg)
-            withProfExe_ = fromFlagOrDefault withProf_ $ configProfExe cfg
-            withProfLib_ = fromFlagOrDefault withProfExe_ $ configProfLib cfg
-        when (withProfExe_ && not withProfLib_) $ warn verbosity $
+        -- The --profiling flag sets the default for both libs and exes,
+        -- but can be overidden by --library-profiling, or the old deprecated
+        -- --executable-profiling flag.
+        let profEnabledLibOnly = configProfLib cfg
+            profEnabledBoth    = fromFlagOrDefault False (configProf cfg)
+            profEnabledLib = fromFlagOrDefault profEnabledBoth profEnabledLibOnly
+            profEnabledExe = fromFlagOrDefault profEnabledBoth (configProfExe cfg)
+
+        -- The --profiling-detail and --library-profiling-detail flags behave
+        -- similarly
+        profDetailLibOnly <- checkProfDetail (configProfLibDetail cfg)
+        profDetailBoth    <- liftM (fromFlagOrDefault ProfDetailDefault)
+                                   (checkProfDetail (configProfDetail cfg))
+        let profDetailLib = fromFlagOrDefault profDetailBoth profDetailLibOnly
+            profDetailExe = profDetailBoth
+
+        when (profEnabledExe && not profEnabledLib) $
+          warn verbosity $
                "Executables will be built with profiling, but library "
             ++ "profiling is disabled. Linking will fail if any executables "
             ++ "depend on the library."
@@ -717,10 +730,12 @@ configure (pkg_descr0, pbi) cfg
                     instantiatedWith    = hole_insts,
                     withPrograms        = programsConfig''',
                     withVanillaLib      = fromFlag $ configVanillaLib cfg,
-                    withProfLib         = withProfLib_,
+                    withProfLib         = profEnabledLib,
                     withSharedLib       = withSharedLib_,
                     withDynExe          = withDynExe_,
-                    withProfExe         = withProfExe_,
+                    withProfExe         = profEnabledExe,
+                    withProfLibDetail   = profDetailLib,
+                    withProfExeDetail   = profDetailExe,
                     withOptimization    = fromFlag $ configOptimization cfg,
                     withDebugInfo       = fromFlag $ configDebugInfo cfg,
                     withGHCiLib         = fromFlagOrDefault ghciLibByDefault $
@@ -768,6 +783,8 @@ configure (pkg_descr0, pbi) cfg
         return lbi
 
     where
+      verbosity = fromFlag (configVerbosity cfg)
+
       addExtraIncludeLibDirs pkg_descr =
           let extraBi = mempty { extraLibDirs = configExtraLibDirs cfg
                                , PD.includeDirs = configExtraIncludeDirs cfg}
@@ -779,6 +796,15 @@ configure (pkg_descr0, pbi) cfg
                       , executables = modifyExecutable  `map`
                                       executables pkg_descr}
 
+      checkProfDetail (Flag (ProfDetailOther other)) = do
+        warn verbosity $
+             "Unknown profiling detail level '" ++ other
+          ++ "', using default.\n"
+          ++ "The profiling detail levels are: " ++ intercalate ", "
+             [ name | (name, _, _) <- knownProfDetailLevels ]
+        return (Flag ProfDetailDefault)
+      checkProfDetail other = return other
+
 mkProgramsConfig :: ConfigFlags -> ProgramConfiguration -> ProgramConfiguration
 mkProgramsConfig cfg initialProgramsConfig = programsConfig
   where
diff --git a/Cabal/Distribution/Simple/GHC.hs b/Cabal/Distribution/Simple/GHC.hs
index 91cdff3911..a4acf95df3 100644
--- a/Cabal/Distribution/Simple/GHC.hs
+++ b/Cabal/Distribution/Simple/GHC.hs
@@ -473,7 +473,8 @@ buildOrReplLib forRepl verbosity numJobs pkg_descr lbi lib clbi = do
 
       profOpts    = vanillaOpts `mappend` mempty {
                       ghcOptProfilingMode = toFlag True,
-                      ghcOptProfilingAuto = toFlag GhcProfAutoExported,
+                      ghcOptProfilingAuto = Internal.profDetailLevelFlag True
+                                              (withProfLibDetail lbi),
                       ghcOptHiSuffix      = toFlag "p_hi",
                       ghcOptObjSuffix     = toFlag "p_o",
                       ghcOptExtra         = toNubListR $ hcProfOptions GHC libBi,
@@ -762,7 +763,8 @@ buildOrReplExe forRepl verbosity numJobs _pkg_descr lbi
                    }
       profOpts   = baseOpts `mappend` mempty {
                       ghcOptProfilingMode  = toFlag True,
-                      ghcOptProfilingAuto  = toFlag GhcProfAutoToplevel,
+                      ghcOptProfilingAuto  = Internal.profDetailLevelFlag False
+                                               (withProfExeDetail lbi),
                       ghcOptHiSuffix       = toFlag "p_hi",
                       ghcOptObjSuffix      = toFlag "p_o",
                       ghcOptExtra          = toNubListR (hcProfOptions GHC exeBi),
@@ -979,7 +981,8 @@ libAbiHash verbosity _pkg_descr lbi lib clbi = do
                    }
       profArgs   = vanillaArgs `mappend` mempty {
                      ghcOptProfilingMode = toFlag True,
-                     ghcOptProfilingAuto = toFlag GhcProfAutoExported,
+                     ghcOptProfilingAuto = Internal.profDetailLevelFlag True
+                                             (withProfLibDetail lbi),
                      ghcOptHiSuffix      = toFlag "p_hi",
                      ghcOptObjSuffix     = toFlag "p_o",
                      ghcOptExtra         = toNubListR $ hcProfOptions GHC libBi
diff --git a/Cabal/Distribution/Simple/GHC/Internal.hs b/Cabal/Distribution/Simple/GHC/Internal.hs
index b161a52989..ee1f603957 100644
--- a/Cabal/Distribution/Simple/GHC/Internal.hs
+++ b/Cabal/Distribution/Simple/GHC/Internal.hs
@@ -25,7 +25,8 @@ module Distribution.Simple.GHC.Internal (
         getHaskellObjects,
         mkGhcOptPackages,
         substTopDir,
-        checkPackageDbEnvVar
+        checkPackageDbEnvVar,
+        profDetailLevelFlag,
  ) where
 
 import Distribution.Simple.GHC.ImplInfo ( GhcImplInfo (..) )
@@ -41,10 +42,11 @@ import Distribution.PackageDescription as PD
 import Distribution.Compat.Exception ( catchExit, catchIO )
 import Distribution.Lex (tokenizeQuotedWords)
 import Distribution.Simple.Compiler
-         ( CompilerFlavor(..), Compiler(..), DebugInfoLevel(..), OptimisationLevel(..) )
+         ( CompilerFlavor(..), Compiler(..), DebugInfoLevel(..)
+         , OptimisationLevel(..), ProfDetailLevel(..) )
 import Distribution.Simple.Program.GHC
 import Distribution.Simple.Setup
-         ( toFlag )
+         ( Flag, toFlag )
 import qualified Distribution.ModuleName as ModuleName
 import Distribution.Simple.Program
          ( Program(..), ConfiguredProgram(..), ProgramConfiguration
@@ -499,3 +501,14 @@ checkPackageDbEnvVar compilerName packagePathEnvVar = do
                ++ packagePathEnvVar ++ " is incompatible with Cabal. Use the "
                ++ "flag --package-db to specify a package database (it can be "
                ++ "used multiple times)."
+
+profDetailLevelFlag :: Bool -> ProfDetailLevel -> Flag GhcProfAuto
+profDetailLevelFlag forLib mpl =
+    case mpl of
+      ProfDetailNone                -> mempty
+      ProfDetailDefault | forLib    -> toFlag GhcProfAutoExported
+                        | otherwise -> toFlag GhcProfAutoToplevel
+      ProfDetailExportedFunctions   -> toFlag GhcProfAutoExported
+      ProfDetailToplevelFunctions   -> toFlag GhcProfAutoToplevel
+      ProfDetailAllFunctions        -> toFlag GhcProfAutoAll
+      ProfDetailOther _             -> mempty
diff --git a/Cabal/Distribution/Simple/LocalBuildInfo.hs b/Cabal/Distribution/Simple/LocalBuildInfo.hs
index 214f216969..76cb64c97b 100644
--- a/Cabal/Distribution/Simple/LocalBuildInfo.hs
+++ b/Cabal/Distribution/Simple/LocalBuildInfo.hs
@@ -74,7 +74,7 @@ import Distribution.Package
          , PackageName )
 import Distribution.Simple.Compiler
          ( Compiler, compilerInfo, PackageDBStack, DebugInfoLevel
-         , OptimisationLevel )
+         , OptimisationLevel, ProfDetailLevel )
 import Distribution.Simple.PackageIndex
          ( InstalledPackageIndex, allPackages )
 import Distribution.ModuleName ( ModuleName )
@@ -139,6 +139,8 @@ data LocalBuildInfo = LocalBuildInfo {
         withSharedLib :: Bool,  -- ^Whether to build shared versions of libs.
         withDynExe    :: Bool,  -- ^Whether to link executables dynamically
         withProfExe   :: Bool,  -- ^Whether to build executables for profiling.
+        withProfLibDetail :: ProfDetailLevel, -- ^Level of automatic profile detail.
+        withProfExeDetail :: ProfDetailLevel, -- ^Level of automatic profile detail.
         withOptimization :: OptimisationLevel, -- ^Whether to build with optimization (if available).
         withDebugInfo :: DebugInfoLevel, -- ^Whether to emit debug info (if available).
         withGHCiLib   :: Bool,  -- ^Whether to build libs suitable for use with GHCi.
diff --git a/Cabal/Distribution/Simple/Program/GHC.hs b/Cabal/Distribution/Simple/Program/GHC.hs
index a243b1d7e3..ad0967b20c 100644
--- a/Cabal/Distribution/Simple/Program/GHC.hs
+++ b/Cabal/Distribution/Simple/Program/GHC.hs
@@ -292,6 +292,8 @@ renderGhcOptions comp opts
   , [ "-prof" | flagBool ghcOptProfilingMode ]
 
   , case flagToMaybe (ghcOptProfilingAuto opts) of
+      _ | not (flagBool ghcOptProfilingMode)
+                                -> []
       Nothing                   -> []
       Just GhcProfAutoAll
         | flagProfAuto implInfo -> ["-fprof-auto"]
diff --git a/Cabal/Distribution/Simple/Setup.hs b/Cabal/Distribution/Simple/Setup.hs
index 16285270ff..e571c5c507 100644
--- a/Cabal/Distribution/Simple/Setup.hs
+++ b/Cabal/Distribution/Simple/Setup.hs
@@ -85,6 +85,7 @@ import Distribution.Simple.Compiler
          ( CompilerFlavor(..), defaultCompilerFlavor, PackageDB(..)
          , DebugInfoLevel(..), flagToDebugInfoLevel
          , OptimisationLevel(..), flagToOptimisationLevel
+         , ProfDetailLevel(..), flagToProfDetailLevel
          , absolutePackageDBPath )
 import Distribution.Simple.Utils
          ( wrapText, wrapLine, lowercase, intercalate )
@@ -295,6 +296,10 @@ data ConfigFlags = ConfigFlags {
                                           -- executables.
     configProf          :: Flag Bool,     -- ^Enable profiling in the library
                                           -- and executables.
+    configProfDetail    :: Flag ProfDetailLevel, -- ^Profiling detail level
+                                          --  in the library and executables.
+    configProfLibDetail :: Flag ProfDetailLevel, -- ^Profiling  detail level
+                                                 -- in the library
     configConfigureArgs :: [String],      -- ^Extra arguments to @configure@
     configOptimization  :: Flag OptimisationLevel,  -- ^Enable optimization.
     configProgPrefix    :: Flag PathTemplate, -- ^Installed executable prefix.
@@ -351,6 +356,8 @@ defaultConfigFlags progConf = emptyConfigFlags {
     configDynExe       = Flag False,
     configProfExe      = NoFlag,
     configProf         = NoFlag,
+    configProfDetail   = NoFlag,
+    configProfLibDetail= NoFlag,
     configOptimization = Flag NormalOptimisation,
     configProgPrefix   = Flag (toPathTemplate ""),
     configProgSuffix   = Flag (toPathTemplate ""),
@@ -463,7 +470,7 @@ configureOptions showOrParseArgs =
          (boolOpt [] [])
 
       ,option "" ["profiling"]
-         "Executable profiling (requires library profiling)"
+         "Executable and library profiling"
          configProf (\v flags -> flags { configProf = v })
          (boolOpt [] [])
 
@@ -472,6 +479,19 @@ configureOptions showOrParseArgs =
          configProfExe (\v flags -> flags { configProfExe = v })
          (boolOpt [] [])
 
+      ,option "" ["profiling-detail"]
+         ("Profiling detail level for executable and library (default, " ++
+          "none, exported-functions, toplevel-functions,  all-functions).")
+         configProfDetail (\v flags -> flags { configProfDetail = v })
+         (reqArg' "level" (Flag . flagToProfDetailLevel)
+                          showProfDetailLevelFlag)
+
+      ,option "" ["library-profiling-detail"]
+         "Profiling detail level for libraries only."
+         configProfLibDetail (\v flags -> flags { configProfLibDetail = v })
+         (reqArg' "level" (Flag . flagToProfDetailLevel)
+                          showProfDetailLevelFlag)
+
       ,multiOption "optimization"
          configOptimization (\v flags -> flags { configOptimization = v })
          [optArg' "n" (Flag . flagToOptimisationLevel)
@@ -646,6 +666,17 @@ showPackageDbList = map showPackageDb
     showPackageDb (Just UserPackageDB)          = "user"
     showPackageDb (Just (SpecificPackageDB db)) = db
 
+showProfDetailLevelFlag :: Flag ProfDetailLevel -> [String]
+showProfDetailLevelFlag dl =
+  case dl of
+    NoFlag                           -> []
+    Flag ProfDetailNone              -> ["none"]
+    Flag ProfDetailDefault           -> ["default"]
+    Flag ProfDetailExportedFunctions -> ["exported-functions"]
+    Flag ProfDetailToplevelFunctions -> ["toplevel-functions"]
+    Flag ProfDetailAllFunctions      -> ["all-functions"]
+    Flag (ProfDetailOther other)     -> [other]
+
 
 parseDependency :: Parse.ReadP r (PackageName, InstalledPackageId)
 parseDependency = do
@@ -743,6 +774,8 @@ instance Monoid ConfigFlags where
     configDynExe        = mempty,
     configProfExe       = mempty,
     configProf          = mempty,
+    configProfDetail    = mempty,
+    configProfLibDetail = mempty,
     configConfigureArgs = mempty,
     configOptimization  = mempty,
     configProgPrefix    = mempty,
@@ -786,6 +819,8 @@ instance Monoid ConfigFlags where
     configDynExe        = combine configDynExe,
     configProfExe       = combine configProfExe,
     configProf          = combine configProf,
+    configProfDetail    = combine configProfDetail,
+    configProfLibDetail = combine configProfLibDetail,
     configConfigureArgs = combine configConfigureArgs,
     configOptimization  = combine configOptimization,
     configProgPrefix    = combine configProgPrefix,
diff --git a/cabal-install/Distribution/Client/Config.hs b/cabal-install/Distribution/Client/Config.hs
index 6143c9e05e..2768c13f3f 100644
--- a/cabal-install/Distribution/Client/Config.hs
+++ b/cabal-install/Distribution/Client/Config.hs
@@ -272,6 +272,8 @@ instance Monoid SavedConfig where
         configSharedLib           = combine configSharedLib,
         configDynExe              = combine configDynExe,
         configProfExe             = combine configProfExe,
+        configProfDetail          = combine configProfDetail,
+        configProfLibDetail       = combine configProfLibDetail,
         -- TODO: NubListify
         configConfigureArgs       = lastNonEmpty configConfigureArgs,
         configOptimization        = combine configOptimization,
diff --git a/cabal-install/Distribution/Client/Setup.hs b/cabal-install/Distribution/Client/Setup.hs
index 264e139820..759b13bc01 100644
--- a/cabal-install/Distribution/Client/Setup.hs
+++ b/cabal-install/Distribution/Client/Setup.hs
@@ -393,7 +393,7 @@ configureOptions = commandOptions configureCommand
 
 filterConfigureFlags :: ConfigFlags -> Version -> ConfigFlags
 filterConfigureFlags flags cabalLibVersion
-  | cabalLibVersion >= Version [1,22,0] [] = flags_latest
+  | cabalLibVersion >= Version [1,23,0] [] = flags_latest
   -- ^ NB: we expect the latest version to be the most common case.
   | cabalLibVersion <  Version [1,3,10] [] = flags_1_3_10
   | cabalLibVersion <  Version [1,10,0] [] = flags_1_10_0
@@ -403,13 +403,18 @@ filterConfigureFlags flags cabalLibVersion
   | cabalLibVersion <  Version [1,19,2] [] = flags_1_19_1
   | cabalLibVersion <  Version [1,21,1] [] = flags_1_20_0
   | cabalLibVersion <  Version [1,22,0] [] = flags_1_21_0
+  | cabalLibVersion <  Version [1,23,0] [] = flags_1_22_0
   | otherwise = flags_latest
   where
     -- Cabal >= 1.19.1 uses '--dependency' and does not need '--constraint'.
     flags_latest = flags        { configConstraints = [] }
 
+    -- Cabal < 1.23 doesn't know about '--profiling-detail'.
+    flags_1_22_0 = flags_latest { configProfDetail    = NoFlag
+                                , configProfLibDetail = NoFlag }
+
     -- Cabal < 1.22 doesn't know about '--disable-debug-info'.
-    flags_1_21_0 = flags_latest { configDebugInfo = NoFlag }
+    flags_1_21_0 = flags_1_22_0 { configDebugInfo = NoFlag }
 
     -- Cabal < 1.21.1 doesn't know about 'disable-relocatable'
     -- Cabal < 1.21.1 doesn't know about 'enable-profiling'
-- 
GitLab