packages feed

asil-1.2: src/Instr.hs

-- | Typed Embedded Instrumentation Language for Object-Oriented programming languages.
-- Haskell provides abstraction facilities and meta-programming.
--
-- The language specifies conditional instrumentation to be performed at
-- program points. See 'LanguageAst.ag' for a description of the underlying
-- core language.
--
-- Note the following phase destinction: execution of the Haskell program produces
-- a description for how to perform the instrumentation. This is a preprocessing step.
-- Interpretation of this description is what we call "compile time". Execution of the
-- instrumented program is what we call "runtime".
--
-- The typed instrumentation language is a monad. The computation
-- itself consists of two essential concepts: matches against program
-- points and insertion of function calls. Program points include method calls and
-- entry/exists of a sequential block of instructions. Each instrumentation can succeed
-- or fail. When the instrumentation succeeded, the value asociated to the monad captures
-- the context information, e.g. binding names to parameters of a function call.
-- Instrumentations can be composed sequentially (m >>= f): the right-hand side |f| is
-- parametrized with the context of the left-hand side |m|, and executed, iff |m| succeeds.
-- The instrumentation can be partially evaluated: matches based on statically known
-- information can be resolved at instrumentation time. Most function calls are required to
-- be done at runtime due to side effect.
--
-- For example, suppose that we want to instrument each
-- entry of a block of instructions with a call to a provided
-- function |enterCycBlock(id)|, where |id| is a unique identification
-- of the block. Moreover, also suppose that we are only interested
-- in intrumenting blocks on a cyclic path in the control flow graph.
-- For that, we write the following monadic expression (explained below):
--
-- > do info <- matchEnterBlock
-- >    equals (isCycl info) (con True)
-- >    call fenterBlock (blockId info)
--
-- The context-information after matching against the block is bound to
-- the Haskell value |info|. This value contains embedded instrumentation
-- values |isCycl info| and |blockId info|. The first is a boolean value
-- that is |True| iff the block is on a cyclic CFG path. The second is an
-- integer identifying the block.
-- 
-- Both are embedded values: values of the type |Var Bool| and |Var Int| respectively.
-- Also, the values are statically known: a corresponding Haskell value
-- can be deduced when the program is instrumented. Some values, e.g. those
-- bound to the inputs of a method for a match against a method call, are
-- dynamic values. These have a symbolic value at instrumentation time.
--
-- The |equals| invocation tests if its two input embedded values are equal.
-- When both values are statically known, the test is resolved at instrumentation time,
-- otherwise it leaves a residual test that succeeds or fails at runtime.
-- The Haskell function |con| encodes a Haskell value as embedded constant,
-- which is known at instrumentation time. Hence, both inputs are known at
-- instrumentation time, and the check can be resolved statically. As a
-- consequence, only residual code is generated for the call to "enterBlock", for
-- only the intended program points. For all other program points, the
-- instrumentation fails already at instrumentation time.
--
-- A failing instrumentation is not an error: it just means that the
-- instrumentation is only performed up to the point of failure for a
-- program point. Care must be taken when side-effect is involved.

