module Camfort.Specification.Stencils.CheckFrontend
(
stencilChecking
, CheckResult
, checkFailure
, checkWarnings
, existingStencils
) where
import Control.Arrow
import Control.Monad.Reader (MonadReader, ReaderT, ask, runReaderT)
import Control.Monad.State.Strict
import Control.Monad.Writer.Strict hiding (Product)
import Data.Function (on)
import Data.Generics.Uniplate.Operations
import Data.List (intercalate, sort, union)
import Camfort.Analysis.Annotations
import Camfort.Analysis.CommentAnnotator
import qualified Camfort.Helpers.Vec as V
import Camfort.Specification.Parser (SpecParseError)
import Camfort.Specification.Stencils.CheckBackend
import qualified Camfort.Specification.Stencils.Consistency as C
import Camfort.Specification.Stencils.Generate
import qualified Camfort.Specification.Stencils.Parser as Parser
import Camfort.Specification.Stencils.Parser.Types (reqRegions)
import Camfort.Specification.Stencils.Model
import Camfort.Specification.Stencils.Syntax
import qualified Language.Fortran.AST as F
import qualified Language.Fortran.Analysis as FA
import qualified Language.Fortran.Analysis.BBlocks as FAB
import qualified Language.Fortran.Analysis.DataFlow as FAD
import qualified Language.Fortran.Util.Position as FU
import qualified Data.Map as M
import Data.Maybe
import Algebra.Lattice (joins1)
import Data.Int
import qualified Data.Set as S
newtype CheckResult = CheckResult [StencilResult]
getCheckResult :: CheckResult -> [StencilResult]
getCheckResult (CheckResult rs) = sort rs
instance Eq CheckResult where
(==) = (==) `on` getCheckResult
newtype CheckError = CheckError { getCheckError :: [StencilCheckError] }
newtype CheckWarning = CheckWarning { getCheckWarning :: [StencilCheckWarning] }
checkFailure :: CheckResult -> Maybe CheckError
checkFailure c = case catMaybes $ fmap toFailure (getCheckResult c) of
[] -> Nothing
xs -> Just $ CheckError xs
where toFailure (SCFail err) = Just err
toFailure _ = Nothing
checkWarnings :: CheckResult -> Maybe CheckWarning
checkWarnings c = case catMaybes $ fmap toWarning (getCheckResult c) of
[] -> Nothing
xs -> Just $ CheckWarning xs
where toWarning (SCWarn warn) = Just warn
toWarning _ = Nothing
data StencilResult
= SCOkay { scSpan :: FU.SrcSpan
, scSpec :: Specification
, scVar :: Variable
, scBodySpan :: FU.SrcSpan
}
| SCFail StencilCheckError
| SCWarn StencilCheckWarning
deriving (Eq)
class GetSpan a where
getSpan :: a -> FU.SrcSpan
instance GetSpan StencilResult where
getSpan SCOkay{scSpan = srcSpan} = srcSpan
getSpan (SCFail err) = getSpan err
getSpan (SCWarn warn) = getSpan warn
instance GetSpan StencilCheckError where
getSpan (SynToAstError _ srcSpan) = srcSpan
getSpan (NotWellSpecified (srcSpan, _) _) = srcSpan
getSpan (ParseError srcSpan _) = srcSpan
getSpan (RegionExists srcSpan _) = srcSpan
instance GetSpan StencilCheckWarning where
getSpan (DuplicateSpecification srcSpan) = srcSpan
getSpan (UnusedRegion srcSpan _) = srcSpan
instance Ord StencilResult where
compare = compare `on` getSpan
instance Ord StencilCheckError where
compare = compare `on` getSpan
data StencilCheckError
= SynToAstError SynToAstError FU.SrcSpan
| NotWellSpecified (FU.SrcSpan, SpecDecls) (FU.SrcSpan, SpecDecls)
| ParseError FU.SrcSpan (SpecParseError Parser.SpecParseError)
| RegionExists FU.SrcSpan Variable
deriving (Eq)
synToAstError :: SynToAstError -> FU.SrcSpan -> StencilResult
synToAstError err srcSpan = SCFail $ SynToAstError err srcSpan
notWellSpecified :: (FU.SrcSpan, SpecDecls) -> (FU.SrcSpan, SpecDecls) -> StencilResult
notWellSpecified got inferred = SCFail $ NotWellSpecified got inferred
parseError :: FU.SrcSpan -> (SpecParseError Parser.SpecParseError) -> StencilResult
parseError srcSpan err = SCFail $ ParseError srcSpan err
regionExistsError :: FU.SrcSpan -> Variable -> StencilResult
regionExistsError srcSpan r = SCFail $ RegionExists srcSpan r
data StencilCheckWarning
= DuplicateSpecification FU.SrcSpan
| UnusedRegion FU.SrcSpan Variable
deriving (Eq)
duplicateSpecification :: FU.SrcSpan -> StencilResult
duplicateSpecification = SCWarn . DuplicateSpecification
unusedRegion :: FU.SrcSpan -> Variable -> StencilResult
unusedRegion srcSpan var = SCWarn $ UnusedRegion srcSpan var
specOkay :: FU.SrcSpan -> Specification -> Variable -> FU.SrcSpan -> StencilResult
specOkay spanSpec@(FU.SrcSpan (FU.Position o1 _ _) (FU.Position o2 _ _)) spec var spanBody@(FU.SrcSpan (FU.Position o1' _ _) (FU.Position o2' _ _)) =
SCOkay { scSpan = spanSpec
, scSpec = spec
, scBodySpan = spanBody
, scVar = var
}
prettyWithSpan :: FU.SrcSpan -> String -> String
prettyWithSpan srcSpan s = show srcSpan ++ " " ++ s
instance Show CheckResult where
show = intercalate "\n" . fmap show . getCheckResult
instance Show CheckError where
show = intercalate "\n" . fmap show . getCheckError
instance Show CheckWarning where
show = intercalate "\n" . fmap show . getCheckWarning
instance Show StencilResult where
show SCOkay{ scSpan = span } = prettyWithSpan span "Correct."
show (SCFail err) = show err
show (SCWarn warn) = show warn
instance Show StencilCheckError where
show (SynToAstError err srcSpan) = prettyWithSpan srcSpan (show err)
show (NotWellSpecified (spanActual, stencilActual) (spanInferred, stencilInferred)) =
let sp = replicate 8 ' '
in concat [prettyWithSpan spanActual "Not well specified.\n", sp,
"Specification is:\n", sp, sp, pprintSpecDecls stencilActual, "\n",
sp, "but at ", show spanInferred, " the code behaves as\n", sp, sp,
pprintSpecDecls stencilInferred]
show (ParseError srcSpan err) = prettyWithSpan srcSpan (show err)
show (RegionExists srcSpan name) =
prettyWithSpan srcSpan ("Region '" ++ name ++ "' already defined")
instance Show StencilCheckWarning where
show (DuplicateSpecification srcSpan) = prettyWithSpan srcSpan
"Warning: Duplicate specification."
show (UnusedRegion srcSpan name) = prettyWithSpan srcSpan $
"Warning: Unused region '" ++ name ++ "'"
stencilChecking :: F.ProgramFile (FA.Analysis A) -> CheckResult
stencilChecking pf = CheckResult . snd . runWriter $ do
pf' <- annotateComments Parser.specParser (\srcSpan err -> tell [parseError srcSpan err]) pf
let
bm = FAD.genBlockMap pf'
bbm = FAB.genBBlockMap pf'
sgr = FAB.genSuperBBGr bbm
gr = FAB.superBBGrGraph sgr
dm = FAD.genDefMap bm
rd = FAD.reachingDefinitions dm gr
flowsGraph = FAD.genFlowsToGraph bm dm gr rd
beMap = FAD.genBackEdgeMap (FAD.dominators gr) gr
ivmap = FAD.genInductionVarMapByASTBlock beMap gr
results = descendBiM perProgramUnitCheck pf'
let addUnusedRegionsToResult = do
regions' <- fmap regions get
usedRegions' <- fmap usedRegions get
let unused = filter ((`notElem` usedRegions') . snd) regions'
mapM_ (addResult . uncurry unusedRegion) unused
output = checkResult $ execState
(runReaderT
(runChecker (results >> addUnusedRegionsToResult))
flowsGraph)
(startState ivmap)
tell output
data CheckState = CheckState
{ regionEnv :: RegionEnv
, checkResult :: [StencilResult]
, prog :: Maybe F.ProgramUnitName
, ivMap :: FAD.InductionVarMapByASTBlock
, regions :: [(FU.SrcSpan, Variable)]
, usedRegions :: [Variable]
}
addResult :: StencilResult -> Checker ()
addResult r = modify (\s -> s { checkResult = r : checkResult s })
informRegionsUsed :: [Variable] -> Checker ()
informRegionsUsed regions = modify
(\s -> s { usedRegions = usedRegions s `union` regions })
addRegionToTracked :: FU.SrcSpan -> Variable -> Checker ()
addRegionToTracked srcSpan@(FU.SrcSpan (FU.Position o1 _ _) (FU.Position o2 _ _)) r =
modify (\s -> s { regions = (srcSpan, r) : regions s })
regionExists :: Variable -> Checker Bool
regionExists reg = do
knownNames <- fmap (fmap snd . regions) get
pure $ reg `elem` knownNames
startState :: FAD.InductionVarMapByASTBlock -> CheckState
startState ivmap =
CheckState { regionEnv = []
, checkResult = []
, prog = Nothing
, ivMap = ivmap
, regions = []
, usedRegions = []
}
newtype Checker a =
Checker { runChecker :: ReaderT (FAD.FlowsGraph A) (State CheckState) a }
deriving ( Functor, Applicative, Monad
, MonadReader (FAD.FlowsGraph A)
, MonadState CheckState
)
parseCommentToAST :: FA.Analysis A -> FU.SrcSpan -> Checker (Either SynToAstError (FA.Analysis A))
parseCommentToAST ann span =
case getParseSpec (FA.prevAnnotation ann) of
Just stencilComment -> do
informRegionsUsed (reqRegions stencilComment)
renv <- fmap regionEnv get
let ?renv = renv
case synToAst stencilComment of
Right ast -> do
pfun <- either (\reg@(var,_) -> do
exists <- regionExists var
if exists
then addResult (regionExistsError span var)
>> pure id
else addRegionToTracked span var
>> pure (giveRegionSpec reg))
(pure . giveAstSpec . pure) ast
pure . pure $ onPrev pfun ann
Left err -> pure . Left $ err
_ -> pure . pure $ ann
updateRegionEnv :: FA.Analysis A -> Checker ()
updateRegionEnv ann =
case getRegionSpec (FA.prevAnnotation ann) of
Just renv -> modify (\s -> s { regionEnv = renv : regionEnv s })
_ -> pure ()
checkOffsetsAgainstSpec :: [(Variable, Multiplicity [[Int]])]
-> [(Variable, Specification)]
-> Bool
checkOffsetsAgainstSpec offsetMaps specMaps =
variablesConsistent &&
all (\case
(spec, Once (V.VL vs)) -> spec `C.consistent` (Once . toUNF) vs == C.Consistent
(spec, Mult (V.VL vs)) -> spec `C.consistent` (Mult . toUNF) vs == C.Consistent)
specToVecList
where
variablesConsistent =
let vs1 = sort . fmap fst $ offsetMaps
vs2 = sort . fmap fst $ specMaps
in vs1 == vs2
toUNF :: [ V.Vec n Int64 ] -> UnionNF n Offsets
toUNF = joins1 . map (return . fmap intToSubscript)
intToSubscript :: Int64 -> Offsets
intToSubscript i
| fromIntegral i == absoluteRep = SetOfIntegers
| otherwise = Offsets . S.singleton $ i
specToVecList :: [ (Specification, Multiplicity (V.VecList Int64)) ]
specToVecList = map (second (fmap V.fromLists)) specToIxs
specToIxs :: [ (Specification, Multiplicity [ [ Int64 ] ]) ]
specToIxs = pairWithFst specMaps (map (second toInt64) offsetMaps)
toInt64 :: Multiplicity [ [ Int ] ] -> Multiplicity [ [ Int64 ] ]
toInt64 = fmap (map (map fromIntegral))
pairWithFst :: Eq a => [ (a, b) ] -> [ (a, c) ] -> [ (b, c) ]
pairWithFst [] _ = []
pairWithFst ((key, val):xs) ys =
map ((val,) . snd) (filter ((key ==) . fst) ys) ++ pairWithFst xs ys
perProgramUnitCheck ::
F.ProgramUnit (FA.Analysis A) -> Checker (F.ProgramUnit (FA.Analysis A))
perProgramUnitCheck p@F.PUModule{} = do
modify (\s -> s { prog = Just $ FA.puName p })
descendBiM perBlockCheck p
perProgramUnitCheck p = descendBiM perBlockCheck p
perBlockCheck :: F.Block (FA.Analysis A) -> Checker (F.Block (FA.Analysis A))
perBlockCheck b@(F.BlComment ann span _) = do
ast <- parseCommentToAST ann span
case ast of
Left err -> addResult (synToAstError err span) *> pure b
Right ann' -> do
flowsGraph <- ask
updateRegionEnv ann'
let b' = F.setAnnotation ann' b
case (getAstSpec $ FA.prevAnnotation ann', stencilBlock $ FA.prevAnnotation ann') of
(Just specDecls, Just block) ->
case block of
s@(F.BlStatement _ span' _ (F.StExpressionAssign _ _ lhs _)) -> do
checkStencil flowsGraph s specDecls span' (isArraySubscript lhs) span
return b'
F.BlDo{} -> return b'
_ -> return b'
_ -> return b'
perBlockCheck b@(F.BlDo _ _ _ _ _ _ body _) = do
mapM_ (descendBiM perBlockCheck) body
return b
perBlockCheck b = do
updateRegionEnv . F.getAnnotation $ b
mapM_ (descendBiM perBlockCheck) $ children b
return b
checkStencil :: FAD.FlowsGraph A -> F.Block (FA.Analysis A) -> SpecDecls
-> FU.SrcSpan -> Maybe [F.Index (FA.Analysis Annotation)] -> FU.SrcSpan -> Checker ()
checkStencil flowsGraph block specDecls spanInferred maybeSubs span = do
let (subs, isStencil) = case maybeSubs of
Nothing -> ([], False)
Just subs -> (subs, True)
ivmap <- fmap ivMap get
let ivs = extractRelevantIVS ivmap block
let lhsN = fromMaybe [] (neighbourIndex ivmap subs)
relOffsets = fst . runWriter $ genOffsets flowsGraph ivs lhsN [block]
multOffsets = map (\relOffset ->
case relOffset of
(var, (True, offsets)) -> (var, Mult offsets)
(var, (False, offsets)) -> (var, Once offsets)) relOffsets
expandedDecls =
concatMap (\(vars,spec) -> map (flip (,) spec) vars) specDecls
let userDefinedIsStencils = map (\(_, Specification _ b) -> b) specDecls
if all (isStencil ==) userDefinedIsStencils && checkOffsetsAgainstSpec multOffsets expandedDecls
then mapM_ (\spec@(v,s) -> do
specExists <- seenBefore spec
if specExists then addResult (duplicateSpecification span)
else addResult (specOkay span s v spanInferred)) expandedDecls
else do
let inferred = fst . fst . runWriter $ genSpecifications flowsGraph ivs lhsN block
addResult (notWellSpecified (span, specDecls) (spanInferred, inferred))
where
seenBefore :: (Variable, Specification) -> Checker Bool
seenBefore (v,spec) = do
checkLog <- fmap checkResult get
pure $ any (\x -> case x of
SCOkay{ scSpec=spec'
, scBodySpan=bspan
, scVar = var}
-> spec' == spec && bspan == spanInferred && v == var
_ -> False) checkLog
genOffsets ::
FAD.FlowsGraph A
-> [Variable]
-> [Neighbour]
-> [F.Block (FA.Analysis A)]
-> Writer EvalLog [(Variable, (Bool, [[Int]]))]
genOffsets flowsGraph ivs lhs blocks = do
let (subscripts, _) = genSubscripts flowsGraph blocks
assocsSequence $ mkOffsets subscripts
where
mkOffsets = M.mapWithKey (\v -> indicesToRelativisedOffsets ivs v lhs)
existingStencils :: CheckResult -> [(Specification, FU.SrcSpan, Variable)]
existingStencils = mapMaybe getExistingStencil . getCheckResult
where getExistingStencil (SCOkay _ spec var bodySpan) = Just (spec, bodySpan, var)
getExistingStencil _ = Nothing