From 9d2a4a3d8a37d3109920680ce2c4d448601dc1d4 Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Sun, 6 Nov 2016 20:18:21 +0200 Subject: [PATCH 01/17] setup.py fix --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0f776daf1..6db88653f 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,8 @@ author_email='nomail@example.com', license='MIT', packages=find_packages(), - include_package_data={'ivy':['include/*.ivy','include/*.h']} + # include_package_data={'ivy':['include/*.ivy','include/*.h']} this causes error when installing + include_package_data=True, install_requires=[ 'ply', 'pygraphviz', From 9b4766795e972f7b1977d3ce03649d03d9088468 Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Sun, 6 Nov 2016 20:27:02 +0200 Subject: [PATCH 02/17] adding some examples --- .../abstract_paxos_9_proof_induction.ivy | 242 ++++++++++++++++++ examples/pldi16/alternating_bit_protocol.ivy | 205 +++++++++++++++ examples/pldi16/concensus_quorum.ivy | 116 +++++++++ examples/pldi16/concensus_unanimously.ivy | 100 ++++++++ examples/pldi16/ironfleet_toy_lock.ivy | 112 ++++++++ .../leader_election_ring_btw_nonreflexive.ivy | 129 ++++++++++ examples/pldi16/learning_switch_nodom.ivy | 118 +++++++++ examples/pldi16/verdi_mutex.ivy | 219 ++++++++++++++++ 8 files changed, 1241 insertions(+) create mode 100644 examples/pldi16/abstract_paxos_9_proof_induction.ivy create mode 100644 examples/pldi16/alternating_bit_protocol.ivy create mode 100644 examples/pldi16/concensus_quorum.ivy create mode 100644 examples/pldi16/concensus_unanimously.ivy create mode 100644 examples/pldi16/ironfleet_toy_lock.ivy create mode 100644 examples/pldi16/leader_election_ring_btw_nonreflexive.ivy create mode 100644 examples/pldi16/learning_switch_nodom.ivy create mode 100644 examples/pldi16/verdi_mutex.ivy diff --git a/examples/pldi16/abstract_paxos_9_proof_induction.ivy b/examples/pldi16/abstract_paxos_9_proof_induction.ivy new file mode 100644 index 000000000..3684a8968 --- /dev/null +++ b/examples/pldi16/abstract_paxos_9_proof_induction.ivy @@ -0,0 +1,242 @@ +#lang ivy1.3 + +################################################################################ +# +# Modules that should probably come from a standard library +# +################################################################################ + + +################################################################################ +# +# Module describing an acyclic partial function. The function is built by +# calling the "set" action. This has preconditions that enforce the required +# invariants. The function can be accessed using "dom" to query if an element is +# in the domain of the function, and "get" to get its value. The "get" action +# has a precondition that the given element must be in the domain. +# +# Because of the invariant that the relation re construct by "set" is an acyclic +# partial function, we can represent it by its transitive closure "tc", while +# remainin in EPR. +# +################################################################################ + +module acyclic_partial_function(carrier) = { + relation tc(X:carrier,Y:carrier) # transitive closure of the function + relation f(X:carrier, Y:carrier) # the function itself, but this is just an under-approximation + + # Conjectures that ensure that tc really describes the transitive closure + # of an acyclic partial function. + # These conjectures form part of the safety property. + conjecture tc(X,X) # Reflexivity + conjecture tc(X, Y) & tc(Y, Z) -> tc(X, Z) # Transitivity + conjecture tc(X, Y) & tc(Y, X) -> X = Y # Anti-symmetry + conjecture tc(X, Y) & tc(X, Z) -> (tc(Y, Z) | tc(Z, Y)) # Semi-linearity + + # conjecture about f (we can't have the iff) + conjecture f(X,Y) -> tc(X,Y) & X ~= Y & ((tc(X, Z) & X ~= Z) -> tc(Y, Z)) + conjecture f(X,Y1) & f(X,Y2) -> Y1 = Y2 + + # initially empty + init (tc(X,Y) <-> X = Y) + init ~f(X,Y) + + action set(x:carrier,y:carrier) = { + tc(X, Y) := tc(X, Y) | (tc(X, x) & tc(y, Y)); + f(x, Y) := Y = y + } + + action get(x:carrier) returns (y:carrier) = { + #assume tc(x,y) & x ~= y & ((tc(x, Z) & x ~= Z) -> tc(y, Z)) + assume f(x,y) + } +} + +################################################################################ +# +# Module for axiomatizing a total order +# +################################################################################ + +module total_order(r) = { + axiom r(X,X) # Reflexivity + axiom r(X, Y) & r(Y, Z) -> r(X, Z) # Transitivity + axiom r(X, Y) & r(Y, X) -> X = Y # Anti-symmetry + axiom r(X, Y) | r(Y, X) # Totality +} + + +################################################################################ +# +# Types, relations and functions describing state of the network +# +################################################################################ + +type node +type value +type quorum +type round + +individual first : round +individual none: round +relation le(X:round, Y:round) +instantiate total_order(le) +axiom le(none, X) +axiom ~le(first, none) +axiom ~le(X, none) -> le(first, X) + +relation member(N:node, Q:quorum) +# forall Q1 Q2 exists N. member(N, Q1) & member(N, Q2) +individual quorum_intersection(Q1:quorum, Q2:quorum) : node # a Skolem function +axiom member(quorum_intersection(Q1, Q2), Q1) +axiom member(quorum_intersection(Q1, Q2), Q2) + +# ghost functions for recording the existence of quorums and voter nodes +individual quorum_of_proposal(R:round) : quorum +individual quorum_of_decision(R:round) : quorum + +relation proposal(R1:round, V:value) # 2a +instantiate p_round : acyclic_partial_function(round) # a proposal round ghost partial function modeled by its transitive closure + +relation vote(N:node, R:round, V:value) # 2b +relation decision(R:round, V:value) # got 2b from a quorum +relation rnd(N:node, R:round) # rnd(n,r) means node n sent 1b to round r + + +init ~proposal(R,V) +init ~vote(N,R,V) +init ~decision(R,V) +init rnd(N, R) <-> R = first + +action cast_vote = { + # phase 2b + local n:node, v:value, r:round { + assume r ~= none; + assume rnd(n, r) & (rnd(n,R) -> le(R,r)); + assume proposal(r, v); + vote(n, r, v) := true + } +} + +action decide = { + # get 2b from a quorum + local r:round, v:value, q:quorum { + assume r ~= none; + assume member(N, q) -> vote(N, r, v); + quorum_of_decision(r) := q; + decision(r, v) := true + } +} + +action join_round = { + # receive 1a and answer with 1b + local n:node, r:round { + assume r ~= none; + assume rnd(n,R) -> ~le(r,R); + # node n joins r2: + rnd(n, r) := true + } +} + +action propose = { + local r:round, v:value, q:quorum, pr:round, voter:node { + assume r ~= none; + assume ~proposal(r,V); + assume p_round.tc(r,R) -> r = R; # this is actually not so good that we need this here + assume member(N, q) -> rnd(N,r); + + # find the latest vote in the quorum before round r + if ~(member(VOTER:node, q) & vote(VOTER:node, PR:round, V:value) & ~le(r, PR:round)) { + assume pr = none + } else { + assume member(voter, q) & vote(voter, pr, v) & ~le(r, pr); + assume member(VOTER ,q) & vote(VOTER, PR, V) & ~le(r, PR) -> le(PR, pr) + }; + proposal(r, v) := true; + quorum_of_proposal(r) := q; + call p_round.set(r, pr) + } +} + +export cast_vote +export join_round +export decide +export propose + +# Bogus conjectures to test the system + +# # counter-example in 7 steps: +# conjecture ~( +# R1 ~= R2 & +# decision(R1,V1) & +# decision(R2,V2) +# ) + +# # counter-example in 11 steps: +# conjecture ~( +# R1 ~= R2 & R3 ~= R2 & R1 ~= R3 & +# decision(R1,V1) & +# decision(R2,V2) & +# decision(R3,V3) +# ) + +# safety property: +conjecture ( + decision(R1,V1) & + decision(R2,V2) +) -> V1 = V2 + +# a proposal in round comes from a quorum: +conjecture proposal(R,V) & member(N, quorum_of_proposal(R)) -> rnd(N, R) + +# proposals are unique per round +conjecture proposal(R,V1) & proposal(R,V2) -> V1 = V2 + +# decisions come from quorums of votes: +conjecture decision(R,V) & member(N, quorum_of_decision(R)) -> vote(N,R,V) + +# only vote for joined rounds +conjecture vote(N,R,V) -> rnd(N,R) + +# only vote for proposed values +conjecture vote(N,R,V) -> proposal(R,V) + +# decisions are respected by future proposals +# conjecture le(R1, R2) & decision(R1,V1) & proposal(R2,V2) -> V1 = V2 +# +# decisions are respected by future proposals +conjecture le(R1, R2) & decision(R1,V1) & proposal(R2,V2) -> V1 = V2 + +# need to apply induction to obtain the minimal R2 that violates the +# above conjecture, and then obtain its p_round.f +individual min_bad_r1:round +individual min_bad_r2:round +individual min_bad_v1:value +individual min_bad_v2:value +individual min_bad_f:round +axiom ( + # if R2 is a bad round w.r.t. R1, then min_bad_r2 is bad w.r.t. min_bad_r1, and R2 >= min_bad_r2 + (le(R1, R2) & decision(R1,V1) & proposal(R2,V2) & V1 ~= V2) -> ( + (le(min_bad_r1, min_bad_r2) & decision(min_bad_r1,min_bad_v1) & proposal(min_bad_r2,min_bad_v2) & min_bad_v1 ~= min_bad_v2) & + le(min_bad_r2, R2) + ) +) +# manually instantiate an AE axiom for min_bad_r2 +axiom p_round.tc(min_bad_r2,R) & min_bad_r2 ~= R -> p_round.f(min_bad_r2, min_bad_f) + +# properties of none +conjecture ~proposal(none,V) +conjecture ~rnd(N,none) +conjecture ~vote(N,none,V) +conjecture ~decision(none,V) + +# properties of p_round.tc +conjecture p_round.tc(R1,R2) -> le(R2,R1) +conjecture proposal(R,V) -> p_round.tc(R,none) +conjecture proposal(R1,V1) & proposal(R2,V2) & p_round.tc(R1,R2) -> V1 = V2 +conjecture ~(p_round.tc(R3,R2) & proposal(R3,V0) & ~proposal(R2,V0) & R2 ~= none) + +# properties of p_round.f +# we want to say that p_round is the last voted round, but we have to use p_round.f +conjecture ~le(R3,R2) & proposal(R3,V3) & member(N,quorum_of_proposal(R3)) & vote(N,R2,V2) & p_round.f(R3,R1) -> le(R2,R1) +# now all we have to say but can't is proposal(R,V) -> exists PR. p_round.f(R,PR) diff --git a/examples/pldi16/alternating_bit_protocol.ivy b/examples/pldi16/alternating_bit_protocol.ivy new file mode 100644 index 000000000..9401dd35f --- /dev/null +++ b/examples/pldi16/alternating_bit_protocol.ivy @@ -0,0 +1,205 @@ +#lang ivy1.3 + +# +# A basic version of alternating bit protocol with binary (1-bit) +# sequence number and FIFO channels. +# + +################################################################################ +# +# Module for axiomatizing a totally ordered set with fixed order +# +# The module includes an le relation, a minimal element (zero) and +# get_succ and get_pred actions. +# +# In this module, the order is arbitrary and fixed. +# +################################################################################ + +module total_order(carrier) = { + relation le(X:carrier,Y:carrier) # less than or equal + + axiom le(X, X) # Reflexivity + axiom le(X, Y) & le(Y, Z) -> le(X, Z) # Transitivity + axiom le(X, Y) & le(Y, X) -> X = Y # Anti-symmetry + axiom le(X, Y) | le(Y, X) # Totality + + individual zero:carrier + axiom le(zero, X) + + action get_succ(x:carrier) returns (y:carrier) = { + assume le(x,y) & x ~= y & ((le(x, Z) & x ~= Z) -> le(y, Z)) + } + + action get_pred(y:carrier) returns (x:carrier) = { + assume le(x,y) & x ~= y & ((le(x, Z) & x ~= Z) -> le(y, Z)) + } +} + + +################################################################################ +# +# A module for a fifo channel +# +################################################################################ + +module fifo_channel(m_t) = { + relation le(X:m_t,Y:m_t) # partial order representing + # messages in the channel - + # larger messages are older + + conjecture le(X, Y) & le(Y, Z) -> le(X, Z) # Transitivity + conjecture le(X, Y) & le(Y, X) -> X = Y # Anti-symmetry + conjecture le(X, Y) -> le(X, X) & le(Y, Y) # Partial reflexivity + conjecture le(X, X) & le(Y, Y) -> le(X, Y) | le(Y, X) # Partial Totality + + init ~le(X, Y) + + action send(m: m_t) = { + # insert m as a newest message + assume ~le(m, m); + le(m, m) := true; + le(m, X) := le(X,X) + } + + action receive returns (m: m_t) = { + # receive the oldest message and remove it + assume le(m, m); + assume le(m,X) -> X = m; + le(X,m) := false + } + + action drop = { + # drop a message + local m: m_t { + le(X,Y) := le(X, Y) & X ~= m & Y ~= m + } + } +} + + +################################################################################ +# +# Types, relations and functions describing state of the network +# +################################################################################ + +# a totally ordered set for indices +type index +instantiate index : total_order(index) + +# an uninterpreted sort for data items +type value +individual bot:value # special bottom value + +# data messages (sent from sender to received), with a fifo channel, +# and a data item and sequence bit for every message +type data_msg_t +instantiate data_msg : fifo_channel(data_msg_t) +individual d(D:data_msg_t) : value +relation dbit(D:data_msg_t) +# need to also remember the index where the data came from +individual d_i(D:data_msg_t) : index + + + +# ack messages (sent from receiver to sender), with a fifo channel and +# a sequence number bit for every message. +type ack_msg_t +instantiate ack_msg : fifo_channel(ack_msg_t) +relation abit(A:ack_msg_t) + +# sender array and receiver array +individual sender_array(I:index) : value +individual receiver_array(I:index) : value +init receiver_array(I) = bot +init sender_array(I) ~= bot + +# sender index and receiver index +individual sender_i:index +init sender_i = index.zero +individual receiver_i:index +init receiver_i = index.zero + +# sender and receiver bits, initially 0 (false) +relation sender_bit(I:index) +init ~sender_bit(I) +relation receiver_bit(I:index) +init ~receiver_bit(I) +conjecture I ~= index.zero -> ~sender_bit(I) & ~receiver_bit(I) + +################################################################################ +# +# Protocol actions +# +################################################################################ + +action sender_send = { + # send (sender_array(sender_i), sender_bit) + local m:data_msg_t { + assume d(m) = sender_array(sender_i); + assume d_i(m) = sender_i; + assume dbit(m) <-> sender_bit(index.zero); + call data_msg.send(m) + } +} + +action sender_receive_ack = { + local a:ack_msg_t { + a := ack_msg.receive(); + if abit(a) <-> sender_bit(index.zero) { + sender_bit(index.zero) := ~sender_bit(index.zero); + sender_i := index.get_succ(sender_i); + call sender_send + } + } +} + +action receiver_receive = { + local m:data_msg_t, a:ack_msg_t { + m := data_msg.receive(); + if dbit(m) <-> receiver_bit(index.zero) { + # send ack with receiver_bit + assume abit(a) <-> receiver_bit(index.zero); + call ack_msg.send(a); + + # flip receiver bit, append to receiver array + receiver_bit(index.zero) := ~receiver_bit(index.zero); + receiver_array(receiver_i) := d(m); + receiver_i := index.get_succ(receiver_i) + } + } +} + +export sender_send +export sender_receive_ack +export receiver_receive +export data_msg.drop +export ack_msg.drop + +################################################################################ +# +# Safety and inductive invariant +# +################################################################################ + +# # Bogus conjecture used to make BMC return a trace receiver array has 4 values +# # takes around 15 seconds to find a trace +# conjecture ~( +# ~index.le(I1,index.zero) & +# ~index.le(I2, I1) & +# ~index.le(I3, I2) & +# receiver_array(I3) ~= bot +# ) + +# safety - receiver array has values from sender array +conjecture receiver_array(I) = bot | receiver_array(I) = sender_array(I) + +# conjectures obtained interactively + +conjecture sender_array(I0) ~= bot +conjecture ~(data_msg.le(D0,D0) & d(D0) ~= sender_array(I0) & sender_array(d_i(D0)) = sender_array(I0)) +conjecture ~(index.le(receiver_i,sender_i) & sender_i ~= receiver_i) + +conjecture (sender_bit(index.zero) <-> receiver_bit(index.zero)) <-> sender_i = receiver_i +conjecture index.le(I,sender_i) | index.le(receiver_i, I) diff --git a/examples/pldi16/concensus_quorum.ivy b/examples/pldi16/concensus_quorum.ivy new file mode 100644 index 000000000..b007ba44d --- /dev/null +++ b/examples/pldi16/concensus_quorum.ivy @@ -0,0 +1,116 @@ +#lang ivy1.3 + +# +# A quorum based concencus protocol. +# Similar to a single round of Paxos (I think). +# +# This has an interesting AE issue: every two quorums have a node in their intersection, +# and every decided node should have a quorum that it decided based on - this is cyclic. +# Here we get around this by remembering the quorum in the decided relation. +# + +type node +type value +type quorum + +relation member(N:node, Q:quorum) + +# forall Q1 Q2 exists N. member(N, Q1) & member(N, Q2) +individual quorum_intersection(Q1:quorum, Q2:quorum) : node # a Skolem function +axiom member(quorum_intersection(Q1, Q2), Q1) +axiom member(quorum_intersection(Q1, Q2), Q2) + +module lone(f) = { + axiom ~f(X, Y1) | ~f(X, Y2) | Y1 = Y2 +} + +relation val(N:node, V:value) # node N has value V +relation decided(N:node, Q:quorum) # node N has decided based on quorum Q + +instantiate lone(val) + +relation propose(S:node, V:value, R:node) # node S proposed value V to node R +relation ack(S:node, V:value, R:node) # node S acknowledged value V to node R +relation knowledge(X:node, Y:node, V:value) # node X knows node Y has value V + +init ~propose(X, Y, Z) +init ~ack(X, Y, Z) +init ~knowledge(X, Y, Z) +init ~decided(X, Q) +init ~val(X, Y) + + +action prop = { + local n:node, v:value { + # chose a node without a value and propose an arbitraty value to everyone (including self) + assume ~val(n, V); + propose(n, v, X) := true + } +} + +action receive_prop = { + local n1:node, n2:node, v1:value, v2:value { + # process a propose message and reply an acknowledge message + assume propose(n1, v1, n2); + if ~val(n2, V:value) & ~decided(n2, Q:quorum) { + # if we have no value and are undecided - take the proposed value + + # actually, we would like to remove the undecided part + # from the if, but this would require an AE invariant! + + val(n2, v1) := true + }; + # acknowledge back with n2's current value + assume val(n2, v2); + ack(n2, v2, n1) := true + } +} + +action receive_ack = { + local n1:node, n2:node, v:value { + # process an acknowledge message by updating the knowledge + assume ack(n1, v, n2); + knowledge(n2, n1, v) := true + } +} + +action decide = { + local n:node, v:value, q:quorum { + # assume that node n has value v, and has knowledge of a quorum that also has v, and make n decided. + assume val(n, v); + assume member(N, q) -> knowledge(n, N, v); + decided(n, q) := true + } +} + +export prop +export receive_prop +export receive_ack +export decide + + +# The safety property +conjecture (decided(N1, Q1) & decided(N2, Q2) & val(N1, V1) & val(N2, V2)) -> V1 = V2 + +# found interactiveley - second attempt (after knowing the first attempt below) +# conjecture ~(knowledge(N0,N1,V0) & ~ack(N1,V0,N0)) # taken from concensus_unanimously +# conjecture ~(ack(N1,V1,N0) & ~val(N1,V1)) # taken from concensus_unanimously +# conjecture ~(decided(N1,Q0) & member(N0,Q0) & val(N1,V1) & ~val(N0,V1)) + + +### + +# # found using IND-MUSMSS +# conjecture ~(knowledge(N1, N0, V0) & ~ack(N0, V0, N1)) +# # conjecture ~(decided(N0, Q0) & member(N0, Q0) & val(N0, V0) & ~knowledge(N0, N0, V0)) +# conjecture ~(decided(N1, Q0) & member(N0, Q0) & val(N1, V1) & ~knowledge(N1, N0, V1)) +# conjecture ~(ack(N1, V1, N0) & ~val(N1, V1)) + +### + +# found interactiveley - first attempt +# conjecture ~(knowledge(N1, N1, V0) & val(N1, V1) & V0 ~= V1) +# conjecture ~(decided(N0, Q) & member(N1, Q) & val(N0, V0) & val(N1, V1) & V0 ~= V1) +# conjecture ~(ack(N0, V1, N1) & val(N0, V0) & V1 ~= V0) +# conjecture ~(ack(N0, V0, N1) & ~val(N0, V0)) +# conjecture ~(knowledge(N0, N1, V0) & ~ack(N1, V0, N0)) diff --git a/examples/pldi16/concensus_unanimously.ivy b/examples/pldi16/concensus_unanimously.ivy new file mode 100644 index 000000000..a203ee890 --- /dev/null +++ b/examples/pldi16/concensus_unanimously.ivy @@ -0,0 +1,100 @@ +#lang ivy1.3 + +# +# A very basic exercise in verification of concencus protocols (ala Paxos). +# +# A contrived protocol that reaches concensus only when a node hears +# that all nodes have the same value. +# +# It does contain an interesting AE example (see below in the receive_prop action). +# + +type node +type value + +module lone(f) = { + axiom ~f(X, Y1) | ~f(X, Y2) | Y1 = Y2 +} + +module injective(f) = { + axiom ~f(X1, Y) | ~f(X2, Y) | X1 = X2 +} + +relation val(N:node, V:value) # node N has value V +relation decided(N:node) # node N has decided + +instantiate lone(val) + +relation propose(S:node, V:value, R:node) # node S proposed value V to node R +relation ack(S:node, V:value, R:node) # node S acknowledged value V to node R +relation knowledge(X:node, Y:node, V:value) # node X knows node Y has value V + +init ~propose(X, Y, Z) +init ~ack(X, Y, Z) +init ~knowledge(X, Y, Z) +init ~decided(X) +init ~val(X, Y) + +action prop = { + local n:node, v:value { + # chose a node without a value and propose an arbitraty value to everyone (including self) + assume ~val(n, V); + propose(n, v, X) := true + } +} + +action receive_prop = { + local n1:node, n2:node, v1:value, v2:value { + # process a propose message and reply an acknowledge message + assume propose(n1, v1, n2); + if ~val(n2, V:value) & ~decided(n2) { + # if we have no value and are undecided - take the proposed value + + # actually, we would like to remove the undecided part + # from the if, but this would require an AE invariant! + + val(n2, v1) := true + }; + # acknowledge back with n2's current value + assume val(n2, v2); + ack(n2, v2, n1) := true + } +} + +action receive_ack = { + local n1:node, n2:node, v:value { + # process an acknowledge message by updating the knowledge + assume ack(n1, v, n2); + knowledge(n2, n1, v) := true + } +} + +action decide = { + local n:node, v:value { + # assume that you know all nodes have the same value v, become decided. + assume knowledge(n, N, v); + decided(n) := true + } +} + +export prop +export receive_prop +export receive_ack +export decide + +# the safety property +conjecture (decided(N1) & decided(N2) & val(N1, V1) & val(N2, V2)) -> V1 = V2 + +# found interactiveley on first attempt +# conjecture ~(knowledge(N1, N1, V0) & val(N1, V1) & V0 ~= V1) +# conjecture ~(decided(N0) & val(N0, V1) & val(N1, V0) & V0 ~= V1) +# conjecture ~(ack(N0, V1, N1) & val(N0, V0) & V1 ~= V0) +# conjecture ~(ack(N0, V0, N0) & ~val(N0, V0)) +# conjecture ~(decided(N0) & val(N0, V1) & ~val(N1, V1)) +# conjecture ~(knowledge(N0, N1, V0) & ~ack(N1, V0, N0)) +# conjecture ~(ack(N0, V0, N1) & ~val(N0, V0)) + +# found interactively later (with knowledge of the previous invariant) +# ~(knowledge(N0,N1,V0) & ~ack(N1,V0,N0)) +# ~(ack(N0,V1,N0) & ~val(N0,V1)) # subsumed by the next one +# ~(ack(N1,V1,N0) & ~val(N1,V1)) diff --git a/examples/pldi16/ironfleet_toy_lock.ivy b/examples/pldi16/ironfleet_toy_lock.ivy new file mode 100644 index 000000000..8c31880ed --- /dev/null +++ b/examples/pldi16/ironfleet_toy_lock.ivy @@ -0,0 +1,112 @@ +#lang ivy1.3 + +# +# An Ivy model of the toy lock example from https://github.com/Microsoft/Ironclad/blob/master/ironfleet/src/Dafny/Distributed/Protocol/Lock/Node.i.dfy +# + +# A total order helper module +module total_order(r) = { + axiom r(X,X) # Reflexivity + axiom r(X, Y) & r(Y, Z) -> r(X, Z) # Transitivity + axiom r(X, Y) & r(Y, X) -> X = Y # Anti-symmetry + axiom r(X, Y) | r(Y, X) # Totality +} + +################################################################################ +# +# Types, relations and functions describing state of the network +# +################################################################################ + +type node +type epoch + +# epochs are totally ordered with a least element called zero +relation le(X:epoch, Y:epoch) +instantiate total_order(le) +individual zero:epoch +axiom le(zero, X) + +individual ep(N:node) : epoch # ep(n) is the current epoch of node n + +relation held(N:node) # held(n) is true iff the lock is currently held by node n + +# initially exactly one node holds the lock, and all others have epoch zero +individual first:node +init held(X) <-> X=first +init N ~= first -> ep(N) = zero + +# transfer messages +relation transfer(E:epoch, N:node) # the node is the message destination +init ~transfer(E, N) + +# locked messages +relation locked(E:epoch, N:node) # the node is the message source +init ~locked(E, N) + +################################################################################ +# +# Protocol description +# +################################################################################ + +action grant = { + local n1:node, n2:node, e:epoch { + # release the lock and send a transfer message + assume held(n1); + assume ~le(e, ep(n1)); # jump to some strictly higher epoch + transfer(e, n2) := true; + held(n1) := false + } +} + +action accept = { + local n:node, e:epoch { + # receive a transfer message and take the lock, sending a locked message + assume transfer(e,n); + if ~le(e, ep(n)) { + held(n) := true; + ep(n) := e; + locked(e, n) := true + } + } +} + +export grant +export accept + +# a bogous conjecture to use BMC to test aht we can actually transfer the lock +# conjecture locked(E, N) -> N = first + +# the safety property +conjecture locked(E, N1) & locked(E, N2) -> N1 = N2 + +# obtained interactively from CTI's (first attempt) +# conjecture ~(locked(E1,N0) & transfer(E1,N1) & N1 ~= N0) # not really needed +# conjecture ~(le(ep(N1),E1) & locked(E1,N1) & E1 ~= ep(N1)) +# conjecture ~(held(N1) & le(ep(N1),E1) & locked(E1,N0) & N1 ~= N0) # not really needed +# conjecture ~(transfer(E1,N0) & transfer(E1,N1) & N1 ~= N0) +# conjecture ~(held(N1) & le(ep(N1),E1) & transfer(E1,N1) & E1 ~= ep(N1)) # not really needed +# conjecture ~(held(N0) & le(ep(N0),E1) & transfer(E1,N1) & E1 ~= ep(N0)) +# conjecture ~(held(N0) & held(N1) & N1 ~= N0) # not really needed +# conjecture ~(le(ep(N0),E1) & le(ep(N0),E2) & transfer(E1,N0) & transfer(E2,N0) & E2 ~= E1 & ep(N0) ~= E1 & ep(N0) ~= E2) # not really needed +# conjecture ~(transfer(ep(N1),N0) & N1 ~= N0) # not really needed +# conjecture ~(held(N1) & le(ep(N1),ep(N0)) & N1 ~= N0) +# conjecture ~(le(E1,ep(N1)) & le(ep(N0),E1) & transfer(E1,N0) & ep(N0) ~= E1) +# conjecture ~(le(E1,E2) & le(ep(N0),E1) & transfer(E1,N0) & transfer(E2,N1) & E2 ~= E1 & ep(N0) ~= E1) + +# the invariant of the first attempt after removal of unnecessary conjectures: +# conjecture ~(le(ep(N1),E1) & locked(E1,N1) & E1 ~= ep(N1)) +# conjecture ~(transfer(E1,N0) & transfer(E1,N1) & N1 ~= N0) +# conjecture ~(held(N0) & le(ep(N0),E1) & transfer(E1,N1) & E1 ~= ep(N0)) +# conjecture ~(held(N1) & le(ep(N1),ep(N0)) & N1 ~= N0) +# conjecture ~(le(E1,ep(N1)) & le(ep(N0),E1) & transfer(E1,N0) & ep(N0) ~= E1) +# conjecture ~(le(E1,E2) & le(ep(N0),E1) & transfer(E1,N0) & transfer(E2,N1) & E2 ~= E1 & ep(N0) ~= E1) + +# a prettyfied version of the above invariant +conjecture transfer(E, N1) & transfer(E, N2) -> N1 = N2 # epochs transfer to at most one node +conjecture locked(E, N) -> le(E, ep(N)) # if a node sent a locked msg, the node's epoch is now higher +conjecture held(N) & N ~= M -> ~le(ep(N), ep(M)) # holding node's epoch is higher than any other node's epoch (this implies a single node holds the lock) +conjecture held(N) & transfer(E, M) -> le(E, ep(N)) # holding node's epoch is higher than any transfer's epoch +conjecture transfer(E, N) & ~le(E, ep(N)) -> ~le(E, ep(M)) # pending transfer epoch is higher than any node's epoch +conjecture transfer(E, N) & ~le(E, ep(N)) & transfer(F, M) -> le(F, E) # pending transfer epoch is higher than any transfer's epoch diff --git a/examples/pldi16/leader_election_ring_btw_nonreflexive.ivy b/examples/pldi16/leader_election_ring_btw_nonreflexive.ivy new file mode 100644 index 000000000..ca4dcfb77 --- /dev/null +++ b/examples/pldi16/leader_election_ring_btw_nonreflexive.ivy @@ -0,0 +1,129 @@ +#lang ivy1.3 + +################################################################################ +# +# A module for axiomatizing a total order +# +################################################################################ + +module total_order(r) = { + axiom r(X,X) # Reflexivity + axiom r(X, Y) & r(Y, Z) -> r(X, Z) # Transitivity + axiom r(X, Y) & r(Y, X) -> X = Y # Anti-symmetry + axiom r(X, Y) | r(Y, X) # Totality +} + + +################################################################################ +# +# Module describing a ring topology. +# +# The module includes an anti-reflexive ternary btw relation. +# +# The module also includes get_next and get_prev actions. +# +# In this module, the ring topology is arbitrary and fixed. +# +################################################################################ + +module ring_topology(carrier) = { + + relation btw(X:carrier,Y:carrier, Z:carrier) # Y is on the acyclic path from X to Z + + # Axiom defining the btw relation - note it's not reflexive + # not needed: axiom btw(X,Y,Z) -> X ~= Y & X ~= Z & Y ~= Z # anti-reflexive + axiom btw(W,X,Y) & btw(W,Y,Z) -> btw(W,X,Z) # transitive + axiom btw(W,X,Y) -> ~btw(W,Y,X) # acyclic + axiom btw(W,X,Y) | btw(W,Y,X) | W=X | W=Y | X=Y # total + axiom btw(X,Y,Z) -> btw(Y,Z,X) # cyclic permutations + + action get_next(x:carrier) returns (y:carrier) = { + assume x ~= y & ((Z ~= x & Z ~= y) -> btw(x,y,Z)) + } + + action get_prev(y:carrier) returns (x:carrier) = { + assume y ~= x & ((Z ~= y & Z ~= x) -> btw(y,x,Z)) + } + +} + + +################################################################################ +# +# Types, relations and functions describing state of the network +# +################################################################################ + +type node +type id + +# A ring topology of nodes +instantiate ring : ring_topology(node) + +# A total order on ids +relation le(X:id, Y:id) +instantiate total_order(le) + +# A function relating a node to its id +individual idn(X:node) : id +axiom idn(X) = idn(Y) -> X = Y # the idn function is injective + +# A relation that keeps track of nodes that think they are the leader +relation leader(N:node) +init ~leader(N) + +# A relation for pending messages, a message is just an id +relation pending(V:id, N:node) # The identity V is pending at node N +init ~pending(V, N) + + +################################################################################ +# +# Protocol description +# +# Two action: send and receive +# +################################################################################ + +action send = { + local n1:node, n2:node { + # send my own id to the next node + n2 := ring.get_next(n1); + pending(idn(n1), n2) := true + } +} + +action receive = { + local n1:node, n2:node, m:id { + # receive a message from the right neighbor + assume pending(m, n1); + pending(m, n1) := *; # abstract the number of pending messages + if m = idn(n1) { # Found a leader + leader(n1) := true + } else { + if le(idn(n1), m) { # pass message to next node + n2 := ring.get_next(n1); + pending(m, n2) := true + } # otherwise drop the message... + } + } +} + +export send +export receive + +# bogus conjecture +# conjecture ~( +# N1 ~= N2 & +# N1 ~= N3 & +# N2 ~= N3 & +# leader(N1) +# ) + +# The safety property: +conjecture leader(X) & leader(Y) -> X = Y # at most one leader + +# conjectures obtained via CTI's +# conjecture ~(le(idn(N1),idn(N0)) & pending(idn(N1),N1) & idn(N1) ~= idn(N0)) +# conjecture ~(le(idn(N2),idn(N0)) & pending(idn(N2),N1) & ring.btw(N0,N1,N2)) +# conjecture ~(le(idn(N1),idn(N0)) & leader(N1) & N1 ~= N0) diff --git a/examples/pldi16/learning_switch_nodom.ivy b/examples/pldi16/learning_switch_nodom.ivy new file mode 100644 index 000000000..f7a2d8afe --- /dev/null +++ b/examples/pldi16/learning_switch_nodom.ivy @@ -0,0 +1,118 @@ +#lang ivy1.3 + +################################################################################ +# +# Module describing an acyclic partial function. The function is built by +# calling the "set" action. This has preconditions that enforce the required +# invariants. The function can be accessed using "dom" to query if an element is +# in the domain of the function, and "get" to get its value. The "get" action +# has a precondition that the given element must be in the domain. +# +# Because of the invariant that the relation re construct by "set" is an acyclic +# partial function, we can represent it by its transitive closure "tc", while +# remainin in EPR. +# +################################################################################ + +module acyclic_partial_function(carrier) = { + relation tc(X:carrier,Y:carrier) # transitive closure of the function + + # Conjectures that ensure that tc really describes the transitive closure + # of an acyclic partial function. + # These conjectures form part of the safety property. + conjecture tc(X,X) # Reflexivity + conjecture tc(X, Y) & tc(Y, Z) -> tc(X, Z) # Transitivity + conjecture tc(X, Y) & tc(Y, X) -> X = Y # Anti-symmetry + conjecture tc(X, Y) & tc(X, Z) -> (tc(Y, Z) | tc(Z, Y)) # Semi-linearity + + init (tc(X,Y) <-> X = Y) #initially empty + + + action set(x:carrier,y:carrier) = { + tc(X, Y) := tc(X, Y) | (tc(X, x) & tc(y, Y)) + } + + action get(x:carrier) returns (y:carrier) = { + assume tc(x,y) & x ~= y & ((tc(x, Z) & x ~= Z) -> tc(y, Z)) + } +} + +################################################################################ +# +# Types, relations and functions describing state of the network +# +################################################################################ + +type packet +type node + +relation pending(P:packet, S:node, T:node) # relation for pending packets +individual src(P:packet) : node # function src : packet -> node +individual dst(P:packet) : node # function dst : packet -> node +relation link(S:node, T:node) # relation for network topology +instantiate route(N:node) : acyclic_partial_function(node) # routing tables + +axiom ~link(X, X) # no self-loops in links +axiom ~link(X, Y) | link(Y, X) # symmetric links + +# The initial state of the network (empty) + +init ~pending(P,S,T) + +################################################################################ +# +# Protocol description +# +# Two action: new_packet and receive +# +################################################################################ + +action new_packet = { + local p:packet { + # Create a new packet, by adding it to pending from the src to itself + pending(p, src(p), src(p)) := true + } +} + +action receive = { + local p:packet, sw0:node, sw1:node, sw2:node { + # receive a pending packet from sw0 to sw1 and process it + + ######################################## + # The action's guard. + assume pending(p, sw0, sw1); + + ######################################## + # Abstract the number of times that the same packet recieved + pending(p, sw0, sw1) := *; + + ######################################## + # learn: if no route from receiving switch back to source... + if ((route(src(p)).tc(sw1,X:node) -> X:node=sw1) & src(p) ~= sw1) { + call route(src(p)).set(sw1, sw0) # create new route from sw1 to sw0 + }; + + ######################################## + # forward the packet if destination is not self + if dst(p) ~= sw1 { + if (route(dst(p)).tc(sw1,X:node) -> X:node=sw1) { # if no route from sw1 to to dst(p)... + pending(p, sw1, Y) := link(sw1, Y) & Y ~= sw0 # flood + } else { + sw2 := route(dst(p)).get(sw1); # get the routing table entry + pending(p, sw1, sw2) := true # route the packet there + } + } + } +} + +export new_packet +export receive + +# The safety property is given by the conjectures of the +# acyclic_partial_function module, that state that the routing tables +# do not create cycles. + +# obtained interactively via CTI's +conjecture ~(route.tc(N1,N1,N0) & N1 ~= N0) +conjecture ~(route.tc(N2,N1,N0) & ~route.tc(N2,N1,N2) & N1 ~= N0) +conjecture ~(pending(P0,N2,N1) & ~route.tc(src(P0),N2,src(P0))) diff --git a/examples/pldi16/verdi_mutex.ivy b/examples/pldi16/verdi_mutex.ivy new file mode 100644 index 000000000..19756e99a --- /dev/null +++ b/examples/pldi16/verdi_mutex.ivy @@ -0,0 +1,219 @@ +#lang ivy1.3 + +# +# The Mutex example (Figure 3) from the Verdi paper's overview section. +# +# This file also includes two modules used to model lists and message +# pools. +# +# [Verdi] Wilcox, James R., et al. "Verdi: A framework for +# implementing and formally verifying distributed systems." PLDI 2015. +# +# + + + +################################################################################ +# +# Module describing an ordered list. The list may contain multiple +# occurences of the same data element. The list contains derived +# relations to test if it's empty, and to test if a data element is at +# the head of the list. +# +################################################################################ + +type list_elem # used to allow multiple identical messages and list items + +module list(data_t) = { + individual data(U:list_elem) : data_t # function from elements to data + individual h : list_elem + relation le(X:list_elem,Y:list_elem) # the list order on elements + relation empty # should be a function when langauge enables it + relation at_head(D:data_t) # true only for the data at the head of the list, empty if the list is empty + + # Conjectures that ensure that le is a total linear order over its support + conjecture le(X,Y) -> (le(X,X) & le(Y,Y)) # Reflexivity + conjecture le(X,Y) & le(Y,Z) -> le(X,Z) # Transitivity + conjecture le(X,Y) & le(Y,X) -> X = Y # Anti-symmetry + conjecture le(X,X) & le(Y,Y) -> (le(X,Y) | le(Y,X)) # Linearity + + # empty, h, and at_head + conjecture empty -> ~le(X,Y) + conjecture ~empty -> le(h, h) + conjecture le(X, X) -> le(h, X) + conjecture at_head(D) -> le(h, h) & data(h) = D + conjecture le(h, h) -> at_head(data(h)) + + init empty + init ~le(X,Y) + init ~at_head(D) + + action append(x:data_t) = { + local u:list_elem { + assume ~le(u, X) & ~le(X, u); + data(u) := x; + le(u, u) := true; + le(X, Y) := le(X, Y) | (le(X, X) & Y = u); + if empty { + h := u + }; + empty := false; + at_head(D) := D = data(h) + } + } + + action pop returns (x:data_t) = { + local u:list_elem { + assume le(u, u) & (le(X,X) -> le(u, X)); + x := data(u); + le(X, Y) := le(X, Y) & (X ~= u); + h := *; + assume le(X, X) -> le(h, X); + if ~le(X:list_elem,X:list_elem) { + empty := true; + at_head(D) := false + } else { + at_head(D) := D = data(h) + } + } + } + + action get_head returns (x:data_t) = { + assume at_head(x) + } +} + +################################################################################ +# +# Module describing a message pool with one message field. +# +# Messages can be sent and received, and multiple messages with the +# same field are supported. +# +################################################################################ + + +module msg_pool(msg_t, field_t) = { + relation pending(M:msg_t) + individual fld(M:msg_t) : field_t + + init ~pending(M) + + action send(f:field_t) = { + local m:msg_t { + assume ~pending(m); + fld(m) := f; + pending(m) := true + } + } + + action receive returns (f:field_t) = { + local m:msg_t { + assume pending(m); + f := fld(m); + pending(m) := false + } + } +} + +################################################################################ +# +# Types, relations and functions describing state of the network +# +################################################################################ + +type client +type lock_m +type unlock_m +type grant_m + +relation has_lock(C:client) +instantiate lock_msg : msg_pool(lock_m, client) +instantiate unlock_msg : msg_pool(unlock_m, client) +instantiate grant_msg : msg_pool(grant_m, client) +instantiate waiting : list(client) # list of waiting clients + +init ~has_lock(C) + +################################################################################ +# +# Protocol description +# +################################################################################ + +action send_lock = { + local c:client { + # A lock event - client requests lock + call lock_msg.send(c) + } +} + +action send_unlock = { + local c:client { + # An unlock event - client requests unlock + assume has_lock(c); + has_lock(c) := false; + call unlock_msg.send(c) + } +} + +action receive_lock = { + local c:client { + c := lock_msg.receive; + if waiting.empty { + call grant_msg.send(c) + }; + call waiting.append(c) + } +} + +action receive_unlock = { + local c:client, ignore:client { + ignore := unlock_msg.receive; + ignore := waiting.pop; + if ~waiting.empty { + c := waiting.get_head; + call grant_msg.send(c) + } + } +} + +action receive_grant = { + local c:client { + c := grant_msg.receive; + has_lock(c) := true + } +} + +export send_lock +export send_unlock +export receive_lock +export receive_unlock +export receive_grant + +# just a bogus conjecture used with BMC to make sure we CAN have a client that holds the lock +#conjecture ~has_lock(C1) + +# The safety property is that no two different clients have the lock +conjecture has_lock(C1) & has_lock(C2) -> C1 = C2 + +# obtained interactively via CTI's (took less than an hour) +# conjecture ~(grant_msg.pending(G0) & ~waiting.at_head(grant_msg.fld(G0))) +# conjecture ~(grant_msg.pending(G0) & unlock_msg.pending(U0)) +# conjecture ~(has_lock(C0) & ~waiting.at_head(C0)) +# conjecture ~(has_lock(C0) & unlock_msg.pending(U0)) +# conjecture ~(grant_msg.pending(G0) & has_lock(C0)) +# conjecture ~(unlock_msg.pending(U0) & ~waiting.at_head(unlock_msg.fld(U0))) +# conjecture ~(grant_msg.pending(G0) & grant_msg.pending(G1) & G1 ~= G0) +# conjecture ~(unlock_msg.pending(U0) & unlock_msg.pending(U1) & U1 ~= U0) + +# obtained interactively via CTI's at the Skype demo with the Verdi team, took about an hour +# (minimization of has_lock lock_msg.pending unlock_msg.pending grant_msg.pending waiting.le) +# ~(has_lock(C0) & ~waiting.at_head(C0)) +# ~(grant_msg.pending(G0) & ~waiting.at_head(grant_msg.fld(G0))) +# ~(grant_msg.pending(G0) & unlock_msg.pending(U0)) +# ~(grant_msg.pending(G0) & has_lock(C0)) +# ~(unlock_msg.pending(U0) & ~waiting.at_head(unlock_msg.fld(U0))) +# ~(has_lock(C0) & unlock_msg.pending(U0)) +# ~(unlock_msg.pending(U0) & unlock_msg.pending(U1) & U1 ~= U0) +# ~(grant_msg.pending(G0) & grant_msg.pending(G1) & G1 ~= G0) From 345a78043e73e81ac4eb3e62e981ae8b2f48a3b6 Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Thu, 24 Aug 2017 00:13:56 -0700 Subject: [PATCH 03/17] paring works, now working on ivy_l2s.py --- ivy/ivy_actions.py | 6 +- ivy/ivy_ast.py | 37 +++++ ivy/ivy_compiler.py | 30 +++- ivy/ivy_l2s.py | 119 +++++++++++++++ ivy/ivy_lexer.py | 9 +- ivy/ivy_logic.py | 31 ++-- ivy/ivy_logic_parser.py | 26 +++- ivy/ivy_logic_parser_gen.py | 2 + ivy/ivy_module.py | 2 +- ivy/ivy_parser.py | 85 +++++++---- ivy/logic.py | 63 +++++++- ivy/logic_util.py | 14 +- ivy/type_inference.py | 49 +++++- test/test_liveness.ivy | 291 ++++++++++++++++++++++++++++++++++++ 14 files changed, 694 insertions(+), 70 deletions(-) create mode 100644 ivy/ivy_l2s.py create mode 100644 test/test_liveness.ivy diff --git a/ivy/ivy_actions.py b/ivy/ivy_actions.py index d3ee36ca0..4a46255fa 100644 --- a/ivy/ivy_actions.py +++ b/ivy/ivy_actions.py @@ -1056,9 +1056,9 @@ def type_check_action(action,domain,pvars = []): with TypeCheckConext(domain): action.int_update(domain,pvars) -def concat_actions(action1,action2): - al1,al2 = ((a.args if isinstance(a,Sequence) else [a]) for a in (action1,action2)) - return Sequence(*(al1+al2)) +def concat_actions(*actions): + als = ((a.args if isinstance(a,Sequence) else [a]) for a in actions) + return Sequence(*(a for al in als for a in al)) def apply_mixin(decl,action1,action2): assert hasattr(action1,'lineno') diff --git a/ivy/ivy_ast.py b/ivy/ivy_ast.py index 050dee661..bcc5c9211 100644 --- a/ivy/ivy_ast.py +++ b/ivy/ivy_ast.py @@ -75,6 +75,30 @@ def __repr__(self): return ' ~= '.join(repr(x) for x in self.args[0].args) return '~' + repr(self.args[0]) +class Globally(Formula): + """ + Temporal globally of a formula. + """ + def __init__(self,*args): + assert len(args) == 1 + self.args = args + def __repr__(self): + return '(globally ' + repr(self.args[0]) + ')' + +class Eventually(Formula): + """ + Temporal eventually of a formula. + """ + def __init__(self,*args): + assert len(args) == 1 + self.args = args + def __repr__(self): + return '(eventually ' + repr(self.args[0]) + ')' + +def has_temporal(f): + assert f is not None + return (type(f) in [Globally, Eventually]) or any(has_temporal(x) for x in f.args) + class Let(Formula): """ Formula of the form let p(X,...,Z) <-> fmla[X,...,Z], ... in fmla @@ -159,6 +183,17 @@ class Forall(Quantifier): class Exists(Quantifier): pass +class Binder(Formula): + def __init__(self, name, bounds, body): + self.name = name + self.bounds = bounds + self.args = [body] + def clone(self,args): + res = type(self)(self.name,self.bounds,*args) + if hasattr(self,'lineno'): + res.lineno = self.lineno + return res + class This(AST): @property def rep(self): @@ -487,6 +522,7 @@ def __init__(self,*args): global lf_counter self.args = args self.id = lf_counter + self.temporal = None lf_counter += 1 @property def label(self): @@ -501,6 +537,7 @@ def clone(self,args): res = AST.clone(self,args) lf_counter -= 1 res.id = self.id + res.temporal = self.temporal return res class AxiomDecl(Decl): diff --git a/ivy/ivy_compiler.py b/ivy/ivy_compiler.py index f37b839c1..284f36956 100644 --- a/ivy/ivy_compiler.py +++ b/ivy/ivy_compiler.py @@ -66,14 +66,17 @@ def other_thing(self): ivy_logic.AST.cmpl = ivy_ast.AST.cmpl = other_thing op_pairs = [ - (ivy_ast.And,ivy_logic.And), - (ivy_ast.Or,ivy_logic.Or), - (ivy_ast.Not,ivy_logic.Not), - (ivy_ast.And,ivy_logic.And), - (ivy_ast.Definition,ivy_logic.Definition), - (ivy_ast.Implies,ivy_logic.Implies), - (ivy_ast.Iff,ivy_logic.Iff), - (ivy_ast.Ite,ivy_logic.Ite)] + (ivy_ast.And,ivy_logic.And), + (ivy_ast.Or,ivy_logic.Or), + (ivy_ast.Not,ivy_logic.Not), + (ivy_ast.Globally,ivy_logic.Globally), + (ivy_ast.Eventually,ivy_logic.Eventually), + (ivy_ast.And,ivy_logic.And), + (ivy_ast.Definition,ivy_logic.Definition), + (ivy_ast.Implies,ivy_logic.Implies), + (ivy_ast.Iff,ivy_logic.Iff), + (ivy_ast.Ite,ivy_logic.Ite), +] for fc,tc in op_pairs: fc.cmpl = lambda self,tc=tc: tc(*[a.compile() for a in self.args]) @@ -251,6 +254,12 @@ def cquant(q): ivy_ast.Quantifier.cmpl = lambda self: cquant(self)([v.compile() for v in self.bounds],self.args[0].compile()) +ivy_ast.Binder.cmpl = lambda self: ivy_logic.Binder( + self.name, + [v.compile() for v in self.bounds], + self.args[0].compile() +) + ivy_ast.LabeledFormula.cmpl = lambda self: self.clone([self.label,sortify_with_inference(self.formula)]) # compiling update patterns is complicated because they declare constants internally @@ -1158,6 +1167,11 @@ def named_trans(prop): mod.labeled_props.append(g.clone([label,g.formula])) mod.labeled_props.append(prop) mod.subgoals.append((prop,subgoals)) + elif prop.temporal: + from ivy_l2s import l2s + print "=================" + "\n" * 10 + l2s(mod, prop) + assert False else: mod.labeled_props.append(prop) if prop.id in nmap: diff --git a/ivy/ivy_l2s.py b/ivy/ivy_l2s.py new file mode 100644 index 000000000..842a5194b --- /dev/null +++ b/ivy/ivy_l2s.py @@ -0,0 +1,119 @@ + # self.definitions = [] # TODO: these are actually "derived" relations + # self.labeled_axioms = [] + # self.labeled_props = [] + # self.labeled_inits = [] + # self.labeled_conjs = [] # conjectures + # self.actions = {} + # self.public_actions = set() + + # self.initializers = [] # list of name,action pairs + + # self.sig + +from ivy_printer import print_module +from ivy_actions import (AssignAction, Sequence, ChoiceAction, + AssumeAction, concat_actions) +import logic as lg +import ivy_logic_utils as ilu + +def l2s(mod, lf): + # modify mod in place + + l2s_waiting = lg.Const('l2s_waiting', lg.Boolean) + l2s_frozen = lg.Const('l2s_frozen', lg.Boolean) + l2s_saved = lg.Const('l2s_saved', lg.Boolean) + l2s_d = lambda sort: lg.Const('l2s_d',lg.FunctionSort(sort,lg.Boolean)) + l2s_a = lambda sort: lg.Const('l2s_a',lg.FunctionSort(sort,lg.Boolean)) + l2s_w = lambda x: lg.Binder('l2s_w', [], x) + l2s_s = lambda vs, t: lg.Binder('l2s_s', vs, t) + + uninterpreted_sorts = [s for s in mod.sig.sorts.values() if type(s) is lg.UninterpretedSort] + + #for k, v in mod.sig.sorts.items() + mod.sig.symbols.items(): + # print repr(k), ':', repr(v) + # print + #print + + # TODO: change wait_for to be from the tableau, this is a mock + wait_for = [c for c in mod.sig.symbols.values() if c.sort == lg.Boolean] + wait_for += [ + f(*[lg.Var('X{}'.format(i), s) for i,s in enumerate(f.sort.domain)]) + for f in mod.sig.symbols.values() + if isinstance(f.sort, lg.FunctionSort) and f.sort.range == lg.Boolean] + done_waiting = [ilu.close_epr(lg.Not(l2s_w(w))) for w in wait_for] + + # TODO: change to_save to be taken from the conjectures + to_save = [] # list of (variables, term) corresponding to l2s_s + for f in mod.sig.symbols.values(): + if isinstance(f.sort, lg.FunctionSort): + vs = tuple(lg.Var('X{}'.format(i), s) for i,s in enumerate(f.sort.domain)) + to_save.append((vs, f(*vs))) + else: + to_save.append(((), f)) + + prelude = ChoiceAction( + # waiting -> frozen + Sequence(*([ + AssumeAction(l2s_waiting) + ] +[ + AssumeAction(x) for x in done_waiting + ] + [ + AssignAction(l2s_waiting, lg.false), + AssignAction(l2s_frozen, lg.true), + ] + [ + AssignAction(l2s_a(s)(v), l2s_d(s)(v)) + for s in uninterpreted_sorts + for v in [lg.Var('X',s)] + ])), + # frozen -> saved + Sequence(*([ + AssumeAction(l2s_frozen), + AssignAction(l2s_frozen, lg.false), + AssignAction(l2s_saved, lg.true), + ] + [ + AssignAction(l2s_s(vs,t)(*vs) if len(vs) > 0 else l2s_s(vs,t), t) + for vs, t in to_save + # TODO copy relations and functions + # TODO reset l2s_w from l2s_d + ])), + # stay + Sequence(), + ) + + add_consts_to_d = [ + AssignAction(l2s_d(s)(c), lg.true) + for s in uninterpreted_sorts + for c in mod.sig.symbols.values() if c.sort == s + ] + + # TODO: add conjectures that constants are in d and a + + for a in mod.public_actions: + action = mod.actions[a] + add_params_to_d = [ + AssignAction(l2s_d(p.sort)(p), lg.true) + for p in action.formal_params + ] + new_action = concat_actions(*( + [prelude] + + add_params_to_d + + [action] + + add_consts_to_d + )) + new_action.lineno = action.lineno + new_action.formal_params = action.formal_params + new_action.formal_returns = action.formal_returns + mod.actions[a] = new_action + + l2s_init = [ + AssignAction(l2s_waiting, lg.true), + AssignAction(l2s_frozen, lg.false), + AssignAction(l2s_saved, lg.false), + ] + l2s_init += add_consts_to_d + # TODO: initialize w + # TODO: assume [~property] + mod.initializers.append(('l2s_init', Sequence(*l2s_init))) + + print "=" * 80 + "\n"*3 + print_module(mod) diff --git a/ivy/ivy_lexer.py b/ivy/ivy_lexer.py index f9606d3db..46641fdbc 100644 --- a/ivy/ivy_lexer.py +++ b/ivy/ivy_lexer.py @@ -34,7 +34,8 @@ 'DOTS', 'DOTDOTDOT', 'NATIVEQUOTE', - 'PTO', + 'PTO', + 'DOLLAR', ) reserved = all_reserved = { @@ -121,6 +122,9 @@ 'proof' : 'PROOF', 'named' : 'NAMED', 'fresh' : 'FRESH', + 'temporal' : 'TEMPORAL', + 'globally' : 'GLOBALLY', + 'eventually' : 'EVENTUALLY', } tokens += tuple(all_reserved.values()) @@ -153,6 +157,7 @@ t_COLON = r':' t_DOTS = r'\.\.' t_DOTDOTDOT = r'\.\.\.' +t_DOLLAR = r'\$' t_ignore = ' \t\r' t_ignore_COMMENT = r'\#.*' @@ -220,7 +225,7 @@ def __enter__(self): if s in reserved: del reserved[s] if self.version <= [1,5]: - for s in ['variant','of']: + for s in ['variant','of', 'globally', 'eventually', 'temporal']: # print "deleting {}".format(s) if s in reserved: del reserved[s] diff --git a/ivy/ivy_logic.py b/ivy/ivy_logic.py index 7fd1c7371..86c1c827a 100644 --- a/ivy/ivy_logic.py +++ b/ivy/ivy_logic.py @@ -66,7 +66,7 @@ import ivy_utils as iu import logic as lg import logic_util as lu -from logic import And,Or,Not,Implies,Iff,Ite,ForAll,Exists,Lambda +from logic import And,Or,Not,Globally,Eventually,Implies,Iff,Ite,ForAll,Exists,Lambda,Binder from type_inference import concretize_sorts, concretize_terms from collections import defaultdict from itertools import chain @@ -242,7 +242,7 @@ def sort(self): -lg_ops = [lg.Eq, lg.Not, lg.And, lg.Or, lg.Implies, lg.Iff, lg.Ite, lg.ForAll, lg.Exists, lg.Lambda] +lg_ops = [lg.Eq, lg.Not, lg.Globally, lg.Eventually, lg.And, lg.Or, lg.Implies, lg.Iff, lg.Ite, lg.ForAll, lg.Exists, lg.Lambda, lg.Binder] for cls in lg_ops: cls.args = property(lambda self: [ a for a in self]) @@ -251,6 +251,8 @@ def sort(self): for cls in [lg.ForAll, lg.Exists, lg.Lambda]: cls.clone = lambda self,args: type(self)(self.variables,*args) +lg.Binder.clone = lambda self,args: lg.Binder(self.name, self.variables, *args) + lg.Apply.clone = lambda self,args: type(self)(self.func, *args) lg.Apply.args = property(lambda self: self.terms) lg.Apply.rep = property(lambda self: self.func) @@ -1090,7 +1092,10 @@ def fmla_to_str_ambiguous(term): return res def app_ugly(self): - name = self.func.name + if type(self.func) is lg.Binder: + name = str(self.func) + else: + name = self.func.name args = [a.ugly() for a in self.args] if name in infix_symbols: return (' ' + name + ' ').join(args) @@ -1113,6 +1118,8 @@ def nary_ugly(op,args,parens = True): lg.Not.ugly = lambda self: (nary_ugly('~=',self.body.args,parens=False) if type(self.body) is lg.Eq else '~{}'.format(self.body.ugly())) +lg.Globally.ugly = lambda self: ('globally {}'.format(self.body.ugly())) +lg.Eventually.ugly = lambda self: ('eventually {}'.format(self.body.ugly())) lg.Implies.ugly = lambda self: nary_ugly('->',self.args,parens=False) lg.Iff.ugly = lambda self: nary_ugly('<->',self.args,parens=False) lg.Ite.ugly = lambda self: '{} if {} else {}'.format(*[self.args[idx].ugly() for idx in (1,0,2)]) @@ -1120,12 +1127,15 @@ def nary_ugly(op,args,parens = True): lg.Apply.ugly = app_ugly def quant_ugly(self): - res = 'forall ' if isinstance(self,lg.ForAll) else 'exists ' if isinstance(self,lg.Exists) else 'lambda ' + res = ('forall ' if isinstance(self,lg.ForAll) else + 'exists ' if isinstance(self,lg.Exists) else + 'lambda ' if isinstance(self,lg.Lambda) else + '$' + self.name) res += ','.join(v.ugly() for v in self.variables) res += '. ' + self.body.ugly() return res -for cls in [lg.ForAll,lg.Exists, lg.Lambda]: +for cls in [lg.ForAll,lg.Exists, lg.Lambda, lg.Binder]: cls.ugly = quant_ugly # Drop the type annotations of variables and polymorphic @@ -1190,13 +1200,16 @@ def quant_drop_annotations(self,inferred_sort,annotated_vars): for cls in [lg.ForAll, lg.Exists, lg.Lambda]: cls.drop_annotations = quant_drop_annotations +lg.Binder.drop_annotations = lambda self,inferred_sort,annotated_vars: lg.Binder( + self.name, + [v.drop_annotations(False,annotated_vars) for v in self.variables], + self.body.drop_annotations(True,annotated_vars) +) + def default_drop_annotations(self,inferred_sort,annotated_vars): return self.clone([arg.drop_annotations(True,annotated_vars) for arg in self.args]) -for cls in [lg.Not, lg.And, lg.Or, lg.Implies, lg.Iff]: - cls.drop_annotations = default_drop_annotations - -for cls in [lg.Not, lg.And, lg.Or, lg.Implies, lg.Iff,]: +for cls in [lg.Not, lg.Globally, lg.Eventually, lg.And, lg.Or, lg.Implies, lg.Iff,]: # should binder be here? cls.drop_annotations = default_drop_annotations def pretty_fmla(self): diff --git a/ivy/ivy_logic_parser.py b/ivy/ivy_logic_parser.py index fa5222720..bfaa71da6 100644 --- a/ivy/ivy_logic_parser.py +++ b/ivy/ivy_logic_parser.py @@ -178,7 +178,7 @@ def p_app_term_infix_term(p): 'app : term infix term' p[0] = App(p[2],p[1],p[3]) p[0].lineno = get_lineno(p,2) - + def p_apps_app(p): 'apps : app' @@ -255,7 +255,7 @@ def p_relop_gt(p): def p_relop_pto(p): 'relop : PTO' p[0] = p[1] - + def p_infix_plus(p): 'infix : PLUS' p[0] = p[1] @@ -296,7 +296,7 @@ def p_fmla_true(p): 'fmla : TRUE' p[0] = And() p[0].lineno = get_lineno(p,1) - + def p_fmla_false(p): 'fmla : FALSE' p[0] = Or() @@ -346,3 +346,23 @@ def p_fmla_exists_vars_dot_fmla(p): 'fmla : EXISTS simplevars DOT fmla' p[0] = Exists(p[2],p[4]) p[0].lineno = get_lineno(p,1) + +def p_fmla_globally_fmla(p): + 'fmla : GLOBALLY fmla' + p[0] = Globally(p[2]) + p[0].lineno = get_lineno(p,1) + +def p_fmla_eventually_fmla(p): + 'fmla : EVENTUALLY fmla' + p[0] = Eventually(p[2]) + p[0].lineno = get_lineno(p,1) + +def p_fmla_binder_vars_dot_fmla(p): + 'fmla : DOLLAR SYMBOL simplevars DOT fmla' + p[0] = Binder(p[2], p[3],p[5]) + p[0].lineno = get_lineno(p,1) + +def p_fmla_binder_dot_fmla(p): + 'fmla : DOLLAR SYMBOL DOT fmla' + p[0] = Binder(p[2], [],p[4]) + p[0].lineno = get_lineno(p,1) diff --git a/ivy/ivy_logic_parser_gen.py b/ivy/ivy_logic_parser_gen.py index ae5a5d054..e47d3b414 100644 --- a/ivy/ivy_logic_parser_gen.py +++ b/ivy/ivy_logic_parser_gen.py @@ -7,6 +7,7 @@ # Parser for formulas precedence = ( + ('left', 'GLOBALLY', 'EVENTUALLY'), ('left', 'IF'), ('left', 'ELSE'), ('left', 'OR'), @@ -19,6 +20,7 @@ ('left', 'MINUS'), ('left', 'TIMES'), ('left', 'DIV'), + ('left', 'DOLLAR'), ) from ivy_logic_parser import * diff --git a/ivy/ivy_module.py b/ivy/ivy_module.py index 0ab705cbe..5f1a5ac54 100644 --- a/ivy/ivy_module.py +++ b/ivy/ivy_module.py @@ -112,7 +112,7 @@ def add_object(self,name): @property def axioms(self): - return map(drop_label,self.labeled_axioms) + return [drop_label(x) for x in self.labeled_axioms if not x.temporal] @property def conjs(self): diff --git a/ivy/ivy_parser.py b/ivy/ivy_parser.py index ee4dc95b2..2de6959ba 100644 --- a/ivy/ivy_parser.py +++ b/ivy/ivy_parser.py @@ -16,6 +16,7 @@ precedence = ( ('left', 'SEMI'), + ('left', 'GLOBALLY', 'EVENTUALLY'), ('left', 'IF'), ('left', 'ELSE'), ('left', 'OR'), @@ -28,6 +29,7 @@ ('left', 'MINUS'), ('left', 'TIMES'), ('left', 'DIV'), + ('left', 'DOLLAR'), ) else: @@ -151,6 +153,15 @@ def do_insts(ivy,insts): if others: ivy.declare(InstantiateDecl(*others)) +def check_non_temporal(x): + assert type(x) is not list + if type(x) is LabeledFormula: + check_non_temporal(x.args[1]) + return x + elif has_temporal(x): + report_error(IvyError(x,"non-temporal formula expected")) + else: + return x class Ivy(object): def __init__(self): @@ -227,11 +238,23 @@ def p_labeledfmla_label_fmla(p): p[0] = LabeledFormula(Atom(p[1][1:-1],[]),p[2]) p[0].lineno = get_lineno(p,1) +def p_opttemporal(p): + 'opttemporal : ' + p[0] = None + +def p_opttemporal_symbol(p): + 'opttemporal : TEMPORAL' + p[0] = True + +def addtemporal(lf): + lf.temporal = True + return lf + def p_top_axiom_labeledfmla(p): - 'top : top AXIOM labeledfmla' - p[0] = p[1] - d = AxiomDecl(p[3]) - d.lineno = get_lineno(p,2) + 'top : top opttemporal AXIOM labeledfmla' + p[0] = check_non_temporal(p[1]) + d = AxiomDecl(addtemporal(p[4]) if p[2] else check_non_temporal(p[4])) + d.lineno = get_lineno(p,3) p[0].declare(d) def p_optskolem(p): @@ -244,15 +267,15 @@ def p_optskolem_symbol(p): p[0].lineno = get_lineno(p,1) def p_top_property_labeledfmla(p): - 'top : top PROPERTY labeledfmla optskolem optproof' + 'top : top opttemporal PROPERTY labeledfmla optskolem optproof' p[0] = p[1] - d = PropertyDecl(p[3]) - d.lineno = get_lineno(p,2) + d = PropertyDecl(addtemporal(p[4]) if p[2] else check_non_temporal(p[4])) + d.lineno = get_lineno(p,3) p[0].declare(d) - if p[4] is not None: - p[0].declare(NamedDecl(p[4])) if p[5] is not None: - p[0].declare(ProofDecl(p[5])) + p[0].declare(NamedDecl(p[5])) + if p[6] is not None: + p[0].declare(ProofDecl(p[6])) def p_top_conjecture_labeledfmla(p): 'top : top CONJECTURE labeledfmla' @@ -331,7 +354,7 @@ def p_top_macro_atom_eq_lcb_action_rcb(p): def p_schdefnrhs_fmla(p): 'schdefnrhs : fmla' - p[0] = p[1] + p[0] = check_non_temporal(p[1]) def p_schdecl_funcdecl(p): 'schdecl : FUNCTION funs' @@ -359,7 +382,7 @@ def p_schdecl_typedecl(p): def p_schdecl_propdecl(p): 'schdecl : PROPERTY labeledfmla' - p[0] = [p[2]] + p[0] = [check_non_temporal(p[2])] def p_schconc_defdecl(p): 'schconc : DEFINITION defn' @@ -367,7 +390,7 @@ def p_schconc_defdecl(p): def p_schconc_propdecl(p): 'schconc : PROPERTY fmla' - p[0] = p[2] + p[0] = check_non_temporal(p[2]) def p_schdecls(p): 'schdecls :' @@ -577,7 +600,7 @@ def p_match_defn(p): def p_match_var_eq_fmla(p): 'match : var EQ fmla' - p[0] = Definition(p[1],p[3]) + p[0] = Definition(p[1],check_non_temporal(p[3])) p[0].lineno = get_lineno(p,2) def p_matches(p): @@ -643,7 +666,7 @@ def p_top_concept_cdefns(p): def p_top_init_fmla(p): 'top : top INIT labeledfmla' p[0] = p[1] - d = InitDecl(p[3]) + d = InitDecl(check_non_temporal(p[3])) d.lineno = get_lineno(p,2) p[0].declare(d) @@ -826,7 +849,7 @@ def p_requires(p): def p_requires_requires_fmla(p): 'requires : REQUIRES fmla' - p[0] = p[2] + p[0] = check_non_temporal(p[2]) def p_modifies(p): 'modifies : ' @@ -850,7 +873,7 @@ def p_modifies_modifies_atoms(p): def p_ensures_ensures_fmla(p): 'ensures : ENSURES fmla' - p[0] = p[2] + p[0] = check_non_temporal(p[2]) if iu.get_numeric_version() <= [1,1]: def p_top_action_symbol_eq_loc_action_loc(p): @@ -1087,7 +1110,7 @@ def p_assert_rhs_lcb_requires_modifies_ensures_rcb(p): def p_assert_rhs_fmla(p): 'assert_rhs : fmla' - p[0] = p[1] + p[0] = check_non_temporal(p[1]) def p_top_assert_symbol_arrow_assert_rhs(p): 'top : top ASSERT SYMBOL ARROW assert_rhs' @@ -1315,17 +1338,17 @@ def p_action_sequence(p): def p_action_assume(p): 'action : ASSUME fmla' - p[0] = AssumeAction(p[2]) + p[0] = AssumeAction(check_non_temporal(p[2])) p[0].lineno = get_lineno(p,1) def p_action_assert(p): 'action : ASSERT fmla' - p[0] = AssertAction(p[2]) + p[0] = AssertAction(check_non_temporal(p[2])) p[0].lineno = get_lineno(p,1) def p_action_ensures(p): 'action : ENSURES fmla' - p[0] = EnsuresAction(p[2]) + p[0] = EnsuresAction(check_non_temporal(p[2])) p[0].lineno = get_lineno(p,1) def p_action_set_lit(p): @@ -1335,7 +1358,7 @@ def p_action_set_lit(p): def p_action_term_assign_fmla(p): 'action : term ASSIGN fmla' - p[0] = AssignAction(p[1],p[3]) + p[0] = AssignAction(p[1],check_non_temporal(p[3])) p[0].lineno = get_lineno(p,2) def p_termtuple_lp_term_comma_terms_rp(p): @@ -1358,12 +1381,12 @@ def p_action_term_assign_times(p): def p_action_if_fmla_lcb_action_rcb(p): 'action : IF fmla sequence' - p[0] = IfAction(p[2],p[3]) + p[0] = IfAction(check_non_temporal(p[2]),p[3]) p[0].lineno = get_lineno(p,1) def p_action_if_fmla_lcb_action_rcb_else_LCB_action_RCB(p): 'action : IF fmla sequence ELSE action' - p[0] = IfAction(p[2],p[3],p[5]) + p[0] = IfAction(check_non_temporal(p[2]),p[3],p[5]) p[0].lineno = get_lineno(p,1) def p_action_if_times_lcb_action_rcb_else_LCB_action_RCB(p): @@ -1420,11 +1443,13 @@ def fix_if_part(cond,part): def p_action_if_somefmla_lcb_action_rcb(p): 'action : IF somefmla sequence' + p[2] = check_non_temporal(p[2]) p[0] = IfAction(p[2],fix_if_part(p[2],p[3])) p[0].lineno = get_lineno(p,1) def p_action_if_somefmla_lcb_action_rcb_else_LCB_action_RCB(p): 'action : IF somefmla sequence ELSE action' + p[2] = check_non_temporal(p[2]) p[0] = IfAction(p[2],fix_if_part(p[2],p[3]),p[5]) p[0].lineno = get_lineno(p,1) @@ -1435,14 +1460,14 @@ def p_invariants(p): def p_invariant_invariant_fmla(p): 'invariants : invariants INVARIANT fmla' p[0] = p[1] - inv = p[3] + inv = check_non_temporal(p[3]) a = AssertAction(inv) a.lineno = get_lineno(p,2) p[0].append(a) def p_action_while_fmla_invariants_lcb_action_rcb(p): 'action : WHILE fmla invariants sequence' - p[0] = WhileAction(*([p[2], p[4]] + p[3])) + p[0] = WhileAction(*([check_non_temporal(p[2]), p[4]] + p[3])) p[0].lineno = get_lineno(p,1) if iu.get_numeric_version() <= [1,2]: @@ -1568,7 +1593,7 @@ def p_optinit(p): def p_optinit_assign_fmla(p): 'optinit : ASSIGN fmla' - p[0] = p[2] + p[0] = check_non_temporal(p[2]) def p_action_var_opttypedsym_assign_fmla(p): 'action : VAR opttypedsym optinit' @@ -1671,7 +1696,7 @@ def p_defnlhs_lp_term_infix_term_rp(p): def p_defn_atom_fmla(p): 'defn : defnlhs EQ fmla' - p[0] = Definition(app_to_atom(p[1]),p[3]) + p[0] = Definition(app_to_atom(p[1]),check_non_temporal(p[3])) p[0].lineno = get_lineno(p,2) def p_defn_defnlhs_eq_nativequote(p): @@ -1703,12 +1728,12 @@ def p_somevarfmla_some_simplevar_dot_fmla(p): def p_defn_atom_somevarfmla(p): 'defn : defnlhs EQ somevarfmla' - p[0] = Definition(app_to_atom(p[1]),p[3]) + p[0] = Definition(app_to_atom(p[1]),check_non_temporal(p[3])) p[0].lineno = get_lineno(p,2) def p_expr_fmla(p): 'expr : LCB fmla RCB' - p[0] = NamedSpace(Literal(1,p[2])) + p[0] = NamedSpace(Literal(1,check_non_temporal(p[2]))) def p_exprterm_aterm(p): 'exprterm : aterm' diff --git a/ivy/logic.py b/ivy/logic.py index eeb8e6cd1..a62ca2325 100644 --- a/ivy/logic.py +++ b/ivy/logic.py @@ -58,7 +58,7 @@ def __str__(self): return '{' + ','.join(self.extension) + '}' @property def constructors(self): - return [Const(n,self) for n in self.extension] + return [Const(n,self) for n in self.extension] @property def card(self): return len(self.extension) @@ -221,6 +221,30 @@ def __str__(self): return 'Not({})'.format(self.body) +class Globally(recstruct('Globally', [], ['body'])): + __slots__ = () + sort = Boolean + @classmethod + def _preprocess_(cls, body): + if body.sort not in (Boolean, TopS): + raise SortError("Globally body must be Boolean: {}".format(body)) + return (body,) + def __str__(self): + return 'Globally({})'.format(self.body) + + +class Eventually(recstruct('Eventually', [], ['body'])): + __slots__ = () + sort = Boolean + @classmethod + def _preprocess_(cls, body): + if body.sort not in (Boolean, TopS): + raise SortError("Eventually body must be Boolean: {}".format(body)) + return (body,) + def __str__(self): + return 'Eventually({})'.format(self.body) + + class And(recstruct('And', [], ['*terms'])): __slots__ = () sort = Boolean @@ -323,6 +347,7 @@ def __str__(self): ', '.join('{}:{}'.format(v.name, v.sort) for v in sorted(self.variables)), self.body) + class Lambda(recstruct('Lambda', ['variables'], ['body'])): __slots__ = () sort = Boolean @@ -336,10 +361,33 @@ def __str__(self): ', '.join('{}:{}'.format(v.name, v.sort) for v in sorted(self.variables)), self.body) + +class Binder(recstruct('Binder', ['name', 'variables'], ['body'])): + __slots__ = () + @classmethod + def _preprocess_(cls, name, variables, body): + if not all(type(v) is Var for v in variables): + raise IvyError("Can only abstract over variables") + # TODO: check the name after we decide on valid names + return name, tuple(variables), body + def __str__(self): + return '({} {}. {})'.format( # TODO: change after we decide on the syntax for this + self.name, + ', '.join('{}:{}'.format(v.name, v.sort) for v in sorted(self.variables)), + self.body) + def __call__(self, *terms): + return Apply(self, *terms) + sort = property( + lambda self: + FunctionSort(*([v.sort for v in self.variables] + [self.body.sort])) + if len(self.variables) > 0 else + self.body.sort + ) + + true = Const('true', Boolean) false = Const('false', Boolean) - if __name__ == '__main__': S = UninterpretedSort('S') X, Y, Z = (Var(n, S) for n in ['X', 'Y', 'Z']) @@ -352,7 +400,7 @@ def __str__(self): antisymmetric = ForAll((X, Y), Implies(And(leq(X,Y), leq(Y,X)), Eq(Y,X))) - assert Eq(X,Y) == Eq(Y,X) + # assert Eq(X,Y) == Eq(Y,X) X, Y = (Var(n, TopS) for n in ['X', 'Y']) # note that Z remains Z:S f = Const('f', FunctionSort(TopS, TopS, Boolean)) @@ -368,5 +416,14 @@ def __str__(self): assert not contains_topsort(g) assert contains_topsort(h) + b = Binder('mybinder', [X,Y,Z], Implies(And(f(X,Y), f(X,Z)), Eq(Y,Z))) + print b + print b.sort + print + + b = Binder('mybinder', [X,Y,Z], Z) + print b + print b.sort + # TODO: add more tests, add tests for errors diff --git a/ivy/logic_util.py b/ivy/logic_util.py index 1d7e8f126..f76e21512 100644 --- a/ivy/logic_util.py +++ b/ivy/logic_util.py @@ -8,7 +8,7 @@ from functools import partial from logic import (Var, Const, Apply, Eq, Ite, Not, And, Or, Implies, - Iff, ForAll, Exists, Lambda) + Iff, ForAll, Exists, Lambda, Binder) from logic import contains_topsort @@ -35,7 +35,7 @@ def used_variables(*terms): Implies, Iff): return union(*(used_variables(x) for x in t)) - elif type(t) in (ForAll, Exists, Lambda): + elif type(t) in (ForAll, Exists, Lambda, Binder): return union(used_variables(t.body), t.variables) elif hasattr(t,'args'): @@ -66,7 +66,7 @@ def free_variables(*terms, **kwargs): Implies, Iff): return union(*(_free_variables(x) for x in t)) - elif type(t) in (ForAll, Exists, Lambda): + elif type(t) in (ForAll, Exists, Lambda, Binder): return _free_variables(t.body) - _free_variables(*t.variables) elif hasattr(t,'args'): @@ -90,7 +90,7 @@ def bound_variables(*terms): Implies, Iff): return union(*(used_variables(x) for x in t)) - elif type(t) in (ForAll, Exists, Lambda): + elif type(t) in (ForAll, Exists, Lambda, Binder): return union(used_variables(t.body), t.variables) elif hasattr(t,'args'): @@ -111,7 +111,7 @@ def used_constants(*terms): return frozenset((t,)) elif type(t) in (tuple, Var, Apply, Eq, Ite, Not, And, Or, - Implies, Iff, ForAll, Exists): + Implies, Iff, ForAll, Exists, Lambda, Binder): return union(*(used_constants(x) for x in t)) elif hasattr(t,'args'): @@ -150,7 +150,7 @@ def substitute(t, subs): elif type(t) in (Apply, Eq, Ite, Not, And, Or, Implies, Iff): return type(t)(*(substitute(x, subs) for x in t)) - elif type(t) in (ForAll, Exists, Lambda): + elif type(t) in (ForAll, Exists, Lambda, Binder): forbidden_variables = free_variables(*subs.values()) if forbidden_variables.isdisjoint(t.variables): return type(t)(t.variables, substitute(t.body, ( @@ -209,7 +209,7 @@ def substitute_apply(t, subs, by_name=False): elif type(t) in (Apply, Eq, Ite, Not, And, Or, Implies, Iff): return type(t)(*(_substitute_apply(x) for x in t)) - elif type(t) in (ForAll, Exists): + elif type(t) in (ForAll, Exists, Lambda, Binder): return type(t)(t.variables, _substitute_apply(t.body, subs=dict( (k, v) for k, v in subs.iteritems() if k not in t.variables diff --git a/ivy/type_inference.py b/ivy/type_inference.py index ce774ea52..85e064841 100644 --- a/ivy/type_inference.py +++ b/ivy/type_inference.py @@ -9,14 +9,12 @@ Type inference identifies variables and constants by name, so any two symbols with the same name will be unified, and a SortError will be raised if the unification fails. - -TODO: add support for Ite and Iff. """ from itertools import product, chain from logic import (Var, Const, Apply, Eq, Ite, Not, And, Or, Implies, - Iff, ForAll, Exists) + Iff, ForAll, Exists, Binder) from logic import (UninterpretedSort, FunctionSort, Boolean, TopSort, SortError, contains_topsort, is_polymorphic) from logic_util import used_constants, free_variables @@ -209,13 +207,31 @@ def infer_sorts(t, env=None): body_t(), ) + elif type(t) is Binder: + # create a copy of the environment and shadow that quantified + # variables + env = env.copy() + env.update((v.name, SortVar()) for v in t.variables) + xys = [infer_sorts(v, env) for v in t.variables] + vars_s = [x for x,y in xys] + vars_t = [y for x,y in xys] + body_s, body_t = infer_sorts(t.body, env) + return ( + FunctionSort(vars_s + [body_s]) if len(t.variables) > 0 else body_s, + lambda: Binder( + t.name, + [x() for x in vars_t], + body_t(), + ) + ) + elif hasattr(t,'clone'): xys = [infer_sorts(tt, env) for tt in t.args] terms_t = [y for x, y in xys] return TopSort(), lambda: t.clone([ x() for x in terms_t ]) - + else: assert False, type(t) @@ -298,6 +314,31 @@ def concretize_terms(terms,sorts=None): print repr(cf4) print + # alpha = SortVar() + # polyfS = FunctionSort(alpha, alpha) + # polyf = Const('ff', polyfS) + # f5 = (ps(xs))) + # cf5 = concretize_sorts(f5) + # print repr(f5) + # print repr(cf5) + # print + + f6 = Binder('mybinder', [XT], ps(XT)) + cf6 = concretize_sorts(f6) + print repr(f6) + print f6.sort + print repr(cf6) + print cf6.sort + print + + f7 = Binder('mybinder', [], ps(XT)) + cf7 = concretize_sorts(f7) + print repr(f7) + print f7.sort + print repr(cf7) + print cf7.sort + print + # TODO: add more tests, specifically a test that checks # unification across different quantified bodies diff --git a/test/test_liveness.ivy b/test/test_liveness.ivy new file mode 100644 index 000000000..eea95a3ae --- /dev/null +++ b/test/test_liveness.ivy @@ -0,0 +1,291 @@ +#lang ivy1.6 + +################################################################################ +# A liveness proof of the ticket protocol according to the "modern" +# version of our methodology: +# * all existentially quantified variables come from action parameters +# * d is updated automatically by inserting all action parameters +# * change from paper: there are constant symbols in the vocabulary - +# and they are handled both when updating d and when comparing states +################################################################################ + + +################################################################################ +# Module for axiomatizing a total order +# +################################################################################ + +module total_order(r) = { + axiom r(X,X) # Reflexivity + axiom r(X, Y) & r(Y, Z) -> r(X, Z) # Transitivity + axiom r(X, Y) & r(Y, X) -> X = Y # Anti-symmetry + axiom r(X, Y) | r(Y, X) # Totality +} + +################################################################################ +# +# Types, relations and functions describing the state +# +################################################################################ + +object ticket_protocol = { + + ################################################################################ + # + # The protocol itself, together with encoding the fairness + # constraints and the negation of the liveness property + # + # property: forall T:thread. G. pc2(T) -> F. pc3(T) + # fairness: forall T:thread. G. F. last_scheduled(T) + # ~property: exists T:thread. F. (pc2(T) & G. ~pc3(T)) + # Sk(~property): F. (pc2(t0) & G. ~pc3(t0)) + # + ################################################################################ + + type thread + type ticket + + relation le(X:ticket, Y:ticket) + instantiate total_order(le) + individual zero:ticket + axiom forall X. le(zero, X) + + relation pc1(T:thread) + relation pc2(T:thread) + relation pc3(T:thread) + + individual service:ticket + individual next_ticket:ticket + relation m(T:thread, K:ticket) # use relation and not a function to be in EPR + + init pc1(T) + init ~pc2(T) + init ~pc3(T) + init service = zero + init next_ticket = zero + init m(T,K) <-> K = zero + + relation request_flag + + # temporal specification + temporal property [mutualexclusion] globally forall T1,T2. pc3(T1) & pc3(T2) -> T1 = T2 + #temporal property [nonstravation] forall T:thread. globally pc2(T) -> eventually pc3(T) + # proof of nonstravation by l2s { + temporal property ($l2s_w. request_flag) <-> ~request_flag + # conjecture l2s_frozen -> request_flag + # conjecture l2s_saved -> request_flag + # } + temporal axiom [fairness] forall T:thread. globally eventually pc1(T) + #axiom [fairness] forall T:thread. globally eventually pc1(T) + # temporal axiom [fairness] forall T:thread. globally eventually ( + # (exists K1,K2. after step12(T,K1,K2)) | + # (exists K1. after step12(T,K1)) | + # (exists K1. after step12(T,K1)) | + # (exists K1,K2. after step12(T,K1,K2)) + # ) + # # naming for use in the inductive invariant + # let t0 = nonstravation.T + # let request_flag = pc2(t0) & globally ~pc3(t0) + # let last_scheduled(T) = ( + # (exists K1,K2. after step12(T,K1,K2)) | + # (exists K1. after step12(T,K1)) | + # (exists K1. after step12(T,K1)) | + # (exists K1,K2. after step12(T,K1,K2)) + # ) + # temporal property (forall T:thread. G. F. last_scheduled(T)) -> (forall T:thread. G. pc2(T) -> F. pc3(T)) + # fair action step12 + # fair action step22 + # fair action step23 + # fair action step31 + + # Axioms we may need (not for ticket, in general): + # (forall x. phi) -> (exists x. phi) + # (exists x. eventually phi) -> (eventually exists x. phi) + # + # these may be needed when x isn't in d, and we know forall + # x. eventually phi, and want to wait until (exists x. phi). + + + ################################################################################ + # + # Protocol actions + # + ################################################################################ + + action succ(x:ticket) returns (y:ticket) = { + assume ~le(y,x) & forall Z:ticket. ~le(Z,x) -> le(y,Z) + } + + action step12(t:thread,k1:ticket, k2:ticket) = { + assume pc1(t); + assume k1 = next_ticket; + assume k2 = succ(k1); + m(t,K) := K = k1; + next_ticket := k2; + pc1(t) := false; + pc2(t) := true; + } + + action step22(t:thread, k1:ticket) = { + assume pc2(t); + assume m(t,k1); + assume ~le(k1,service) + # stay in pc2 + } + + action step23(t:thread, k1:ticket) = { + assume pc2(t); + assume m(t,k1); + assume le(k1,service); + pc2(t) := false; + pc3(t) := true; + } + + action step31(t:thread, k1:ticket, k2:ticket) = { + assume pc3(t); + assume k1 = service; + assume k2 = succ(k1); + service := k2; + pc3(t) := false; + pc1(t) := true + } + + export step12 + export step22 + export step23 + export step31 + + ################################################################################ + # + # Conjectures for proving safety (also helps for liveness) + # + ################################################################################ + + # basic + conjecture pc1(T) | pc2(T) | pc3(T) + conjecture ~pc1(T) | ~pc2(T) + conjecture ~pc1(T) | ~pc3(T) + conjecture ~pc2(T) | ~pc3(T) + conjecture m(T,K1) & m(T,K2) -> K1 = K2 + + # safety property + conjecture pc3(T1) & pc3(T2) -> T1 = T2 + + # inductive invariant for proving safety + conjecture next_ticket = zero -> m(T,zero) + conjecture next_ticket ~= zero & m(T,M) -> ~le(next_ticket,M) + conjecture (pc2(T) | pc3(T)) -> next_ticket ~= zero + conjecture m(T1,M) & m(T2,M) & M ~= zero -> T1 = T2 + conjecture pc2(T) & m(T,M) -> le(service,M) + conjecture pc3(T) -> m(T,service) + conjecture le(service,next_ticket) + conjecture ~(~pc1(T1) & ~pc1(T2) & m(T1,zero) & m(T2,zero) & T1 ~= T2) + + ################################################################################ + # + # The liveness to safety construction introduces the following symbols: + # + # relation nonstravation.l2s.waiting + # relation nonstravation.l2s.frozen + # relation nonstravation.l2s.saved + # + # relation nonstravation.l2s.d_thread(T:thread) + # relation nonstravation.l2s.d_ticket(K:ticket) + # + # relation nonstravation.l2s.a_thread(T:thread) + # relation nonstravation.l2s.a_ticket(K:ticket) + # + # relation nonstravation.l2s.w[phi] for phi in FO-LTL(original vocabulary) + # relation nonstravation.l2s.wa[A] for A in fair-actions = {step12(T,K1,K2), + # step22(T,K1), + # step23(T,K1), + # step31(T,K1,K2)} + # + # relation nonstravation.l2s.s.pc1(T:thread) + # relation nonstravation.l2s.s.pc2(T:thread) + # relation nonstravation.l2s.s.pc3(T:thread) + # individual nonstravation.l2s.s.service : ticket + # individual nonstravation.l2s.s.next_ticket : ticket + # relation nonstravation.l2s.s.m(T:thread, K:ticket) + # relation nonstravation.l2s.s.[phi] for phi in FO-LTL(original vocabulary) + # + ################################################################################ + + + ################################################################################ + # + # Conjectures for proving liveness + # + ################################################################################ + + + # all safety conjectures for saved state are autumatically added + # conjecture X.l2s.saved -> phi(X.l2s.s) for phi in conjectures over original vocabulary + + # # basic + # conjecture nonstravation.l2s.w[request_flag] <-> ~request_flag + # conjecture nonstravation.l2s.frozen -> request_flag + # conjecture nonstravation.l2s.saved -> request_flag + # conjecture request_flag -> pc2(t0) + # conjecture nonstravation.l2s.saved -> nonstravation.l2s.s_m(t0,K) <-> m(t0,K) + # conjecture nonstravation.l2s.saved -> le(nonstravation.l2s.s_service, service) + + # # more properties of reachable protocol states + # conjecture pc1(T) & m(T,M) & M ~= zero -> ~le(service, M) + # conjecture forall K:ticket. ~le(next_ticket, K) & le(service, K) -> + # exists T:thread. m(T,K) & ~pc1(T) + # conjecture exists M. m(t0, M) + # # their saved counterpars are automatically added + + # # conjecture that nonstravation.l2s.d is large enough + # conjecture nonstravation.l2s.d_thread(t0) + # conjecture ~pc1(T) -> nonstravation.l2s.d_thread(T) + # conjecture le(K,next_ticket) -> nonstravation.l2s.d_ticket(K) + # # conjecture that nonstravation.l2s.a is large enough + # conjecture ~nonstravation.l2s.waiting -> nonstravation.l2s.a_thread(t0) + # conjecture ~nonstravation.l2s.waiting & m(T,K) & m(t0,K0) & ~le(K0,K) & ~pc1(T) -> nonstravation.l2s.a_thread(T) + # conjecture ~nonstravation.l2s.waiting & m(t0,K0) & le(K,K0) -> nonstravation.l2s.a_ticket(K) + + # # thread that have not been scheduled have not changed + # # conjecture l2s.saved & l2s.w_last_scheduled(T) -> (l2s.s_pc1(T) <-> pc1(T)) + # # conjecture l2s.saved & l2s.w_last_scheduled(T) -> (l2s.s_pc2(T) <-> pc2(T)) + # # conjecture l2s.saved & l2s.w_last_scheduled(T) -> (l2s.s_pc3(T) <-> pc3(T)) + # # conjecture l2s.saved & l2s.w_last_scheduled(T) -> (l2s.s_m(T,K) <-> m(T,K)) + # # this now will be written as: + # conjecture (nonstravation.l2s.saved & + # nonstravation.l2s.s.pc1(T) & + # nonstravation.l2s.wa[step12(T,K1,K2)] problem with K1,K2 not being in d_... + # # for now, try with last_scheduled + # conjecture nonstravation.l2s.saved & nonstravation.l2s.w[last_scheduled](T) -> ( + # (nonstravation.l2s.s_pc1(T) <-> pc1(T)) & + # (nonstravation.l2s.s_pc2(T) <-> pc2(T)) & + # (nonstravation.l2s.s_pc3(T) <-> pc3(T)) & + # (nonstravation.l2s.s_m(T,K) <-> m(T,K)) + # ) + + # # the thread that must advance - the thread that had the service as its local ticket at the save point + # conjecture ( + # nonstravation.l2s.saved & + # nonstravation.l2s.s_m(T,nonstravation.l2s.s_service) & + # ~nonstravation.l2s.w[last_scheduled](T) & + # nonstravation.l2s.s_pc2(T) & + # m(T,K) & + # m(t0,K0) + # ) -> ( + # (pc1(T) & K = nonstravation.l2s.s_service) | + # (pc2(T) & ~le(K,K0)) | + # (pc3(T) & K = nonstravation.l2s.s_service) + # ) + # conjecture ( + # nonstravation.l2s.saved & + # nonstravation.l2s.s_m(T,nonstravation.l2s.s_service) & + # ~nonstravation.l2s.w[last_scheduled](T) & + # nonstravation.l2s.s_pc3(T) & + # m(T,K) & + # m(t0,K0) + # ) -> ( + # (pc1(T) & K = nonstravation.l2s.s_service & ~le(service, nonstravation.l2s.s_service)) | + # (pc2(T) & ~le(K,K0)) + # ) + +} From 95b956087e38ce5fbbf435837c812b34d1800b68 Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Thu, 24 Aug 2017 09:45:36 -0700 Subject: [PATCH 04/17] most of the (non-nested) monitor is implemented in ivy_l2s.py --- ivy/ivy_l2s.py | 107 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 72 insertions(+), 35 deletions(-) diff --git a/ivy/ivy_l2s.py b/ivy/ivy_l2s.py index 842a5194b..d9d7d9a7e 100644 --- a/ivy/ivy_l2s.py +++ b/ivy/ivy_l2s.py @@ -12,9 +12,8 @@ from ivy_printer import print_module from ivy_actions import (AssignAction, Sequence, ChoiceAction, - AssumeAction, concat_actions) + AssumeAction, AssertAction, concat_actions) import logic as lg -import ivy_logic_utils as ilu def l2s(mod, lf): # modify mod in place @@ -34,13 +33,39 @@ def l2s(mod, lf): # print #print - # TODO: change wait_for to be from the tableau, this is a mock - wait_for = [c for c in mod.sig.symbols.values() if c.sort == lg.Boolean] + reset_a = [ + AssignAction(l2s_a(s)(v), l2s_d(s)(v)) + for s in uninterpreted_sorts + for v in [lg.Var('X',s)] + ] + + # TODO: change wait_for to be from the tableau or conjectures, this is a mock + wait_for = [] # list of (variables, term) + wait_for += [((), c) for c in mod.sig.symbols.values() if c.sort == lg.Boolean] wait_for += [ - f(*[lg.Var('X{}'.format(i), s) for i,s in enumerate(f.sort.domain)]) + (vs, f(*vs)) for f in mod.sig.symbols.values() - if isinstance(f.sort, lg.FunctionSort) and f.sort.range == lg.Boolean] - done_waiting = [ilu.close_epr(lg.Not(l2s_w(w))) for w in wait_for] + if isinstance(f.sort, lg.FunctionSort) and f.sort.range == lg.Boolean + for vs in [tuple(lg.Var('X{}'.format(i), s) for i,s in enumerate(f.sort.domain))] + ] + done_waiting = [ + lg.ForAll(vs, lg.Not(l2s_w(t))) if len(vs) > 0 else lg.Not(l2s_w(t)) + for vs, t in wait_for + ] + reset_w = [ + AssignAction( + l2s_w(t), + lg.And(*(l2s_d(v.sort)(v) for v in vs)) + ) + for vs, t in wait_for + ] + update_w = [ + AssignAction( + l2s_w(t), + lg.And(l2s_w(t), lg.Not(t), lg.Not(lg.Globally(lg.Not(t)))) + ) + for vs, t in wait_for + ] # TODO: change to_save to be taken from the conjectures to_save = [] # list of (variables, term) corresponding to l2s_s @@ -50,35 +75,45 @@ def l2s(mod, lf): to_save.append((vs, f(*vs))) else: to_save.append(((), f)) + save_state = [ + AssignAction(l2s_s(vs,t)(*vs) if len(vs) > 0 else l2s_s(vs,t), t) + for vs, t in to_save + ] + + fair_cycle = [l2s_saved] + fair_cycle += done_waiting + fair_cycle += [ + lg.ForAll(vs, lg.Implies( + lg.And(*(l2s_a(v.sort)(v) for v in vs)), + lg.Iff(l2s_s(vs, t)(*vs), t) + )) + if len(vs) > 0 else + lg.Iff(l2s_s(vs, t), t) + for vs, t in to_save + if t.sort == lg.Boolean or isinstance(t.sort, lg.FunctionSort) and t.sort.range == lg.Boolean + ] - prelude = ChoiceAction( + edge = lambda s1, s2: [ + AssumeAction(s1), + AssignAction(s1, lg.false), + AssignAction(s2, lg.true), + ] + change_monitor_state = [ChoiceAction( # waiting -> frozen - Sequence(*([ - AssumeAction(l2s_waiting) - ] +[ - AssumeAction(x) for x in done_waiting - ] + [ - AssignAction(l2s_waiting, lg.false), - AssignAction(l2s_frozen, lg.true), - ] + [ - AssignAction(l2s_a(s)(v), l2s_d(s)(v)) - for s in uninterpreted_sorts - for v in [lg.Var('X',s)] - ])), + Sequence(*( + edge(l2s_waiting, l2s_frozen) + + [AssumeAction(x) for x in done_waiting] + + reset_a + )), # frozen -> saved - Sequence(*([ - AssumeAction(l2s_frozen), - AssignAction(l2s_frozen, lg.false), - AssignAction(l2s_saved, lg.true), - ] + [ - AssignAction(l2s_s(vs,t)(*vs) if len(vs) > 0 else l2s_s(vs,t), t) - for vs, t in to_save - # TODO copy relations and functions - # TODO reset l2s_w from l2s_d - ])), - # stay + Sequence(*( + edge(l2s_frozen, l2s_saved) + + save_state + + reset_w + )), + # stay in same state (self edge) Sequence(), - ) + )] add_consts_to_d = [ AssignAction(l2s_d(s)(c), lg.true) @@ -95,10 +130,12 @@ def l2s(mod, lf): for p in action.formal_params ] new_action = concat_actions(*( - [prelude] + + change_monitor_state + add_params_to_d + [action] + - add_consts_to_d + add_consts_to_d + + update_w + + [AssertAction(lg.Not(lg.And(*fair_cycle)))] )) new_action.lineno = action.lineno new_action.formal_params = action.formal_params @@ -111,7 +148,7 @@ def l2s(mod, lf): AssignAction(l2s_saved, lg.false), ] l2s_init += add_consts_to_d - # TODO: initialize w + l2s_init += reset_w # TODO: assume [~property] mod.initializers.append(('l2s_init', Sequence(*l2s_init))) From e3460039949f08781792bea1c9c363fe6c6fd48e Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Thu, 24 Aug 2017 15:41:12 -0700 Subject: [PATCH 05/17] fixed bunch of problems, renamed Binder -> NamedBinder, ticket protocol conjectures now parse (and type infer) --- ivy/ivy_ast.py | 13 +++- ivy/ivy_compiler.py | 4 +- ivy/ivy_l2s.py | 42 ++++++++-- ivy/ivy_logic.py | 70 ++++++++++------- ivy/ivy_logic_parser.py | 18 +++-- ivy/ivy_logic_utils.py | 34 +++++++-- ivy/logic.py | 18 +++-- ivy/logic_util.py | 14 ++-- ivy/type_inference.py | 12 +-- test/test_liveness.ivy | 164 ++++++++++++++++++++-------------------- 10 files changed, 232 insertions(+), 157 deletions(-) diff --git a/ivy/ivy_ast.py b/ivy/ivy_ast.py index bcc5c9211..2b1872e1f 100644 --- a/ivy/ivy_ast.py +++ b/ivy/ivy_ast.py @@ -183,7 +183,7 @@ class Forall(Quantifier): class Exists(Quantifier): pass -class Binder(Formula): +class NamedBinder(Formula): def __init__(self, name, bounds, body): self.name = name self.bounds = bounds @@ -1182,17 +1182,22 @@ def ast_rewrite(x,rewrite): return Variable(x.rep,rewrite_sort(rewrite,x.sort)) if isinstance(x,Atom) or isinstance(x,App): # print "rewrite: x = {!r}, type(x.rep) = {!r}".format(x,type(x.rep)) - atom = type(x)(rewrite.rewrite_name(x.rep),ast_rewrite(x.args,rewrite)) + if isinstance(x.rep, NamedBinder): + atom = type(x)(ast_rewrite(x.rep,rewrite),ast_rewrite(x.args,rewrite)) + else: + atom = type(x)(rewrite.rewrite_name(x.rep),ast_rewrite(x.args,rewrite)) copy_attributes_ast(x,atom) if hasattr(x,'sort'): atom.sort = rewrite_sort(rewrite,x.sort) - if base_name_differs(x.rep,atom.rep): + if isinstance(x.rep, NamedBinder) or base_name_differs(x.rep,atom.rep): return atom return rewrite.rewrite_atom(atom) if isinstance(x,Literal): return Literal(x.polarity,ast_rewrite(x.atom,rewrite)) - if isinstance(x,Quantifier): + if isinstance(x, Quantifier): return type(x)(ast_rewrite(x.bounds,rewrite),ast_rewrite(x.args[0],rewrite)) + if isinstance(x, NamedBinder): + return type(x)(x.name,ast_rewrite(x.bounds,rewrite),ast_rewrite(x.args[0],rewrite)) if hasattr(x,'rewrite'): return x.rewrite(rewrite) if isinstance(x,LabeledFormula) or isinstance(x,NativeDef): diff --git a/ivy/ivy_compiler.py b/ivy/ivy_compiler.py index 284f36956..f3b4771b3 100644 --- a/ivy/ivy_compiler.py +++ b/ivy/ivy_compiler.py @@ -223,7 +223,7 @@ def compile_app(self): # handle action calls in rhs of assignment if expr_context and top_context and self.rep in top_context.actions: return compile_inline_call(self,args) - sym = ivy_logic.Equals if self.rep == '=' else ivy_logic.find_polymorphic_symbol(self.rep,throw=False) + sym = self.rep.cmpl() if isinstance(self.rep,ivy_ast.NamedBinder) else ivy_logic.Equals if self.rep == '=' else ivy_logic.find_polymorphic_symbol(self.rep,throw=False) if sym is not None: return (sym)(*args) res = compile_field_reference(self.rep,args) @@ -254,7 +254,7 @@ def cquant(q): ivy_ast.Quantifier.cmpl = lambda self: cquant(self)([v.compile() for v in self.bounds],self.args[0].compile()) -ivy_ast.Binder.cmpl = lambda self: ivy_logic.Binder( +ivy_ast.NamedBinder.cmpl = lambda self: ivy_logic.NamedBinder( self.name, [v.compile() for v in self.bounds], self.args[0].compile() diff --git a/ivy/ivy_l2s.py b/ivy/ivy_l2s.py index d9d7d9a7e..6ae3e8667 100644 --- a/ivy/ivy_l2s.py +++ b/ivy/ivy_l2s.py @@ -14,8 +14,17 @@ from ivy_actions import (AssignAction, Sequence, ChoiceAction, AssumeAction, AssertAction, concat_actions) import logic as lg +import ivy_logic_utils as ilu + +def forall(vs, body): + return lg.ForAll(vs, body) if len(vs) > 0 else body + def l2s(mod, lf): + + print ilu.used_symbols_asts(mod.labeled_conjs) + print '='*40 + print list(ilu.named_binders_asts(mod.labeled_conjs)) # modify mod in place l2s_waiting = lg.Const('l2s_waiting', lg.Boolean) @@ -23,8 +32,8 @@ def l2s(mod, lf): l2s_saved = lg.Const('l2s_saved', lg.Boolean) l2s_d = lambda sort: lg.Const('l2s_d',lg.FunctionSort(sort,lg.Boolean)) l2s_a = lambda sort: lg.Const('l2s_a',lg.FunctionSort(sort,lg.Boolean)) - l2s_w = lambda x: lg.Binder('l2s_w', [], x) - l2s_s = lambda vs, t: lg.Binder('l2s_s', vs, t) + l2s_w = lambda vs, t: lg.NamedBinder('l2s_w', vs, t) + l2s_s = lambda vs, t: lg.NamedBinder('l2s_s', vs, t) uninterpreted_sorts = [s for s in mod.sig.sorts.values() if type(s) is lg.UninterpretedSort] @@ -49,20 +58,20 @@ def l2s(mod, lf): for vs in [tuple(lg.Var('X{}'.format(i), s) for i,s in enumerate(f.sort.domain))] ] done_waiting = [ - lg.ForAll(vs, lg.Not(l2s_w(t))) if len(vs) > 0 else lg.Not(l2s_w(t)) + forall(vs, lg.Not(l2s_w(vs,t)(*vs))) for vs, t in wait_for ] reset_w = [ AssignAction( - l2s_w(t), + l2s_w(vs,t)(*vs), lg.And(*(l2s_d(v.sort)(v) for v in vs)) ) for vs, t in wait_for ] update_w = [ AssignAction( - l2s_w(t), - lg.And(l2s_w(t), lg.Not(t), lg.Not(lg.Globally(lg.Not(t)))) + l2s_w(vs,t)(*vs), + lg.And(l2s_w(vs,t)(*vs), lg.Not(t), lg.Not(lg.Globally(lg.Not(t)))) ) for vs, t in wait_for ] @@ -76,7 +85,7 @@ def l2s(mod, lf): else: to_save.append(((), f)) save_state = [ - AssignAction(l2s_s(vs,t)(*vs) if len(vs) > 0 else l2s_s(vs,t), t) + AssignAction(l2s_s(vs,t)(*vs), t) for vs, t in to_save ] @@ -90,7 +99,23 @@ def l2s(mod, lf): if len(vs) > 0 else lg.Iff(l2s_s(vs, t), t) for vs, t in to_save - if t.sort == lg.Boolean or isinstance(t.sort, lg.FunctionSort) and t.sort.range == lg.Boolean + if (t.sort == lg.Boolean or + isinstance(t.sort, lg.FunctionSort) and t.sort.range == lg.Boolean + ) + ] + fair_cycle += [ + forall(vs, lg.Implies( + lg.And(*( + [l2s_a(v.sort)(v) for v in vs] + + [lg.Or(l2s_a(t.sort)(l2s_s(vs, t)(*vs)), + l2s_a(t.sort)(t))] + )), + lg.Eq(l2s_s(vs, t)(*vs), t) + )) + for vs, t in to_save + if (isinstance(t.sort, lg.UninterpretedSort) or + isinstance(t.sort, lg.FunctionSort) and isinstance(t.sort.range, lg.UninterpretedSort) + ) ] edge = lambda s1, s2: [ @@ -120,6 +145,7 @@ def l2s(mod, lf): for s in uninterpreted_sorts for c in mod.sig.symbols.values() if c.sort == s ] + # TODO add all ground terms, not just consts (if stratified) # TODO: add conjectures that constants are in d and a diff --git a/ivy/ivy_logic.py b/ivy/ivy_logic.py index 86c1c827a..98c348119 100644 --- a/ivy/ivy_logic.py +++ b/ivy/ivy_logic.py @@ -66,7 +66,7 @@ import ivy_utils as iu import logic as lg import logic_util as lu -from logic import And,Or,Not,Globally,Eventually,Implies,Iff,Ite,ForAll,Exists,Lambda,Binder +from logic import And,Or,Not,Globally,Eventually,Implies,Iff,Ite,ForAll,Exists,Lambda,NamedBinder from type_inference import concretize_sorts, concretize_terms from collections import defaultdict from itertools import chain @@ -121,7 +121,7 @@ def __init__(self): self.sort = lg.TopSort('alpha') def is_numeral_name(s): - return s[0].isdigit() or s[0] == '"' + return s[0].isdigit() or s[0] == '"' Symbol = lg.Const @@ -240,9 +240,9 @@ def to_constraint(self): def sort(self): return lg.Boolean - -lg_ops = [lg.Eq, lg.Not, lg.Globally, lg.Eventually, lg.And, lg.Or, lg.Implies, lg.Iff, lg.Ite, lg.ForAll, lg.Exists, lg.Lambda, lg.Binder] + +lg_ops = [lg.Eq, lg.Not, lg.Globally, lg.Eventually, lg.And, lg.Or, lg.Implies, lg.Iff, lg.Ite, lg.ForAll, lg.Exists, lg.Lambda, lg.NamedBinder] for cls in lg_ops: cls.args = property(lambda self: [ a for a in self]) @@ -251,13 +251,15 @@ def sort(self): for cls in [lg.ForAll, lg.Exists, lg.Lambda]: cls.clone = lambda self,args: type(self)(self.variables,*args) -lg.Binder.clone = lambda self,args: lg.Binder(self.name, self.variables, *args) +lg.NamedBinder.clone = lambda self,args: lg.NamedBinder(self.name, self.variables, *args) +lg.NamedBinder.rep = property(lambda self: self) lg.Apply.clone = lambda self,args: type(self)(self.func, *args) lg.Apply.args = property(lambda self: self.terms) lg.Apply.rep = property(lambda self: self.func) lg.Apply.relname = property(lambda self: self.func) + for cls in [lg.Apply] + lg_ops: cls.is_numeral = lambda self: False @@ -278,7 +280,11 @@ def is_atom(term): # note: ivy1 treats instances of a constant in a formula as an app def is_app(term): - return isinstance(term,App) or isinstance(term,lg.Const) + return ( + isinstance(term,App) or + isinstance(term,lg.Const) or + isinstance(term,lg.NamedBinder) and len(term.variables) == 0 + ) def is_rel_app(term): return isinstance(term,App) and term.rep.is_relation() @@ -442,7 +448,7 @@ def is_segregated(fmla): reason_text = "{} is not segrated (variable positions differ)".format(name) return False return True - + def reason(): global reason_text return reason_text @@ -494,7 +500,7 @@ def symbols_over_universals(fmlas): print fmla raise foo return syms - + def universal_variables_rec(fmla,pos,univs): if is_quantifier(fmla): if pos == isinstance(fmla,lg.ForAll): @@ -540,7 +546,7 @@ def is_in_logic(term,logic,unstrat = False): if unstrat: reason_text = "functions are not stratified" return False - + if not is_segregated(term): reason_text = "formula is unsegregated" return False @@ -555,7 +561,7 @@ def is_in_logic(term,logic,unstrat = False): reason_text = "'{}' is iterpreted".format(s) return False return True - + def Constant(sym): @@ -574,7 +580,10 @@ def is_quantifier(term): return isinstance(term,lg.ForAll) or isinstance(term,lg.Exists) def is_binder(term): - return isinstance(term,lg.ForAll) or isinstance(term,lg.Exists) or isinstance(term,lg.Lambda) + return isinstance(term, (lg.ForAll, lg.Exists, lg.Lambda, lg.NamedBinder)) + +def is_named_binder(term): + return isinstance(term, lg.NamedBinder) def quantifier_vars(term): return term.variables @@ -611,7 +620,7 @@ def extensionality(destrs): c.append(eqn) res = Implies(And(*c),Equals(x,y)) return res - + # Return a prediciate stating relation "rel" is a partial function def partial_function(rel): lsort,rsort = rel.sort.dom @@ -623,10 +632,10 @@ def partial_function(rel): # use a function to an enumerated type to express this constraint. # We also include here extensionality for variants, that is to values # that point to the same value are the equal. A sore point, however, is that -# null values may not be equal. +# null values may not be equal. def exclusivity(sort,variants): - # partial funciton + # partial funciton def pto(s): return Symbol('*>',RelationSort([sort,s])) excs = [partial_function(pto(s)) for s in variants] @@ -647,7 +656,7 @@ def pto(s): Variable.rename = lambda self,name: Variable(name,self.sort) Variable.resort = lambda self,sort : Variable(self.name,sort) - + class Literal(AST): """ Either a positive or negative atomic formula. Literals are not @@ -773,7 +782,7 @@ def TopFunctionSort(arity): return lg.TopSort('alpha') res = FunctionSort(*[lg.TopSort('alpha{}'.format(idx)) for idx in range(arity+1)]) return res - + TopS = lg.TopS def apply(symbol,args): @@ -923,9 +932,16 @@ def __exit__(self,exc_type, exc_val, exc_tb): ('bvand' , [alpha,alpha,alpha]), ('bvor' , [alpha,alpha,alpha]), ('bvnot' , [alpha,alpha]), + # for liveness to safety reduction: + ('l2s_waiting', [lg.Boolean]), + ('l2s_frozen', [lg.Boolean]), + ('l2s_saved', [lg.Boolean]), + ('l2s_d', [alpha, lg.Boolean]), + ('l2s_a', [alpha, lg.Boolean]), ] -polymorphic_symbols = dict((x,lg.Const(x,lg.FunctionSort(*y))) for x,y in polymorphic_symbols_list) +polymorphic_symbols = dict((x,lg.Const(x,lg.FunctionSort(*y) if len(y) > 1 else y[0])) + for x,y in polymorphic_symbols_list) polymorphic_macros_map = { '<=' : '<', @@ -944,7 +960,7 @@ def is_macro(term): def expand_macro(term): return macros_expansions[term.func.name](term) - + def default_sort(): ds = sig._default_sort if ds != None: return ds @@ -989,7 +1005,7 @@ def is_constant(term): def is_variable(term): return isinstance(term,lg.Var) - + def all_concretely_sorted(*terms): return True @@ -1001,7 +1017,7 @@ def check_concretely_sorted(term,no_error=False,unsorted_var_names=()): if no_error: raise lg.SortError raise IvyError(None,"cannot infer sort of {} in {}".format(x,term)) - + def sort_infer(term,sort=None,no_error=False): res = concretize_sorts(term,sort) @@ -1092,7 +1108,7 @@ def fmla_to_str_ambiguous(term): return res def app_ugly(self): - if type(self.func) is lg.Binder: + if type(self.func) is lg.NamedBinder: name = str(self.func) else: name = self.func.name @@ -1102,7 +1118,7 @@ def app_ugly(self): if len(args) == 0: # shouldn't happen return name return name + '(' + ','.join(args) + ')' - + def nary_ugly(op,args,parens = True): res = (' ' + op + ' ').join([a.ugly() for a in args]) return ('(' + res + ')') if len(args) > 1 and parens else res @@ -1130,12 +1146,12 @@ def quant_ugly(self): res = ('forall ' if isinstance(self,lg.ForAll) else 'exists ' if isinstance(self,lg.Exists) else 'lambda ' if isinstance(self,lg.Lambda) else - '$' + self.name) + '$' + self.name + ' ') res += ','.join(v.ugly() for v in self.variables) res += '. ' + self.body.ugly() return res -for cls in [lg.ForAll,lg.Exists, lg.Lambda, lg.Binder]: +for cls in [lg.ForAll,lg.Exists, lg.Lambda, lg.NamedBinder]: cls.ugly = quant_ugly # Drop the type annotations of variables and polymorphic @@ -1200,7 +1216,7 @@ def quant_drop_annotations(self,inferred_sort,annotated_vars): for cls in [lg.ForAll, lg.Exists, lg.Lambda]: cls.drop_annotations = quant_drop_annotations -lg.Binder.drop_annotations = lambda self,inferred_sort,annotated_vars: lg.Binder( +lg.NamedBinder.drop_annotations = lambda self,inferred_sort,annotated_vars: lg.NamedBinder( self.name, [v.drop_annotations(False,annotated_vars) for v in self.variables], self.body.drop_annotations(True,annotated_vars) @@ -1217,7 +1233,7 @@ def pretty_fmla(self): return d.ugly() for cls in [lg.Eq, lg.Not, lg.And, lg.Or, lg.Implies, lg.Iff, lg.Ite, lg.ForAll, lg.Exists, - lg.Apply, lg.Var, lg.Const, lg.Lambda]: + lg.Apply, lg.Var, lg.Const, lg.Lambda, lg.NamedBinder]: cls.__str__ = pretty_fmla # end string conversion stuff @@ -1430,5 +1446,3 @@ def rec(self,fmla,vmap): return vmap[fmla] args = [self.rec(f,vmap) for f in fmla.args] return fmla.clone(args) - - diff --git a/ivy/ivy_logic_parser.py b/ivy/ivy_logic_parser.py index bfaa71da6..1a82d368b 100644 --- a/ivy/ivy_logic_parser.py +++ b/ivy/ivy_logic_parser.py @@ -357,12 +357,16 @@ def p_fmla_eventually_fmla(p): p[0] = Eventually(p[2]) p[0].lineno = get_lineno(p,1) -def p_fmla_binder_vars_dot_fmla(p): - 'fmla : DOLLAR SYMBOL simplevars DOT fmla' - p[0] = Binder(p[2], p[3],p[5]) - p[0].lineno = get_lineno(p,1) +def p_term_namedbinder_vars_dot_fmla(p): + 'term : LPAREN DOLLAR SYMBOL simplevars DOT fmla RPAREN LPAREN terms RPAREN' + x = NamedBinder(p[3], p[4],p[6]) + x.lineno = get_lineno(p,2) + p[0] = App(x, p[9]) + p[0].lineno = get_lineno(p,2) -def p_fmla_binder_dot_fmla(p): - 'fmla : DOLLAR SYMBOL DOT fmla' - p[0] = Binder(p[2], [],p[4]) +def p_term_namedbinder_dot_fmla(p): + 'term : DOLLAR SYMBOL DOT fmla' + p[0] = NamedBinder(p[2], [],p[4]) p[0].lineno = get_lineno(p,1) + +# TODO: should the above rules create formulas also or only for terms diff --git a/ivy/ivy_logic_utils.py b/ivy/ivy_logic_utils.py index f22dc2eeb..94f25f66c 100644 --- a/ivy/ivy_logic_utils.py +++ b/ivy/ivy_logic_utils.py @@ -183,7 +183,7 @@ def rename_ast(ast,subs): a sort conflict. """ args = [rename_ast(x,subs) for x in ast.args] - if is_app(ast): + if is_app(ast) and not is_named_binder(ast): return subs.get(ast.rep,ast.rep)(*args) return ast.clone(args) @@ -322,17 +322,35 @@ def constants_ast(ast): def symbols_ast(ast): if is_app(ast): - yield ast.rep + if is_binder(ast.rep): + for x in symbols_ast(ast.rep.body): + yield x + else: + yield ast.rep for arg in ast.args: for x in symbols_ast(arg): yield x +def named_binders_ast(ast): + if is_named_binder(ast): + yield ast + elif is_app(ast): + if is_named_binder(ast.rep): + yield ast.rep + for x in named_binders_ast(ast.rep.body): + yield x + for arg in ast.args: + for x in named_binders_ast(arg): + yield x + # extend to clauses, etc... symbols_asts = symbols_clause = symbols_cube = apply_gen_to_list(symbols_ast) symbols_clauses = symbols_cubes = apply_gen_to_clauses(symbols_ast) +named_binders_asts = apply_gen_to_list(named_binders_ast) + # get set of symbols occurring used_symbols_ast = gen_to_set(symbols_ast) @@ -1134,7 +1152,7 @@ def eqcm_upd(lhs,rhs,symset,map2): del l[:] return True return False - + def exists_quant_clauses_map(syms,clauses): used = used_symbols_clauses(clauses) symset = set(syms) @@ -1179,13 +1197,13 @@ def true_clauses(): instantiator = None def definition_instances(fmla): - if instantiator != None: + if instantiator != None: gts = ground_apps_ast(fmla) return instantiator(gts) return Clauses([]) def unfold_definitions_clauses(clauses): - if instantiator != None: + if instantiator != None: gts = ground_apps_clauses(clauses) insts = instantiator(gts) if insts.fmlas: @@ -1199,7 +1217,7 @@ def dual_clauses(clauses, skolemizer=None): sksubs = dict((v.rep,skolemizer(v)) for v in vs) clauses = substitute_clauses(clauses,sksubs) fmla = negate(clauses_to_formula(clauses)) - if instantiator != None: + if instantiator != None: gts = ground_apps_clauses(clauses) insts = instantiator(gts) fmla = And(fmla,clauses_to_formula(insts)) @@ -1211,7 +1229,7 @@ def dual_formula(fmla, skolemizer=None): vs = used_variables_in_order_ast(fmla) sksubs = dict((v.rep,skolemizer(v)) for v in vs) fmla = negate(substitute_ast(fmla,sksubs)) - if instantiator != None: + if instantiator != None: gts = ground_apps_ast(fmla) insts = clauses_to_formula(instantiator(gts)) fmla = And(fmla,insts) @@ -1226,7 +1244,7 @@ def skolemize_formula(fmla, skolemizer=None): fmla = fmla.body sksubs = dict((v.rep,skolemizer(v)) for v in vs) fmla = substitute_ast(fmla,sksubs) - if instantiator != None: + if instantiator != None: gts = ground_apps_ast(fmla) insts = clauses_to_formula(instantiator(gts)) fmla = And(fmla,insts) diff --git a/ivy/logic.py b/ivy/logic.py index a62ca2325..967f680d9 100644 --- a/ivy/logic.py +++ b/ivy/logic.py @@ -116,7 +116,7 @@ def _preprocess_(cls, name, sort): def __str__(self): return self.name def __call__(self, *terms): - return Apply(self, *terms) + return Apply(self, *terms) if len(terms) > 0 else self class Const(recstruct('Const', ['name', 'sort'], [])): @@ -129,7 +129,7 @@ def _preprocess_(cls, name, sort): def __str__(self): return self.name def __call__(self, *terms): - return Apply(self, *terms) + return Apply(self, *terms) if len(terms) > 0 else self class Apply(recstruct('Apply', [], ['func', '*terms'])): @@ -321,6 +321,8 @@ class ForAll(recstruct('ForAll', ['variables'], ['body'])): sort = Boolean @classmethod def _preprocess_(cls, variables, body): + if len(variables) == 0: + raise IvyError("Must quantify over at least one variable") if not all(type(v) is Var for v in variables): raise IvyError("Can only quantify over variables") if body.sort not in (Boolean, TopS): @@ -337,6 +339,8 @@ class Exists(recstruct('Exists', ['variables'], ['body'])): sort = Boolean @classmethod def _preprocess_(cls, variables, body): + if len(variables) == 0: + raise IvyError("Must quantify over at least one variable") if not all(type(v) is Var for v in variables): raise IvyError("Can only quantify over variables") if body.sort not in (Boolean, TopS): @@ -362,7 +366,7 @@ def __str__(self): self.body) -class Binder(recstruct('Binder', ['name', 'variables'], ['body'])): +class NamedBinder(recstruct('NamedBinder', ['name', 'variables'], ['body'])): __slots__ = () @classmethod def _preprocess_(cls, name, variables, body): @@ -371,12 +375,12 @@ def _preprocess_(cls, name, variables, body): # TODO: check the name after we decide on valid names return name, tuple(variables), body def __str__(self): - return '({} {}. {})'.format( # TODO: change after we decide on the syntax for this + return '(${} {}. {})'.format( # TODO: change after we decide on the syntax for this self.name, ', '.join('{}:{}'.format(v.name, v.sort) for v in sorted(self.variables)), self.body) def __call__(self, *terms): - return Apply(self, *terms) + return Apply(self, *terms) if len(terms) > 0 else self sort = property( lambda self: FunctionSort(*([v.sort for v in self.variables] + [self.body.sort])) @@ -416,12 +420,12 @@ def __call__(self, *terms): assert not contains_topsort(g) assert contains_topsort(h) - b = Binder('mybinder', [X,Y,Z], Implies(And(f(X,Y), f(X,Z)), Eq(Y,Z))) + b = NamedBinder('mybinder', [X,Y,Z], Implies(And(f(X,Y), f(X,Z)), Eq(Y,Z))) print b print b.sort print - b = Binder('mybinder', [X,Y,Z], Z) + b = NamedBinder('mybinder', [X,Y,Z], Z) print b print b.sort diff --git a/ivy/logic_util.py b/ivy/logic_util.py index f76e21512..5cea0c378 100644 --- a/ivy/logic_util.py +++ b/ivy/logic_util.py @@ -8,7 +8,7 @@ from functools import partial from logic import (Var, Const, Apply, Eq, Ite, Not, And, Or, Implies, - Iff, ForAll, Exists, Lambda, Binder) + Iff, ForAll, Exists, Lambda, NamedBinder) from logic import contains_topsort @@ -35,7 +35,7 @@ def used_variables(*terms): Implies, Iff): return union(*(used_variables(x) for x in t)) - elif type(t) in (ForAll, Exists, Lambda, Binder): + elif type(t) in (ForAll, Exists, Lambda, NamedBinder): return union(used_variables(t.body), t.variables) elif hasattr(t,'args'): @@ -66,7 +66,7 @@ def free_variables(*terms, **kwargs): Implies, Iff): return union(*(_free_variables(x) for x in t)) - elif type(t) in (ForAll, Exists, Lambda, Binder): + elif type(t) in (ForAll, Exists, Lambda, NamedBinder): return _free_variables(t.body) - _free_variables(*t.variables) elif hasattr(t,'args'): @@ -90,7 +90,7 @@ def bound_variables(*terms): Implies, Iff): return union(*(used_variables(x) for x in t)) - elif type(t) in (ForAll, Exists, Lambda, Binder): + elif type(t) in (ForAll, Exists, Lambda, NamedBinder): return union(used_variables(t.body), t.variables) elif hasattr(t,'args'): @@ -111,7 +111,7 @@ def used_constants(*terms): return frozenset((t,)) elif type(t) in (tuple, Var, Apply, Eq, Ite, Not, And, Or, - Implies, Iff, ForAll, Exists, Lambda, Binder): + Implies, Iff, ForAll, Exists, Lambda, NamedBinder): return union(*(used_constants(x) for x in t)) elif hasattr(t,'args'): @@ -150,7 +150,7 @@ def substitute(t, subs): elif type(t) in (Apply, Eq, Ite, Not, And, Or, Implies, Iff): return type(t)(*(substitute(x, subs) for x in t)) - elif type(t) in (ForAll, Exists, Lambda, Binder): + elif type(t) in (ForAll, Exists, Lambda, NamedBinder): forbidden_variables = free_variables(*subs.values()) if forbidden_variables.isdisjoint(t.variables): return type(t)(t.variables, substitute(t.body, ( @@ -209,7 +209,7 @@ def substitute_apply(t, subs, by_name=False): elif type(t) in (Apply, Eq, Ite, Not, And, Or, Implies, Iff): return type(t)(*(_substitute_apply(x) for x in t)) - elif type(t) in (ForAll, Exists, Lambda, Binder): + elif type(t) in (ForAll, Exists, Lambda, NamedBinder): return type(t)(t.variables, _substitute_apply(t.body, subs=dict( (k, v) for k, v in subs.iteritems() if k not in t.variables diff --git a/ivy/type_inference.py b/ivy/type_inference.py index 85e064841..6fa40b432 100644 --- a/ivy/type_inference.py +++ b/ivy/type_inference.py @@ -14,7 +14,7 @@ from itertools import product, chain from logic import (Var, Const, Apply, Eq, Ite, Not, And, Or, Implies, - Iff, ForAll, Exists, Binder) + Iff, ForAll, Exists, NamedBinder) from logic import (UninterpretedSort, FunctionSort, Boolean, TopSort, SortError, contains_topsort, is_polymorphic) from logic_util import used_constants, free_variables @@ -207,7 +207,7 @@ def infer_sorts(t, env=None): body_t(), ) - elif type(t) is Binder: + elif type(t) is NamedBinder: # create a copy of the environment and shadow that quantified # variables env = env.copy() @@ -217,8 +217,8 @@ def infer_sorts(t, env=None): vars_t = [y for x,y in xys] body_s, body_t = infer_sorts(t.body, env) return ( - FunctionSort(vars_s + [body_s]) if len(t.variables) > 0 else body_s, - lambda: Binder( + FunctionSort(*(vars_s + [body_s])) if len(t.variables) > 0 else body_s, + lambda: NamedBinder( t.name, [x() for x in vars_t], body_t(), @@ -323,7 +323,7 @@ def concretize_terms(terms,sorts=None): # print repr(cf5) # print - f6 = Binder('mybinder', [XT], ps(XT)) + f6 = NamedBinder('mybinder', [XT], ps(XT)) cf6 = concretize_sorts(f6) print repr(f6) print f6.sort @@ -331,7 +331,7 @@ def concretize_terms(terms,sorts=None): print cf6.sort print - f7 = Binder('mybinder', [], ps(XT)) + f7 = NamedBinder('mybinder', [], ps(XT)) cf7 = concretize_sorts(f7) print repr(f7) print f7.sort diff --git a/test/test_liveness.ivy b/test/test_liveness.ivy index eea95a3ae..a24171e64 100644 --- a/test/test_liveness.ivy +++ b/test/test_liveness.ivy @@ -57,6 +57,7 @@ object ticket_protocol = { individual service:ticket individual next_ticket:ticket relation m(T:thread, K:ticket) # use relation and not a function to be in EPR + individual testfunction(K:ticket):thread # use relation and not a function to be in EPR init pc1(T) init ~pc2(T) @@ -72,9 +73,8 @@ object ticket_protocol = { #temporal property [nonstravation] forall T:thread. globally pc2(T) -> eventually pc3(T) # proof of nonstravation by l2s { temporal property ($l2s_w. request_flag) <-> ~request_flag - # conjecture l2s_frozen -> request_flag - # conjecture l2s_saved -> request_flag - # } + conjecture l2s_frozen -> request_flag + conjecture l2s_saved -> request_flag temporal axiom [fairness] forall T:thread. globally eventually pc1(T) #axiom [fairness] forall T:thread. globally eventually pc1(T) # temporal axiom [fairness] forall T:thread. globally eventually ( @@ -85,6 +85,8 @@ object ticket_protocol = { # ) # # naming for use in the inductive invariant # let t0 = nonstravation.T + individual t0:thread + relation last_scheduled(T:thread) # let request_flag = pc2(t0) & globally ~pc3(t0) # let last_scheduled(T) = ( # (exists K1,K2. after step12(T,K1,K2)) | @@ -185,29 +187,29 @@ object ticket_protocol = { # # The liveness to safety construction introduces the following symbols: # - # relation nonstravation.l2s.waiting - # relation nonstravation.l2s.frozen - # relation nonstravation.l2s.saved + # relation nonstravation.l2s_waiting + # relation nonstravation.l2s_frozen + # relation nonstravation.l2s_saved # - # relation nonstravation.l2s.d_thread(T:thread) - # relation nonstravation.l2s.d_ticket(K:ticket) + # relation nonstravation.l2s_d_thread(T:thread) + # relation nonstravation.l2s_d_ticket(K:ticket) # - # relation nonstravation.l2s.a_thread(T:thread) - # relation nonstravation.l2s.a_ticket(K:ticket) + # relation nonstravation.l2s_a_thread(T:thread) + # relation nonstravation.l2s_a_ticket(K:ticket) # - # relation nonstravation.l2s.w[phi] for phi in FO-LTL(original vocabulary) - # relation nonstravation.l2s.wa[A] for A in fair-actions = {step12(T,K1,K2), + # relation nonstravation.l2s_w[phi] for phi in FO-LTL(original vocabulary) + # relation nonstravation.l2s_wa[A] for A in fair-actions = {step12(T,K1,K2), # step22(T,K1), # step23(T,K1), # step31(T,K1,K2)} # - # relation nonstravation.l2s.s.pc1(T:thread) - # relation nonstravation.l2s.s.pc2(T:thread) - # relation nonstravation.l2s.s.pc3(T:thread) - # individual nonstravation.l2s.s.service : ticket - # individual nonstravation.l2s.s.next_ticket : ticket - # relation nonstravation.l2s.s.m(T:thread, K:ticket) - # relation nonstravation.l2s.s.[phi] for phi in FO-LTL(original vocabulary) + # relation nonstravation.l2s_s.pc1(T:thread) + # relation nonstravation.l2s_s.pc2(T:thread) + # relation nonstravation.l2s_s.pc3(T:thread) + # individual nonstravation.l2s_s.service : ticket + # individual nonstravation.l2s_s.next_ticket : ticket + # relation nonstravation.l2s_s.m(T:thread, K:ticket) + # relation nonstravation.l2s_s.[phi] for phi in FO-LTL(original vocabulary) # ################################################################################ @@ -220,72 +222,74 @@ object ticket_protocol = { # all safety conjectures for saved state are autumatically added - # conjecture X.l2s.saved -> phi(X.l2s.s) for phi in conjectures over original vocabulary + # conjecture X.l2s_saved -> phi(X.l2s_s) for phi in conjectures over original vocabulary # # basic - # conjecture nonstravation.l2s.w[request_flag] <-> ~request_flag - # conjecture nonstravation.l2s.frozen -> request_flag - # conjecture nonstravation.l2s.saved -> request_flag - # conjecture request_flag -> pc2(t0) - # conjecture nonstravation.l2s.saved -> nonstravation.l2s.s_m(t0,K) <-> m(t0,K) - # conjecture nonstravation.l2s.saved -> le(nonstravation.l2s.s_service, service) + conjecture ($l2s_w. request_flag) <-> ~request_flag + conjecture l2s_frozen -> request_flag + conjecture l2s_saved -> request_flag + conjecture request_flag -> pc2(t0) + conjecture l2s_saved -> ($l2s_s X,Y. m(X,Y))(t0,K) <-> m(t0,K) + conjecture ($l2s_s X, Y. m(X,Y))(X,Y) + conjecture ($l2s_w X, Y. m(X,Y))(X,Y) + conjecture l2s_saved -> le( ($ l2s_s . service) , service) # # more properties of reachable protocol states - # conjecture pc1(T) & m(T,M) & M ~= zero -> ~le(service, M) - # conjecture forall K:ticket. ~le(next_ticket, K) & le(service, K) -> - # exists T:thread. m(T,K) & ~pc1(T) - # conjecture exists M. m(t0, M) + conjecture pc1(T) & m(T,M) & M ~= zero -> ~le(service, M) + conjecture forall K:ticket. ~le(next_ticket, K) & le(service, K) -> + exists T:thread. m(T,K) & ~pc1(T) + conjecture exists M. m(t0, M) # # their saved counterpars are automatically added - # # conjecture that nonstravation.l2s.d is large enough - # conjecture nonstravation.l2s.d_thread(t0) - # conjecture ~pc1(T) -> nonstravation.l2s.d_thread(T) - # conjecture le(K,next_ticket) -> nonstravation.l2s.d_ticket(K) - # # conjecture that nonstravation.l2s.a is large enough - # conjecture ~nonstravation.l2s.waiting -> nonstravation.l2s.a_thread(t0) - # conjecture ~nonstravation.l2s.waiting & m(T,K) & m(t0,K0) & ~le(K0,K) & ~pc1(T) -> nonstravation.l2s.a_thread(T) - # conjecture ~nonstravation.l2s.waiting & m(t0,K0) & le(K,K0) -> nonstravation.l2s.a_ticket(K) - - # # thread that have not been scheduled have not changed - # # conjecture l2s.saved & l2s.w_last_scheduled(T) -> (l2s.s_pc1(T) <-> pc1(T)) - # # conjecture l2s.saved & l2s.w_last_scheduled(T) -> (l2s.s_pc2(T) <-> pc2(T)) - # # conjecture l2s.saved & l2s.w_last_scheduled(T) -> (l2s.s_pc3(T) <-> pc3(T)) - # # conjecture l2s.saved & l2s.w_last_scheduled(T) -> (l2s.s_m(T,K) <-> m(T,K)) - # # this now will be written as: - # conjecture (nonstravation.l2s.saved & - # nonstravation.l2s.s.pc1(T) & - # nonstravation.l2s.wa[step12(T,K1,K2)] problem with K1,K2 not being in d_... - # # for now, try with last_scheduled - # conjecture nonstravation.l2s.saved & nonstravation.l2s.w[last_scheduled](T) -> ( - # (nonstravation.l2s.s_pc1(T) <-> pc1(T)) & - # (nonstravation.l2s.s_pc2(T) <-> pc2(T)) & - # (nonstravation.l2s.s_pc3(T) <-> pc3(T)) & - # (nonstravation.l2s.s_m(T,K) <-> m(T,K)) - # ) - - # # the thread that must advance - the thread that had the service as its local ticket at the save point - # conjecture ( - # nonstravation.l2s.saved & - # nonstravation.l2s.s_m(T,nonstravation.l2s.s_service) & - # ~nonstravation.l2s.w[last_scheduled](T) & - # nonstravation.l2s.s_pc2(T) & - # m(T,K) & - # m(t0,K0) - # ) -> ( - # (pc1(T) & K = nonstravation.l2s.s_service) | - # (pc2(T) & ~le(K,K0)) | - # (pc3(T) & K = nonstravation.l2s.s_service) - # ) - # conjecture ( - # nonstravation.l2s.saved & - # nonstravation.l2s.s_m(T,nonstravation.l2s.s_service) & - # ~nonstravation.l2s.w[last_scheduled](T) & - # nonstravation.l2s.s_pc3(T) & - # m(T,K) & - # m(t0,K0) - # ) -> ( - # (pc1(T) & K = nonstravation.l2s.s_service & ~le(service, nonstravation.l2s.s_service)) | - # (pc2(T) & ~le(K,K0)) - # ) + # conjecture that l2s_d is large enough + conjecture l2s_d(t0) + conjecture ~pc1(T) -> l2s_d(T) + conjecture le(K,next_ticket) -> l2s_d(K) + # conjecture that l2s_a is large enough + conjecture ~l2s_waiting -> l2s_a(t0) + conjecture ~l2s_waiting & m(T,K) & m(t0,K0) & ~le(K0,K) & ~pc1(T) -> l2s_a(T) + conjecture ~l2s_waiting & m(t0,K0) & le(K,K0) -> l2s_a(K) + + # thread that have not been scheduled have not changed + # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_pc1(T) <-> pc1(T)) + # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_pc2(T) <-> pc2(T)) + # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_pc3(T) <-> pc3(T)) + # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_m(T,K) <-> m(T,K)) + # this now will be written as: + # conjecture (l2s_saved & + # l2s_s.pc1(T) & + # l2s_wa[step12(T,K1,K2)] problem with K1,K2 not being in d_... + # for now, try with last_scheduled + conjecture l2s_saved & ($l2s_w T. last_scheduled(T))(T) -> ( + (($l2s_s T. pc1(T))(T) <-> pc1(T)) & + (($l2s_s T. pc2(T))(T) <-> pc2(T)) & + (($l2s_s T. pc3(T))(T) <-> pc3(T)) & + (($l2s_s T,K. m(T,K))(T,K) <-> m(T,K)) + ) + + # the thread that must advance - the thread that had the service as its local ticket at the save point + conjecture ( + l2s_saved & + ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & + ~($l2s_w T. last_scheduled(T))(T) & + ($l2s_s T. pc2(T))(T) & + m(T,K) & + m(t0,K0) + ) -> ( + (pc1(T) & K = ($l2s_s. service)) | + (pc2(T) & ~le(K,K0)) | + (pc3(T) & K = ($l2s_s. service)) + ) + conjecture ( + l2s_saved & + ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & + ~($l2s_w T. last_scheduled(T))(T) & + ($l2s_s T. pc3(T))(T) & + m(T,K) & + m(t0,K0) + ) -> ( + (pc1(T) & K = ($l2s_s. service) & ~le(service, ($l2s_s. service))) | + (pc2(T) & ~le(K,K0)) + ) } From 5258b74fabe788145bd15e384379cf44a6be79fe Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Sat, 26 Aug 2017 21:21:01 -0700 Subject: [PATCH 06/17] l2s finished and passes on to safety checks, still not able to prove ticket --- ivy/ivy_compiler.py | 1 - ivy/ivy_l2s.py | 165 +++++++++++++++++++++++++++++------ ivy/ivy_logic.py | 3 + ivy/ivy_logic_utils.py | 56 ++++++++++++ ivy/logic.py | 6 +- test/test_liveness.ivy | 192 ++++++++++++++++++++++++----------------- 6 files changed, 314 insertions(+), 109 deletions(-) diff --git a/ivy/ivy_compiler.py b/ivy/ivy_compiler.py index f3b4771b3..58fee2738 100644 --- a/ivy/ivy_compiler.py +++ b/ivy/ivy_compiler.py @@ -1171,7 +1171,6 @@ def named_trans(prop): from ivy_l2s import l2s print "=================" + "\n" * 10 l2s(mod, prop) - assert False else: mod.labeled_props.append(prop) if prop.id in nmap: diff --git a/ivy/ivy_l2s.py b/ivy/ivy_l2s.py index 6ae3e8667..506bbdbda 100644 --- a/ivy/ivy_l2s.py +++ b/ivy/ivy_l2s.py @@ -10,9 +10,13 @@ # self.sig +from collections import defaultdict +from itertools import chain + from ivy_printer import print_module from ivy_actions import (AssignAction, Sequence, ChoiceAction, - AssumeAction, AssertAction, concat_actions) + AssumeAction, AssertAction, HavocAction, + concat_actions) import logic as lg import ivy_logic_utils as ilu @@ -22,9 +26,9 @@ def forall(vs, body): def l2s(mod, lf): - print ilu.used_symbols_asts(mod.labeled_conjs) - print '='*40 - print list(ilu.named_binders_asts(mod.labeled_conjs)) + #print ilu.used_symbols_asts(mod.labeled_conjs) + #print '='*40 + #print list(ilu.named_binders_asts(mod.labeled_conjs)) # modify mod in place l2s_waiting = lg.Const('l2s_waiting', lg.Boolean) @@ -34,6 +38,8 @@ def l2s(mod, lf): l2s_a = lambda sort: lg.Const('l2s_a',lg.FunctionSort(sort,lg.Boolean)) l2s_w = lambda vs, t: lg.NamedBinder('l2s_w', vs, t) l2s_s = lambda vs, t: lg.NamedBinder('l2s_s', vs, t) + l2s_g = lambda vs, t: lg.NamedBinder('l2s_g', vs, t) + old_l2s_g = lambda vs, t: lg.NamedBinder('old_l2s_g', vs, t) uninterpreted_sorts = [s for s in mod.sig.sorts.values() if type(s) is lg.UninterpretedSort] @@ -48,42 +54,90 @@ def l2s(mod, lf): for v in [lg.Var('X',s)] ] - # TODO: change wait_for to be from the tableau or conjectures, this is a mock - wait_for = [] # list of (variables, term) - wait_for += [((), c) for c in mod.sig.symbols.values() if c.sort == lg.Boolean] - wait_for += [ - (vs, f(*vs)) - for f in mod.sig.symbols.values() - if isinstance(f.sort, lg.FunctionSort) and f.sort.range == lg.Boolean - for vs in [tuple(lg.Var('X{}'.format(i), s) for i,s in enumerate(f.sort.domain))] - ] + named_binders = defaultdict(list) # dict mapping names to lists of (vars, body) + for b in ilu.named_binders_asts(mod.labeled_conjs): + named_binders[b.name].append((b.variables, b.body)) + named_binders = defaultdict(list,((k,list(set(v))) for k,v in named_binders.iteritems())) + # TODO: the above appears twice in this file, why? fix it! + # TODO: normalize named binders, so variable names won't be significant + + # DONE?: change to_wait to be from the tableau or conjectures, this is a mock + to_wait = [] # list of (variables, term) + # to_wait += [((), c) for c in mod.sig.symbols.values() if c.sort == lg.Boolean] + # to_wait += [ + # (vs, f(*vs)) + # for f in mod.sig.symbols.values() + # if isinstance(f.sort, lg.FunctionSort) and f.sort.range == lg.Boolean + # for vs in [tuple(lg.Var('X{}'.format(i), s) for i,s in enumerate(f.sort.domain))] + # ] + to_wait = named_binders['l2s_w'] done_waiting = [ forall(vs, lg.Not(l2s_w(vs,t)(*vs))) - for vs, t in wait_for + for vs, t in to_wait ] reset_w = [ AssignAction( l2s_w(vs,t)(*vs), lg.And(*(l2s_d(v.sort)(v) for v in vs)) ) - for vs, t in wait_for + for vs, t in to_wait ] update_w = [ AssignAction( l2s_w(vs,t)(*vs), lg.And(l2s_w(vs,t)(*vs), lg.Not(t), lg.Not(lg.Globally(lg.Not(t)))) ) - for vs, t in wait_for + for vs, t in to_wait + ] + + # tableau construction (sort of) + temporals = [] # TODO get from temporal axioms and temporal properties + temporals += list(ilu.temporals_asts( + mod.labeled_conjs + + mod.labeled_axioms + + mod.labeled_props + + [lf] + )) + temporals += [lg.Globally(lg.Not(t)) for vs, t in to_wait] + print '='*40 + for t in temporals: + print t, '\n' + print '='*40 + to_g = [ # list of (variables, formula) + (tuple(sorted(ilu.variables_ast(tt))), tt) + for t in temporals + for tt in [t.body if type(t) is lg.Globally else + lg.Not(t.body) if type(t) is lg.Eventually else 1/0] + ] + to_g = list(set(to_g)) + print '='*40 + for vs, t in to_g: + print vs, t, '\n' + print '='*40 + + assume_g_axioms = [ + AssumeAction(forall(vs, lg.Implies(l2s_g(vs, t)(*vs), t))) + for vs, t in to_g ] + update_g = [a for vs, t in to_g for a in [ + HavocAction(l2s_g(vs, t)(*vs)), + AssumeAction(forall(vs, lg.Implies(old_l2s_g(vs, t)(*vs), l2s_g(vs, t)(*vs)))), + AssumeAction(forall(vs, lg.Implies(lg.And(lg.Not(old_l2s_g(vs, t)(*vs)), t), lg.Not(l2s_g(vs, t)(*vs))))), +# lg.Or( +# l2s_g(vs, t)(*vs), +# lg.And(lg.Not(t), l2s_ga(vs, t)(*vs)) +# )), + ]] - # TODO: change to_save to be taken from the conjectures + # DONE?: change to_save to be taken from the conjectures to_save = [] # list of (variables, term) corresponding to l2s_s - for f in mod.sig.symbols.values(): - if isinstance(f.sort, lg.FunctionSort): - vs = tuple(lg.Var('X{}'.format(i), s) for i,s in enumerate(f.sort.domain)) - to_save.append((vs, f(*vs))) - else: - to_save.append(((), f)) + # for f in mod.sig.symbols.values(): + # if isinstance(f.sort, lg.FunctionSort): + # vs = tuple(lg.Var('X{}'.format(i), s) for i,s in enumerate(f.sort.domain)) + # to_save.append((vs, f(*vs))) + # else: + # to_save.append(((), f)) + to_save = named_binders['l2s_s'] save_state = [ AssignAction(l2s_s(vs,t)(*vs), t) for vs, t in to_save @@ -117,6 +171,8 @@ def l2s(mod, lf): isinstance(t.sort, lg.FunctionSort) and isinstance(t.sort.range, lg.UninterpretedSort) ) ] + assert_no_fair_cycle = AssertAction(lg.true) #AssertAction(lg.Not(lg.And(*fair_cycle))) + assert_no_fair_cycle.lineno = lf.lineno edge = lambda s1, s2: [ AssumeAction(s1), @@ -158,10 +214,12 @@ def l2s(mod, lf): new_action = concat_actions(*( change_monitor_state + add_params_to_d + + update_g + [action] + + assume_g_axioms + add_consts_to_d + update_w + - [AssertAction(lg.Not(lg.And(*fair_cycle)))] + [assert_no_fair_cycle] )) new_action.lineno = action.lineno new_action.formal_params = action.formal_params @@ -175,8 +233,65 @@ def l2s(mod, lf): ] l2s_init += add_consts_to_d l2s_init += reset_w - # TODO: assume [~property] + l2s_init += assume_g_axioms + l2s_init += [AssumeAction(lg.Not(lf.formula))] + # TODO: check assume [~property] mod.initializers.append(('l2s_init', Sequence(*l2s_init))) print "=" * 80 + "\n"*3 print_module(mod) + print "=" * 80 + "\n"*3 + + # module pass helper funciton + def mod_pass(transform): + mod.labeled_conjs = [transform(x) for x in mod.labeled_conjs] + for a in mod.public_actions: + action = mod.actions[a] + new_action = transform(action) + new_action.lineno = action.lineno + new_action.formal_params = action.formal_params + new_action.formal_returns = action.formal_returns + mod.actions[a] = new_action + mod.initializers = [(x, transform(y)) for x, y in mod.initializers] + + # now replace all temporal operators by l2s_g + l2s_gs = set() + def _l2s_g(vs, t): + vs = tuple(vs) + res = l2s_g(vs, t) + l2s_gs.add((vs,t)) + return res + mod_pass(lambda ast: ilu.replace_temporals_by_named_binder_g_ast(ast, _l2s_g)) + + #print "=" * 80 + "\n"*3 + #print_module(mod) + #print "=" * 80 + "\n"*3 + + # now replace the named binders by fresh relations + + named_binders = defaultdict(list) # dict mapping names to lists of (vars, body) + for b in ilu.named_binders_asts(chain( + mod.labeled_conjs, + mod.actions.values(), + (y for x,y in mod.initializers), + )): + named_binders[b.name].append(b) + named_binders = defaultdict(list, ((k,list(sorted(set(v)))) for k,v in named_binders.iteritems())) + # make sure old_l2s_g is consistent with l2s_g + # assert len(named_binders['l2s_g']) == len(named_binders['old_l2s_g']) + named_binders['old_l2s_g'] = [ + lg.NamedBinder('old_l2s_g', b.variables, b.body) + for b in named_binders['l2s_g'] + ] + subs = dict( + (b, lg.Const('{}_{}'.format(k, i), b.sort)) + for k, v in named_binders.iteritems() + for i, b in enumerate(v) + ) + #for k, v in subs.items(): + # print k, ' : ', v, '\n' + mod_pass(lambda ast: ilu.replace_named_binders_ast(ast, subs)) + + print "=" * 80 + "\n"*3 + print_module(mod) + print "=" * 80 + "\n"*3 diff --git a/ivy/ivy_logic.py b/ivy/ivy_logic.py index 98c348119..b5e8c9e63 100644 --- a/ivy/ivy_logic.py +++ b/ivy/ivy_logic.py @@ -585,6 +585,9 @@ def is_binder(term): def is_named_binder(term): return isinstance(term, lg.NamedBinder) +def is_temporal(term): + return isinstance(term, (lg.Globally, lg.Eventually)) + def quantifier_vars(term): return term.variables diff --git a/ivy/ivy_logic_utils.py b/ivy/ivy_logic_utils.py index 94f25f66c..b8834259a 100644 --- a/ivy/ivy_logic_utils.py +++ b/ivy/ivy_logic_utils.py @@ -188,6 +188,48 @@ def rename_ast(ast,subs): return ast.clone(args) +def replace_temporals_by_named_binder_g_ast(ast, g=lambda vs, t: lg.NamedBinder('g', vs, t)): + """ + Replace temporal operators globally and eventually by a named + binder g. Note that temporal operators inside formulas bound by + named binders are left unaltered. + """ + if type(ast) == lg.Globally: + body = replace_temporals_by_named_binder_g_ast(ast.body, g) + # normalize variables + subs = dict() + vs = [] + nvs = [] + for v in variables_ast(ast.body): + if v not in subs: + nv = lg.Var('V{}'.format(len(subs)), v.sort) + subs[v.name] = nv + vs.append(v) + nvs.append(nv) + body = substitute_ast(body, subs) + return g(nvs,body)(*vs) + elif type(ast) == lg.Eventually: + return replace_temporals_by_named_binder_g_ast(lg.Not(lg.Globally(lg.Not(ast.body))), g) + else: + args = [replace_temporals_by_named_binder_g_ast(x, g) for x in ast.args] + return ast.clone(args) + + +def replace_named_binders_ast(ast,subs): + """ + Replace named binders that are present in subs. Here, subs is a + dict from logic.NamedBinder objects to (usually) logic.Const + objects. Exception is thrown in case of a sort conflict. + + Note: named binders do not get replaced in formulas bound by named binders! + """ + if is_named_binder(ast): + return subs.get(ast,ast) + args = [replace_named_binders_ast(x, subs) for x in ast.args] + if is_app(ast): + return subs.get(ast.rep,ast.rep)(*args) + return ast.clone(args) + def resort_sort(sort,subs): """ Substitute sorts for sorts in a sort. @@ -343,6 +385,18 @@ def named_binders_ast(ast): for x in named_binders_ast(arg): yield x +def temporals_ast(ast): + if is_temporal(ast): + yield ast + elif is_app(ast): + if is_named_binder(ast.rep): + for x in temporals_ast(ast.rep.body): + yield x + for arg in ast.args: + for x in temporals_ast(arg): + yield x + + # extend to clauses, etc... @@ -351,6 +405,8 @@ def named_binders_ast(ast): named_binders_asts = apply_gen_to_list(named_binders_ast) +temporals_asts = apply_gen_to_list(temporals_ast) + # get set of symbols occurring used_symbols_ast = gen_to_set(symbols_ast) diff --git a/ivy/logic.py b/ivy/logic.py index 967f680d9..aa20be6c7 100644 --- a/ivy/logic.py +++ b/ivy/logic.py @@ -389,8 +389,10 @@ def __call__(self, *terms): ) -true = Const('true', Boolean) -false = Const('false', Boolean) +# true = Const('true', Boolean) +# false = Const('false', Boolean) +true = And() +false = Or() if __name__ == '__main__': S = UninterpretedSort('S') diff --git a/test/test_liveness.ivy b/test/test_liveness.ivy index a24171e64..1e267af79 100644 --- a/test/test_liveness.ivy +++ b/test/test_liveness.ivy @@ -57,7 +57,10 @@ object ticket_protocol = { individual service:ticket individual next_ticket:ticket relation m(T:thread, K:ticket) # use relation and not a function to be in EPR - individual testfunction(K:ticket):thread # use relation and not a function to be in EPR + # individual testfunction(K:ticket):thread # use relation and not a function to be in EPR + + individual t0:thread + relation last_scheduled(T:thread) init pc1(T) init ~pc2(T) @@ -65,17 +68,17 @@ object ticket_protocol = { init service = zero init next_ticket = zero init m(T,K) <-> K = zero - - relation request_flag + init ~last_scheduled(T) # temporal specification - temporal property [mutualexclusion] globally forall T1,T2. pc3(T1) & pc3(T2) -> T1 = T2 - #temporal property [nonstravation] forall T:thread. globally pc2(T) -> eventually pc3(T) + temporal axiom [fairness] forall T:thread. globally eventually last_scheduled(T) + # temporal property [mutualexclusion] globally forall T1,T2. pc3(T1) & pc3(T2) -> T1 = T2 + # temporal property [nonstravation] forall T:thread. globally (pc2(T) -> eventually pc3(T)) # TODO parsing precedence bug + temporal property [nonstravation] globally (pc2(t0) -> eventually pc3(t0)) # TODO parsing precedence bug # proof of nonstravation by l2s { - temporal property ($l2s_w. request_flag) <-> ~request_flag - conjecture l2s_frozen -> request_flag - conjecture l2s_saved -> request_flag - temporal axiom [fairness] forall T:thread. globally eventually pc1(T) + # conjecture ($l2s_w. (pc2(t0) & (globally ~pc3(t0)))) <-> ~(pc2(t0) & (globally ~pc3(t0))) + # conjecture l2s_frozen -> (pc2(t0) & (globally ~pc3(t0))) + # conjecture l2s_saved -> (pc2(t0) & (globally ~pc3(t0))) #axiom [fairness] forall T:thread. globally eventually pc1(T) # temporal axiom [fairness] forall T:thread. globally eventually ( # (exists K1,K2. after step12(T,K1,K2)) | @@ -85,8 +88,6 @@ object ticket_protocol = { # ) # # naming for use in the inductive invariant # let t0 = nonstravation.T - individual t0:thread - relation last_scheduled(T:thread) # let request_flag = pc2(t0) & globally ~pc3(t0) # let last_scheduled(T) = ( # (exists K1,K2. after step12(T,K1,K2)) | @@ -126,13 +127,15 @@ object ticket_protocol = { next_ticket := k2; pc1(t) := false; pc2(t) := true; + last_scheduled(T) := T = t; } action step22(t:thread, k1:ticket) = { assume pc2(t); assume m(t,k1); - assume ~le(k1,service) + assume ~le(k1,service); # stay in pc2 + last_scheduled(T) := T = t; } action step23(t:thread, k1:ticket) = { @@ -141,6 +144,7 @@ object ticket_protocol = { assume le(k1,service); pc2(t) := false; pc3(t) := true; + last_scheduled(T) := T = t; } action step31(t:thread, k1:ticket, k2:ticket) = { @@ -149,7 +153,8 @@ object ticket_protocol = { assume k2 = succ(k1); service := k2; pc3(t) := false; - pc1(t) := true + pc1(t) := true; + last_scheduled(T) := T = t; } export step12 @@ -221,75 +226,100 @@ object ticket_protocol = { ################################################################################ - # all safety conjectures for saved state are autumatically added - # conjecture X.l2s_saved -> phi(X.l2s_s) for phi in conjectures over original vocabulary + # # all safety conjectures for saved state are autumatically added + # # conjecture X.l2s_saved -> phi(X.l2s_s) for phi in conjectures over original vocabulary + conjecture l2s_saved -> ( ($l2s_s T. pc1(T))(T) | ($l2s_s T. pc2(T))(T) | ($l2s_s T. pc3(T))(T) ) + conjecture l2s_saved -> ( ~($l2s_s T. pc1(T))(T) | ~($l2s_s T. pc2(T))(T) ) + conjecture l2s_saved -> ( ~($l2s_s T. pc1(T))(T) | ~($l2s_s T. pc3(T))(T) ) + conjecture l2s_saved -> ( ~($l2s_s T. pc2(T))(T) | ~($l2s_s T. pc3(T))(T) ) + conjecture l2s_saved -> ( ($l2s_s T,K. m(T,K))(T,K1) & ($l2s_s T,K. m(T,K))(T,K2) -> K1 = K2 ) + conjecture l2s_saved -> ( ($l2s_s T. pc3(T))(T1) & ($l2s_s T. pc3(T))(T2) -> T1 = T2 ) + conjecture l2s_saved -> ( ($l2s_s. next_ticket) = ($l2s_s. zero) -> ($l2s_s T,K. m(T,K))(T,($l2s_s. zero)) ) + conjecture l2s_saved -> ( ($l2s_s. next_ticket) ~= ($l2s_s. zero) & ($l2s_s T,K. m(T,K))(T,M) -> ~($l2s_s K1,K2. le(K1,K2))(($l2s_s. next_ticket),M) ) + conjecture l2s_saved -> ( (($l2s_s T. pc2(T))(T) | ($l2s_s T. pc3(T))(T)) -> ($l2s_s. next_ticket) ~= ($l2s_s. zero) ) + conjecture l2s_saved -> ( ($l2s_s T,K. m(T,K))(T1,M) & ($l2s_s T,K. m(T,K))(T2,M) & M ~= ($l2s_s. zero) -> T1 = T2 ) + conjecture l2s_saved -> ( ($l2s_s T. pc2(T))(T) & ($l2s_s T,K. m(T,K))(T,M) -> ($l2s_s K1,K2. le(K1,K2))(($l2s_s. service),M) ) + conjecture l2s_saved -> ( ($l2s_s T. pc3(T))(T) -> ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) ) + conjecture l2s_saved -> ( ($l2s_s K1,K2. le(K1,K2))(($l2s_s. service),($l2s_s. next_ticket)) ) + conjecture l2s_saved -> ( ~(~($l2s_s T. pc1(T))(T1) & ~($l2s_s T. pc1(T))(T2) & ($l2s_s T,K. m(T,K))(T1,($l2s_s. zero)) & ($l2s_s T,K. m(T,K))(T2,($l2s_s. zero)) & T1 ~= T2) ) + + conjecture l2s_saved -> (le(X,Y) <-> ($l2s_s K1,K2. le(K1,K2))(X,Y)) + + + # # for testing + # # conjecture ($l2s_s X. globally eventually pc1(X))(Y) # # basic - conjecture ($l2s_w. request_flag) <-> ~request_flag - conjecture l2s_frozen -> request_flag - conjecture l2s_saved -> request_flag - conjecture request_flag -> pc2(t0) - conjecture l2s_saved -> ($l2s_s X,Y. m(X,Y))(t0,K) <-> m(t0,K) - conjecture ($l2s_s X, Y. m(X,Y))(X,Y) - conjecture ($l2s_w X, Y. m(X,Y))(X,Y) - conjecture l2s_saved -> le( ($ l2s_s . service) , service) - - # # more properties of reachable protocol states - conjecture pc1(T) & m(T,M) & M ~= zero -> ~le(service, M) - conjecture forall K:ticket. ~le(next_ticket, K) & le(service, K) -> - exists T:thread. m(T,K) & ~pc1(T) - conjecture exists M. m(t0, M) - # # their saved counterpars are automatically added - - # conjecture that l2s_d is large enough - conjecture l2s_d(t0) - conjecture ~pc1(T) -> l2s_d(T) - conjecture le(K,next_ticket) -> l2s_d(K) - # conjecture that l2s_a is large enough - conjecture ~l2s_waiting -> l2s_a(t0) - conjecture ~l2s_waiting & m(T,K) & m(t0,K0) & ~le(K0,K) & ~pc1(T) -> l2s_a(T) - conjecture ~l2s_waiting & m(t0,K0) & le(K,K0) -> l2s_a(K) - - # thread that have not been scheduled have not changed - # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_pc1(T) <-> pc1(T)) - # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_pc2(T) <-> pc2(T)) - # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_pc3(T) <-> pc3(T)) - # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_m(T,K) <-> m(T,K)) - # this now will be written as: - # conjecture (l2s_saved & - # l2s_s.pc1(T) & - # l2s_wa[step12(T,K1,K2)] problem with K1,K2 not being in d_... - # for now, try with last_scheduled - conjecture l2s_saved & ($l2s_w T. last_scheduled(T))(T) -> ( - (($l2s_s T. pc1(T))(T) <-> pc1(T)) & - (($l2s_s T. pc2(T))(T) <-> pc2(T)) & - (($l2s_s T. pc3(T))(T) <-> pc3(T)) & - (($l2s_s T,K. m(T,K))(T,K) <-> m(T,K)) - ) - - # the thread that must advance - the thread that had the service as its local ticket at the save point - conjecture ( - l2s_saved & - ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & - ~($l2s_w T. last_scheduled(T))(T) & - ($l2s_s T. pc2(T))(T) & - m(T,K) & - m(t0,K0) - ) -> ( - (pc1(T) & K = ($l2s_s. service)) | - (pc2(T) & ~le(K,K0)) | - (pc3(T) & K = ($l2s_s. service)) - ) - conjecture ( - l2s_saved & - ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & - ~($l2s_w T. last_scheduled(T))(T) & - ($l2s_s T. pc3(T))(T) & - m(T,K) & - m(t0,K0) - ) -> ( - (pc1(T) & K = ($l2s_s. service) & ~le(service, ($l2s_s. service))) | - (pc2(T) & ~le(K,K0)) - ) + conjecture ~(globally (pc2(t0) -> eventually pc3(t0))) + # conjecture eventually (pc2(t0) & globally ~pc3(t0))) + + conjecture ~($l2s_w. ~(pc2(t0) -> (eventually pc3(t0)))) -> ~(pc2(t0) -> (eventually pc3(t0))) + + # conjecture ($l2s_w. (~(pc2(t0) -> (eventually pc3(t0))))) <-> (~(~(pc2(t0) -> (eventually pc3(t0))))) + # conjecture eventually (pc2(t0) & (globally ~pc3(t0))) + # conjecture ($l2s_w T. (pc2(T) & (globally ~pc3(T))))(t0) -> ~(pc2(t0) & (globally ~pc3(t0))) + # conjecture ~($l2s_w T. (pc2(T) & (globally ~pc3(T))))(t0) -> (pc2(t0) & (globally ~pc3(t0))) + conjecture l2s_frozen -> (~(pc2(t0) -> (eventually pc3(t0)))) + conjecture l2s_saved -> (pc2(t0) & (globally ~pc3(t0))) + conjecture l2s_saved -> ($l2s_s T,K. m(T,K))(t0,K) <-> m(t0,K) + conjecture l2s_saved -> le( ($ l2s_s . service) , service) + + # # # more properties of reachable protocol states + # conjecture pc1(T) & m(T,M) & M ~= zero -> ~le(service, M) + # conjecture forall K:ticket. ~le(next_ticket, K) & le(service, K) -> + # exists T:thread. m(T,K) & ~pc1(T) + # conjecture exists M. m(t0, M) + # # # their saved counterpars are automatically added + + # # conjecture that l2s_d is large enough + # conjecture l2s_d(t0) + # conjecture ~pc1(T) -> l2s_d(T) + # conjecture le(K,next_ticket) -> l2s_d(K) + # # conjecture that l2s_a is large enough + # conjecture ~l2s_waiting -> l2s_a(t0) + # conjecture ~l2s_waiting & m(T,K) & m(t0,K0) & ~le(K0,K) & ~pc1(T) -> l2s_a(T) + # conjecture ~l2s_waiting & m(t0,K0) & le(K,K0) -> l2s_a(K) + + # # thread that have not been scheduled have not changed + # # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_pc1(T) <-> pc1(T)) + # # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_pc2(T) <-> pc2(T)) + # # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_pc3(T) <-> pc3(T)) + # # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_m(T,K) <-> m(T,K)) + # # this now will be written as: + # # conjecture (l2s_saved & + # # l2s_s.pc1(T) & + # # l2s_wa[step12(T,K1,K2)] problem with K1,K2 not being in d_... + # # for now, try with last_scheduled + # conjecture l2s_saved & ($l2s_w T. last_scheduled(T))(T) -> ( + # (($l2s_s T. pc1(T))(T) <-> pc1(T)) & + # (($l2s_s T. pc2(T))(T) <-> pc2(T)) & + # (($l2s_s T. pc3(T))(T) <-> pc3(T)) & + # (($l2s_s T,K. m(T,K))(T,K) <-> m(T,K)) + # ) + + # # the thread that must advance - the thread that had the service as its local ticket at the save point + # conjecture ( + # l2s_saved & + # ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & + # ~($l2s_w X. last_scheduled(X))(T) & + # ($l2s_s T. pc2(T))(T) & + # m(T,K) & + # m(t0,K0) + # ) -> ( + # (pc1(T) & K = ($l2s_s. service)) | + # (pc2(T) & ~le(K,K0)) | + # (pc3(T) & K = ($l2s_s. service)) + # ) + # conjecture ( + # l2s_saved & + # ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & + # ~($l2s_w T. last_scheduled(T))(T) & + # ($l2s_s T. pc3(T))(T) & + # m(T,K) & + # m(t0,K0) + # ) -> ( + # (pc1(T) & K = ($l2s_s. service) & ~le(service, ($l2s_s. service))) | + # (pc2(T) & ~le(K,K0)) + # ) } From 9fd3115f94237a286eb8185c06c6d1002bae25d6 Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Wed, 13 Sep 2017 15:06:37 +0300 Subject: [PATCH 07/17] fixes to the normalization of variables in ivy_l2s.ivy. the ticket example now verifies. --- ivy/ivy_l2s.py | 341 ++++++++++++++++++++++++----------------- ivy/ivy_logic_utils.py | 70 +++++++-- test/test_liveness.ivy | 170 +++++++++++--------- 3 files changed, 353 insertions(+), 228 deletions(-) diff --git a/ivy/ivy_l2s.py b/ivy/ivy_l2s.py index 506bbdbda..6c134e663 100644 --- a/ivy/ivy_l2s.py +++ b/ivy/ivy_l2s.py @@ -1,14 +1,47 @@ - # self.definitions = [] # TODO: these are actually "derived" relations - # self.labeled_axioms = [] - # self.labeled_props = [] - # self.labeled_inits = [] - # self.labeled_conjs = [] # conjectures - # self.actions = {} - # self.public_actions = set() +# +# Copyright (c) Microsoft Corporation. All Rights Reserved. +# +""" +This module contains a liveness to safety reduction that allows +proving temporal properties. - # self.initializers = [] # list of name,action pairs - # self.sig +TODO's and open issues: + +* automatically add conjectures of original system to the saved state + +* automatically add basic conjectures about the monitor (e.g. states + are mutually exclusive) + +* handle multiple temporal properties + +* temporal axioms? + +* support nesting structure? + +* review the correctness + +* figure out the public_actions issue + +* decide abotu normalizing the Boolean structure of temporal formulas, + properties, waited formulas, and named binders (e.g. normalize ~~phi + to phi?) + +* a syntax for accessing Skolem constants and functions from the + negation of temporal properties. + + +Useful definitions from ivy_module: +self.definitions = [] # TODO: these are actually "derived" relations +self.labeled_axioms = [] +self.labeled_props = [] +self.labeled_inits = [] +self.labeled_conjs = [] # conjectures +self.actions = {} +self.public_actions = set() +self.initializers = [] # list of name,action pairs +self.sig +""" from collections import defaultdict from itertools import chain @@ -20,17 +53,28 @@ import logic as lg import ivy_logic_utils as ilu + def forall(vs, body): return lg.ForAll(vs, body) if len(vs) > 0 else body def l2s(mod, lf): - #print ilu.used_symbols_asts(mod.labeled_conjs) - #print '='*40 - #print list(ilu.named_binders_asts(mod.labeled_conjs)) # modify mod in place + # module pass helper funciton + def mod_pass(transform): + mod.labeled_conjs = [transform(x) for x in mod.labeled_conjs] + # TODO: what about axioms and properties? + for a in mod.public_actions: + action = mod.actions[a] + new_action = transform(action) + new_action.lineno = action.lineno + new_action.formal_params = action.formal_params + new_action.formal_returns = action.formal_returns + mod.actions[a] = new_action + mod.initializers = [(x, transform(y)) for x, y in mod.initializers] + l2s_waiting = lg.Const('l2s_waiting', lg.Boolean) l2s_frozen = lg.Const('l2s_frozen', lg.Boolean) l2s_saved = lg.Const('l2s_saved', lg.Boolean) @@ -41,36 +85,78 @@ def l2s(mod, lf): l2s_g = lambda vs, t: lg.NamedBinder('l2s_g', vs, t) old_l2s_g = lambda vs, t: lg.NamedBinder('old_l2s_g', vs, t) - uninterpreted_sorts = [s for s in mod.sig.sorts.values() if type(s) is lg.UninterpretedSort] + #print ilu.used_symbols_asts(mod.labeled_conjs) + #print '='*40 + #print list(ilu.named_binders_asts(mod.labeled_conjs)) + + # some normalization + + # We first convert all temporal operators to named binders, so + # it's possible to normalize them. Otherwise we won't have the + # connection betweel (globally p(X)) and (globally p(Y)). Note + # that we replace them even inside named binders. + l2s_gs = set() + def _l2s_g(vs, t): + vs = tuple(vs) + res = l2s_g(vs, t) + l2s_gs.add((vs,t)) + return res + replace_temporals_by_l2s_g = lambda ast: ilu.replace_temporals_by_named_binder_g_ast(ast, _l2s_g) + mod_pass(replace_temporals_by_l2s_g) + not_lf = replace_temporals_by_l2s_g(lg.Not(lf.formula)) + print "=" * 80 +"\nafter replace_temporals_by_named_binder_g_ast"+ "\n"*3 + print "=" * 80 + "\nl2s_gs:" + for vs, t in sorted(l2s_gs): + print vs, t + print "=" * 80 + "\n"*3 + print_module(mod) + print "=" * 80 + "\n"*3 - #for k, v in mod.sig.sorts.items() + mod.sig.symbols.items(): - # print repr(k), ':', repr(v) - # print - #print + # now we normalize all named binders + mod_pass(ilu.normalize_named_binders) + print "=" * 80 +"\nafter normalize_named_binders"+ "\n"*3 + print_module(mod) + print "=" * 80 + "\n"*3 + + # TODO: what about normalizing lf? + + # construct the monitor related building blocks + uninterpreted_sorts = [s for s in mod.sig.sorts.values() if type(s) is lg.UninterpretedSort] reset_a = [ AssignAction(l2s_a(s)(v), l2s_d(s)(v)) for s in uninterpreted_sorts for v in [lg.Var('X',s)] ] + add_consts_to_d = [ + AssignAction(l2s_d(s)(c), lg.true) + for s in uninterpreted_sorts + for c in mod.sig.symbols.values() if c.sort == s + ] + # TODO: maybe add all ground terms, not just consts (if stratified) + # TODO: add conjectures that constants are in d and a - named_binders = defaultdict(list) # dict mapping names to lists of (vars, body) + # figure out which l2s_w and l2s_s are used in conjectures + named_binders_conjs = defaultdict(list) # dict mapping names to lists of (vars, body) for b in ilu.named_binders_asts(mod.labeled_conjs): - named_binders[b.name].append((b.variables, b.body)) - named_binders = defaultdict(list,((k,list(set(v))) for k,v in named_binders.iteritems())) - # TODO: the above appears twice in this file, why? fix it! - # TODO: normalize named binders, so variable names won't be significant - - # DONE?: change to_wait to be from the tableau or conjectures, this is a mock - to_wait = [] # list of (variables, term) - # to_wait += [((), c) for c in mod.sig.symbols.values() if c.sort == lg.Boolean] - # to_wait += [ - # (vs, f(*vs)) - # for f in mod.sig.symbols.values() - # if isinstance(f.sort, lg.FunctionSort) and f.sort.range == lg.Boolean - # for vs in [tuple(lg.Var('X{}'.format(i), s) for i,s in enumerate(f.sort.domain))] - # ] - to_wait = named_binders['l2s_w'] + named_binders_conjs[b.name].append((b.variables, b.body)) + named_binders_conjs = defaultdict(list,((k,list(set(v))) for k,v in named_binders_conjs.iteritems())) + to_wait = [] # list of (variables, term) corresponding to l2s_w in conjectures + to_wait += named_binders_conjs['l2s_w'] + to_save = [] # list of (variables, term) corresponding to l2s_s in conjectures + to_save += named_binders_conjs['l2s_s'] + + print "=" * 40 + "\nto_wait:\n" + for vs, t in to_wait: + print vs, t + print list(ilu.variables_ast(t)) == list(vs) + print + print "=" * 40 + + save_state = [ + AssignAction(l2s_s(vs,t)(*vs), t) + for vs, t in to_save + ] done_waiting = [ forall(vs, lg.Not(l2s_w(vs,t)(*vs))) for vs, t in to_wait @@ -85,66 +171,21 @@ def l2s(mod, lf): update_w = [ AssignAction( l2s_w(vs,t)(*vs), - lg.And(l2s_w(vs,t)(*vs), lg.Not(t), lg.Not(lg.Globally(lg.Not(t)))) + lg.And(l2s_w(vs,t)(*vs), lg.Not(t), replace_temporals_by_l2s_g(lg.Not(lg.Globally(lg.Not(t))))) + # TODO check this and make sure its correct + # note this adds to l2s_gs ) for vs, t in to_wait ] - - # tableau construction (sort of) - temporals = [] # TODO get from temporal axioms and temporal properties - temporals += list(ilu.temporals_asts( - mod.labeled_conjs + - mod.labeled_axioms + - mod.labeled_props + - [lf] - )) - temporals += [lg.Globally(lg.Not(t)) for vs, t in to_wait] - print '='*40 - for t in temporals: - print t, '\n' - print '='*40 - to_g = [ # list of (variables, formula) - (tuple(sorted(ilu.variables_ast(tt))), tt) - for t in temporals - for tt in [t.body if type(t) is lg.Globally else - lg.Not(t.body) if type(t) is lg.Eventually else 1/0] - ] - to_g = list(set(to_g)) - print '='*40 - for vs, t in to_g: - print vs, t, '\n' - print '='*40 - - assume_g_axioms = [ - AssumeAction(forall(vs, lg.Implies(l2s_g(vs, t)(*vs), t))) - for vs, t in to_g - ] - update_g = [a for vs, t in to_g for a in [ - HavocAction(l2s_g(vs, t)(*vs)), - AssumeAction(forall(vs, lg.Implies(old_l2s_g(vs, t)(*vs), l2s_g(vs, t)(*vs)))), - AssumeAction(forall(vs, lg.Implies(lg.And(lg.Not(old_l2s_g(vs, t)(*vs)), t), lg.Not(l2s_g(vs, t)(*vs))))), -# lg.Or( -# l2s_g(vs, t)(*vs), -# lg.And(lg.Not(t), l2s_ga(vs, t)(*vs)) -# )), - ]] - - # DONE?: change to_save to be taken from the conjectures - to_save = [] # list of (variables, term) corresponding to l2s_s - # for f in mod.sig.symbols.values(): - # if isinstance(f.sort, lg.FunctionSort): - # vs = tuple(lg.Var('X{}'.format(i), s) for i,s in enumerate(f.sort.domain)) - # to_save.append((vs, f(*vs))) - # else: - # to_save.append(((), f)) - to_save = named_binders['l2s_s'] - save_state = [ - AssignAction(l2s_s(vs,t)(*vs), t) - for vs, t in to_save - ] + print "=" * 40 + "\nupdate_w:\n" + for x in update_w: + print x + print + print "=" * 40 fair_cycle = [l2s_saved] fair_cycle += done_waiting + # projection of relations fair_cycle += [ lg.ForAll(vs, lg.Implies( lg.And(*(l2s_a(v.sort)(v) for v in vs)), @@ -157,6 +198,7 @@ def l2s(mod, lf): isinstance(t.sort, lg.FunctionSort) and t.sort.range == lg.Boolean ) ] + # projection of functions and constants fair_cycle += [ forall(vs, lg.Implies( lg.And(*( @@ -171,10 +213,10 @@ def l2s(mod, lf): isinstance(t.sort, lg.FunctionSort) and isinstance(t.sort.range, lg.UninterpretedSort) ) ] - assert_no_fair_cycle = AssertAction(lg.true) #AssertAction(lg.Not(lg.And(*fair_cycle))) + assert_no_fair_cycle = AssertAction(lg.Not(lg.And(*fair_cycle))) assert_no_fair_cycle.lineno = lf.lineno - edge = lambda s1, s2: [ + monitor_edge = lambda s1, s2: [ AssumeAction(s1), AssignAction(s1, lg.false), AssignAction(s2, lg.true), @@ -182,28 +224,75 @@ def l2s(mod, lf): change_monitor_state = [ChoiceAction( # waiting -> frozen Sequence(*( - edge(l2s_waiting, l2s_frozen) + + monitor_edge(l2s_waiting, l2s_frozen) + [AssumeAction(x) for x in done_waiting] + reset_a )), - # frozen -> saved - Sequence(*( - edge(l2s_frozen, l2s_saved) + - save_state + - reset_w - )), + # # frozen -> saved + # Sequence(*( + # monitor_edge(l2s_frozen, l2s_saved) + + # save_state + + # reset_w + # )), # stay in same state (self edge) Sequence(), )] - add_consts_to_d = [ - AssignAction(l2s_d(s)(c), lg.true) - for s in uninterpreted_sorts - for c in mod.sig.symbols.values() if c.sort == s + # tableau construction (sort of) + + # Note that we first transformed globally and eventually to named + # binders, in order to normalize. Without this, we would get + # multiple redundant axioms like: + # forall X. (globally phi(X)) -> phi(X) + # forall Y. (globally phi(Y)) -> phi(Y) + # and the same redundancy will happen for transition updates. + + # temporals = [] + # temporals += list(ilu.temporals_asts( + # # TODO: these should be handled by mod_pass instead (and come via l2s_gs): + # # mod.labeled_axioms + + # # mod.labeled_props + + # [lf] + # )) + # temporals += [lg.Globally(lg.Not(t)) for vs, t in to_wait] + # temporals += [lg.Globally(t) for vs, t in l2s_gs] + # # TODO get from temporal axioms and temporal properties as well + # print '='*40 + "\ntemporals:" + # for t in temporals: + # print t, '\n' + # print '='*40 + # to_g = [ # list of (variables, formula) + # (tuple(sorted(ilu.variables_ast(tt))), tt) # TODO what about variable normalization?? + # for t in temporals + # for tt in [t.body if type(t) is lg.Globally else + # lg.Not(t.body) if type(t) is lg.Eventually else 1/0] + # ] + # TODO: get rid of the above, after properly combining it + to_g = [] # list of (variables, formula) + to_g += list(l2s_gs) + to_g = list(set(to_g)) + print '='*40 + "\nto_g:\n" + for vs, t in sorted(to_g): + print vs, t, '\n' + print '='*40 + + assume_g_axioms = [ + AssumeAction(forall(vs, lg.Implies(l2s_g(vs, t)(*vs), t))) + for vs, t in to_g ] - # TODO add all ground terms, not just consts (if stratified) + update_g = [a for vs, t in to_g for a in [ + HavocAction(l2s_g(vs, t)(*vs)), + AssumeAction(forall(vs, lg.Implies(old_l2s_g(vs, t)(*vs), l2s_g(vs, t)(*vs)))), + AssumeAction(forall(vs, lg.Implies(lg.And(lg.Not(old_l2s_g(vs, t)(*vs)), t), lg.Not(l2s_g(vs, t)(*vs))))), + ]] - # TODO: add conjectures that constants are in d and a + # now patch the module actions with monitor and tableau + + print "public_actions:", mod.public_actions + # TODO: this includes the succ action (for the ticket example of + # test/test_liveness.ivy). seems to be a bug, and this causes + # wrong behavior for the monitor, since a call to succ from within + # another action lets it take a step for a in mod.public_actions: action = mod.actions[a] @@ -234,40 +323,14 @@ def l2s(mod, lf): l2s_init += add_consts_to_d l2s_init += reset_w l2s_init += assume_g_axioms - l2s_init += [AssumeAction(lg.Not(lf.formula))] - # TODO: check assume [~property] + l2s_init += [AssumeAction(not_lf)] mod.initializers.append(('l2s_init', Sequence(*l2s_init))) - print "=" * 80 + "\n"*3 + print "=" * 80 + "\nafter patching actions" + "\n"*3 print_module(mod) print "=" * 80 + "\n"*3 - # module pass helper funciton - def mod_pass(transform): - mod.labeled_conjs = [transform(x) for x in mod.labeled_conjs] - for a in mod.public_actions: - action = mod.actions[a] - new_action = transform(action) - new_action.lineno = action.lineno - new_action.formal_params = action.formal_params - new_action.formal_returns = action.formal_returns - mod.actions[a] = new_action - mod.initializers = [(x, transform(y)) for x, y in mod.initializers] - - # now replace all temporal operators by l2s_g - l2s_gs = set() - def _l2s_g(vs, t): - vs = tuple(vs) - res = l2s_g(vs, t) - l2s_gs.add((vs,t)) - return res - mod_pass(lambda ast: ilu.replace_temporals_by_named_binder_g_ast(ast, _l2s_g)) - - #print "=" * 80 + "\n"*3 - #print_module(mod) - #print "=" * 80 + "\n"*3 - - # now replace the named binders by fresh relations + # now replace all named binders by fresh relations named_binders = defaultdict(list) # dict mapping names to lists of (vars, body) for b in ilu.named_binders_asts(chain( @@ -278,20 +341,22 @@ def _l2s_g(vs, t): named_binders[b.name].append(b) named_binders = defaultdict(list, ((k,list(sorted(set(v)))) for k,v in named_binders.iteritems())) # make sure old_l2s_g is consistent with l2s_g - # assert len(named_binders['l2s_g']) == len(named_binders['old_l2s_g']) - named_binders['old_l2s_g'] = [ - lg.NamedBinder('old_l2s_g', b.variables, b.body) - for b in named_binders['l2s_g'] + assert len(named_binders['l2s_g']) == len(named_binders['old_l2s_g']) + assert named_binders['old_l2s_g'] == [ + lg.NamedBinder('old_l2s_g', b.variables, b.body) + for b in named_binders['l2s_g'] ] subs = dict( (b, lg.Const('{}_{}'.format(k, i), b.sort)) for k, v in named_binders.iteritems() for i, b in enumerate(v) ) - #for k, v in subs.items(): - # print k, ' : ', v, '\n' + print "=" * 80 + "\nsubs:" + "\n"*3 + for k, v in subs.items(): + print k, ' : ', v, '\n' + print "=" * 80 + "\n"*3 mod_pass(lambda ast: ilu.replace_named_binders_ast(ast, subs)) - print "=" * 80 + "\n"*3 + print "=" * 80 + "\nafter replace_named_binders" + "\n"*3 print_module(mod) print "=" * 80 + "\n"*3 diff --git a/ivy/ivy_logic_utils.py b/ivy/ivy_logic_utils.py index b8834259a..b1caffa23 100644 --- a/ivy/ivy_logic_utils.py +++ b/ivy/ivy_logic_utils.py @@ -187,32 +187,74 @@ def rename_ast(ast,subs): return subs.get(ast.rep,ast.rep)(*args) return ast.clone(args) +def normalize_free_variables(ast): + """ + Transforms (p(X,Y) & r(X)) or (p(A,B) & r(A)) to p(V0,V1) & r(V0). + The order of the variables is determined by the order they are + generated by variables_ast(ast). + Returns (old_vars, new_vars, normalized_ast) + Where old_vars and new_vars are tuples of variables. + normalized_ast(p(X,Y) & r(X)) returns (X,Y), (V0, V1), (p(V0,V1) & r(V0)) + normalized_ast(p(A,B) & r(A)) returns (A,B), (V0, V1), (p(V0,V1) & r(V0)) + """ + subs = dict() + vs = [] + nvs = [] + for v in variables_ast(ast): + if v not in subs: + nv = lg.Var('V{}'.format(len(subs)), v.sort) + subs[v.name] = nv + vs.append(v) + nvs.append(nv) + nast = substitute_ast(ast, subs) + return vs, nvs, nast + +def normalize_named_binders(ast,names=None): + """ + Normalize the variables bound by named binders, whose names are in + names, or all named binders if names is None. + + Note: also recurs into formulas bound by named binders + """ + if is_constant(ast): + return ast + if (is_named_binder(ast) and + (names is None or ast.name in names)): + vs = ast.variables + nvs = [lg.Var('V{}'.format(i), v.sort) + for i,v in enumerate(vs)] + free = set(variables_ast(ast)) + assert all(nv not in free for nv in nvs) + subs = dict((v.name,nv) for v,nv in zip(vs,nvs)) + b = normalize_named_binders(ast.body, names) + b = substitute_ast(b, subs) + return type(ast)(ast.name, nvs, b) + args = [normalize_named_binders(x, names) for x in ast.args] + if is_app(ast): + return normalize_named_binders(ast.rep,names)(*args) + else: + return ast.clone(args) def replace_temporals_by_named_binder_g_ast(ast, g=lambda vs, t: lg.NamedBinder('g', vs, t)): """ Replace temporal operators globally and eventually by a named binder g. Note that temporal operators inside formulas bound by - named binders are left unaltered. + named binders are also altered. """ if type(ast) == lg.Globally: body = replace_temporals_by_named_binder_g_ast(ast.body, g) - # normalize variables - subs = dict() - vs = [] - nvs = [] - for v in variables_ast(ast.body): - if v not in subs: - nv = lg.Var('V{}'.format(len(subs)), v.sort) - subs[v.name] = nv - vs.append(v) - nvs.append(nv) - body = substitute_ast(body, subs) + vs, nvs, body = normalize_free_variables(body) return g(nvs,body)(*vs) elif type(ast) == lg.Eventually: - return replace_temporals_by_named_binder_g_ast(lg.Not(lg.Globally(lg.Not(ast.body))), g) + notbody = lg.Not(ast.body) #if type(ast.body) != lg.Not else ast.body.body + return replace_temporals_by_named_binder_g_ast(lg.Not(lg.Globally(notbody)), g) else: args = [replace_temporals_by_named_binder_g_ast(x, g) for x in ast.args] - return ast.clone(args) + if type(ast) == lg.Apply: + func = replace_temporals_by_named_binder_g_ast(ast.func, g) + return type(ast)(func, *args) + else: + return ast.clone(args) def replace_named_binders_ast(ast,subs): diff --git a/test/test_liveness.ivy b/test/test_liveness.ivy index 1e267af79..0da59904f 100644 --- a/test/test_liveness.ivy +++ b/test/test_liveness.ivy @@ -1,12 +1,7 @@ #lang ivy1.6 ################################################################################ -# A liveness proof of the ticket protocol according to the "modern" -# version of our methodology: -# * all existentially quantified variables come from action parameters -# * d is updated automatically by inserting all action parameters -# * change from paper: there are constant symbols in the vocabulary - -# and they are handled both when updating d and when comparing states +# A liveness proof of the ticket protocol ################################################################################ @@ -71,10 +66,13 @@ object ticket_protocol = { init ~last_scheduled(T) # temporal specification - temporal axiom [fairness] forall T:thread. globally eventually last_scheduled(T) + # temporal axiom [fairness] forall T:thread. globally eventually last_scheduled(T) # temporal property [mutualexclusion] globally forall T1,T2. pc3(T1) & pc3(T2) -> T1 = T2 # temporal property [nonstravation] forall T:thread. globally (pc2(T) -> eventually pc3(T)) # TODO parsing precedence bug - temporal property [nonstravation] globally (pc2(t0) -> eventually pc3(t0)) # TODO parsing precedence bug + #temporal property [nonstravation] globally (pc2(t0) -> eventually pc3(t0)) # TODO parsing precedence bug + # temporal property [nonstravation] globally ~(pc2(t0) & globally ~pc3(t0)) # TODO parsing precedence bug + temporal property [nonstravation] (forall T:thread. globally (~globally (~last_scheduled(T)))) -> + (globally ~(pc2(t0) & globally ~pc3(t0))) # proof of nonstravation by l2s { # conjecture ($l2s_w. (pc2(t0) & (globally ~pc3(t0)))) <-> ~(pc2(t0) & (globally ~pc3(t0))) # conjecture l2s_frozen -> (pc2(t0) & (globally ~pc3(t0))) @@ -122,7 +120,8 @@ object ticket_protocol = { action step12(t:thread,k1:ticket, k2:ticket) = { assume pc1(t); assume k1 = next_ticket; - assume k2 = succ(k1); + # assume k2 = succ(k1); # bug: succ is in mod.public_actions + assume ~le(k2,k1) & forall Z:ticket. ~le(Z,k1) -> le(k2,Z); m(t,K) := K = k1; next_ticket := k2; pc1(t) := false; @@ -150,7 +149,8 @@ object ticket_protocol = { action step31(t:thread, k1:ticket, k2:ticket) = { assume pc3(t); assume k1 = service; - assume k2 = succ(k1); + # assume k2 = succ(k1); # bug: succ is in mod.public_actions + assume ~le(k2,k1) & forall Z:ticket. ~le(Z,k1) -> le(k2,Z); service := k2; pc3(t) := false; pc1(t) := true; @@ -168,6 +168,10 @@ object ticket_protocol = { # ################################################################################ + # for testing + # conjecture true | ($l2s_s X. globally eventually ~pc1(X))(Y) + # conjecture true | ($l2s_s X. globally ~globally pc1(X))(Y) + # basic conjecture pc1(T) | pc2(T) | pc3(T) conjecture ~pc1(T) | ~pc2(T) @@ -244,82 +248,96 @@ object ticket_protocol = { conjecture l2s_saved -> ( ~(~($l2s_s T. pc1(T))(T1) & ~($l2s_s T. pc1(T))(T2) & ($l2s_s T,K. m(T,K))(T1,($l2s_s. zero)) & ($l2s_s T,K. m(T,K))(T2,($l2s_s. zero)) & T1 ~= T2) ) conjecture l2s_saved -> (le(X,Y) <-> ($l2s_s K1,K2. le(K1,K2))(X,Y)) + conjecture l2s_saved -> (zero = ($l2s_s. zero)) + conjecture l2s_saved -> (t0 = ($l2s_s. t0)) + # # basic + # conjecture (forall T:thread. globally ~(globally ~last_scheduled(T))) + conjecture globally ~(globally ~last_scheduled(V0)) + conjecture globally ~(globally ~last_scheduled(T)) # just to try a different variable name + conjecture ~(globally ~last_scheduled(V0)) + conjecture ~(globally ~(pc2(t0) & globally ~pc3(t0))) + conjecture ~($l2s_w. (pc2(t0) & globally ~pc3(t0))) <-> (pc2(t0) & globally ~pc3(t0)) - # # for testing - # # conjecture ($l2s_s X. globally eventually pc1(X))(Y) - # # basic - conjecture ~(globally (pc2(t0) -> eventually pc3(t0))) - # conjecture eventually (pc2(t0) & globally ~pc3(t0))) + # TODO: should be added automatically + conjecture l2s_waiting | l2s_frozen | l2s_saved + conjecture ~l2s_waiting | ~l2s_frozen + conjecture ~l2s_waiting | ~l2s_saved + conjecture ~l2s_frozen | ~l2s_saved + + + # # basic (but not working) + # conjecture ~(globally (pc2(t0) -> eventually pc3(t0))) + # conjecture eventually (pc2(t0) & globally ~pc3(t0)) - conjecture ~($l2s_w. ~(pc2(t0) -> (eventually pc3(t0)))) -> ~(pc2(t0) -> (eventually pc3(t0))) + # conjecture ~($l2s_w. ~(pc2(t0) -> (eventually pc3(t0)))) -> ~(pc2(t0) -> (eventually pc3(t0))) # conjecture ($l2s_w. (~(pc2(t0) -> (eventually pc3(t0))))) <-> (~(~(pc2(t0) -> (eventually pc3(t0))))) # conjecture eventually (pc2(t0) & (globally ~pc3(t0))) # conjecture ($l2s_w T. (pc2(T) & (globally ~pc3(T))))(t0) -> ~(pc2(t0) & (globally ~pc3(t0))) # conjecture ~($l2s_w T. (pc2(T) & (globally ~pc3(T))))(t0) -> (pc2(t0) & (globally ~pc3(t0))) - conjecture l2s_frozen -> (~(pc2(t0) -> (eventually pc3(t0)))) - conjecture l2s_saved -> (pc2(t0) & (globally ~pc3(t0))) + conjecture l2s_frozen -> (pc2(t0) & globally ~pc3(t0)) + conjecture l2s_saved -> (pc2(t0) & globally ~pc3(t0)) conjecture l2s_saved -> ($l2s_s T,K. m(T,K))(t0,K) <-> m(t0,K) conjecture l2s_saved -> le( ($ l2s_s . service) , service) - - # # # more properties of reachable protocol states - # conjecture pc1(T) & m(T,M) & M ~= zero -> ~le(service, M) - # conjecture forall K:ticket. ~le(next_ticket, K) & le(service, K) -> - # exists T:thread. m(T,K) & ~pc1(T) - # conjecture exists M. m(t0, M) - # # # their saved counterpars are automatically added - - # # conjecture that l2s_d is large enough - # conjecture l2s_d(t0) - # conjecture ~pc1(T) -> l2s_d(T) - # conjecture le(K,next_ticket) -> l2s_d(K) - # # conjecture that l2s_a is large enough - # conjecture ~l2s_waiting -> l2s_a(t0) - # conjecture ~l2s_waiting & m(T,K) & m(t0,K0) & ~le(K0,K) & ~pc1(T) -> l2s_a(T) - # conjecture ~l2s_waiting & m(t0,K0) & le(K,K0) -> l2s_a(K) - - # # thread that have not been scheduled have not changed - # # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_pc1(T) <-> pc1(T)) - # # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_pc2(T) <-> pc2(T)) - # # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_pc3(T) <-> pc3(T)) - # # conjecture l2s_saved & l2s_w_last_scheduled(T) -> (l2s_s_m(T,K) <-> m(T,K)) - # # this now will be written as: - # # conjecture (l2s_saved & - # # l2s_s.pc1(T) & - # # l2s_wa[step12(T,K1,K2)] problem with K1,K2 not being in d_... - # # for now, try with last_scheduled - # conjecture l2s_saved & ($l2s_w T. last_scheduled(T))(T) -> ( - # (($l2s_s T. pc1(T))(T) <-> pc1(T)) & - # (($l2s_s T. pc2(T))(T) <-> pc2(T)) & - # (($l2s_s T. pc3(T))(T) <-> pc3(T)) & - # (($l2s_s T,K. m(T,K))(T,K) <-> m(T,K)) - # ) - - # # the thread that must advance - the thread that had the service as its local ticket at the save point - # conjecture ( - # l2s_saved & - # ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & - # ~($l2s_w X. last_scheduled(X))(T) & - # ($l2s_s T. pc2(T))(T) & - # m(T,K) & - # m(t0,K0) - # ) -> ( - # (pc1(T) & K = ($l2s_s. service)) | - # (pc2(T) & ~le(K,K0)) | - # (pc3(T) & K = ($l2s_s. service)) - # ) - # conjecture ( - # l2s_saved & - # ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & - # ~($l2s_w T. last_scheduled(T))(T) & - # ($l2s_s T. pc3(T))(T) & - # m(T,K) & - # m(t0,K0) - # ) -> ( - # (pc1(T) & K = ($l2s_s. service) & ~le(service, ($l2s_s. service))) | - # (pc2(T) & ~le(K,K0)) - # ) + conjecture l2s_saved -> le( ($ l2s_s . next_ticket) , next_ticket) + + # more properties of reachable protocol states + conjecture pc1(T) & m(T,M) & M ~= zero -> ~le(service, M) + conjecture forall K:ticket. ~le(next_ticket, K) & le(service, K) -> + exists T:thread. m(T,K) & ~pc1(T) + conjecture exists M. m(t0, M) + # and their saved counterpars (to be automatically added...) + conjecture l2s_saved -> ( + ($l2s_s X. pc1(X))(T) & ($l2s_s X,Y. m(X,Y))(T,M) & M ~= ($l2s_s. zero) -> ~($l2s_s X,Y. le(X,Y))(($l2s_s. service), M) + ) + conjecture l2s_saved -> ( + forall KK:ticket. ~($l2s_s X,Y. le(X,Y))(($l2s_s. next_ticket), KK) & ($l2s_s X,Y. le(X,Y))(($l2s_s. service), KK) -> + exists TT:thread. ($l2s_s T,K. m(T,K))(TT,KK) & ~($l2s_s T. pc1(T))(TT) + ) + conjecture l2s_saved -> ( + exists M. ($l2s_s T,K. m(T,K))(($l2s_s. t0), M) + ) + + # conjecture that l2s_d is large enough + conjecture l2s_d(t0) + conjecture ~pc1(T) -> l2s_d(T) + conjecture le(K,next_ticket) -> l2s_d(K) + # conjecture that l2s_a is large enough + conjecture ~l2s_waiting -> l2s_a(t0) + conjecture ~l2s_waiting & m(T,K) & m(t0,K0) & ~le(K0,K) & ~pc1(T) -> l2s_a(T) + conjecture ~l2s_waiting & m(t0,K0) & le(K,K0) -> l2s_a(K) + + # thread that have not been scheduled have not changed + conjecture l2s_saved & ($l2s_w T. last_scheduled(T))(T) -> (($l2s_s T. pc1(T))(T) <-> pc1(T)) + conjecture l2s_saved & ($l2s_w T. last_scheduled(T))(T) -> (($l2s_s T. pc2(T))(T) <-> pc2(T)) + conjecture l2s_saved & ($l2s_w T. last_scheduled(T))(T) -> (($l2s_s T. pc3(T))(T) <-> pc3(T)) + conjecture l2s_saved & ($l2s_w T. last_scheduled(T))(T) -> (($l2s_s T,K. m(T,K))(T,K) <-> m(T,K)) + + # the thread that must advance - the thread that had the service as its local ticket at the save point + conjecture ( + l2s_saved & + ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & + ~($l2s_w X. last_scheduled(X))(T) & + ($l2s_s T. pc2(T))(T) & + m(T,K) & + m(t0,K0) + ) -> ( + (pc1(T) & K = ($l2s_s. service)) | + (pc2(T) & ~le(K,K0)) | + (pc3(T) & K = ($l2s_s. service)) + ) + conjecture ( + l2s_saved & + ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & + ~($l2s_w T. last_scheduled(T))(T) & + ($l2s_s T. pc3(T))(T) & + m(T,K) & + m(t0,K0) + ) -> ( + (pc1(T) & K = ($l2s_s. service) & ~le(service, ($l2s_s. service))) | + (pc2(T) & ~le(K,K0)) + ) } From b6e627723906b1eed150661cf14fb85bc47081f4 Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Thu, 1 Mar 2018 02:05:50 +0200 Subject: [PATCH 08/17] move import of ivy_l2s, so command line options (e.g., l2s_debug) work --- ivy/ivy_check.py | 28 +++++++++++++--------------- ivy/ivy_compiler.py | 4 ---- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/ivy/ivy_check.py b/ivy/ivy_check.py index 55a492fb5..b9553dce7 100644 --- a/ivy/ivy_check.py +++ b/ivy/ivy_check.py @@ -18,6 +18,7 @@ import ivy_theory as ith import ivy_transrel as itr import ivy_solver as islv +from ivy_l2s import l2s import sys from collections import defaultdict @@ -36,7 +37,7 @@ def display_cex(msg,ag): ui.ui_main_loop(ag) exit(1) raise iu.IvyError(None,msg) - + def check_properties(): if itp.false_properties(): if diagnose.get(): @@ -68,7 +69,7 @@ def show_counterexample(ag,state,bmc_res): gui.tk.mainloop() exit(1) - + def check_conjectures(kind,msg,ag,state): failed = itp.undecided_conjectures(state) if failed: @@ -87,13 +88,12 @@ def check_conjectures(kind,msg,ag,state): def has_temporal_stuff(f): return any(True for x in lut.temporals_ast(f)) or any(True for x in lut.named_binders_ast(f)) - + def check_temporals(): props = im.module.labeled_props proved = [] for prop in props: if prop.temporal: - from ivy_l2s import l2s mod = im.module.copy() mod.labeled_axioms.extend(proved) mod.labeled_props = [] @@ -139,7 +139,7 @@ def get_checked_actions(): def print_dots(): print '...', sys.stdout.flush() - + class Checker(object): def __init__(self,conj,report_pass=True): @@ -171,7 +171,7 @@ def pretty_lineno(ast): def pretty_lf(lf,indent=8): return indent*' ' + "{}{}".format(pretty_lineno(lf),pretty_label(lf.label)) - + class ConjChecker(Checker): def __init__(self,lf,indent=8): self.lf = lf @@ -180,7 +180,7 @@ def __init__(self,lf,indent=8): def start(self): print pretty_lf(self.lf,self.indent), print_dots() - + class ConjAssumer(Checker): def __init__(self,lf): self.lf = lf @@ -234,7 +234,7 @@ def show_sym(self,sym,renamed_sym): continue self.current[lhs] = rhs print ' {}'.format(rfmla) - + def eval(self,cond): truth = self.model.eval_to_constant(cond) if lg.is_false(truth): @@ -242,9 +242,9 @@ def eval(self,cond): elif lg.is_true(truth): return True assert False,truth - + def handle(self,action,env): - + # iu.dbg('env') if hasattr(action,'lineno'): # print ' env: {}'.format('{'+','.join('{}:{}'.format(x,y) for x,y in env.iteritems())+'}') @@ -264,7 +264,7 @@ def end(self): for sym in self.vocab: if not itr.is_new(sym) and not itr.is_skolem(sym): self.show_sym(sym,sym) - + def filter_fcs(fcs): @@ -426,7 +426,7 @@ def summarize_isolate(mod): check_conjs_in_state(mod,ag,post,indent=12) else: print '' - + callgraph = defaultdict(list) @@ -501,7 +501,6 @@ def check_isolate(): if temporals: if len(temporals) > 1: raise IvyError(None,'multiple temporal properties in an isolate not supported yet') - from ivy_l2s import l2s l2s(mod, temporals[0]) mod.concept_spaces = [] mod.update_conjs() @@ -565,7 +564,7 @@ def check_module(): else: if coverage.get(): missing = ivy_isolate.check_isolate_completeness() - + if missing: raise iu.IvyError(None,"Some assertions are not checked") @@ -608,4 +607,3 @@ def main(): if __name__ == "__main__": main() - diff --git a/ivy/ivy_compiler.py b/ivy/ivy_compiler.py index a689a94fb..cfb018b2e 100644 --- a/ivy/ivy_compiler.py +++ b/ivy/ivy_compiler.py @@ -1310,10 +1310,6 @@ def named_trans(prop): mod.labeled_props.append(g.clone([label,g.formula])) mod.labeled_props.append(prop) mod.subgoals.append((prop,subgoals)) - # elif prop.temporal: - # from ivy_l2s import l2s - # print "=================" + "\n" * 10 - # l2s(mod, prop) else: mod.labeled_props.append(prop) if prop.id in nmap: From df20fa9c33ea826e89147e711a50252c90f2e223 Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Thu, 1 Mar 2018 02:10:36 +0200 Subject: [PATCH 09/17] fixed bug in ivy_logic_utils.normalize_free_variables --- ivy/ivy_logic_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ivy/ivy_logic_utils.py b/ivy/ivy_logic_utils.py index 1db62e690..cf2278462 100644 --- a/ivy/ivy_logic_utils.py +++ b/ivy/ivy_logic_utils.py @@ -203,12 +203,16 @@ def normalize_free_variables(ast): subs = dict() vs = [] nvs = [] + seen = set() for v in variables_ast(ast): - if v not in subs: + if v not in seen: + seen.add(v) + assert v.name not in subs # we have a problem if two different variables have the same name nv = lg.Var('V{}'.format(len(subs)), v.sort) subs[v.name] = nv vs.append(v) nvs.append(nv) + assert len(set(nvs)) == len(nvs) nast = substitute_ast(ast, subs) return vs, nvs, nast From 250162f0ab57f1c8f38819fca24525189bb35582 Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Thu, 1 Mar 2018 02:19:03 +0200 Subject: [PATCH 10/17] improvements and fixes to ivy_l2s.py --- ivy/ivy_l2s.py | 150 ++++++++++++++++++++++++++----------------------- 1 file changed, 81 insertions(+), 69 deletions(-) diff --git a/ivy/ivy_l2s.py b/ivy/ivy_l2s.py index 8ad70a392..ae0320a15 100644 --- a/ivy/ivy_l2s.py +++ b/ivy/ivy_l2s.py @@ -8,29 +8,36 @@ TODO's and open issues: -* automatically add conjectures of original system to the saved state +* allow exporting and using of temporal properties + +* support for ivy1.7 syntax -* automatically add basic conjectures about the monitor (e.g. states - are mutually exclusive) +* add support for enumerated types + +* add support for adding all elements smaller than a constant (downward finite total order) + +* automatically add conjectures of original system to the saved state * handle multiple temporal properties * temporal axioms? -* support nesting structure? - * review the correctness -* figure out the public_actions issue - * decide abotu normalizing the Boolean structure of temporal formulas, properties, waited formulas, and named binders (e.g. normalize ~~phi to phi?) -* a syntax for accessing Skolem constants and functions from the +* syntax for accessing Skolem constants and functions from the negation of temporal properties. +* syntax for fair scheduling of actions (e.g., eliminate scheduled + from ticket) +* use assetions instead of l2s_error +""" + +""" Useful definitions from ivy_module: self.definitions = [] # TODO: these are actually "derived" relations self.labeled_axioms = [] @@ -50,18 +57,20 @@ from ivy_actions import (AssignAction, Sequence, ChoiceAction, AssumeAction, AssertAction, HavocAction, concat_actions) +import ivy_ast as ast import logic as lg import ivy_logic_utils as ilu import ivy_utils as iu + debug = iu.BooleanParameter("l2s_debug",False) + def forall(vs, body): return lg.ForAll(vs, body) if len(vs) > 0 else body -def l2s(mod, lf): - +def l2s(mod, temporal_goal): # modify mod in place # module pass helper funciton @@ -80,6 +89,7 @@ def mod_pass(transform): l2s_waiting = lg.Const('l2s_waiting', lg.Boolean) l2s_frozen = lg.Const('l2s_frozen', lg.Boolean) l2s_saved = lg.Const('l2s_saved', lg.Boolean) + l2s_error = lg.Const('l2s_error', lg.Boolean) l2s_d = lambda sort: lg.Const('l2s_d',lg.FunctionSort(sort,lg.Boolean)) l2s_a = lambda sort: lg.Const('l2s_a',lg.FunctionSort(sort,lg.Boolean)) l2s_w = lambda vs, t: lg.NamedBinder('l2s_w', vs, t) @@ -87,6 +97,24 @@ def mod_pass(transform): l2s_g = lambda vs, t: lg.NamedBinder('l2s_g', vs, t) old_l2s_g = lambda vs, t: lg.NamedBinder('old_l2s_g', vs, t) + # add conjectures about monitor state + conjs = [ + lg.Or(l2s_waiting, l2s_frozen, l2s_saved), + lg.Or(lg.Not(l2s_waiting), lg.Not(l2s_frozen)), + lg.Or(lg.Not(l2s_waiting), lg.Not(l2s_saved)), + lg.Or(lg.Not(l2s_frozen), lg.Not(l2s_saved)), + ] + for f in conjs: + c = ast.LabeledFormula(ast.Atom('l2s_internal'), f) + c.lineno = temporal_goal.lineno + mod.labeled_conjs.append(c) + + # add conjecture that we are not in the error state (this is + # instead of using an assertion. see below) + c = ast.LabeledFormula(ast.Atom('not_l2s_error'), lg.Not(l2s_error)) + c.lineno = temporal_goal.lineno + mod.labeled_conjs.append(c) + #print ilu.used_symbols_asts(mod.labeled_conjs) #print '='*40 #print list(ilu.named_binders_asts(mod.labeled_conjs)) @@ -105,7 +133,7 @@ def _l2s_g(vs, t): return res replace_temporals_by_l2s_g = lambda ast: ilu.replace_temporals_by_named_binder_g_ast(ast, _l2s_g) mod_pass(replace_temporals_by_l2s_g) - not_lf = replace_temporals_by_l2s_g(lg.Not(lf.formula)) + not_temporal_goal = replace_temporals_by_l2s_g(lg.Not(temporal_goal.formula)) if debug.get(): print "=" * 80 +"\nafter replace_temporals_by_named_binder_g_ast"+ "\n"*3 print "=" * 80 + "\nl2s_gs:" @@ -122,7 +150,9 @@ def _l2s_g(vs, t): print_module(mod) print "=" * 80 + "\n"*3 - # TODO: what about normalizing lf? + # TODO: what about normalizing temporal_goal? - temporal_goal + # should not contain any named binders except for temporal + # properties, so it is normalized by construction # construct the monitor related building blocks @@ -144,7 +174,7 @@ def _l2s_g(vs, t): named_binders_conjs = defaultdict(list) # dict mapping names to lists of (vars, body) for b in ilu.named_binders_asts(mod.labeled_conjs): named_binders_conjs[b.name].append((b.variables, b.body)) - named_binders_conjs = defaultdict(list,((k,list(set(v))) for k,v in named_binders_conjs.iteritems())) + named_binders_conjs = defaultdict(list,((k,sorted(list(set(v)))) for k,v in named_binders_conjs.iteritems())) to_wait = [] # list of (variables, term) corresponding to l2s_w in conjectures to_wait += named_binders_conjs['l2s_w'] to_save = [] # list of (variables, term) corresponding to l2s_s in conjectures @@ -154,8 +184,8 @@ def _l2s_g(vs, t): print "=" * 40 + "\nto_wait:\n" for vs, t in to_wait: print vs, t - print list(ilu.variables_ast(t)) == list(vs) - print + # print list(ilu.variables_ast(t)) == list(vs) + # print print "=" * 40 save_state = [ @@ -176,8 +206,9 @@ def _l2s_g(vs, t): update_w = [ AssignAction( l2s_w(vs,t)(*vs), - lg.And(l2s_w(vs,t)(*vs), lg.Not(t), replace_temporals_by_l2s_g(lg.Not(lg.Globally(lg.Not(t))))) - # TODO check this and make sure its correct + lg.And(l2s_w(vs,t)(*vs), lg.Not(t), replace_temporals_by_l2s_g(lg.Not(lg.Globally(ilu.negate(t))))) + # ($l2s_w. phi) waits until ( phi | globally ~phi), but + # ($l2s_w. ~phi) waits until (~phi | globally phi) (i.e., we avoid "globally ~~phi" here) # note this adds to l2s_gs ) for vs, t in to_wait @@ -219,8 +250,21 @@ def _l2s_g(vs, t): isinstance(t.sort, lg.FunctionSort) and isinstance(t.sort.range, lg.UninterpretedSort) ) ] - assert_no_fair_cycle = AssertAction(lg.Not(lg.And(*fair_cycle))) - assert_no_fair_cycle.lineno = lf.lineno + if debug.get(): + print "=" * 40 + "\nfair_cycle:\n" + for x in fair_cycle: + print x + print + print "=" * 40 + # TODO: figure out why AssertAction doesn't work properly + def assert_no_fair_cycle(a): + # comment and uncomment the following lines to debug: + # res = AssertAction(lg.Not(lg.And(*fair_cycle))) + # res = AssertAction(lg.false) + # res.lineno = temporal_goal.lineno + # res.lineno = a.lineno + res = AssignAction(l2s_error, lg.And(*fair_cycle)) + return res monitor_edge = lambda s1, s2: [ AssumeAction(s1), @@ -234,55 +278,24 @@ def _l2s_g(vs, t): [AssumeAction(x) for x in done_waiting] + reset_a )), - # # frozen -> saved - # Sequence(*( - # monitor_edge(l2s_frozen, l2s_saved) + - # save_state + - # reset_w - # )), + # frozen -> saved + Sequence(*( + monitor_edge(l2s_frozen, l2s_saved) + + save_state + + reset_w + )), # stay in same state (self edge) Sequence(), )] - # tableau construction (sort of) - - # Note that we first transformed globally and eventually to named - # binders, in order to normalize. Without this, we would get - # multiple redundant axioms like: - # forall X. (globally phi(X)) -> phi(X) - # forall Y. (globally phi(Y)) -> phi(Y) - # and the same redundancy will happen for transition updates. - - # temporals = [] - # temporals += list(ilu.temporals_asts( - # # TODO: these should be handled by mod_pass instead (and come via l2s_gs): - # # mod.labeled_axioms + - # # mod.labeled_props + - # [lf] - # )) - # temporals += [lg.Globally(lg.Not(t)) for vs, t in to_wait] - # temporals += [lg.Globally(t) for vs, t in l2s_gs] - # # TODO get from temporal axioms and temporal properties as well - # print '='*40 + "\ntemporals:" - # for t in temporals: - # print t, '\n' - # print '='*40 - # to_g = [ # list of (variables, formula) - # (tuple(sorted(ilu.variables_ast(tt))), tt) # TODO what about variable normalization?? - # for t in temporals - # for tt in [t.body if type(t) is lg.Globally else - # lg.Not(t.body) if type(t) is lg.Eventually else 1/0] - # ] - # TODO: get rid of the above, after properly combining it + # tableau construction to_g = [] # list of (variables, formula) - to_g += list(l2s_gs) - to_g = list(set(to_g)) + to_g += sorted(list(l2s_gs)) if debug.get(): print '='*40 + "\nto_g:\n" - for vs, t in sorted(to_g): + for vs, t in to_g: print vs, t, '\n' print '='*40 - assume_g_axioms = [ AssumeAction(forall(vs, lg.Implies(l2s_g(vs, t)(*vs), t))) for vs, t in to_g @@ -295,14 +308,8 @@ def _l2s_g(vs, t): # now patch the module actions with monitor and tableau - if debug.get(): print "public_actions:", mod.public_actions - # TODO: this includes the succ action (for the ticket example of - # test/test_liveness.ivy). seems to be a bug, and this causes - # wrong behavior for the monitor, since a call to succ from within - # another action lets it take a step - for a in mod.public_actions: action = mod.actions[a] add_params_to_d = [ @@ -310,6 +317,8 @@ def _l2s_g(vs, t): for p in action.formal_params ] new_action = concat_actions(*( + # TODO: check this with Sharon + assume_g_axioms + change_monitor_state + add_params_to_d + update_g + @@ -317,7 +326,7 @@ def _l2s_g(vs, t): assume_g_axioms + add_consts_to_d + update_w + - [assert_no_fair_cycle] + [assert_no_fair_cycle(action)] )) new_action.lineno = action.lineno new_action.formal_params = action.formal_params @@ -328,11 +337,12 @@ def _l2s_g(vs, t): AssignAction(l2s_waiting, lg.true), AssignAction(l2s_frozen, lg.false), AssignAction(l2s_saved, lg.false), + AssignAction(l2s_error, lg.false), ] l2s_init += add_consts_to_d l2s_init += reset_w l2s_init += assume_g_axioms - l2s_init += [AssumeAction(not_lf)] + l2s_init += [AssumeAction(not_temporal_goal)] mod.initializers.append(('l2s_init', Sequence(*l2s_init))) if debug.get(): @@ -363,8 +373,10 @@ def _l2s_g(vs, t): ) if debug.get(): print "=" * 80 + "\nsubs:" + "\n"*3 - for k, v in subs.items(): - print k, ' : ', v, '\n' + for k in sorted(named_binders.keys()): + v = named_binders[k] + for i, b in enumerate(v): + print '{}_{}'.format(k, i), ' : ', b print "=" * 80 + "\n"*3 mod_pass(lambda ast: ilu.replace_named_binders_ast(ast, subs)) From 9368ac91935eb8a463c72c653a533b425e32a85f Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Thu, 1 Mar 2018 02:36:05 +0200 Subject: [PATCH 11/17] added support for random seed (with seed=r|rand|random) --- ivy/ivy_solver.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/ivy/ivy_solver.py b/ivy/ivy_solver.py index e32a10a7b..e45291c98 100644 --- a/ivy/ivy_solver.py +++ b/ivy/ivy_solver.py @@ -10,6 +10,7 @@ from collections import defaultdict import re import functools +import random import z3 import ivy_logic @@ -32,15 +33,16 @@ use_z3_enums = False def set_seed(seed): - print 'setting seed to {}'.format(seed) + print 'setting smt seed to {}'.format(seed) z3.set_param('smt.random_seed',seed) + # TODO: should we also set sat.random_seed? -opt_seed = iu.Parameter("seed",0,process=int) +opt_seed = iu.Parameter("seed",0,process=lambda seed: random.randint(0,4294967295) if seed in ('r', 'rand', 'random') else int(seed)) opt_seed.set_callback(set_seed) def set_macro_finder(truth): z3.set_param('smt.macro_finder',truth) - + opt_incremental = iu.BooleanParameter("incremental",True) #z3.set_param('smt.mbqi.trace',True) @@ -91,7 +93,7 @@ def sorts(name): if name == 'strlit': return z3.StringSort() return None - + #sorts = {} #sorts = {"S":S, # "Int":z3.IntSort()} @@ -102,7 +104,7 @@ def parse_int_params(name): if not all(t.endswith(']') for t in things): raise SyntaxError() return [int(t[:-1]) for t in things] - + def is_solver_sort(name): return name.startswith('bv[') and name.endswith(']') or name == 'int' or name == 'nat' or name == 'strlit' or name.startswith('strbv[') @@ -149,7 +151,7 @@ def clear(): z3_constants = dict() z3_functions = dict() -clear() +clear() #z3_sorts_inv = dict((get_id(z3sort),ivysort) for ivysort,z3sort in z3_sorts.iteritems()) z3_sorts_inv = {} @@ -205,7 +207,7 @@ def lookup_native(thing,table,kind): return z3val def check_native_compat_sym(sym): - table,kind = (relations,"relation") if sym.is_relation() else (functions,"function") + table,kind = (relations,"relation") if sym.is_relation() else (functions,"function") thing = lookup_native(sym,table,kind) # print "check_native_compat_sym: {} {}".format(sym,thing) try: @@ -338,7 +340,7 @@ def lt_pred(sort): sym = ivy_logic.Symbol('<',sort) sig = sym.sort.to_z3() return z3.Function(solver_name(sym), *sig) - + polymacs = { '<=' : lambda s,x,y: z3.Or(x == y,lt_pred(s)(x,y)), '>' : lambda s,x,y: lt_pred(s)(y,x), @@ -424,7 +426,7 @@ def type_constraints(syms): z3_fmla = formula_to_z3_closed(fmla) res.append(z3_fmla) return res - + def clauses_to_z3(clauses): z3_clauses = [conj_to_z3(cl) for cl in clauses.fmlas] @@ -480,7 +482,7 @@ def formula_to_z3(fmla): if len(tcs) > 0: z3_fmla = z3.And(*([z3_fmla] + tcs)) return z3_fmla - + def unsat_core(clauses1, clauses2, implies = None, unlikely=lambda x:False): @@ -594,7 +596,7 @@ def __call__(self,x,y): fact = substitute(self.order,*interp) fact_val = self.model.eval(fact) # print "order: %s = %s" % (fact,fact_val) - return -1 if z3.is_true(fact_val) else 1 + return -1 if z3.is_true(fact_val) else 1 def collect_numerals(z3term): if z3.is_int_value(z3term) or z3.is_bv_value(z3term): @@ -625,7 +627,7 @@ def mine_interpreted_constants(model,vocab): if sort in sort_values: sort_values[sort].update(collect_model_values(sort,model,s)) return dict((x,map(term_to_z3,list(y))) for x,y in sort_values.iteritems()) - + class HerbrandModel(object): def __init__(self,solver,model,vocab): @@ -704,7 +706,7 @@ def eval_to_constant(self,t): def __str__(self): return self.model.sexpr() - + # TODO: need to map Z3 sorts back to ivy sorts def sort_from_z3(s): return z3_sorts_inv[get_id(s)] @@ -756,7 +758,7 @@ def clauses_imply_list(clauses1, clauses2_list): res = [] negs = map(dual_clauses,clauses2_list) - + for clauses2 in negs: z2 = clauses_to_z3(clauses2) # print "check {}".format(clauses2) @@ -1013,7 +1015,7 @@ def get_small_model(clauses, sorts_to_minimize, relations_to_minimize, final_con s = z3.Solver() s.add(clauses_to_z3(clauses)) - + # res = decide(s) # if res == z3.unsat: # return None @@ -1209,7 +1211,7 @@ def filter_redundant_facts(clauses,axioms): "axioms". Currently, this removes only negative formulas that are implied by the positive formulas, so it should work well for facts about total orders, for example. """ - + fmlas = clauses.fmlas pos_fmlas = [fmla for fmla in fmlas if not isinstance(fmla,ivy_logic.Not)] neg_fmlas = [fmla for fmla in fmlas if isinstance(fmla,ivy_logic.Not)] @@ -1324,7 +1326,7 @@ def function_model_to_clauses(h,f): for c in rng.defines(): eq = ivy_logic._eq_lit(fterm,ivy_logic.Constant(ivy_logic.Symbol(c,rng))) # print "function_model_to_clauses: {}".format(eq) - get_lit_facts(h,eq,res) + get_lit_facts(h,eq,res) # non-enumerated function types else: lit = ivy_logic.Literal(1,fun_eq_inst(f)) From a7ba4e86817f56d5c2f9deffc0dd632177526238 Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Thu, 1 Mar 2018 02:57:36 +0200 Subject: [PATCH 12/17] removed ancient examples.txt file --- examples/examples.txt | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 examples/examples.txt diff --git a/examples/examples.txt b/examples/examples.txt deleted file mode 100644 index f0e5dcb15..000000000 --- a/examples/examples.txt +++ /dev/null @@ -1,11 +0,0 @@ -learning - proved, using updr -learning with count -spanning tree -leader - proved, using iupdr -client server -chord -flash -hotel -chain panda -gc copy - proved, mainly using cti -bakery From 4a7e0d8a08a3bfff29e12a8e1fd4f63c898d4555 Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Thu, 1 Mar 2018 03:02:14 +0200 Subject: [PATCH 13/17] added liveness examples: ticket, nested ticket, and alternating bit protocol --- .../liveness/alternating_bit_protocol.ivy | 488 ++++++++++++++++++ examples/liveness/ticket.ivy | 279 ++++++++++ examples/liveness/ticket_nested.ivy | 330 ++++++++++++ 3 files changed, 1097 insertions(+) create mode 100644 examples/liveness/alternating_bit_protocol.ivy create mode 100644 examples/liveness/ticket.ivy create mode 100644 examples/liveness/ticket_nested.ivy diff --git a/examples/liveness/alternating_bit_protocol.ivy b/examples/liveness/alternating_bit_protocol.ivy new file mode 100644 index 000000000..b17b3bd4a --- /dev/null +++ b/examples/liveness/alternating_bit_protocol.ivy @@ -0,0 +1,488 @@ +#lang ivy1.6 +################################################################################ +# +# A basic version of alternating bit protocol with binary (1-bit) +# sequence number and FIFO channels. +# +################################################################################ + + +################################################################################ +# +# Module for axiomatizing a totally ordered set with fixed order +# +# The module includes an le relation, a minimal element (zero) and +# get_succ and get_pred actions. +# +# In this module, the order is arbitrary and fixed. +# +################################################################################ + +module total_order(carrier) = { + relation le(X:carrier,Y:carrier) # less than or equal + + axiom le(X, X) # Reflexivity + axiom le(X, Y) & le(Y, Z) -> le(X, Z) # Transitivity + axiom le(X, Y) & le(Y, X) -> X = Y # Anti-symmetry + axiom le(X, Y) | le(Y, X) # Totality + + individual zero:carrier + axiom le(zero, X) + + action get_succ(x:carrier) returns (y:carrier) = { + assume le(x,y) & x ~= y & ((le(x, Z) & x ~= Z) -> le(y, Z)) + } + + action get_pred(y:carrier) returns (x:carrier) = { + assume le(x,y) & x ~= y & ((le(x, Z) & x ~= Z) -> le(y, Z)) + } +} + +################################################################################ +# +# A module for a fifo channel +# +################################################################################ + +module fifo_channel(m_t) = { + relation le(X:m_t,Y:m_t) # partial order representing messages in + # the channel - larger messages are + # older. le(x,x) means message x is in + # the channel + + conjecture le(X, Y) & le(Y, Z) -> le(X, Z) # Transitivity + conjecture le(X, Y) & le(Y, X) -> X = Y # Anti-symmetry + conjecture le(X, Y) -> le(X, X) & le(Y, Y) # Partial reflexivity + conjecture le(X, X) & le(Y, Y) -> le(X, Y) | le(Y, X) # Partial Totality + + init ~le(X, Y) + + action send(m: m_t) = { + # insert m as a newest message + assume ~le(m, m); + le(m, m) := true; + le(m, X) := le(X,X) + } + + action receive returns (m: m_t) = { + # receive the oldest message and remove it + assume le(m, m); + assume le(m,X) -> X = m; + le(X,m) := false + } + + action drop(m:m_t) = { + # drop a message + le(X,Y) := le(X, Y) & X ~= m & Y ~= m + } +} + +object abp = { + + ################################################################################ + # + # Types, relations and functions describing state of the network + # + ################################################################################ + + ################################################################################ + # + # The protocol itself, with tracking of events for expressing + # fairness assumptions. + # + ################################################################################ + + # a totally ordered set for indices + type index_t + instantiate index : total_order(index_t) + + # an uninterpreted sort for data items + type value + individual bot:value # special bottom value + + # data messages (sent from sender to received), with a fifo channel, + # and a data item and sequence bit for every message + type data_msg_t + instantiate data_msg : fifo_channel(data_msg_t) + individual d(D:data_msg_t) : value # immutable + relation dbit(D:data_msg_t) # immutable + + # ack messages (sent from receiver to sender), with a fifo channel and + # a sequence number bit for every message. + type ack_msg_t + instantiate ack_msg : fifo_channel(ack_msg_t) + relation abit(A:ack_msg_t) # immutable + + # sender array and receiver array + individual sender_array(I:index_t) : value + individual receiver_array(I:index_t) : value + init sender_array(I) = bot + init receiver_array(I) = bot + + # sender index and receiver index + individual sender_i:index_t + init sender_i = index.zero + individual sender_gen_i:index_t + init sender_gen_i = index.zero + individual receiver_i:index_t + init receiver_i = index.zero + + # sender and receiver bits, initially 0 (false) + relation sender_bit + init ~sender_bit + relation receiver_bit + init ~receiver_bit + + # state for encoding the fairness assumptions + relation sender_scheduled + relation receiver_scheduled + relation data_sent + relation data_received + relation ack_sent + relation ack_received + init ~sender_scheduled + init ~receiver_scheduled + init ~data_sent + init ~data_received + init ~ack_sent + init ~ack_received + + ################################################################################ + # + # Protocol actions + # + ################################################################################ + + action reset_fairness = { + sender_scheduled := false; + receiver_scheduled := false; + data_sent := false; + data_received := false; + ack_sent := false; + ack_received := false; + } + + action gen_data(v:value) = { + call reset_fairness; + assume v ~= bot; + sender_array(sender_gen_i) := v; + sender_gen_i := index.get_succ(sender_gen_i); + } + + action sender_send_data(m:data_msg_t, a:ack_msg_t) = { + call reset_fairness; + sender_scheduled := true; + if (sender_array(sender_i) ~= bot) { + assume d(m) = sender_array(sender_i); + assume dbit(m) <-> sender_bit; + call data_msg.send(m); + data_sent := true; + }; + } + + action sender_receive_ack(m:data_msg_t, a:ack_msg_t) = { + call reset_fairness; + ack_received := true; + a := ack_msg.receive(); + if abit(a) <-> sender_bit { + sender_bit := ~sender_bit; + sender_i := index.get_succ(sender_i) + }; + } + + action receiver_receive_data(m:data_msg_t, a:ack_msg_t) = { + call reset_fairness; + data_received := true; + m := data_msg.receive(); + if dbit(m) <-> receiver_bit { + # flip receiver bit, append to receiver array + receiver_bit := ~receiver_bit; + receiver_array(receiver_i) := d(m); + receiver_i := index.get_succ(receiver_i) + }; + } + + action receiver_send_ack(m:data_msg_t, a:ack_msg_t) = { + call reset_fairness; + receiver_scheduled := true; + # send ack with ~receiver_bit + assume abit(a) <-> ~receiver_bit; + call ack_msg.send(a); + ack_sent := true; + } + + action data_msg_drop(m:data_msg_t, a:ack_msg_t) = { + call reset_fairness; + call data_msg.drop(m); + } + + action ack_msg_drop(m:data_msg_t, a:ack_msg_t) = { + call reset_fairness; + call ack_msg.drop(a); + } + + export gen_data + export sender_send_data + export sender_receive_ack + export receiver_receive_data + export receiver_send_ack + export data_msg_drop + export ack_msg_drop + + ################################################################################ + # + # Conjectures for proving safety (also helps for liveness) + # + ################################################################################ + + # safety - receiver array has values from sender array for all received indices + conjecture receiver_array(I) ~= bot -> receiver_array(I) = sender_array(I) + + # inductive invariant for proving safety + + conjecture index.le(sender_gen_i, I) <-> sender_array(I) = bot + conjecture index.le(receiver_i, I) <-> receiver_array(I) = bot + conjecture index.le(sender_i, sender_gen_i) + + conjecture ~sender_bit & ~receiver_bit & ack_msg.le(A,A) -> abit(A) + conjecture ~sender_bit & ~receiver_bit & data_msg.le(M1,M2) -> ~(dbit(M1) & ~dbit(M2)) + + conjecture sender_bit & receiver_bit & ack_msg.le(A,A) -> ~abit(A) + conjecture sender_bit & receiver_bit & data_msg.le(M1,M2) -> ~(~dbit(M1) & dbit(M2)) + + conjecture ~sender_bit & receiver_bit & data_msg.le(M,M) -> ~dbit(M) + conjecture ~sender_bit & receiver_bit & ack_msg.le(A1,A2) -> ~(abit(A1) & ~abit(A2)) + + conjecture sender_bit & ~receiver_bit & data_msg.le(M,M) -> dbit(M) + conjecture sender_bit & ~receiver_bit & ack_msg.le(A1,A2) -> ~(~abit(A1) & abit(A2)) + + conjecture (sender_bit <-> receiver_bit) -> sender_i = receiver_i + conjecture (sender_bit <-> ~receiver_bit) -> ( + # receiver_i = sender_i + 1 + ~index.le(receiver_i, sender_i) & + (forall I. ~index.le(I,sender_i) -> index.le(receiver_i,I)) + ) + + conjecture data_msg.le(M,M) & (dbit(M) <-> sender_bit) -> ~index.le(sender_gen_i, sender_i) + conjecture data_msg.le(M,M) & (dbit(M) <-> sender_bit) -> d(M) = sender_array(sender_i) + conjecture data_msg.le(M,M) -> d(M) ~= bot + + conjecture ack_msg.le(A,A) & (abit(A) <-> sender_bit) -> ~index.le(receiver_i,sender_i) + conjecture ack_msg.le(A,A) & (abit(A) <-> sender_bit) -> receiver_array(sender_i) = sender_array(sender_i) + conjecture ack_msg.le(A,A) & (abit(A) <-> sender_bit) -> receiver_array(sender_i) ~= bot + + ################################################################################ + # + # Temporal property and its proof + # + ################################################################################ + + isolate tp1 = { + + ################################################################################ + # + # Liveness proof + # + # The property to prove is: + # ( + # (globally eventually sender_scheduled) & # scheduling fairness + # (globally eventually receiver_scheduled) & # scheduling fairness + # ((globally eventually data_sent) -> (globally eventually data_received)) # data channel fairness + # ((globally eventually ack_sent) -> (globally eventually ack_received)) # ack channel fairness + # ) -> (forall I. globally (sender_array(I) ~= bot -> eventually (receiver_array(I) ~= bot))) + # + # The set A of formulas containts the following formulas (and their subformulas): + # 1. the property + # 2. eventually globally ~sender_bit & ~receiver_bit + # 3. eventually globally ~sender_bit & receiver_bit + # 4. eventually globally sender_bit & ~receiver_bit + # 5. eventually globally sender_bit & receiver_bit + # + # We also use "witness constants" for the following formula: + # ~ globally (sender_array(I) ~= bot -> eventually receiver_array(I) ~= bot) + # + ################################################################################ + + individual sk0:index_t # witness for the formula (exists I. ~ globally (sender_array(I) ~= bot -> (eventually receiver_array(I) ~= bot))) + + object spec = { + temporal property [liveness] ( + ((exists I. ~ globally (sender_array(I) ~= bot -> (eventually receiver_array(I) ~= bot))) -> + (~ globally (sender_array(sk0) ~= bot -> (eventually receiver_array(sk0) ~= bot)))) & + + (globally eventually sender_scheduled) & # scheduling fairness + (globally eventually receiver_scheduled) & # scheduling fairness + ((globally eventually data_sent) -> (globally eventually data_received)) & # data channel fairness + ((globally eventually ack_sent) -> (globally eventually ack_received)) # ack channel fairness + ) -> (forall I. globally (sender_array(I) ~= bot -> eventually (receiver_array(I) ~= bot))) + } + + ################################################################################ + # + # Conjectures for proving liveness + # + ################################################################################ + + object impl = { + # all safety conjectures for saved state (in the future, will be autumatically added) + conjecture l2s_saved -> ( ($l2s_s X. receiver_array(X))(I) ~= ($l2s_s. bot) -> ($l2s_s X. receiver_array(X))(I) = ($l2s_s X. sender_array(X))(I) ) + conjecture l2s_saved -> ( ($l2s_s X,Y. index.le(X,Y))(($l2s_s. sender_gen_i), I) <-> ($l2s_s X. sender_array(X))(I) = ($l2s_s. bot) ) + conjecture l2s_saved -> ( ($l2s_s X,Y. index.le(X,Y))(($l2s_s. receiver_i), I) <-> ($l2s_s X. receiver_array(X))(I) = ($l2s_s. bot) ) + conjecture l2s_saved -> ( ($l2s_s X,Y. index.le(X,Y))(($l2s_s. sender_i), ($l2s_s. sender_gen_i)) ) + conjecture l2s_saved -> ( ~($l2s_s. sender_bit) & ~($l2s_s. receiver_bit) & ($l2s_s X,Y. ack_msg.le(X,Y))(A,A) -> ($l2s_s X. abit(X))(A) ) + conjecture l2s_saved -> ( ~($l2s_s. sender_bit) & ~($l2s_s. receiver_bit) & ($l2s_s X,Y. data_msg.le(X,Y))(M1,M2) -> ~(($l2s_s X. dbit(X))(M1) & ~($l2s_s X. dbit(X))(M2)) ) + conjecture l2s_saved -> ( ($l2s_s. sender_bit) & ($l2s_s. receiver_bit) & ($l2s_s X,Y. ack_msg.le(X,Y))(A,A) -> ~($l2s_s X. abit(X))(A) ) + conjecture l2s_saved -> ( ($l2s_s. sender_bit) & ($l2s_s. receiver_bit) & ($l2s_s X,Y. data_msg.le(X,Y))(M1,M2) -> ~(~($l2s_s X. dbit(X))(M1) & ($l2s_s X. dbit(X))(M2)) ) + conjecture l2s_saved -> ( ~($l2s_s. sender_bit) & ($l2s_s. receiver_bit) & ($l2s_s X,Y. data_msg.le(X,Y))(M,M) -> ~($l2s_s X. dbit(X))(M) ) + conjecture l2s_saved -> ( ~($l2s_s. sender_bit) & ($l2s_s. receiver_bit) & ($l2s_s X,Y. ack_msg.le(X,Y))(A1,A2) -> ~(($l2s_s X. abit(X))(A1) & ~($l2s_s X. abit(X))(A2)) ) + conjecture l2s_saved -> ( ($l2s_s. sender_bit) & ~($l2s_s. receiver_bit) & ($l2s_s X,Y. data_msg.le(X,Y))(M,M) -> ($l2s_s X. dbit(X))(M) ) + conjecture l2s_saved -> ( ($l2s_s. sender_bit) & ~($l2s_s. receiver_bit) & ($l2s_s X,Y. ack_msg.le(X,Y))(A1,A2) -> ~(~($l2s_s X. abit(X))(A1) & ($l2s_s X. abit(X))(A2)) ) + conjecture l2s_saved -> ( (($l2s_s. sender_bit) <-> ($l2s_s. receiver_bit)) -> ($l2s_s. sender_i) = ($l2s_s. receiver_i) ) + conjecture l2s_saved -> ( (($l2s_s. sender_bit) <-> ~($l2s_s. receiver_bit)) -> (~($l2s_s X,Y. index.le(X,Y))(($l2s_s. receiver_i), ($l2s_s. sender_i)) & (forall I. ~($l2s_s X,Y. index.le(X,Y))(I,($l2s_s. sender_i)) -> ($l2s_s X,Y. index.le(X,Y))(($l2s_s. receiver_i),I))) ) + conjecture l2s_saved -> ( ($l2s_s X,Y. data_msg.le(X,Y))(M,M) & (($l2s_s X. dbit(X))(M) <-> ($l2s_s. sender_bit)) -> ~($l2s_s X,Y. index.le(X,Y))(($l2s_s. sender_gen_i), ($l2s_s. sender_i)) ) + conjecture l2s_saved -> ( ($l2s_s X,Y. data_msg.le(X,Y))(M,M) & (($l2s_s X. dbit(X))(M) <-> ($l2s_s. sender_bit)) -> ($l2s_s X. d(X))(M) = ($l2s_s X. sender_array(X))(($l2s_s. sender_i)) ) + conjecture l2s_saved -> ( ($l2s_s X,Y. data_msg.le(X,Y))(M,M) -> ($l2s_s X. d(X))(M) ~= ($l2s_s. bot) ) + conjecture l2s_saved -> ( ($l2s_s X,Y. ack_msg.le(X,Y))(A,A) & (($l2s_s X. abit(X))(A) <-> ($l2s_s. sender_bit)) -> ~($l2s_s X,Y. index.le(X,Y))(($l2s_s. receiver_i),($l2s_s. sender_i)) ) + conjecture l2s_saved -> ( ($l2s_s X,Y. ack_msg.le(X,Y))(A,A) & (($l2s_s X. abit(X))(A) <-> ($l2s_s. sender_bit)) -> ($l2s_s X. receiver_array(X))(($l2s_s. sender_i)) = ($l2s_s X. sender_array(X))(($l2s_s. sender_i)) ) + conjecture l2s_saved -> ( ($l2s_s X,Y. ack_msg.le(X,Y))(A,A) & (($l2s_s X. abit(X))(A) <-> ($l2s_s. sender_bit)) -> ($l2s_s X. receiver_array(X))(($l2s_s. sender_i)) ~= ($l2s_s. bot) ) + + # some things never change (maybe this should also be detected automatically) + conjecture l2s_saved -> (index.le(X,Y) <-> ($l2s_s X,Y. index.le(X,Y))(X,Y)) + conjecture l2s_saved -> (index.zero = ($l2s_s. index.zero)) + conjecture l2s_saved -> (abit(X) <-> ($l2s_s X. abit(X))(X)) + conjecture l2s_saved -> (dbit(X) <-> ($l2s_s X. dbit(X))(X)) + conjecture l2s_saved -> (d(X) = ($l2s_s X. d(X))(X)) + conjecture l2s_saved -> (sk0 = ($l2s_s. sk0)) + + # basic temporal information + conjecture globally eventually sender_scheduled + conjecture globally eventually receiver_scheduled + conjecture (globally eventually data_sent) -> (globally eventually data_received) + conjecture (globally eventually ack_sent) -> (globally eventually ack_received) + + # properties of sk0 + conjecture ~globally (sender_array(sk0) ~= bot -> (eventually receiver_array(sk0) ~= bot)) + conjecture ~($l2s_w. ~(sender_array(sk0) ~= bot -> (eventually receiver_array(sk0) ~= bot))) -> ~(sender_array(sk0) ~= bot -> (eventually receiver_array(sk0) ~= bot)) + conjecture ~l2s_waiting -> sender_array(sk0) ~= bot + conjecture ~l2s_waiting -> (~eventually receiver_array(sk0) ~= bot) + conjecture ~l2s_waiting -> receiver_array(sk0) = bot + # interestingly, this is not provable in the l2s_waiting state: + # conjecture receiver_array(sk0) = bot + + # basic use of temporal prophecy formulas + conjecture (eventually globally (~sender_bit & ~receiver_bit)) & (~l2s_waiting | ~($l2s_w. globally (~sender_bit & ~receiver_bit))) -> globally (~sender_bit & ~receiver_bit) + conjecture (eventually globally (~sender_bit & receiver_bit)) & (~l2s_waiting | ~($l2s_w. globally (~sender_bit & receiver_bit))) -> globally (~sender_bit & receiver_bit) + conjecture (eventually globally ( sender_bit & ~receiver_bit)) & (~l2s_waiting | ~($l2s_w. globally ( sender_bit & ~receiver_bit))) -> globally ( sender_bit & ~receiver_bit) + conjecture (eventually globally ( sender_bit & receiver_bit)) & (~l2s_waiting | ~($l2s_w. globally ( sender_bit & receiver_bit))) -> globally ( sender_bit & receiver_bit) + + # l2s_d is large enough + conjecture index.le(I,sender_gen_i) -> l2s_d(I) + conjecture data_msg.le(M,M) -> l2s_d(M) + conjecture ack_msg.le(A,A) -> l2s_d(A) + # l2s_a is large enough + conjecture ~l2s_waiting -> ( index.le(I,sk0) -> l2s_a(I) ) + conjecture ~l2s_waiting & ( + (globally (~sender_bit & ~receiver_bit)) | + (globally (~sender_bit & receiver_bit)) | + (globally ( sender_bit & ~receiver_bit)) | + (globally ( sender_bit & receiver_bit)) + ) -> ( data_msg.le(M,M) & (dbit(M) <-> ~sender_bit) -> l2s_a(M) ) + conjecture ~l2s_waiting & ( + (globally (~sender_bit & ~receiver_bit)) | + (globally (~sender_bit & receiver_bit)) | + (globally ( sender_bit & ~receiver_bit)) | + (globally ( sender_bit & receiver_bit)) + ) -> ( ack_msg.le(A,A) & (abit(A) <-> receiver_bit) -> l2s_a(A) ) + + + # more properties of reachable protocol states (but only in ~l2s_waiting) + conjecture ~l2s_waiting -> index.le(sender_i, sk0) + conjecture ~l2s_waiting -> index.le(receiver_i, sk0) + # and their saved state counterparts + conjecture l2s_saved -> ($l2s_s X,Y. index.le(X,Y))(($l2s_s. sender_i), ($l2s_s. sk0)) + conjecture l2s_saved -> ($l2s_s X,Y. index.le(X,Y))(($l2s_s. receiver_i), ($l2s_s. sk0)) + + # relating current state and saved state + conjecture l2s_saved -> index.le(($l2s_s. sender_i), sender_i) + conjecture l2s_saved -> index.le(($l2s_s. receiver_i), receiver_i) + conjecture l2s_saved & ( + (globally (~sender_bit & ~receiver_bit)) | + (globally (~sender_bit & receiver_bit)) | + (globally ( sender_bit & ~receiver_bit)) | + (globally ( sender_bit & receiver_bit)) + ) -> ( + ( sender_bit <-> ($l2s_s. sender_bit) ) & + ( receiver_bit <-> ($l2s_s. receiver_bit) ) & + ( sender_i = ($l2s_s. sender_i) ) & + ( receiver_i = ($l2s_s. receiver_i) ) + ) + conjecture l2s_saved & ( + (globally (~sender_bit & ~receiver_bit)) | + (globally (~sender_bit & receiver_bit)) | + (globally ( sender_bit & ~receiver_bit)) | + (globally ( sender_bit & receiver_bit)) + ) -> ( + data_msg.le(M,M) & (dbit(M) <-> ~sender_bit) -> ($l2s_s X,Y. data_msg.le(X,Y))(M,M) + ) + conjecture l2s_saved & ( + (globally (~sender_bit & ~receiver_bit)) | + (globally (~sender_bit & receiver_bit)) | + (globally ( sender_bit & ~receiver_bit)) | + (globally ( sender_bit & receiver_bit)) + ) -> ( + ack_msg.le(A,A) & (abit(A) <-> receiver_bit) -> ($l2s_s X,Y. ack_msg.le(X,Y))(A,A) + ) + conjecture l2s_saved & (sender_bit <-> ~($l2s_s. sender_bit)) -> sender_i ~= ($l2s_s. sender_i) + conjecture l2s_saved & (receiver_bit <-> ~($l2s_s. receiver_bit)) -> receiver_i ~= ($l2s_s. receiver_i) + + # proof that messages are infinitely often sent and received + conjecture ~(globally eventually data_sent) & (~l2s_waiting | ~($l2s_w. globally ~data_sent)) -> globally ~data_sent + conjecture ~(globally eventually ack_sent) & (~l2s_waiting | ~($l2s_w. globally ~ack_sent)) -> globally ~ack_sent + conjecture l2s_saved -> (($l2s_s. globally ~data_sent) -> globally ~data_sent) + conjecture l2s_saved -> (($l2s_s. globally ~ack_sent) -> globally ~ack_sent) + conjecture l2s_saved & ~($l2s_w. sender_scheduled) -> ~($l2s_s. globally ~data_sent) + conjecture l2s_saved & ~($l2s_w. receiver_scheduled) -> ~($l2s_s. globally ~ack_sent) + + # the messages that exist and show the difference between current state and saved state + conjecture l2s_saved & ( + (globally (~sender_bit & ~receiver_bit)) | + (globally (~sender_bit & receiver_bit)) | + (globally ( sender_bit & ~receiver_bit)) | + (globally ( sender_bit & receiver_bit)) + ) & ~($l2s_w. data_received) & ~(globally ~data_sent) & (($l2s_s. sender_bit) <-> ($l2s_s. receiver_bit)) -> + exists M:data_msg_t. l2s_a(M) & ~data_msg.le(M,M) & ($l2s_s X,Y. data_msg.le(X,Y))(M,M) & (dbit(M) <-> ~receiver_bit) + + conjecture l2s_saved & ( + (globally (~sender_bit & ~receiver_bit)) | + (globally (~sender_bit & receiver_bit)) | + (globally ( sender_bit & ~receiver_bit)) | + (globally ( sender_bit & receiver_bit)) + ) & ~($l2s_w. ack_received) & ~(globally ~ack_sent) & (($l2s_s. sender_bit) <-> ~($l2s_s. receiver_bit)) -> + exists A:ack_msg_t. l2s_a(A) & ~ack_msg.le(A,A) & ($l2s_s X,Y. ack_msg.le(X,Y))(A,A) & (abit(A) <-> ~sender_bit) + + # index shows the difference between current state and + # saved state (just the same conjecture 4 times, for the + # possible valuations of saved sender and receiver bits + conjecture l2s_saved & + ~(globally (~sender_bit & ~receiver_bit)) & + ~($l2s_s. sender_bit) & ~($l2s_s. receiver_bit) & + ~($l2s_w. ~(~sender_bit & ~receiver_bit)) + -> ( sender_i ~= ($l2s_s. sender_i) | receiver_i ~= ($l2s_s. receiver_i) ) + conjecture l2s_saved & + ~(globally (~sender_bit & receiver_bit)) & + ~($l2s_s. sender_bit) & ($l2s_s. receiver_bit) & + ~($l2s_w. ~(~sender_bit & receiver_bit)) + -> ( sender_i ~= ($l2s_s. sender_i) | receiver_i ~= ($l2s_s. receiver_i) ) + conjecture l2s_saved & + ~(globally ( sender_bit & ~receiver_bit)) & + ($l2s_s. sender_bit) & ~($l2s_s. receiver_bit) & + ~($l2s_w. ~( sender_bit & ~receiver_bit)) + -> ( sender_i ~= ($l2s_s. sender_i) | receiver_i ~= ($l2s_s. receiver_i) ) + conjecture l2s_saved & + ~(globally ( sender_bit & receiver_bit)) & + ($l2s_s. sender_bit) & ($l2s_s. receiver_bit) & + ~($l2s_w. ~( sender_bit & receiver_bit)) + -> ( sender_i ~= ($l2s_s. sender_i) | receiver_i ~= ($l2s_s. receiver_i) ) + } + } with this +} diff --git a/examples/liveness/ticket.ivy b/examples/liveness/ticket.ivy new file mode 100644 index 000000000..f2b7175d1 --- /dev/null +++ b/examples/liveness/ticket.ivy @@ -0,0 +1,279 @@ +#lang ivy1.6 + +################################################################################ +# A liveness proof of the ticket protocol +################################################################################ + + +################################################################################ +# Module for axiomatizing a total order +# +################################################################################ + +module total_order(r) = { + axiom r(X,X) # Reflexivity + axiom r(X, Y) & r(Y, Z) -> r(X, Z) # Transitivity + axiom r(X, Y) & r(Y, X) -> X = Y # Anti-symmetry + axiom r(X, Y) | r(Y, X) # Totality +} + +################################################################################ +# +# Types, relations and functions describing the state +# +################################################################################ + +object ticket_protocol = { + + ################################################################################ + # + # The protocol description + # + ################################################################################ + + type thread + type ticket + + relation le(X:ticket, Y:ticket) + instantiate total_order(le) + individual zero:ticket + axiom forall X. le(zero, X) + + relation pc1(T:thread) + relation pc2(T:thread) + relation pc3(T:thread) + + individual service:ticket + individual next_ticket:ticket + relation m(T:thread, K:ticket) # use relation and not a function to be in EPR + + relation scheduled(T:thread) + + init pc1(T) + init ~pc2(T) + init ~pc3(T) + init service = zero + init next_ticket = zero + init m(T,K) <-> K = zero + init ~scheduled(T) + + ################################################################################ + # + # Protocol actions + # + ################################################################################ + + action succ(x:ticket) returns (y:ticket) = { + assume ~le(y,x) & forall Z:ticket. ~le(Z,x) -> le(y,Z) + } + + action step12(t:thread,k1:ticket, k2:ticket) = { + assume pc1(t); + assume k1 = next_ticket; + assume k2 = succ(k1); + m(t,K) := K = k1; + next_ticket := k2; + pc1(t) := false; + pc2(t) := true; + scheduled(T) := T = t; + } + + action step22(t:thread, k1:ticket) = { + assume pc2(t); + assume m(t,k1); + assume ~le(k1,service); + # stay in pc2 + scheduled(T) := T = t; + } + + action step23(t:thread, k1:ticket) = { + assume pc2(t); + assume m(t,k1); + assume le(k1,service); + pc2(t) := false; + pc3(t) := true; + scheduled(T) := T = t; + } + + action step31(t:thread, k1:ticket, k2:ticket) = { + assume pc3(t); + assume k1 = service; + assume k2 = succ(k1); + service := k2; + pc3(t) := false; + pc1(t) := true; + scheduled(T) := T = t; + } + + export step12 + export step22 + export step23 + export step31 + + ################################################################################ + # + # Conjectures for proving safety (also helps for liveness) + # + ################################################################################ + + # safety property + conjecture [safety] pc3(T1) & pc3(T2) -> T1 = T2 + # basic + conjecture pc1(T) | pc2(T) | pc3(T) + conjecture ~pc1(T) | ~pc2(T) + conjecture ~pc1(T) | ~pc3(T) + conjecture ~pc2(T) | ~pc3(T) + conjecture m(T,K1) & m(T,K2) -> K1 = K2 + # inductive invariant for proving safety + conjecture next_ticket = zero -> m(T,zero) + conjecture next_ticket ~= zero & m(T,M) -> ~le(next_ticket,M) + conjecture (pc2(T) | pc3(T)) -> next_ticket ~= zero + conjecture m(T1,M) & m(T2,M) & M ~= zero -> T1 = T2 + conjecture pc2(T) & m(T,M) -> le(service,M) + conjecture pc3(T) -> m(T,service) + conjecture le(service,next_ticket) + conjecture ~(~pc1(T1) & ~pc1(T2) & m(T1,zero) & m(T2,zero) & T1 ~= T2) + + + ################################################################################ + # + # Temporal property and its proof + # + ################################################################################ + + isolate tp1 = { + + individual sk0:thread # witness for the formula (exists T0. ~(globally ~(pc2(T0) & globally ~pc3(T0)))) + + object spec = { + temporal property [nonstarvation] ( + ((exists T0. ~(globally ~(pc2(T0) & globally ~pc3(T0)))) -> ~(globally ~(pc2(sk0) & globally ~pc3(sk0)))) & + (forall T1. globally (eventually scheduled(T1))) + ) -> (forall T2. globally ~(pc2(T2) & globally ~pc3(T2))) + # the following "more natural" formulation doesn't work: + # (forall T:thread. globally (eventually scheduled(T))) -> (globally (pc2(sk0) -> (eventually pc3(sk0)))) + } + + ################################################################################ + # + # The liveness to safety construction introduces the following symbols: + # + # relation l2s_waiting + # relation l2s_frozen + # relation l2s_saved + # + # relation l2s_d(X) - polymorphic relation for d + # relation l2s_a(X) - polymorphic relation for a + # + # named binder: $l2s_w - for waiting on fairness constraint + # named binder: $l2s_s - for saved state + # + ################################################################################ + + ################################################################################ + # + # Conjectures for proving liveness + # + ################################################################################ + + object impl = { + # all safety conjectures for saved state (in the future, will be autumatically added) + conjecture l2s_saved -> ( ($l2s_s T. pc3(T))(T1) & ($l2s_s T. pc3(T))(T2) -> T1 = T2 ) + conjecture l2s_saved -> ( ($l2s_s T. pc1(T))(T) | ($l2s_s T. pc2(T))(T) | ($l2s_s T. pc3(T))(T) ) + conjecture l2s_saved -> ( ~($l2s_s T. pc1(T))(T) | ~($l2s_s T. pc2(T))(T) ) + conjecture l2s_saved -> ( ~($l2s_s T. pc1(T))(T) | ~($l2s_s T. pc3(T))(T) ) + conjecture l2s_saved -> ( ~($l2s_s T. pc2(T))(T) | ~($l2s_s T. pc3(T))(T) ) + conjecture l2s_saved -> ( ($l2s_s T,K. m(T,K))(T,K1) & ($l2s_s T,K. m(T,K))(T,K2) -> K1 = K2 ) + conjecture l2s_saved -> ( ($l2s_s. next_ticket) = ($l2s_s. zero) -> ($l2s_s T,K. m(T,K))(T,($l2s_s. zero)) ) + conjecture l2s_saved -> ( ($l2s_s. next_ticket) ~= ($l2s_s. zero) & ($l2s_s T,K. m(T,K))(T,M) -> ~($l2s_s K1,K2. le(K1,K2))(($l2s_s. next_ticket),M) ) + conjecture l2s_saved -> ( (($l2s_s T. pc2(T))(T) | ($l2s_s T. pc3(T))(T)) -> ($l2s_s. next_ticket) ~= ($l2s_s. zero) ) + conjecture l2s_saved -> ( ($l2s_s T,K. m(T,K))(T1,M) & ($l2s_s T,K. m(T,K))(T2,M) & M ~= ($l2s_s. zero) -> T1 = T2 ) + conjecture l2s_saved -> ( ($l2s_s T. pc2(T))(T) & ($l2s_s T,K. m(T,K))(T,M) -> ($l2s_s K1,K2. le(K1,K2))(($l2s_s. service),M) ) + conjecture l2s_saved -> ( ($l2s_s T. pc3(T))(T) -> ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) ) + conjecture l2s_saved -> ( ($l2s_s K1,K2. le(K1,K2))(($l2s_s. service),($l2s_s. next_ticket)) ) + conjecture l2s_saved -> ( ~(~($l2s_s T. pc1(T))(T1) & ~($l2s_s T. pc1(T))(T2) & ($l2s_s T,K. m(T,K))(T1,($l2s_s. zero)) & ($l2s_s T,K. m(T,K))(T2,($l2s_s. zero)) & T1 ~= T2) ) + + # some things never change (maybe this should also be detected automatically) + conjecture l2s_saved -> (le(X,Y) <-> ($l2s_s K1,K2. le(K1,K2))(X,Y)) + conjecture l2s_saved -> (zero = ($l2s_s. zero)) + conjecture l2s_saved -> (sk0 = ($l2s_s. sk0)) + + # basic temporal information + conjecture forall T:thread. globally eventually scheduled(T) + conjecture ~(globally ~(pc2(sk0) & globally ~pc3(sk0))) + conjecture ~($l2s_w. (pc2(sk0) & globally ~pc3(sk0))) <-> (pc2(sk0) & globally ~pc3(sk0)) # TODO: why does this make sense immediately after the save point? + + # TODO: look into why these are not working: + # conjecture ~(globally (pc2(sk0) -> eventually pc3(sk0))) + # conjecture eventually (pc2(sk0) & globally ~pc3(sk0)) + # conjecture ~($l2s_w. ~(pc2(sk0) -> (eventually pc3(sk0)))) -> ~(pc2(sk0) -> (eventually pc3(sk0))) + # conjecture ($l2s_w. (~(pc2(sk0) -> (eventually pc3(sk0))))) <-> (~(~(pc2(sk0) -> (eventually pc3(sk0))))) + # conjecture eventually (pc2(sk0) & (globally ~pc3(sk0))) + # conjecture ($l2s_w T. (pc2(T) & (globally ~pc3(T))))(sk0) -> ~(pc2(sk0) & (globally ~pc3(sk0))) + # conjecture ~($l2s_w T. (pc2(T) & (globally ~pc3(T))))(sk0) -> (pc2(sk0) & (globally ~pc3(sk0))) + + # more basic temporal properties, now in connection with monitor state + conjecture l2s_frozen -> (pc2(sk0) & globally ~pc3(sk0)) + conjecture l2s_saved -> (pc2(sk0) & globally ~pc3(sk0)) + conjecture l2s_saved -> ($l2s_s T,K. m(T,K))(sk0,K) <-> m(sk0,K) + conjecture l2s_saved -> le( ($ l2s_s . service) , service) + conjecture l2s_saved -> le( ($ l2s_s . next_ticket) , next_ticket) + + # more properties of reachable protocol states + conjecture pc1(T) & m(T,M) & M ~= zero -> ~le(service, M) + conjecture forall K:ticket. ~le(next_ticket, K) & le(service, K) -> exists T:thread. m(T,K) & ~pc1(T) + conjecture exists M. m(sk0, M) + # and their saved counterpars (to be automatically added...) + conjecture l2s_saved -> ( + ($l2s_s X. pc1(X))(T) & ($l2s_s X,Y. m(X,Y))(T,M) & M ~= ($l2s_s. zero) -> ~($l2s_s X,Y. le(X,Y))(($l2s_s. service), M) + ) + conjecture l2s_saved -> ( + forall KK:ticket. ~($l2s_s X,Y. le(X,Y))(($l2s_s. next_ticket), KK) & ($l2s_s X,Y. le(X,Y))(($l2s_s. service), KK) -> + exists TT:thread. ($l2s_s T,K. m(T,K))(TT,KK) & ~($l2s_s T. pc1(T))(TT) + ) + conjecture l2s_saved -> ( + exists M. ($l2s_s T,K. m(T,K))(($l2s_s. sk0), M) + ) + + # l2s_d is large enough + conjecture l2s_d(sk0) + conjecture ~pc1(T) -> l2s_d(T) + conjecture le(K,next_ticket) -> l2s_d(K) + # l2s_a is large enough + conjecture ~l2s_waiting -> l2s_a(sk0) + conjecture ~l2s_waiting & m(T,K) & m(sk0,K0) & ~le(K0,K) & ~pc1(T) -> l2s_a(T) + conjecture ~l2s_waiting & m(sk0,K0) & le(K,K0) -> l2s_a(K) + + # thread that have not been scheduled have not changed + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T. pc1(T))(T) <-> pc1(T)) + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T. pc2(T))(T) <-> pc2(T)) + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T. pc3(T))(T) <-> pc3(T)) + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T,K. m(T,K))(T,K) <-> m(T,K)) + + # the thread that must advance - the thread that had the service as its local ticket at the save point + conjecture ( + l2s_saved & + ($l2s_s T,K. m(T,K)) (T,($l2s_s. service)) & + ~($l2s_w X. scheduled(X))(T) & + ($l2s_s T. pc2(T))(T) & + m(T,K) & + m(sk0,K0) + ) -> ( + (pc1(T) & K = ($l2s_s. service)) | + (pc2(T) & ~le(K,K0)) | + (pc3(T) & K = ($l2s_s. service)) + ) + conjecture ( + l2s_saved & + ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & + ~($l2s_w T. scheduled(T))(T) & + ($l2s_s T. pc3(T))(T) & + m(T,K) & + m(sk0,K0) + ) -> ( + (pc1(T) & K = ($l2s_s. service) & ~le(service, ($l2s_s. service))) | + (pc2(T) & ~le(K,K0)) + ) + } + } with this +} diff --git a/examples/liveness/ticket_nested.ivy b/examples/liveness/ticket_nested.ivy new file mode 100644 index 000000000..895f631c7 --- /dev/null +++ b/examples/liveness/ticket_nested.ivy @@ -0,0 +1,330 @@ +#lang ivy1.6 + +################################################################################ +# A liveness proof of the nested ticket protocol +################################################################################ + + +################################################################################ +# Module for axiomatizing a total order +# +################################################################################ + +module total_order(r) = { + axiom r(X,X) # Reflexivity + axiom r(X, Y) & r(Y, Z) -> r(X, Z) # Transitivity + axiom r(X, Y) & r(Y, X) -> X = Y # Anti-symmetry + axiom r(X, Y) | r(Y, X) # Totality +} + +################################################################################ +# +# Types, relations and functions describing the state +# +################################################################################ + +object ticket_protocol = { + + ################################################################################ + # + # The protocol description + # + ################################################################################ + + type thread + type ticket + + relation le(X:ticket, Y:ticket) + instantiate total_order(le) + individual zero:ticket + axiom forall X. le(zero, X) + + relation pc1(T:thread) + relation pc2(T:thread) + relation pc3(T:thread) + + individual service:ticket + individual next_ticket:ticket + relation m(T:thread, K:ticket) # use relation and not a function to be in EPR + relation c(T:thread, K:ticket) # use relation and not a function to be in EPR + + relation scheduled(T:thread) + + init pc1(T) + init ~pc2(T) + init ~pc3(T) + init service = zero + init next_ticket = zero + init m(T,K) <-> K = zero + init c(T,K) <-> K = zero + init ~scheduled(T) + + ################################################################################ + # + # Protocol actions + # + ################################################################################ + + action step12(t:thread,k1:ticket, k2:ticket) = { + assume pc1(t); + assume k1 = next_ticket; + assume ~le(k2,k1) & forall Z:ticket. ~le(Z,k1) -> le(k2,Z); + m(t,K) := K = k1; + next_ticket := k2; + pc1(t) := false; + pc2(t) := true; + scheduled(T) := T = t; + } + + action step22(t:thread, k1:ticket) = { + assume pc2(t); + assume m(t,k1); + assume ~le(k1,service); + # stay in pc2 + scheduled(T) := T = t; + } + + action step23(t:thread, k1:ticket, k2:ticket) = { + assume pc2(t); + assume m(t,k1); + assume le(k1,service); + pc2(t) := false; + pc3(t) := true; + c(t,K) := K = next_ticket; # TODO: change this to k2, and change the update of d to account for a downward finite total order + scheduled(T) := T = t; + } + + action step33(t:thread, k1:ticket, k2:ticket) = { + assume pc3(t); + assume c(t,k2); + assume ~le(k2,k1) & forall Z:ticket. ~le(Z,k1) -> le(k2,Z); + c(t,K) := K = k1; + # stay in pc3 + scheduled(T) := T = t; + } + + action step31(t:thread, k1:ticket, k2:ticket) = { + assume pc3(t); + assume c(t,zero); + assume k1 = service; + assume ~le(k2,k1) & forall Z:ticket. ~le(Z,k1) -> le(k2,Z); + service := k2; + pc3(t) := false; + pc1(t) := true; + scheduled(T) := T = t; + } + + export step12 + export step22 + export step23 + export step33 + export step31 + + ################################################################################ + # + # Conjectures for proving safety (also helps for liveness) + # + ################################################################################ + + # safety property + conjecture [safety] pc3(T1) & pc3(T2) -> T1 = T2 + # basic + conjecture pc1(T) | pc2(T) | pc3(T) + conjecture ~pc1(T) | ~pc2(T) + conjecture ~pc1(T) | ~pc3(T) + conjecture ~pc2(T) | ~pc3(T) + conjecture m(T,K1) & m(T,K2) -> K1 = K2 + conjecture c(T,K1) & c(T,K2) -> K1 = K2 + # inductive invariant for proving safety + conjecture next_ticket = zero -> m(T,zero) + conjecture next_ticket ~= zero & m(T,M) -> ~le(next_ticket,M) + conjecture (pc2(T) | pc3(T)) -> next_ticket ~= zero + conjecture m(T1,M) & m(T2,M) & M ~= zero -> T1 = T2 + conjecture pc2(T) & m(T,M) -> le(service,M) + conjecture pc3(T) -> m(T,service) + conjecture le(service,next_ticket) + conjecture ~(~pc1(T1) & ~pc1(T2) & m(T1,zero) & m(T2,zero) & T1 ~= T2) + + ################################################################################ + # + # Temporal property and its proof + # + ################################################################################ + + isolate tp1 = { + + ################################################################################ + # + # Liveness proof + # + # The property to prove is: + # (forall T. globally eventually scheduled(T)) -> (forall T. globally (pc2(T) -> eventually pc3(T))) + # + # The set A of formulas containts the following formulas (and their subformulas): + # the property: (forall T. globally ~ globally ~scheduled(T)) -> (forall T. globally (~pc2(T) | ~ globally ~pc3(T))) + # additional formula: forall T. globally ~globally pc3(T) + # + # We also use "witness constants" for the following formulas: + # + # + ################################################################################ + + + individual sk0:thread # witness for the formula (exists T0. ~(globally ~(pc2(T0) & globally ~pc3(T0)))) + individual sk1:thread # witness for the formula (exists T1. ~(globally ~(globally pc3(T1)))) + + object spec = { + temporal property [nonstravation] ( + ((exists T0. ~(globally ~(pc2(T0) & globally ~pc3(T0)))) -> ~(globally ~(pc2(sk0) & globally ~pc3(sk0)))) & + ((exists T1. ~(globally ~(globally pc3(T1)))) -> (~(globally ~(globally pc3(sk1))))) & + (forall T2. globally (eventually scheduled(T2))) + ) -> (forall T3. globally ~(pc2(T3) & globally ~pc3(T3))) + } + + ################################################################################ + # + # The liveness to safety construction introduces the following symbols: + # + # relation l2s_waiting + # relation l2s_frozen + # relation l2s_saved + # + # relation l2s_d(X) - polymorphic relation for d + # relation l2s_a(X) - polymorphic relation for a + # + # named binder: $l2s_w - for waiting on fairness constraint + # named binder: $l2s_s - for saved state + # + ################################################################################ + + ################################################################################ + # + # Conjectures for proving liveness + # + ################################################################################ + + object impl = { + # all safety conjectures for saved state (in the future, will be autumatically added) + conjecture l2s_saved -> ( ($l2s_s T. pc3(T))(T1) & ($l2s_s T. pc3(T))(T2) -> T1 = T2 ) + conjecture l2s_saved -> ( ($l2s_s T. pc1(T))(T) | ($l2s_s T. pc2(T))(T) | ($l2s_s T. pc3(T))(T) ) + conjecture l2s_saved -> ( ~($l2s_s T. pc1(T))(T) | ~($l2s_s T. pc2(T))(T) ) + conjecture l2s_saved -> ( ~($l2s_s T. pc1(T))(T) | ~($l2s_s T. pc3(T))(T) ) + conjecture l2s_saved -> ( ~($l2s_s T. pc2(T))(T) | ~($l2s_s T. pc3(T))(T) ) + conjecture l2s_saved -> ( ($l2s_s T,K. m(T,K))(T,K1) & ($l2s_s T,K. m(T,K))(T,K2) -> K1 = K2 ) + conjecture l2s_saved -> ( ($l2s_s T,K. c(T,K))(T,K1) & ($l2s_s T,K. c(T,K))(T,K2) -> K1 = K2 ) + conjecture l2s_saved -> ( ($l2s_s. next_ticket) = ($l2s_s. zero) -> ($l2s_s T,K. m(T,K))(T,($l2s_s. zero)) ) + conjecture l2s_saved -> ( ($l2s_s. next_ticket) ~= ($l2s_s. zero) & ($l2s_s T,K. m(T,K))(T,M) -> ~($l2s_s K1,K2. le(K1,K2))(($l2s_s. next_ticket),M) ) + conjecture l2s_saved -> ( (($l2s_s T. pc2(T))(T) | ($l2s_s T. pc3(T))(T)) -> ($l2s_s. next_ticket) ~= ($l2s_s. zero) ) + conjecture l2s_saved -> ( ($l2s_s T,K. m(T,K))(T1,M) & ($l2s_s T,K. m(T,K))(T2,M) & M ~= ($l2s_s. zero) -> T1 = T2 ) + conjecture l2s_saved -> ( ($l2s_s T. pc2(T))(T) & ($l2s_s T,K. m(T,K))(T,M) -> ($l2s_s K1,K2. le(K1,K2))(($l2s_s. service),M) ) + conjecture l2s_saved -> ( ($l2s_s T. pc3(T))(T) -> ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) ) + conjecture l2s_saved -> ( ($l2s_s K1,K2. le(K1,K2))(($l2s_s. service),($l2s_s. next_ticket)) ) + conjecture l2s_saved -> ( ~(~($l2s_s T. pc1(T))(T1) & ~($l2s_s T. pc1(T))(T2) & ($l2s_s T,K. m(T,K))(T1,($l2s_s. zero)) & ($l2s_s T,K. m(T,K))(T2,($l2s_s. zero)) & T1 ~= T2) ) + + # some things never change (maybe this should also be detected automatically) + conjecture l2s_saved -> (le(X,Y) <-> ($l2s_s K1,K2. le(K1,K2))(X,Y)) + conjecture l2s_saved -> (zero = ($l2s_s. zero)) + conjecture l2s_saved -> (sk0 = ($l2s_s. sk0)) + conjecture l2s_saved -> (sk1 = ($l2s_s. sk1)) + + # basic temporal information + conjecture forall T:thread. globally eventually scheduled(T) + conjecture ~(globally ~(pc2(sk0) & globally ~pc3(sk0))) + conjecture ~($l2s_w. (pc2(sk0) & globally ~pc3(sk0))) <-> (pc2(sk0) & globally ~pc3(sk0)) + + # more basic temporal properties, now in connection with monitor state + conjecture l2s_frozen -> (pc2(sk0) & globally ~pc3(sk0)) + conjecture l2s_saved -> (pc2(sk0) & globally ~pc3(sk0)) + conjecture l2s_saved -> ($l2s_s T,K. m(T,K))(sk0,K) <-> m(sk0,K) + conjecture l2s_saved -> le( ($ l2s_s . service) , service) + conjecture l2s_saved -> le( ($ l2s_s . next_ticket) , next_ticket) + + # temporal information regarding sk1 + conjecture ~(globally ~(globally pc3(sk1))) | (forall T. globally (~globally pc3(T))) + conjecture ~(globally ~(globally pc3(sk1))) | (forall T. (~globally pc3(T))) + conjecture (~($l2s_w. (globally pc3(sk1)))) -> ( + (globally ~(globally pc3(sk1))) | + (globally pc3(sk1)) + ) + conjecture ~(globally ~(globally pc3(sk1))) & ~l2s_waiting -> globally pc3(sk1) + # more information about sk1 + conjecture exists K. c(sk1,K) + conjecture l2s_saved -> exists K. ($l2s_s T,K. c(T,K))(sk1,K) + conjecture (globally pc3(sk1)) & l2s_saved & c(sk1,K1) & ($l2s_s T,K. c(T,K))(sk1,K2) -> le(K1,K2) + conjecture (globally pc3(sk1)) & l2s_saved & c(sk1,K1) & ($l2s_s T,K. c(T,K))(sk1,K2) & ~($l2s_w X. scheduled(X))(sk1) -> K1 ~= K2 + + # more properties of reachable protocol states + conjecture pc1(T) & m(T,M) & M ~= zero -> ~le(service, M) + conjecture forall K:ticket. ~le(next_ticket, K) & le(service, K) -> exists T:thread. m(T,K) & ~pc1(T) + conjecture exists M. m(sk0, M) + # and their saved counterpars (to be automatically added...) + conjecture l2s_saved -> ( + ($l2s_s X. pc1(X))(T) & ($l2s_s X,Y. m(X,Y))(T,M) & M ~= ($l2s_s. zero) -> ~($l2s_s X,Y. le(X,Y))(($l2s_s. service), M) + ) + conjecture l2s_saved -> ( + forall KK:ticket. ~($l2s_s X,Y. le(X,Y))(($l2s_s. next_ticket), KK) & ($l2s_s X,Y. le(X,Y))(($l2s_s. service), KK) -> + exists TT:thread. ($l2s_s T,K. m(T,K))(TT,KK) & ~($l2s_s T. pc1(T))(TT) + ) + conjecture l2s_saved -> ( + exists M. ($l2s_s T,K. m(T,K))(($l2s_s. sk0), M) + ) + + # l2s_d is large enough + conjecture l2s_d(sk0) + conjecture l2s_d(sk1) + conjecture ~pc1(T) -> l2s_d(T) + conjecture le(K,next_ticket) -> l2s_d(K) + conjecture c(sk1,K0) & le(K,K0) -> l2s_d(K) + # l2s_a is large enough + conjecture ~l2s_waiting -> l2s_a(sk0) + conjecture ~l2s_waiting -> l2s_a(sk1) + conjecture ~l2s_waiting & m(T,K) & m(sk0,K0) & ~le(K0,K) & ~pc1(T) -> l2s_a(T) + conjecture ~l2s_waiting & m(sk0,K0) & le(K,K0) -> l2s_a(K) + conjecture ~l2s_waiting & (globally pc3(sk1)) & c(sk1,K0) & le(K,K0) -> l2s_a(K) + + # thread that have not been scheduled have not changed + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T. pc1(T))(T) <-> pc1(T)) + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T. pc2(T))(T) <-> pc2(T)) + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T. pc3(T))(T) <-> pc3(T)) + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T,K. m(T,K))(T,K) <-> m(T,K)) + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T,K. c(T,K))(T,K) <-> c(T,K)) + + # the thread that must advance - the thread that had the service as its local ticket at the save point + conjecture ( + l2s_saved & + ($l2s_s T,K. m(T,K)) (T,($l2s_s. service)) & + ~($l2s_w X. scheduled(X))(T) & + ($l2s_s T. pc2(T))(T) & + m(T,K) & + m(sk0,K0) + ) -> ( + (pc1(T) & K = ($l2s_s. service)) | + (pc2(T) & ~le(K,K0)) | + (pc3(T) & K = ($l2s_s. service)) + ) + conjecture ( + l2s_saved & + ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & + ($l2s_s T. pc3(T))(T) & + m(T,K) & + m(sk0,K0) + ) -> ( + (pc3(T) & K = ($l2s_s. service) & K = service) | + (pc1(T) & K = ($l2s_s. service) & ~le(service, ($l2s_s. service))) | + (pc2(T) & ~le(K,K0)) + ) + conjecture ( + l2s_saved & + ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & + ($l2s_s T. pc3(T))(T) & + m(T,K) & + m(sk0,K0) & + ~($l2s_w T. ~pc3(T))(T) & + ~(globally pc3(T)) + ) -> ( + (pc1(T) & K = ($l2s_s. service) & ~le(service, ($l2s_s. service))) | + (pc2(T) & ~le(K,K0)) + ) + } + } with this +} From 13c3430289b06d80cc79e547fe8a2d58baf21f7a Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Thu, 1 Mar 2018 03:18:23 +0200 Subject: [PATCH 14/17] updated test/test_liveness.ivy (it's the same as examples/liveness/ticket.ivy) --- test/test_liveness.ivy | 246 ++++++++++++++--------------------------- 1 file changed, 86 insertions(+), 160 deletions(-) diff --git a/test/test_liveness.ivy b/test/test_liveness.ivy index c0e4efbfb..f2b7175d1 100644 --- a/test/test_liveness.ivy +++ b/test/test_liveness.ivy @@ -27,13 +27,7 @@ object ticket_protocol = { ################################################################################ # - # The protocol itself, together with encoding the fairness - # constraints and the negation of the liveness property - # - # property: forall T:thread. G. pc2(T) -> F. pc3(T) - # fairness: forall T:thread. G. F. last_scheduled(T) - # ~property: exists T:thread. F. (pc2(T) & G. ~pc3(T)) - # Sk(~property): F. (pc2(t0) & G. ~pc3(t0)) + # The protocol description # ################################################################################ @@ -52,10 +46,8 @@ object ticket_protocol = { individual service:ticket individual next_ticket:ticket relation m(T:thread, K:ticket) # use relation and not a function to be in EPR - # individual testfunction(K:ticket):thread # use relation and not a function to be in EPR - individual t0:thread - relation last_scheduled(T:thread) + relation scheduled(T:thread) init pc1(T) init ~pc2(T) @@ -63,48 +55,7 @@ object ticket_protocol = { init service = zero init next_ticket = zero init m(T,K) <-> K = zero - init ~last_scheduled(T) - - # temporal specification - # temporal axiom [fairness] forall T:thread. globally eventually last_scheduled(T) - # temporal property [mutualexclusion] globally forall T1,T2. pc3(T1) & pc3(T2) -> T1 = T2 - # temporal property [nonstravation] forall T:thread. globally (pc2(T) -> eventually pc3(T)) # TODO parsing precedence bug - #temporal property [nonstravation] globally (pc2(t0) -> eventually pc3(t0)) # TODO parsing precedence bug - # temporal property [nonstravation] globally ~(pc2(t0) & globally ~pc3(t0)) # TODO parsing precedence bug - - # proof of nonstravation by l2s { - # conjecture ($l2s_w. (pc2(t0) & (globally ~pc3(t0)))) <-> ~(pc2(t0) & (globally ~pc3(t0))) - # conjecture l2s_frozen -> (pc2(t0) & (globally ~pc3(t0))) - # conjecture l2s_saved -> (pc2(t0) & (globally ~pc3(t0))) - #axiom [fairness] forall T:thread. globally eventually pc1(T) - # temporal axiom [fairness] forall T:thread. globally eventually ( - # (exists K1,K2. after step12(T,K1,K2)) | - # (exists K1. after step12(T,K1)) | - # (exists K1. after step12(T,K1)) | - # (exists K1,K2. after step12(T,K1,K2)) - # ) - # # naming for use in the inductive invariant - # let t0 = nonstravation.T - # let request_flag = pc2(t0) & globally ~pc3(t0) - # let last_scheduled(T) = ( - # (exists K1,K2. after step12(T,K1,K2)) | - # (exists K1. after step12(T,K1)) | - # (exists K1. after step12(T,K1)) | - # (exists K1,K2. after step12(T,K1,K2)) - # ) - # temporal property (forall T:thread. G. F. last_scheduled(T)) -> (forall T:thread. G. pc2(T) -> F. pc3(T)) - # fair action step12 - # fair action step22 - # fair action step23 - # fair action step31 - - # Axioms we may need (not for ticket, in general): - # (forall x. phi) -> (exists x. phi) - # (exists x. eventually phi) -> (eventually exists x. phi) - # - # these may be needed when x isn't in d, and we know forall - # x. eventually phi, and want to wait until (exists x. phi). - + init ~scheduled(T) ################################################################################ # @@ -119,13 +70,12 @@ object ticket_protocol = { action step12(t:thread,k1:ticket, k2:ticket) = { assume pc1(t); assume k1 = next_ticket; - # assume k2 = succ(k1); # bug: succ is in mod.public_actions - assume ~le(k2,k1) & forall Z:ticket. ~le(Z,k1) -> le(k2,Z); + assume k2 = succ(k1); m(t,K) := K = k1; next_ticket := k2; pc1(t) := false; pc2(t) := true; - last_scheduled(T) := T = t; + scheduled(T) := T = t; } action step22(t:thread, k1:ticket) = { @@ -133,7 +83,7 @@ object ticket_protocol = { assume m(t,k1); assume ~le(k1,service); # stay in pc2 - last_scheduled(T) := T = t; + scheduled(T) := T = t; } action step23(t:thread, k1:ticket) = { @@ -142,18 +92,17 @@ object ticket_protocol = { assume le(k1,service); pc2(t) := false; pc3(t) := true; - last_scheduled(T) := T = t; + scheduled(T) := T = t; } action step31(t:thread, k1:ticket, k2:ticket) = { assume pc3(t); assume k1 = service; - # assume k2 = succ(k1); # bug: succ is in mod.public_actions - assume ~le(k2,k1) & forall Z:ticket. ~le(Z,k1) -> le(k2,Z); + assume k2 = succ(k1); service := k2; pc3(t) := false; pc1(t) := true; - last_scheduled(T) := T = t; + scheduled(T) := T = t; } export step12 @@ -167,20 +116,14 @@ object ticket_protocol = { # ################################################################################ - # for testing - # conjecture true | ($l2s_s X. globally eventually ~pc1(X))(Y) - # conjecture true | ($l2s_s X. globally ~globally pc1(X))(Y) - + # safety property + conjecture [safety] pc3(T1) & pc3(T2) -> T1 = T2 # basic conjecture pc1(T) | pc2(T) | pc3(T) conjecture ~pc1(T) | ~pc2(T) conjecture ~pc1(T) | ~pc3(T) conjecture ~pc2(T) | ~pc3(T) conjecture m(T,K1) & m(T,K2) -> K1 = K2 - - # safety property - conjecture pc3(T1) & pc3(T2) -> T1 = T2 - # inductive invariant for proving safety conjecture next_ticket = zero -> m(T,zero) conjecture next_ticket ~= zero & m(T,M) -> ~le(next_ticket,M) @@ -192,60 +135,55 @@ object ticket_protocol = { conjecture ~(~pc1(T1) & ~pc1(T2) & m(T1,zero) & m(T2,zero) & T1 ~= T2) + ################################################################################ + # + # Temporal property and its proof + # + ################################################################################ + isolate tp1 = { + individual sk0:thread # witness for the formula (exists T0. ~(globally ~(pc2(T0) & globally ~pc3(T0)))) + object spec = { - temporal property [nonstravation] (forall T:thread. globally (~globally (~last_scheduled(T)))) -> - (globally ~(pc2(t0) & globally ~pc3(t0))) + temporal property [nonstarvation] ( + ((exists T0. ~(globally ~(pc2(T0) & globally ~pc3(T0)))) -> ~(globally ~(pc2(sk0) & globally ~pc3(sk0)))) & + (forall T1. globally (eventually scheduled(T1))) + ) -> (forall T2. globally ~(pc2(T2) & globally ~pc3(T2))) + # the following "more natural" formulation doesn't work: + # (forall T:thread. globally (eventually scheduled(T))) -> (globally (pc2(sk0) -> (eventually pc3(sk0)))) } ################################################################################ # # The liveness to safety construction introduces the following symbols: # - # relation nonstravation.l2s_waiting - # relation nonstravation.l2s_frozen - # relation nonstravation.l2s_saved + # relation l2s_waiting + # relation l2s_frozen + # relation l2s_saved # - # relation nonstravation.l2s_d_thread(T:thread) - # relation nonstravation.l2s_d_ticket(K:ticket) + # relation l2s_d(X) - polymorphic relation for d + # relation l2s_a(X) - polymorphic relation for a # - # relation nonstravation.l2s_a_thread(T:thread) - # relation nonstravation.l2s_a_ticket(K:ticket) - # - # relation nonstravation.l2s_w[phi] for phi in FO-LTL(original vocabulary) - # relation nonstravation.l2s_wa[A] for A in fair-actions = {step12(T,K1,K2), - # step22(T,K1), - # step23(T,K1), - # step31(T,K1,K2)} - # - # relation nonstravation.l2s_s.pc1(T:thread) - # relation nonstravation.l2s_s.pc2(T:thread) - # relation nonstravation.l2s_s.pc3(T:thread) - # individual nonstravation.l2s_s.service : ticket - # individual nonstravation.l2s_s.next_ticket : ticket - # relation nonstravation.l2s_s.m(T:thread, K:ticket) - # relation nonstravation.l2s_s.[phi] for phi in FO-LTL(original vocabulary) + # named binder: $l2s_w - for waiting on fairness constraint + # named binder: $l2s_s - for saved state # ################################################################################ - ################################################################################ # # Conjectures for proving liveness # ################################################################################ - - object impl = { - # # all safety conjectures for saved state are autumatically added - # # conjecture X.l2s_saved -> phi(X.l2s_s) for phi in conjectures over original vocabulary + object impl = { + # all safety conjectures for saved state (in the future, will be autumatically added) + conjecture l2s_saved -> ( ($l2s_s T. pc3(T))(T1) & ($l2s_s T. pc3(T))(T2) -> T1 = T2 ) conjecture l2s_saved -> ( ($l2s_s T. pc1(T))(T) | ($l2s_s T. pc2(T))(T) | ($l2s_s T. pc3(T))(T) ) conjecture l2s_saved -> ( ~($l2s_s T. pc1(T))(T) | ~($l2s_s T. pc2(T))(T) ) conjecture l2s_saved -> ( ~($l2s_s T. pc1(T))(T) | ~($l2s_s T. pc3(T))(T) ) conjecture l2s_saved -> ( ~($l2s_s T. pc2(T))(T) | ~($l2s_s T. pc3(T))(T) ) conjecture l2s_saved -> ( ($l2s_s T,K. m(T,K))(T,K1) & ($l2s_s T,K. m(T,K))(T,K2) -> K1 = K2 ) - conjecture l2s_saved -> ( ($l2s_s T. pc3(T))(T1) & ($l2s_s T. pc3(T))(T2) -> T1 = T2 ) conjecture l2s_saved -> ( ($l2s_s. next_ticket) = ($l2s_s. zero) -> ($l2s_s T,K. m(T,K))(T,($l2s_s. zero)) ) conjecture l2s_saved -> ( ($l2s_s. next_ticket) ~= ($l2s_s. zero) & ($l2s_s T,K. m(T,K))(T,M) -> ~($l2s_s K1,K2. le(K1,K2))(($l2s_s. next_ticket),M) ) conjecture l2s_saved -> ( (($l2s_s T. pc2(T))(T) | ($l2s_s T. pc3(T))(T)) -> ($l2s_s. next_ticket) ~= ($l2s_s. zero) ) @@ -255,47 +193,36 @@ object ticket_protocol = { conjecture l2s_saved -> ( ($l2s_s K1,K2. le(K1,K2))(($l2s_s. service),($l2s_s. next_ticket)) ) conjecture l2s_saved -> ( ~(~($l2s_s T. pc1(T))(T1) & ~($l2s_s T. pc1(T))(T2) & ($l2s_s T,K. m(T,K))(T1,($l2s_s. zero)) & ($l2s_s T,K. m(T,K))(T2,($l2s_s. zero)) & T1 ~= T2) ) + # some things never change (maybe this should also be detected automatically) conjecture l2s_saved -> (le(X,Y) <-> ($l2s_s K1,K2. le(K1,K2))(X,Y)) conjecture l2s_saved -> (zero = ($l2s_s. zero)) - conjecture l2s_saved -> (t0 = ($l2s_s. t0)) - - # # basic - # conjecture (forall T:thread. globally ~(globally ~last_scheduled(T))) - conjecture globally ~(globally ~last_scheduled(V0)) - conjecture globally ~(globally ~last_scheduled(T)) # just to try a different variable name - conjecture ~(globally ~last_scheduled(V0)) - conjecture ~(globally ~(pc2(t0) & globally ~pc3(t0))) - conjecture ~($l2s_w. (pc2(t0) & globally ~pc3(t0))) <-> (pc2(t0) & globally ~pc3(t0)) - - - # TODO: should be added automatically - conjecture l2s_waiting | l2s_frozen | l2s_saved - conjecture ~l2s_waiting | ~l2s_frozen - conjecture ~l2s_waiting | ~l2s_saved - conjecture ~l2s_frozen | ~l2s_saved - - - # # basic (but not working) - # conjecture ~(globally (pc2(t0) -> eventually pc3(t0))) - # conjecture eventually (pc2(t0) & globally ~pc3(t0)) - - # conjecture ~($l2s_w. ~(pc2(t0) -> (eventually pc3(t0)))) -> ~(pc2(t0) -> (eventually pc3(t0))) - - # conjecture ($l2s_w. (~(pc2(t0) -> (eventually pc3(t0))))) <-> (~(~(pc2(t0) -> (eventually pc3(t0))))) - # conjecture eventually (pc2(t0) & (globally ~pc3(t0))) - # conjecture ($l2s_w T. (pc2(T) & (globally ~pc3(T))))(t0) -> ~(pc2(t0) & (globally ~pc3(t0))) - # conjecture ~($l2s_w T. (pc2(T) & (globally ~pc3(T))))(t0) -> (pc2(t0) & (globally ~pc3(t0))) - conjecture l2s_frozen -> (pc2(t0) & globally ~pc3(t0)) - conjecture l2s_saved -> (pc2(t0) & globally ~pc3(t0)) - conjecture l2s_saved -> ($l2s_s T,K. m(T,K))(t0,K) <-> m(t0,K) + conjecture l2s_saved -> (sk0 = ($l2s_s. sk0)) + + # basic temporal information + conjecture forall T:thread. globally eventually scheduled(T) + conjecture ~(globally ~(pc2(sk0) & globally ~pc3(sk0))) + conjecture ~($l2s_w. (pc2(sk0) & globally ~pc3(sk0))) <-> (pc2(sk0) & globally ~pc3(sk0)) # TODO: why does this make sense immediately after the save point? + + # TODO: look into why these are not working: + # conjecture ~(globally (pc2(sk0) -> eventually pc3(sk0))) + # conjecture eventually (pc2(sk0) & globally ~pc3(sk0)) + # conjecture ~($l2s_w. ~(pc2(sk0) -> (eventually pc3(sk0)))) -> ~(pc2(sk0) -> (eventually pc3(sk0))) + # conjecture ($l2s_w. (~(pc2(sk0) -> (eventually pc3(sk0))))) <-> (~(~(pc2(sk0) -> (eventually pc3(sk0))))) + # conjecture eventually (pc2(sk0) & (globally ~pc3(sk0))) + # conjecture ($l2s_w T. (pc2(T) & (globally ~pc3(T))))(sk0) -> ~(pc2(sk0) & (globally ~pc3(sk0))) + # conjecture ~($l2s_w T. (pc2(T) & (globally ~pc3(T))))(sk0) -> (pc2(sk0) & (globally ~pc3(sk0))) + + # more basic temporal properties, now in connection with monitor state + conjecture l2s_frozen -> (pc2(sk0) & globally ~pc3(sk0)) + conjecture l2s_saved -> (pc2(sk0) & globally ~pc3(sk0)) + conjecture l2s_saved -> ($l2s_s T,K. m(T,K))(sk0,K) <-> m(sk0,K) conjecture l2s_saved -> le( ($ l2s_s . service) , service) conjecture l2s_saved -> le( ($ l2s_s . next_ticket) , next_ticket) # more properties of reachable protocol states conjecture pc1(T) & m(T,M) & M ~= zero -> ~le(service, M) - conjecture forall K:ticket. ~le(next_ticket, K) & le(service, K) -> - exists T:thread. m(T,K) & ~pc1(T) - conjecture exists M. m(t0, M) + conjecture forall K:ticket. ~le(next_ticket, K) & le(service, K) -> exists T:thread. m(T,K) & ~pc1(T) + conjecture exists M. m(sk0, M) # and their saved counterpars (to be automatically added...) conjecture l2s_saved -> ( ($l2s_s X. pc1(X))(T) & ($l2s_s X,Y. m(X,Y))(T,M) & M ~= ($l2s_s. zero) -> ~($l2s_s X,Y. le(X,Y))(($l2s_s. service), M) @@ -305,49 +232,48 @@ object ticket_protocol = { exists TT:thread. ($l2s_s T,K. m(T,K))(TT,KK) & ~($l2s_s T. pc1(T))(TT) ) conjecture l2s_saved -> ( - exists M. ($l2s_s T,K. m(T,K))(($l2s_s. t0), M) + exists M. ($l2s_s T,K. m(T,K))(($l2s_s. sk0), M) ) - # conjecture that l2s_d is large enough - conjecture l2s_d(t0) + # l2s_d is large enough + conjecture l2s_d(sk0) conjecture ~pc1(T) -> l2s_d(T) conjecture le(K,next_ticket) -> l2s_d(K) - # conjecture that l2s_a is large enough - conjecture ~l2s_waiting -> l2s_a(t0) - conjecture ~l2s_waiting & m(T,K) & m(t0,K0) & ~le(K0,K) & ~pc1(T) -> l2s_a(T) - conjecture ~l2s_waiting & m(t0,K0) & le(K,K0) -> l2s_a(K) + # l2s_a is large enough + conjecture ~l2s_waiting -> l2s_a(sk0) + conjecture ~l2s_waiting & m(T,K) & m(sk0,K0) & ~le(K0,K) & ~pc1(T) -> l2s_a(T) + conjecture ~l2s_waiting & m(sk0,K0) & le(K,K0) -> l2s_a(K) # thread that have not been scheduled have not changed - conjecture l2s_saved & ($l2s_w T. last_scheduled(T))(T) -> (($l2s_s T. pc1(T))(T) <-> pc1(T)) - conjecture l2s_saved & ($l2s_w T. last_scheduled(T))(T) -> (($l2s_s T. pc2(T))(T) <-> pc2(T)) - conjecture l2s_saved & ($l2s_w T. last_scheduled(T))(T) -> (($l2s_s T. pc3(T))(T) <-> pc3(T)) - conjecture l2s_saved & ($l2s_w T. last_scheduled(T))(T) -> (($l2s_s T,K. m(T,K))(T,K) <-> m(T,K)) + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T. pc1(T))(T) <-> pc1(T)) + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T. pc2(T))(T) <-> pc2(T)) + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T. pc3(T))(T) <-> pc3(T)) + conjecture l2s_saved & ($l2s_w T. scheduled(T))(T) -> (($l2s_s T,K. m(T,K))(T,K) <-> m(T,K)) # the thread that must advance - the thread that had the service as its local ticket at the save point conjecture ( - l2s_saved & - ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & - ~($l2s_w X. last_scheduled(X))(T) & - ($l2s_s T. pc2(T))(T) & - m(T,K) & - m(t0,K0) + l2s_saved & + ($l2s_s T,K. m(T,K)) (T,($l2s_s. service)) & + ~($l2s_w X. scheduled(X))(T) & + ($l2s_s T. pc2(T))(T) & + m(T,K) & + m(sk0,K0) ) -> ( - (pc1(T) & K = ($l2s_s. service)) | - (pc2(T) & ~le(K,K0)) | - (pc3(T) & K = ($l2s_s. service)) + (pc1(T) & K = ($l2s_s. service)) | + (pc2(T) & ~le(K,K0)) | + (pc3(T) & K = ($l2s_s. service)) ) conjecture ( - l2s_saved & - ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & - ~($l2s_w T. last_scheduled(T))(T) & - ($l2s_s T. pc3(T))(T) & - m(T,K) & - m(t0,K0) + l2s_saved & + ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & + ~($l2s_w T. scheduled(T))(T) & + ($l2s_s T. pc3(T))(T) & + m(T,K) & + m(sk0,K0) ) -> ( - (pc1(T) & K = ($l2s_s. service) & ~le(service, ($l2s_s. service))) | - (pc2(T) & ~le(K,K0)) + (pc1(T) & K = ($l2s_s. service) & ~le(service, ($l2s_s. service))) | + (pc2(T) & ~le(K,K0)) ) } } with this - } From d722598ec359e694d86153c1e9acbebffd0669c5 Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Tue, 6 Mar 2018 00:29:38 +0200 Subject: [PATCH 15/17] fixed old typo --- ivy/ivy_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivy/ivy_graph.py b/ivy/ivy_graph.py index 356632727..29938982b 100644 --- a/ivy/ivy_graph.py +++ b/ivy/ivy_graph.py @@ -709,7 +709,7 @@ def materialize(self,node,recomp=False): return witness_concept(witness) def empty_edge(self,edge,recomp=False): - self.materialize_edge(edge,false) + self.materialize_edge(edge,False) def materialize_edge(self,edge,truth,recomp=False): rel,head,tail = edge From fd489f9fbedc5913fb35af9cbf60a1f40649b29f Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Tue, 6 Mar 2018 00:31:08 +0200 Subject: [PATCH 16/17] minor improvements to examples/liveness/ticket.ivy --- examples/liveness/ticket.ivy | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/examples/liveness/ticket.ivy b/examples/liveness/ticket.ivy index f2b7175d1..1d61552a5 100644 --- a/examples/liveness/ticket.ivy +++ b/examples/liveness/ticket.ivy @@ -67,12 +67,10 @@ object ticket_protocol = { assume ~le(y,x) & forall Z:ticket. ~le(Z,x) -> le(y,Z) } - action step12(t:thread,k1:ticket, k2:ticket) = { + action step12(t:thread) = { assume pc1(t); - assume k1 = next_ticket; - assume k2 = succ(k1); - m(t,K) := K = k1; - next_ticket := k2; + m(t,K) := K = next_ticket; + next_ticket := succ(next_ticket); pc1(t) := false; pc2(t) := true; scheduled(T) := T = t; @@ -95,11 +93,9 @@ object ticket_protocol = { scheduled(T) := T = t; } - action step31(t:thread, k1:ticket, k2:ticket) = { + action step31(t:thread) = { assume pc3(t); - assume k1 = service; - assume k2 = succ(k1); - service := k2; + service := succ(service); pc3(t) := false; pc1(t) := true; scheduled(T) := T = t; @@ -200,7 +196,7 @@ object ticket_protocol = { # basic temporal information conjecture forall T:thread. globally eventually scheduled(T) - conjecture ~(globally ~(pc2(sk0) & globally ~pc3(sk0))) + conjecture eventually (pc2(sk0) & globally ~pc3(sk0)) conjecture ~($l2s_w. (pc2(sk0) & globally ~pc3(sk0))) <-> (pc2(sk0) & globally ~pc3(sk0)) # TODO: why does this make sense immediately after the save point? # TODO: look into why these are not working: @@ -253,7 +249,7 @@ object ticket_protocol = { # the thread that must advance - the thread that had the service as its local ticket at the save point conjecture ( l2s_saved & - ($l2s_s T,K. m(T,K)) (T,($l2s_s. service)) & + ($l2s_s T,K. m(T,K))(T,($l2s_s. service)) & ~($l2s_w X. scheduled(X))(T) & ($l2s_s T. pc2(T))(T) & m(T,K) & From d16922564bbcd8623f63d02e709f89e6cd4bf38c Mon Sep 17 00:00:00 2001 From: Oded Padon Date: Wed, 16 Jan 2019 00:52:17 -0800 Subject: [PATCH 17/17] some minor and old changes --- ivy/ivy_actions.py | 4 ++-- ivy/ivy_l2s.py | 12 ++++++++++-- lib/emacs/ivy-mode.el | 2 ++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ivy/ivy_actions.py b/ivy/ivy_actions.py index 39968353e..e7886a975 100644 --- a/ivy/ivy_actions.py +++ b/ivy/ivy_actions.py @@ -1334,7 +1334,7 @@ def recur(action,annot,env,pos=None): recur(action.failed_action(),annot,env) return handler.handle(action,env) - iu.dbg('action') - iu.dbg('annot') + # iu.dbg('action') + # iu.dbg('annot') recur(action,annot,dict()) diff --git a/ivy/ivy_l2s.py b/ivy/ivy_l2s.py index ae0320a15..4e08587ab 100644 --- a/ivy/ivy_l2s.py +++ b/ivy/ivy_l2s.py @@ -359,8 +359,16 @@ def assert_no_fair_cycle(a): (y for x,y in mod.initializers), )): named_binders[b.name].append(b) - named_binders = defaultdict(list, ((k,list(sorted(set(v)))) for k,v in named_binders.iteritems())) - # make sure old_l2s_g is consistent with l2s_g + # sort named binders according to a consistent order + named_binders = defaultdict(list, ( + (k,list(sorted( + set(v), + key=lambda x: (len(x.variables), str(x.variables), str(x.body)), + ))) + for k,v in named_binders.iteritems() + )) + # make sure old_l2s_g is consistent with l2s_g, so that + # old_l2s_g_X is really old l2s_g_X after the substitution assert len(named_binders['l2s_g']) == len(named_binders['old_l2s_g']) assert named_binders['old_l2s_g'] == [ lg.NamedBinder('old_l2s_g', b.variables, b.body) diff --git a/lib/emacs/ivy-mode.el b/lib/emacs/ivy-mode.el index b62710b2e..846d1d0f9 100644 --- a/lib/emacs/ivy-mode.el +++ b/lib/emacs/ivy-mode.el @@ -87,6 +87,8 @@ "ensure" "around" "parameter" + "globally" + "eventually" ) ) (setq ivy-types '("bool" "int" "bv"))