copilot-theorem (empty) → 2.2.0
raw patch · 37 files changed
+5169/−0 lines, 37 filesdep +ansi-terminaldep +basedep +bimapsetup-changed
Dependencies added: ansi-terminal, base, bimap, containers, copilot-core, data-default, directory, mtl, parsec, pretty, process, random, smtlib2, transformers, xml
Files
- LICENSE +29/−0
- README.md +863/−0
- Setup.hs +2/−0
- copilot-theorem.cabal +87/−0
- src/Copilot/Theorem.hs +13/−0
- src/Copilot/Theorem/IL.hs +10/−0
- src/Copilot/Theorem/IL/PrettyPrint.hs +84/−0
- src/Copilot/Theorem/IL/Spec.hs +177/−0
- src/Copilot/Theorem/IL/Transform.hs +41/−0
- src/Copilot/Theorem/IL/Translate.hs +324/−0
- src/Copilot/Theorem/Kind2.hs +10/−0
- src/Copilot/Theorem/Kind2/AST.hs +39/−0
- src/Copilot/Theorem/Kind2/Output.hs +51/−0
- src/Copilot/Theorem/Kind2/PrettyPrint.hs +69/−0
- src/Copilot/Theorem/Kind2/Prover.hs +72/−0
- src/Copilot/Theorem/Kind2/Translate.hs +212/−0
- src/Copilot/Theorem/Misc/Error.hs +31/−0
- src/Copilot/Theorem/Misc/SExpr.hs +81/−0
- src/Copilot/Theorem/Misc/Utils.hs +60/−0
- src/Copilot/Theorem/Prove.hs +185/−0
- src/Copilot/Theorem/Prover/Backend.hs +28/−0
- src/Copilot/Theorem/Prover/SMT.hs +368/−0
- src/Copilot/Theorem/Prover/SMTIO.hs +108/−0
- src/Copilot/Theorem/Prover/SMTLib.hs +100/−0
- src/Copilot/Theorem/Prover/TPTP.hs +105/−0
- src/Copilot/Theorem/Prover/Z3.hs +608/−0
- src/Copilot/Theorem/Tactics.hs +17/−0
- src/Copilot/Theorem/TransSys.hs +10/−0
- src/Copilot/Theorem/TransSys/Cast.hs +82/−0
- src/Copilot/Theorem/TransSys/Invariants.hs +16/−0
- src/Copilot/Theorem/TransSys/Operators.hs +301/−0
- src/Copilot/Theorem/TransSys/PrettyPrint.hs +121/−0
- src/Copilot/Theorem/TransSys/Renaming.hs +74/−0
- src/Copilot/Theorem/TransSys/Spec.hs +198/−0
- src/Copilot/Theorem/TransSys/Transform.hs +267/−0
- src/Copilot/Theorem/TransSys/Translate.hs +286/−0
- src/Copilot/Theorem/TransSys/Type.hs +40/−0
+ LICENSE view
@@ -0,0 +1,29 @@+2009+BSD3 License terms++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++Redistributions of source code must retain the above copyright+notice, this list of conditions and the following disclaimer.++Redistributions in binary form must reproduce the above copyright+notice, this list of conditions and the following disclaimer in the+documentation and/or other materials provided with the distribution.++Neither the name of the developers nor the names of its contributors+may be used to endorse or promote products derived from this software+without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,863 @@+[](https://travis-ci.org/Copilot-Language/copilot-theorem)++# Copilot Theorem++Highly automated proof techniques are a necessary step for the widespread+adoption of formal methods in the software industry. Moreover, it could provide+a partial answer to one of its main issue which is scalability.++*copilot-theorem* is a Copilot library aimed at checking automatically some safety+properties on Copilot programs. It includes :++* A general interface for *provers* and a *proof scheme* mechanism aimed at+ splitting the task of proving a complex property into checking a sequence of+ smaller lemmas.++* A prover implementing basic **k-induction** model checking [1], useful for+ proving basic k-inductive properties and for pedagogical purposes.++* A prover producing native inputs for the *Kind2* model checker, developed at+ University of Iowa. The latter uses both the *k-induction algorithm* extended+ with *path compression* and *structural abstraction* [2] and the **IC3+ algorithm** with counterexample generalization based on *approximate+ quantifier elimination* [3].++## A Tutorial++### Installation instructions++*copilot-theorem* needs the following dependencies to be installed :++* The *copilot-core* and *copilot-language* Haskell libraries+* The *Yices2* SMT-solver : `yices-smt2` must be in your `$PATH`+* The *Z3* SMT-solver : `z3` must be in your `$PATH`+* The *Kind2* model checker : `kind2` must be in your `$PATH`++To build it, just clone this repository and use `cabal install`. You will find+some examples in the `examples` folder, which can be built with `cabal install`+too, producing an executable `copilot-theorem-example` in your `.cabal/bin`+folder.++### First steps++*copilot-theorem* is aimed at checking **safety properties** on Copilot programs.+Intuitively, a safety property is a property which express the idea that+*nothing bad can happen*. In particular, any invalid safety property can be+disproved by a finite execution trace of the program called a+**counterexample**. Safety properties are often opposed to **liveness**+properties, which express the idea that *something good will eventually+happen*. The latters are out of the scope of *copilot-theorem*.++Safety properties are simply expressed with standard boolean streams. In+addition to triggers and observers declarations, it is possible to bind a+boolean stream to a property name with the `prop` construct in the+specification.++For instance, here is a straightforward specification declaring one property :++```haskell+spec :: Spec+spec = do+ prop "gt0" (x > 0)+ where+ x = [1] ++ (1 + x)+```++Let's say we want to check that `gt0` holds. For this, we use the `prove ::+Prover -> ProofScheme -> Spec -> IO ()` function exported by `Copilot.Theorem`.+This function takes three arguments :++* The prover we want to use. For now, two provers are available, exported by+ the `Copilot.Theorem.Light` and `Copilot.Theorem.Kind2` module.+* A *proof scheme*, which is a sequence of instructions like *check*, *assume*,+ *assert*...+* The Copilot specification++Here, we can just write++```haskell+prove (lightProver def) (check "gt0") spec+```++where `lightProver def` stands for the *light prover* with default+configuration.++### The Prover interface++The `Copilot.Theorem.Prover` defines a general interface for provers. Therefore,+it is really easy to add a new prover by creating a new object of type+`Prover`. The latter is defined like this :++```haskell+data Cex = Cex++type Infos = [String]++data Output = Output Status Infos++data Status+ = Valid+ | Invalid (Maybe Cex)+ | Unknown+ | Error++data Feature = GiveCex | HandleAssumptions++data Prover = forall r . Prover+ { proverName :: String+ , hasFeature :: Feature -> Bool+ , startProver :: Core.Spec -> IO r+ , askProver :: r -> [PropId] -> PropId -> IO Output+ , closeProver :: r -> IO ()+ }+```++Each prover mostly has to provide a `askProver` function which takes as an+argument+* The prover descriptor+* A list of assumptions+* A conclusion++and checks if the assumptions logically entail the conclusion.++Two provers are provided by default : `Light` and `Kind2`.++#### The light prover++The *light prover* is a really simple prover which uses the Yices SMT solver+with the `QF_UFLIA` theory and is limited to prove *k-inductive* properties,+that is properties such that there exists some *k* such that :++* The property holds during the first *k* steps of the algorithm.+* From the hypothesis the property has held during *k* consecutive steps, we+ can prove it is still true one step further.++For instance, in this example++```haskell+spec :: Spec+spec = do+ prop "gt0" (x > 0)+ prop "neq0" (x /= 0)+ where+ x = [1] ++ (1 + x)+```+the property `"gt0"` is inductive (1-inductive) but the property `"neq0"` is+not.+++The *light prover* is defined in `Copilot.Theorem.Light`. This module exports the+`lightProver :: Options -> Prover` function which builds a prover from a record+of type `Options` :++```haskell+data Options = Options+ { kTimeout :: Integer+ , onlyBmc :: Bool+ , debugMode :: Bool }+```++Here,++* `kTimeout` is the maximum number of steps of the k-induction algorithm the+ prover executes before giving up.+* If `onlyBmc` is set to `True`, the prover will only search for+ counterexamples and won't try to prove the properties discharged to it.+* If `debugMode` is set to `True`, the SMTLib queries produced by the prover+ are displayed in the standard output.++`Options` is an instance of the `Data.Default` typeclass :++```haskell+instance Default Options where+ def = Options+ { kTimeout = 100+ , debugMode = False+ , onlyBmc = False }+```++Therefore, `def` stands for the default configuration.++#### The Kind2 prover++The *Kind2* prover uses the model checker with the same name, from Iowa+university. It translates the Copilot specification into a *modular transition+system* (the Kind2 native format) and then calls the `kind2` executable.++It is provided by the `Copilot.Theorem.Kind2` module, which exports a `kind2Prover+:: Options -> Prover` where the `Options` type is defined as++```haskell+data Options = Options { bmcMax :: Int }+```+and where `bmcMax` corresponds to the `--bmc_max` option of *kind2* and is+equivalent to the `kTimeout` option of the light prover. Its default value is+0, which stands for infinity.++#### Combining provers++The `combine :: Prover -> Prover -> Prover` function lets you merge two provers+A and B into a prover C which launches both A and B and returns the *most+precise* output. It would be interesting to implement other merging behaviours+in the future. For instance, a *lazy* one such that C launches B only if A has+returns *unknown* or *error*.++As an example, the following prover is used in `Driver.hs` :++```haskell+prover =+ lightProver def {onlyBmc = True, kTimeout = 5}+ `combine` kind2Prover def+```++We will discuss the internals and the experimental results of these provers+later.++### Proof schemes++Let's consider again this example :++```haskell+spec :: Spec+spec = do+ prop "gt0" (x > 0)+ prop "neq0" (x /= 0)+ where+ x = [1] ++ (1 + x)+```++and let's say we want to prove `"neq0"`. Currently, the two available solvers+fail at showing this non-inductive property (we will discuss this limitation+later). Therefore, we can prove the more general inductive lemma `"gt0"` and+deduce our main goal from this. For this, we use the proof scheme++```haskell+assert "gt0" >> check "neq0"+```+instead of just `check "neq0"`. A proof scheme is chain of primitives schemes+glued by the `>>` operator. For now, the available primitives are :++* `check "prop"` checks whether or not a given property is true in the current+ context.+* `assume "prop"` adds an assumption in the current context.+* `assert "prop"` is a shortcut for `check "prop" >> assume "prop"`.+* `assuming :: [PropId] -> ProofScheme -> ProofScheme` is such that `assuming+ props scheme` assumes the list of properties *props*, executes the proof+ scheme *scheme* in this context, and forgets the assumptions.+* `msg "..."` displays a string in the standard output++We will discuss the limitations of this tool and a way to use it in practice+later.++### Some examples++Some examples are in the *examples* folder. The `Driver.hs` contains the `main`+function to run any example. Each other example file exports a specification+`spec` and a proof scheme `scheme`. You can change the example being run just+by changing one *import* directive in `Driver.hs`.++These examples include :++* `Incr.hs` : a straightforward example in the style of the previous one.+* `Grey.hs` : an example where two different implementations of a periodical+ counter are shown to be equivalent.+* `BoyerMoore.hs` : a certified version of the majority vote algorithm+ introduced in the Copilot tutorial.+* `SerialBoyerMoore.hs` : a *serial* version of the first step of the *Boyer+ Moore algorithm*, where a new element is added to the list and the majority+ candidate is updated at each clock tick. See the section *Limitations related+ to the SMT solvers* for an analysis of this example.++## Technical details++### An introduction to SMT-based model checking++An introduction to the model-checking techniques used by *copilot-theorem* can be+found in the `doc` folder of this repository. It consists in a self sufficient+set of slides. You can find some additional readings in the *References*+section.++### Architecture of copilot-kind++#### An overview of the proving process++Each prover first translates the Copilot specification into an intermediate+representation best suited for model checking. Two representations are+available :++* The **IL** format : a Copilot program is translated into a list of+ quantifier-free equations over integer sequences, implicitly universally+ quantified by a free variable *n*. Each sequence roughly corresponds to a+ stream. This format is the one used in G. Hagen's thesis [4]. The *light+ prover* works with this format.++* The **TransSys** format : a Copilot program is *flattened* and translated+ into a *state transition system* [1]. Moreover, in order to keep some+ structure in this representation, the variables of this system are grouped by+ *nodes*, each node exporting and importing variables. The *Kind2 prover* uses+ this format, which can be easily translated into the native format.++For each of these formats, there is a folder in `src/Copilot/Theorem` which+contains at least+* `Spec.hs` where the format is defined+* `PrettyPrint.hs` for pretty printing (useful for debugging)+* `Translate.hs` where the translation process from `Core.Spec` is defined.++These three formats share a simplified set of types and operators, defined+respectively in `Misc.Type` and `Misc.Operator`.++##### An example++The following program :++```haskell+spec = do+ prop "pos" (fib > 0)++ where+ fib :: Stream Word64+ fib = [1, 1] ++ (fib + drop 1 fib)+```++can be translated into this IL specification :++```+SEQUENCES+ s0 : Int++MODEL INIT+ s0[0] = 1+ s0[1] = 1++MODEL REC+ s0[n + 2] = s0[n] + s0[n + 1]++PROPERTIES+ 'pos' : s0[n] > 0+```++or this modular transition system :++```+NODE 's0' DEPENDS ON []+DEFINES+ out : Int =+ 1 -> pre out.1+ out.1 : Int =+ 1 -> pre out.2+ out.2 : Int =+ (out) + (out.1)++NODE 'prop-pos' DEPENDS ON [s0]+IMPORTS+ (s0 : out) as 's0.out'+ (s0 : out.1) as 's0.out.1'+ (s0 : out.2) as 's0.out.2'+DEFINES+ out : Bool =+ (s0.out) > (0)++NODE 'top' DEPENDS ON [prop-pos, s0]+IMPORTS+ (prop-pos : out) as 'pos'+ (s0 : out) as 's0.out'+ (s0 : out.1) as 's0.out.1'+ (s0 : out.2) as 's0.out.2'++PROPS+'pos' is (top : pos)++```++Note that the names of the streams are lost in the Copilot *reification+process* [7] and so we have no way to keep them.++#### Types++In these three formats, GADTs are used to statically ensure a part of the+type-corectness of the specification, in the same spirit it is done in the+other Copilot libraries. *copilot-theorem* handles only three types which are+`Integer`, `Real` and `Bool` and which are handled by the SMTLib standard.+*copilot-theorem* works with *pure* reals and integers. Thus, it is unsafe in the+sense it ignores integer overflow problems and the loss of precision due to+floating point arithmetic.++The rules of translation between Copilot types and *copilot-theorem* types are+defined in `Misc/Cast`.++#### Operators++The operators provided by `Misc.Operator` mostly consists in boolean+connectors, linear operators, equality and inequality operators. If other+operators are used in the Copilot program, they are handled using+non-determinism or uninterpreted functions.++The file `CoreUtils/Operators` contains helper functions to translate Copilot+operators into *copilot-theorem* operators.+++#### The Light prover++As said in the tutorial, the *light prover* is a simple tool implementing the+basic *k-induction* algorithm [1]. The `Light` directory contains three files :++* `Prover.hs` : the prover and the *k-induction* algorithm are implemented in+ this file.+* `SMT.hs` contains some functions to interact with the Yices SMT provers.+* `SMTLib.hs` is a set of functions to output SMTLib directives. It uses the+ `Misc.SExpr` module to deal with S-expressions.++The code is both concise and simple and should be worth a look.++The prover first translates the copilot specification into the *IL* format.+This translation is implemented in `IL.Translate`. It is straightforward as the+*IL* format does not differ a lot from the *copilot core* format. This is the+case because the reification process has transformed the copilot program such+that the `++` operator only occurs at the top of a stream definition.+Therefore, each stream definition directly gives us a recurrence equation and+initial conditions for the associated sequence.++The translation process mostly :++* onverts the types and operators, using uninterpreted functions to handle+ non-linear operators and external functions.+* creates a sequence for each stream, local stream ands external stream.++The reader is invited to use the *light prover* on the examples with `debugMode+= true`, in order to have a look at the SMTLib code produced. For instance, if+we check the property `"pos"` on the previous example involving the Fibonacci+sequence, we get :++```+<step> (set-logic QF_UFLIA)+<step> (declare-fun n () Int)+<step> (declare-fun s0 (Int) Int)+<step> (assert (= (s0 (+ n 2)) (+ (s0 (+ n 0)) (s0 (+ n 1)))))+<step> (assert (= (s0 (+ n 3)) (+ (s0 (+ n 1)) (s0 (+ n 2)))))+<step> (assert (> (s0 (+ n 0)) 0))+<step> (push 1)+<step> (assert (or false (not (> (s0 (+ n 1)) 0))))+<step> (check-sat)+<step> (pop 1)+<step> (assert (= (s0 (+ n 4)) (+ (s0 (+ n 2)) (s0 (+ n 3)))))+<step> (assert (> (s0 (+ n 1)) 0))+<step> (push 1)+<step> (assert (or false (not (> (s0 (+ n 2)) 0))))+<step> (check-sat)+unsat+<step> (pop 1)+```++Here, we just kept the outputs related to the `<step>` psolver, which is the+solver trying to prove the *continuation step*.++You can see that the SMT solver is used in an incremental way (`push` and `pop`+instructions), so we don't need to restart it at each step of the algorithm+(see [2]).+++#### The Kind2 prover++The *Kind2 prover* first translates the copilot specification into a *modular+transition system*. Then, a chain of transformations is applied to this system+(for instance, in order to remove dependency cycles among nodes). After this,+the system is translated into the *Kind2 native format* and the `kind2`+executable is launched. The following sections will bring more details about+this process.++##### Modular transition systems++Let's look at the definition of a *modular transition systems*, in the+`TransSys.Spec` module :++```haskell+type NodeId = String+type PropId = String++data Spec = Spec+ { specNodes :: [Node]+ , specTopNodeId :: NodeId+ , specProps :: Map PropId ExtVar }++data Node = Node+ { nodeId :: NodeId+ , nodeDependencies :: [NodeId]+ , nodeLocalVars :: Map Var LVarDescr+ , nodeImportedVars :: Bimap Var ExtVar+ , nodeConstrs :: [Expr Bool] }++data Var = Var {varName :: String}+ deriving (Eq, Show, Ord)++data ExtVar = ExtVar {extVarNode :: NodeId, extVarLocalPart :: Var }+ deriving (Eq, Ord)++data VarDescr = forall t . VarDescr+ { varType :: Type t+ , varDef :: VarDef t }++data VarDef t =+ Pre t Var+ | Expr (Expr t)+ | Constrs [Expr Bool]++data Expr t where+ Const :: Type t -> t -> Expr t+ Ite :: Type t -> Expr Bool -> Expr t -> Expr t -> Expr t+ Op1 :: Type t -> Op1 x t -> Expr x -> Expr t+ Op2 :: Type t -> Op2 x y t -> Expr x -> Expr y -> Expr t+ VarE :: Type t -> Var -> Expr t+```++A transition system (`Spec` type) is mostly made of a list of nodes. A *node*+is just a set of variables living in a local namespace and corresponding to the+`Var` type. The `ExtVar` type is used to identify a variable in the global+namespace by specifying both a node name and a variable. A node contains two+types of variables :++* Some variables imported from other nodes. The structure `nodeImportedVars`+ binds each imported variable to its local name. The set of nodes from which a+ node imports some variables is stored in the `nodeDependencies` field.++* Some locally defined variables contained in the `nodeLocalVars` field. Such a+ variable can be+ - Defined as the previous value of another variable (`Pre` constructor of+ `VarDef`)+ - Defined by an expression involving other variables (`Expr` constructor)+ - Defined implicitly by a set of constraints (`Constrs` constructor)++##### The translation process++First, a copilot specification is translated into a modular transition system.+This process is defined in the `TransSys.Translate` module. Each stream is+associated to a node. The most significant task of this translation process is+to *flatten* the copilot specification so the value of all streams at time *n*+only depends on the values of all the streams at time *n - 1*, which is not the+case in the `Fib` example shown earlier. This is done by a simple program+transformation which turns this :++```haskell+fib = [1, 1] ++ (fib + drop 1 fib)+```+into this :++```haskell+fib0 = [1] ++ fib1+fib1 = [1] ++ (fib1 + fib0)+```++and then into the node++```+NODE 'fib' DEPENDS ON []+DEFINES+ out : Int =+ 1 -> pre out.1+ out.1 : Int =+ 1 -> pre out.2+ out.2 : Int =+ (out) + (out.1)+```++Once again, this flattening process is made easier by the fact that the `++`+operator only occurs leftmost in a stream definition after the reification+process.++##### Some transformations over modular transition systems++The transition system obtained by the `TransSys.Translate` module is perfectly+consistent. However, it can't be directly translated into the *Kind2 native+file format*. Indeed, it is natural to bind each node to a predicate but the+Kind2 file format requires that each predicate only uses previously defined+predicates. However, some nodes in our transition system could be mutually+recursive. Therefore, the goal of the `removeCycles :: Spec -> Spec` function+defined in `TransSys.Transform` is to remove such dependency cycles.++This function relies on the `mergeNodes :: [NodeId] -> Spec -> Spec` function+which signature is self-explicit. The latter solves name conflicts by using the+`Misc.Renaming` monad. Some code complexity has been added so the variable+names remains as clear as possible after merging two nodes.++The function `removeCycles` computes the strongly connected components of the+dependency graph and merge each one into a single node. The complexity of this+process is high in the worst case (the square of the total size of the system+times the size of the biggest node) but good in practice as few nodes are to be+merged in most practical cases.++After the cycles have been removed, it is useful to apply another+transformation which makes the translation from `TransSys.Spec` to `Kind2.AST`+easier. This transformation is implemented in the `complete` function. In a+nutshell, it transforms a system such that++* If a node depends on another, it imports *all* its variables.+* The dependency graph is transitive, that is if *A* depends of *B* which+ depends of *C* then *A* depends on *C*.++After this transformation, the translation from `TransSys.Spec` to `Kind2.AST`+is almost only a matter of syntax.++###### Bonus track++Thanks to the `mergeNodes` function, we can get for free the function++```haskell+inline :: Spec -> Spec+inline spec = mergeNodes [nodeId n | n <- specNodes spec] spec+```++which discards all the structure of a *modular transition system* and turns it+into a *non-modular transition system* with only one node. In fact, when+translating a copilot specification to a kind2 file, two styles are available :+the `Kind2.toKind2` function takes a `Style` argument which can take the value+`Inlined` or `Modular`. The only difference is that in the first case, a call+to `removeCycles` is replaced by a call to `inline`.++### Limitations of copilot-theorem++Now, we will discuss some limitations of the *copilot-theorem* tool. These+limitations are organized in two categories : the limitations related to the+Copilot language itself and its implementation, and the limitations related to+the model-checking techniques we are using.++#### Limitations related to Copilot implementation++The reification process used to build the `Core.Spec` object looses many+informations about the structure of the original Copilot program. In fact, a+stream is kept in the reified program only if it is recursively defined.+Otherwise, all its occurences will be inlined. Moreover, let's look at the+`intCounter` function defined in the example `Grey.hs` :++```haskell+intCounter :: Stream Bool -> Stream Word64+intCounter reset = time+ where+ time = if reset then 0+ else [0] ++ if time == 3 then 0 else time + 1+```++If *n* counters are created with this function, the same code will be inlined+*n* times and the structure of the original code will be lost.++There are many problems with this :++* It makes some optimizations of the model-checking based on a static analysis+ of the program more difficult (for instance *structural abstraction* - see+ [2]).+* It makes the inputs given to the SMT solvers larger and repetitive.++We can't rewrite the Copilot reification process in order to avoid these+inconvenients as these informations are lost by GHC itself before it occurs.+The only solution we can see would be to use *Template Haskell* to generate+automatically some structural annotations, which might not be worth the dirt+introduced.++#### Limitations related to the model-checking techniques used++##### Limitations of the IC3 algorithm++The IC3 algorithm was shown to be a very powerful tool for hardware+certification. However, the problems encountered when verifying softwares are+much more complex. For now, very few non-inductive properties can be proved by+*Kind2* when basic integer arithmetic is involved.++The critical point of the IC3 algorithm is the counterexample generalization+and the lemma tightening parts of it. When encountering a *counterexample to+the inductiveness* (CTI) for a property, these techniques are used to find a+lemma discarding it which is general enough so that all CTIs can be discarded+in a finite number of steps.++The lemmas found by the current version fo *Kind2* are often too weak. Some+suggestions to enhance this are presented in [1]. We hope some progress will be+made in this area in a near future.++A workaround to this problem would be to write kind of an interactive mode+where the user is invited to provide some additional lemmas when automatic+techniques fail. Another solution would be to make the properties being checked+quasi-inductive by hand. In this case, *copilot-theorem* is still a useful tool+(especially for finding bugs) but the verification of a program can be long and+requires a high level of technicity.++##### Limitations related to the SMT solvers++The use of SMT solvers introduces two kind of limitations :++1. We are limited by the computing power needed by the SMT solvers+2. SMT solvers can't handle quantifiers efficiently++Let's consider the first point. SMT solving is costly and its performances are+sometimes unpredictable. For instance, when running the `SerialBoyerMoore`+example with the *light prover*, Yices2 does not terminate. However, the *z3*+SMT solver used by *Kind2* solves the problem instantaneously. Note that this+performance gap is not due to the use of the IC3 algorithm because the property+to check is inductive. It could be related to the fact the SMT problem produced+by the *light prover* uses uninterpreted functions for encoding streams instead+of simple integer variables, which is the case when the copilot program is+translated into a transition system. However, this wouldn't explain why the+*light prover* still terminates instantaneously on the `BoyerMoore` example,+which seems not simpler by far.++The second point keeps you from expressing or proving some properties+universally quantified over a stream or a constant. Sometimes, this is still+possible. For instance, in the `Grey` example, as we check a property like+`intCounter reset == greyCounter reset` with `reset` an external stream+(therefore totally unconstrained), we kind of show a universally quantified+property. This fact could be used to enhance the proof scheme system (see the+*Future work* section). However, this trick is not always possible. For+instance, in the `SerialBoyerMoore` example, the property being checked should+be quantified over all integer constants. Here, we can't just introduce an+arbitrary constant stream because it is the quantified property which is+inductive and not the property specialized for a given constant stream. That's+why we have no other solution than replacing universal quantification by+*bounded* universal quantification by assuming all the elements of the input+stream are in the finite list `allowed` and using the function `forAllCst`+defined in `Copilot.Kind.Lib` :++```haskell+conj :: [Stream Bool] -> Stream Bool+conj = foldl (&&) true++forAllCst ::(Typed a) => [a] -> (Stream a -> Stream Bool) -> Stream Bool+forAllCst l f = conj $ map (f . constant) l+```++However, this solution isn't completely satisfying because the size of the+property generated is proportionnal to the cardinal of `allowed`.++#### Some scalability issues++A standard way to prove large programs is to rely on its logical structure by+writing a specification for each of its functions. This very natural approach+is hard to follow in our case because of++* The difficulty to deal with universal quantification.+* The lack of *true* functions in Copilot : the latter offers metaprogramming+ facilities but no concept of functions like *Lustre* does with its *nodes*).+* The inlining policy of the reification process. This point is related to the+ previous one.++Once again, *copilot-theorem* is still a very useful tool, especially for+debugging purposes. However, we don't think it is adapted to write and check a+complete specification for large scale programs.++## Future work++### Missing features in the Kind2 prover++These features are not currently provided due to the lack of important features+in the Kind2 SMT solver.++#### Counterexamples displaying++Counterexamples are not displayed with the Kind2 prover because Kind2 doesn't+support XML output of counterexamples. If the last feature is provided, it+should be easy to implement counterexamples displaying in *copilot-theorem*. For+this, we recommend to keep some informations about *observers* in+`TransSys.Spec` and to add one variable per observer in the Kind2 output file.++#### Bad handling of non-linear operators and external functions++Non-linear Copilot operators and external functions are poorly handled because+of the lack of support of uninterpreted functions in the Kind2 native format. A+good way to handle these would be to use uninterpreted functions. With this+solution, properties like+```haskell+2 * sin x + 1 <= 3+```+with `x` any stream can't be proven but at least the following can be proved+```haskell+let y = x in sin x == sin y+```+Currently, the *Kind2 prover* fail with the last example, as the results of+unknown functions are turned into fresh unconstrained variables.++### Simple extensions++The following extensions would be really simple to implement given the current+architecture of Kind2.+++ If inductive proving of a property fails, giving the user a concrete CTI+ (*Counterexample To The Inductiveness*, see the [1]).+++ Use Template Haskell to declare automatically some observers with the same+ names used in the original program.++### Refactoring suggestions+++ Implement a cleaner way to deal with arbitrary streams and arbitrary+ constants by extending the `Copilot.Core.Expr type`. See the+ `Copilot.Kind.Lib` module to observe how inelegant the current solution is.+++ Use `Cnub` as an intermediary step in the translation from `Core.Spec` to+ `IL.Spec` and `TransSys.Spec`.++### More advanced enhancements+++ Enhance the proof scheme system such that when proving a property depending+ on an arbitrary stream, it is possible to assume some specialized versions of+ this property for given values of the arbitrary stream. In other words,+ implementing a basic way to deal with universal quantification.+++ It could be useful to extend the Copilot language in a way it is possible to+ use annotations inside the Copilot code. For instance, we could++ - Declare assumptions and invariants next to the associated code instead of+ gathering all properties in a single place.+ - Declare a frequent code pattern which should be factorized in the+ transition problem (see the section about Copilot limitations)++## FAQ++### Why does the light prover not deliver counterexamples ?++The problem is the light prover is using uninterpreted functions to represent+streams and Yices2 can't give you values for uninterpreted functions when you+ask it for a valid assignment. Maybe we could get better performances and+easily counterexample display if we rewrite the *light prover* so that it works+with *transition systems* instead of *IL*.++### Why does the code related to transition systems look so complex ?++It is true the code of `TransSys` is quite complex. In fact, it would be really+straightforward to produce a flattened transition system and then a Kind2 file+with just a single *top* predicate. In fact, It would be as easy as producing+an *IL* specification.++To be honest, I'm not sure producing a modular *Kind2* output is worth the+complexity added. It's especially true at the time I write this in the sense+that :++* Each predicate introduced is used only one time (which is true because+ copilot doesn't handle functions or parametrized streams like Lustre does and+ everything is inlined during the reification process).+* A similar form of structure could be obtained from a flattened Kind2 native+ input file with some basic static analysis by producing a dependency graph+ between variables.+* For now, the *Kind2* model-checker ignores these structure informations.++However, the current code offers some nice transformation tools (node merging,+`Renaming` monad...) which could be useful if you intend to write a tool for+simplifying or factorizing transition systems. Moreover, it becomes easier to+write local transformations on transition systems as name conflicts can be+avoided more easily when introducing more variables, as there is one namespace+per node.++## References++1. *An insight into An insight into SMT-based model checking techniques for+ formal software verification of synchronous dataflow programs*, talk,+ Jonathan Laurent (see the `doc` folder of this repository)++2. *Scaling up the formal verification of Lustre programs with SMT-based+ techniques*, G. Hagen, C. Tinelli++3. *SMT-based Unbounded Model Checking with IC3 and Approximate Quantifier+ Elimination*, C. Sticksel, C. Tinelli++4. *Verifying safety properties of Lustre programs : an SMT-based approach*,+ PhD thesis, G. Hagen++5. *Understanding IC3*, Aaron R. Bradley++6. *IC3: Where Monolithic and Incremental Meet*, F. Somenzi, A.R. Bradley++7. *Copilot: Monitoring Embedded Systems*, L. Pike, N. Wegmann, S. Niller
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ copilot-theorem.cabal view
@@ -0,0 +1,87 @@+cabal-version : >= 1.10+name : copilot-theorem+synopsis: k-induction for Copilot.+description:++ Some tools to prove properties on Copilot programs with k-induction model+ checking.++version : 2.2.0+license : BSD3+license-file : LICENSE+maintainer : jonathan.laurent@ens.fr+stability : Experimental+category : Language, Embedded+build-type : Simple+extra-source-files : README.md++author : Jonathan Laurent++library+ default-language : Haskell2010+ hs-source-dirs : src++ ghc-options : -Wall -fwarn-tabs+ -fno-warn-name-shadowing+ -fno-warn-unused-binds+ -fno-warn-missing-signatures+ -fcontext-stack=100++ build-depends : base >= 4.0 && < 5+ , copilot-core == 2.2.0+ , mtl+ , containers+ , pretty+ , process+ , directory+ , parsec+ , data-default+ , bimap+ , xml+ , random+ , transformers+ , smtlib2 >= 0.3+ , ansi-terminal++ exposed-modules : Copilot.Theorem+ , Copilot.Theorem.Prove+ , Copilot.Theorem.Kind2+ , Copilot.Theorem.Prover.SMT+ , Copilot.Theorem.Prover.Z3+ , Copilot.Theorem.Kind2.Prover+ + other-modules : Copilot.Theorem.Tactics+ + , Copilot.Theorem.IL+ , Copilot.Theorem.IL.PrettyPrint+ , Copilot.Theorem.IL.Spec+ , Copilot.Theorem.IL.Translate+ , Copilot.Theorem.IL.Transform+ + , Copilot.Theorem.Kind2.AST+ , Copilot.Theorem.Kind2.Output+ , Copilot.Theorem.Kind2.PrettyPrint+ , Copilot.Theorem.Kind2.Translate+ + , Copilot.Theorem.Prover.SMTIO+ , Copilot.Theorem.Prover.SMTLib+ , Copilot.Theorem.Prover.TPTP+ , Copilot.Theorem.Prover.Backend+ + , Copilot.Theorem.Misc.Error+ , Copilot.Theorem.Misc.SExpr+ , Copilot.Theorem.Misc.Utils+ + , Copilot.Theorem.TransSys+ , Copilot.Theorem.TransSys.Cast+ , Copilot.Theorem.TransSys.PrettyPrint+ , Copilot.Theorem.TransSys.Renaming+ , Copilot.Theorem.TransSys.Spec+ , Copilot.Theorem.TransSys.Transform+ , Copilot.Theorem.TransSys.Translate+ , Copilot.Theorem.TransSys.Invariants+ , Copilot.Theorem.TransSys.Operators+ , Copilot.Theorem.TransSys.Type+ ++
+ src/Copilot/Theorem.hs view
@@ -0,0 +1,13 @@+--------------------------------------------------------------------------------++module Copilot.Theorem+ ( module X+ , Proof+ , PropId, PropRef+ , Universal, Existential+ ) where++import Copilot.Theorem.Tactics as X+import Copilot.Theorem.Prove++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/IL.hs view
@@ -0,0 +1,10 @@+--------------------------------------------------------------------------------++module Copilot.Theorem.IL (module X) where++import Copilot.Theorem.IL.Spec as X+import Copilot.Theorem.IL.Translate as X+import Copilot.Theorem.IL.Transform as X+import Copilot.Theorem.IL.PrettyPrint as X++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/IL/PrettyPrint.hs view
@@ -0,0 +1,84 @@+---------------------------------------------------------------------------------++{-# LANGUAGE NamedFieldPuns, GADTs #-}++module Copilot.Theorem.IL.PrettyPrint (prettyPrint, printConstraint) where++import Copilot.Theorem.IL.Spec+import Text.PrettyPrint.HughesPJ+import qualified Data.Map as Map++--------------------------------------------------------------------------------++prettyPrint :: IL -> String+prettyPrint = render . ppSpec++printConstraint :: Expr -> String+printConstraint = render . ppExpr++indent = nest 4+emptyLine = text ""++ppSpec :: IL -> Doc+ppSpec (IL { modelInit, modelRec, properties }) =+ text "MODEL INIT"+ $$ indent (foldr (($$) . ppExpr) empty modelInit) $$ emptyLine+ $$ text "MODEL REC"+ $$ indent (foldr (($$) . ppExpr) empty modelRec) $$ emptyLine+ $$ text "PROPERTIES"+ $$ indent (Map.foldrWithKey (\k -> ($$) . ppProp k)+ empty properties )++ppProp :: PropId -> ([Expr], Expr) -> Doc+ppProp id (as, c) = (foldr (($$) . ppExpr) empty as)+ $$ quotes (text id) <+> colon <+> ppExpr c++ppSeqDescr :: SeqDescr -> Doc+ppSeqDescr (SeqDescr id ty) = text id <+> colon <+> ppType ty++ppVarDescr :: VarDescr -> Doc+ppVarDescr (VarDescr id ret args) =+ text id+ <+> colon+ <+> (hsep . punctuate (space <> text "->" <> space) $ map ppType args)+ <+> text "->"+ <+> ppType ret++ppType :: Type -> Doc+ppType = text . show++ppExpr :: Expr -> Doc+ppExpr (ConstB v) = text . show $ v+ppExpr (ConstR v) = text . show $ v+ppExpr (ConstI _ v) = text . show $ v++ppExpr (Ite _ c e1 e2) =+ text "if" <+> ppExpr c+ <+> text "then" <+> ppExpr e1+ <+> text "else" <+> ppExpr e2++ppExpr (Op1 _ op e) = ppOp1 op <+> ppExpr e++ppExpr (Op2 _ op e1 e2) =+ ppExpr e1 <+> ppOp2 op <+> ppExpr e2++ppExpr (SVal _ s i) = text s <> brackets (ppSeqIndex i)++ppExpr (FunApp _ name args) =+ text name <> parens (hsep . punctuate (comma <> space) $ map ppExpr args)++ppSeqIndex :: SeqIndex -> Doc+ppSeqIndex (Var i)+ | i == 0 = text "n"+ | i < 0 = text "n" <+> text "-" <+> integer (-i)+ | otherwise = text "n" <+> text "+" <+> integer i++ppSeqIndex (Fixed i) = integer i++ppOp1 :: Op1 -> Doc+ppOp1 = text . show++ppOp2 :: Op2 -> Doc+ppOp2 = text . show++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/IL/Spec.hs view
@@ -0,0 +1,177 @@+--------------------------------------------------------------------------------++{-# LANGUAGE ExistentialQuantification, GADTs, LambdaCase #-}++module Copilot.Theorem.IL.Spec+ ( Type (..)+ , Op1 (..)+ , Op2 (..)+ , SeqId+ , SeqIndex (..)+ , SeqDescr (..)+ , VarDescr (..)+ , Expr (..)+ , IL (..)+ , PropId+ , typeOf+ , _n_+ , _n_plus+ , evalAt+ ) where++import Data.Map (Map)+import Data.Function (on)++--------------------------------------------------------------------------------++type SeqId = String++data SeqIndex = Fixed Integer | Var Integer+ deriving (Eq, Ord, Show)++data Type = Bool | Real+ | SBV8 | SBV16 | SBV32 | SBV64+ | BV8 | BV16 | BV32 | BV64+ deriving (Eq, Ord)++instance Show Type where+ show = \case+ Bool -> "Bool"+ Real -> "Real"+ SBV8 -> "SBV8"+ SBV16 -> "SBV16"+ SBV32 -> "SBV32"+ SBV64 -> "SBV64"+ BV8 -> "BV8"+ BV16 -> "BV16"+ BV32 -> "BV32"+ BV64 -> "BV64"++data Expr+ = ConstB Bool+ | ConstR Double+ | ConstI Type Integer+ | Ite Type Expr Expr Expr+ | Op1 Type Op1 Expr+ | Op2 Type Op2 Expr Expr+ | SVal Type SeqId SeqIndex+ | FunApp Type String [Expr]+ deriving (Eq, Ord, Show)++--------------------------------------------------------------------------------++data VarDescr = VarDescr+ { varName :: String+ , varType :: Type+ , args :: [Type]+ }++instance Eq VarDescr where+ (==) = (==) `on` varName++instance Ord VarDescr where+ compare = compare `on` varName++--------------------------------------------------------------------------------++type PropId = String++data SeqDescr = SeqDescr+ { seqId :: SeqId+ , seqType :: Type+ }++data IL = IL+ { modelInit :: [Expr]+ , modelRec :: [Expr]+ , properties :: Map PropId ([Expr], Expr)+ , inductive :: Bool+ }++--------------------------------------------------------------------------------++data Op1 = Not | Neg | Abs | Exp | Sqrt | Log | Sin | Tan | Cos | Asin | Atan+ | Acos | Sinh | Tanh | Cosh | Asinh | Atanh | Acosh+ deriving (Eq, Ord)++data Op2 = Eq | And | Or | Le | Lt | Ge | Gt | Add | Sub | Mul | Mod | Fdiv | Pow+ deriving (Eq, Ord)++-------------------------------------------------------------------------------++instance Show Op1 where+ show op = case op of+ Neg -> "-"+ Not -> "not"+ Abs -> "abs"+ Exp -> "exp"+ Sqrt -> "sqrt"+ Log -> "log"+ Sin -> "sin"+ Tan -> "tan"+ Cos -> "cos"+ Asin -> "asin"+ Atan -> "atan"+ Acos -> "acos"+ Sinh -> "sinh"+ Tanh -> "tanh"+ Cosh -> "cosh"+ Asinh -> "asinh"+ Atanh -> "atanh"+ Acosh -> "acosh"++instance Show Op2 where+ show op = case op of+ And -> "and"+ Or -> "or"++ Add -> "+"+ Sub -> "-"+ Mul -> "*"++ Mod -> "mod"++ Fdiv -> "/"++ Pow -> "^"++ Eq -> "="++ Le -> "<="+ Ge -> ">="+ Lt -> "<"+ Gt -> ">"++-------------------------------------------------------------------------------++typeOf :: Expr -> Type+typeOf e = case e of+ ConstB _ -> Bool+ ConstR _ -> Real+ ConstI t _ -> t+ Ite t _ _ _ -> t+ Op1 t _ _ -> t+ Op2 t _ _ _ -> t+ SVal t _ _ -> t+ FunApp t _ _ -> t++_n_ :: SeqIndex+_n_ = Var 0++_n_plus :: (Integral a) => a -> SeqIndex+_n_plus d = Var (toInteger d)++evalAt :: SeqIndex -> Expr -> Expr+evalAt _ e@(ConstB _) = e+evalAt _ e@(ConstR _) = e+evalAt _ e@(ConstI _ _) = e+evalAt i (Op1 t op e) = Op1 t op (evalAt i e)+evalAt i (Op2 t op e1 e2) = Op2 t op (evalAt i e1) (evalAt i e2)+evalAt i (Ite t c e1 e2) = Ite t (evalAt i c) (evalAt i e1) (evalAt i e2)+evalAt i (FunApp t name args) = FunApp t name $ map (\e -> evalAt i e) args++evalAt _ e@(SVal _ _ (Fixed _)) = e+evalAt (Fixed n) (SVal t s (Var d)) = SVal t s (Fixed $ n + d)+evalAt (Var k) (SVal t s (Var d)) = SVal t s (Var $ k + d)++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/IL/Transform.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE LambdaCase #-}++module Copilot.Theorem.IL.Transform ( bsimpl ) where++import Copilot.Theorem.IL.Spec++-- | A transformation intended to remove boolean literals.+bsimpl :: Expr -> Expr+bsimpl = until (\x -> bsimpl' x == x) bsimpl'+ where+ bsimpl' = \case+ Ite _ (ConstB True) e _ -> bsimpl' e+ Ite _ (ConstB False) _ e -> bsimpl' e+ Ite t c e1 e2 -> Ite t (bsimpl' c) (bsimpl' e1) (bsimpl' e2)++ Op1 _ Not (Op1 _ Not e) -> bsimpl' e+ Op1 _ Not (ConstB True) -> ConstB False+ Op1 _ Not (ConstB False) -> ConstB True+ Op1 t o e -> Op1 t o (bsimpl' e)++ Op2 _ Or e (ConstB False) -> bsimpl' e+ Op2 _ Or (ConstB False) e -> bsimpl' e+ Op2 _ Or _ (ConstB True) -> ConstB True+ Op2 _ Or (ConstB True) _ -> ConstB True++ Op2 _ And _ (ConstB False) -> ConstB False+ Op2 _ And (ConstB False) _ -> ConstB False+ Op2 _ And e (ConstB True) -> bsimpl' e+ Op2 _ And (ConstB True) e -> bsimpl' e++ Op2 _ Eq e (ConstB False) -> bsimpl' (Op1 Bool Not e)+ Op2 _ Eq (ConstB False) e -> bsimpl' (Op1 Bool Not e)+ Op2 _ Eq e (ConstB True) -> bsimpl' e+ Op2 _ Eq (ConstB True) e -> bsimpl' e++ Op2 t o e1 e2 -> Op2 t o (bsimpl' e1) (bsimpl' e2)++ FunApp t f args -> FunApp t f (map bsimpl' args)++ e -> e+
+ src/Copilot/Theorem/IL/Translate.hs view
@@ -0,0 +1,324 @@+--------------------------------------------------------------------------------++{-# LANGUAGE RankNTypes, NamedFieldPuns, ScopedTypeVariables, GADTs,+ LambdaCase #-}++module Copilot.Theorem.IL.Translate ( translate ) where++import Copilot.Theorem.IL.Spec++import qualified Copilot.Core as C++import qualified Data.Map.Strict as Map++import Control.Applicative ((<$>), (<*))+import Control.Monad.State++import Data.Char+import Data.List (find)++import Text.Printf++import GHC.Float (float2Double)++--------------------------------------------------------------------------------++-- 'nc' stands for naming convention.+ncSeq :: C.Id -> SeqId+ncSeq = printf "s%d"++-- We assume all local variables have distinct names whatever their scopes.+ncLocal :: C.Name -> SeqId+ncLocal s = "l" ++ dropWhile (not . isNumber) s++ncExternVar :: C.Name -> SeqId+ncExternVar n = "ext_" ++ n++ncExternFun :: C.Name -> SeqId+ncExternFun n = "_" ++ n++ncUnhandledOp :: String -> String+ncUnhandledOp = id++ncMux :: Integer -> SeqId+ncMux n = "mux" ++ show n++--------------------------------------------------------------------------------++-- | Translates a Copilot specification to an IL specification++translate :: C.Spec -> IL+translate (C.Spec {C.specStreams, C.specProperties}) = runTrans $ do++ let modelInit = concatMap streamInit specStreams++ mainConstraints <- mapM streamRec specStreams++ localConstraints <- popLocalConstraints+ properties <- Map.fromList <$>+ forM specProperties+ (\(C.Property {C.propertyName, C.propertyExpr}) -> do+ e' <- expr propertyExpr+ propConds <- popLocalConstraints+ return (propertyName, (propConds, e')))++ return IL+ { modelInit+ , modelRec = mainConstraints ++ localConstraints+ , properties+ , inductive = not $ null specStreams+ }++bound :: Expr -> C.Type a -> Trans ()+bound s t = case t of+ C.Int8 -> bound' C.Int8+ C.Int16 -> bound' C.Int16+ C.Int32 -> bound' C.Int32+ C.Int64 -> bound' C.Int64+ C.Word8 -> bound' C.Word8+ C.Word16 -> bound' C.Word16+ C.Word32 -> bound' C.Word32+ C.Word64 -> bound' C.Word64+ _ -> return ()+ where bound' :: (Bounded a, Integral a) => C.Type a -> Trans ()+ bound' t = localConstraint (Op2 Bool And+ (Op2 Bool Le (trConst t minBound) s)+ (Op2 Bool Ge (trConst t maxBound) s))++streamInit :: C.Stream -> [Expr]+streamInit (C.Stream { C.streamId = id+ , C.streamBuffer = b :: [val]+ , C.streamExprType = t }) =+ zipWith initConstraint [0..] b+ where initConstraint :: Integer -> val -> Expr+ initConstraint p v = Op2 Bool Eq+ (SVal (trType t) (ncSeq id) (Fixed p))+ $ trConst t v++streamRec :: C.Stream -> Trans Expr+streamRec (C.Stream { C.streamId = id+ , C.streamExpr = e+ , C.streamBuffer = b+ , C.streamExprType = t })+ = do+ let s = SVal (trType t) (ncSeq id) (_n_plus $ length b)+ bound s t+ e' <- expr e+ return $ Op2 Bool Eq s e'++--------------------------------------------------------------------------------++expr :: C.Expr a -> Trans Expr++expr (C.Const t v) = return $ trConst t v++expr (C.Label _ _ e) = expr e++expr (C.Drop t k id) = return $ SVal (trType t) (ncSeq id) (_n_plus k)++expr (C.Local ta _ name ea eb) = do+ ea' <- expr ea+ localConstraint (Op2 Bool Eq (SVal (trType ta) (ncLocal name) _n_) ea')+ expr eb++expr (C.Var t name) = return $ SVal (trType t) (ncLocal name) _n_++expr (C.ExternVar t name _) = bound s t >> return s+ where s = SVal (trType t) (ncExternVar name) _n_++expr (C.ExternFun t name args _ _) = do+ args' <- mapM trArg args+ let s = FunApp (trType t) (ncExternFun name) args'+ bound s t+ return s+ where trArg (C.UExpr {C.uExprExpr}) = expr uExprExpr++-- Arrays and functions are treated the same way+expr (C.ExternArray ta tb name _ ind _ _) =+ expr (C.ExternFun tb name [C.UExpr ta ind] Nothing Nothing)++expr (C.Op1 (C.Sign ta) e) = case ta of+ C.Int8 -> trSign ta e+ C.Int16 -> trSign ta e+ C.Int32 -> trSign ta e+ C.Int64 -> trSign ta e+ C.Float -> trSign ta e+ C.Double -> trSign ta e+ _ -> expr $ C.Const ta 1+ where trSign :: (Ord a, Num a) => C.Type a -> C.Expr a -> Trans Expr+ trSign ta e =+ expr (C.Op3 (C.Mux ta)+ (C.Op2 (C.Lt ta) e (C.Const ta 0))+ (C.Const ta (-1))+ (C.Op3 (C.Mux ta)+ (C.Op2 (C.Gt ta) e (C.Const ta 0))+ (C.Const ta 1)+ (C.Const ta 0)))+expr (C.Op1 (C.Sqrt _) e) = do+ e' <- expr e+ return $ Op2 Real Pow e' (ConstR 0.5)+expr (C.Op1 (C.Cast _ _) e) = expr e+expr (C.Op1 op e) = do+ e' <- expr e+ return $ Op1 t' op' e'+ where (op', t') = trOp1 op++expr (C.Op2 (C.Ne t) e1 e2) = do+ e1' <- expr e1+ e2' <- expr e2+ return $ Op1 Bool Not (Op2 t' Eq e1' e2')+ where t' = trType t++expr (C.Op2 op e1 e2) = do+ e1' <- expr e1+ e2' <- expr e2+ return $ Op2 t' op' e1' e2'+ where (op', t') = trOp2 op++expr (C.Op3 (C.Mux t) cond e1 e2) = do+ cond' <- expr cond+ e1' <- expr e1+ e2' <- expr e2+ newMux cond' (trType t) e1' e2'++trConst :: C.Type a -> a -> Expr+trConst t v = case t of+ C.Bool -> ConstB v+ C.Float -> negifyR (float2Double v)+ C.Double -> negifyR v+ t@C.Int8 -> negifyI v (trType t)+ t@C.Int16 -> negifyI v (trType t)+ t@C.Int32 -> negifyI v (trType t)+ t@C.Int64 -> negifyI v (trType t)+ t@C.Word8 -> negifyI v (trType t)+ t@C.Word16 -> negifyI v (trType t)+ t@C.Word32 -> negifyI v (trType t)+ t@C.Word64 -> negifyI v (trType t)+ where negifyR :: Double -> Expr+ negifyR v+ | v >= 0 = ConstR v+ | otherwise = Op1 Real Neg $ ConstR $ negate $ v+ negifyI :: Integral a => a -> Type -> Expr+ negifyI v t+ | v >= 0 = ConstI t $ toInteger v+ | otherwise = Op1 t Neg $ ConstI t $ negate $ toInteger v++trOp1 :: C.Op1 a b -> (Op1, Type)+trOp1 = \case+ C.Not -> (Not, Bool)+ C.Abs t -> (Abs, trType t)+ -- C.Sign t ->+ -- C.Recip t ->+ C.Exp t -> (Exp, trType t)+ -- C.Sqrt t ->+ C.Log t -> (Log, trType t)+ C.Sin t -> (Sin, trType t)+ C.Tan t -> (Tan, trType t)+ C.Cos t -> (Cos, trType t)+ C.Asin t -> (Asin, trType t)+ C.Atan t -> (Atan, trType t)+ C.Acos t -> (Acos, trType t)+ C.Sinh t -> (Sinh, trType t)+ C.Tanh t -> (Tanh, trType t)+ C.Cosh t -> (Cosh, trType t)+ C.Asinh t -> (Asinh, trType t)+ C.Atanh t -> (Atanh, trType t)+ C.Acosh t -> (Acosh, trType t)+ -- C.BwNot t ->+ -- C.Cast t ->+ _ -> error "Unsupported unary operator in input." -- TODO(chathhorn)++trOp2 :: C.Op2 a b c -> (Op2, Type)+trOp2 = \case+ C.And -> (And, Bool)+ C.Or -> (Or, Bool)++ C.Add t -> (Add, trType t)+ C.Sub t -> (Sub, trType t)+ C.Mul t -> (Mul, trType t)++ C.Mod t -> (Mod, trType t)+ -- C.Div t ->++ C.Fdiv t -> (Fdiv, trType t)++ C.Pow t -> (Pow, trType t)+ -- C.Logb t ->++ C.Eq t -> (Eq, Bool)+ -- C.Ne t ->++ C.Le t -> (Le, trType t)+ C.Ge t -> (Ge, trType t)+ C.Lt t -> (Lt, trType t)+ C.Gt t -> (Gt, trType t)++ -- C.BwAnd t ->+ -- C.BwOr t ->+ -- C.BwXor t ->+ -- C.BwShiftL t _ ->+ -- C.BwShiftR t _ ->++ _ -> error "Unsupported binary operator in input." -- TODO(chathhorn)++trType :: C.Type a -> Type+trType = \case+ C.Bool -> Bool+ C.Int8 -> SBV8+ C.Int16 -> SBV16+ C.Int32 -> SBV32+ C.Int64 -> SBV64+ C.Word8 -> BV8+ C.Word16 -> BV16+ C.Word32 -> BV32+ C.Word64 -> BV64+ C.Float -> Real+ C.Double -> Real++--------------------------------------------------------------------------------++data TransST = TransST+ { localConstraints :: [Expr]+ , muxes :: [(Expr, (Expr, Type, Expr, Expr))]+ , nextFresh :: Integer+ }++newMux :: Expr -> Type -> Expr -> Expr -> Trans Expr+newMux c t e1 e2 = do+ ms <- muxes <$> get+ case find ((==mux) . snd) ms of+ Nothing -> do+ f <- fresh+ let v = SVal t (ncMux f) _n_+ modify $ \st -> st { muxes = (v, mux) : ms }+ return v+ Just (v, _) -> return v+ where mux = (c, t, e1, e2)++getMuxes :: Trans [Expr]+getMuxes = muxes <$> get >>= return . concat . (map toConstraints)+ where toConstraints (v, (c, _, e1, e2)) =+ [ Op2 Bool Or (Op1 Bool Not c) (Op2 Bool Eq v e1)+ , Op2 Bool Or c (Op2 Bool Eq v e2)+ ]++type Trans = State TransST++fresh :: Trans Integer+fresh = do+ modify $ \st -> st {nextFresh = nextFresh st + 1}+ nextFresh <$> get++localConstraint :: Expr -> Trans ()+localConstraint c =+ modify $ \st -> st {localConstraints = c : localConstraints st}++popLocalConstraints :: Trans [Expr]+popLocalConstraints = liftM2 (++) (localConstraints <$> get) getMuxes+ <* (modify $ \st -> st {localConstraints = [], muxes = []})++runTrans :: Trans a -> a+runTrans m = evalState m $ TransST [] [] 0++--------------------------------------------------------------------------------+
+ src/Copilot/Theorem/Kind2.hs view
@@ -0,0 +1,10 @@+--------------------------------------------------------------------------------++module Copilot.Theorem.Kind2 (module X) where++import Copilot.Theorem.Kind2.AST as X+import Copilot.Theorem.Kind2.Translate as X+import Copilot.Theorem.Kind2.PrettyPrint as X+import Copilot.Theorem.Kind2.Prover as X++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Kind2/AST.hs view
@@ -0,0 +1,39 @@+--------------------------------------------------------------------------------++module Copilot.Theorem.Kind2.AST where++--------------------------------------------------------------------------------++data File = File+ { filePreds :: [PredDef]+ , fileProps :: [Prop] }++data Prop = Prop+ { propName :: String+ , propTerm :: Term }++data PredDef = PredDef+ { predId :: String+ , predStateVars :: [StateVarDef]+ , predInit :: Term+ , predTrans :: Term }++data StateVarDef = StateVarDef+ { varId :: String+ , varType :: Type+ , varFlags :: [StateVarFlag] }++data Type = Int | Real | Bool++data StateVarFlag = FConst++data PredType = Init | Trans++data Term =+ ValueLiteral String+ | PrimedStateVar String+ | StateVar String+ | FunApp String [Term]+ | PredApp String PredType [Term]++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Kind2/Output.hs view
@@ -0,0 +1,51 @@+--------------------------------------------------------------------------------++{-# LANGUAGE RankNTypes #-}++module Copilot.Theorem.Kind2.Output (parseOutput) where++import Text.XML.Light hiding (findChild)+import Copilot.Theorem.Prove as P+import Data.Maybe (fromJust)++import qualified Copilot.Theorem.Misc.Error as Err++--------------------------------------------------------------------------------++simpleName s = QName s Nothing Nothing++parseOutput :: String -> String -> P.Output+parseOutput prop xml = fromJust $ do+ root <- parseXMLDoc xml+ case findAnswer . findPropTag $ root of+ "valid" -> return (Output Valid [])+ "invalid" -> return (Output Invalid [])+ s -> err $ "Unrecognized status : " ++ s++ where++ searchForRuntimeError = undefined++ findPropTag root =+ let rightElement elt =+ qName (elName elt) == "Property"+ && lookupAttr (simpleName "name") (elAttribs elt)+ == Just prop+ in case filterChildren rightElement root of+ tag : _ -> tag+ _ -> err $ "Tag for property " ++ prop ++ " not found"++ findAnswer tag =+ case findChildren (simpleName "Answer") tag of+ answTag : _ ->+ case onlyText (elContent answTag) of+ answ : _ -> cdData answ+ _ -> err "Invalid 'Answer' attribute"+ _ -> err "Attribute 'Answer' not found"++ err :: forall a . String -> a+ err msg = Err.fatal $+ "Parse error while reading the Kind2 XML output : \n"+ ++ msg ++ "\n\n" ++ xml++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Kind2/PrettyPrint.hs view
@@ -0,0 +1,69 @@+--------------------------------------------------------------------------------++module Copilot.Theorem.Kind2.PrettyPrint ( prettyPrint ) where++import Copilot.Theorem.Misc.SExpr+import qualified Copilot.Theorem.Misc.SExpr as SExpr+import Copilot.Theorem.Kind2.AST++import Data.List (intercalate)++--------------------------------------------------------------------------------++type SSExpr = SExpr String++kwPrime = "prime"++--------------------------------------------------------------------------------++prettyPrint :: File -> String+prettyPrint =+ intercalate "\n\n"+ . map (SExpr.toString shouldIndent id)+ . ppFile++-- Defines the indentation policy of the S-Expressions+shouldIndent :: SSExpr -> Bool+shouldIndent (Atom _) = False+shouldIndent (List [Atom a, Atom _]) = a `notElem` [kwPrime]+shouldIndent _ = True++--------------------------------------------------------------------------------++ppFile :: File -> [SSExpr]+ppFile (File preds props) = map ppPredDef preds ++ ppProps props++ppProps :: [Prop] -> [SSExpr]+ppProps ps = [ node "check-prop" [ list $ map ppProp ps ] ]++ppProp :: Prop -> SSExpr+ppProp (Prop n t) = list [atom n, ppTerm t]++ppPredDef :: PredDef -> SSExpr+ppPredDef pd =+ list [ atom "define-pred"+ , atom (predId pd)+ , list . map ppStateVarDef . predStateVars $ pd+ , node "init" [ppTerm $ predInit pd]+ , node "trans" [ppTerm $ predTrans pd] ]++ppStateVarDef :: StateVarDef -> SSExpr+ppStateVarDef svd =+ list [atom (varId svd), ppType (varType svd)]++ppType :: Type -> SSExpr+ppType Int = atom "Int"+ppType Real = atom "Real"+ppType Bool = atom "Bool"++ppTerm :: Term -> SSExpr+ppTerm (ValueLiteral c) = atom c+ppTerm (PrimedStateVar v) = list [atom kwPrime, atom v]+ppTerm (StateVar v) = atom v+ppTerm (FunApp f args) = node f $ map ppTerm args+ppTerm (PredApp p t args) = node (p ++ "." ++ ext) $ map ppTerm args+ where ext = case t of+ Init -> "init"+ Trans -> "trans"++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Kind2/Prover.hs view
@@ -0,0 +1,72 @@+--------------------------------------------------------------------------------++{-# LANGUAGE LambdaCase #-}++module Copilot.Theorem.Kind2.Prover+ ( module Data.Default+ , Options (..)+ , kind2Prover+ ) where++import Copilot.Theorem.Prove+import Copilot.Theorem.Kind2.Output+import Copilot.Theorem.Kind2.PrettyPrint+import Copilot.Theorem.Kind2.Translate++-- It seems [IO.openTempFile] doesn't work on Mac OSX+import System.IO hiding (openTempFile)+import Copilot.Theorem.Misc.Utils (openTempFile)++import System.Process++import System.Directory+import Data.Default++import qualified Copilot.Theorem.TransSys as TS++--------------------------------------------------------------------------------++data Options = Options+ { bmcMax :: Int }++instance Default Options where+ def = Options { bmcMax = 0 }++data ProverST = ProverST+ { options :: Options+ , transSys :: TS.TransSys }++kind2Prover :: Options -> Prover+kind2Prover opts = Prover+ { proverName = "Kind2"+ , startProver = return . ProverST opts . TS.translate+ , askProver = askKind2+ , closeProver = const $ return () }++--------------------------------------------------------------------------------++kind2Prog = "kind2"+kind2BaseOptions = ["--input-format", "native", "-xml"]++--------------------------------------------------------------------------------++askKind2 :: ProverST -> [PropId] -> [PropId] -> IO Output+askKind2 (ProverST opts spec) assumptions toCheck = do++ let kind2Input = prettyPrint . toKind2 Inlined assumptions toCheck $ spec++ (tempName, tempHandle) <- openTempFile "." "out" "kind"+ hPutStr tempHandle kind2Input+ hClose tempHandle++ let kind2Options =+ kind2BaseOptions ++ ["--bmc_max", show $ bmcMax opts, tempName]++ (_, output, _) <- readProcessWithExitCode kind2Prog kind2Options ""++ putStrLn kind2Input++ removeFile tempName+ return $ parseOutput (head toCheck) output -- TODO support multiple toCheck props++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Kind2/Translate.hs view
@@ -0,0 +1,212 @@+--------------------------------------------------------------------------------++{-# LANGUAGE RankNTypes, ViewPatterns, NamedFieldPuns, GADTs #-}++module Copilot.Theorem.Kind2.Translate+ ( toKind2+ , Style (..)+ ) where++import Copilot.Theorem.TransSys+import qualified Copilot.Theorem.Kind2.AST as K++import Control.Exception.Base (assert)++import Data.Function (on)+import Data.Maybe (fromJust)++import Data.List (sort, sortBy)+import Data.Map (Map, (!))++import qualified Data.Map as Map+import qualified Data.Bimap as Bimap++--------------------------------------------------------------------------------++{- The following properties MUST hold for the given transition system :+ * Nodes are sorted by topological order+ * Nodes are `completed`, which means the dependency graph is transitive+ and each node imports all the local variables of its dependencies+-}++--------------------------------------------------------------------------------++type DepGraph = Map NodeId [NodeId]++--------------------------------------------------------------------------------++data Style = Inlined | Modular++toKind2 :: Style -> [PropId] -> [PropId] -> TransSys -> K.File+toKind2 style assumptions checkedProps spec =+ addAssumptions spec assumptions+ $ trSpec (complete spec') predCallsGraph assumptions checkedProps+ where predCallsGraph = specDependenciesGraph spec'+ spec' = case style of+ Inlined -> inline spec+ Modular -> removeCycles spec++trSpec :: TransSys -> DepGraph -> [PropId] -> [PropId] -> K.File+trSpec spec predCallsGraph _assumptions checkedProps = K.File preds props+ where preds = map (trNode spec predCallsGraph) (specNodes spec)+ props = map trProp $+ filter ((`elem` checkedProps) . fst) $+ Map.toList (specProps spec)++trProp :: (PropId, ExtVar) -> K.Prop+trProp (pId, var) = K.Prop pId (trVar . extVarLocalPart $ var)++trNode :: TransSys -> DepGraph -> Node -> K.PredDef+trNode spec predCallsGraph node =+ K.PredDef { K.predId, K.predStateVars, K.predInit, K.predTrans }+ where+ predId = nodeId node+ predStateVars = gatherPredStateVars spec node+ predInit = mkConj $ initLocals node+ ++ map (trExpr False) (nodeConstrs node)+ ++ predCalls True spec predCallsGraph node+ predTrans = mkConj $ transLocals node+ ++ map (trExpr True) (nodeConstrs node)+ ++ predCalls False spec predCallsGraph node+++addAssumptions :: TransSys -> [PropId] -> K.File -> K.File+addAssumptions spec assumptions (K.File {K.filePreds, K.fileProps}) =+ K.File (changeTail aux filePreds) fileProps+ where+ changeTail f (reverse -> l) = case l of+ [] -> error "impossible"+ x : xs -> reverse $ f x : xs++ aux pred =+ let init' = mkConj ( K.predInit pred : map K.StateVar vars )+ trans' = mkConj ( K.predTrans pred : map K.PrimedStateVar vars )+ in pred { K.predInit = init', K.predTrans = trans' }++ vars =+ let bindings = nodeImportedVars (specTopNode spec)+ toExtVar a = fromJust $ Map.lookup a (specProps spec)+ toTopVar (ExtVar nId v) = assert (nId == specTopNodeId spec) v+ in map (varName . toTopVar . toExtVar) assumptions++--------------------------------------------------------------------------------++{- The ordering really matters here because the variables+ have to be given in this order in a pred call+ Our convention :+ * First the local variables, sorted by alphabetical order+ * Then the imported variables, by alphabetical order on+ the father node then by alphabetical order on the variable name+-}++gatherPredStateVars :: TransSys -> Node -> [K.StateVarDef]+gatherPredStateVars spec node = locals ++ imported+ where+ nodesMap = Map.fromList [(nodeId n, n) | n <- specNodes spec]+ extVarType :: ExtVar -> K.Type+ extVarType (ExtVar n v) =+ case nodeLocalVars (nodesMap ! n) ! v of+ VarDescr Integer _ -> K.Int+ VarDescr Bool _ -> K.Bool+ VarDescr Real _ -> K.Real++ locals =+ map (\v -> K.StateVarDef (varName v)+ (extVarType $ ExtVar (nodeId node) v) [])+ . sort . Map.keys $ nodeLocalVars node++ imported =+ map (\(v, ev) -> K.StateVarDef (varName v) (extVarType ev) [])+ . sortBy (compare `on` snd) . Bimap.toList $ nodeImportedVars node++--------------------------------------------------------------------------------++mkConj :: [K.Term] -> K.Term+mkConj [] = trConst Bool True+mkConj [x] = x+mkConj xs = K.FunApp "and" xs++mkEquality :: K.Term -> K.Term -> K.Term+mkEquality t1 t2 = K.FunApp "=" [t1, t2]++trVar :: Var -> K.Term+trVar v = K.StateVar (varName v)++trPrimedVar :: Var -> K.Term+trPrimedVar v = K.PrimedStateVar (varName v)++trConst :: Type t -> t -> K.Term+trConst Integer v = K.ValueLiteral (show v)+trConst Real v = K.ValueLiteral (show v)+trConst Bool True = K.ValueLiteral "true"+trConst Bool False = K.ValueLiteral "false"++--------------------------------------------------------------------------------++initLocals :: Node -> [K.Term]+initLocals node =+ concatMap f (Map.toList $ nodeLocalVars node)+ where+ f (v, VarDescr t def) =+ case def of+ Pre c _ -> [mkEquality (trVar v) (trConst t c)]+ Expr e -> [mkEquality (trVar v) (trExpr False e)]+ Constrs cs -> map (trExpr False) cs+++transLocals :: Node -> [K.Term]+transLocals node =+ concatMap f (Map.toList $ nodeLocalVars node)+ where+ f (v, VarDescr _ def) =+ case def of+ Pre _ v' -> [mkEquality (trPrimedVar v) (trVar v')]+ Expr e -> [mkEquality (trPrimedVar v) (trExpr True e)]+ Constrs cs -> map (trExpr True) cs++predCalls :: Bool -> TransSys -> DepGraph -> Node -> [K.Term]+predCalls isInitCall spec predCallsGraph node =+ map mkCall toCall+ where+ nid = nodeId node+ toCall = predCallsGraph ! nid+ nodesMap = Map.fromList [(nodeId n, n) | n <- specNodes spec]++ nodeLocals n =+ map (ExtVar n) . sort . Map.keys+ . nodeLocalVars $ (nodesMap ! n)++ mkCall callee+ | isInitCall =+ K.PredApp callee K.Init (argsSeq trVar)+ | otherwise =+ K.PredApp callee K.Trans (argsSeq trVar ++ argsSeq trPrimedVar)+ where++ calleeLocals = nodeLocals callee+ calleeImported =+ (concatMap nodeLocals . sort . nodeDependencies) $ nodesMap ! callee++ localAlias trVarF ev =+ case Bimap.lookupR ev $ nodeImportedVars node of+ Nothing -> error $+ "This spec is not complete : "+ ++ show ev ++ " should be imported in " ++ nid+ Just v -> trVarF v++ argsSeq trVarF =+ map (localAlias trVarF) (calleeLocals ++ calleeImported)++--------------------------------------------------------------------------------++trExpr :: Bool -> Expr t -> K.Term+trExpr primed = tr+ where+ tr :: forall t . Expr t -> K.Term+ tr (Const t c) = trConst t c+ tr (Ite _ c e1 e2) = K.FunApp "ite" [tr c, tr e1, tr e2]+ tr (Op1 _ op e) = K.FunApp (show op) [tr e]+ tr (Op2 _ op e1 e2) = K.FunApp (show op) [tr e1, tr e2]+ tr (VarE _ v) = if primed then trPrimedVar v else trVar v++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Misc/Error.hs view
@@ -0,0 +1,31 @@+--------------------------------------------------------------------------------++module Copilot.Theorem.Misc.Error+ ( badUse+ , impossible+ , impossible_+ , notHandled+ , fatal+ ) where++--------------------------------------------------------------------------------++errorHeader :: String+errorHeader = "[Copilot-kind ERROR] "++badUse :: String -> a+badUse s = error $ errorHeader ++ s++impossible :: String -> a+impossible s = error $ errorHeader ++ "Unexpected internal error : " ++ s++impossible_ :: a+impossible_ = error $ errorHeader ++ "Unexpected internal error"++notHandled :: String -> a+notHandled s = error $ errorHeader ++ "Not handled : " ++ s++fatal :: String -> a+fatal = error++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Misc/SExpr.hs view
@@ -0,0 +1,81 @@+--------------------------------------------------------------------------------++{-# LANGUAGE FlexibleInstances #-}++module Copilot.Theorem.Misc.SExpr where++import Text.ParserCombinators.Parsec+import Text.PrettyPrint.HughesPJ as PP hiding (char, Str)+import Control.Applicative hiding ((<|>), empty)++import Control.Monad++--------------------------------------------------------------------------------++data SExpr a = Atom a+ | List [SExpr a]++blank = Atom ""+atom = Atom -- s+unit = List [] -- ()+singleton a = List [Atom a] -- (s)+list = List -- (ss)+node a l = List (Atom a : l) -- (s ss)++--------------------------------------------------------------------------------++-- A straightforward string representation++instance Show (SExpr String) where+ show = PP.render . show'+ where+ show' (Atom s) = text s+ show' (List ts) = parens . hsep . map show' $ ts+++-- More advanced printing with some basic indentation++indent = nest 1++toString :: (SExpr a -> Bool) -> (a -> String) -> SExpr a -> String+toString shouldIndent printAtom expr =+ PP.render (toDoc shouldIndent printAtom expr)++toDoc :: (SExpr a -> Bool) -> (a -> String) -> SExpr a -> Doc+toDoc shouldIndent printAtom expr = case expr of+ Atom a -> text (printAtom a)+ List l -> parens (foldl renderItem empty l)++ where renderItem doc s+ | shouldIndent s =+ doc $$ indent (toDoc shouldIndent printAtom s)+ | otherwise =+ doc <+> toDoc shouldIndent printAtom s++--------------------------------------------------------------------------------++parser :: GenParser Char st (SExpr String)+parser =+ choice [try unitP, nodeP, leafP]++ where symbol = oneOf "!#$%&|*+-/:<=>?@^_~."+ lonelyStr = many1 (alphaNum <|> symbol)++ unitP = string "()" >> return unit++ leafP = atom <$> lonelyStr++ nodeP = do void $ char '('+ spaces+ st <- sepBy parser spaces+ spaces+ void $ char ')'+ return $ List st+++parseSExpr :: String -> Maybe (SExpr String)+parseSExpr str = case parse parser "" str of+ Left s -> error (show s) -- Nothing+ Right t -> Just t++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Misc/Utils.hs view
@@ -0,0 +1,60 @@+--------------------------------------------------------------------------------++module Copilot.Theorem.Misc.Utils+ ( isSublistOf, nub', nubBy', nubEq+ , openTempFile+ ) where++--------------------------------------------------------------------------------++import Data.Function (on)+import Data.List (groupBy, sortBy, group, sort)++import Control.Applicative ((<$>))+import Control.Monad++import qualified Data.Set as Set++import System.IO hiding (openTempFile)+import System.Random+import System.Directory++--------------------------------------------------------------------------------++isSublistOf :: Ord a => [a] -> [a] -> Bool+isSublistOf = Set.isSubsetOf `on` Set.fromList++nubEq :: Ord a => [a] -> [a] -> Bool+nubEq = (==) `on` Set.fromList++-- An efficient version of 'nub'+nub' :: Ord a => [a] -> [a]+nub' = map head . group . sort++nubBy' :: (a -> a -> Ordering) -> [a] -> [a]+nubBy' f = map head . groupBy (\x y -> f x y == EQ) . sortBy f++--------------------------------------------------------------------------------++openTempFile :: String -> String -> String -> IO (String, Handle)+openTempFile loc baseName extension = do++ path <- freshPath+ handle <- openFile path WriteMode+ return (path, handle)++ where++ freshPath :: IO FilePath+ freshPath = do+ path <- pathFromSuff <$> randSuff+ exists <- doesFileExist path+ if exists then freshPath else return path++ randSuff :: IO String+ randSuff = replicateM 4 $ randomRIO ('0', '9')++ pathFromSuff :: String -> FilePath+ pathFromSuff suf = loc ++ "/" ++ baseName ++ suf ++ "." ++ extension++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Prove.hs view
@@ -0,0 +1,185 @@+--------------------------------------------------------------------------------++{-# LANGUAGE NamedFieldPuns, ViewPatterns, ExistentialQuantification, GADTs #-}++module Copilot.Theorem.Prove+ ( Output (..)+ , Status (..)+ , Prover (..)+ , PropId, PropRef (..)+ , Proof, UProof, ProofScheme (..)+ , Action (..)+ , Universal, Existential+ , check+ , prove+ ) where++import qualified Copilot.Core as Core++import Data.List (intercalate)+import Control.Applicative (liftA2, Applicative(..))+import Control.Monad (liftM, ap)+import Control.Monad.Writer++--------------------------------------------------------------------------------++data Output = Output Status [String]++data Status = Sat | Valid | Invalid | Unknown | Error++{- Each prover has to provide the following five functions.+ The most important is `askProver`, which takes 3 arguments :+ * The prover descriptor+ * A list of properties names which are assumptions+ * A property name which has to be deduced from these assumptions+-}++data Prover = forall r . Prover+ { proverName :: String+ , startProver :: Core.Spec -> IO r+ , askProver :: r -> [PropId] -> [PropId] -> IO Output+ , closeProver :: r -> IO ()+ }++type PropId = String++data PropRef a where+ PropRef :: PropId -> PropRef a++data Universal+data Existential++type Proof a = ProofScheme a ()++type UProof = Writer [Action] ()++data ProofScheme a b where+ Proof :: Writer [Action] b -> ProofScheme a b++instance Functor (ProofScheme a) where+ fmap = liftM++instance Applicative (ProofScheme a) where+ pure = return+ (<*>) = ap++instance Monad (ProofScheme a) where+ (Proof p) >>= f = Proof $ p >>= (\a -> case f a of Proof p -> p)+ return a = Proof (return a)++data Action where+ Check :: Prover -> Action+ Assume :: PropId -> Action+ Admit :: Action++--------------------------------------------------------------------------------++check :: Prover -> Proof a+check prover = Proof $ tell [Check prover]++prove :: Core.Spec -> PropId -> UProof -> IO Bool+prove spec propId (execWriter -> actions) = do++ thms <- processActions [] actions+ putStr $ "Finished: " ++ propId ++ ": proof "+ if (elem propId thms) then (putStrLn "checked successfully") else (putStrLn "failed")+ return $ elem propId thms++ where+ processActions context [] = return context+ processActions context (action:nextActions) = case action of+ Check (Prover { startProver, askProver, closeProver }) -> do+ prover <- startProver spec+ (Output status infos) <- askProver prover context [propId]+ closeProver prover+ case status of+ Sat -> do+ putStrLn $ propId ++ ": sat " ++ "(" ++ intercalate ", " infos ++ ")"+ processActions (propId : context) nextActions+ Valid -> do+ putStrLn $ propId ++ ": valid " ++ "(" ++ intercalate ", " infos ++ ")"+ processActions (propId : context) nextActions+ Invalid -> do+ putStrLn $ propId ++ ": invalid " ++ "(" ++ intercalate ", " infos ++ ")"+ processActions context nextActions+ Error -> do+ putStrLn $ propId ++ ": error " ++ "(" ++ intercalate ", " infos ++ ")"+ processActions context nextActions+ Unknown -> do+ putStrLn $ propId ++ ": unknown " ++ "(" ++ intercalate ", " infos ++ ")"+ processActions context nextActions++ Assume propId -> do+ putStrLn $ propId ++ ": assumption"+ processActions (propId : context) nextActions++ Admit -> do+ putStrLn $ propId ++ ": admitted"+ processActions (propId : context) nextActions++combine :: Prover -> Prover -> Prover+combine+ (Prover { proverName = proverNameL+ , startProver = startProverL+ , askProver = askProverL+ , closeProver = closeProverL+ })++ (Prover { proverName = proverNameR+ , startProver = startProverR+ , askProver = askProverR+ , closeProver = closeProverR+ })++ = Prover+ { proverName = proverNameL ++ "_" ++ proverNameR+ , startProver = \spec -> do+ proverL <- startProverL spec+ proverR <- startProverR spec+ return (proverL, proverR)++ , askProver = \(stL, stR) assumptions toCheck ->+ liftA2 (combineOutputs proverNameL proverNameR)+ (askProverL stL assumptions toCheck)+ (askProverR stR assumptions toCheck)++ , closeProver = \(stL, stR) -> do+ closeProverL stL+ closeProverR stR+ }++combineOutputs nameL nameR (Output stL msgL) (Output stR msgR) =+ Output (combineSt stL stR) infos++ where+ combineSt Error _ = Error+ combineSt _ Error = Error++ combineSt Valid Invalid = Error+ combineSt Invalid Valid = Error++ combineSt Invalid _ = Invalid+ combineSt _ Invalid = Invalid++ combineSt Valid _ = Valid+ combineSt _ Valid = Valid++ combineSt Sat _ = Sat+ combineSt _ Sat = Sat++ combineSt Unknown Unknown = Unknown++ prefixMsg = case (stL, stR) of+ (Valid, Invalid) -> ["The two provers don't agree"]+ _ -> []++ decoName s = "<" ++ s ++ ">"++ infos =+ prefixMsg+ ++ [decoName nameL]+ ++ msgL+ ++ [decoName nameR]+ ++ msgR++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Prover/Backend.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE RankNTypes #-}++module Copilot.Theorem.Prover.Backend (SmtFormat(..), Backend(..), SatResult(..)) where++import Copilot.Theorem.IL++import System.IO++class Show a => SmtFormat a where+ push :: a+ pop :: a+ checkSat :: a+ setLogic :: String -> a+ declFun :: String -> Type -> [Type] -> a+ assert :: Expr -> a++data Backend a = Backend+ { name :: String+ , cmd :: String+ , cmdOpts :: [String]+ , inputTerminator :: Handle -> IO ()+ , incremental :: Bool+ , logic :: String+ , interpret :: String -> Maybe SatResult+ }++data SatResult = Sat | Unsat | Unknown+
+ src/Copilot/Theorem/Prover/SMT.hs view
@@ -0,0 +1,368 @@+--------------------------------------------------------------------------------++{-# LANGUAGE LambdaCase, NamedFieldPuns, FlexibleInstances, RankNTypes, GADTs #-}++module Copilot.Theorem.Prover.SMT+ ( module Data.Default+ , Options (..)+ , induction, kInduction, onlySat, onlyValidity+ , yices, dReal, altErgo, metit, z3, cvc4, mathsat+ , Backend, SmtFormat+ , SmtLib, Tptp+ ) where++import Copilot.Theorem.IL.Translate+import Copilot.Theorem.IL+import Copilot.Theorem.Prove (Output (..), check, Proof, Universal, Existential)+import qualified Copilot.Theorem.Prove as P++import Copilot.Theorem.Prover.Backend+import qualified Copilot.Theorem.Prover.SMTIO as SMT++import Copilot.Theorem.Prover.SMTLib (SmtLib)+import Copilot.Theorem.Prover.TPTP (Tptp)+import qualified Copilot.Theorem.Prover.SMTLib as SMTLib+import qualified Copilot.Theorem.Prover.TPTP as TPTP++import Control.Applicative ((<$>), (<*))+import Control.Monad (msum, unless, mzero)+import Control.Monad.State (StateT, runStateT, lift, get, modify)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans.Maybe (MaybeT (..))++import Data.Word+import Data.Maybe (fromJust, fromMaybe)+import Data.Function (on)+import Data.Default (Default(..))+import Data.Map (Map)+import qualified Data.Map as Map+import Copilot.Theorem.Misc.Utils++import System.IO (hClose)++--------------------------------------------------------------------------------++-- | Tactics++data Options = Options+ { startK :: Word32+ -- The maximum number of steps of the k-induction algorithm the prover runs+ -- before giving up.+ , maxK :: Word32++ -- If `debug` is set to `True`, the SMTLib/TPTP queries produced by the+ -- prover are displayed in the standard output.+ , debug :: Bool+ }++instance Default Options where+ def = Options+ { startK = 0+ , maxK = 10+ , debug = False+ }++onlySat :: SmtFormat a => Options -> Backend a -> Proof Existential+onlySat opts backend = check P.Prover+ { P.proverName = "OnlySat"+ , P.startProver = return . ProofState opts backend Map.empty . translate+ , P.askProver = onlySat'+ , P.closeProver = const $ return ()+ }++onlyValidity :: SmtFormat a => Options -> Backend a -> Proof Universal+onlyValidity opts backend = check P.Prover+ { P.proverName = "OnlyValidity"+ , P.startProver = return . ProofState opts backend Map.empty . translate+ , P.askProver = onlyValidity'+ , P.closeProver = const $ return ()+ }++induction :: SmtFormat a => Options -> Backend a -> Proof Universal+induction opts backend = check P.Prover+ { P.proverName = "Induction"+ , P.startProver = return . ProofState opts backend Map.empty . translate+ , P.askProver = kInduction' 0 0+ , P.closeProver = const $ return ()+ }++kInduction :: SmtFormat a => Options -> Backend a -> Proof Universal+kInduction opts backend = check P.Prover+ { P.proverName = "K-Induction"+ , P.startProver = return . ProofState opts backend Map.empty . translate+ , P.askProver = kInduction' (startK opts) (maxK opts)+ , P.closeProver = const $ return ()+ }++-------------------------------------------------------------------------------++-- | Backends++yices :: Backend SmtLib+yices = Backend+ { name = "Yices"+ , cmd = "yices-smt2"+ , cmdOpts = ["--incremental"]+ , inputTerminator = const $ return ()+ , incremental = True+ , logic = "QF_NRA"+ , interpret = SMTLib.interpret+ }++cvc4 :: Backend SmtLib+cvc4 = Backend+ { name = "CVC4"+ , cmd = "cvc4"+ , cmdOpts = ["--incremental", "--lang=smt2", "--tlimit-per=5000"]+ , inputTerminator = const $ return ()+ , incremental = True+ , logic = "QF_UFNIRA"+ , interpret = SMTLib.interpret+ }++altErgo :: Backend SmtLib+altErgo = Backend+ { name = "Alt-Ergo"+ , cmd = "alt-ergo.opt"+ , cmdOpts = []+ , inputTerminator = hClose+ , incremental = False+ , logic = "QF_UFNIRA"+ , interpret = SMTLib.interpret+ }++z3 :: Backend SmtLib+z3 = Backend+ { name = "Z3"+ , cmd = "z3"+ , cmdOpts = ["-smt2", "-in"]+ , inputTerminator = const $ return ()+ , incremental = True+ , logic = ""+ , interpret = SMTLib.interpret+ }++dReal :: Backend SmtLib+dReal = Backend+ { name = "dReal"+ , cmd = "perl"+ , cmdOpts = ["-e", "alarm 10; exec dReal"]+ , inputTerminator = hClose+ , incremental = False+ , logic = "QF_NRA"+ , interpret = SMTLib.interpret+ }++mathsat :: Backend SmtLib+mathsat = Backend+ { name = "MathSAT"+ , cmd = "mathsat"+ , cmdOpts = []+ , inputTerminator = const $ return ()+ , incremental = True+ , logic = "QF_NRA"+ , interpret = SMTLib.interpret+ }++-- The argument is the path to the "tptp" subdirectory of the metitarski+-- install location.+metit :: String -> Backend Tptp+metit installDir = Backend+ { name = "MetiTarski"+ , cmd = "metit"+ , cmdOpts =+ [ "--time", "5"+ , "--autoInclude"+ , "--tptp", installDir+ , "/dev/stdin"+ ]+ , inputTerminator = hClose+ , incremental = False+ , logic = ""+ , interpret = TPTP.interpret+ }++-------------------------------------------------------------------------------++-- | Checks the Copilot specification with k-induction++type ProofScript b = MaybeT (StateT (ProofState b) IO)++runPS :: ProofScript b a -> ProofState b -> IO (Maybe a, ProofState b)+runPS ps = runStateT (runMaybeT ps)++data ProofState b = ProofState+ { options :: Options+ , backend :: Backend b+ , solvers :: Map SolverId (SMT.Solver b)+ , spec :: IL+ }++data SolverId = Base | Step+ deriving (Show, Ord, Eq)++getModels :: [PropId] -> [PropId] -> ProofScript b ([Expr], [Expr], [Expr], Bool)+getModels assumptionIds toCheckIds = do+ IL {modelInit, modelRec, properties, inductive} <- spec <$> get+ let (as, as') = selectProps assumptionIds properties+ (as'', toCheck) = selectProps toCheckIds properties+ modelRec' = modelRec ++ as ++ as' ++ as''+ return (modelInit, modelRec', toCheck, inductive)++getSolver :: SmtFormat b => SolverId -> ProofScript b (SMT.Solver b)+getSolver sid = do+ solvers <- solvers <$> get+ case Map.lookup sid solvers of+ Nothing -> startNewSolver sid+ Just solver -> return solver++setSolver :: SolverId -> SMT.Solver b -> ProofScript b ()+setSolver sid solver =+ (lift . modify) $ \s -> s { solvers = Map.insert sid solver (solvers s) }++deleteSolver :: SolverId -> ProofScript b ()+deleteSolver sid =+ (lift . modify) $ \s -> s { solvers = Map.delete sid (solvers s) }++startNewSolver :: SmtFormat b => SolverId -> ProofScript b (SMT.Solver b)+startNewSolver sid = do+ dbg <- (options <$> get >>= return . debug)+ backend <- backend <$> get+ s <- liftIO $ SMT.startNewSolver (show sid) dbg backend+ setSolver sid s+ return s++declVars :: SmtFormat b => SolverId -> [VarDescr] -> ProofScript b ()+declVars sid vs = do+ s <- getSolver sid+ s' <- liftIO $ SMT.declVars s vs+ setSolver sid s'++assume :: SmtFormat b => SolverId -> [Expr] -> ProofScript b ()+assume sid cs = do+ s <- getSolver sid+ s' <- liftIO $ SMT.assume s cs+ setSolver sid s'++entailed :: SmtFormat b => SolverId -> [Expr] -> ProofScript b SatResult+entailed sid cs = do+ backend <- backend <$> get+ s <- getSolver sid+ result <- liftIO $ SMT.entailed s cs+ unless (incremental backend) $ stop sid+ return result++stop :: SmtFormat b => SolverId -> ProofScript b ()+stop sid = do+ s <- getSolver sid+ deleteSolver sid+ liftIO $ SMT.stop s++proofKind :: Integer -> String+proofKind 0 = "induction"+proofKind k = "k-induction (k = " ++ show k ++ ")"++stopSolvers :: SmtFormat b => ProofScript b ()+stopSolvers = do+ solvers <- solvers <$> get+ mapM_ stop (fst <$> Map.toList solvers)++entailment :: SmtFormat b => SolverId -> [Expr] -> [Expr] -> ProofScript b SatResult+entailment sid assumptions props = do+ declVars sid $ nub' $ getVars assumptions ++ getVars props+ assume sid assumptions+ entailed sid props++getVars :: [Expr] -> [VarDescr]+getVars = nubBy' (compare `on` varName) . concatMap getVars'+ where getVars' :: Expr -> [VarDescr]+ getVars' = \case+ ConstB _ -> []+ ConstI _ _ -> []+ ConstR _ -> []+ Ite _ e1 e2 e3 -> getVars' e1 ++ getVars' e2 ++ getVars' e3+ Op1 _ _ e -> getVars' e+ Op2 _ _ e1 e2 -> getVars' e1 ++ getVars' e2+ SVal t seq (Fixed i) -> [VarDescr (seq ++ "_" ++ show i) t []]+ SVal t seq (Var i) -> [VarDescr (seq ++ "_n" ++ show i) t []]+ FunApp t name args -> [VarDescr name t (map typeOf args)]+ ++ concatMap getVars' args++unknown :: ProofScript b a+unknown = mzero++unknown' :: String -> ProofScript b Output+unknown' msg = return $ Output P.Unknown [msg]++invalid :: String -> ProofScript b Output+invalid msg = return $ Output P.Invalid [msg]++sat :: String -> ProofScript b Output+sat msg = return $ Output P.Sat [msg]++valid :: String -> ProofScript b Output+valid msg = return $ Output P.Valid [msg]++kInduction' :: SmtFormat b => Word32 -> Word32 -> ProofState b -> [PropId] -> [PropId] -> IO Output+kInduction' startK maxK s as ps = (fromMaybe (Output P.Unknown ["proof by k-induction failed"]) . fst)+ <$> runPS (msum (map induction [(toInteger startK) .. (toInteger maxK)]) <* stopSolvers) s+ where+ induction k = do+ (modelInit, modelRec, toCheck, inductive) <- getModels as ps++ let base = [evalAt (Fixed i) m | m <- modelRec, i <- [0 .. k]]+ baseInv = [evalAt (Fixed k) m | m <- toCheck]++ let step = [evalAt (_n_plus i) m | m <- modelRec, i <- [0 .. k + 1]]+ ++ [evalAt (_n_plus i) m | m <- toCheck, i <- [0 .. k]]+ stepInv = [evalAt (_n_plus $ k + 1) m | m <- toCheck]++ entailment Base (modelInit ++ base) baseInv >>= \case+ Sat -> invalid $ "base case failed for " ++ proofKind k+ Unknown -> unknown+ Unsat ->+ if not inductive then valid ("proved without induction")+ else entailment Step step stepInv >>= \case+ Sat -> unknown+ Unknown -> unknown+ Unsat -> valid $ "proved with " ++ proofKind k+++onlySat' :: SmtFormat b => ProofState b -> [PropId] -> [PropId] -> IO Output+onlySat' s as ps = (fromJust . fst) <$> runPS (script <* stopSolvers) s+ where+ script = do+ (modelInit, modelRec, toCheck, inductive) <- getModels as ps++ let base = map (evalAt (Fixed 0)) modelRec+ baseInv = map (evalAt (Fixed 0)) toCheck++ if inductive+ then unknown' "proposition requires induction to prove."+ else entailment Base (modelInit ++ base) (map (Op1 Bool Not) baseInv) >>= \case+ Unsat -> invalid "prop not satisfiable"+ Unknown -> unknown' "failed to find a satisfying model"+ Sat -> sat "prop is satisfiable"++onlyValidity' :: SmtFormat b => ProofState b -> [PropId] -> [PropId] -> IO Output+onlyValidity' s as ps = (fromJust . fst) <$> runPS (script <* stopSolvers) s+ where+ script = do+ (modelInit, modelRec, toCheck, inductive) <- getModels as ps++ let base = map (evalAt (Fixed 0)) modelRec+ baseInv = map (evalAt (Fixed 0)) toCheck++ if inductive+ then unknown' "proposition requires induction to prove."+ else entailment Base (modelInit ++ base) baseInv >>= \case+ Unsat -> valid "proof by SMT solver"+ Unknown -> unknown+ Sat -> invalid "SMT solver found a counter-example."++selectProps :: [PropId] -> Map PropId ([Expr], Expr) -> ([Expr], [Expr])+selectProps propIds properties =+ (squash . unzip) [(as, p) | (id, (as, p)) <- Map.toList properties, id `elem` propIds]+ where squash (a, b) = (concat a, b)++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Prover/SMTIO.hs view
@@ -0,0 +1,108 @@+--------------------------------------------------------------------------------++{-# LANGUAGE LambdaCase, NamedFieldPuns, RankNTypes, ViewPatterns #-}++module Copilot.Theorem.Prover.SMTIO+ ( Solver+ , startNewSolver, assume, entailed, stop, declVars+ ) where++import Copilot.Theorem.IL+import Copilot.Theorem.Prover.Backend++import System.IO+import System.Process+import Control.Monad+import Control.Monad.Trans+import Control.Monad.Trans.Maybe+import Control.Applicative ((<$>))+import Data.Maybe+import Data.Set ((\\), fromList, Set, union, empty, elems)++--------------------------------------------------------------------------------++data Solver a = Solver+ { solverName :: String+ , inh :: Handle+ , outh :: Handle+ , process :: ProcessHandle+ , debugMode :: Bool+ , vars :: Set VarDescr+ , model :: Set Expr+ , backend :: Backend a+ }++--------------------------------------------------------------------------------++debug :: Bool -> Solver a -> String -> IO ()+debug printName s str = when (debugMode s) $+ putStrLn $ (if printName then "<" ++ solverName s ++ "> " else "") ++ str++send :: Show a => Solver a -> a -> IO ()+send _ (show -> "") = return ()+send s (show -> a) = do+ hPutStr (inh s) $ a ++ "\n"+ debug True s a+ hFlush $ inh s++receive :: Solver a -> IO SatResult+receive s = fromJust <$> runMaybeT (msum $ repeat line)+ where+ line :: MaybeT IO SatResult+ line = do+ eof <- liftIO $ hIsEOF $ outh s+ if eof+ then liftIO (debug True s "[received: EOF]") >> return Unknown+ else do+ ln <- liftIO $ hGetLine $ outh s+ liftIO $ debug True s $ "[received: " ++ ln ++ "]"+ MaybeT $ return $ (interpret $ backend s) ln++--------------------------------------------------------------------------------++startNewSolver :: SmtFormat a => String -> Bool -> Backend a -> IO (Solver a)+startNewSolver name dbgMode b = do+ (i, o, e, p) <- runInteractiveProcess (cmd b) (cmdOpts b) Nothing Nothing+ hClose e+ let s = Solver name i o p dbgMode empty empty b+ send s $ setLogic $ logic b+ return s++stop :: Solver a -> IO ()+stop s = do+ hClose $ inh s+ hClose $ outh s+ terminateProcess $ process s++--------------------------------------------------------------------------------++assume :: SmtFormat a => Solver a -> [Expr] -> IO (Solver a)+assume s@(Solver { model }) cs = do+ let newAxioms = elems $ fromList cs \\ model+ assume' s newAxioms+ return s { model = model `union` fromList newAxioms }++assume' :: SmtFormat a => Solver a -> [Expr] -> IO ()+assume' s cs = forM_ cs (send s . assert . bsimpl)++entailed :: SmtFormat a => Solver a -> [Expr] -> IO SatResult+entailed s cs = do+ when (incremental $ backend s) $ send s push+ case cs of+ [] -> putStrLn "Warning: no proposition to prove." >> assume' s [ConstB True]+ _ -> assume' s [foldl1 (Op2 Bool Or) (map (Op1 Bool Not) cs)]+ send s checkSat+ (inputTerminator $ backend s) (inh s)++ when (incremental $ backend s) $ send s pop+ receive s++declVars :: SmtFormat a => Solver a -> [VarDescr] -> IO (Solver a)+declVars s@(Solver { vars }) decls = do+ let newVars = elems $ fromList decls \\ vars+ forM_ newVars $ \(VarDescr {varName, varType, args}) ->+ send s $ declFun varName varType args+ return s { vars = vars `union` fromList newVars }++--------------------------------------------------------------------------------+
+ src/Copilot/Theorem/Prover/SMTLib.hs view
@@ -0,0 +1,100 @@+--------------------------------------------------------------------------------++{-# LANGUAGE GADTs, FlexibleInstances #-}++module Copilot.Theorem.Prover.SMTLib (SmtLib, interpret) where++import Copilot.Theorem.Prover.Backend (SmtFormat (..), SatResult (..))++import Copilot.Theorem.IL+import Copilot.Theorem.Misc.SExpr++import Text.Printf++--------------------------------------------------------------------------------++newtype SmtLib = SmtLib (SExpr String)++instance Show SmtLib where+ show (SmtLib s) = show s++smtTy :: Type -> String+smtTy Bool = "Bool"+smtTy Real = "Real"+smtTy _ = "Int"++--------------------------------------------------------------------------------++instance SmtFormat SmtLib where+ push = SmtLib $ node "push" [atom "1"]+ pop = SmtLib $ node "pop" [atom "1"]+ checkSat = SmtLib $ singleton "check-sat"+ setLogic "" = SmtLib $ blank+ setLogic l = SmtLib $ node "set-logic" [atom l]+ declFun name retTy args = SmtLib $+ node "declare-fun" [atom name, (list $ map (atom . smtTy) args), atom (smtTy retTy)]+ assert c = SmtLib $ node "assert" [expr c]++interpret :: String -> Maybe SatResult+interpret "sat" = Just Sat+interpret "unsat" = Just Unsat+interpret _ = Just Unknown++--------------------------------------------------------------------------------++expr :: Expr -> SExpr String++expr (ConstB v) = atom $ if v then "true" else "false"+expr (ConstI _ v) = atom $ show v+expr (ConstR v) = atom $ printf "%f" v++expr (Ite _ cond e1 e2) = node "ite" [expr cond, expr e1, expr e2]++expr (FunApp _ funName args) = node funName $ map expr args++expr (Op1 _ op e) =+ node smtOp [expr e]+ where+ smtOp = case op of+ Not -> "not"+ Neg -> "-"+ Abs -> "abs"+ Exp -> "exp"+ Sqrt -> "sqrt"+ Log -> "log"+ Sin -> "sin"+ Tan -> "tan"+ Cos -> "cos"+ Asin -> "asin"+ Atan -> "atan"+ Acos -> "acos"+ Sinh -> "sinh"+ Tanh -> "tanh"+ Cosh -> "cosh"+ Asinh -> "asinh"+ Atanh -> "atanh"+ Acosh -> "acosh"++expr (Op2 _ op e1 e2) =+ node smtOp [expr e1, expr e2]+ where+ smtOp = case op of+ Eq -> "="+ Le -> "<="+ Lt -> "<"+ Ge -> ">="+ Gt -> ">"+ And -> "and"+ Or -> "or"+ Add -> "+"+ Sub -> "-"+ Mul -> "*"+ Mod -> "mod"+ Fdiv -> "/"+ Pow -> "^"++expr (SVal _ f ix) = atom $ case ix of+ Fixed i -> f ++ "_" ++ show i+ Var off -> f ++ "_n" ++ show off++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Prover/TPTP.hs view
@@ -0,0 +1,105 @@+--------------------------------------------------------------------------------++{-# LANGUAGE GADTs, LambdaCase #-}++module Copilot.Theorem.Prover.TPTP (Tptp, interpret) where++import Copilot.Theorem.Prover.Backend (SmtFormat (..), SatResult (..))+import Copilot.Theorem.IL++import Data.List++--------------------------------------------------------------------------------++data Tptp = Ax TptpExpr | Null++data TptpExpr = Bin TptpExpr String TptpExpr | Un String TptpExpr+ | Atom String | Fun String [TptpExpr]++instance Show Tptp where+ show (Ax e) = "fof(formula, axiom, " ++ show e ++ ")."+ show Null = ""++instance Show TptpExpr where+ show (Bin e1 op e2) = "(" ++ show e1 ++ " " ++ op ++ " " ++ show e2 ++ ")"+ show (Un op e) = "(" ++ op ++ " " ++ show e ++ ")"+ show (Atom atom) = atom+ show (Fun name args) = name ++ "(" ++ intercalate ", " (map show args) ++ ")"++--------------------------------------------------------------------------------++instance SmtFormat Tptp where+ push = Null+ pop = Null+ checkSat = Null+ setLogic = const Null+ declFun = const $ const $ const Null+ assert c = Ax $ expr c++interpret :: String -> Maybe SatResult+interpret str+ | "SZS status Unsatisfiable" `isPrefixOf` str = Just Unsat+ | "SZS status" `isPrefixOf` str = Just Unknown+ | otherwise = Nothing++--------------------------------------------------------------------------------++expr :: Expr -> TptpExpr+expr = \case+ ConstB v -> Atom $ if v then "$true" else "$false"+ ConstR v -> Atom $ show v+ ConstI _ v -> Atom $ show v++ Ite _ c e1 e2 -> Bin (Bin (expr c) "=>" (expr e1))+ "&" (Bin (Un "~" (expr c)) "=>" (expr e2))++ FunApp _ f args -> Fun f $ map expr args++ Op1 _ Not e -> Un (showOp1 Not) $ expr e+ Op1 _ Neg e -> Un (showOp1 Neg) $ expr e+ Op1 _ op e -> Fun (showOp1 op) [expr e]++ Op2 _ op e1 e2 -> Bin (expr e1) (showOp2 op) (expr e2)++ SVal _ f ix -> case ix of+ Fixed i -> Atom $ f ++ "_" ++ show i+ Var off -> Atom $ f ++ "_n" ++ show off++showOp1 :: Op1 -> String+showOp1 = \case+ Not -> "~"+ Neg -> "-"+ Abs -> "abs"+ Exp -> "exp"+ Sqrt -> "sqrt"+ Log -> "log"+ Sin -> "sin"+ Tan -> "tan"+ Cos -> "cos"+ Asin -> "arcsin"+ Atan -> "arctan"+ Acos -> "arccos"+ Sinh -> "sinh"+ Tanh -> "tanh"+ Cosh -> "cosh"+ Asinh -> "arcsinh"+ Atanh -> "arctanh"+ Acosh -> "arccosh"++showOp2 :: Op2 -> String+showOp2 = \case+ Eq -> "="+ Le -> "<="+ Lt -> "<"+ Ge -> ">="+ Gt -> ">"+ And -> "&"+ Or -> "|"+ Add -> "+"+ Sub -> "-"+ Mul -> "*"+ Mod -> "mod"+ Fdiv -> "/"+ Pow -> "^"++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/Prover/Z3.hs view
@@ -0,0 +1,608 @@+--------------------------------------------------------------------------------++{-# LANGUAGE LambdaCase, NamedFieldPuns, FlexibleInstances, RankNTypes, GADTs,+ MultiParamTypeClasses, FlexibleContexts #-}++module Copilot.Theorem.Prover.Z3+ ( module Data.Default+ , Options (..)+ , induction, kInduction, onlySat, onlyValidity+ )+where++import Copilot.Theorem.IL.Translate+import Copilot.Theorem.IL+import Copilot.Theorem.Prove (Output (..), check, Proof, Universal, Existential)+import qualified Copilot.Theorem.Prove as P++import Control.Applicative ((<$>), (<*))+import Control.Monad (msum, mzero, when, void, unless)+import Control.Monad.State (StateT, runStateT, get, modify)+import Control.Monad.Trans.Maybe (MaybeT (..))++import Data.Word+import Data.Unit+import Data.Maybe (fromJust, fromMaybe)+import Data.Default (Default(..))+import Data.List (foldl')++import Data.Set (Set, (\\), union)+import qualified Data.Set as Set+import Data.Map (Map)+import qualified Data.Map as Map++import Language.SMTLib2+import Language.SMTLib2.Pipe+import Language.SMTLib2.Connection+import Language.SMTLib2.Strategy++import Language.SMTLib2.Internals hiding (Var)++import System.Console.ANSI+import System.IO+import Control.Monad.Trans++--------------------------------------------------------------------------------++-- | Tactics++data Options = Options+ { nraNLSat :: Bool+ , startK :: Word32+ -- The maximum number of steps of the k-induction algorithm the prover runs+ -- before giving up.+ , maxK :: Word32++ -- If `debug` is set to `True`, the SMTLib/TPTP queries produced by the+ -- prover are displayed in the standard output.+ , debug :: Bool+ }++instance Default Options where+ def = Options+ { nraNLSat = True+ , startK = 0+ , maxK = 10+ , debug = False+ }++onlySat :: Options -> Proof Existential+onlySat opts = check P.Prover+ { P.proverName = "OnlySat"+ , P.startProver = return . ProofState opts Map.empty Map.empty Map.empty . translate+ , P.askProver = onlySat'+ , P.closeProver = const $ return ()+ }++onlyValidity :: Options -> Proof Universal+onlyValidity opts = check P.Prover+ { P.proverName = "OnlyValidity"+ , P.startProver = return . ProofState opts Map.empty Map.empty Map.empty . translate+ , P.askProver = onlyValidity'+ , P.closeProver = const $ return ()+ }++induction :: Options -> Proof Universal+induction opts = check P.Prover+ { P.proverName = "Induction"+ , P.startProver = return . ProofState opts Map.empty Map.empty Map.empty . translate+ , P.askProver = kInduction' 0 0+ , P.closeProver = const $ return ()+ }++kInduction :: Options -> Proof Universal+kInduction opts = check P.Prover+ { P.proverName = "K-Induction"+ , P.startProver = return . ProofState opts Map.empty Map.empty Map.empty . translate+ , P.askProver = kInduction' (startK opts) (maxK opts)+ , P.closeProver = const $ return ()+ }++-------------------------------------------------------------------------------++-- | Checks the Copilot specification with k-induction++type Solver = SMTConnection (DebugBackend SMTPipe)++type ProofScript = MaybeT (StateT ProofState IO)++runPS :: ProofScript a -> ProofState -> IO (Maybe a, ProofState)+runPS ps = runStateT (runMaybeT ps)++data ProofState = ProofState+ { options :: Options+ , solvers :: Map SolverId Solver+ , vars :: Map SolverId TransState+ , assumps :: Map SolverId (Set Expr)+ , spec :: IL+ }++data SolverId = Base | Step+ deriving (Show, Ord, Eq)++getModels :: [PropId] -> [PropId] -> ProofScript ([Expr], [Expr], [Expr], Bool)+getModels assumptionIds toCheckIds = do+ IL {modelInit, modelRec, properties, inductive} <- spec <$> get+ let (as, as') = selectProps assumptionIds properties+ (as'', toCheck) = selectProps toCheckIds properties+ modelRec' = modelRec ++ as ++ as' ++ as''+ return (modelInit, modelRec', toCheck, inductive)++getSolver :: SolverId -> ProofScript Solver+getSolver sid = do+ solvers <- solvers <$> get+ case Map.lookup sid solvers of+ Nothing -> startNewSolver sid+ Just solver -> return solver++setSolver :: SolverId -> Solver -> ProofScript ()+setSolver sid solver =+ (lift . modify) $ \s -> s { solvers = Map.insert sid solver (solvers s) }++getVars :: SolverId -> ProofScript TransState+getVars sid = do+ vars <- vars <$> get+ return $ case Map.lookup sid vars of+ Nothing -> noVars+ Just vs -> vs++setVars :: SolverId -> TransState -> ProofScript ()+setVars sid vs =+ (lift . modify) $ \s -> s { vars = Map.insert sid vs (vars s) }++newAssumps :: SolverId -> [Expr] -> ProofScript [Expr]+newAssumps sid as' = do+ assumps <- assumps <$> get+ case Map.lookup sid assumps of+ Nothing -> do+ modify $ \s -> s { assumps = Map.insert sid (Set.fromList as') assumps }+ return as'+ Just as -> do+ let as'' = (Set.fromList as') `union` as+ modify $ \s -> s { assumps = Map.insert sid as'' assumps }+ return $ Set.elems $ (Set.fromList as') \\ as++deleteSolver :: SolverId -> ProofScript ()+deleteSolver sid =+ (lift . modify) $ \s -> s { solvers = Map.delete sid (solvers s) }++startNewSolver :: SolverId -> ProofScript Solver+startNewSolver sid = do+ pipe <- liftIO $ createSMTPipe "z3" ["-smt2", "-in"]+ dbg <- (options <$> get >>= return . debug)+ s <- liftIO $ open (namedDebugBackend (show sid) (not dbg) pipe)+ setSolver sid s+ return s++stop :: SolverId -> ProofScript ()+stop sid = do+ s <- getSolver sid+ deleteSolver sid+ liftIO $ close s++stopSolvers :: ProofScript ()+stopSolvers = do+ solvers <- solvers <$> get+ mapM_ stop (fst <$> Map.toList solvers)++proofKind :: Integer -> String+proofKind 0 = "induction"+proofKind k = "k-induction (k = " ++ show k ++ ")"++entailment :: SolverId -> [Expr] -> [Expr] -> ProofScript CheckSatResult+entailment sid assumptions props = do+ s <- getSolver sid+ liftIO $ performSMT s $ setOption (ProduceModels True)+ -- liftIO $ performSMT s $ setOption (ProduceProofs True)+ -- liftIO $ performSMT s $ setOption (ProduceUnsatCores True)+ vs <- getVars sid+ assumps' <- newAssumps sid assumptions+ (_, vs') <- liftIO $ performSMT s $ runStateT (mapM_ (\e -> transB e >>= lift . assert) assumps') vs+ setVars sid vs'+ liftIO $ performSMT s $ push+ _ <- liftIO $ performSMT s $ runStateT+ (transB (bsimpl (foldl' (Op2 Bool Or) (ConstB False) $ map (Op1 Bool Not) props)) >>= lift . assert) vs'++ nraNL <- (options <$> get >>= return . nraNLSat)+ res <- if nraNL+ then liftIO $ performSMT s $ checkSat' (Just (UsingParams (CustomTactic "qfnra-nlsat") []))+ (CheckSatLimits (Just 5000) Nothing)+ else liftIO $ performSMT s $ checkSat' Nothing (CheckSatLimits (Just 5000) Nothing)++ when (res == Sat) $ void $ liftIO $ performSMT s $ getModel+ -- when (res == Unsat) $ void $ liftIO $ performSMT s $ getProof+ liftIO $ performSMT s $ pop+ -- liftIO $ print model+ return res++unknown :: ProofScript a+unknown = mzero++unknown' :: String -> ProofScript Output+unknown' msg = return $ Output P.Unknown [msg]++invalid :: String -> ProofScript Output+invalid msg = return $ Output P.Invalid [msg]++sat :: String -> ProofScript Output+sat msg = return $ Output P.Sat [msg]++valid :: String -> ProofScript Output+valid msg = return $ Output P.Valid [msg]++kInduction' :: Word32 -> Word32 -> ProofState -> [PropId] -> [PropId] -> IO Output+kInduction' startK maxK s as ps = (fromMaybe (Output P.Unknown ["proof by k-induction failed"]) . fst)+ <$> runPS (msum (map induction [(toInteger startK) .. (toInteger maxK)]) <* stopSolvers) s+ where+ induction k = do+ (modelInit, modelRec, toCheck, inductive) <- getModels as ps++ let base = [evalAt (Fixed i) m | m <- modelRec, i <- [0 .. k]]+ baseInv = [evalAt (Fixed k) m | m <- toCheck]++ let step = [evalAt (_n_plus i) m | m <- modelRec, i <- [0 .. k + 1]]+ ++ [evalAt (_n_plus i) m | m <- toCheck, i <- [0 .. k]]+ stepInv = [evalAt (_n_plus $ k + 1) m | m <- toCheck]++ entailment Base (modelInit ++ base) baseInv >>= \case+ Sat -> invalid $ "base case failed for " ++ proofKind k+ Unknown -> unknown+ Unsat ->+ if not inductive then valid ("proved without induction")+ else entailment Step step stepInv >>= \case+ Sat -> unknown+ Unknown -> unknown+ Unsat -> valid $ "proved with " ++ proofKind k++onlySat' :: ProofState -> [PropId] -> [PropId] -> IO Output+onlySat' s as ps = (fromJust . fst) <$> runPS (script <* stopSolvers) s+ where+ script = do+ (modelInit, modelRec, toCheck, inductive) <- getModels as ps++ let base = map (evalAt (Fixed 0)) modelRec+ baseInv = map (evalAt (Fixed 0)) toCheck++ if inductive+ then unknown' "proposition requires induction to prove."+ else entailment Base (modelInit ++ base) (map (Op1 Bool Not) baseInv) >>= \case+ Unsat -> invalid "prop not satisfiable"+ Unknown -> unknown' "failed to find a satisfying model"+ Sat -> sat "prop is satisfiable"++onlyValidity' :: ProofState -> [PropId] -> [PropId] -> IO Output+onlyValidity' s as ps = (fromJust . fst) <$> runPS (script <* stopSolvers) s+ where+ script = do+ (modelInit, modelRec, toCheck, inductive) <- getModels as ps++ let base = map (evalAt (Fixed 0)) modelRec+ baseInv = map (evalAt (Fixed 0)) toCheck++ if inductive+ then unknown' "proposition requires induction to prove."+ else entailment Base (modelInit ++ base) baseInv >>= \case+ Unsat -> valid "proof by Z3"+ Unknown -> unknown+ Sat -> invalid "Z3 found a counter-example."++selectProps :: [PropId] -> Map PropId ([Expr], Expr) -> ([Expr], [Expr])+selectProps propIds properties =+ (squash . unzip) [(as, p) | (id, (as, p)) <- Map.toList properties, id `elem` propIds]+ where squash (a, b) = (concat a, b)++--------------------------------------------------------------------------------++-- | This is all very ugly. It might make better sense to go straight from Core to SMTExpr, or maybe use SBV instead.++type Trans = StateT TransState SMT++data TransState = TransState+ { boolVars :: Map String (SMTExpr Bool)+ , bv8Vars :: Map String (SMTExpr BV8)+ , bv16Vars :: Map String (SMTExpr BV16)+ , bv32Vars :: Map String (SMTExpr BV32)+ , bv64Vars :: Map String (SMTExpr BV64)+ , ratVars :: Map String (SMTExpr Rational)+ }++noVars = TransState Map.empty Map.empty Map.empty Map.empty Map.empty Map.empty++getBoolVar :: String -> Trans (SMTExpr Bool)+getBoolVar = getVar boolVars (\v s -> s {boolVars = v})++getBV8Var :: String -> Trans (SMTExpr BV8)+getBV8Var = getVar bv8Vars (\v s -> s {bv8Vars = v})++getBV16Var :: String -> Trans (SMTExpr BV16)+getBV16Var = getVar bv16Vars (\v s -> s {bv16Vars = v})++getBV32Var :: String -> Trans (SMTExpr BV32)+getBV32Var = getVar bv32Vars (\v s -> s {bv32Vars = v})++getBV64Var :: String -> Trans (SMTExpr BV64)+getBV64Var = getVar bv64Vars (\v s -> s {bv64Vars = v})++getRatVar :: String -> Trans (SMTExpr Rational)+getRatVar = getVar ratVars (\v s -> s {ratVars = v})++getVar proj mod v = do+ vs <- proj <$> get+ case Map.lookup v vs of+ Nothing -> do+ newVar <- lift $ varNamed v+ modify $ mod $ Map.insert v newVar vs+ return newVar+ Just x -> return x++transB :: Expr -> Trans (SMTExpr Bool)+transB = \case+ ConstB b -> return $ constant b++ Ite _ c e1 e2 -> do+ c' <- transB c+ trans2B e1 e2 (ite c')++ Op1 _ Not e -> transB e >>= return . not'++ Op2 _ And e1 e2 -> do+ e1' <- transB e1+ e2' <- transB e2+ return $ e1' .&&. e2'+ Op2 _ Or e1 e2 -> do+ e1' <- transB e1+ e2' <- transB e2+ return $ e1' .||. e2'++ Op2 _ Eq e1 e2 -> case typeOf e1 of+ Bool -> trans2B e1 e2 (.==.)+ Real -> trans2R e1 e2 (.==.)+ BV8 -> trans2BV8 e1 e2 (.==.)+ BV16 -> trans2BV16 e1 e2 (.==.)+ BV32 -> trans2BV32 e1 e2 (.==.)+ BV64 -> trans2BV64 e1 e2 (.==.)+ SBV8 -> trans2BV8 e1 e2 (.==.)+ SBV16 -> trans2BV16 e1 e2 (.==.)+ SBV32 -> trans2BV32 e1 e2 (.==.)+ SBV64 -> trans2BV64 e1 e2 (.==.)++ Op2 _ Le e1 e2 -> case typeOf e1 of+ Real -> trans2R e1 e2 (.<=.)+ BV8 -> trans2BV8 e1 e2 bvule+ BV16 -> trans2BV16 e1 e2 bvule+ BV32 -> trans2BV32 e1 e2 bvule+ BV64 -> trans2BV64 e1 e2 bvule+ SBV8 -> trans2BV8 e1 e2 bvsle+ SBV16 -> trans2BV16 e1 e2 bvsle+ SBV32 -> trans2BV32 e1 e2 bvsle+ SBV64 -> trans2BV64 e1 e2 bvsle+ _ -> undefined+ Op2 _ Ge e1 e2 -> case typeOf e1 of+ Real -> trans2R e1 e2 (.>=.)+ BV8 -> trans2BV8 e1 e2 bvuge+ BV16 -> trans2BV16 e1 e2 bvuge+ BV32 -> trans2BV32 e1 e2 bvuge+ BV64 -> trans2BV64 e1 e2 bvuge+ SBV8 -> trans2BV8 e1 e2 bvsge+ SBV16 -> trans2BV16 e1 e2 bvsge+ SBV32 -> trans2BV32 e1 e2 bvsge+ SBV64 -> trans2BV64 e1 e2 bvsge+ _ -> undefined+ Op2 _ Lt e1 e2 -> case typeOf e1 of+ Real -> trans2R e1 e2 (.<.)+ BV8 -> trans2BV8 e1 e2 bvult+ BV16 -> trans2BV16 e1 e2 bvult+ BV32 -> trans2BV32 e1 e2 bvult+ BV64 -> trans2BV64 e1 e2 bvult+ SBV8 -> trans2BV8 e1 e2 bvslt+ SBV16 -> trans2BV16 e1 e2 bvslt+ SBV32 -> trans2BV32 e1 e2 bvslt+ SBV64 -> trans2BV64 e1 e2 bvslt+ _ -> undefined+ Op2 _ Gt e1 e2 -> case typeOf e1 of+ Real -> trans2R e1 e2 (.>.)+ BV8 -> trans2BV8 e1 e2 bvugt+ BV16 -> trans2BV16 e1 e2 bvugt+ BV32 -> trans2BV32 e1 e2 bvugt+ BV64 -> trans2BV64 e1 e2 bvugt+ SBV8 -> trans2BV8 e1 e2 bvsgt+ SBV16 -> trans2BV16 e1 e2 bvsgt+ SBV32 -> trans2BV32 e1 e2 bvsgt+ SBV64 -> trans2BV64 e1 e2 bvsgt+ _ -> undefined++ SVal _ s i -> getBoolVar $ ncVar s i++ e -> error $ "Encountered unhandled expression (Bool): " ++ show e++ncVar s (Fixed i) = s ++ "_" ++ show i+ncVar s (Var i) = s ++ "_n" ++ show i++transR :: Expr -> Trans (SMTExpr Rational)+transR = \case+ ConstR n -> return $ constant $ toRational n+ Ite _ c e1 e2 -> do+ c' <- transB c+ trans2R e1 e2 (ite c')++ Op1 _ Neg e -> transR e >>= return . (app neg)+ Op1 _ Abs e -> transR e >>= return . (app SMTAbs)++ Op2 _ Add e1 e2 -> trans2R e1 e2 $ \x y -> app plus [x, y]+ Op2 _ Sub e1 e2 -> trans2R e1 e2 $ \x y -> app minus (x, y)+ Op2 _ Mul e1 e2 -> trans2R e1 e2 $ \x y -> app mult [x, y]+ Op2 _ Fdiv e1 e2 -> trans2R e1 e2 divide++ Op2 _ Pow e1 e2 -> do+ let pow = SMTBuiltIn "^" unit :: SMTFunction (SMTExpr Rational, SMTExpr Rational) Rational+ trans2R e1 e2 $ \x y -> app pow (x, y)++ SVal _ s i -> getRatVar $ ncVar s i++ e -> error $ "Encountered unhandled expression (Rat): " ++ show e++-- TODO(chathhorn): bleghh+transBV8 :: Expr -> Trans (SMTExpr BV8)+transBV8 = \case+ ConstI _ n -> return $ constant $ BitVector n+ Ite _ c e1 e2 -> do+ c' <- transB c+ trans2BV8 e1 e2 (ite c')++ Op1 _ Abs e -> transBV8 e >>= return . abs+ Op1 _ Neg e -> transBV8 e >>= return . negate+ Op2 _ Add e1 e2 -> trans2BV8 e1 e2 (+)+ Op2 _ Sub e1 e2 -> trans2BV8 e1 e2 (-)+ Op2 _ Mul e1 e2 -> trans2BV8 e1 e2 (*)+ SVal _ s i -> getBV8Var $ ncVar s i++ e -> error $ "Encountered unhandled expression (BV8): " ++ show e++transBV16 :: Expr -> Trans (SMTExpr BV16)+transBV16 = \case+ ConstI _ n -> return $ constant $ BitVector n+ Ite _ c e1 e2 -> do+ c' <- transB c+ trans2BV16 e1 e2 (ite c')++ Op1 _ Abs e -> transBV16 e >>= return . abs+ Op1 _ Neg e -> transBV16 e >>= return . negate+ Op2 _ Add e1 e2 -> trans2BV16 e1 e2 (+)+ Op2 _ Sub e1 e2 -> trans2BV16 e1 e2 (-)+ Op2 _ Mul e1 e2 -> trans2BV16 e1 e2 (*)+ SVal _ s i -> getBV16Var $ ncVar s i++ e -> error $ "Encountered unhandled expression (BV16): " ++ show e++transBV32 :: Expr -> Trans (SMTExpr BV32)+transBV32 = \case+ ConstI _ n -> return $ constant $ BitVector n+ Ite _ c e1 e2 -> do+ c' <- transB c+ trans2BV32 e1 e2 (ite c')++ Op1 _ Abs e -> transBV32 e >>= return . abs+ Op1 _ Neg e -> transBV32 e >>= return . negate+ Op2 _ Add e1 e2 -> trans2BV32 e1 e2 (+)+ Op2 _ Sub e1 e2 -> trans2BV32 e1 e2 (-)+ Op2 _ Mul e1 e2 -> trans2BV32 e1 e2 (*)+ SVal _ s i -> getBV32Var $ ncVar s i++ e -> error $ "Encountered unhandled expression (BV32): " ++ show e++transBV64 :: Expr -> Trans (SMTExpr BV64)+transBV64 = \case+ ConstI _ n -> return $ constant $ BitVector n+ Ite _ c e1 e2 -> do+ c' <- transB c+ trans2BV64 e1 e2 (ite c')++ Op1 _ Abs e -> transBV64 e >>= return . abs+ Op1 _ Neg e -> transBV64 e >>= return . negate+ Op2 _ Add e1 e2 -> trans2BV64 e1 e2 (+)+ Op2 _ Sub e1 e2 -> trans2BV64 e1 e2 (-)+ Op2 _ Mul e1 e2 -> trans2BV64 e1 e2 (*)+ SVal _ s i -> getBV64Var $ ncVar s i++ e -> error $ "Encountered unhandled expression (BV64): " ++ show e++trans2BV8 :: Expr -> Expr -> (SMTExpr BV8 -> SMTExpr BV8 -> SMTExpr a) -> Trans (SMTExpr a)+trans2BV8 e1 e2 f = do+ e1' <- transBV8 e1+ e2' <- transBV8 e2+ return $ f e1' e2'++trans2BV16 :: Expr -> Expr -> (SMTExpr BV16 -> SMTExpr BV16 -> SMTExpr a) -> Trans (SMTExpr a)+trans2BV16 e1 e2 f = do+ e1' <- transBV16 e1+ e2' <- transBV16 e2+ return $ f e1' e2'++trans2BV32 :: Expr -> Expr -> (SMTExpr BV32 -> SMTExpr BV32 -> SMTExpr a) -> Trans (SMTExpr a)+trans2BV32 e1 e2 f = do+ e1' <- transBV32 e1+ e2' <- transBV32 e2+ return $ f e1' e2'++trans2BV64 :: Expr -> Expr -> (SMTExpr BV64 -> SMTExpr BV64 -> SMTExpr a) -> Trans (SMTExpr a)+trans2BV64 e1 e2 f = do+ e1' <- transBV64 e1+ e2' <- transBV64 e2+ return $ f e1' e2'++trans2R :: Expr -> Expr -> (SMTExpr Rational -> SMTExpr Rational -> SMTExpr a) -> Trans (SMTExpr a)+trans2R e1 e2 f = do+ e1' <- transR e1+ e2' <- transR e2+ return $ f e1' e2'++trans2B :: Expr -> Expr -> (SMTExpr Bool -> SMTExpr Bool -> SMTExpr a) -> Trans (SMTExpr a)+trans2B e1 e2 f = do+ e1' <- transB e1+ e2' <- transB e2+ return $ f e1' e2'++-----------------------------------------------------+-- Debug stuff from the the smtlib2 library github --+-----------------------------------------------------++debugBackend :: Bool -> b -> DebugBackend b+debugBackend mute b = DebugBackend b stderr (Just 0) Nothing True mute++namedDebugBackend :: String -> Bool -> b -> DebugBackend b+namedDebugBackend name mute b = DebugBackend b stderr (Just 0) (Just name) True mute++data DebugBackend b = DebugBackend+ { debugBackend' :: b+ , debugHandle :: Handle+ , debugLines :: Maybe Integer+ , debugPrefix :: Maybe String+ , debugUseColor :: Bool+ , mute :: Bool+ }++instance (SMTBackend b m,MonadIO m) => SMTBackend (DebugBackend b) m where+ smtGetNames b = smtGetNames (debugBackend' b)+ smtNextName b = smtNextName (debugBackend' b)+ smtHandle b req = do+ getName <- smtGetNames (debugBackend' b)+ nxtName <- smtNextName (debugBackend' b)+ (dts,b1) <- smtHandle (debugBackend' b) SMTDeclaredDataTypes+ let rendering = renderSMTRequest nxtName getName dts req+ case debugPrefix b of+ Nothing -> return ()+ Just prf -> case rendering of+ Right "" -> return ()+ _ -> do+ when (debugUseColor b) $ liftIO $ hSetSGR (debugHandle b) [Reset,SetColor Foreground Dull Cyan]+ liftIO $ unless (mute b) $ hPutStr (debugHandle b) prf+ nline <- case rendering of+ Right "" -> return (debugLines b)+ _ -> do+ nline <- case debugLines b of+ Nothing -> return Nothing+ Just line -> do+ when (debugUseColor b) $ liftIO $ hSetSGR (debugHandle b) [Reset,SetColor Foreground Dull Red]+ let line_str = show line+ line_str_len = length line_str+ line_str' = replicate (4-line_str_len) ' '++line_str++" "+ liftIO $ unless (mute b) $ hPutStr (debugHandle b) line_str'+ return (Just (line+1))+ case rendering of+ Left l -> do+ when (debugUseColor b) $ liftIO $ hSetSGR (debugHandle b) [Reset,SetColor Foreground Dull Green]+ liftIO $ unless (mute b) $ hPutStrLn (debugHandle b) (show l)+ Right msg -> do+ when (debugUseColor b) $ liftIO $ hSetSGR (debugHandle b) [Reset,SetColor Foreground Dull White]+ liftIO $ unless (mute b) $ hPutStr (debugHandle b) $ unlines $ fmap (\xs -> ';':xs) (lines msg)+ return nline+ (resp,b2) <- smtHandle b1 req+ case renderSMTResponse getName dts req resp of+ Nothing -> return ()+ Just str -> do+ when (debugUseColor b) $ liftIO $ hSetSGR (debugHandle b) [Reset,SetColor Foreground Dull Blue]+ liftIO $ unless (mute b) $ hPutStrLn (debugHandle b) str+ when (debugUseColor b) $ liftIO $ hSetSGR (debugHandle b) [Reset]+ return (resp,b { debugBackend' = b2 , debugLines = nline })++
+ src/Copilot/Theorem/Tactics.hs view
@@ -0,0 +1,17 @@+module Copilot.Theorem.Tactics+ ( instantiate, assume, admit+ ) where++import Copilot.Theorem.Prove++import Data.Word+import Control.Monad.Writer++instantiate :: Proof Universal -> Proof Existential+instantiate (Proof p) = Proof p++assume :: PropRef Universal -> Proof a+assume (PropRef p) = Proof $ tell [Assume p]++admit :: Proof a+admit = Proof $ tell [Admit]
+ src/Copilot/Theorem/TransSys.hs view
@@ -0,0 +1,10 @@+--------------------------------------------------------------------------------++module Copilot.Theorem.TransSys (module X) where++import Copilot.Theorem.TransSys.Spec as X+import Copilot.Theorem.TransSys.PrettyPrint as X+import Copilot.Theorem.TransSys.Translate as X+import Copilot.Theorem.TransSys.Transform as X++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/TransSys/Cast.hs view
@@ -0,0 +1,82 @@+--------------------------------------------------------------------------------++{-# LANGUAGE RankNTypes, ScopedTypeVariables, GADTs #-}++module Copilot.Theorem.TransSys.Cast+ ( Dyn+ , toDyn+ , cast+ , castedType+ , casting+ ) where++--------------------------------------------------------------------------------++import Copilot.Core as C+import Copilot.Core.Type.Equality+import Copilot.Core.Type.Dynamic++import GHC.Float++import qualified Copilot.Theorem.TransSys.Type as K++--------------------------------------------------------------------------------++type Dyn = Dynamic Type++castedType :: Type t -> K.U K.Type+castedType t = case t of+ Bool -> K.U K.Bool+ Int8 -> K.U K.Integer+ Int16 -> K.U K.Integer+ Int32 -> K.U K.Integer+ Int64 -> K.U K.Integer+ Word8 -> K.U K.Integer+ Word16 -> K.U K.Integer+ Word32 -> K.U K.Integer+ Word64 -> K.U K.Integer+ Float -> K.U K.Real+ Double -> K.U K.Real++cast :: K.Type t -> Dyn -> t+cast t v+ | K.Integer <- t, Just (vi :: Integer) <- _cast v = vi+ | K.Bool <- t, Just (vb :: Bool) <- _cast v = vb+ | K.Real <- t, Just (vr :: Double) <- _cast v = vr+ | otherwise = error "Bad type cast"++casting :: Type t -> (forall t' . K.Type t' -> a) -> a+casting t f = case castedType t of+ K.U K.Bool -> f K.Bool+ K.U K.Integer -> f K.Integer+ K.U K.Real -> f K.Real++--------------------------------------------------------------------------------++class Casted b where+ _cast :: Dyn -> Maybe b++instance Casted Integer where+ _cast (Dynamic v tv)+ | Just Refl <- tv =~= Int8 = Just $ toInteger v+ | Just Refl <- tv =~= Int16 = Just $ toInteger v+ | Just Refl <- tv =~= Int32 = Just $ toInteger v+ | Just Refl <- tv =~= Int64 = Just $ toInteger v+ | Just Refl <- tv =~= Word16 = Just $ toInteger v+ | Just Refl <- tv =~= Word8 = Just $ toInteger v+ | Just Refl <- tv =~= Word32 = Just $ toInteger v+ | Just Refl <- tv =~= Word64 = Just $ toInteger v+ | otherwise = Nothing++instance Casted Bool where+ _cast (Dynamic v tv)+ | Just Refl <- tv =~= Bool = Just v+ | otherwise = Nothing++instance Casted Double where+ _cast (Dynamic v tv)+ | Just Refl <- tv =~= Float = Just $ float2Double v+ | Just Refl <- tv =~= Double = Just v+ | otherwise = Nothing++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/TransSys/Invariants.hs view
@@ -0,0 +1,16 @@+{-# OPTIONS_GHC -O0 #-}++module Copilot.Theorem.TransSys.Invariants+ ( HasInvariants (..)+ , prop+ ) where++class HasInvariants a where++ invariants :: a -> [(String, Bool)]++ checkInvs :: a -> Bool+ checkInvs obj = all snd $ invariants obj++prop :: String -> Bool -> (String, Bool)+prop = (,)
+ src/Copilot/Theorem/TransSys/Operators.hs view
@@ -0,0 +1,301 @@+--------------------------------------------------------------------------------++{-# LANGUAGE GADTs, ExistentialQuantification, LambdaCase, ScopedTypeVariables,+ RankNTypes #-}++module Copilot.Theorem.TransSys.Operators where++import qualified Copilot.Core as C+import Copilot.Theorem.TransSys.Cast+import Copilot.Theorem.TransSys.Type++import Copilot.Theorem.Misc.Error as Err++import Control.Applicative ((<$>))++--------------------------------------------------------------------------------++data Op1 a where+ Not :: Op1 Bool+ Neg :: Op1 a+ Abs :: Op1 a+ Exp :: Op1 a+ Sqrt :: Op1 a+ Log :: Op1 a+ Sin :: Op1 a+ Tan :: Op1 a+ Cos :: Op1 a+ Asin :: Op1 a+ Atan :: Op1 a+ Acos :: Op1 a+ Sinh :: Op1 a+ Tanh :: Op1 a+ Cosh :: Op1 a+ Asinh :: Op1 a+ Atanh :: Op1 a+ Acosh :: Op1 a++data Op2 a b where+ Eq :: Op2 a Bool+ And :: Op2 Bool Bool+ Or :: Op2 Bool Bool++ Le :: (Num a) => Op2 a Bool+ Lt :: (Num a) => Op2 a Bool+ Ge :: (Num a) => Op2 a Bool+ Gt :: (Num a) => Op2 a Bool++ Add :: (Num a) => Op2 a a+ Sub :: (Num a) => Op2 a a+ Mul :: (Num a) => Op2 a a+ Mod :: (Num a) => Op2 a a+ Fdiv :: (Num a) => Op2 a a++ Pow :: (Num a) => Op2 a a++-------------------------------------------------------------------------------++instance Show (Op1 a) where+ show op = case op of+ Neg -> "-"+ Not -> "not"+ Abs -> "abs"+ Exp -> "exp"+ Sqrt -> "sqrt"+ Log -> "log"+ Sin -> "sin"+ Tan -> "tan"+ Cos -> "cos"+ Asin -> "asin"+ Atan -> "atan"+ Acos -> "acos"+ Sinh -> "sinh"+ Tanh -> "tanh"+ Cosh -> "cosh"+ Asinh -> "asinh"+ Atanh -> "atanh"+ Acosh -> "acosh"++instance Show (Op2 a b) where+ show op = case op of+ Eq -> "="+ Le -> "<="+ Lt -> "<"+ Ge -> ">="+ Gt -> ">"+ And -> "and"+ Or -> "or"+ Add -> "+"+ Sub -> "-"+ Mul -> "*"+ Mod -> "mod"+ Fdiv -> "/"+ Pow -> "^"++-------------------------------------------------------------------------------++-- | Some high level utilities to translate a Copilot operator in a standard way+-- | The unhandled operators are monomorphic, and their names are labeled so+-- | that each name corresponds to a unique uninterpreted function with a+-- | monomorphic type.++--------------------------------------------------------------------------------++data UnhandledOp1 = forall a b .+ UnhandledOp1 String (Type a) (Type b)++data UnhandledOp2 = forall a b c .+ UnhandledOp2 String (Type a) (Type b) (Type c)++handleOp1 ::+ -- 'm' is the monad in which the computation is made+ -- 'resT' is the desired return type of the expression being translated+ forall m expr _a _b resT. (Functor m) =>+ -- The desired return type+ Type resT ->+ -- The unary operator encountered and its argument+ (C.Op1 _a _b, C.Expr _a) ->+ -- The monadic function to translate an expression+ -- (for recursive calls to be mmadess)+ (forall t t'. Type t -> C.Expr t' -> m (expr t)) ->+ -- A function to deal with a operators not handled by copilot-kind+ (UnhandledOp1 -> m (expr resT)) ->+ -- The Op1 constructor of the 'expr' type+ (forall t . Type t -> Op1 t -> expr t -> expr t) ->++ m (expr resT)++handleOp1 resT (op, e) handleExpr notHandledF mkOp = case op of++ C.Not -> boolOp Not (handleExpr Bool e)++ -- Numeric operators+ C.Abs _ -> numOp Abs+ C.Sign ta -> notHandled ta "sign"++ -- Fractional operators+ C.Recip ta -> notHandled ta "recip"++ -- Floating operators+ C.Exp _ -> numOp Exp+ C.Sqrt _ -> numOp Sqrt+ C.Log _ -> numOp Log+ C.Sin _ -> numOp Sin+ C.Tan _ -> numOp Tan+ C.Cos _ -> numOp Cos+ C.Asin _ -> numOp Asin+ C.Atan _ -> numOp Atan+ C.Acos _ -> numOp Acos+ C.Sinh _ -> numOp Sinh+ C.Tanh _ -> numOp Tanh+ C.Cosh _ -> numOp Cosh+ C.Asinh _ -> numOp Asinh+ C.Atanh _ -> numOp Atanh+ C.Acosh _ -> numOp Acosh++ -- Bitwise operators.+ C.BwNot ta -> notHandled ta "bwnot"++ -- Casting operator.+ C.Cast _ tb -> castTo tb++ where+ boolOp :: Op1 Bool -> m (expr Bool) -> m (expr resT)+ boolOp op e = case resT of+ Bool -> (mkOp resT op) <$> e+ _ -> Err.impossible typeErrMsg++ numOp :: Op1 resT -> m (expr resT)+ numOp op = (mkOp resT op) <$> (handleExpr resT e)++ -- Casting from Integer (Only possible solution)+ castTo :: C.Type ctb -> m (expr resT)+ castTo tb = casting tb $ \tb' -> case (tb', resT) of+ (Integer, Integer) -> handleExpr Integer e+ (Real, Real) -> handleExpr Real e+ _ -> Err.impossible typeErrMsg++ notHandled ::+ C.Type a -> String -> m (expr resT)+ notHandled ta s = casting ta $ \ta' ->+ notHandledF $ UnhandledOp1 s ta' resT++--------------------------------------------------------------------------------++-- See the 'handleOp1' function for documentation+handleOp2 ::+ forall m expr _a _b _c resT . (Monad m) =>+ Type resT ->+ (C.Op2 _a _b _c, C.Expr _a, C.Expr _b) ->+ (forall t t'. Type t -> C.Expr t' -> m (expr t)) ->+ (UnhandledOp2 -> m (expr resT)) ->+ (forall t a . Type t -> Op2 a t -> expr a -> expr a -> expr t) ->+ (expr Bool -> expr Bool) ->+ m (expr resT)+++handleOp2 resT (op, e1, e2) handleExpr notHandledF mkOp notOp = case op of++ C.And -> boolConnector And+ C.Or -> boolConnector Or++ -- Numeric operators+ C.Add _ -> numOp Add+ C.Sub _ -> numOp Sub+ C.Mul _ -> numOp Mul++ -- Integral operators.+ C.Mod _ -> numOp Mod+ C.Div ta -> notHandled ta "div"++ -- Fractional operators.+ C.Fdiv _ -> numOp Fdiv++ -- Floating operators.+ C.Pow _ -> numOp Pow+ C.Logb ta -> notHandled ta "logb"++ -- Equality operators.+ C.Eq ta -> eqOp ta+ C.Ne ta -> neqOp ta++ -- Relational operators.+ C.Le ta -> numComp ta Le+ C.Ge ta -> numComp ta Ge+ C.Lt ta -> numComp ta Lt+ C.Gt ta -> numComp ta Gt++ -- Bitwise operators.+ C.BwAnd ta -> notHandled ta "bwand"+ C.BwOr ta -> notHandled ta "bwor"+ C.BwXor ta -> notHandled ta "bwxor"++ -- In fact, '_tb' is ignored caused it can only+ -- be casted to 'Integer', like 'ta'+ C.BwShiftL ta _tb -> notHandled ta "bwshiftl"+ C.BwShiftR ta _tb -> notHandled ta "bwshiftr"++ where++ boolOp :: Op2 a Bool -> expr a -> expr a -> expr resT+ boolOp op e1' e2' = case resT of+ Bool -> mkOp resT op e1' e2'+ _ -> Err.impossible typeErrMsg++ boolConnector :: Op2 Bool Bool -> m (expr resT)+ boolConnector op = do+ e1' <- handleExpr Bool e1+ e2' <- handleExpr Bool e2+ return $ boolOp op e1' e2'++ eqOp :: C.Type cta -> m (expr resT)+ eqOp ta = casting ta $ \ta' -> do+ e1' <- handleExpr ta' e1+ e2' <- handleExpr ta' e2+ return $ boolOp Eq e1' e2'++ neqOp :: C.Type cta -> m (expr resT)+ neqOp ta = case resT of+ Bool -> do+ e <- eqOp ta+ return $ notOp e+ _ -> Err.impossible typeErrMsg++ numOp :: (forall num . (Num num) => Op2 num num) -> m (expr resT)+ numOp op = case resT of+ Integer -> do+ e1' <- handleExpr Integer e1+ e2' <- handleExpr Integer e2+ return $ mkOp resT op e1' e2'++ Real -> do+ e1' <- handleExpr Real e1+ e2' <- handleExpr Real e2+ return $ mkOp resT op e1' e2'++ _ -> Err.impossible typeErrMsg++ numComp ::+ C.Type cta ->+ (forall num . (Num num) => Op2 num Bool) -> m (expr resT)+ numComp ta op = casting ta $ \case+ Integer -> do+ e1' <- handleExpr Integer e1+ e2' <- handleExpr Integer e2+ return $ boolOp op e1' e2'+ Real -> do+ e1' <- handleExpr Real e1+ e2' <- handleExpr Real e2+ return $ boolOp op e1' e2'+ _ -> Err.impossible typeErrMsg++ notHandled :: forall a . C.Type a -> String -> m (expr resT)+ notHandled ta s = casting ta $ \ta' ->+ notHandledF (UnhandledOp2 s ta' ta' ta')++--------------------------------------------------------------------------------++typeErrMsg :: String+typeErrMsg = "Unexpected type error in 'Misc.CoreOperators'"++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/TransSys/PrettyPrint.hs view
@@ -0,0 +1,121 @@+--------------------------------------------------------------------------------++{-# LANGUAGE NamedFieldPuns, GADTs #-}++module Copilot.Theorem.TransSys.PrettyPrint ( prettyPrint ) where++import Copilot.Theorem.TransSys.Spec++import Text.PrettyPrint.HughesPJ++import qualified Data.Map as Map+import qualified Data.Bimap as Bimap++--------------------------------------------------------------------------------++indent = nest 4+emptyLine = text ""++prettyPrint :: TransSys -> String+prettyPrint = render . pSpec++pSpec :: TransSys -> Doc+pSpec spec = items $$ props+ where+ items = foldr (($$) . pNode) empty (specNodes spec)+ props = text "PROPS" $$+ Map.foldrWithKey (\k -> ($$) . pProp k)+ empty (specProps spec)++pProp pId extvar = quotes (text pId) <+> text "is" <+> pExtVar extvar++pType :: Type t -> Doc+pType = text . show++pList :: (t -> Doc) -> [t] -> Doc+pList f l = brackets (hcat . punctuate (comma <> space) $ map f l)++pNode :: Node -> Doc+pNode n =+ header $$ imported $$ local $$ constrs $$ emptyLine+ where+ header =+ text "NODE"+ <+> quotes (text $ nodeId n)+ <+> text "DEPENDS ON"+ <+> pList text (nodeDependencies n)++ imported+ | Bimap.null (nodeImportedVars n) = empty+ | otherwise = text "IMPORTS" $$ indent+ (Map.foldrWithKey (\k -> ($$) . pIVar k)+ empty (Bimap.toMap $ nodeImportedVars n))++ local+ | Map.null (nodeLocalVars n) = empty+ | otherwise = text "DEFINES" $$ indent+ (Map.foldrWithKey (\k -> ($$) . pLVar k)+ empty (nodeLocalVars n))++ constrs = case nodeConstrs n of+ [] -> empty+ l -> text "WITH CONSTRAINTS" $$+ foldr (($$) . pExpr) empty l++pConst :: Type t -> t -> Doc+pConst Integer v = text $ show v+pConst Real v = text $ show v+pConst Bool v = text $ show v++pExtVar :: ExtVar -> Doc+pExtVar (ExtVar n v) = parens (text n <+> text ":" <+> text (varName v))++pIVar :: Var -> ExtVar -> Doc+pIVar v ev =+ pExtVar ev+ <+> text "as" <+> quotes (text (varName v))++pLVar :: Var -> VarDescr -> Doc+pLVar l (VarDescr {varType, varDef}) = header $$ indent body+ where header =+ text (varName l)+ <+> text ":"+ <+> pType varType+ <+> text "="++ body = case varDef of+ Pre val var ->+ pConst varType val+ <+> text "->" <+> text "pre"+ <+> text (varName var)+ Expr e -> pExpr e++ Constrs cs ->+ text "{"+ <+> (hsep . punctuate (space <> text ";" <> space)) (map pExpr cs)+ <+> text "}"+++pExpr :: Expr t -> Doc++pExpr (Const t v) = pConst t v++pExpr (Ite _ c e1 e2) =+ text "if" <+> pExpr c+ <+> text "then" <+> pExpr e1+ <+> text "else" <+> pExpr e2++pExpr (Op1 _ op e) = pOp1 op <+> parens (pExpr e)++pExpr (Op2 _ op e1 e2) =+ parens (pExpr e1) <+> pOp2 op <+> parens (pExpr e2)++pExpr (VarE _ v) = text (varName v)++pOp1 :: Op1 a -> Doc+pOp1 = text . show++pOp2 :: Op2 a b -> Doc+pOp2 = text . show++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/TransSys/Renaming.hs view
@@ -0,0 +1,74 @@+--------------------------------------------------------------------------------++{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Copilot.Theorem.TransSys.Renaming+ ( Renaming+ , addReservedName+ , rename+ , getFreshName+ , runRenaming+ , getRenamingF+ ) where++import Copilot.Theorem.TransSys.Spec++import Control.Monad.State.Lazy+import Control.Applicative++import Data.Maybe (fromMaybe)+import Data.Map (Map)+import Data.Set (Set, member)++import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.List as List++--------------------------------------------------------------------------------++newtype Renaming a = Renaming (State RenamingST a)+ deriving (Applicative, Monad, Functor)++data RenamingST = RenamingST+ { _reservedNames :: Set Var+ , _renaming :: Map ExtVar Var }++--------------------------------------------------------------------------------++addReservedName :: Var -> Renaming ()+addReservedName v =+ Renaming $ modify $ \st ->+ st {_reservedNames = Set.insert v (_reservedNames st)}+++getFreshName :: [Var] -> Renaming Var+getFreshName vs = do+ usedNames <- _reservedNames <$> Renaming get+ let varAppend (Var s) = Var $ s ++ "_"+ applicants = vs ++ List.iterate varAppend (head vs)+ v = case dropWhile (`member` usedNames) applicants of+ v:_ -> v+ [] -> error "No more names available"+ addReservedName v+ return v++rename :: NodeId -> Var -> Var -> Renaming ()+rename n v v' =+ Renaming $ modify $ \st ->+ st {_renaming = Map.insert (ExtVar n v) v' (_renaming st)}++getRenamingF :: Renaming (ExtVar -> Var)+getRenamingF = do+ mapping <- _renaming <$> Renaming get+ return $ \extv -> fromMaybe (extVarLocalPart extv) (Map.lookup extv mapping)++runRenaming :: Renaming a -> (a, ExtVar -> Var)+runRenaming m =+ evalState st' (RenamingST Set.empty Map.empty)+ where+ Renaming st' = do+ r <- m+ f <- getRenamingF+ return (r, f)++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/TransSys/Spec.hs view
@@ -0,0 +1,198 @@+--------------------------------------------------------------------------------++{-# LANGUAGE ExistentialQuantification, GADTs, RankNTypes #-}++module Copilot.Theorem.TransSys.Spec+ ( module Copilot.Theorem.TransSys.Operators+ , module Copilot.Theorem.TransSys.Type+ , module Copilot.Theorem.TransSys.Invariants+ , TransSys (..)+ , Node (..)+ , PropId+ , NodeId+ , Var (..)+ , ExtVar (..)+ , VarDef (..)+ , VarDescr (..)+ , Expr (..)+ , mkExtVar+ , transformExpr+ , isTopologicallySorted+ , nodeVarsSet+ , specDependenciesGraph+ , specTopNode ) where++import Copilot.Theorem.TransSys.Type+import Copilot.Theorem.TransSys.Operators+import Copilot.Theorem.TransSys.Invariants++import Copilot.Theorem.Misc.Utils++import Control.Applicative (liftA2)+import Control.Monad (foldM, guard)++import Data.Maybe+import Data.Monoid (Monoid, (<>), mempty, mconcat)+import Data.Map (Map)+import Data.Set (Set, isSubsetOf, member)+import Data.Bimap (Bimap)++import qualified Data.List as List+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.Bimap as Bimap++--------------------------------------------------------------------------------++type NodeId = String+type PropId = String++data TransSys = TransSys+ { specNodes :: [Node]+ , specTopNodeId :: NodeId+ , specProps :: Map PropId ExtVar }+++data Node = Node+ { nodeId :: NodeId+ , nodeDependencies :: [NodeId]+ , nodeLocalVars :: Map Var VarDescr+ , nodeImportedVars :: Bimap Var ExtVar+ , nodeConstrs :: [Expr Bool] }+++data Var = Var {varName :: String}+ deriving (Eq, Show, Ord)++data ExtVar = ExtVar {extVarNode :: NodeId, extVarLocalPart :: Var }+ deriving (Eq, Ord)++data VarDescr = forall t . VarDescr+ { varType :: Type t+ , varDef :: VarDef t }++data VarDef t = Pre t Var | Expr (Expr t) | Constrs [Expr Bool]++data Expr t where+ Const :: Type t -> t -> Expr t+ Ite :: Type t -> Expr Bool -> Expr t -> Expr t -> Expr t+ Op1 :: Type t -> Op1 t -> Expr t -> Expr t+ Op2 :: Type t -> Op2 a t -> Expr a -> Expr a -> Expr t+ VarE :: Type t -> Var -> Expr t++--------------------------------------------------------------------------------++mkExtVar node name = ExtVar node (Var name)++foldExpr :: (Monoid m) => (forall t . Expr t -> m) -> Expr a -> m+foldExpr f expr = f expr <> fargs+ where+ fargs = case expr of+ (Ite _ c e1 e2) -> foldExpr f c <> foldExpr f e1 <> foldExpr f e2+ (Op1 _ _ e) -> foldExpr f e+ (Op2 _ _ e1 e2) -> foldExpr f e1 <> foldExpr f e2+ _ -> mempty++foldUExpr :: (Monoid m) => (forall t . Expr t -> m) -> U Expr -> m+foldUExpr f (U e) = foldExpr f e++transformExpr :: (forall a . Expr a -> Expr a) -> Expr t -> Expr t+transformExpr f = tre+ where+ tre :: forall t . Expr t -> Expr t+ tre (Ite t c e1 e2) = f (Ite t (tre c) (tre e1) (tre e2))+ tre (Op1 t op e) = f (Op1 t op (tre e))+ tre (Op2 t op e1 e2) = f (Op2 t op (tre e1) (tre e2))+ tre e = f e++--------------------------------------------------------------------------------++nodeVarsSet :: Node -> Set Var+nodeVarsSet = liftA2 Set.union+ nodeLocalVarsSet+ (Map.keysSet . Bimap.toMap . nodeImportedVars)++nodeLocalVarsSet :: Node -> Set Var+nodeLocalVarsSet = Map.keysSet . nodeLocalVars++nodeRhsVarsSet :: Node -> Set Var+nodeRhsVarsSet n =+ let varOcc (VarE _ v) = Set.singleton v+ varOcc _ = Set.empty++ descrRhsVars (VarDescr _ (Expr e)) = foldExpr varOcc e+ descrRhsVars (VarDescr _ (Pre _ v)) = Set.singleton v+ descrRhsVars (VarDescr _ (Constrs cs)) =+ mconcat (map (foldExpr varOcc) cs)++ in Map.fold (Set.union . descrRhsVars) Set.empty (nodeLocalVars n)++nodeImportedExtVarsSet :: Node -> Set ExtVar+nodeImportedExtVarsSet = Map.keysSet . Bimap.toMapR . nodeImportedVars++nodeExportedExtVarsSet :: Node -> Set ExtVar+nodeExportedExtVarsSet n = Set.map (ExtVar $ nodeId n) (nodeLocalVarsSet n)++--------------------------------------------------------------------------------++instance HasInvariants Node where++ invariants n =+ [ prop "The dependencies declaration doesn't lie" $+ (map extVarNode . Bimap.elems $ nodeImportedVars n)+ `isSublistOf` nodeDependencies n++ , prop "All local variables are declared" $+ nodeRhsVarsSet n `isSubsetOf` nodeVarsSet n++ , prop "Never apply 'pre' to an imported var" $+ let preVars = Set.fromList+ [v | (VarDescr _ (Pre _ v)) <- Map.elems $ nodeLocalVars n]+ in preVars `isSubsetOf` nodeLocalVarsSet n+ ]++--------------------------------------------------------------------------------++specNodesIds :: TransSys -> Set NodeId+specNodesIds s = Set.fromList . map nodeId $ specNodes s++specDependenciesGraph :: TransSys -> Map NodeId [NodeId]+specDependenciesGraph s =+ Map.fromList [ (nodeId n, nodeDependencies n) | n <- specNodes s ]++specTopNode :: TransSys -> Node+specTopNode spec = fromJust $ List.find+ ((== specTopNodeId spec) . nodeId)+ (specNodes spec)++--------------------------------------------------------------------------------++instance HasInvariants TransSys where++ invariants s =+ [ prop "All mentioned nodes are declared" $+ specTopNodeId s `member` specNodesIds s+ && Set.fromList [nId | n <- specNodes s, nId <- nodeDependencies n]+ `isSubsetOf` specNodesIds s++ , prop "The imported vars are not broken" $+ mconcat (map nodeImportedExtVarsSet $ specNodes s) `isSubsetOf`+ mconcat (map nodeExportedExtVarsSet $ specNodes s)++ , prop "The nodes invariants hold" $ all checkInvs (specNodes s)+ ]++isTopologicallySorted :: TransSys -> Bool+isTopologicallySorted spec =+ isJust $ foldM inspect Set.empty (specNodes spec)+ where inspect acc n = do+ guard $ Set.fromList (nodeDependencies n) `isSubsetOf` acc+ return . Set.insert (nodeId n) $ acc++--------------------------------------------------------------------------------++-- For debugging purposes+instance Show ExtVar where+ show (ExtVar n v) = "(" ++ n ++ " : " ++ show v ++ ")"++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/TransSys/Transform.hs view
@@ -0,0 +1,267 @@+--------------------------------------------------------------------------------++{-# LANGUAGE RankNTypes #-}++module Copilot.Theorem.TransSys.Transform+ ( mergeNodes+ , inline+ , removeCycles+ , complete+ ) where++import Copilot.Theorem.TransSys.Spec+import Copilot.Theorem.TransSys.Renaming++import Copilot.Theorem.Misc.Utils++import Control.Monad (foldM, forM_, forM, guard)++import Data.List (sort, (\\), intercalate, partition)++import Control.Exception.Base (assert)++import Data.Map (Map, (!))+import Data.Set (member)+import Data.Bimap (Bimap)++import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.Graph as Graph+import qualified Data.Bimap as Bimap++--------------------------------------------------------------------------------++prefix :: String -> Var -> Var+prefix s1 (Var s2) = Var $ s1 ++ "." ++ s2++ncNodeIdSep = "-"++--------------------------------------------------------------------------------++mergeNodes :: [NodeId] -> TransSys -> TransSys+mergeNodes toMergeIds spec =+ spec+ { specNodes = newNode :+ map (updateOtherNode newNodeId toMergeIds renamingExtF) otherNodes+ , specProps = Map.map renamingExtF (specProps spec) }++ where+ nodes = specNodes spec+ (toMerge, otherNodes) = partition ((`elem` toMergeIds) . nodeId) nodes++ -- Choosing the new node ID. If the top node is merged,+ -- its name is kept+ newNodeId+ | specTopNodeId spec `elem` toMergeIds = specTopNodeId spec+ | otherwise = intercalate ncNodeIdSep (sort toMergeIds)++ newNode = Node+ { nodeId = newNodeId+ , nodeDependencies = dependencies+ , nodeImportedVars = importedVars+ , nodeLocalVars = localVars+ , nodeConstrs = constrs }++ -- Computing the dependencies of the new node+ dependencies = nub'+ [ id |+ n <- toMerge+ , id <- nodeDependencies n+ , id `notElem` toMergeIds ]++ -- All the work of renaming is done in the monad with the same name+ (importedVars, renamingF) = runRenaming $ do+ renameLocalVars toMerge+ redirectLocalImports toMerge+ selectImportedVars toMerge otherNodes dependencies++ -- Converting the variables descriptors+ localVars = mergeVarsDescrs toMerge renamingF++ -- Computing the global renaming function+ renamingExtF (gv@(ExtVar nId _))+ | nId `elem` toMergeIds = ExtVar newNodeId (renamingF gv)+ | otherwise = gv++ constrs = mergeConstrs toMerge renamingF+++updateOtherNode :: NodeId -> [NodeId] -> (ExtVar -> ExtVar) -> Node -> Node+updateOtherNode newNodeId mergedNodesIds renamingF n = n+ { nodeDependencies =+ let ds = nodeDependencies n+ ds' = ds \\ mergedNodesIds+ in if length ds' < length ds then newNodeId : ds' else ds++ , nodeImportedVars =+ Bimap.fromList [ (lv, renamingF gv)+ | (lv, gv) <- Bimap.toList $ nodeImportedVars n ]+ }++++updateExpr :: NodeId -> (ExtVar -> Var) -> Expr t -> Expr t+updateExpr nId renamingF = transformExpr aux+ where+ aux :: forall t. Expr t -> Expr t+ aux (VarE t v) = VarE t (renamingF (ExtVar nId v))+ aux e = e+++mergeVarsDescrs :: [Node] -> (ExtVar -> Var) -> Map Var VarDescr+mergeVarsDescrs toMerge renamingF = Map.fromList $ do+ n <- toMerge+ let nId = nodeId n+ (v, VarDescr t def) <- Map.toList $ nodeLocalVars n+ let d' = case def of+ Pre val v' -> VarDescr t $+ Pre val $ renamingF (ExtVar nId v')+ Expr e -> VarDescr t $+ Expr $ updateExpr nId renamingF e+ Constrs cs -> VarDescr t $+ Constrs $ map (updateExpr nId renamingF) cs++ return (renamingF $ ExtVar nId v, d')++mergeConstrs :: [Node] -> (ExtVar -> Var) -> [Expr Bool]+mergeConstrs toMerge renamingF =+ [ updateExpr (nodeId n) renamingF c | n <- toMerge, c <- nodeConstrs n ]++renameLocalVars :: [Node] -> Renaming ()+renameLocalVars toMerge =+ forM_ niVars $ \(n, v) -> do+ v' <- getFreshName [n `prefix` v]+ rename n v v'+ where+ niVars = [ (nodeId n, v)+ | n <- toMerge, (v, _) <- Map.toList (nodeLocalVars n) ]++selectImportedVars :: [Node] -> [Node] -> [NodeId]+ -> Renaming (Bimap Var ExtVar)+selectImportedVars toMerge otherNodes dependencies =+ foldM checkImport Bimap.empty depsVars++ where+ otherNodesMap = Map.fromList [(nodeId n, n) | n <- otherNodes]++ depsVars = [ (nId, v)+ | nId <- dependencies, let n = otherNodesMap ! nId+ , v <- Map.keys (nodeLocalVars n)]++ checkImport acc (nId, v) = do+ v' <- getFreshName [nId `prefix` v]+ bmap <- forM toMerge $ \n' ->+ case Bimap.lookupR (ExtVar nId v)+ (nodeImportedVars n') of++ Just lv -> rename (nodeId n') lv v' >> return True+ Nothing -> return False++ return $+ if True `elem` bmap+ then Bimap.insert v' (ExtVar nId v) acc+ else acc++++redirectLocalImports :: [Node] -> Renaming ()+redirectLocalImports toMerge = do+ renamingF <- getRenamingF+ forM_ x $ \(n, alias, n', v) ->+ rename n alias (renamingF (ExtVar n' v))++ where+ mergedNodesSet = Set.fromList [nodeId n | n <- toMerge]+ x = do+ n <- toMerge+ let nId = nodeId n+ (alias, ExtVar n' v) <- Bimap.toList (nodeImportedVars n)+ guard $ n' `member` mergedNodesSet+ return (nId, alias, n', v)++--------------------------------------------------------------------------------++inline :: TransSys -> TransSys+inline spec = mergeNodes [nodeId n | n <- specNodes spec] spec++removeCycles :: TransSys -> TransSys+removeCycles spec =+ topoSort $ foldr mergeComp spec (buildScc nodeId $ specNodes spec)+ where++ mergeComp (Graph.AcyclicSCC _) s = s+ mergeComp (Graph.CyclicSCC ids) s = mergeNodes ids s++ buildScc nrep ns =+ let depGraph = map (\n -> (nrep n, nodeId n, nodeDependencies n)) ns+ in Graph.stronglyConnComp depGraph++ topoSort s = s { specNodes =+ map (\(Graph.AcyclicSCC n) -> n) $ buildScc id (specNodes s) }++--------------------------------------------------------------------------------++-- | Completes each node of a specification with imported variables such+-- | that each node contains a copy of all its dependencies+-- | The given specification should have its node sorted by topological+-- | order.+-- | The top nodes should have all the other nodes as its dependencies++complete :: TransSys -> TransSys+complete spec =+ assert (isTopologicallySorted spec) $ spec { specNodes = specNodes' }++ where++ specNodes' =+ reverse+ . foldl completeNode []+ . specNodes+ . completeTopNodeDeps+ $ spec++ completeTopNodeDeps spec = spec { specNodes = map aux nodes }+ where+ nodes = specNodes spec+ aux n+ | nodeId n == specTopNodeId spec =+ n { nodeDependencies = map nodeId nodes \\ [nodeId n] }+ | otherwise = n++ -- Takes a list of nodes 'ns', 'n' whose dependencies are in 'ns', and+ -- returns 'n2:ns' where 'n2' is 'n' completed+ completeNode :: [Node] -> Node -> [Node]+ completeNode ns n = (n { nodeDependencies = dependencies'+ , nodeImportedVars = importedVars' }) : ns++ where+ nsMap = Map.fromList [(nodeId n, n) | n <- ns]+ dependencies' =+ let newDeps = do+ dId <- nodeDependencies n+ let d = nsMap ! dId+ nodeDependencies d++ in nub' $ nodeDependencies n ++ newDeps++ importedVars' = fst . runRenaming $ do+ forM_ (Set.toList $ nodeVarsSet n) addReservedName+ let toImportVars = nub' [ ExtVar nId v+ | nId <- dependencies'+ , let n' = nsMap ! nId+ , v <- Map.keys (nodeLocalVars n') ]++ tryImport acc ev@(ExtVar n' v) = do++ -- To get readable names, we don't prefix variables+ -- which come from merged nodes as they are already+ -- decorated+ let preferedName+ | head ncNodeIdSep `elem` n' = v+ | otherwise = n' `prefix` v+ alias <- getFreshName [preferedName, n' `prefix` v]+ return $ Bimap.tryInsert alias ev acc++ foldM tryImport (nodeImportedVars n) toImportVars++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/TransSys/Translate.hs view
@@ -0,0 +1,286 @@+--------------------------------------------------------------------------------++{-# LANGUAGE RankNTypes, NamedFieldPuns, ViewPatterns,+ ScopedTypeVariables, GADTs, FlexibleContexts #-}++module Copilot.Theorem.TransSys.Translate ( translate ) where++import Copilot.Theorem.TransSys.Spec+import Copilot.Theorem.TransSys.Cast+import Copilot.Theorem.Misc.Utils++import Control.Applicative ((<$>))+import Control.Monad.State.Lazy++import Data.Char (isNumber)+import Data.Function (on)++import Data.Map (Map)+import Data.Bimap (Bimap)++import qualified Copilot.Core as C+import qualified Data.Map as Map+import qualified Data.Bimap as Bimap++--------------------------------------------------------------------------------++-- Naming conventions+-- These are important in order to avoid name conflicts++ncSep = "."+ncMain = "out"+ncNode i = "s" ++ show i+ncPropNode s = "prop-" ++ s+ncTopNode = "top"+ncAnonInput = "in"+ncLocal s = "l" ++ dropWhile (not . isNumber) s++ncExternVarNode name = "ext-" ++ name+ncExternFunNode name = "fun-" ++ name+ncExternArrNode name = "arr-" ++ name++ncImported :: NodeId -> String -> String+ncImported n s = n ++ ncSep ++ s++ncTimeAnnot :: String -> Int -> String+ncTimeAnnot s d+ | d == 0 = s+ | otherwise = s ++ ncSep ++ show d++--------------------------------------------------------------------------------++translate :: C.Spec -> TransSys+translate cspec =++ TransSys { specNodes = [topNode] ++ modelNodes ++ propNodes ++ extVarNodes+ , specTopNodeId = topNodeId+ , specProps = propBindings }++ where++ topNodeId = ncTopNode++ cprops :: [C.Property]+ cprops = C.specProperties cspec++ propBindings :: Map PropId ExtVar+ propBindings = Map.fromList $ do+ pid <- map C.propertyName cprops+ return (pid, mkExtVar topNodeId pid)++ ((modelNodes, propNodes), extvarNodesNames) = runTrans $+ liftM2 (,) (mapM stream (C.specStreams cspec)) (mkPropNodes cprops)++ topNode = mkTopNode topNodeId (map nodeId propNodes) cprops+ extVarNodes = map mkExtVarNode extvarNodesNames++--------------------------------------------------------------------------------+++mkTopNode :: String -> [NodeId] -> [C.Property] -> Node+mkTopNode topNodeId dependencies cprops =+ Node { nodeId = topNodeId+ , nodeDependencies = dependencies+ , nodeLocalVars = Map.empty+ , nodeImportedVars = importedVars+ , nodeConstrs = []}+ where+ importedVars = Bimap.fromList+ [ (Var cp, mkExtVar (ncPropNode cp) ncMain)+ | cp <- C.propertyName <$> cprops ]++++mkExtVarNode (name, U t) =+ Node { nodeId = name+ , nodeDependencies = []+ , nodeLocalVars = Map.singleton (Var ncMain) (VarDescr t $ Constrs [])+ , nodeImportedVars = Bimap.empty+ , nodeConstrs = []}+++mkPropNodes :: [C.Property] -> Trans [Node]+mkPropNodes = mapM propNode+ where+ propNode p = do+ s <- stream (streamOfProp p)+ return $ s {nodeId = ncPropNode (C.propertyName p)}++-- A dummy ID is given to this stream, which is not a problem+-- because this ID will never be used+streamOfProp :: C.Property -> C.Stream+streamOfProp prop =+ C.Stream { C.streamId = 42+ , C.streamBuffer = []+ , C.streamExpr = C.propertyExpr prop+ , C.streamExprType = C.Bool }++--------------------------------------------------------------------------------++stream :: C.Stream -> Trans Node+stream (C.Stream { C.streamId+ , C.streamBuffer+ , C.streamExpr+ , C.streamExprType })++ = casting streamExprType $ \t -> do++ let nodeId = ncNode streamId+ outvar i = Var (ncMain `ncTimeAnnot` i)+ buf = map (cast t . toDyn streamExprType) streamBuffer++ (e, nodeAuxVars, nodeImportedVars, nodeDependencies) <-+ runExprTrans t nodeId streamExpr++ let outputLocals =+ let from i [] = Map.singleton (outvar i) (VarDescr t $ Expr e)+ from i (b : bs) =+ Map.insert (outvar i)+ (VarDescr t $ Pre b $ outvar (i + 1))+ $ from (i + 1) bs+ in from 0 buf+ nodeLocalVars = Map.union nodeAuxVars outputLocals+ nodeOutputs = map outvar [0 .. length buf - 1]++ return Node+ { nodeId, nodeDependencies, nodeLocalVars+ , nodeImportedVars, nodeConstrs = [] }++--------------------------------------------------------------------------------++expr :: Type t -> C.Expr t' -> Trans (Expr t)++expr t (C.Const t' v) = return $ Const t (cast t $ toDyn t' v)++expr t (C.Drop _ (fromIntegral -> k :: Int) id) = do+ let node = ncNode id+ selfRef <- (== node) <$> curNode+ let varName = ncMain `ncTimeAnnot` k+ let var = Var $ if selfRef then varName else ncImported node varName+ unless selfRef $ do+ newDep node+ newImportedVar var (mkExtVar node varName)+ return $ VarE t var++expr t (C.Label _ _ e) = expr t e++expr t (C.Local tl _tr id l e) = casting tl $ \tl' -> do+ l' <- expr tl' l+ newLocal (Var $ ncLocal id) $ VarDescr tl' $ Expr l'+ expr t e++expr t (C.Var _t' id) = return $ VarE t (Var $ ncLocal id)++expr t (C.Op3 (C.Mux _) cond e1 e2) = do+ cond' <- expr Bool cond+ e1' <- expr t e1+ e2' <- expr t e2+ return $ Ite t cond' e1' e2'++expr t (C.ExternVar _ name _) = do+ let nodeName = ncExternVarNode name+ let localAlias = Var nodeName+ newExtVarNode nodeName (U t)+ newDep nodeName+ newImportedVar localAlias (ExtVar nodeName (Var ncMain))+ return $ VarE t localAlias++-- TODO : Use uninterpreted functions to handle+-- * Unhandled operators+-- * Extern functions+-- * Extern arrays+-- For now, the result of these operations is a new unconstrained variable++expr t (C.Op1 op e) = handleOp1+ t (op, e) expr notHandled Op1+ where+ notHandled (UnhandledOp1 _opName _ta _tb) =+ newUnconstrainedVar t++expr t (C.Op2 op e1 e2) = handleOp2+ t (op, e1, e2) expr notHandled Op2 (Op1 Bool Not)+ where+ notHandled (UnhandledOp2 _opName _ta _tb _tc) =+ newUnconstrainedVar t++expr t (C.ExternFun _ta _name _args _ _mtag) = newUnconstrainedVar t++expr t (C.ExternArray _ _tb _name _ _ind _ _) = newUnconstrainedVar t++newUnconstrainedVar :: Type t -> Trans (Expr t)+newUnconstrainedVar t = do+ newNode <- getFreshNodeName+ newLocal (Var newNode) $ VarDescr t $ Constrs []+ newDep newNode+ return $ VarE t (Var newNode)++--------------------------------------------------------------------------------++runTrans :: Trans a -> (a, [(NodeId, U Type)])+runTrans mx =+ (x, nubBy' (compare `on` fst) $ _extVarsNodes st)+ where+ (x, st) = runState mx initState+ initState = TransSt+ { _lvars = Map.empty+ , _importedVars = Bimap.empty+ , _dependencies = []+ , _extVarsNodes = []+ , _curNode = ""+ , _nextUid = 0 }++runExprTrans ::+ Type t -> NodeId -> C.Expr a ->+ Trans (Expr t, Map Var VarDescr, Bimap Var ExtVar, [NodeId])++runExprTrans t curNode e = do+ modify $ \st -> st { _curNode = curNode }+ modify $ \st -> st { _nextUid = 0 }+ e' <- expr t e+ (lvs, ivs, dps) <- popLocalInfos+ return (e', lvs, ivs, dps)++data TransSt = TransSt+ { _lvars :: Map Var VarDescr+ , _importedVars :: Bimap Var ExtVar+ , _dependencies :: [NodeId]+ , _extVarsNodes :: [(NodeId, U Type)]+ , _curNode :: NodeId+ , _nextUid :: Int }++type Trans a = State TransSt a++newDep d = modify $ \s -> s { _dependencies = d : _dependencies s }++popLocalInfos :: State TransSt (Map Var VarDescr, Bimap Var ExtVar, [NodeId])+popLocalInfos = do+ lvs <- _lvars <$> get+ ivs <- _importedVars <$> get+ dps <- _dependencies <$> get+ modify $ \st -> st+ { _lvars = Map.empty+ , _importedVars = Bimap.empty+ , _dependencies = [] }+ return (lvs, ivs, nub' dps)+++getUid :: Trans Int+getUid = do+ uid <- _nextUid <$> get+ modify $ \st -> st { _nextUid = uid + 1 }+ return uid++getFreshNodeName :: Trans NodeId+getFreshNodeName = liftM (("_" ++) . show) getUid++newImportedVar l g = modify $+ \s -> s { _importedVars = Bimap.insert l g (_importedVars s) }++newLocal l d = modify $ \s -> s { _lvars = Map.insert l d $ _lvars s }++curNode = _curNode <$> get++newExtVarNode id t =+ modify $ \st -> st { _extVarsNodes = (id, t) : _extVarsNodes st }++--------------------------------------------------------------------------------
+ src/Copilot/Theorem/TransSys/Type.hs view
@@ -0,0 +1,40 @@+--------------------------------------------------------------------------------++{-# LANGUAGE ExistentialQuantification, GADTs #-}++module Copilot.Theorem.TransSys.Type+ ( Type (..)+ , U (..)+ , U2 (..)+ ) where++import Copilot.Core.Type.Equality++--------------------------------------------------------------------------------++data Type a where+ Bool :: Type Bool+ Integer :: Type Integer+ Real :: Type Double++instance EqualType Type where+ Bool =~= Bool = Just Refl+ Integer =~= Integer = Just Refl+ Real =~= Real = Just Refl+ _ =~= _ = Nothing++--------------------------------------------------------------------------------++-- For instance, 'U Expr' is the type of an expression of unknown type++data U f = forall t . U (f t)+data U2 f g = forall t . U2 (f t) (g t)++--------------------------------------------------------------------------------++instance Show (Type t) where+ show Integer = "Int"+ show Bool = "Bool"+ show Real = "Real"++--------------------------------------------------------------------------------