{-# LANGUAGE EmptyDataDecls, GADTs, KindSignatures, ScopedTypeVariables,
    MultiParamTypeClasses, FunctionalDependencies, TypeFamilies,
    FlexibleInstances, FlexibleContexts, UndecidableInstances, TypeSynonymInstances #-}
module Instr where

import Control.Applicative
import Control.Monad
import Control.Monad.State.Strict
import Data.Word
import qualified Language as L


--
-- Variables and Values
-- Certain Haskell values can (easily) be promoted as embedded constants. (Symbolic) values
-- are obtained via intrumentation instructions, and cannot be constructed directly.
-- Embedded constant values can be mapped back to their Haskell values.
--

-- | A variable is a symbolic value of a certain type.
--   It is associated with an integer value that can be used to identify the
--   symbolic value. There may be a statically known substitution for a
--   symbolic value.
data Var :: * -> * where
  Sym :: Int -> Var a

-- | A constant is a fixed value of a certain type.
data Con :: * -> * where
  ConInt     :: Int     -> Con Int
  ConUInt    :: Word32  -> Con Word32
  ConBool    :: Bool    -> Con Bool
  ConString  :: String  -> Con String
  ConArray   :: [Val a] -> Con [a]
  ConType    :: Type a  -> Con (Type a)
  ConNothing ::            Con (Maybe a)
  ConJust    :: Val a   -> Con (Maybe a)
  ConMethod  :: String  -> FunSpec a b -> Con (FunSpec a b)

-- | Values
data Val  :: * -> * where
  ValSym  :: Var a -> Val a
  ValCon  :: Con a -> Val a
  ValInd  :: Val [a] -> Val Word32 -> Val a
  ValProp :: IsSuper c a => Val a -> Key c b -> Val b
  ValDyn  :: IsSuper a Base => Val a -> Val String -> Val Any

-- | Returns the Haskell value associated to a constant embedded value.
--   Those parts of the value that are not constant, are mapped to bottom.
unCon :: Val a -> a
unCon (ValCon c)   = unCon' c
unCon (ValInd a i) = unCon a !! (fromIntegral $ unCon i)
unCon _ = error "unCon on a non-constant value"

-- | Returns the wrapped Haskell value.
unCon' :: Con a -> a
unCon' (ConInt i)      = i
unCon' (ConUInt i)     = i
unCon' (ConBool b)     = b
unCon' (ConString s)   = s
unCon' (ConArray xs)   = map unCon xs
unCon' (ConType t)     = t
unCon' (ConNothing)    = Nothing
unCon' (ConJust x)     = Just (error "unCon'::value")
unCon' (ConMethod x s) = s

-- | Statically known index in an object.
data Key :: * -> * -> * where
  Key :: String -> Key a b

-- | Embedding of constants.
-- Prefixing a Haskell value with |con|, turns the Haskell value
-- into an embedded constant.
class IsConstant a where
  con :: a -> Val a

instance IsConstant String where
  con = ValCon . ConString

instance IsConstant Bool where
  con = ValCon . ConBool

instance IsConstant Int where
  con = ValCon . ConInt

instance IsConstant Word32 where
  con = ValCon . ConUInt

instance IsConstant (Type a) where
  con = ValCon . ConType


-- | Specification of embedded types.
--   The idea is to have for every type occurring in a program (embedded types),
--   a Haskell value that describes this type, and a constructor-less corresponding
--   Haskell type for object/interface types.
--   The Haskell value can be turned into an embedded constant describing
--   this type.
--   The embedded types/values are transcribed to Haskell names: the types are
--   prefixed with a capital 'T', the values with a small 't'.

-- | Description of a type
data Type :: * -> * where
  TypeAny     :: Type Any
  TypeBool    :: Type Bool
  TypeInt     :: Type Int
  TypeUInt    :: Type Word32
  TypeDouble  :: Type Double
  TypeString  :: Type String
  TypeArray   :: Type a -> Type [a]   -- Allows Type-parametrized arrays to be specified
  TypeBase    :: Type Base
  TypeObject  :: String -> Type a
  TypeMethod  :: Type (FunSpec a b)

instance Show (Type a) where
  show t = case t of
    TypeAny      -> "T˚Any"
    TypeBool     -> "T˚Bool"
    TypeInt      -> "T˚Int"
    TypeUInt     -> "T˚UInt"
    TypeDouble   -> "T˚Double"
    TypeString   -> "T˚String"
    TypeArray t  -> "T˚Array˚" ++ show t
    TypeBase     -> "T˚Base"
    TypeObject s -> "T˚" ++ s
    TypeMethod   -> "T˚Method"

-- | The untyped object.
data Any :: *
tAny :: Type Any
tAny = TypeAny

-- | The base type of all types.
data Base :: *
tBase :: Type Base
tBase = TypeBase


-- | Specification of coercions.
--   With coercions, we explicitly encode the conversion of certain types ot other types.
--   In particular, we do not support automatic upcasting of types: this can be done
--   via |CoerceInstance|.
--   To cast (possibly fails) a value of some arbitrary type to another type, use a
--   coercion from and to |Any|.
data Coercion :: * -> * -> * where
  CoerceNone      :: Coercion a a     -- explicit absence of coercion
  CoerceAny       :: Coercion a Any   -- arbitrary coercion
  CoerceAny'      :: Coercion Any b
  CoerceString    :: Coercion a String
  CoerceDouble    :: Coercion a Double
  CoerceInt       :: Coercion a Int
  CoerceUInt      :: Coercion a Word32
  CoerceInstance  :: IsSuper a b => Coercion a b

-- | Matches an arbitrary coercion/represents an arbitrary coercion.
coeAny :: Coercion Any Any
coeAny = CoerceAny

-- | Describes the inheritance relation between classes/interfaces.
-- This is a non-symmetric, transitive and reflexive relation.
-- When a type extends or implements another type, we capture this
-- as an instance of |IsSuper|. Note that the instances may be
-- overlapping: that doesn't matter. As the class has no members,
-- we are only interested in the existence of membership of the
-- relation, and not in particular memberships.
class IsSuper a b                                     -- captures inheritance relation
instance IsSuper a a                                  -- reflexivity
instance (IsSuper a b, IsSuper b c) => IsSuper a c    -- transitivity

-- | Automatically constructs coercions related to upcasting.
--   Also, an untyped value (of type |Any|) can always be
--   automatically downcast to any other value.
class HasCoercion a b where
  mkCoercion :: Coercion a b

instance HasCoercion a a where
  mkCoercion = CoerceNone

instance IsSuper a b => HasCoercion a b where
  mkCoercion = CoerceInstance

instance HasCoercion Any b where
  mkCoercion = CoerceAny'


--
-- Typed expressions
-- An |Expr a| is mappeable to a |Val a|.
-- Expressions may contain nested instrumentations
--

-- Expressions
data Expr :: * -> * where
  ExprVal    :: Val a -> Expr a
  ExprUn     :: UnOp a b -> Expr a -> Expr b
  ExprBin    :: BinOp a b c -> Expr a -> Expr b -> Expr c
  ExprInstr  :: Instr (Val a) -> Expr a     -- nested instrumentation (with a 'Val' as return value)

-- Binary operators
data BinOp :: * -> * -> * -> * where
  OpAnd :: BinOp Bool Bool Bool
  OpOr  :: BinOp Bool Bool Bool
  OpRel :: BinRel a -> BinOp a a Bool
  OpAdd :: IsNumeric a => BinOp a a a
  OpSub :: IsNumeric a => BinOp a a a
  OpMul :: IsNumeric a => BinOp a a a
  OpDiv :: IsNumeric a => BinOp a a a
  OpMod :: IsNumInt a  => BinOp a a a
  OpMax :: IsNumeric a => BinOp a a a
  OpMin :: IsNumeric a => BinOp a a a

-- Unary operators
data UnOp :: * -> * -> * where
  OpAbs       :: IsNumeric a => UnOp a a
  OpNeg       :: IsNumeric a => UnOp a a
  OpNot       :: UnOp Bool Bool
  OpIsJust    :: UnOp (Maybe a) Bool
  OpIsNothing :: UnOp (Maybe a) Bool
  OpExtract   :: UnOp (Maybe a) a
  OpLength    :: UnOp [a] Int

-- Binary relations
data BinRel :: * -> * where
  RelEqual        :: BinRel a
  RelSmaller      :: IsComparable a => BinRel a
  RelSmallerEqual :: IsComparable a => BinRel a
  RelGreater      :: IsComparable a => BinRel a
  RelGreaterEqual :: IsComparable a => BinRel a

class IsComparable a
instance IsComparable Bool
instance IsNumeric a => IsComparable a

class IsNumeric a
instance IsNumInt a => IsNumInt a
instance IsNumeric Double

class IsNumInt a
instance IsNumInt Int
instance IsNumInt Word32

-- | Data-types used to distinguish dictionaries of 'IsExpr'
data DictPrim  = DictPrim
data DictConst = DictConst
data DictCoe   = DictCoe

class ExprDict d where exprDict :: d
instance ExprDict DictPrim  where exprDict = DictPrim
instance ExprDict DictConst where exprDict = DictConst
instance ExprDict DictCoe   where exprDict = DictCoe

-- | Conversion of different forms of values to expressions.
class IsExpr a b | a -> b where
  toExpr :: a -> Expr b

class IsExpr' d a b | a -> d where
  toExpr' :: d -> a -> Expr b

instance (ExprDict d, IsExpr' d a b) => IsExpr a b where
  toExpr = toExpr' (exprDict :: d)

instance (d ~ DictPrim) => IsExpr' d (Val a) a where
  toExpr' DictPrim = ExprVal

instance (d ~ DictPrim) => IsExpr' d (Var a) a where
  toExpr' DictPrim = toExpr . ValSym

instance (d ~ DictPrim) => IsExpr' d (Expr a) a where
  toExpr' DictPrim = id

instance (d ~ DictConst, IsConstant a) => IsExpr' d a a where
  toExpr' DictConst = toExpr . con


--
-- Declaration of a function call
--

-- | Specification of a function, which gives the
--   types of the parameters and the types of the
--   return values. Typically, functions have only
--   zero or one return values.
data FunSpec :: * -> * -> * where
  FunSig :: ParamSpecs a -> ParamSpecs b -> FunSpec a b

-- Arbitrary function.
anyFun :: FunSpec Any Any
anyFun = FunSig SpecsAny SpecsAny

-- | Function without a return value.
anyFun0 :: FunSpec Any ()
anyFun0 = FunSig SpecsAny SpecsEmpty

-- | Specifies the types of the parameters.
data ParamSpecs :: * -> * where
  SpecsAny   :: ParamSpecs Any                     -- any parameter specification
  SpecsEmpty :: ParamSpecs ()
  SpecsCons  :: Type a -> ParamSpecs b -> ParamSpecs (a, b)

-- A typed product of parameter values.
data Params :: * -> * where
  ParamsAny   :: Val [Any] -> Params Any           -- array of additional paramsters (of unknown type)
  ParamsEmpty :: Params ()
  ParamsCons  :: Val a -> Params b -> Params (a, b)

-- A Typed product of expression values.
data Exprs :: * -> * where
  ExprsAny   :: Expr [Any] -> Exprs Any            -- array of expressions of unknown type
  ExprsEmpty :: Exprs ()                           -- no expressions
  ExprsCons  :: Expr a -> Exprs b -> Exprs (a, b)  -- combination of expressions

class IsExprs a p | a -> p where   -- syntactic sugar to translate values into Exprs values.
  toExprs :: p -> Exprs a

-- Convenience function to build 'Exprs' values.
instance IsExprs Any (Expr [Any])        where  toExprs    = ExprsAny                         -- arbitrary parameters
instance IsExprs () ()                   where  toExprs () = ExprsEmpty                       -- zero parameters
instance IsExpr p a => IsExprs (a, ()) p where  toExprs e  = ExprsCons (toExpr e) ExprsEmpty  -- one parameter
instance (IsExpr p a, IsExpr q b) => IsExprs (a, (b, ())) (p, q)
  where  toExprs (e1,e2) = ExprsCons (toExpr e1) $ toExprs e2                                 -- two parameters
instance (IsExpr p a, IsExpr q b, IsExpr r c) => IsExprs (a, (b, (c, ()))) (p, q, r)
  where  toExprs (e1,e2,e3) = ExprsCons (toExpr e1) $ toExprs (e2,e3)                         -- three parameters
instance (IsExpr p a, IsExpr q b, IsExpr r c, IsExpr s d) => IsExprs (a, (b, (c, (d, ())))) (p, q, r, s)
  where  toExprs (e1,e2,e3,e4) = ExprsCons (toExpr e1) $ toExprs (e2,e3,e4)                   -- four parameters
instance (IsExpr p a, IsExpr q b, IsExpr r c, IsExpr s d, IsExpr t e) => IsExprs (a, (b, (c, (d, (e, ()))))) (p, q, r, s, t)
  where  toExprs (e1,e2,e3,e4,e5) = ExprsCons (toExpr e1) $ toExprs (e2,e3,e4,e5)             -- five parameters

-- | Overloaded to extract several forms of parameter specifications of functions.
class InputSpecs a b | a -> b where
  inputSpecs :: a -> ParamSpecs b

-- | Overloaded to extract server forms of return-type specifications of functions.
class OutputSpecs a b | a -> b where
  outputSpecs :: a -> ParamSpecs b

-- | Extracts the specifications.
instance InputSpecs (FunSpec a b) a   where  inputSpecs (FunSig p _) = p
instance InputSpecs (ParamSpecs a) a  where  inputSpecs = id
instance OutputSpecs(FunSpec a b) b   where  outputSpecs (FunSig _ p) = p
instance OutputSpecs (ParamSpecs a) a where  outputSpecs = id


--
-- Specification of a match on a join point
--

-- | Generic joinpoint information
data MatchInfo :: * -> * where
  MatchInfo :: { matchId :: Val Int, matchInfo :: a } -> MatchInfo a

-- | The result of matching against a coercion.
data CoerceMatch :: * -> * -> * where
  CoerceMatch :: { coerceCoe :: Coercion a b, coerceInfo :: CoerceInfo a b } -> CoerceMatch a b

-- | Information about what sort of coercion match we are talking about
data CoerceInfo :: * -> * -> * where
  CoerceEntry :: { coerceInp :: Val a } -> CoerceInfo a b
  CoerceDone  :: { coerceOut :: Val a } -> CoerceInfo a b
  CoerceFail  :: { coerceInp :: Val a, coerceExcp :: Val Any } -> CoerceInfo a b

-- | The result of a match against a block.
-- It consists of a unique identifier for the block, and
-- a Boolean value that identifies if the block is cyclic or not,
-- and a Maybe value that indicates if the block exited with an exception, or is an exception handler.
data BlockMatch :: * where
  BlockMatch :: { blockCycl :: Val Bool, blockExcp :: Val (Maybe Any) } -> BlockMatch

-- | The result of a match against a function.
-- It consists of the name of the function, the kind of the
-- function (i.e. property or method), and either the input
-- or output values.
-- May match against a failing function.
data FunMatch :: * -> * where
  FunMatch :: { funName :: Val String, funKind :: Val FunKind, funInfo :: a } -> FunMatch a

-- | Match info for a function that is called or has succeeded
data FunParams :: * -> * where
  FunParams :: { funParams :: Params a  } -> FunParams a

-- | Match info for a failed function.
data FunFail :: * where
  FunFail :: { funExcp :: Val Any } -> FunFail

-- | The kind of a function. Either a method or property (that get the object
-- as first parameter), or an objectless function.
data FunKind :: * where
  KindFunction :: FunKind
  KindMethod   :: FunKind
  KindProperty :: FunKind

-- | Returns the number of values in the parameter product as Haskell value.
numParams :: Params a -> Int
numParams ParamsEmpty       = 0
numParams (ParamsCons _ ps) = 1 + numParams ps


--
-- Instrumentation monad
-- Combination of a State monad, writer monad, and continuation monad.
--

newtype Instr a = Instr (Int -> (Int -> a -> L.Instr -> (L.Instr, Int)) -> (L.Instr, Int))

runInstr :: Instr a -> L.Instr
runInstr (Instr f) = fst $ f 1 (\n _ l -> (l, n))

-- | Functor instance is just handy to have
instance Functor Instr where
  fmap f (Instr g) = Instr (\n k -> g n (\m v -> k m (f v)))

-- | Monad instance provides meta-programming via sequences.
instance Monad Instr where
  return x = Instr $ \n k -> k n x L.Instr_Nop
  (Instr m1) >>= f = Instr $ \n1 k ->
    m1 n1 $ \n2 v1 i1 ->
      let (Instr m2) = f v1
      in m2 n2 $ \n3 v2 i2 -> k n3 v2 (optSeq i1 i2)
  fail s = Instr $ \n k -> k n (error s) (L.Instr_Fail (Just s))

-- | Applicative instance provides sequencing.
--   Can be defined in terms of the monad; the current implementation has slightly less
--   Haskell overhead.
instance Applicative Instr where
  pure x = return x
  (Instr m1) <*> (Instr m2) = Instr $ \n1 k ->
    m1 n1 $ \n2 f i1 ->
      m2 n2 $ \n3 v i2 ->
        k n3 (f v) (L.Instr_Seq i1 i2)

-- | Alternative instance provides parallel composition.
instance Alternative Instr where
  empty = Instr $ \n1 k -> k n1 (error "Alternative:empty") (L.Instr_Fail Nothing)
  (<|>) = alt False True  -- try both alternatives; share continuation

-- | Some variations on the parallel composition.
infixl 3 <<|>
(<<|>) :: Instr a -> Instr a -> Instr a
(<<|>) = alt False False  -- doesn't do right alternative if left one succeeds

infixl 2 <#>
infixl 2 <<#>

(<#>), (<<#>) :: Instr a -> Instr a -> Instr a
(<#>)  = alt True True    -- try both alternatives; duplicate continuation
(<<#>) = alt True False   -- doesn't do right alternative if left one succeeds

-- | Different strategies for combining instrumentations in parallel.
alt :: Bool -> Bool -> Instr a -> Instr a -> Instr a
alt dupCont mode (Instr m1) (Instr m2) = Instr $ \n1 k ->
  if dupCont
  then let (i1,n2) = m1 n1 k
           (i2,n3) = m2 n2 k
       in (optAlt mode i1 i2, n3)
  else m1 n1 $ \n2 v1 i1 ->
         m2 n2 $ \n3 v2 i2 -> k n3 v2 (optAlt mode i1 i2)

-- | Smart constructor for sequencing: optimizes 'Nop' and 'Fail' instrumentations away.
optSeq :: L.Instr -> L.Instr -> L.Instr
optSeq L.Instr_Nop q = q
optSeq p L.Instr_Nop = p
optSeq (L.Instr_Fail msg) q = L.Instr_Fail msg
-- optSeq p (L.Instr_Fail msg) = L.Instr_Fail msg  -- this optimization may only be done if |p| does not have side effect
optSeq p q = L.Instr_Seq p q

-- | Smart constructor for parallel composition: optimizes 'Nop' and 'Fail' instrumentation away.
optAlt :: Bool -> L.Instr -> L.Instr -> L.Instr
optAlt _ (L.Instr_Fail _) q = q
optAlt _ p (L.Instr_Fail _) = p
optAlt _ L.Instr_Nop q = q
optAlt _ p L.Instr_Nop = p
optAlt b p q = L.Instr_Alt p q b

-- | Instrumentation that keeps repeating itself, until it fails. The associated value
--   is the value of the last iteration. Loop never fails: it simply stops executing the body.
--   Loop forever, unless there is side-effect.
--   This is the only instruction that creates cycles in the control-flow of the generated
--   instrumentation. With this combinator, we can capture loops in the generated instrumentation.
loop :: Instr a -> Instr a
loop (Instr m) = Instr $ \n1 k ->
  m n1 $ \n2 v i -> k n2 v (optLoop i)

-- | Eliminates trivial endless loops, and trivial always failing loops
optLoop :: L.Instr -> L.Instr
optLoop (L.Instr_Fail s) = L.Instr_Fail s
optLoop L.Instr_Nop      = L.Instr_Nop
optLoop i                = L.Instr_Loop i


--
-- Primitive combinators
--

-- Provides a fresh integer.
freshInt :: Instr Int
freshInt = Instr $ \n k ->
  let m = n + 1
  in k m n L.Instr_Nop

-- Outputs a core instruction
embed :: L.Instr -> Instr ()
embed l = Instr $ \n k -> k n () l

-- | Primitive operation to invoke a function with a number of parameters.
invokeFun :: FunSpec a b -> Val String -> Params a -> Instr (Params b)
invokeFun (FunSig _ rs) nm inps = do
  outs <- freshParams rs
  let name  = valToVal nm
      inps' = paramsToParams inps
      outs' = paramsToParams outs
  embed (L.Instr_CallFun name inps' outs')
  return outs

-- | Primitive operation to invoke a property with a number of parameters.
invokeProp :: ParamsDescr b => Val (FunSpec a b) -> Params a -> Instr (Params b)
invokeProp prop inps = do
  outs <- outputsFromPropVal prop
  let vProp  = valToVal prop
      inps' = paramsToParams inps
      outs' = paramsToParams outs
  embed (L.Instr_CallProp vProp inps' outs')
  return outs

-- | Derive outputs based on the known type info
outputsFromPropVal :: ParamsDescr b => Val (FunSpec a b) -> Instr (Params b)
outputsFromPropVal _ = paramsFromType

-- | Type level computation only
class ParamsDescr a where
  paramsFromType :: Instr (Params a)

instance ParamsDescr Any where
  paramsFromType = do
    v <- fresh
    return $ ParamsAny v

instance ParamsDescr () where
  paramsFromType = return ParamsEmpty

instance ParamsDescr b => ParamsDescr (a,b) where
  paramsFromType = do
    v  <- fresh
    ps <- paramsFromType
    return (ParamsCons v ps)


-- | Convert values to core structure.
valToVal :: Val a -> L.Val
valToVal v = case v of
  ValSym (Sym n)    -> L.Val_Sym n
  ValCon con        -> case con of
    ConInt n      -> L.Val_Int n
    ConUInt n     -> L.Val_UInt n
    ConBool b     -> L.Val_Bool b
    ConString s   -> L.Val_String s
    ConArray vs   -> L.Val_Array (map valToVal vs)
    ConType t     -> L.Val_Type (typeToType t)
    ConMethod n _ -> L.Val_Method n
  ValInd v1 v2      -> L.Val_Ind (valToVal v1) (valToVal v2)
  ValProp v (Key s) -> L.Val_Prop (valToVal v) s
  ValDyn v1 v2      -> L.Val_Dyn (valToVal v1) (valToVal v2)

-- | Convert types to core structure.
typeToType :: Type a -> L.Type
typeToType t = case t of
  TypeAny      -> L.Type_Any
  TypeBool     -> L.Type_Bool
  TypeInt      -> L.Type_Int
  TypeUInt     -> L.Type_UInt
  TypeDouble   -> L.Type_Double
  TypeString   -> L.Type_String
  TypeArray t  -> L.Type_Array (typeToType t)
  TypeBase     -> L.Type_Base
  TypeObject s -> L.Type_Object s
  TypeMethod   -> L.Type_Method

-- | Convert parameters to core structure.
paramsToParams :: Params a -> L.Params
paramsToParams (ParamsAny v)     = L.Params_Any (valToVal v)
paramsToParams ParamsEmpty       = L.Params_Nil
paramsToParams (ParamsCons v ps) = L.Params_Cons (L.Param_Param (valToVal v)) (paramsToParams ps)

-- | Introduces a fresh value for each of the parameters in the specification.
freshParams :: ParamSpecs a -> Instr (Params a)
freshParams SpecsAny        = fresh >>= return . ParamsAny
freshParams SpecsEmpty      = return ParamsEmpty
freshParams (SpecsCons _ r) = do
  v  <- fresh
  ps <- freshParams r
  return (ParamsCons v ps)


-- Matches

-- | Matches upon the entry of a block
matchBlockEntry :: Instr (MatchInfo BlockMatch)
matchBlockEntry = do
  vId   <- fresh
  vCyc  <- fresh
  vExpt <- fresh
  embed $ L.Instr_Match $ L.Match_EnterBlock (valToVal vId) (valToVal vCyc) (valToVal vExpt)
  return $ MatchInfo vId $ BlockMatch vCyc vExpt

-- | Matches against a block that succeeded
matchBlockDone :: Instr (MatchInfo BlockMatch)
matchBlockDone = do
  vId <- fresh
  vCyc <- fresh
  let vExpt = ValCon ConNothing
  embed $ L.Instr_Match $ L.Match_LeaveBlock (valToVal vId) (valToVal vCyc)
  return $ MatchInfo vId $ BlockMatch vCyc vExpt

-- | Matches against a block that fails
matchBlockFail :: Instr (MatchInfo BlockMatch)
matchBlockFail = do
  vId   <- fresh
  vExpt <- fresh
  let vCyc = con False
  let vMbExpt = ValCon $ ConJust vExpt
  embed $ L.Instr_Match $ L.Match_FailBlock (valToVal vId) (valToVal vMbExpt)
  return $ MatchInfo vId $ BlockMatch vCyc vMbExpt

-- | Matches against the entry of a coercion.
matchCoerceEnter :: Coercion a b -> Instr (MatchInfo (CoerceMatch a b))
matchCoerceEnter CoerceNone = fail "a none coercion never matches"
matchCoerceEnter coe = do
  vId  <- fresh
  vVal <- fresh
  embed $ L.Instr_Match $ L.Match_BeginCoerce (valToVal vId) (valToVal vVal) (coeToCoe coe)
  return $ MatchInfo vId $ CoerceMatch coe $ CoerceEntry vVal

-- | Matches when a coercion succeeded.
matchCoerceDone :: Coercion a b -> Instr (MatchInfo (CoerceMatch a b))
matchCoerceDone CoerceNone = fail "a none coercion never matches"
matchCoerceDone coe = do
  vId  <- fresh
  vVal <- fresh
  embed $ L.Instr_Match $ L.Match_DoneCoerce (valToVal vId) (valToVal vVal) (coeToCoe coe)
  return $ MatchInfo vId $ CoerceMatch coe $ CoerceDone vVal

-- | Matches when a coercion failed.
matchCoerceFail :: Coercion a b -> Instr (MatchInfo (CoerceMatch a b))
matchCoerceFail CoerceNone = fail "a none coercion never matches"
matchCoerceFail coe = do
  vId   <- fresh
  vInp  <- fresh
  vExpt <- fresh
  embed $ L.Instr_Match $ L.Match_FailedCoerce (valToVal vId) (valToVal vExpt) (valToVal vInp) (coeToCoe coe)
  return $ MatchInfo vId $ CoerceMatch coe $ CoerceFail vInp vExpt

-- | Converts a coercion to core representation.
coeToCoe :: Coercion a b -> L.Coercion
coeToCoe coe = case coe of
  CoerceNone      -> L.Coercion_None
  CoerceAny       -> L.Coercion_Any
  CoerceAny'      -> L.Coercion_Any'
  CoerceString    -> L.Coercion_String
  CoerceDouble    -> L.Coercion_Double
  CoerceInt       -> L.Coercion_Int
  CoerceUInt      -> L.Coercion_UInt
  CoerceInstance  -> L.Coercion_Instance
  

-- | Match entry of a function.
matchFunEnter :: InputSpecs p a => p -> Instr (MatchInfo (FunMatch (FunParams a)))
matchFunEnter specs = mFunOk (inputSpecs specs) L.Match_EnterFun

-- | Match successfully leaving of a function.
matchFunDone :: OutputSpecs p b => p -> Instr (MatchInfo (FunMatch (FunParams b)))
matchFunDone specs = mFunOk (outputSpecs specs) L.Match_LeaveFun

-- | Match a function that exits with an exception.
matchFunFail :: Instr (MatchInfo (FunMatch FunFail))
matchFunFail = mFunFail L.Match_FailFun

-- | Match entry of a function.
matchCallEnter :: InputSpecs p a => p -> Instr (MatchInfo (FunMatch (FunParams a)))
matchCallEnter specs = mFunOk (inputSpecs specs) L.Match_BeginCall

-- | Match successfully leaving of a function.
matchCallDone :: OutputSpecs p b => p -> Instr (MatchInfo (FunMatch (FunParams b)))
matchCallDone specs = mFunOk (outputSpecs specs) L.Match_DoneCall

-- | Match a function that exits with an exception.
matchCallFail :: Instr (MatchInfo (FunMatch FunFail))
matchCallFail = mFunFail L.Match_FailedCall

-- | Matches against several forms of function invocation/return.
mFunOk :: ParamSpecs a -> (L.Val -> L.Val -> L.Val -> L.Params -> L.Match) -> Instr (MatchInfo (FunMatch (FunParams a)))
mFunOk specs f = do
  vId   <- fresh
  vName <- fresh
  vKind <- fresh
  ps    <- freshParams specs
  embed $ L.Instr_Match $ f (valToVal vId) (valToVal vName) (valToVal vKind) (paramsToParams ps)
  return $ MatchInfo vId $ FunMatch vName vKind $ FunParams ps

-- | Matches against several forms of failed functions.
mFunFail :: (L.Val -> L.Val -> L.Val -> L.Val -> L.Match) -> Instr (MatchInfo (FunMatch FunFail))
mFunFail f = do
  vId   <- fresh
  vName <- fresh
  vKind <- fresh
  excpt  <- fresh
  embed $ L.Instr_Match $ f (valToVal vId) (valToVal vName) (valToVal vKind) (valToVal excpt)
  return $ MatchInfo vId $ FunMatch vName vKind $ FunFail excpt

-- | Applies a transformation function on the compiled instrumentation.
transform :: (L.Instr -> L.Instr) -> Instr a -> Instr a
transform f (Instr m) = Instr $ \n1 k ->
  let (l, n2) = m n1 k
  in (f l, n2)

-- | Instrumentation is deferred to runtime.
dynamic :: Instr a -> Instr a
dynamic = transform L.Instr_Dyn

-- | Instrumentation must be resolved at instrumentation time.
static :: Instr a -> Instr a
static = transform L.Instr_Static

-- | Tests if the value has a |True| value: succeeds if this is the case, fails if not.
assertValTrue :: Val Bool -> Instr ()
assertValTrue v = embed $ L.Instr_Assert $ valToVal v

-- | Runs an instrumentation on predecessors; returns the result of the last one, or fails.
predecessors :: Instr a -> Instr a
predecessors = transform L.Instr_Last


-- | Expression to instrumentation. The instrumentation succeeds if the
-- expression evaluates to a value, then returns that value. Since the
-- expression may contain nested instrumentations, the evaluation of
-- expressions may fail.
expr :: IsExpr p a => p -> Instr (Val a)
expr = expr' . toExpr

-- | Compiles the expression to instrumentations.
expr' :: Expr a -> Instr (Val a)
expr' e = case e of
  ExprVal v      -> return v
  ExprUn op e'   -> do
    vIn <- expr' e'
    vOut <- fresh
    embed $ L.Instr_UnOp (valToVal vIn) (valToVal vOut) (unopToUnop op)
    return vOut
  ExprBin op l r -> do
    vL   <- expr' l
    vR   <- expr' r
    vOut <- fresh
    embed $ L.Instr_BinOp (valToVal vL) (valToVal vR) (valToVal vOut) (binopToBinop op)
    return vOut
  ExprInstr m    -> m

-- | Converts a unary operator to its core representation.
unopToUnop :: UnOp a b -> L.UnOp
unopToUnop op = case op of
  OpAbs       -> L.UnOp_Abs
  OpNeg       -> L.UnOp_Neg
  OpNot       -> L.UnOp_Not
  OpIsJust    -> L.UnOp_IsJust
  OpIsNothing -> L.UnOp_IsNothing
  OpExtract   -> L.UnOp_ExtractJust
  OpLength    -> L.UnOp_Length

-- | Converts a binary operator to its core representation.
binopToBinop :: BinOp a b c -> L.BinOp
binopToBinop op = case op of
  OpAnd -> L.BinOp_And
  OpOr  -> L.BinOp_Or
  OpRel r -> L.BinOp_Rel (relToRel r)  -- relation operator
  OpAdd -> L.BinOp_Add
  OpSub -> L.BinOp_Sub
  OpMul -> L.BinOp_Mul
  OpDiv -> L.BinOp_Div
  OpMod -> L.BinOp_Mod
  OpMax -> L.BinOp_Max
  OpMin -> L.BinOp_Min

-- | Converts a BinRel to the underlying representation.
relToRel :: BinRel a -> L.Rel
relToRel rel = case rel of
  RelEqual         -> L.Rel_Equal
  RelSmaller       -> L.Rel_Smaller
  RelSmallerEqual  -> L.Rel_SmallerEqual
  RelGreater       -> L.Rel_Greater
  RelGreaterEqual  -> L.Rel_GreaterEqual

-- | Returns the type of a value.
typeofVal :: Val a -> Instr (Val (Type a))
typeofVal v = do
  v' <- fresh
  embed $ L.Instr_TypeOf (valToVal v) (valToVal v')
  return v'

-- | Coerces the value of some type to another type.
coerceVal :: Coercion a b -> Val a -> Instr (Val b)
coerceVal CoerceNone v = return v
coerceVal coe v = do
  v' <- fresh
  let coe' :: L.Coercion
      coe' = case coe of
               CoerceAny      -> L.Coercion_Any
               CoerceAny'     -> L.Coercion_Any'
               CoerceString   -> L.Coercion_String
               CoerceDouble   -> L.Coercion_Double
               CoerceInt      -> L.Coercion_Int
               CoerceUInt     -> L.Coercion_UInt
               CoerceInstance -> L.Coercion_Instance
  embed $ L.Instr_Coerce coe' (valToVal v) (valToVal v')
  return v'

-- | Assigns a value to a variable.
assignVal :: Var a -> Val a -> Instr ()
assignVal (Sym n) v = embed $ L.Instr_Assign n (valToVal v)

-- | Static type assumption
assumeType :: Val a -> Val (Type a) -> Instr ()
assumeType v t = embed (L.Instr_Type (valToVal v) (valToVal t))


--
-- Derived instrumentations
--


-- | Calls a property that may take a tuple of expressions as parameters.
-- type signature for v required for inference
callProp :: forall a b e p . (IsExprs a p, IsExpr e (FunSpec a b), ParamsDescr b) => e -> p -> Instr (Params b)
callProp e ps = do
  v <- (expr e :: Instr (Val (FunSpec a b)))
  ps' <- exprsToParams $ toExprs ps
  invokeProp v ps'

-- | Calls a function that may take a tuple of expressions as parameters.
callFun :: (IsExprs a p, IsExpr s String) => FunSpec a b -> s -> p -> Instr (Params b)
callFun f nm ps = do
    nm' <- expr $ toExpr nm
    ps' <- exprsToParams $ toExprs ps
    invokeFun f nm' ps'

-- | Tests if some condition is met; if the condition is 'False', evaluation aborts.
assert :: IsExpr a Bool => a -> Instr ()
assert g = do
  v <- expr $ toExpr g
  assertValTrue v

-- | Tests if two expressions return equal values.
equal :: (IsExpr p Bool, IsExpr q Bool) => p -> q -> Instr ()
equal p q = assert ((.==.) p q)

-- | Assignment of a value to a variable.
infix 2 .=
(.=) :: IsExpr p a => Var a -> p -> Instr ()
ref .= e = expr (toExpr e) >>= assignVal ref

-- | Introduces a fresh, initialized variable.
var :: IsExpr p a => p -> Instr (Var a)
var e = do ref <- freshVar
           ref .= e
           return ref

-- | If-then-else instrumentation.
ifte :: (IsExpr g Bool, IsExpr p a, IsExpr q a) => g -> p -> q -> Instr (Expr a)
ifte g t e = fmap toExpr (ifte' g t e)

ifte' :: (IsExpr g Bool, IsExpr p a, IsExpr q a) => g -> p -> q -> Instr (Val a)
ifte' g t e = (assert g >> expr t) <<|> expr e

-- | While-loop (reevaluates 'g' for each iteration)
while :: IsExpr g Bool => g -> Instr a -> Instr a
while g m = loop (assert g >> m)

-- | For-loop
for :: (IsNumeric a, IsExpr p a) => p -> (Expr a -> Expr Bool) -> (Expr a -> Expr a) -> (Expr a -> Instr b) -> Instr b
for i g u b = do
  x <- var i
  let e = toExpr x
  while (g e) $ do
    v <- b e
    x .= u e
    return v


--
-- Derived expressions
--

-- | Produces a fresh value.
fresh :: Instr (Val a)
fresh = fmap ValSym freshVar

-- | Produces a fresh variable.
freshVar :: Instr (Var a)
freshVar = fmap Sym freshInt

-- | Nested instrumentation (no result).
nest0 :: Instr () -> Expr Bool
nest0 m = nest $ fmap (const (con True)) m

-- | Nested instrumentation (with value as result).
nest :: Instr (Val a) -> Expr a
nest = ExprInstr

-- | Function call without a result value.
call0 :: (IsExprs a p, IsExpr s String) => FunSpec a () -> s -> p -> Expr Bool
call0 f nm ps = nest0 $ fmap (const ()) $ callFun f nm ps

-- | Function call with a single result value.
call :: (IsExprs a p, IsExpr s String) => FunSpec a (b, ()) -> s -> p -> Expr b
call f nm ps = nest $ fmap extract $ callFun f nm ps where
  extract :: Params (b, ()) -> Val b
  extract (ParamsCons v ParamsEmpty) = v

-- | Turns a expression product into a val-product.
exprsToParams :: Exprs a -> Instr (Params a)
exprsToParams ExprsEmpty = return ParamsEmpty
exprsToParams (ExprsCons e es) = do
  v  <- expr e
  fmap (ParamsCons v) $ exprsToParams es

-- | Casts one value into another value (implicitly).
cast :: (IsExpr a b, HasCoercion b c) => a -> Expr c
cast = coerce mkCoercion

-- | Coerces a value into another value (explicitly).
coerce :: IsExpr a b => Coercion b c -> a -> Expr c
coerce c e = nest (expr (toExpr e) >>= coerceVal c)

-- | Obtains the type of an expression.
typeof :: IsExpr a b => a -> Expr (Type b)
typeof e = nest (expr (toExpr e) >>= typeofVal)

-- | If-then-else expression.
ite :: (IsExpr g Bool, IsExpr p a, IsExpr q a) => g -> p -> q -> Expr a
ite g t e = nest (ifte' g t e)

-- | Common case for binary operators.
bin :: (IsExpr p a, IsExpr q b) => BinOp a b c -> p -> q -> Expr c
bin o p q = ExprBin o (toExpr p) (toExpr q)

-- | Common case for unary operators.
una :: IsExpr p a => UnOp a b -> p -> Expr b
una o p = ExprUn o (toExpr p)


--
-- Convenience operators
--

unNot :: IsExpr p Bool => p -> Expr Bool
unNot = una OpNot

unAbs :: (IsExpr p a, IsNumeric a) => p -> Expr a
unAbs = una OpAbs

unNeg :: (IsExpr p a, IsNumeric a) => p -> Expr a
unNeg = una OpNeg

-- Check if the value is a nothing-value.
unIsNothing :: (IsExpr p (Maybe a)) => p -> Expr Bool
unIsNothing = una OpIsNothing

-- | Check if the value is a just-value.
unIsJust :: (IsExpr p (Maybe a)) => p -> Expr Bool
unIsJust = una OpIsJust

-- | Get the value stored in a just-value.
unExtract :: (IsExpr p (Maybe a)) => p -> Expr a
unExtract = una OpExtract

-- | Get the length of an array.
unLength :: (IsExpr p [a]) => p -> Expr Int
unLength = una OpLength


infix 4 .==., .<=., .<., .>=., .>., ./=.
(.==.), (./=.) :: (IsExpr p a, IsExpr q a) => p -> q -> Expr Bool
(.==.) = bin (OpRel RelEqual)
(./=.) p q = unNot (p .==. q)

(.<=.), (.<.), (.>=.), (.>.) :: (IsExpr p a, IsExpr q a, IsComparable a) => p -> q -> Expr Bool
(.<=.) = bin (OpRel RelSmallerEqual)
(.<.)  = bin (OpRel RelSmaller)
(.>=.) = bin (OpRel RelGreaterEqual)
(.>.)  = bin (OpRel RelGreater)

infixr 3 .&&.
infixr 2 .||.
(.||.), (.&&.) :: (IsExpr p Bool, IsExpr q Bool) => p -> q -> Expr Bool
(.||.) = bin OpOr
(.&&.) = bin OpAnd

infixl 6 .+., .-.
infixl 7 .*., ./., .%.
infixl 5 ./\., .\/.

(.+.), (.-.), (.*.), (./.), (./\.), (.\/.) :: (IsNumeric a, IsExpr p a, IsExpr q a) => p -> q -> Expr a
(.+.)  = bin OpAdd
(.-.)  = bin OpSub
(.*.)  = bin OpMul
(./.)  = bin OpDiv
(./\.) = bin OpMax
(.\/.) = bin OpMin

(.%.) :: (IsNumInt a, IsExpr p a, IsExpr q a) => p -> q -> Expr a
(.%.) = bin OpMod