This commit is contained in:
Ken McMillan 2016-08-18 18:38:49 -07:00
Родитель e3f0413b81
Коммит b1438bb340
26 изменённых файлов: 414 добавлений и 134 удалений

7
doc/examples/.gitignore поставляемый
Просмотреть файл

@ -6,3 +6,10 @@ account2
account3
leader_election_ring_repl
udp
leader_election_ring_udp
leader_election_ring_udp2
paraminit
paraminit3
timeout_test
udp_test
udp_test2

Просмотреть файл

@ -12,4 +12,4 @@ object foo(me:t) = {
export foo.flip
extract iso_foo(me) = foo(me)
extract iso_foo(me:t) = foo(me)

Просмотреть файл

@ -12,4 +12,4 @@ object foo(me:t) = {
export foo.flip
extract iso_foo(me) = foo(me),bit(me)
extract iso_foo(me:t) = foo(me),bit(me)

Просмотреть файл

@ -12,4 +12,4 @@ object foo(me:t) = {
export foo.flip
extract iso_foo(me) = foo(me),bit(me)
extract iso_foo(me:t) = foo(me),bit(me)

Просмотреть файл

@ -172,4 +172,4 @@ object id_impl = {
isolate iso_id = id with id_impl
extract iso_impl(me) = app(me),trans.impl(me),node_impl,id_impl,asgn
extract iso_impl(me:node.trans) = app(me),trans.impl(me),node_impl,id_impl,asgn

Просмотреть файл

@ -174,4 +174,4 @@ isolate iso_id = id with id_impl
include timeout
instance sec(X:node.t) : timeout_sec
extract iso_impl(me) = app(me),trans.impl(me),sec.impl(me),node_impl,id_impl,asgn
extract iso_impl(me:node.t) = app(me),trans.impl(me),sec.impl(me),node_impl,id_impl,asgn

Просмотреть файл

@ -10,12 +10,12 @@ unsatisfactory. Ideally, we would like a real network to take this
role.
In this section, we'll see how to accomplish that. That is, we will
implment a protocol as a collection of processes communicating over a
implement a protocol as a collection of processes communicating over a
network.
## A network interface
Ivy provides a simple interface to the Unix Datagram protocol (UDP) in
Ivy provides a simple interface to the Unix Datagram Protocol (UDP) in
a module called `udp`. Here is the interface specification:
module udp_simple(addr,pkt) = {
@ -43,14 +43,14 @@ The interface is a module with two parameters: the type `addr` of
network addresses, and the type `pkt` of packets. It has two actions,
`recv` and `send`. These are very similar to actions of the abstract
transport interface we used in the leader election protocol. The only
difference is that the `send` action the source address of the packet
as a paramater as well as the destination address. We'll see the rason
difference is that the `send` action takes the source address of the packet
as a parameter as well as the destination address. We'll see the reason
for this shortly. Action `recv` is imported by the interface (meaning
the user must implement it) while `send` is exported (meaning that the
module implements it).
the user must implement it) while `send` is exported (meaning that `udp_simple`
implements it).
As in the leader example, the specification promises that only packets
that have been sent wil be received (but packets may be dropped,
that have been sent will be received (but packets may be dropped,
duplicated or re-ordered).
For now, ignore the definition of object `impl`. We'll come back to it
@ -78,7 +78,7 @@ Here, we define a type of network addresses `a` and a type of packets
`p`. We include the `udp` module (which is part of IVy's system
libraries). We then make an instance of `udp_simple`. To allow us to
interact with this module, we import the `recv` action from the
environment and export `send`. Finally, we interp the types:
environment and export `send`. Finally, we interpret the types:
addresses are one-bit numbers and packets are 16-bit numbers. Since
we don't want to compile the specification, we extract just the
implementation of the interface, which is `foo.impl`.
@ -86,7 +86,7 @@ implementation of the interface, which is `foo.impl`.
We compile and run this program as follows:
$ ivy_to_cpp target=repl isolate=iso_impl udp_test.ivy
` $ g++ -o udp_test udp_test.cpp
$ g++ -o udp_test udp_test.cpp
$ ./udp_test
>
@ -95,10 +95,10 @@ Now we can send a packets:
> foo.send(0,1,2)
> foo.recv(1,2)
What happend here? When we started the REPL, IVY bound addresses 0 and
1 to two UDP port numbers. Call then ports A and B. We called
What happened here? When we started the REPL, IVY bound addresses 0 and
1 to two UDP port numbers. Call them ports A and B. We called
`foo.send` to send a packet from address 0 to address 1 with content
2. The module `foo` somehow marshalled the number 2 into a packet and
2. The module `foo` somehow marshaled the number 2 into a packet and
sent it out on port A. It then printed a new prompt. At this point,
the packet was received on the port B. This caused `foo.recv` to be
called, resulting in the print-out `foo.recv(1,2)` after the prompt.
@ -129,7 +129,7 @@ process performing the action. This is why `send` has the parameter
We can extract just one process as follows:
extract iso_impl(me) = foo.impl(me)
extract iso_impl(me:a) = foo.impl(me)
Here, `me` is a symbolic constant that represents the first parameter
of of each action. The first parameter is "stripped" by replacing it
@ -139,7 +139,7 @@ Before thinking about this too hard, let's just run it to see what
happens. We compile as usual:
$ ivy_to_cpp target=repl isolate=iso_impl udp_test2.ivy
` $ g++ -o udp_test2 udp_test2.cpp
$ g++ -o udp_test2 udp_test2.cpp
Now, open up a new terminal that we'll call terminal A. In that
terminal try to run `upd_test2`:
@ -169,7 +169,7 @@ Now, let's send a packet from A to B:
Notice that we omit the `src` parameter of `foo.send`, since that was
given on the command line. Similarly, when terminal B receives the
packet, it print `foo.recv(2)`, because the first parameter `dst` is
packet, it prints `foo.recv(2)`, because the first parameter `dst` is
implicit.
We can, of course, send a packet in the opposite direction:
@ -188,7 +188,7 @@ Also, one terminal can send a packet to itself:
IVy places some restriction on extracts with parameters. These
restrictions guarantee that the parallel processes behave as if they
were running sequentially in a single procees space (technically, the
were running sequentially in a single process space (technically, the
Ivy guarantees that the actions are
[*serializable*](https://en.wikipedia.org/wiki/Serializability)). We'll
discuss these restrictions shortly. For now let's look at using the
@ -197,20 +197,20 @@ discuss these restrictions shortly. For now let's look at using the
# Running a distributed protocol
Recall that the leader election protocol had an interface `trans` that
represented the network. Since this interace has the same specification
represented the network. Since this interface has the same specification
`udp_simple`, we can replace object `trans` with this code:
include udp
instance trans : udp_simple(node.t,id.t)
Notice, we use `node.t` as the `addr` type and `id.t` as the `pkt`
Notice that we use `node.t` as the `addr` type and `id.t` as the `pkt`
type. We also have to remove these declarations:
import trans.send
export trans.recv
This is because `send` is now privided by `trans`, and `recv` is now
called by `trans`. The environment is now just:
This is because `send` is now provided by `trans`, and `recv` is now
called by `trans`, not by the REPL. The environment is now just:
import serv.elect
export app.async
@ -218,10 +218,10 @@ called by `trans`. The environment is now just:
Finally, we change the `extract` declaration to include the
*implementation* of `trans`:
extract iso_impl(me) = app(me),trans.impl(me),node_impl,id_impl,asgn
extract iso_impl(me:node.t) = app(me),trans.impl(me),node_impl,id_impl,asgn
That is, we don't want the specification object `trans.spec` in the
code we actually run. Notice that we strip the first parameter of bot
code we actually run. Notice that we strip the first parameter of both
`app` and `trans.impl`. We don't strip parameters from `node_impl`,
`id_impl`, `asgn` because these objects provide functions without any
state (they are a bit like static methods in a C++ class). We'll
@ -243,10 +243,10 @@ of `trans` hasn't changed, so our inductive invariant is still good.
We might ask why we didn't have to verify `trans.impl`. This is
because this object is part of IVy's trusted code base. In fact,
`udp_simple` is implemented in C++ and makes use of the operating
system's API, which we can't formally verify. We can only verify this
object by testing.
system's API, which we can't formally verify. We can only verify
`trans.impl` by testing.
For now, let's try to run our protol. As before, we compile it like this:
For now, let's try to run our protocol. As before, we compile it like this:
$ ivy_to_cpp target=repl isolate=iso_impl leader_election_ring_udp.ivy
$ g++ -o leader_election_ring_udp leader_election_ring_udp.cpp
@ -284,7 +284,7 @@ Clearly, node 1's id did not pass node 0, so node 1 is not elected.
Our protocol works, but is still unsatisfactory in at least one
way. That is, it only does something in response to a call to
`app.async`. We'd like `app.async` to be connected to some kind of
system event to it repeasts at some interval. We can do this using the
system event so it repeats at some interval. We can do this using the
system `timeout` module. This contains the following interface:
module timeout_sec = {
@ -316,9 +316,9 @@ To test, this interface, let's write a simple program that just imports
Here's what we get:
ivy_to_cpp target=repl timeout_test.ivy
g++ -o timeout_test timeout_test.cpp
./timeout_test
$ ivy_to_cpp target=repl timeout_test.ivy
$ g++ -o timeout_test timeout_test.cpp
$ ./timeout_test
> foo.timeout
foo.timeout
foo.timeout
@ -348,7 +348,7 @@ process. We also have to remove this line, since action `app.async` no longer ex
Finally, we include the implementation of `sec` in the extract:
extract iso_impl(me) = app(me),trans.impl(me),sec.impl(me),node_impl,id_impl,asgn
extract iso_impl(me:node.t) = app(me),trans.impl(me),sec.impl(me),node_impl,id_impl,asgn
Notice that `sec.impl` also has one parameter stripped, since we have
one timer per process. We compile as before:
@ -358,10 +358,10 @@ one timer per process. We compile as before:
Now we run the processes in terminals A and B:
A: $ ./leader_election_ring_udp 0
A: $ ./leader_election_ring_udp2 0
A: >
B: $ ./leader_election_ring_udp 1
B: $ ./leader_election_ring_udp2 1
B: >
A: serve.elect
@ -369,7 +369,7 @@ Now we run the processes in terminals A and B:
A: serve.elect
...
Notice the nothing happens when we start node 0. It is sending packets
Notice that nothing happens when we start node 0. It is sending packets
once per second, but they are being dropped because no port is yet
open to receive them. After we start node 1, it forwards node 0's
packets, which causes node 0 to be elected (and to continues to be
@ -386,16 +386,16 @@ deal with this problem when we consider
[liveness](https://en.wikipedia.org/wiki/Liveness) specifications.
IVy will however, warn us that there might be a problem:
ivy_to_cpp target=repl isolate=iso_impl leader_election_ring_udp2.ivy
$ ivy_to_cpp target=repl isolate=iso_impl leader_election_ring_udp2.ivy
leader_election_ring_udp2.ivy: line 131: warning: action sec.timeout is never called
## Serializability
As mentioned above, IVy guarantees serializability. This means that
actions executed by concurrent process produce the same input/output
result as if the actions had been executed *in isolation* in some
order. By isolation, we mean that each action completes before the next
action begins.
result as if the actions had been executed [*in isolation*](https://en.wikipedia.org/wiki/ACID) in some
order. By isolation, we mean that each action completes before any other action
begins.
To guarantee serializability, IVy requires that processes be
*non-interfering*. This means that one process cannot have a
@ -417,11 +417,11 @@ As an example of interference, consider the following program:
export foo.flip
extract iso_foo(me) = foo(me)
extract iso_foo(me:t) = foo(me)
This program has one parallel process for each value of a type
`t`. Each process provides a method that modifies a global variable
`bit`. Obviously, this is not a distributed program, becuase the processes
`bit`. Obviously, this is not a distributed program, because the processes
share a variable. Here's what happens when we try to compile the program:
ivy_to_cpp target=repl isolate=iso_foo interference2.ivy
@ -449,7 +449,7 @@ process:
export foo.flip
extract iso_foo(me) = foo(me),bit(me)
extract iso_foo(me:t) = foo(me),bit(me)
This program compiles successfully. But what would happen if
one process tried to access the variable of another process.
@ -469,7 +469,7 @@ Consider this program:
export foo.flip
extract iso_foo(me) = foo(me),bit(me)
extract iso_foo(me:t) = foo(me),bit(me)
Here, object `foo(me)` accesses the `bit` of some process `x`,
where `x` is an input. Here's what happens when we compile:
@ -487,13 +487,13 @@ Of course, to communicate with each other, processes must have a
visible effect on something. That something is the environment, which
includes the operating system and the physical network. A call to
`trans.send` has an an effect on the environment, namely sending a
packet. The Ivy runtime guarantees, however, that actions remain
packet. The Ivy run-time guarantees, however, that actions remain
serializable despite these effects, using [Lipton's theory of left-movers
and right-movers](http://dl.acm.org/citation.cfm?id=361234). In the theory's terms,
receiving a packet is right-mover, while sending a packet is a
left-mover. Every action consists of zero or one receive events followed by
any number of send events. The theory tells use that such actions are
serializable. That is, we can re-orer any concurrent execution by
serializable. That is, we can re-order any concurrent execution by
commuting left-movers and right-movers such that the result is an
equivalent isolated execution.
@ -508,29 +508,29 @@ Ivy allows immutable objects (that is, read-only objects) such as the
`pid` map in the leader protocol, to be shared between processes.
Each process refers to its own local copy of the immutable object. In
the case of the `pid` map, IVy solved for a value of this function
satisfying the given axiom that no to nodes have the same id. A table
satisfying the given axiom that no two nodes have the same id. A table
of this function was stored in the compiled process.
Currently, IVy has some limitations in its ability to generate the
immutable objects and also to generate initial states for paramterized
immutable objects and also to generate initial states for parametrized
processes. Consider, for example, this program:
#lang ivy1.6
#lang ivy1.6
type t
object foo(me:t) = {
individual bit:bool
init ~bit
individual bit:bool
init ~bit
action get_bit returns (x:bool) = {
x := bit
}
action get_bit returns (x:bool) = {
x := bit
}
}
export foo.get_bit
extract iso_foo(me) = foo(me)
extract iso_foo(me:t) = foo(me)
IVy can compile this program and run it:
@ -543,16 +543,50 @@ But suppose we change the initial condition so it depends on the stripped parame
init bit <-> me = 0
That is, we want `bit` to be true initially only for process 0. Here's what happens when we try to comppile:
That is, we want `bit` to be true initially only for process 0. Here's what happens when we try to compile:
ivy_to_cpp target=repl isolate=iso_foo paraminit2.ivy
paraminit2.ivy: line 7: error: initial condition depends on stripped parameter
IVy isn't smart enough to compile an initial condition that is a
function of the stripped parameter.
function of the stripped parameter. We can work around this by
providing this function explicitly, using an initializer:
#lang ivy1.6
type t
object foo(me:t) = {
function bit:bool
after init {
bit := (me = 0)
}
action get_bit returns (x:bool) = {
x := bit
}
}
export foo.get_bit
extract iso_foo(me:t) = foo(me)
This version can be stripped and gives the expected result:
$ ivy_to_cpp target=repl isolate=iso_foo paraminit3.ivy
$ g++ -o paraminit3 paraminit3.cpp
$ ./paraminit3 0
> foo.get_bit
1
> ^C
$ ./paraminit3 1
> foo.get_bit
0
>
# Aspects

Просмотреть файл

@ -13,5 +13,5 @@ object foo(me:t) = {
export foo.get_bit
extract iso_foo(me) = foo(me)
extract iso_foo(me:t) = foo(me)

Просмотреть файл

@ -13,5 +13,5 @@ object foo(me:t) = {
export foo.get_bit
extract iso_foo(me) = foo(me)
extract iso_foo(me:t) = foo(me)

Просмотреть файл

@ -2,19 +2,19 @@
type t
object foo = {
function bit(X:t):bool
object foo(me:t) = {
function bit:bool
after init {
bit(X) := (X = 0)
bit := (me = 0)
}
action get_bit (me:t) returns (x:bool) = {
x := bit(me)
action get_bit returns (x:bool) = {
x := bit
}
}
export foo.get_bit
extract iso_foo(me) = foo(me)
extract iso_foo(me:t) = foo(me)

Просмотреть файл

@ -12,4 +12,4 @@ export foo.send
interpret a->bv[1]
interpret p->bv[16]
extract iso_impl(me) = foo.impl(me)
extract iso_impl(me:a) = foo.impl(me)

Просмотреть файл

@ -692,6 +692,20 @@ For example:
In this case, calling `c(id).my_id` will return `id`.
An alternative way to write the above would be:
type id_t
object c(id:id_t) = {
action my_id returns (x:id_t) = {
x := id
}
}
Notice that the object parameter is given as a logical constant rather
than a variable. This constant can be referred to in the body of the
object.
## Monitors
While embedding assertions in code is a useful way to write
@ -955,6 +969,81 @@ Roughly speaking, this means that assertions about an object's inputs
are assumptions for that object, while assertions about its outputs
are guarantees.
## Initializers
In some cases it is awkward to give the initial condition as a
formula. An alternative is to use an initializer. This is a special
action that is executed once initially, before any exported actions
are called. For example:
individual bit : bool
after init {
bit := false
}
This behaves like a monitor after a special internal action called
`init`. As the `after` implies, the initial condition prescribed by
the `init` declarations is assumed to be established before any
initializers are called. Initializers are executed in the order they
are declared.
Initializers may call other actions. For example, suppose we have a
module `collection` representing a set of objects that is initially
empty. If we wanted a set that initially contained the value zero, we
could use an initializer like this:
type t
object foo = {
instance myset : collection(t)
after init {
call myset.add(0)
}
}
This action is called exactly once after `myset` is initialized.
Parameterized objects can also have initializers. For example, we may
want to have a collection of objects that each contain a bit, where initially
only the bit of object 0 is true:
type t
object foo(self:t) = {
individual bit : bool
after init {
bit := (self = 0)
}
}
There are some restrictions in initializers of parameterized objects,
however. These are:
- Conditions of `if` statements may not refer to the parameter, and
- In assignments, the left-hand side must contain the parameter if the right-hand side does.
For example, this initializer would not be legal:
type t
individual bit : bool
object foo(self:t) = {
after init {
bit := (self = 0)
}
}
This is because the component `bit` being assigned is not
parameterized. This means it is in effect being assigned a different
value for each value of `self`. The restrictions guarantee that the
result of the initializer does not depend on the order in which it is
called for different parameter values.
## Interpreted types and theories
The normal way of using IVy is to declare uninterpreted types and to

Просмотреть файл

@ -7,7 +7,7 @@ from ivy_logic_utils import to_clauses, formula_to_clauses, substitute_constants
substitute_clause, substitute_ast, used_symbols_clauses, used_symbols_ast, rename_clauses, subst_both_clauses,\
variables_distinct_ast, is_individual_ast, variables_distinct_list_ast, sym_placeholders, sym_inst, apps_ast,\
eq_atom, eq_lit, eqs_ast, TseitinContext, formula_to_clauses_tseitin,\
used_symbols_asts, symbols_asts, has_enumerated_sort, false_clauses, true_clauses, or_clauses, dual_formula, Clauses, and_clauses, substitute_constants_ast, rename_ast, bool_const
used_symbols_asts, symbols_asts, has_enumerated_sort, false_clauses, true_clauses, or_clauses, dual_formula, Clauses, and_clauses, substitute_constants_ast, rename_ast, bool_const, used_variables_ast
from ivy_transrel import state_to_action,new, compose_updates, condition_update_on_fmla, hide, join_action,\
subst_action, null_update, exist_quant, hide_state, hide_state_map, constrain_state
from ivy_utils import unzip_append, IvyError, IvyUndefined, distinct_obj_renaming, dbg
@ -319,6 +319,10 @@ class AssignAction(Action):
else:
rhs = add_parameters_ast(rhs,extend)
lhs_vars = used_variables_ast(lhs)
if any(v not in lhs_vars for v in used_variables_ast(rhs)):
raise IvyError(self,"multiply assigned: {}".format(lhs.rep))
type_check(domain,rhs)
if is_individual_ast(lhs) != is_individual_ast(rhs):
# print type(lhs.rep)
@ -884,3 +888,7 @@ def action_def_to_str(name,action):
else:
res += '{' + str(action) + '}'
return res
def has_code(action):
return any(not isinstance(a,Sequence) for a in action.iter_subactions())

Просмотреть файл

@ -12,6 +12,7 @@ from string import *
import copy
import functools
import pickle
import ivy_actions
################################################################################
@ -104,8 +105,17 @@ class AnalysisGraph(object):
if ic == None:
ic = im.init_cond
s = self.domain.new_state(ic)
s2 = self.domain.new_state(ic)
self.add(s2,s)
if self.domain.initializers:
action = ivy_actions.Sequence(*[a for n,a in self.domain.initializers])
s = action_app(action,s)
with AC(self,no_add=True):
with EvalContext(check=False):
s2 = eval_state(s)
s2.expr = s
self.add(s2)
else:
s2 = self.domain.new_state(ic)
self.add(s2,s)
if abstractor:
abstractor(s2)
# print "initial state: {}".format(s2)

Просмотреть файл

@ -23,10 +23,10 @@ coverage = iu.BooleanParameter("coverage",True)
def display_cex(msg,ag):
print msg
if diagnose.get():
ui.ui_main_loop(ag)
exit(1)
exit(1)
raise iu.IvyError(None,msg)
def check_properties():
if itp.false_properties():
@ -87,6 +87,10 @@ def check_module():
ivy_isolate.create_isolate(isolate) # ,ext='ext'
check_properties()
ag = ivy_art.AnalysisGraph(initializer=ivy_alpha.alpha)
if im.module.initializers:
cex = ag.check_bounded_safety(ag.states[0])
if cex is not None:
display_cex("safety failed in initializer",cex)
with ivy_interp.EvalContext(check=False):
check_conjectures('Initiation','These conjectures are false initially.',ag,ag.states[0])
for a in sorted(im.module.public_actions):

Просмотреть файл

@ -7,7 +7,7 @@ from ivy_interp import Interp, eval_state_facts
from functools import partial
from ivy_concept_space import *
from ivy_parser import parse,ConstantDecl,ActionDef
from ivy_actions import DerivedUpdate, type_check_action, type_check, SymbolList, UpdatePattern, ActionContext, LocalAction, AssignAction, CallAction, Sequence, IfAction, AssertAction, AssumeAction, NativeAction
from ivy_actions import DerivedUpdate, type_check_action, type_check, SymbolList, UpdatePattern, ActionContext, LocalAction, AssignAction, CallAction, Sequence, IfAction, AssertAction, AssumeAction, NativeAction, has_code
from ivy_utils import IvyError
import ivy_logic
import ivy_dafny_compiler as dc
@ -21,6 +21,7 @@ import ivy_alpha
import ivy_module as im
import ivy_theory as ith
import ivy_isolate as iso
import ivy_printer
from collections import defaultdict
class IvyDeclInterp(object):
@ -552,6 +553,7 @@ def check_instantiations(mod,decls):
raise IvyError(inst,"{} undefined in instantiation".format(inst.relname))
def ivy_compile(decls,mod=None,create_isolate=True,**kwargs):
mod = mod or im.module
with mod.sig:
@ -584,7 +586,6 @@ def ivy_compile(decls,mod=None,create_isolate=True,**kwargs):
iso.create_isolate(isolate.get(),mod,**kwargs)
def clear_rules(modname):
import sys
if modname in sys.modules:

Просмотреть файл

@ -16,6 +16,7 @@ import ivy_theory as ith
import ivy_concept_space as ics
from ivy_ast import ASTContext
from collections import defaultdict
import ivy_printer
show_compiled = iu.BooleanParameter("show_compiled",False)
cone_of_influence = iu.BooleanParameter("coi",True)
@ -149,19 +150,27 @@ def set_interpret_all_sorts(t):
#def startswith_some(s,prefixes):
# return any(s.startswith(name+iu.ivy_compose_character) for name in prefixes)
def startswith_some(s,prefixes,mod):
def startswith_some_rec(s,prefixes,mod):
if s in mod.privates:
return False
parts = s.rsplit(iu.ivy_compose_character,1)
return len(parts)==2 and startswith_eq_some(parts[0],prefixes,mod)
return len(parts)==2 and startswith_eq_some_rec(parts[0],prefixes,mod)
#def startswith_eq_some(s,prefixes):
# return any(s.startswith(name+iu.ivy_compose_character) or s == name for name in prefixes)
def startswith_eq_some(s,prefixes,mod):
def startswith_eq_some_rec(s,prefixes,mod):
if s in prefixes:
return True
return startswith_some(s,prefixes,mod)
return startswith_some_rec(s,prefixes,mod)
def startswith_some(s,prefixes,mod):
s = implementation_map.get(s,s)
return startswith_some_rec(s,prefixes,mod)
def startswith_eq_some(s,prefixes,mod):
s = implementation_map.get(s,s)
return startswith_eq_some_rec(s,prefixes,mod)
def strip_map_lookup(name,strip_map,with_dot=False):
name = canon_act(name)
@ -197,7 +206,8 @@ def strip_action(ast,strip_map,strip_binding):
return ast.clone([call]+[strip_action(arg,strip_map,strip_binding) for arg in ast.args[1:]])
if isinstance(ast,ia.AssignAction):
if ast.args[0].rep.name in ivy_logic.sig.symbols:
if len(strip_map_lookup(ast.args[0].rep.name,strip_map)) != num_isolate_params:
lhs_params = strip_map_lookup(ast.args[0].rep.name,strip_map)
if len(lhs_params) != num_isolate_params:
raise iu.IvyError(ast,"assignment may be interfering")
if (ivy_logic.is_constant(ast) or ivy_logic.is_variable(ast)) and ast in strip_binding:
sname = strip_binding[ast]
@ -271,6 +281,11 @@ def strip_isolate(mod,isolate,impl_mixins,extra_strip):
global strip_added_symbols
global num_isolate_params
num_isolate_params = len(isolate.params())
ips = set(p.rep for p in isolate.params())
for atom in isolate.verified()+isolate.present():
for p in atom.args:
if p.rep not in ips:
raise iu.IvyError(p,'unbound isolate parameter: {}'.format(p))
strip_added_symbols = []
strip_map = {}
for atom in isolate.verified() + isolate.present():
@ -294,8 +309,6 @@ def strip_isolate(mod,isolate,impl_mixins,extra_strip):
new_actions = {}
for name,action in mod.actions.iteritems():
strip_params = strip_map_lookup(canon_act(name),strip_map,with_dot=False)
iu.dbg('strip_params')
iu.dbg('action.formal_params')
if not(len(action.formal_params) >= len(strip_params)):
raise iu.IvyError(action,"cannot strip isolate parameters from {}".format(name))
strip_binding = dict(zip(action.formal_params,strip_params))
@ -503,6 +516,8 @@ def isolate_component(mod,isolate_name,extra_with=[],extra_strip=None):
impl_mixins = defaultdict(list)
# delegate all the stub actions to their implementations
global implementation_map
implementation_map = {}
for actname,ms in mod.mixins.iteritems():
implements = [m for m in ms if isinstance(m,ivy_ast.MixinImplementDef)]
impl_mixins[actname].extend(implements)
@ -518,10 +533,7 @@ def isolate_component(mod,isolate_name,extra_with=[],extra_strip=None):
raise IvyError(m,'multiple implementations of action {}'.format(m.mixee()))
action = ia.apply_mixin(m,mod.actions[m.mixer()],action)
mod.actions[m.mixee()] = action
if startswith_eq_some(m.mixer(),verified,mod):
verified.add(m.mixee())
if startswith_eq_some(m.mixer(),present,mod):
present.add(m.mixee())
implementation_map[m.mixee()] = m.mixer()
new_actions = {}
use_mixin = lambda name: startswith_some(name,present,mod)
@ -755,15 +767,6 @@ def hide_action_params(action):
res = ia.LocalAction(*(params + [action]))
return res
def labeled_fmlas_to_str(kwd,lfmlas):
res = ''
for f in lfmlas:
res += kwd + ' '
if f.label:
res += '[{}] '.format(f.label)
res += str(f.formula) + '\n'
return res
def get_cone(mod,action_name,cone):
if action_name not in cone:
cone.add(action_name)
@ -780,20 +783,39 @@ def get_mod_cone(mod):
get_cone(mod,a.rep,cone)
return cone
def has_code(action):
return any(not isinstance(a,ia.Sequence) for a in action.iter_subactions())
def loop_action(action,mod):
subst = dict((p,ivy_logic.Variable('Y'+p.name,p.sort)) for p in action.formal_params)
action = lu.substitute_constants_ast(action,subst)
ia.type_check_action(action,mod) # make sure all is legal
return action
def fix_initializers(mod,after_inits):
for m in after_inits:
name = m.mixer()
extname = 'ext:'+name
action = mod.actions[extname] if extname in mod.actions else mod.actions[name]
if not ia.has_code(action):
continue
mod.initializers.append((name,loop_action(action,mod)))
if name in mod.actions:
del mod.actions[name]
if name in mod.public_actions:
mod.public_actions.remove(name)
if extname in mod.actions:
del mod.actions[extname]
if extname in mod.public_actions:
mod.public_actions.remove(extname)
ais = set(m.mixer() for m in after_inits)
mod.exports = [e for e in mod.exports if e.exported() not in ais]
def create_isolate(iso,mod = None,**kwargs):
mod = mod or im.module
# make an imaginary init operation:
init_action = ia.Sequence()
init_action.formal_params = []
init_action.formal_returns = []
init_action.lineno = None
mod.actions["init"] = init_action
# treat initializers as exports
after_inits = mod.mixins["init"]
del mod.mixins["init"]
mod.exports.extend(ivy_ast.ExportDef(ivy_ast.Atom(a.mixer()),ivy_ast.Atom('')) for a in after_inits)
# check all mixin declarations
@ -879,11 +901,6 @@ def create_isolate(iso,mod = None,**kwargs):
for a in mod.actions:
mod.public_actions.add(a)
mod.init_action = mod.actions.get('ext:.init',mod.actions['.init'])
del mod.actions['init']
del mod.actions['ext:.init']
iu.dbg('mod.init_action')
# Create one big external action if requested
@ -925,24 +942,17 @@ def create_isolate(iso,mod = None,**kwargs):
if a not in cone and not a.startswith('ext:') and a not in mixers:
ea = 'ext:' + a
if ea in mod.actions and ea not in cone:
if has_code(mod.actions[a]):
if ia.has_code(mod.actions[a]):
iu.warn(mod.actions[a],"action {} is never called".format(a))
fix_initializers(mod,after_inits)
# show the compiled code if requested
if show_compiled.get():
print ivy_logic.sig
thing = ''
for kwd,lst in [('axiom',mod.labeled_axioms),
('property',mod.labeled_props),
('init',mod.labeled_inits),
('conjecture',mod.labeled_conjs),]:
thing += labeled_fmlas_to_str(kwd,lst)
for tn in sorted(mod.sig.interp):
thing += "interp {} -> {}\n".format(tn,mod.sig.interp[tn])
print thing
for x,y in mod.actions.iteritems():
print iu.pretty(ia.action_def_to_str(x,y))
ivy_printer.print_module(mod)
def has_assertions(mod,callee):
return any(isinstance(action,ia.AssertAction) for action in mod.actions[callee].iter_subactions())
@ -966,6 +976,8 @@ def check_isolate_completeness(mod = None):
checked_context = defaultdict(set) # maps action name to set of delegees
delegates = set(s.delegated() for s in mod.delegates if not s.delegee())
delegated_to = dict((s.delegated(),s.delegee()) for s in mod.delegates if s.delegee())
global implementation_map
implementation_map = {}
for iso_name,isolate in mod.isolates.iteritems():
verified = set(a.relname for a in isolate.verified())
present = set(a.relname for a in isolate.present())

Просмотреть файл

@ -56,6 +56,7 @@ class Module(object):
self.privates = set() # set of string (names of private actions)
self.interps = defaultdict(list) # maps type names to lists of labeled interpretions
self.natives = [] # list of NativeDef
self.initializers = [] # list of name,action pairs
self.params = []
self.sig = il.sig.copy() # capture the current signature

Просмотреть файл

@ -711,23 +711,23 @@ if not (iu.get_numeric_version() <= [1,1]):
atom.lineno = get_lineno(p,3)
handle_before_after("implement",atom,p[7],p[0],p[4],p[5])
def p_top_isolate_callatom_eq_callatoms(p):
'top : top ISOLATE callatom EQ callatoms'
d = IsolateDecl(IsolateDef(*([p[3]] + p[5])))
'top : top ISOLATE SYMBOL optargs EQ callatoms'
d = IsolateDecl(IsolateDef(*([Atom(p[3],p[4])] + p[6])))
d.args[0].with_args = 0
d.lineno = get_lineno(p,2)
p[0] = p[1]
p[0].declare(d)
def p_top_isolate_callatom_eq_callatoms_with_callatoms(p):
'top : top ISOLATE callatom EQ callatoms WITH callatoms'
d = IsolateDecl(IsolateDef(*([p[3]] + p[5] + p[7])))
d.args[0].with_args = len(p[7])
'top : top ISOLATE SYMBOL optargs EQ callatoms WITH callatoms'
d = IsolateDecl(IsolateDef(*([Atom(p[3],p[4])] + p[6] + p[8])))
d.args[0].with_args = len(p[8])
d.lineno = get_lineno(p,2)
p[0] = p[1]
p[0].declare(d)
def p_top_extract_callatom_eq_callatoms(p):
'top : top EXTRACT callatom EQ callatoms'
d = IsolateDecl(IsolateDef(*([p[3]] + p[5])))
d.args[0].with_args = len(p[5])
'top : top EXTRACT SYMBOL optargs EQ callatoms'
d = IsolateDecl(IsolateDef(*([Atom(p[3],p[4])] + p[6])))
d.args[0].with_args = len(p[6])
d.lineno = get_lineno(p,2)
p[0] = p[1]
p[0].declare(d)

30
ivy/ivy_printer.py Normal file
Просмотреть файл

@ -0,0 +1,30 @@
import ivy_logic
import ivy_utils as iu
import ivy_actions as ia
def labeled_fmlas_to_str(kwd,lfmlas):
res = ''
for f in lfmlas:
res += kwd + ' '
if f.label:
res += '[{}] '.format(f.label)
res += str(f.formula) + '\n'
return res
def print_module(mod):
print ivy_logic.sig
thing = ''
for kwd,lst in [('axiom',mod.labeled_axioms),
('property',mod.labeled_props),
('init',mod.labeled_inits),
('conjecture',mod.labeled_conjs),]:
thing += labeled_fmlas_to_str(kwd,lst)
for tn in sorted(mod.sig.interp):
thing += "interp {} -> {}\n".format(tn,mod.sig.interp[tn])
print thing
for name,action in mod.initializers:
print iu.pretty("after init {" + str(action) + "}")
for x,y in mod.actions.iteritems():
print iu.pretty(ia.action_def_to_str(x,y))

Просмотреть файл

@ -890,6 +890,9 @@ def emit_one_initial_state(header):
header.append(' this->{} = {};\n'.format(name,name))
elif sym not in is_derived:
assign_symbol_from_model(header,sym,m)
action = ia.Sequence(*[a for n,a in im.module.initializers])
action.emit(header)
def emit_constant(self,header,code):

47
test/afterinit1.py Normal file
Просмотреть файл

@ -0,0 +1,47 @@
from ivy import ivy_module as im
from ivy.ivy_compiler import ivy_from_string
from ivy.tk_ui import new_ui
from ivy import ivy_utils as iu
prog = """#lang ivy1.6
type t
individual x(X:t) : t
object foo(me:t) = {
after init {
x(me) := me;
assert false
}
}
isolate iso_foo(me:t) = foo(me) with x(me)
"""
with im.Module():
iu.set_parameters({'mode':'induction','isolate':'iso_foo','show_compiled':'true'})
main_ui = new_ui()
ui = main_ui.add(ivy_from_string(prog))
main_ui.tk.update_idletasks()
main_ui.answer("OK")
ui.check_safety_node(ui.node(0))
assert not ui.node(0).safe
# ui.check_inductiveness()
# # ui = ui.cti
# cg = ui.current_concept_graph
# cg.show_relation(cg.relation('link(X,Y)'),'+')
# cg.gather()
# main_ui.answer("OK")
# cg.strengthen()
# main_ui.answer("OK")
# ui.check_inductiveness()
# # cg.show_relation(cg.relation('semaphore'),'+')
# cg.gather()
# main_ui.answer("View")
# cg.bmc_conjecture(bound=1)
# main_ui.mainloop()

34
test/afterinit2.py Normal file
Просмотреть файл

@ -0,0 +1,34 @@
from ivy import ivy_module as im
from ivy.ivy_compiler import ivy_from_string
from ivy.tk_ui import new_ui
from ivy import ivy_utils as iu
from ivy import ivy_check as ick
from ivy import ivy_utils as iu
prog = """#lang ivy1.6
type t
individual x(X:t) : t
object foo(me:t) = {
after init {
x(me) := me;
assert false
}
}
isolate iso_foo(me:t) = foo(me) with x(me)
"""
with im.Module():
iu.set_parameters({'mode':'induction','isolate':'iso_foo','show_compiled':'true'})
ivy_from_string(prog,create_isolate=False)
try:
ick.check_module()
assert False,"safety should have failed"
except iu.IvyError as e:
print e
assert str(e) == "error: safety failed in initializer"

Просмотреть файл

@ -28,7 +28,7 @@ module mod(me) = {
instance inst(X:foo) : mod(X)
isolate iso = inst(me)
isolate iso(me:foo) = inst(me)
export inst.set_me
"""

Просмотреть файл

@ -32,7 +32,7 @@ module mod(me) = {
instance inst(X:foo) : mod(X)
isolate iso = inst(me) with inst(me).baz
isolate iso(me:foo) = inst(me) with inst(me).baz
export inst.set_me
"""

Просмотреть файл

@ -30,7 +30,7 @@ instance inst(X:foo) : mod(X)
axiom inst(X).r & inst(X).x = X
isolate iso = inst(me)
isolate iso(me:foo) = inst(me)
export inst.set_me
"""