crucible-0.7: src/Lang/Crucible/CFG/Core.hs
{- |
Module : Lang.Crucible.CFG.Core
Description : SSA-based control flow graphs
Copyright : (c) Galois, Inc 2014-2016
License : BSD3
Maintainer : Joe Hendrix <jhendrix@galois.com>
Define a SSA-based control flow graph data structure using a side-effect free
expression syntax.
Unlike usual SSA forms, we do not use phi-functions, but instead rely on an
argument-passing formulation for basic blocks. In this form, concrete values
are bound to formal parameters instead of using phi-functions that switch
on the place from which you jumped.
-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ParallelListComp #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Lang.Crucible.CFG.Core
( -- * CFG
CFG(..)
, SomeCFG(..)
, HasSomeCFG(..)
, AnyCFG(..)
, ppCFG
, ppCFG'
, cfgArgTypes
, cfgReturnType
, CFGPostdom
-- * Blocks
, BlockMap
, getBlock
, extendBlockMap
, BlockID(..)
, extendBlockID
, extendBlockID'
, Block(..)
, blockLoc
, blockStmts
, withBlockTermStmt
, nextBlocks
-- * Jump targets
, JumpTarget(..)
, extendJumpTarget
, jumpTargetID
, SwitchTarget(..)
, switchTargetID
, extendSwitchTarget
-- * Statements
, StmtSeq(..)
, firstStmtLoc
, stmtSeqTermStmt
, Stmt(..)
, ppStmt
, nextStmtHeight
, applyEmbeddingStmt
, TermStmt(..)
, termStmtNextBlocks
-- * Expressions
, Expr(..)
, Reg(..)
, extendReg
, lastReg
-- * Re-exports
, module Lang.Crucible.Types
, module Lang.Crucible.CFG.Common
, module Data.Parameterized.Classes
, module Data.Parameterized.Some
) where
import Control.Applicative
import Control.Lens
import Data.Bimap (Bimap)
import Data.Maybe (fromMaybe)
import Data.Kind (Type)
import Data.Parameterized.Classes
import Data.Parameterized.Map (Pair(..))
import Data.Parameterized.Some
import Data.Parameterized.TraversableFC
import Data.String
import Prettyprinter
import What4.ProgramLoc
import What4.Symbol
import Lang.Crucible.CFG.Common
import Lang.Crucible.CFG.Expr
import Lang.Crucible.FunctionHandle
import Lang.Crucible.Types
import Lang.Crucible.Utils.PrettyPrint
#ifdef UNSAFE_OPS
-- We deliberately import Context.Unsafe as it is the only one that supports
-- the unsafe coerces between an index and its extension.
import Data.Parameterized.Context as Ctx hiding (Assignment)
import Data.Parameterized.Context.Unsafe as Ctx (Assignment)
import Unsafe.Coerce
#else
import Data.Parameterized.Context as Ctx
#endif
------------------------------------------------------------------------
-- Reg
-- | A temporary register identifier introduced during translation.
-- These are unique to the current function. The `ctx` parameter
-- is a context containing the types of all the temporary registers
-- currently in scope, and the `tp` parameter indicates the type
-- of this register (which necessarily appears somewhere in `ctx`)
newtype Reg (ctx :: Ctx CrucibleType) (tp :: CrucibleType) = Reg { regIndex :: Ctx.Index ctx tp }
deriving (Eq, TestEquality, Ord, OrdF)
instance Show (Reg ctx tp) where
show (Reg i) = '$' : show (indexVal i)
instance ShowF (Reg ctx)
instance Pretty (Reg ctx tp) where
pretty (Reg i) = pretty '$' <> pretty (indexVal i)
instance ApplyEmbedding' Reg where
applyEmbedding' ctxe r = Reg $ applyEmbedding' ctxe (regIndex r)
instance ExtendContext' Reg where
extendContext' diff r = Reg $ extendContext' diff (regIndex r)
-- | Finds the value of the most-recently introduced register in scope.
lastReg :: KnownContext ctx => Reg (ctx ::> tp) tp
lastReg = Reg (nextIndex knownSize)
-- | Extend the set of registers in scope for a given register value
-- without changing its index or type.
extendReg :: Reg ctx tp -> Reg (ctx ::> r) tp
extendReg = Reg . extendIndex . regIndex
------------------------------------------------------------------------
-- Expr
-- | An expression is just an App applied to some registers.
newtype Expr ext (ctx :: Ctx CrucibleType) (tp :: CrucibleType)
= App (App ext (Reg ctx) tp)
instance IsString (Expr ext ctx (StringType Unicode)) where
fromString = App . StringLit . fromString
instance PrettyApp (ExprExtension ext) => Pretty (Expr ext ctx tp) where
pretty (App a) = ppApp pretty a
ppAssignment :: Assignment (Reg ctx) args -> [Doc ann]
ppAssignment = toListFC pretty
instance ( TraversableFC (ExprExtension ext)
) => ApplyEmbedding' (Expr ext) where
applyEmbedding' ctxe (App e) = App (mapApp (applyEmbedding' ctxe) e)
instance ( TraversableFC (ExprExtension ext)
) => ExtendContext' (Expr ext) where
extendContext' diff (App e) = App (mapApp (extendContext' diff) e)
------------------------------------------------------------------------
-- BlockID
-- | A `BlockID` uniquely identifies a block in a control-flow graph.
-- Each block has an associated context, indicating the formal arguments
-- it expects to find in registers from its calling locations.
newtype BlockID (blocks :: Ctx (Ctx CrucibleType)) (tp :: Ctx CrucibleType)
= BlockID { blockIDIndex :: Ctx.Index blocks tp }
deriving (Eq, Ord)
instance TestEquality (BlockID blocks) where
testEquality (BlockID x) (BlockID y) = testEquality x y
instance OrdF (BlockID blocks) where
compareF (BlockID x) (BlockID y) = compareF x y
instance Pretty (BlockID blocks tp) where
pretty (BlockID i) = pretty '%' <> pretty (indexVal i)
instance Show (BlockID blocks ctx) where
show (BlockID i) = '%' : show (indexVal i)
instance ShowF (BlockID blocks)
extendBlockID :: KnownDiff l r => BlockID l tp -> BlockID r tp
extendBlockID = BlockID . extendIndex . blockIDIndex
extendBlockID' :: Diff l r -> BlockID l tp -> BlockID r tp
extendBlockID' e = BlockID . extendIndex' e . blockIDIndex
------------------------------------------------------------------------
-- JumpTarget
-- | Target for jump and branch statements
data JumpTarget blocks ctx where
JumpTarget :: !(BlockID blocks args) -- BlockID to jump to
-> !(CtxRepr args) -- expected argument types
-> !(Assignment (Reg ctx) args) -- jump target actual arguments
-> JumpTarget blocks ctx
instance Pretty (JumpTarget blocks ctx) where
pretty (JumpTarget tgt _ a) = pretty tgt <> parens (commas (ppAssignment a))
jumpTargetID :: JumpTarget blocks ctx -> Some (BlockID blocks)
jumpTargetID (JumpTarget tgt _ _) = Some tgt
extendJumpTarget :: Diff blocks' blocks -> JumpTarget blocks' ctx -> JumpTarget blocks ctx
extendJumpTarget diff (JumpTarget b tps a) = JumpTarget (extendBlockID' diff b) tps a
instance ApplyEmbedding (JumpTarget blocks) where
applyEmbedding ctxe (JumpTarget dest tys args) =
JumpTarget dest tys (fmapFC (applyEmbedding' ctxe) args)
instance ExtendContext (JumpTarget blocks) where
extendContext diff (JumpTarget dest tys args) =
JumpTarget dest tys (fmapFC (extendContext' diff) args)
------------------------------------------------------------------------
-- SwitchTarget
-- | Target for a switch statement.
data SwitchTarget blocks ctx tp where
SwitchTarget :: !(BlockID blocks (args ::> tp)) -- BlockID to jump to
-> !(CtxRepr args) -- expected argument types
-> !(Assignment (Reg ctx) args) -- switch target actual arguments
-> SwitchTarget blocks ctx tp
switchTargetID :: SwitchTarget blocks ctx tp -> Some (BlockID blocks)
switchTargetID (SwitchTarget tgt _ _) = Some tgt
ppCase :: String -> SwitchTarget blocks ctx tp -> Doc ann
ppCase nm (SwitchTarget tgt _ a) =
pretty nm <+> pretty "->" <+> pretty tgt <> parens (commas (ppAssignment a))
extendSwitchTarget :: Diff blocks' blocks
-> SwitchTarget blocks' ctx tp
-> SwitchTarget blocks ctx tp
extendSwitchTarget diff (SwitchTarget b tps a) =
SwitchTarget (extendBlockID' diff b) tps a
instance ApplyEmbedding' (SwitchTarget blocks) where
applyEmbedding' ctxe (SwitchTarget dest tys args) =
SwitchTarget dest tys (fmapFC (applyEmbedding' ctxe) args)
instance ExtendContext' (SwitchTarget blocks) where
extendContext' diff (SwitchTarget dest tys args) =
SwitchTarget dest tys (fmapFC (extendContext' diff) args)
------------------------------------------------------------------------
-- Stmt
-- | A sequential statement that does not affect the
-- program location within the current block or leave the current
-- block.
data Stmt ext (ctx :: Ctx CrucibleType) (ctx' :: Ctx CrucibleType) where
-- Assign the value of a register
SetReg :: !(TypeRepr tp)
-> !(Expr ext ctx tp)
-> Stmt ext ctx (ctx ::> tp)
-- Assign a register via an extension statement
ExtendAssign :: !(StmtExtension ext (Reg ctx) tp)
-> Stmt ext ctx (ctx ::> tp)
-- Statement used for evaluating function calls
CallHandle :: !(TypeRepr ret) -- The type of the return value(s)
-> !(Reg ctx (FunctionHandleType args ret)) -- The function handle to call
-> !(CtxRepr args) -- The expected types of the arguments
-> !(Assignment (Reg ctx) args) -- The actual arguments to the call
-> Stmt ext ctx (ctx ::> ret)
-- Print a message out to the console
Print :: !(Reg ctx (StringType Unicode)) -> Stmt ext ctx ctx
-- Read a global variable.
ReadGlobal :: !(GlobalVar tp)
-> Stmt ext ctx (ctx ::> tp)
-- Write to a global variable.
WriteGlobal :: !(GlobalVar tp)
-> !(Reg ctx tp)
-> Stmt ext ctx ctx
-- Create a fresh constant
FreshConstant :: !(BaseTypeRepr bt)
-> !(Maybe SolverSymbol)
-> Stmt ext ctx (ctx ::> BaseToType bt)
-- Create a fresh floating-point constant
FreshFloat :: !(FloatInfoRepr fi)
-> !(Maybe SolverSymbol)
-> Stmt ext ctx (ctx ::> FloatType fi)
-- Create a fresh natural number constant
FreshNat :: !(Maybe SolverSymbol)
-> Stmt ext ctx (ctx ::> NatType)
-- Allocate a new reference cell
NewRefCell :: !(TypeRepr tp)
-> !(Reg ctx tp)
-> Stmt ext ctx (ctx ::> ReferenceType tp)
-- Allocate a new, unassigned reference cell
NewEmptyRefCell :: !(TypeRepr tp)
-> Stmt ext ctx (ctx ::> ReferenceType tp)
-- Read the current value of a reference cell
ReadRefCell :: !(Reg ctx (ReferenceType tp))
-> Stmt ext ctx (ctx ::> tp)
-- Write the current value of a reference cell
WriteRefCell :: !(Reg ctx (ReferenceType tp))
-> !(Reg ctx tp)
-> Stmt ext ctx ctx
-- Deallocate the storage associated with a reference cell
DropRefCell :: !(Reg ctx (ReferenceType tp))
-> Stmt ext ctx ctx
-- Assert a boolean condition. If the condition fails, print the given string.
Assert :: !(Reg ctx BoolType) -> !(Reg ctx (StringType Unicode)) -> Stmt ext ctx ctx
-- Assume a boolean condition, remembering the given string as the 'reason' for this assumption.
Assume :: !(Reg ctx BoolType) -> !(Reg ctx (StringType Unicode)) -> Stmt ext ctx ctx
------------------------------------------------------------------------
-- TermStmt
data TermStmt blocks (ret :: CrucibleType) (ctx :: Ctx CrucibleType) where
-- Jump to the given jump target
Jump :: !(JumpTarget blocks ctx)
-> TermStmt blocks ret ctx
-- Branch on condition. If true, jump to the first jump target; otherwise
-- jump to the second jump target.
Br :: !(Reg ctx BoolType)
-> !(JumpTarget blocks ctx)
-> !(JumpTarget blocks ctx)
-> TermStmt blocks ret ctx
-- Switch on whether this is a maybe value. Jump to the switch target if
-- the maybe value is a "Some". Otherwise (if "Nothing"), jump to the jump target.
MaybeBranch :: !(TypeRepr tp)
-> !(Reg ctx (MaybeType tp))
-> !(SwitchTarget blocks ctx tp)
-> !(JumpTarget blocks ctx)
-> TermStmt blocks rtp ctx
-- Switch on a variant value. Examine the tag of the variant
-- and jump to the appropriate switch target.
VariantElim :: !(CtxRepr varctx)
-> !(Reg ctx (VariantType varctx))
-> !(Assignment (SwitchTarget blocks ctx) varctx)
-> TermStmt blocks ret ctx
-- Return from function, providing the return value(s).
Return :: !(Reg ctx ret)
-> TermStmt blocks ret ctx
-- End block with a tail call.
TailCall :: !(Reg ctx (FunctionHandleType args ret))
-> !(CtxRepr args)
-> !(Assignment (Reg ctx) args)
-> TermStmt blocks ret ctx
-- Block ends with an error.
ErrorStmt :: !(Reg ctx (StringType Unicode)) -> TermStmt blocks ret ctx
#ifndef UNSAFE_OPS
extendTermStmt :: Diff blocks' blocks -> TermStmt blocks' ret ctx -> TermStmt blocks ret ctx
extendTermStmt diff (Jump tgt) = Jump (extendJumpTarget diff tgt)
extendTermStmt diff (Br c x y) = Br c (extendJumpTarget diff x) (extendJumpTarget diff y)
extendTermStmt diff (MaybeBranch tp c x y) =
MaybeBranch tp c (extendSwitchTarget diff x) (extendJumpTarget diff y)
extendTermStmt diff (VariantElim ctx e asgn) =
VariantElim ctx e (fmapFC (extendSwitchTarget diff) asgn)
extendTermStmt _diff (Return e) = Return e
extendTermStmt _diff (TailCall h tps args) = TailCall h tps args
extendTermStmt _diff (ErrorStmt e) = ErrorStmt e
#endif
-- | Return the set of possible next blocks from a TermStmt
termStmtNextBlocks :: TermStmt b ret ctx -> Maybe [Some (BlockID b)]
termStmtNextBlocks s0 =
case s0 of
Jump tgt -> Just [ jumpTargetID tgt ]
Br _ x y -> Just [ jumpTargetID x, jumpTargetID y ]
MaybeBranch _ _ x y -> Just [ switchTargetID x, jumpTargetID y ]
VariantElim _ _ a -> Just (toListFC switchTargetID a)
Return _ -> Nothing
TailCall _ _ _ -> Nothing
ErrorStmt _ -> Just []
instance Pretty (TermStmt blocks ret ctx) where
pretty s =
case s of
Jump b -> pretty "jump" <+> pretty b
Br e x y -> pretty "br" <+> pretty e <+> pretty x <+> pretty y
MaybeBranch _ e j n ->
vcat
[ pretty "maybeBranch" <+> pretty e <+> lbrace
, indent 2 $
vcat [ ppCase "Just" j
, pretty "Nothing ->" <+> pretty n
, rbrace
]
]
VariantElim _ e asgn ->
let branches =
[ f (show i) <> semi
| i <- [(0::Int) .. ]
| f <- toListFC (\tgt nm -> ppCase nm tgt) asgn
] in
vcat
[ pretty "vswitch" <+> pretty e <+> lbrace
, indent 2 (vcat branches)
, rbrace
]
Return e ->
pretty "return"
<+> pretty e
TailCall h _ args ->
pretty "tailCall"
<+> pretty h
<+> parens (commas (ppAssignment args))
ErrorStmt msg ->
pretty "error" <+> pretty msg
applyEmbeddingStmt :: forall ext ctx ctx' sctx.
TraverseExt ext =>
CtxEmbedding ctx ctx' ->
Stmt ext ctx sctx ->
Pair (Stmt ext ctx') (CtxEmbedding sctx)
applyEmbeddingStmt ctxe stmt =
case stmt of
SetReg tp e -> Pair (SetReg tp (applyEmbedding' ctxe e))
(extendEmbeddingBoth ctxe)
ExtendAssign estmt ->
Pair (ExtendAssign (fmapFC (Ctx.applyEmbedding' ctxe) estmt))
(Ctx.extendEmbeddingBoth ctxe)
CallHandle ret hdl tys args ->
Pair (CallHandle ret (reg hdl) tys (fmapFC reg args))
(extendEmbeddingBoth ctxe)
Print str -> Pair (Print (reg str)) ctxe
ReadGlobal var -> Pair (ReadGlobal var)
(extendEmbeddingBoth ctxe)
WriteGlobal var r -> Pair (WriteGlobal var (reg r)) ctxe
FreshConstant bt nm -> Pair (FreshConstant bt nm)
(Ctx.extendEmbeddingBoth ctxe)
FreshFloat fi nm -> Pair (FreshFloat fi nm)
(Ctx.extendEmbeddingBoth ctxe)
FreshNat nm -> Pair (FreshNat nm) (Ctx.extendEmbeddingBoth ctxe)
NewRefCell tp r -> Pair (NewRefCell tp (reg r))
(Ctx.extendEmbeddingBoth ctxe)
NewEmptyRefCell tp -> Pair (NewEmptyRefCell tp)
(Ctx.extendEmbeddingBoth ctxe)
ReadRefCell r -> Pair (ReadRefCell (reg r))
(Ctx.extendEmbeddingBoth ctxe)
WriteRefCell r r' -> Pair (WriteRefCell (reg r) (reg r')) ctxe
DropRefCell r -> Pair (DropRefCell (reg r)) ctxe
Assert b str -> Pair (Assert (reg b) (reg str)) ctxe
Assume b str -> Pair (Assume (reg b) (reg str)) ctxe
where
reg :: forall tp. Reg ctx tp -> Reg ctx' tp
reg = applyEmbedding' ctxe
instance ApplyEmbedding (TermStmt blocks ret) where
applyEmbedding :: forall ctx ctx'.
CtxEmbedding ctx ctx'
-> TermStmt blocks ret ctx
-> TermStmt blocks ret ctx'
applyEmbedding ctxe term =
case term of
Jump jt -> Jump (apC jt)
Br b jtl jtr -> Br (apC' b) (apC jtl) (apC jtr)
MaybeBranch tp b swt jt -> MaybeBranch tp (apC' b) (apC' swt) (apC jt)
VariantElim repr r targets -> VariantElim repr (apC' r) (fmapFC apC' targets)
Return r -> Return (apC' r)
TailCall hdl tys args -> TailCall (apC' hdl) tys (fmapFC apC' args)
ErrorStmt r -> ErrorStmt (apC' r)
where
apC' :: forall f v. ApplyEmbedding' f => f ctx v -> f ctx' v
apC' = applyEmbedding' ctxe
apC :: forall f. ApplyEmbedding f => f ctx -> f ctx'
apC = applyEmbedding ctxe
instance ExtendContext (TermStmt blocks ret) where
extendContext :: forall ctx ctx'.
Diff ctx ctx'
-> TermStmt blocks ret ctx
-> TermStmt blocks ret ctx'
extendContext diff term =
case term of
Jump jt -> Jump (extC jt)
Br b jtl jtr -> Br (extC' b) (extC jtl) (extC jtr)
MaybeBranch tp b swt jt -> MaybeBranch tp (extC' b) (extC' swt) (extC jt)
VariantElim repr r targets -> VariantElim repr (extC' r) (fmapFC extC' targets)
Return r -> Return (extC' r)
TailCall hdl tys args -> TailCall (extC' hdl) tys (fmapFC extC' args)
ErrorStmt r -> ErrorStmt (extC' r)
where
extC' :: forall f v. ExtendContext' f => f ctx v -> f ctx' v
extC' = extendContext' diff
extC :: forall f. ExtendContext f => f ctx -> f ctx'
extC = extendContext diff
------------------------------------------------------------------------
-- StmtSeq
-- | A sequence of straight-line program statements that end with
-- a terminating statement (return, jump, etc).
data StmtSeq ext blocks (ret :: CrucibleType) ctx where
ConsStmt :: !ProgramLoc
-> !(Stmt ext ctx ctx')
-> !(StmtSeq ext blocks ret ctx')
-> StmtSeq ext blocks ret ctx
TermStmt :: !ProgramLoc
-> !(TermStmt blocks ret ctx)
-> (StmtSeq ext blocks ret ctx)
-- | Return the location of a statement.
firstStmtLoc :: StmtSeq ext b r ctx -> ProgramLoc
firstStmtLoc (ConsStmt pl _ _) = pl
firstStmtLoc (TermStmt pl _) = pl
-- | A lens-like operation that gives one access to program location and term statement,
-- and allows the terminal statement to be replaced with an arbitrary sequence of
-- statements.
stmtSeqTermStmt :: Functor f
=> (forall ctx
. (ProgramLoc, TermStmt b ret ctx)
-> f (StmtSeq ext b' ret ctx))
-> StmtSeq ext b ret args
-> f (StmtSeq ext b' ret args)
stmtSeqTermStmt f (ConsStmt l s t) = ConsStmt l s <$> stmtSeqTermStmt f t
stmtSeqTermStmt f (TermStmt p t) = f (p, t)
ppReg :: Size ctx -> Doc ann
ppReg h = pretty "$" <> pretty (sizeInt h)
nextStmtHeight :: Size ctx -> Stmt ext ctx ctx' -> Size ctx'
nextStmtHeight h s =
case s of
SetReg{} -> incSize h
ExtendAssign{} -> incSize h
CallHandle{} -> incSize h
Print{} -> h
ReadGlobal{} -> incSize h
WriteGlobal{} -> h
FreshConstant{} -> Ctx.incSize h
FreshFloat{} -> Ctx.incSize h
FreshNat{} -> Ctx.incSize h
NewRefCell{} -> Ctx.incSize h
NewEmptyRefCell{} ->Ctx.incSize h
ReadRefCell{} -> Ctx.incSize h
WriteRefCell{} -> h
DropRefCell{} -> h
Assert{} -> h
Assume{} -> h
ppStmt :: PrettyExt ext => Size ctx -> Stmt ext ctx ctx' -> Doc ann
ppStmt r s =
case s of
SetReg _ e -> ppReg r <+> pretty "=" <+> pretty e
ExtendAssign s' -> ppReg r <+> pretty "=" <+> ppApp pretty s'
CallHandle _ h _ args ->
ppReg r <+> pretty "= call"
<+> pretty h <> parens (commas (ppAssignment args))
<> pretty ";"
Print msg -> ppFn "print" [ pretty msg ]
ReadGlobal v -> pretty "read" <+> ppReg r <+> pretty v
WriteGlobal v e -> pretty "write" <+> pretty v <+> pretty e
-- TODO: replace viaShow once we have instance Pretty SolverSymbol
FreshConstant bt nm -> ppReg r <+> pretty "=" <+> pretty "fresh" <+> pretty bt <+> maybe mempty viaShow nm
FreshFloat fi nm -> ppReg r <+> pretty "=" <+> pretty "fresh-float" <+> pretty fi <+> maybe mempty viaShow nm
FreshNat nm -> ppReg r <+> pretty "=" <+> pretty "fresh-nat" <+> maybe mempty viaShow nm
NewRefCell _ e -> ppReg r <+> pretty "=" <+> ppFn "newref" [ pretty e ]
NewEmptyRefCell tp -> ppReg r <+> pretty "=" <+> ppFn "emptyref" [ pretty tp ]
ReadRefCell e -> ppReg r <+> pretty "= !" <> pretty e
WriteRefCell r1 r2 -> pretty r1 <+> pretty ":=" <+> pretty r2
DropRefCell r1 -> pretty "drop" <+> pretty r1
Assert c e -> ppFn "assert" [ pretty c, pretty e ]
Assume c e -> ppFn "assume" [ pretty c, pretty e ]
prefixLineNum :: Bool -> ProgramLoc -> Doc ann -> Doc ann
prefixLineNum True pl d = vcat [pretty "%" <+> ppNoFileName (plSourceLoc pl), d]
prefixLineNum False _ d = d
ppStmtSeq :: PrettyExt ext => Bool -> Size ctx -> StmtSeq ext blocks ret ctx -> Doc ann
ppStmtSeq ppLineNum h (ConsStmt pl s r) =
vcat
[ prefixLineNum ppLineNum pl (ppStmt h s)
, ppStmtSeq ppLineNum (nextStmtHeight h s) r
]
ppStmtSeq ppLineNum _ (TermStmt pl s) =
prefixLineNum ppLineNum pl (pretty s)
#ifndef UNSAFE_OPS
extendStmtSeq :: Diff blocks' blocks -> StmtSeq ext blocks' ret ctx -> StmtSeq ext blocks ret ctx
extendStmtSeq diff (ConsStmt p s l) = ConsStmt p s (extendStmtSeq diff l)
extendStmtSeq diff (TermStmt p s) = TermStmt p (extendTermStmt diff s)
#endif
instance TraverseExt ext => ApplyEmbedding (StmtSeq ext blocks ret) where
applyEmbedding ctxe (ConsStmt loc stmt rest) =
case applyEmbeddingStmt ctxe stmt of
Pair stmt' ctxe' -> ConsStmt loc stmt' (applyEmbedding ctxe' rest)
applyEmbedding ctxe (TermStmt loc term) =
TermStmt loc (applyEmbedding ctxe term)
------------------------------------------------------------------------
-- CFGPostdom
-- | Postdominator information about a CFG. The assignment maps each block
-- to the postdominators of the given block. The postdominators are ordered
-- with nearest postdominator first.
type CFGPostdom blocks = Assignment (Const [Some (BlockID blocks)]) blocks
emptyCFGPostdomInfo :: Size blocks -> CFGPostdom blocks
emptyCFGPostdomInfo sz = Ctx.replicate sz (Const [])
------------------------------------------------------------------------
-- Block
-- | A basic block within a function.
data Block ext (blocks :: Ctx (Ctx CrucibleType)) (ret :: CrucibleType) ctx
= Block { blockID :: !(BlockID blocks ctx)
-- ^ The unique identifier of this block
, blockInputs :: !(CtxRepr ctx)
-- ^ The expected types of the formal arguments to this block
, _blockStmts :: !(StmtSeq ext blocks ret ctx)
-- ^ The sequence of statements in this block
}
blockStmts :: Simple Lens (Block ext b r c) (StmtSeq ext b r c)
blockStmts = lens _blockStmts (\b s -> b { _blockStmts = s })
-- | Return location of start of block.
blockLoc :: Block ext blocks ret ctx -> ProgramLoc
blockLoc b = firstStmtLoc (b^.blockStmts)
-- | Get the terminal statement of a basic block. This is implemented
-- in a CPS style due to the block context.
withBlockTermStmt :: Block ext blocks ret args
-> (forall ctx . ProgramLoc -> TermStmt blocks ret ctx -> r)
-> r
withBlockTermStmt b f = getConst (stmtSeqTermStmt (Const . uncurry f) (b^.blockStmts))
nextBlocks :: Block ext b r a -> [Some (BlockID b)]
nextBlocks b =
withBlockTermStmt b (\_ s -> fromMaybe [] (termStmtNextBlocks s))
blockInputCount :: Block ext blocks ret ctx -> Size ctx
blockInputCount b = size (blockInputs b)
ppBlock :: PrettyExt ext
=> Bool
-- ^ Print line numbers.
-> Bool
-- ^ Print block args. Note that you can infer the number
-- of block args from the first SSA temp register assigned
-- to in the block: if the block has @n@ args, then the
-- first register it assigns to will be @$n@.
-> Maybe (CFGPostdom blocks)
-- ^ Optionally print postdom info.
-> Block ext blocks ret ctx
-- ^ Block to print.
-> Doc ann
ppBlock ppLineNumbers ppBlockArgs mPda b = do
let stmts = ppStmtSeq ppLineNumbers (blockInputCount b) (b^.blockStmts)
let mPostdom = flip fmap mPda $ \ pda ->
let Const pd = pda ! blockIDIndex (blockID b)
in if Prelude.null pd
then pretty "% no postdom"
else pretty "% postdom" <+> hsep (viewSome pretty <$> pd)
let numArgs = lengthFC (blockInputs b)
let argList = [ pretty '$' <> pretty n | n <- [0 .. numArgs-1] ]
let args = encloseSep lparen rparen comma argList
let block = pretty (blockID b) <>
if ppBlockArgs then args else mempty
let body = case mPostdom of
Nothing -> stmts
Just postdom -> vcat [stmts, postdom]
vcat [block, indent 2 body]
instance PrettyExt ext => Show (Block ext blocks ret args) where
show blk = show $ ppBlock False False Nothing blk
instance PrettyExt ext => ShowF (Block ext blocks ret)
#ifndef UNSAFE_OPS
extendBlock :: Block ext blocks ret ctx -> Block ext (blocks ::> new) ret ctx
extendBlock b =
Block { blockID = extendBlockID (blockID b)
, blockInputs = blockInputs b
, _blockStmts = extendStmtSeq knownDiff (b^.blockStmts)
}
#endif
------------------------------------------------------------------------
-- BlockMap
-- | A mapping from block indices to CFG blocks
type BlockMap ext blocks ret = Assignment (Block ext blocks ret) blocks
getBlock :: BlockID blocks args
-> BlockMap ext blocks ret
-> Block ext blocks ret args
getBlock (BlockID i) m = m Ctx.! i
extendBlockMap :: Assignment (Block ext blocks ret) b
-> Assignment (Block ext (blocks ::> args) ret) b
#ifdef UNSAFE_OPS
extendBlockMap = unsafeCoerce
#else
extendBlockMap = fmapFC extendBlock
#endif
------------------------------------------------------------------------
-- CFG
-- | A CFG consists of:
--
-- * a function handle, uniquely identifying the function this CFG
-- implements;
--
-- * a block map, representing the main CFG data structure;
--
-- * and the identifier of the function entry point.
--
-- The @blocks@ type parameter maps each block identifier to the
-- formal arguments it expects. The @init@ type parameter identifies
-- the formal arguments of the function represented by this control-flow graph,
-- which correspond to the formal arguments of the CFG entry point.
-- The @ret@ type parameter indicates the return type of the function.
data CFG (ext :: Type)
(blocks :: Ctx (Ctx CrucibleType))
(init :: Ctx CrucibleType)
(ret :: CrucibleType)
= CFG { cfgHandle :: FnHandle init ret
, cfgBlockMap :: !(BlockMap ext blocks ret)
, cfgEntryBlockID :: !(BlockID blocks init)
, cfgBreakpoints :: !(Bimap BreakpointName (Some (BlockID blocks)))
}
cfgArgTypes :: CFG ext blocks init ret -> CtxRepr init
cfgArgTypes g = handleArgTypes (cfgHandle g)
cfgReturnType :: CFG ext blocks init ret -> TypeRepr ret
cfgReturnType g = handleReturnType (cfgHandle g)
-- | Class for types that embed a CFG of some sort.
class HasSomeCFG f ext init ret | f -> ext, f -> init, f -> ret where
getCFG :: f b -> SomeCFG ext init ret
instance PrettyExt ext => Show (CFG ext blocks init ret) where
show g = show (ppCFG True g)
-- | Pretty print a CFG.
ppCFG :: PrettyExt ext
=> Bool -- ^ Flag indicates if we should print line numbers
-> CFG ext blocks init ret
-> Doc ann
ppCFG lineNumbers g = ppCFG' lineNumbers (emptyCFGPostdomInfo sz) g
where sz = size (cfgBlockMap g)
-- | Pretty print CFG with postdom information.
ppCFG' :: PrettyExt ext
=> Bool -- ^ Flag indicates if we should print line numbers
-> CFGPostdom blocks
-> CFG ext blocks init ret
-> Doc ann
ppCFG' lineNumbers pdInfo g = vcat (toListFC (ppBlock lineNumbers blockArgs (Just pdInfo)) (cfgBlockMap g))
where blockArgs = False
-- | Control flow graph with some blocks. This data type closes
-- existentially over the @blocks@ type parameter.
data SomeCFG ext (init :: Ctx CrucibleType) (ret :: CrucibleType) where
SomeCFG :: CFG ext blocks init ret -> SomeCFG ext init ret
instance PrettyExt ext => Show (SomeCFG ext i r)
where show cfg = case cfg of SomeCFG c -> show c
-- | Control flow graph. This data type closes existentially
-- over all the type parameters except @ext@.
data AnyCFG ext where
AnyCFG :: CFG ext blocks init ret
-> AnyCFG ext
instance PrettyExt ext => Show (AnyCFG ext) where
show cfg = case cfg of AnyCFG c -> show c