-- | 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