module Camfort.Specification.Stencils.InferenceFrontend
(
InferMode(..)
, stencilInference
) where
import Control.Monad.State.Strict
import Control.Monad.Reader
import Control.Monad.Writer.Strict hiding (Product)
import Camfort.Analysis.CommentAnnotator
import Camfort.Specification.Stencils.CheckBackend (synToAst)
import Camfort.Specification.Stencils.CheckFrontend
(CheckResult, existingStencils, stencilChecking)
import Camfort.Specification.Stencils.Generate
import Camfort.Specification.Stencils.InferenceBackend
import Camfort.Specification.Stencils.Model
import Camfort.Specification.Stencils.Syntax
import qualified Camfort.Specification.Stencils.Parser as Parser
import Camfort.Specification.Stencils.Parser.Types (SpecInner)
import qualified Camfort.Specification.Stencils.Synthesis as Synth
import Camfort.Analysis.Annotations
import Camfort.Helpers (collect, descendReverseM, descendBiReverseM)
import qualified Camfort.Helpers.Vec as V
import Camfort.Input
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 Data.Data
import Data.Foldable
import Data.Generics.Uniplate.Operations
import Data.Graph.Inductive.Graph hiding (isEmpty)
import qualified Data.Map as M
import qualified Data.Set as S
import Data.Maybe
import Data.Monoid ((<>))
data InferMode =
AssignMode | EvalMode | Synth
deriving (Eq, Show, Data, Read)
instance Default InferMode where
defaultValue = AssignMode
data InferState = IS {
ivMap :: FAD.InductionVarMapByASTBlock
, visitedNodes :: [Int]}
data InferEnv = IE
{
ieExistingSpecs :: [(Specification, FU.SrcSpan, Variable)]
, ieFlowsGraph :: FAD.FlowsGraph A
, ieInferMode :: InferMode
, ieMarker :: Char
, ieMetaInfo :: F.MetaInfo
}
type LogLine = (FU.SrcSpan, Either [([Variable], Specification)] (String,Variable))
type Inferer = WriterT [LogLine]
(ReaderT InferEnv
(State InferState))
runInferer :: CheckResult
-> InferMode
-> Char
-> F.MetaInfo
-> FAD.InductionVarMapByASTBlock
-> FAD.FlowsGraph A
-> Inferer a
-> (a, [LogLine])
runInferer cr mode marker mi ivmap flTo =
flip evalState (IS ivmap [])
. flip runReaderT env
. runWriterT
where env = IE
{ ieExistingSpecs = existingStencils cr
, ieFlowsGraph = flTo
, ieInferMode = mode
, ieMarker = marker
, ieMetaInfo = mi
}
specToSynSpec :: SpecInner -> Maybe Specification
specToSynSpec spec = let ?renv = [] in
case synToAst spec of
Left err -> Nothing
Right x -> Just x
stencilInference :: InferMode
-> Char
-> F.ProgramFile (FA.Analysis A)
-> (F.ProgramFile (FA.Analysis A), [LogLine])
stencilInference mode marker pf =
(F.ProgramFile mi pus', log1)
where
(pf'@(F.ProgramFile mi pus), _log0) =
if mode == Synth
then runWriter (annotateComments Parser.specParser (const . const . pure $ ()) pf)
else (pf, [])
(pus', log1) = runWriter (transformBiM perPU pus)
checkRes = stencilChecking pf
perPU :: F.ProgramUnit (FA.Analysis A)
-> Writer [LogLine] (F.ProgramUnit (FA.Analysis A))
perPU pu | Just _ <- FA.bBlocks $ F.getAnnotation pu = do
let
blocksM = mapM perBlockInfer (F.programUnitBody pu)
pum = (F.updateProgramUnitBody pu) <$> blocksM
rd = FAD.reachingDefinitions dm gr
Just gr = M.lookup (FA.puName pu) bbm
flTo = FAD.genFlowsToGraph bm dm gr rd
beMap = FAD.genBackEdgeMap (FAD.dominators gr) gr
ivMap = FAD.genInductionVarMapByASTBlock beMap gr
(pu', log) = runInferer checkRes mode marker mi ivMap flTo pum
tell log
return pu'
perPU pu = return pu
bm = FAD.genBlockMap pf'
bbm = FAB.genBBlockMap pf'
dm = FAD.genDefMap bm
genSpecsAndReport ::
FU.SrcSpan -> [Neighbour]
-> F.Block (FA.Analysis A)
-> Inferer [([Variable], Specification)]
genSpecsAndReport span lhsIxs block = do
(IS ivmap _) <- get
let ivs = extractRelevantIVS ivmap block
mode <- fmap ieInferMode ask
flowsGraph <- fmap ieFlowsGraph ask
let ((specs, visited), evalInfos) = runWriter $ genSpecifications flowsGraph ivs lhsIxs block
modify (\state -> state { visitedNodes = visitedNodes state ++ visited })
tell [ (span, Left specs) ]
when (mode == EvalMode) $ do
tell [ (span, Right ("EVALMODE: assign to relative array subscript\
\ (tag: tickAssign)","")) ]
forM_ evalInfos $ \evalInfo ->
tell [ (span, Right evalInfo) ]
forM_ specs $ \spec ->
when (show spec == "") $
tell [ (span, Right ("EVALMODE: Cannot make spec\
\ (tag: emptySpec)","")) ]
return specs
perBlockInfer :: F.Block (FA.Analysis A)
-> Inferer (F.Block (FA.Analysis A))
perBlockInfer = perBlockInfer' False
perBlockInfer' _ b@F.BlComment{} = pure b
perBlockInfer' inDo b@(F.BlStatement ann span@(FU.SrcSpan lp _) _ stmnt) = do
(IS ivmap visitedStmts) <- get
mode <- fmap ieInferMode ask
let label = fromMaybe (1) (FA.insLabel ann)
if label `elem` visitedStmts
then
return b
else do
userSpecs <- fmap ieExistingSpecs ask
let lhses = [lhs | (F.StExpressionAssign _ _ lhs _)
<- universe stmnt :: [F.Statement (FA.Analysis A)]]
specs <- mapM (genSpecsFor ivmap mode) lhses
marker <- fmap ieMarker ask
mi <- fmap ieMetaInfo ask
if mode == Synth && not (null specs) && specs /= [[]]
then
let specComment = Synth.formatSpec mi tabs marker (span, Left specs')
specs' = concatMap (mapMaybe noSpecAlready) specs
noSpecAlready (vars, spec) =
if null vars'
then Nothing
else Just (vars', spec)
where vars' = filter (\v -> (spec, span, v) `notElem` userSpecs) vars
tabs = FU.posColumn lp 1
(FU.SrcSpan loc _) = span
span' = FU.SrcSpan (lp {FU.posColumn = 1}) (lp {FU.posColumn = 1})
ann' = ann { FA.prevAnnotation = (FA.prevAnnotation ann) { refactored = Just loc } }
in pure (F.BlComment ann' span' (F.Comment specComment))
else return b
where
genSpecsFor _ _ (F.ExpValue _ _ (F.ValVariable _)) | inDo = genSpecsAndReport span [] b
genSpecsFor ivmap mode lhs =
case isArraySubscript lhs of
Just subs ->
case neighbourIndex ivmap subs of
Just lhs -> genSpecsAndReport span lhs b
Nothing -> if mode == EvalMode
then do
tell [(span , Right ("EVALMODE: LHS is an array\
\ subscript we can't handle \
\(tag: LHSnotHandled)",""))]
pure []
else pure []
_ -> pure []
perBlockInfer' _ b@(F.BlDo ann span lab cname lab' mDoSpec body tlab) = do
body' <- mapM (descendBiReverseM (perBlockInfer' True)) (reverse body)
return $ F.BlDo ann span lab cname lab' mDoSpec (reverse body') tlab
perBlockInfer' inDo b =
descendReverseM (descendBiReverseM (perBlockInfer' inDo)) b