camfort-1.0: src/Camfort/Specification/Units/ModFile.hs
{- |
Module : Camfort.Specification.Units.ModFile
Description : Helpers for working with units-relevant ModFiles.
Copyright : (c) 2017, Dominic Orchard, Andrew Rice, Mistral Contrastin, Matthew Danish
License : Apache-2.0
Maintainer : dom.orchard@gmail.com
Stability : experimental
-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TupleSections #-}
module Camfort.Specification.Units.ModFile
(
genUnitsModFile
, initializeModFiles
, runCompileUnits
, dumpModFileCompiledUnits
) where
import Camfort.Analysis (analysisModFiles)
import Camfort.Specification.Units.Annotation (UA)
import Camfort.Specification.Units.Environment (Constraint(..), foldUnits, UnitInfo(..), colSort, Constraints)
import Camfort.Specification.Units.InferenceBackend (flattenConstraints, flattenUnits, genUnitAssignments')
import Camfort.Specification.Units.Monad
import Control.Monad.State (get, gets, lift)
import Data.Binary (Binary, decodeOrFail, encode)
import Data.Data (Data)
import Data.Generics.Uniplate.Operations (universeBi)
import Data.List (partition)
import qualified Data.Map as M
import Data.Maybe (mapMaybe, catMaybes)
import qualified Data.Set as S
import Data.Typeable (Typeable)
import GHC.Generics (Generic)
import qualified Language.Fortran.AST as F
import qualified Language.Fortran.Analysis as FA
import Language.Fortran.Util.ModFile
import Prelude hiding (mod)
-- | The data-structure stored in 'fortran-src mod files'
data CompiledUnits = CompiledUnits
{ cuTemplateMap :: TemplateMap
, cuNameParamMap :: NameParamMap
} deriving (Ord, Eq, Show, Data, Typeable, Generic)
instance Binary CompiledUnits
emptyCompiledUnits :: CompiledUnits
emptyCompiledUnits = CompiledUnits M.empty M.empty
combinedCompiledUnits :: ModFiles -> CompiledUnits
combinedCompiledUnits mfs = CompiledUnits { cuTemplateMap = M.unions tmaps
, cuNameParamMap = M.unions nmaps }
where
cus = map mfCompiledUnits mfs
tmaps = map cuTemplateMap cus
nmaps = map cuNameParamMap cus
-- | Name of the labeled data within a ModFile containing unit-specific info.
unitsCompiledDataLabel :: String
unitsCompiledDataLabel = "units-compiled-data"
mfCompiledUnits :: ModFile -> CompiledUnits
mfCompiledUnits mf = case lookupModFileData unitsCompiledDataLabel mf of
Nothing -> emptyCompiledUnits
Just bs -> case decodeOrFail bs of
Left _ -> emptyCompiledUnits
Right (_, _, cu) -> cu
-- | Initialize units-relevant ModFile information.
initializeModFiles :: UnitSolver ()
initializeModFiles = do
mfs <- lift . lift $ analysisModFiles
let compiledUnits = combinedCompiledUnits mfs
modifyTemplateMap . const . cuTemplateMap $ compiledUnits
modifyNameParamMap . const . cuNameParamMap $ compiledUnits
-- | Produce information for a "units-mod" file.
runCompileUnits :: UnitSolver CompiledUnits
runCompileUnits = do
cons <- usConstraints `fmap` get
pf <- usProgramFile `fmap` get
let unitAssigns = flattenConstraints cons
let epsilon = 0.001 -- arbitrary
let approxEq a b = abs (b - a) < epsilon
let variables = M.fromList [ (NPKVariable var, units) | ([UnitPow (UnitVar var) k], units) <- unitAssigns
, k `approxEq` 1 ]
-- Create sets of relevant program-unit and variable names.
let getName pu | F.Named n <- FA.puName pu = Just n | otherwise = Nothing
let puNameSet = S.fromList $ catMaybes [ getName pu | pu <- universeBi pf :: [F.ProgramUnit UA] ] ++
[ FA.varName e | F.BlInterface _ _ (Just e) _ _ _ <- universeBi pf :: [F.Block UA] ]
let puVarNameSet pu = S.fromList $
[ (FA.varName v, FA.srcName v) | F.DeclVariable _ _ v _ _ <- universeBi pu :: [F.Declarator UA] ] ++
[ (FA.varName v, FA.srcName v) | F.DeclArray _ _ v _ _ _ <- universeBi pu :: [F.Declarator UA] ]
-- Map of modules -> associated declared variables
let puVarNameMap :: M.Map F.ProgramUnitName (S.Set VV)
puVarNameMap = M.fromListWith S.union [ (F.getName pu, puVarNameSet pu)
| pu@F.PUModule {} <- universeBi pf :: [F.ProgramUnit UA] ]
-- Filter functions that cut out any information not having to do
-- with the current modules being compiled.
let filterPUs = M.filterWithKey (const . (`S.member` puNameSet))
-- FIXME: investigate whether we should include vars that are linked
-- transitively to current pf vars should also be included.
tmap <- (filterPUs . M.map optimiseTemplate) `fmap` gets usTemplateMap
let findNPK vv = ( (NPKVariable vv), ) <$> M.lookup (NPKVariable vv) variables
-- 'Name Param Map': module names -> (variables -> unit info)
let npm = M.filter (not . M.null) $ flip M.map puVarNameMap (M.fromList . mapMaybe findNPK . S.toList)
pure CompiledUnits { cuTemplateMap = tmap, cuNameParamMap = npm }
-- | Cut out unnecessary constraints in the template using the solver.
optimiseTemplate :: Constraints -> Constraints
optimiseTemplate cons = map (\ (l, r) -> ConEq (foldUnits l) r) optimised
where
unitAssigns = genUnitAssignments' (compileColSort) cons
unitPows = map (fmap flattenUnits) unitAssigns
optimised = filter cull $ map (fmap foldUnits . shiftTerms) unitPows
cull (lhs, _) = or [ True | (UnitPow (UnitParamPosAbs _) _) <- universeBi lhs ]
isUnitRHS (UnitPow (UnitName _) _) = True
isUnitRHS (UnitPow (UnitParamEAPAbs _) _) = True
isUnitRHS (UnitPow (UnitParamImpAbs _) _) = True
isUnitRHS (UnitPow (UnitParamPosAbs _) _) = False
isUnitRHS _ = False
negateCons = map (\ (UnitPow u k) -> UnitPow u (-k))
shiftTerms :: ([UnitInfo], [UnitInfo]) -> ([UnitInfo], [UnitInfo])
shiftTerms (lhs, rhs) = (lhsOk ++ negateCons rhsShift, rhsOk ++ negateCons lhsShift)
where
(lhsOk, lhsShift) = partition (not . isUnitRHS) lhs
(rhsOk, rhsShift) = partition isUnitRHS rhs
compileColSort = flip colSort
-- | Generate a new ModFile containing Units information.
genUnitsModFile :: F.ProgramFile UA -> CompiledUnits -> ModFile
genUnitsModFile pf cu = alterModFileData f unitsCompiledDataLabel $ genModFile pf
where
f _ = Just $ encode cu
-- | Return information about compiled units for debugging purposes
dumpModFileCompiledUnits :: ModFile -> Maybe String
dumpModFileCompiledUnits mf = do
bs <- lookupModFileData unitsCompiledDataLabel mf
cu <- case decodeOrFail bs of
Left _ -> Nothing
Right (_, _, cu) -> Just cu
pure . unlines $ [ "Template Map (size=" ++ show (M.size (cuTemplateMap cu)) ++ "):"
, concat [ unlines (i 2 fname':map (i 4 . show) temp)
| (fname, temp) <- M.toList (cuTemplateMap cu)
, let fname' = "Template for " ++ show fname ]
, "NameParam Map (size=" ++ show (M.size (cuNameParamMap cu)) ++ "):"
, concat [ unlines (i 2 mod':[ i 4 (show ui ++ " :: " ++ show v)
| (NPKVariable (v,_), ui) <- M.toList npkmap ])
| (mod, npkmap) <- M.toList (cuNameParamMap cu)
, let mod' = "Module " ++ show mod ]
]
where
i n s = replicate n ' ' ++ s