def admit_definition(self, defn, proof=None): """ Admits a definition if it is non-recursive or match a definition schema. If a proof is given it is used to match the definition to a schema, else default heuristic matching is used. - defn is an ivy_ast.LabeledFormula """ sym = defn.formula.defines() if sym in self.definitions: raise Redefinition(defn, "redefinition of {}".format(sym)) if sym in self.deps: raise Circular(defn, "symbol {} defined after reference".format(sym)) deps = list(lu.symbols_ast(defn.formula.rhs())) self.deps.update(deps) if sym in deps: # Recursive definitions must match a schema if proof is None: raise NoMatch(defn, "no proof given for recursive definition") subgoals = self.match_schema(defn.formula, proof) if subgoals is None: raise NoMatch( defn, "recursive definition does not match the given schema") else: subgoals = [] self.definitions[sym] = defn
def get_assumes_and_asserts(): assumes = [] asserts = [] macros = [] for name,action in im.module.actions.iteritems(): for sa in action.iter_subactions(): if isinstance(sa,ia.AssumeAction): assumes.append((sa.args[0],sa)) if isinstance(sa,ia.AssertAction): asserts.append((sa.args[0],sa)) if isinstance(sa,ia.IfAction): asserts.append((sa.get_cond(),sa)) for ldf in im.module.definitions: if ldf.formula.defines() not in ilu.symbols_ast(ldf.formula.rhs()): macros.append((ldf.formula.to_constraint(),ldf)) else: # can't treat recursive definition as macro assumes.append((ldf.formula.to_constraint(),ldf)) for ldf in im.module.labeled_axioms: assumes.append((ldf.formula,ldf)) for ldf in im.module.labeled_props: asserts.append((ldf.formula,ldf)) # TODO: check axioms, inits, conjectures return assumes,asserts,macros
def admit_definition(self,defn,proof=None): """ Admits a definition if it is non-recursive or match a definition schema. If a proof is given it is used to match the definition to a schema, else default heuristic matching is used. - defn is an ivy_ast.LabeledFormula """ sym = defn.formula.defines() if sym in self.definitions: raise Redefinition(defn,"redefinition of {}".format(sym)) if sym in self.deps: raise Circular(defn,"symbol {} defined after reference".format(sym)) deps = list(lu.symbols_ast(defn.formula.rhs())) self.deps.update(deps) if sym in deps: # Recursive definitions must match a schema if proof is None: raise NoMatch(defn,"no proof given for recursive definition") subgoals = self.match_schema(defn.formula,proof) if subgoals is None: raise NoMatch(defn,"recursive definition does not match the given schema") else: subgoals = [] self.definitions[sym] = defn
def relevant_definitions(symbols): dfn_map = dict((ldf.formula.defines(), ldf.formula.args[1]) for ldf in module.definitions) rch = set( iu.reachable( symbols, lambda sym: lu.symbols_ast(dfn_map[sym]) if sym in dfn_map else [])) return [ldf for ldf in module.definitions if ldf.formula.defines() in rch]
def prev_expr(stvarset,expr,sort_constants): if any(sym in stvarset or tr.is_skolem(sym) and not sym in sort_constants[sym.sort] for sym in ilu.symbols_ast(expr)): return None news = [sym for sym in ilu.used_symbols_ast(expr) if tr.is_new(sym)] if news: rn = dict((sym,tr.new_of(sym)) for sym in news) return ilu.rename_ast(expr,rn) return None
def get_assumes_and_asserts(preconds_only): assumes = [] asserts = [] macros = [] # for name,action in im.module.actions.iteritems(): # for sa in action.iter_subactions(): # if isinstance(sa,ia.AssumeAction): # assumes.append((sa.args[0],sa)) # if isinstance(sa,ia.AssertAction): # asserts.append((sa.args[0],sa)) # if isinstance(sa,ia.IfAction): # asserts.append((sa.get_cond(),sa)) if preconds_only: for name in im.module.before_export: action = im.module.before_export[name] triple = action.update(im.module, []) foo = ilu.close_epr(ilu.clauses_to_formula(triple[1])) assumes.append((foo, action)) else: for name in im.module.public_actions: action = im.module.actions[name] triple = action.update(im.module, []) # print 'ivy_theory.py: triple[1]: {}'.format(triple[1]) foo = ilu.close_epr(ilu.clauses_to_formula(triple[1])) # print 'ivy_theory.py: foo (1): {}'.format(foo) assumes.append((foo, action)) # print 'ivy_theory.py: triple[2]: {}'.format(triple[2]) foo = ilu.close_epr(ilu.clauses_to_formula(triple[2])) # print 'ivy_theory.py: foo (2): {}'.format(foo) assumes.append((foo, action)) for ldf in im.module.definitions: if ldf.formula.defines() not in ilu.symbols_ast(ldf.formula.rhs()): macros.append((ldf.formula.to_constraint(), ldf)) else: # can't treat recursive definition as macro assumes.append((ldf.formula.to_constraint(), ldf)) for ldf in im.module.labeled_axioms: if not ldf.temporal: assumes.append((ldf.formula, ldf)) for ldf in im.module.labeled_props: if not ldf.temporal: asserts.append((ldf.formula, ldf)) for ldf in im.module.labeled_conjs: asserts.append((ldf.formula, ldf)) assumes.append((ldf.formula, ldf)) # TODO: check axioms, inits, conjectures return assumes, asserts, macros
def get_assumes_and_asserts(preconds_only): assumes = [] asserts = [] macros = [] # for name,action in im.module.actions.iteritems(): # for sa in action.iter_subactions(): # if isinstance(sa,ia.AssumeAction): # assumes.append((sa.args[0],sa)) # if isinstance(sa,ia.AssertAction): # asserts.append((sa.args[0],sa)) # if isinstance(sa,ia.IfAction): # asserts.append((sa.get_cond(),sa)) if preconds_only: for name in im.module.before_export: action = im.module.before_export[name] triple = action.update(im.module,[]) foo = ilu.close_epr(ilu.clauses_to_formula(triple[1])) assumes.append((foo,action)) else: for name in im.module.public_actions: action = im.module.actions[name] triple = action.update(im.module,[]) # print 'ivy_theory.py: triple[1]: {}'.format(triple[1]) foo = ilu.close_epr(ilu.clauses_to_formula(triple[1])) # print 'ivy_theory.py: foo (1): {}'.format(foo) assumes.append((foo,action)) # print 'ivy_theory.py: triple[2]: {}'.format(triple[2]) foo = ilu.close_epr(ilu.clauses_to_formula(triple[2])) # print 'ivy_theory.py: foo (2): {}'.format(foo) assumes.append((foo,action)) for ldf in im.module.definitions: if ldf.formula.defines() not in ilu.symbols_ast(ldf.formula.rhs()): macros.append((ldf.formula.to_constraint(),ldf)) else: # can't treat recursive definition as macro assumes.append((ldf.formula.to_constraint(),ldf)) for ldf in im.module.labeled_axioms: if not ldf.temporal: assumes.append((ldf.formula,ldf)) for ldf in im.module.labeled_props: if not ldf.temporal: asserts.append((ldf.formula,ldf)) for ldf in im.module.labeled_conjs: asserts.append((ldf.formula,ldf)) assumes.append((ldf.formula,ldf)) # TODO: check axioms, inits, conjectures return assumes,asserts,macros
def get_assumes_and_asserts(preconds_only): assumes = [] asserts = [] macros = [] # for name,action in im.module.actions.iteritems(): # for sa in action.iter_subactions(): # if isinstance(sa,ia.AssumeAction): # assumes.append((sa.args[0],sa)) # if isinstance(sa,ia.AssertAction): # asserts.append((sa.args[0],sa)) # if isinstance(sa,ia.IfAction): # asserts.append((sa.get_cond(),sa)) if preconds_only: for name in im.module.before_export: action = im.module.before_export[name] triple = action.update(im.module, []) foo = ilu.close_epr(ilu.clauses_to_formula(triple[1])) assumes.append((foo, action)) else: for name in im.module.public_actions: action = im.module.actions[name] triple = action.update(im.module, []) # print 'ivy_theory.py: triple[1]: {}'.format(triple[1]) foo = ilu.close_epr(ilu.clauses_to_formula(triple[1])) # print 'ivy_theory.py: foo (1): {}'.format(foo) assumes.append((foo, action)) # print 'ivy_theory.py: triple[2]: {}'.format(triple[2]) foo = ilu.close_epr(ilu.clauses_to_formula(triple[2])) # print 'ivy_theory.py: foo (2): {}'.format(foo) assumes.append((foo, action)) for ldf in im.module.definitions: if not isinstance(ldf.formula, il.DefinitionSchema): if (ldf.formula.defines() not in ilu.symbols_ast(ldf.formula.rhs()) and not isinstance(ldf.formula.rhs(), il.Some)): # print 'macro : {}'.format(ldf.formula) macros.append((ldf.formula, ldf)) else: # can't treat recursive definition as macro # print 'axiom : {}'.format(ldf.formula) assumes.append((ldf.formula.to_constraint(), ldf)) for ldf in im.module.labeled_axioms: if not ldf.temporal: # print 'axiom : {}'.format(ldf.formula) assumes.append((ldf.formula, ldf)) pfs = set(lf.id for lf, p in im.module.proofs) sgs = set(x.id for x, y in im.module.subgoals) for ldf in im.module.labeled_props: if not ldf.temporal: # print 'prop : {}{} {}'.format(ldf.lineno,ldf.label,ldf.formula) if ldf.id not in pfs: asserts.append((ldf.formula, ldf)) elif ldf.id in sgs and not ldf.explicit: assumes.append((ldf.formula, ldf)) for ldf in im.module.labeled_conjs: asserts.append((ldf.formula, ldf)) assumes.append((ldf.formula, ldf)) for ldf in im.module.assumed_invariants: if not ldf.explicit: assumes.append((ldf.formula, ldf)) # TODO: check axioms, inits, conjectures # for x in assumes: # print 'assume: {}'.format(x[0]) # for x in asserts: # print 'assert: {}'.format(x[0]) # for x in macros: # print 'macro: {}'.format(x[0]) return assumes, asserts, macros
def l2s_tactic(prover, goals, proof): mod = im.module goal = goals[0] # pick up the first proof goal lineno = goal.lineno conc = ipr.goal_conc(goal) # get its conclusion if not isinstance(conc, itm.TemporalModels): raise iu.IvyError(proof, 'proof goal is not temporal') model = conc.model.clone([]) fmla = conc.fmla # Get all the temporal properties from the prover environment as assumptions # Add all the assumed invariants to the model assumed_gprops = [ x for x in prover.axioms if not x.explicit and x.temporal ] model.asms.extend( [p.clone([p.label, p.formula.args[0]]) for p in assumed_gprops]) # TRICKY: We postpone compiling formulas in the tactic until now, so # that tactics can introduce their own symbols. But, this means that the # tactic has to be given an appropriate environment label for any temporal # operators. Here, we compile the invariants in the tactic, using the given # label. assert hasattr(proof, 'labels') and len(proof.labels) == 1 proof_label = proof.labels[0] # print 'proof label: {}'.format(proof_label) invars = [ ilg.label_temporal(inv.compile(), proof_label) for inv in proof.tactic_decls ] 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 vs, t: lg.NamedBinder('l2s_w', vs, proof_label, t) l2s_s = lambda vs, t: lg.NamedBinder('l2s_s', vs, proof_label, t) l2s_g = lambda vs, t, environ: lg.NamedBinder('l2s_g', vs, environ, t) old_l2s_g = lambda vs, t, environ: lg.NamedBinder('_old_l2s_g', vs, environ, t) # Desugar the invariants. # # $was. phi(V) --> l2s_saved & ($l2s_s V.phi(V))(V) # $happened. phi --> l2s_saved & ~($l2s_w V.phi(V))(V) # # We push $l2s_s inside propositional connectives, so that the saved # values correspond to atoms. Otherwise, we would have redundant # saved values, for example p(X) and ~p(X). def desugar(expr): def apply_was(expr): if isinstance(expr, (lg.And, lg.Or, lg.Not, lg.Implies, lg.Iff)): return expr.clone([apply_was(a) for a in expr.args]) vs = list(iu.unique(ilu.variables_ast(expr))) return l2s_s(vs, expr)(*vs) def apply_happened(expr): vs = list(iu.unique(ilu.variables_ast(expr))) return lg.Not(l2s_w(vs, expr)(*vs)) if ilg.is_named_binder(expr): if expr.name == 'was': if len(expr.variables) > 0: raise iu.IvyError( expr, "operator 'was' does not take parameters") return lg.And(l2s_saved, apply_was(expr.body)) elif expr.name == 'happened': if len(expr.variables) > 0: raise iu.IvyError( expr, "operator 'happened' does not take parameters") return lg.And(l2s_saved, apply_happened(expr.body)) return expr.clone([desugar(a) for a in expr.args]) invars = map(desugar, invars) # Add the invariant phi to the list. TODO: maybe, if it is a G prop # invars.append(ipr.clone_goal(goal,[],invar)) # Add the invariant list to the model model.invars = model.invars + invars # for inv in invars: # print inv # for b in ilu.named_binders_ast(inv): # print 'orig binder: {} {} {}'.format(b.name,b.environ,b.body) # model pass helper funciton def mod_pass(transform): model.invars = [transform(x) for x in model.invars] model.asms = [transform(x) for x in model.asms] # TODO: what about axioms and properties? newb = [] model.bindings = [ b.clone([transform(b.action)]) for b in model.bindings ] model.init = transform(model.init) # 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, env): vs = tuple(vs) res = l2s_g(vs, t, env) # print 'l2s_gs: {} {} {}'.format(vs,t,env) l2s_gs.add((vs, t, env)) 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(fmla)) if debug.get(): print "=" * 80 + "\nafter replace_temporals_by_named_binder_g_ast" + "\n" * 3 print "=" * 80 + "\nl2s_gs:" for vs, t, env in sorted(l2s_gs): print vs, t, env print "=" * 80 + "\n" * 3 print model print "=" * 80 + "\n" * 3 # now we normalize all named binders mod_pass(ilu.normalize_named_binders) if debug.get(): print "=" * 80 + "\nafter normalize_named_binders" + "\n" * 3 print model print "=" * 80 + "\n" * 3 # construct the monitor related building blocks uninterpreted_sorts = [ s for s in ilg.sig.sorts.values() if type(s) is lg.UninterpretedSort and s.name not in mod.finite_sorts ] reset_a = [ AssignAction(l2s_a(s)(v), l2s_d(s)(v)).set_lineno(lineno) for s in uninterpreted_sorts for v in [lg.Var('X', s)] ] add_consts_to_d = [ AssignAction(l2s_d(s)(c), lg.true).set_lineno(lineno) for s in uninterpreted_sorts for c in ilg.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 # 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(model.invars): # print 'binder: {} {} {}'.format(b.name,b.environ,b.body) 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'] if debug.get(): 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).set_lineno(lineno) for vs, t in to_save ] done_waiting = [forall(vs, lg.Not(l2s_w(vs, t)(*vs))) 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 if v.sort.name not in mod.finite_sorts ] + [ lg.Not(t), replace_temporals_by_l2s_g( lg.Not(lg.Globally(proof_label, ilu.negate(t)))) ]))).set_lineno(lineno) for vs, t in to_wait ] 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 if v.sort.name not in mod.finite_sorts)), 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) ] # projection of functions and constants fair_cycle += [ forall( vs, lg.Implies( lg.And(*([ l2s_a(v.sort)(v) for v in vs if v.sort.name not in mod.finite_sorts ] + ([ lg.Or(l2s_a(t.sort)(l2s_s(vs, t)(*vs)), l2s_a(t.sort)(t)) ] if t.sort.name not in mod.finite_sorts else []))), 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)) ] assert_no_fair_cycle = AssertAction(lg.Not( lg.And(*fair_cycle))).set_lineno(lineno) assert_no_fair_cycle.lineno = goal.lineno monitor_edge = lambda s1, s2: [ AssumeAction(s1).set_lineno(lineno), AssignAction(s1, lg.false).set_lineno(lineno), AssignAction(s2, lg.true).set_lineno(lineno), ] change_monitor_state = [ ChoiceAction( # waiting -> frozen Sequence( *(monitor_edge(l2s_waiting, l2s_frozen) + [AssumeAction(x).set_lineno(lineno) for x in done_waiting] + reset_a)).set_lineno(lineno), # frozen -> saved Sequence(*(monitor_edge(l2s_frozen, l2s_saved) + save_state + reset_w)), # stay in same state (self edge) Sequence().set_lineno(lineno), ).set_lineno(lineno) ] # 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)) if debug.get(): print '=' * 40 + "\nto_g:\n" for vs, t, env in sorted(to_g): print vs, t, '\n' print '=' * 40 assume_g_axioms = [ AssumeAction(forall(vs, lg.Implies(l2s_g(vs, t, env)(*vs), t))).set_lineno(lineno) for vs, t, env in to_g ] # now patch the module actions with monitor and tableau if debug.get(): print "public_actions:", model.calls # Tableau construction # # Each temporal operator has an 'environment'. The operator # applies to states *not* in actions labeled with this # environment. This has several consequences: # # 1) The operator's semantic constraint is an assumed invariant (i.e., # it holds outside of any action) # # 2) An 'event' for the temporal operator occurs when (a) we return # from an execution context inside its environment to one outside, # or (b) we are outside the environment of the operator and some symbol # occurring in it's body is mutated. # # 3) At any event for the operator, we update its truth value and # and re-establish its semantic constraint. # # This procedure generates code for an event corresponding to a # list of operators. The tableau state is updated and the # semantics applied. def prop_events(gprops): res = [] for gprop in gprops: vs, t, env = gprop.variables, gprop.body, gprop.environ res.append( AssignAction( old_l2s_g(vs, t, env)(*vs), l2s_g(vs, t, env)(*vs)).set_lineno(lineno)) res.append(HavocAction(l2s_g(vs, t, env)(*vs)).set_lineno(lineno)) for gprop in gprops: vs, t, env = gprop.variables, gprop.body, gprop.environ res.append( AssumeAction( forall( vs, lg.Implies( old_l2s_g(vs, t, env)(*vs), l2s_g(vs, t, env)(*vs)))).set_lineno(lineno)) res.append( AssumeAction( forall( vs, lg.Implies( lg.And(lg.Not(old_l2s_g(vs, t, env)(*vs)), t), lg.Not(l2s_g(vs, t, env)(*vs))))).set_lineno(lineno)) res.append( AssumeAction(forall(vs, lg.Implies(l2s_g(vs, t, env)(*vs), t))).set_lineno(lineno)) return res # This procedure generates code for an event corresponding to a # list of eventualites to be waited on. The tableau state is updated and the # semantics applied. def wait_events(waits): res = [] for wait in waits: vs = wait.variables t = wait.body # (l2s_w V. phi)(V) := (l2s_w V. phi)(V) & ~phi & ~(l2s_g V. ~phi)(V) res.append( AssignAction( wait(*vs), lg.And( wait(*vs), lg.Not(t), replace_temporals_by_l2s_g( lg.Not(lg.Globally(proof_label, ilu.negate(t))))) # TODO check this and make sure its correct # note this adds to l2s_gs ).set_lineno(lineno)) return res # The following procedure instruments a statement with operator # events for all of the temporal operators. This depends on the # statement's environment, that is, current set of environment # labels. # # Currently, the environment labels of a statement have to be # statically determined, but this could change, i.e., the labels # could be represented by boolean variables. # # First, make some memo tables envprops = defaultdict(list) symprops = defaultdict(list) symwaits = defaultdict(list) for vs, t, env in l2s_gs: prop = l2s_g(vs, t, env) envprops[env].append(prop) for sym in ilu.symbols_ast(t): symprops[sym].append(prop) for vs, t in to_wait: wait = l2s_w(vs, t) for sym in ilu.symbols_ast(t): symwaits[sym].append(wait) actions = dict((b.name, b.action) for b in model.bindings) # lines = dict(zip(gprops,gproplines)) def instr_stmt(stmt, labels): # first, recur on the sub-statements args = [ instr_stmt(a, labels) if isinstance(a, Action) else a for a in stmt.args ] res = stmt.clone(args) # now add any needed temporal events after this statement event_props = set() event_waits = set() # first, if it is a call, we must consider any events associated with # the return # if isinstance(stmt,CallAction): # callee = actions[stmt.callee()] # get the called action # exiting = [l for l in callee.labels if l not in labels] # environments we exit on return # for label in exiting: # for prop in envprops[label]: # event_props.add(prop) # Second, if a symbol is modified, we must add events for every property that # depends on the symbol, but only if we are not in the environment of that property. for sym in stmt.modifies(): for prop in symprops[sym]: # if prop.environ not in labels: event_props.add(prop) for wait in symwaits[sym]: event_waits.add(wait) # Now, for every property event, we update the property state (none in this case) # and also assert the property semantic constraint. events = prop_events(event_props) events += wait_events(event_waits) res = iact.postfix_action(res, events) stmt.copy_formals(res) # HACK: This shouldn't be needed return res # Instrument all the actions model.bindings = [ b.clone([b.action.clone([instr_stmt(b.action.stmt, b.action.labels)])]) for b in model.bindings ] # Now, for every exported action, we add the l2s construction. On # exit of each external procedure, we add a tableau event for all # the operators whose scope is being exited. # # TODO: This is wrong in the case of an exported procedure that is # also internally called. We do *not* want to update the tableau # in the case of an internal call, since the scope of the # operators os not exited. One solution to this is to create to # duplicate the actions so there is one version for internal # callers and one for external callers. It is possible that this # is already done by ivy_isolate, but this needs to be verified. calls = set(model.calls) # the exports for b in model.bindings: if b.name in calls: add_params_to_d = [ AssignAction(l2s_d(p.sort)(p), lg.true) for p in b.action.inputs if p.sort.name not in mod.finite_sorts ] # tableau updates for exit to environment # event_props = set() # for label in b.action.labels: # for prop in envprops[label]: # event_props.add(prop) # events = prop_events(event_props) stmt = concat_actions(*( add_params_to_d + assume_g_axioms + # could be added to model.asms [b.action.stmt] + add_consts_to_d)).set_lineno(lineno) b.action.stmt.copy_formals(stmt) # HACK: This shouldn't be needed b.action = b.action.clone([stmt]) # The idle action handles automaton state update and cycle checking idle_action = concat_actions(*( change_monitor_state + assume_g_axioms + # could be added to model.asms add_consts_to_d + [assert_no_fair_cycle])).set_lineno(lineno) idle_action.formal_params = [] idle_action.formal_returns = [] model.bindings.append( itm.ActionTermBinding('idle', itm.ActionTerm([], [], [], idle_action))) model.calls.append('idle') l2s_init = [ AssignAction(l2s_waiting, lg.true).set_lineno(lineno), AssignAction(l2s_frozen, lg.false).set_lineno(lineno), AssignAction(l2s_saved, lg.false).set_lineno(lineno), ] l2s_init += add_consts_to_d l2s_init += reset_w l2s_init += assume_g_axioms l2s_init += [AssumeAction(not_lf).set_lineno(lineno)] if not hasattr(model.init, 'lineno'): model.init.lineno = None # Hack: fix this model.init = iact.postfix_action(model.init, l2s_init) if debug.get(): print "=" * 80 + "\nafter patching actions" + "\n" * 3 print model print "=" * 80 + "\n" * 3 # 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( model.invars, model.asms, [model.init], [b.action for b in model.bindings], )): 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.environ, 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)) if debug.get(): 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)) if debug.get(): print "=" * 80 + "\nafter replace_named_binders" + "\n" * 3 print model print "=" * 80 + "\n" * 3 # if len(gprops) > 0: # assumes = [gprop_to_assume(x) for x in gprops] # model.bindings = [b.clone([prefix_action(b.action,assumes)]) for b in model.bindings] # HACK: reestablish invariant that shouldn't be needed for b in model.bindings: b.action.stmt.formal_params = b.action.inputs b.action.stmt.formal_returns = b.action.outputs # Change the conclusion formula to M |= true conc = itm.TemporalModels(model, lg.And()) # Build the new goal goal = ipr.clone_goal(goal, ipr.goal_prems(goal), conc) # Return the new goal stack goals = [goal] + goals[1:] return goals
def to_aiger(mod,ext_act): erf = il.Symbol('err_flag',il.find_sort('bool')) errconds = [] add_err_flag_mod(mod,erf,errconds) # we use a special state variable __init to indicate the initial state ext_acts = [mod.actions[x] for x in sorted(mod.public_actions)] ext_act = ia.EnvAction(*ext_acts) init_var = il.Symbol('__init',il.find_sort('bool')) init = add_err_flag(ia.Sequence(*([a for n,a in mod.initializers]+[ia.AssignAction(init_var,il.And())])),erf,errconds) action = ia.Sequence(ia.AssignAction(erf,il.Or()),ia.IfAction(init_var,ext_act,init)) # get the invariant to be proved, replacing free variables with # skolems. First, we apply any proof tactics. pc = ivy_proof.ProofChecker(mod.axioms,mod.definitions,mod.schemata) pmap = dict((lf.id,p) for lf,p in mod.proofs) conjs = [] for lf in mod.labeled_conjs: if lf.id in pmap: proof = pmap[lf.id] subgoals = pc.admit_proposition(lf,proof) conjs.extend(subgoals) else: conjs.append(lf) invariant = il.And(*[il.drop_universals(lf.formula) for lf in conjs]) # iu.dbg('invariant') skolemizer = lambda v: ilu.var_to_skolem('__',il.Variable(v.rep,v.sort)) vs = ilu.used_variables_in_order_ast(invariant) sksubs = dict((v.rep,skolemizer(v)) for v in vs) invariant = ilu.substitute_ast(invariant,sksubs) invar_syms = ilu.used_symbols_ast(invariant) # compute the transition relation stvars,trans,error = action.update(mod,None) # print 'action : {}'.format(action) # print 'annotation: {}'.format(trans.annot) annot = trans.annot # match_annotation(action,annot,MatchHandler()) indhyps = [il.close_formula(il.Implies(init_var,lf.formula)) for lf in mod.labeled_conjs] # trans = ilu.and_clauses(trans,indhyps) # save the original symbols for trace orig_syms = ilu.used_symbols_clauses(trans) orig_syms.update(ilu.used_symbols_ast(invariant)) # TODO: get the axioms (or maybe only the ground ones?) # axioms = mod.background_theory() # rn = dict((sym,tr.new(sym)) for sym in stvars) # next_axioms = ilu.rename_clauses(axioms,rn) # return ilu.and_clauses(axioms,next_axioms) funs = set() for df in trans.defs: funs.update(ilu.used_symbols_ast(df.args[1])) for fmla in trans.fmlas: funs.update(ilu.used_symbols_ast(fmla)) # funs = ilu.used_symbols_clauses(trans) funs.update(ilu.used_symbols_ast(invariant)) funs = set(sym for sym in funs if il.is_function_sort(sym.sort)) iu.dbg('[str(fun) for fun in funs]') # Propositionally abstract # step 1: get rid of definitions of non-finite symbols by turning # them into constraints new_defs = [] new_fmlas = [] for df in trans.defs: if len(df.args[0].args) == 0 and is_finite_sort(df.args[0].sort): new_defs.append(df) else: fmla = df.to_constraint() new_fmlas.append(fmla) trans = ilu.Clauses(new_fmlas+trans.fmlas,new_defs) # step 2: get rid of ite's over non-finite sorts, by introducing constraints cnsts = [] new_defs = [elim_ite(df,cnsts) for df in trans.defs] new_fmlas = [elim_ite(fmla,cnsts) for fmla in trans.fmlas] trans = ilu.Clauses(new_fmlas+cnsts,new_defs) # step 3: eliminate quantfiers using finite instantiations from_asserts = il.And(*[il.Equals(x,x) for x in ilu.used_symbols_ast(il.And(*errconds)) if tr.is_skolem(x) and not il.is_function_sort(x.sort)]) iu.dbg('from_asserts') invar_syms.update(ilu.used_symbols_ast(from_asserts)) sort_constants = mine_constants(mod,trans,il.And(invariant,from_asserts)) sort_constants2 = mine_constants2(mod,trans,invariant) print '\ninstantiations:' trans,invariant = Qelim(sort_constants,sort_constants2)(trans,invariant,indhyps) # print 'after qe:' # print 'trans: {}'.format(trans) # print 'invariant: {}'.format(invariant) # step 4: instantiate the axioms using patterns # We have to condition both the transition relation and the # invariant on the axioms, so we define a boolean symbol '__axioms' # to represent the axioms. axs = instantiate_axioms(mod,stvars,trans,invariant,sort_constants,funs) ax_conj = il.And(*axs) ax_var = il.Symbol('__axioms',ax_conj.sort) ax_def = il.Definition(ax_var,ax_conj) invariant = il.Implies(ax_var,invariant) trans = ilu.Clauses(trans.fmlas+[ax_var],trans.defs+[ax_def]) # step 5: eliminate all non-propositional atoms by replacing with fresh booleans # An atom with next-state symbols is converted to a next-state symbol if possible stvarset = set(stvars) prop_abs = dict() # map from atoms to proposition variables global prop_abs_ctr # sigh -- python lameness prop_abs_ctr = 0 # counter for fresh symbols new_stvars = [] # list of fresh symbols # get the propositional abstraction of an atom def new_prop(expr): res = prop_abs.get(expr,None) if res is None: prev = prev_expr(stvarset,expr,sort_constants) if prev is not None: # print 'stvar: old: {} new: {}'.format(prev,expr) pva = new_prop(prev) res = tr.new(pva) new_stvars.append(pva) prop_abs[expr] = res # prevent adding this again to new_stvars else: global prop_abs_ctr res = il.Symbol('__abs[{}]'.format(prop_abs_ctr),expr.sort) # print '{} = {}'.format(res,expr) prop_abs[expr] = res prop_abs_ctr += 1 return res # propositionally abstract an expression global mk_prop_fmlas mk_prop_fmlas = [] def mk_prop_abs(expr): if il.is_quantifier(expr) or len(expr.args) > 0 and any(not is_finite_sort(a.sort) for a in expr.args): return new_prop(expr) return expr.clone(map(mk_prop_abs,expr.args)) # apply propositional abstraction to the transition relation new_defs = map(mk_prop_abs,trans.defs) new_fmlas = [mk_prop_abs(il.close_formula(fmla)) for fmla in trans.fmlas] # find any immutable abstract variables, and give them a next definition def my_is_skolem(x): res = tr.is_skolem(x) and x not in invar_syms return res def is_immutable_expr(expr): res = not any(my_is_skolem(sym) or tr.is_new(sym) or sym in stvarset for sym in ilu.used_symbols_ast(expr)) return res for expr,v in prop_abs.iteritems(): if is_immutable_expr(expr): new_stvars.append(v) print 'new state: {}'.format(expr) new_defs.append(il.Definition(tr.new(v),v)) trans = ilu.Clauses(new_fmlas+mk_prop_fmlas,new_defs) # apply propositional abstraction to the invariant invariant = mk_prop_abs(invariant) # create next-state symbols for atoms in the invariant (is this needed?) rn = dict((sym,tr.new(sym)) for sym in stvars) mk_prop_abs(ilu.rename_ast(invariant,rn)) # this is to pick up state variables from invariant # update the state variables by removing the non-finite ones and adding the fresh state booleans stvars = [sym for sym in stvars if is_finite_sort(sym.sort)] + new_stvars # iu.dbg('trans') # iu.dbg('stvars') # iu.dbg('invariant') # exit(0) # For each state var, create a variable that corresponds to the input of its latch # Also, havoc all the state bits except the init flag at the initial time. This # is needed because in aiger, all latches start at 0! def fix(v): return v.prefix('nondet') def curval(v): return v.prefix('curval') def initchoice(v): return v.prefix('initchoice') stvars_fix_map = dict((tr.new(v),fix(v)) for v in stvars) stvars_fix_map.update((v,curval(v)) for v in stvars if v != init_var) trans = ilu.rename_clauses(trans,stvars_fix_map) # iu.dbg('trans') new_defs = trans.defs + [il.Definition(ilu.sym_inst(tr.new(v)),ilu.sym_inst(fix(v))) for v in stvars] new_defs.extend(il.Definition(curval(v),il.Ite(init_var,v,initchoice(v))) for v in stvars if v != init_var) trans = ilu.Clauses(trans.fmlas,new_defs) # Turn the transition constraint into a definition cnst_var = il.Symbol('__cnst',il.find_sort('bool')) new_defs = list(trans.defs) new_defs.append(il.Definition(tr.new(cnst_var),fix(cnst_var))) new_defs.append(il.Definition(fix(cnst_var),il.Or(cnst_var,il.Not(il.And(*trans.fmlas))))) stvars.append(cnst_var) trans = ilu.Clauses([],new_defs) # Input are all the non-defined symbols. Output indicates invariant is false. # iu.dbg('trans') def_set = set(df.defines() for df in trans.defs) def_set.update(stvars) # iu.dbg('def_set') used = ilu.used_symbols_clauses(trans) used.update(ilu.symbols_ast(invariant)) inputs = [sym for sym in used if sym not in def_set and not il.is_interpreted_symbol(sym)] fail = il.Symbol('__fail',il.find_sort('bool')) outputs = [fail] # iu.dbg('trans') # make an aiger aiger = Encoder(inputs,stvars,outputs) comb_defs = [df for df in trans.defs if not tr.is_new(df.defines())] invar_fail = il.Symbol('invar__fail',il.find_sort('bool')) # make a name for invariant fail cond comb_defs.append(il.Definition(invar_fail,il.Not(invariant))) aiger.deflist(comb_defs) for df in trans.defs: if tr.is_new(df.defines()): aiger.set(tr.new_of(df.defines()),aiger.eval(df.args[1])) miter = il.And(init_var,il.Not(cnst_var),il.Or(invar_fail,il.And(fix(erf),il.Not(fix(cnst_var))))) aiger.set(fail,aiger.eval(miter)) # aiger.sub.debug() # make a decoder for the abstract propositions decoder = dict((y,x) for x,y in prop_abs.iteritems()) for sym in aiger.inputs + aiger.latches: if sym not in decoder and sym in orig_syms: decoder[sym] = sym cnsts = set(sym for syms in sort_constants.values() for sym in syms) return aiger,decoder,annot,cnsts,action,stvarset
def relevant_definitions(symbols): dfn_map = dict((ldf.formula.defines(),ldf.formula.args[1]) for ldf in module.definitions) rch = set(iu.reachable(symbols,lambda sym: lu.symbols_ast(dfn_map[sym]) if sym in dfn_map else [])) return [ldf for ldf in module.definitions if ldf.formula.defines() in rch]
def invariance_tactic(prover,goals,proof): goal = goals[0] # pick up the first proof goal conc = ipr.goal_conc(goal) # get its conclusion if not isinstance(conc,TemporalModels): raise iu.IvyError(proof,'proof goal is not temporal') model = conc.model fmla = conc.fmla if not isinstance(fmla,il.Globally): raise iu.IvyError(proof,'invariance tactic applies only to globally formulas') invar = fmla.args[0] if il.is_temporal(invar): raise iu.IvyError(proof,'invariance tactic applies only formulas "globally p"' + ' where p is non-temporal') # Collect the auxiliary invariants invars = [inv.compile() for inv in proof.tactic_decls] # Add the invariant phi to the list invars.append(ipr.clone_goal(goal,[],invar)) # Add the invariant list to the model model = model.clone([]) model.invars = model.invars + invars # Get all the implicit globally properties from the proof # environment. Each temporal operator has an 'environment'. The # operator applies to states *not* in actions labeled with this # environment. This has several consequences: # # 1) The operator's semantic constraint is an assumed invariant (i.e., # it holds outside of any action) # # 2) An 'event' for the temporal operator occurs when (a) we return # from an execution context inside its environment to one outside, # or (b) we are outside the environment of the operator and some symbol # occurring in it's body is mutated. # # 3) At any event for the operator, we update its truth value and # and re-establish its senatic constraint. # # # The following procedure instruments a statement with operator events for # both the property to be proved and the invariant assumptions (all G properties here). # This depends on the statement's environment, that is, current set of environment # labels. # # Currently, the environment labels of a statement have to be # statically determined, but this could change, i.e., the labels # could be represented by boolean variables. # # TODO: there is something a bit inelegant here, because when we return from # an exported action to the external environment, we need to update the operator # states, however, we do *not* want to do this when returning to in internal caller. # The best solution currently for this is to duplication the actions so there # is one version for internal callers and one for external callers. This issue doesn't # affect this simple invariance tactic, because it doesn't need to update the truth # values of the operators. # Get all the G properties from the prover environment as assumptions assumed_gprops = [x for x in prover.axioms if not x.explicit and x.temporal and il.is_gprop(x.formula)] gprops = [x.formula for x in assumed_gprops] gproplines = [x.lineno for x in assumed_gprops] # We represent the property G phi to be proved by its negation F ~phi. gprops.append(il.Eventually(fmla.environ,il.Not(invar))) gproplines.append(goal.lineno) # Make some memo tables envprops = defaultdict(list) symprops = defaultdict(list) for prop in gprops: envprops[prop.environ].append(prop) for sym in ilu.symbols_ast(prop): symprops[sym].append(prop) actions = dict((b.name,b.action) for b in model.bindings) lines = dict(zip(gprops,gproplines)) def instr_stmt(stmt,labels): # first, recur on the sub-statements args = [instr_stmt(a,labels) if isinstance(a,iact.Action) else a for a in stmt.args] res = stmt.clone(args) # now add any needed temporal events after this statement event_props = set() # first, if it is a call, we must consider any events associated with # the return if isinstance(stmt,iact.CallAction): callee = actions[stmt.callee()] # get the called action exiting = [l for l in callee.labels if l not in labels] # environments we exit on return for label in exiting: for prop in envprops[label]: event_props.add(prop) # Second, if a symbol is modified, we must add events for every property that # depends on the symbol, but only if we are not in the environment of that property. for sym in stmt.modifies(): for prop in symprops[sym]: if prop.environ not in labels: event_props.add(prop) # Now, for every property event, we update the property state (none in this case) # and also assert the property semantic constraint. events = [prop_event(prop,lines[prop]) for prop in event_props] res = iact.postfix_action(res,events) stmt.copy_formals(res) # HACK: This shouldn't be needed return res # Add property events to all of the actions: model.bindings = [b.clone([b.action.clone([instr_stmt(b.action.stmt,b.action.labels)])]) for b in model.bindings] # Add all the assumed invariants to the model model.asms.extend([p.clone([p.label,p.formula.args[0]]) for p in assumed_gprops]) # if len(gprops) > 0: # assumes = [gprop_to_assume(x) for x in gprops] # model.bindings = [b.clone([prefix_action(b.action,assumes)]) for b in model.bindings] # Change the conclusion formula to M |= true conc = TemporalModels(model,il.And()) # Build the new goal goal = ipr.clone_goal(goal,ipr.goal_prems(goal),conc) # Return the new goal stack goals = [goal] + goals[1:] return goals