-- | Llvm code generator configuration
module GHC.CmmToLlvm.Config
  ( LlvmCgConfig(..)
  , LlvmConfig(..)
  , LlvmTarget(..)
  , initLlvmConfig
  )
where

import GHC.Prelude
import GHC.Platform

import GHC.Utils.Outputable
import GHC.Settings.Utils
import GHC.Utils.Panic
import GHC.CmmToLlvm.Version.Type (LlvmVersion)

import System.FilePath

data LlvmCgConfig = LlvmCgConfig
  { LlvmCgConfig -> Platform
llvmCgPlatform          :: !Platform     -- ^ Target platform
  , LlvmCgConfig -> SDocContext
llvmCgContext           :: !SDocContext  -- ^ Context for LLVM code generation
  , LlvmCgConfig -> Bool
llvmCgFillUndefWithGarbage :: !Bool      -- ^ Fill undefined literals with garbage values
  , LlvmCgConfig -> Bool
llvmCgSplitSection      :: !Bool         -- ^ Split sections
  , LlvmCgConfig -> Bool
llvmCgAvxEnabled        :: !Bool
  , LlvmCgConfig -> Maybe BmiVersion
llvmCgBmiVersion        :: Maybe BmiVersion  -- ^ (x86) BMI instructions
  , LlvmCgConfig -> Maybe LlvmVersion
llvmCgLlvmVersion       :: Maybe LlvmVersion -- ^ version of Llvm we're using
  , LlvmCgConfig -> Bool
llvmCgDoWarn            :: !Bool         -- ^ True ==> warn unsupported Llvm version
  , LlvmCgConfig -> String
llvmCgLlvmTarget        :: !String       -- ^ target triple passed to LLVM
  , LlvmCgConfig -> LlvmConfig
llvmCgLlvmConfig        :: !LlvmConfig   -- ^ Supported LLVM configurations.
                                             -- see Note [LLVM configuration]
  }

data LlvmTarget = LlvmTarget
  { LlvmTarget -> String
lDataLayout :: String
  , LlvmTarget -> String
lCPU        :: String
  , LlvmTarget -> [String]
lAttributes :: [String]
  }

-- Note [LLVM configuration]
-- ~~~~~~~~~~~~~~~~~~~~~~~~~
-- The `llvm-targets` and `llvm-passes` files are shipped with GHC and contain
-- information needed by the LLVM backend to invoke `llc` and `opt`.
-- Specifically:
--
--  * llvm-targets maps autoconf host triples to the corresponding LLVM
--    `data-layout` declarations. This information is extracted from clang using
--    the script in utils/llvm-targets/gen-data-layout.sh and should be updated
--    whenever we target a new version of LLVM.
--
--  * llvm-passes maps GHC optimization levels to sets of LLVM optimization
--    flags that GHC should pass to `opt`.
--
-- This information is contained in files rather the GHC source to allow users
-- to add new targets to GHC without having to recompile the compiler.
--

initLlvmConfig :: FilePath -> IO LlvmConfig
initLlvmConfig :: String -> IO LlvmConfig
initLlvmConfig String
top_dir
  = do
      targets <- String -> IO [(String, (String, String, String))]
forall a. Read a => String -> IO a
readAndParse String
"llvm-targets"
      passes <- readAndParse "llvm-passes"
      return $ LlvmConfig
        { llvmTargets = fmap mkLlvmTarget <$> targets
        , llvmPasses = passes
        }
  where
    readAndParse :: Read a => String -> IO a
    readAndParse :: forall a. Read a => String -> IO a
readAndParse String
name = do
      let f :: String
f = String
top_dir String -> String -> String
</> String
name
      llvmConfigStr <- String -> IO String
readFile String
f
      case maybeReadFuzzy llvmConfigStr of
        Just a
s -> a -> IO a
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return a
s
        Maybe a
Nothing -> String -> IO a
forall a. HasCallStack => String -> a
pgmError (String
"Can't parse LLVM config file: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
forall a. Show a => a -> String
show String
f)

    mkLlvmTarget :: (String, String, String) -> LlvmTarget
    mkLlvmTarget :: (String, String, String) -> LlvmTarget
mkLlvmTarget (String
dl, String
cpu, String
attrs) = String -> String -> [String] -> LlvmTarget
LlvmTarget String
dl String
cpu (String -> [String]
words String
attrs)

data LlvmConfig = LlvmConfig
  { LlvmConfig -> [(String, LlvmTarget)]
llvmTargets :: [(String, LlvmTarget)]
  , LlvmConfig -> [(Int, String)]
llvmPasses  :: [(Int, String)]
  }