packages feed

crucible-0.7: src/Lang/Crucible/Utils/CoreRewrite.hs

------------------------------------------------------------------------
-- |
-- Module           : Lang.Crucible.Utils.CoreRewrite
-- Description      : Operations for manipulating Core CFGs
-- Copyright        : (c) Galois, Inc 2016
-- License          : BSD3
-- Maintainer       : Simon Winwood <sjw@galois.com>
-- Stability        : provisional
--
------------------------------------------------------------------------
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE RankNTypes #-}

module Lang.Crucible.Utils.CoreRewrite
( annotateCFGStmts
) where

import           Control.Lens

import qualified Data.Parameterized.Context as Ctx
import           Data.Parameterized.Map (Pair(..))
import           Data.Parameterized.TraversableFC

import           Lang.Crucible.CFG.Core
import           Lang.Crucible.CFG.Extension

------------------------------------------------------------------------
-- CFG annotation


-- | This function walks through all the blocks in the CFG calling
-- @fS@ on each @Stmt@ and @fT@ on each @TermStmt@.  These functions
-- return a possible annotaition statement (which has access to the
-- result of the statement, if any) along with a context diff which
-- describes any new variables.
annotateCFGStmts ::
   TraverseExt ext =>
   (forall cin cout. Some (BlockID blocks) -> Ctx.Size cout -> Stmt ext cin cout -> Maybe (StmtSeq ext blocks UnitType cout))
  -- ^ This is the annotation function.  The resulting @StmtSeq@ gets
  -- spliced in after the statement so that they can inspect the
  -- result if desired.  The terminal statement is ignored.
  -> (forall ctx'. Some (BlockID blocks)  -> Ctx.Size ctx' -> TermStmt blocks ret ctx' -> Maybe (StmtSeq ext blocks UnitType ctx'))
  -- ^ As above but for the final term stmt, where the annotation will
  -- be _before_ the term stmt.
  -> CFG ext blocks ctx ret -> CFG ext blocks ctx ret
annotateCFGStmts fS fT = mapCFGBlocks (annotateBlockStmts fS fT)

mapCFGBlocks :: (forall x. Block ext blocks ret x -> Block ext blocks ret x)
             -> CFG ext blocks ctx ret -> CFG ext blocks ctx ret
mapCFGBlocks f cfg = cfg { cfgBlockMap = fmapFC f (cfgBlockMap cfg) }

annotateBlockStmts ::
  forall ext blocks ret ctx.
  TraverseExt ext =>
  (forall cin cout. Some (BlockID blocks) -> Ctx.Size cout -> Stmt ext cin cout -> Maybe (StmtSeq ext blocks UnitType cout))
  -- ^ This is the annotation function.  Annotation statements go
  -- after the statement so that they can inspect the result if
  -- desired.  We use Diff here over CtxEmbedding as the remainder of
  -- the statements can't use the result of the annotation function
  -> (forall ctx'. Some (BlockID blocks) -> Ctx.Size ctx' -> TermStmt blocks ret ctx' -> Maybe (StmtSeq ext blocks UnitType ctx'))
  -- ^ As above but for the final term stmt, where the annotation will
  -- be _before_ the term stmt.
  -> Block ext blocks ret ctx
  -> Block ext blocks ret ctx
annotateBlockStmts fS fT b = b & blockStmts %~ goStmts initialCtxe
  where
    initialCtxe = Ctx.identityEmbedding (Ctx.size (blockInputs b))
    goStmts :: forall ctx' ctx''. Ctx.CtxEmbedding ctx' ctx''
            -> StmtSeq ext blocks ret ctx' -> StmtSeq ext blocks ret ctx''
    goStmts ctxe (ConsStmt loc stmt rest) =
      case applyEmbeddingStmt ctxe stmt of
        Pair stmt' ctxe' ->
          case fS (Some $ blockID b) (ctxe' ^. Ctx.ctxeSize) stmt' of
            Nothing  -> ConsStmt loc stmt' (goStmts ctxe' rest)
            Just annotSeq ->
              ConsStmt loc stmt' (appendStmtSeq ctxe' annotSeq (flip goStmts rest))
    goStmts ctxe (TermStmt loc term) =
      let term' = Ctx.applyEmbedding ctxe term in
      case fT (Some $ blockID b) (ctxe ^. Ctx.ctxeSize) term' of
        Nothing -> TermStmt loc term'
        Just annotSeq ->
          -- FIXME: we could use extendContext here instead
          let restf :: forall fctx. Ctx.CtxEmbedding ctx' fctx -> StmtSeq ext blocks ret fctx
              restf ctxe'' = TermStmt loc (Ctx.applyEmbedding ctxe'' term)
          in appendStmtSeq ctxe annotSeq restf

stmtDiff :: Stmt ext ctx ctx' -> Ctx.Diff ctx ctx'
stmtDiff stmt =
  case stmt of
    SetReg {}        -> Ctx.knownDiff
    ExtendAssign{}   -> Ctx.knownDiff
    CallHandle {}    -> Ctx.knownDiff
    Print {}         -> Ctx.knownDiff
    ReadGlobal {}    -> Ctx.knownDiff
    WriteGlobal {}   -> Ctx.knownDiff
    FreshConstant{}  -> Ctx.knownDiff
    FreshFloat{}     -> Ctx.knownDiff
    FreshNat{}       -> Ctx.knownDiff
    NewRefCell {}    -> Ctx.knownDiff
    NewEmptyRefCell{}-> Ctx.knownDiff
    ReadRefCell {}   -> Ctx.knownDiff
    WriteRefCell {}  -> Ctx.knownDiff
    DropRefCell {}   -> Ctx.knownDiff
    Assert {}        -> Ctx.knownDiff
    Assume {}        -> Ctx.knownDiff

-- | This appends two @StmtSeq@, throwing away the @TermStmt@ from the first @StmtSeq@
-- It could probably be generalized to @Ctx.Diff@ instead of an embedding.
appendStmtSeq :: forall ext blocks ret ret' ctx ctx'.
                 Ctx.CtxEmbedding ctx ctx'
              -> StmtSeq ext blocks ret  ctx'
              -> (forall ctx''. Ctx.CtxEmbedding ctx ctx'' -> StmtSeq ext blocks ret' ctx'')
              -> StmtSeq ext blocks ret' ctx'
appendStmtSeq ctxe seq1 seq2f = go ctxe seq1
  where
    go :: forall ctx''.
          Ctx.CtxEmbedding ctx ctx''
          -> StmtSeq ext blocks ret ctx''
          -> StmtSeq ext blocks ret' ctx''
    go ctxe' (ConsStmt loc stmt rest) =
      -- This just throws away the new variables, which is OK as seq2
      -- can't reference them.
      let ctxe'' = Ctx.extendEmbeddingRightDiff (stmtDiff stmt) ctxe'
      in ConsStmt loc stmt (go ctxe'' rest)
    go ctxe' (TermStmt _loc _term)    = seq2f ctxe'