SSTG-0.1.1.3: src/SSTG/Core/Language/Support.hs
-- | Symbolic STG Execution Support Architecture
module SSTG.Core.Language.Support
( SymbolicT(..)
, fmap
, pure
, (<*>)
, return
, (>>=)
, State(..)
, Symbol(..)
, Status(..)
, Stack
, Frame(..)
, MemAddr
, Value(..)
, Locals
, Heap
, HeapObj(..)
, Globals
, Code(..)
, PathCons
, Constraint(..)
, nameOccStr
, nameUnique
, varName
, null_addr
, addrInt
, init_status
, incStatusSteps
, updateStatusId
, empty_stack
, popStack
, pushStack
, stackToList
, empty_locals
, lookupLocals
, insertLocals
, insertLocalsList
, localsToList
, empty_heap
, lookupHeap
, allocHeap
, allocHeapList
, insertHeap
, insertHeapList
, heapToList
, empty_globals
, lookupGlobals
, insertGlobals
, insertGlobalsList
, globalsToList
, empty_pathcons
, insertPathCons
, insertPathConsList
, pathconsToList
, lookupValue
, vlookupHeap
, memAddrType
) where
import SSTG.Core.Language.Syntax
import SSTG.Core.Language.Typing
import qualified Data.Map as M
-- | Symbolic Transformation represents transformations applied to some
-- State`(s). This is useful in allowing us to transfer from different actions
-- within the engine.
newtype SymbolicT s a = SymbolicT { run :: s -> (s, a) }
-- | Functor instance of Symbolic Transformation.
instance Functor (SymbolicT s) where
fmap f st = SymbolicT (\s0 -> let (s1, a1) = (run st) s0 in (s1, f a1))
-- | Applicative instance of Symbolic Transformation.
instance Applicative (SymbolicT s) where
pure a = SymbolicT (\s -> (s, a))
sf <*> st = SymbolicT (\s0 -> let (s1, a1) = (run st) s0
(s2, f2) = (run sf) s1 in (s2, f2 a1))
-- | Monad instance of Symbolic Transformation.
instance Monad (SymbolicT s) where
return a = pure a
st >>= fs = SymbolicT (\s0 -> let (s1, a1) = (run st) s0
(s2, a2) = (run (fs a1)) s1 in (s2, a2))
-- | `State` contains the information necessary to perform symbolic execution.
-- Eval/Apply graph reduction semantics are used.
data State = State { state_status :: !Status
, state_stack :: !Stack
, state_heap :: !Heap
, state_globals :: !Globals
, state_code :: !Code
, state_names :: ![Name]
, state_paths :: !PathCons
} deriving (Show, Eq, Read)
-- | Symbolic variables. The @Maybe (Expr, Locals)@ can be used to trace the
-- source from which the symbolic variable was generated. For instance, this is
-- useful during symbolic function application.
data Symbol = Symbol Var (Maybe (Expr, Locals)) deriving (Show, Eq, Read)
-- | State status.
data Status = Status { status_id :: !Int
, status_parent :: !Int
, status_steps :: !Int
} deriving (Show, Eq, Read)
-- | Execution stack used in graph reduction semnatics.
newtype Stack = Stack [Frame] deriving (Show, Eq, Read)
-- | Frames of a stack.
data Frame = CaseFrame Var [Alt] Locals
| ApplyFrame [Atom] Locals
| UpdateFrame MemAddr
deriving (Show, Eq, Read)
-- | Memory address for things on the `Heap`.
newtype MemAddr = MemAddr Int deriving (Show, Eq, Read, Ord)
-- | A `Value` is something that we aim to reduce our current expression down
-- into. `MemAddr` is a pointer to an object on the heap, such as `FunObj` or
-- `ConObj`, which are "returned" from expression evaluation in this form.
data Value = LitVal Lit
| MemVal MemAddr
deriving (Show, Eq, Read)
-- | Locals binds a `Var`'s `Name` to its some `Value`.
newtype Locals = Locals (M.Map Name Value) deriving (Show, Eq, Read)
-- | Heaps map `MemAddr` to `HeapObj`, while keeping track of the last address
-- that was allocated. This allows us to consistently allocate fresh addresses
-- on the `Heap`.
data Heap = Heap (M.Map MemAddr HeapObj) MemAddr deriving (Show, Eq, Read)
-- | Heap objects.
data HeapObj = LitObj Lit
| SymObj Symbol
| ConObj DataCon [Value]
| FunObj [Var] Expr Locals
| AddrObj MemAddr
| Blackhole
deriving (Show, Eq, Read)
-- | Globals are statically loaded at the time when a `State` is loaded.
-- However, because uninterpreted / out-of-scope variables are made symbolic
-- at runtime, it can be modified during execution.
newtype Globals = Globals (M.Map Name Value) deriving (Show, Eq, Read)
-- | Evaluation of the current expression. We are either evaluating, or ready
-- to return with some `Value`.
data Code = Evaluate Expr Locals
| Return Value
deriving (Show, Eq, Read)
-- | Path constraints are the conjunctive normal form of `Constraint`s.
newtype PathCons = PathCons [Constraint] deriving (Show, Eq, Read)
-- | Constraints denote logical paths taken in program execution thus far.
data Constraint = Constraint (AltCon, [Var]) Expr Locals Bool
deriving (Show, Eq, Read)
-- Simple functions that require only the immediate data structure.
-- | A `Name`'s occurrence string.
nameOccStr :: Name -> String
nameOccStr (Name occ _ _ _) = occ
-- | `Name` imique `Int`.
nameUnique :: Name -> Int
nameUnique (Name _ _ _ unq) = unq
-- | Variable name.
varName :: Var -> Name
varName (Var name _) = name
-- | Null `MemAddr`.
null_addr :: MemAddr
null_addr = MemAddr 0
-- | `MemAddr`'s `Int` value.
addrInt :: MemAddr -> Int
addrInt (MemAddr int) = int
-- | Initial `Status`.
init_status :: Status
init_status = Status { status_id = 1
, status_parent = 0
, status_steps = 0 }
-- | Increment `Status` steps.
incStatusSteps :: Status -> Status
incStatusSteps status = status { status_steps = (status_steps status) + 1 }
-- | Update the `Status` id.
updateStatusId :: Int -> Status -> Status
updateStatusId new_id status = status { status_id = new_id
, status_parent = status_id status }
-- | Empty `Stack.
empty_stack :: Stack
empty_stack = Stack []
-- | `Stack` pop.
popStack :: Stack -> Maybe (Frame, Stack)
popStack (Stack []) = Nothing
popStack (Stack (frame:frames)) = Just (frame, Stack frames)
-- | `Stack` push.
pushStack :: Frame -> Stack -> Stack
pushStack frame (Stack frames) = Stack (frame : frames)
-- | `Stack` as list of `Frame`s.
stackToList :: Stack -> [Frame]
stackToList (Stack frames) = frames
-- | Empty `Locals`.
empty_locals :: Locals
empty_locals = Locals M.empty
-- | `Locals` lookup.
lookupLocals :: Var -> Locals -> Maybe Value
lookupLocals var (Locals lmap) = M.lookup (varName var) lmap
-- | `Locals` insertion.
insertLocals :: (Var, Value) -> Locals -> Locals
insertLocals (k, v) (Locals lmap) = Locals (M.insert (varName k) v lmap)
-- | List insertion into `Locals`.
insertLocalsList :: [(Var, Value)] -> Locals -> Locals
insertLocalsList kvs locals = foldr insertLocals locals kvs
-- | `Locals` to key value pairs.
localsToList :: Locals -> [(Name, Value)]
localsToList (Locals lmap) = M.toList lmap
-- | Empty `Heap`.
empty_heap :: Heap
empty_heap = Heap M.empty null_addr
-- | `Heap` lookup.
lookupHeap :: MemAddr -> Heap -> Maybe HeapObj
lookupHeap addr (Heap hmap prev) = case M.lookup addr hmap of
Just (AddrObj redir) -> lookupHeap redir (Heap hmap prev)
mb_hobj -> mb_hobj
-- | `Heap` allocation. Updates the last `MemAddr` kept in the `Heap`.
allocHeap :: HeapObj -> Heap -> (Heap, MemAddr)
allocHeap hobj (Heap hmap prev) = (Heap hmap' addr, addr)
where
addr = MemAddr ((addrInt prev) + 1)
hmap' = M.insert addr hobj hmap
-- | Allocate a list of `HeapObj` in a `Heap`, returning in the same order the
-- `MemAddr` at which they have been allocated at.
allocHeapList :: [HeapObj] -> Heap -> (Heap, [MemAddr])
allocHeapList [] heap = (heap, [])
allocHeapList (hobj:hobjs) heap = (heapf, addr : as)
where
(heap', addr) = allocHeap hobj heap
(heapf, as) = allocHeapList hobjs heap'
-- | `Heap` direct insertion at a specific `MemAddr`.
insertHeap :: (MemAddr, HeapObj) -> Heap -> Heap
insertHeap (k, v) (Heap hmap prev) = Heap (M.insert k v hmap) prev
-- | Insert a list of `HeapObj` at specified `MemAddr` locations.
insertHeapList :: [(MemAddr, HeapObj)] -> Heap -> Heap
insertHeapList kvs heap = foldr insertHeap heap kvs
-- | `Heap` to key value pairs.
heapToList :: Heap -> [(MemAddr, HeapObj)]
heapToList (Heap hmap _) = M.toList hmap
-- | Empty `Globals`.
empty_globals :: Globals
empty_globals = Globals M.empty
-- | `Globals` lookup.
lookupGlobals :: Var -> Globals -> Maybe Value
lookupGlobals var (Globals gmap) = M.lookup (varName var) gmap
-- | `Globals` insertion.
insertGlobals :: (Var, Value) -> Globals -> Globals
insertGlobals (k, v) (Globals gmap) = Globals (M.insert (varName k) v gmap)
-- | Insert a list of `Var` and `Value` pairs into `Globals`. This would
-- typically occur for new symbolic variables created from uninterpreted /
-- out-of-scope variables during runtime.
insertGlobalsList :: [(Var, Value)] -> Globals -> Globals
insertGlobalsList kvs globals = foldr insertGlobals globals kvs
-- | `Globals` to key value pairs.
globalsToList :: Globals -> [(Name, Value)]
globalsToList (Globals gmap) = M.toList gmap
-- | Empty `PathCons`.
empty_pathcons :: PathCons
empty_pathcons = PathCons []
-- | `PathCons` insertion.
insertPathCons :: Constraint -> PathCons -> PathCons
insertPathCons cons (PathCons conss) = PathCons (cons : conss)
-- | Insert a list of `Constraint`s into a `PathCons`.
insertPathConsList :: [Constraint] -> PathCons -> PathCons
insertPathConsList conss pathcons = foldr insertPathCons pathcons conss
-- | `PathCons` to list of `Constraint`s.
pathconsToList :: PathCons -> [Constraint]
pathconsToList (PathCons conss) = conss
-- Complex functions that involve multiple data structures.
-- | `Value` lookup from the `Locals` first, then `Globals`.
lookupValue :: Var -> Locals -> Globals -> Maybe Value
lookupValue var locals globals = case lookupLocals var locals of
Nothing -> lookupGlobals var globals
Just val -> Just val
-- | `Heap` lookup. Returns the corresponding `MemAddr` and `HeapObj` if found.
vlookupHeap :: Var -> Locals -> Globals -> Heap -> Maybe (MemAddr, HeapObj)
vlookupHeap var locals globals heap = do
val <- lookupValue var locals globals
case val of
LitVal _ -> Nothing
MemVal addr -> lookupHeap addr heap >>= \hobj -> Just (addr, hobj)
-- | Type of `HeapObj` held at `MemAddr`, if found.
memAddrType :: MemAddr -> Heap -> Maybe Type
memAddrType addr heap = do
hobj <- lookupHeap addr heap
case hobj of
AddrObj redir -> memAddrType redir heap
LitObj lit -> Just (litType lit)
SymObj (Symbol s _) -> Just (varType s)
ConObj dcon _ -> Just (dataconType dcon)
FunObj ps e _ -> Just (foldr FunTy (exprType e) (map varType ps))
Blackhole -> Just Bottom