1 | % (c) 2014-2022 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, | |
2 | % Heinrich Heine Universitaet Duesseldorf | |
3 | % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) | |
4 | ||
5 | :- module(ic3,[ic3_symbolic_model_check/1, | |
6 | counter_example_can_be_replayed/0]). | |
7 | ||
8 | :- use_module(probsrc(module_information)). | |
9 | :- module_info(group,symbolic_model_checker). | |
10 | :- module_info(description,'A symbolic model checker based on IC3.'). | |
11 | ||
12 | :- use_module(library(heaps)). | |
13 | :- use_module(library(avl)). | |
14 | %:- use_module(library(ordsets)). | |
15 | :- use_module(library(lists)). | |
16 | ||
17 | :- use_module(extension('counter/counter'),[counter_init/0, | |
18 | new_counter/1, | |
19 | get_counter/2]). | |
20 | ||
21 | :- use_module(probsrc(preferences),[get_preference/2]). | |
22 | :- use_module(probsrc(bmachine),[b_get_invariant_from_machine/1, | |
23 | %b_get_initialisation_from_machine/2, | |
24 | b_get_properties_from_machine/1, | |
25 | b_specialized_invariant_for_op/2, | |
26 | get_proven_invariant/2]). | |
27 | :- use_module(probsrc(bsyntaxtree), [conjunct_predicates/2, | |
28 | disjunct_predicates/2, | |
29 | conjunction_to_list/2, | |
30 | create_implication/3, | |
31 | create_negation/2 | |
32 | %create_exists/3, replace_id_by_expr/4 | |
33 | ]). | |
34 | :- use_module(probsrc(translate), [translate_bexpression/2, print_bexpr/1]). | |
35 | :- use_module(probsrc(error_manager), [add_error_and_fail/2]). | |
36 | :- use_module(probsrc(state_space),[state_space_reset/0]). | |
37 | ||
38 | :- use_module(symbolic_model_checker(predicate_handling)). | |
39 | :- use_module(symbolic_model_checker(solver_handling)). | |
40 | :- use_module(symbolic_model_checker(unsat_core_generalization)). | |
41 | :- use_module(symbolic_model_checker(mic_generation)). | |
42 | ||
43 | % algorithm for computing the generalization of counter-examples | |
44 | % the unsat_core one tries to drop parts of assignements that | |
45 | % do not lead to the failling consecution query | |
46 | % use_generalization(none). | |
47 | use_generalization(unsat_core). | |
48 | ||
49 | % algorithm for computing the minimal inductive clause | |
50 | mic_algorithm(down). | |
51 | % mic_algorithm(ctgDown). | |
52 | ||
53 | counter_example_can_be_replayed :- | |
54 | \+ use_generalization(unsat_core). | |
55 | ||
56 | ic3_symbolic_model_check(Res) :- | |
57 | statistics(walltime,[Start,_]), | |
58 | catch(symbolic_model_check_aux(Res),solver_and_provers_too_weak,Res=solver_and_provers_too_weak), | |
59 | statistics(walltime,[End,_]), | |
60 | Took is End - Start, | |
61 | get_counter(ic3solvercalls,Calls), | |
62 | format('Result of IC3 based model checking: ~w,~nIC3 took ~w ms and ~w solver calls~n',[Res,Took,Calls]). | |
63 | ||
64 | symbolic_model_check_aux(Result) :- | |
65 | % count number of solver calls | |
66 | counter_init, new_counter(ic3solvercalls), | |
67 | % fetch the invariant we want to check | |
68 | b_get_invariant_from_machine(Invariant), | |
69 | % and the predicate describing the initial state | |
70 | get_initial_state_predicate(Init), | |
71 | % debug output | |
72 | translate_bexpression(Init,PPInit), translate_bexpression(Invariant,PPInvariant), | |
73 | format('Symbolic Model Checking:~nProperty: ~w~nInitial Predicate: ~w~n',[PPInvariant,PPInit]), | |
74 | symbolic_model_check(Invariant,Init,Result). | |
75 | ||
76 | % check base cases (length 0 and 1) first - as in Bradleys implementation | |
77 | symbolic_model_check(Property,Init,ResultOut) :- | |
78 | % ce of length 0? check if Init => Invariant | |
79 | create_implication(Init,Property,Predicate), | |
80 | % debug output | |
81 | translate_bexpression(Predicate,PPPredicate), | |
82 | format('~nChecking for Counter-Example of Length 0: ~w~n',[PPPredicate]), | |
83 | solve_negated(Predicate,Result), | |
84 | functor(Result,ForPrint,_), | |
85 | format('Counter-Example of Length 0 exists: ~w~n', [ForPrint]), | |
86 | Result = solution(Sol), !, | |
87 | create_state_predicate(Sol,StatePred), | |
88 | replay_counter_example([StatePred]), | |
89 | ResultOut = counterexample_found. | |
90 | symbolic_model_check(Property,Init,ResultOut) :- | |
91 | % ce of length 1? check if Init & Transition => Invariant' | |
92 | prime_predicate(Property,PrimedProperty), | |
93 | get_single_transition_predicate(OpName,Transition), | |
94 | conjunct_predicates([Init,Transition],LHS), | |
95 | create_implication(LHS,PrimedProperty,Predicate), | |
96 | % debug output | |
97 | translate_bexpression(Predicate,PPPredicate), | |
98 | format('~nChecking for Counter-Example of Length 1: ~w~n',[PPPredicate]), | |
99 | solve_negated(Predicate,Result), | |
100 | functor(Result,ForPrint,_), | |
101 | format('Counter-Example of Length 1 (transition ~w) exists: ~w~n', [OpName,ForPrint]), | |
102 | Result = solution(Sol), !, | |
103 | handle_counter_example(Sol,[]), | |
104 | ResultOut = counterexample_found. | |
105 | % recursively check other cases. this corresponds to method "check" of Bradleys reference implementation | |
106 | % internal data structures taken from Bradleys original paper: | |
107 | % frame(F_i as in the paper (+transition), set of clauses to check for convergence) | |
108 | % i/K (= level) is the key to the AVL tree | |
109 | % obligation(state / predicate, primary inputs / predicate, list of successor states) | |
110 | % level is the key to the priority queue | |
111 | symbolic_model_check(Property,Init,Result) :- | |
112 | K = 1, | |
113 | % two initial frames: | |
114 | % F_0 holds the initial condition | |
115 | % F_1 holds the property | |
116 | % further frames will be added by extend later on | |
117 | % we store the transition in the frames as well in order to spare passing it around | |
118 | b_get_properties_from_machine(PropertiesOnConstants), | |
119 | F_0 = frame([Init,PropertiesOnConstants],[]), F_1 = frame([Property,PropertiesOnConstants],[]), | |
120 | empty_avl(E), | |
121 | avl_store(0,E,F_0,FramesT), | |
122 | avl_store(1,FramesT,F_1,Frames), | |
123 | catch(symbolic_model_check_fx(K,Frames,Property,Init,Result), | |
124 | ic3_counter_example(Solution,Succs), | |
125 | (handle_counter_example(Solution,Succs),Result=counterexample_found)). | |
126 | ||
127 | symbolic_model_check_fx(K,Frames,Property,Init,Result) :- | |
128 | % initially, K is 1 | |
129 | % the algorithm tries to maintain the following invariants: | |
130 | % (1) ! i . i > 0 . I => F_i | |
131 | % (2) ! i . i > 0 . F_i => P (P = Property to check / Invariant) | |
132 | % (3) ! i . i > 0 . clauses(F_{i+1}) <: clauses(F_i) | |
133 | % (4) ! i . 0 <= i < k . F_i & T => F_{i+1}' | |
134 | (debug:debug_mode(off) -> true | |
135 | ; format('~nIC3 k=~w~n',[K]), print_frames(Frames),nl), | |
136 | extend(Frames,K,[Property],Frames2), | |
137 | (debug:debug_mode(off) -> true | |
138 | ; format('IC3 k=~w after extend~n',[K]), print_frames(Frames2),nl), | |
139 | % strengthen establishes (1) - (4) | |
140 | % if impossible => counter example | |
141 | strengthen(K,Frames2,Property,FramesOut), !, | |
142 | % propagate_clauses assumes (1) - (4) | |
143 | % pushes clauses from one frame to a later one (as far as possible) | |
144 | (debug:debug_mode(off) -> true | |
145 | ; format('IC3 k=~w after strengthen~n',[K]), print_frames(FramesOut),nl), | |
146 | propagate_clauses(K,FramesOut,FramesOut2), | |
147 | (debug:debug_mode(off) -> true | |
148 | ; format('IC3 k=~w after propagate_clauses~n',[K]), print_frames(FramesOut2),nl), | |
149 | (check_convergence(FramesOut2) | |
150 | -> Result = property_holds | |
151 | ; K2 is K + 1, | |
152 | symbolic_model_check_fx(K2,FramesOut2,Property,Init,Result)). | |
153 | ||
154 | print_frames(Frames) :- | |
155 | avl_to_list(Frames,List), | |
156 | print_frames_list(List). | |
157 | print_frames_list([]). | |
158 | print_frames_list([K-frame(_,Clauses)|T]) :- | |
159 | length(Clauses,L), | |
160 | format(' frame ~w holds ~w clauses:~n ',[K,L]), | |
161 | print_clauses(Clauses), | |
162 | print_frames_list(T). | |
163 | ||
164 | print_clauses([]) :- nl. | |
165 | print_clauses([C|Cs]) :- | |
166 | disjunct_predicates(C,DC), | |
167 | translate:print_bexpr(DC),nl, print(' '), | |
168 | print_clauses(Cs). | |
169 | ||
170 | extend(FramesIn,K,Content,FramesOut) :- | |
171 | % any new frame holds the properties in addition to the content (usually the transition) | |
172 | NextFrameId is K + 1, | |
173 | b_get_properties_from_machine(PropertiesOnConstants), | |
174 | avl_store(NextFrameId,FramesIn,frame([PropertiesOnConstants|Content],[]),FramesOut). | |
175 | ||
176 | propagate_clauses(K,Frames,FramesOut) :- | |
177 | propagate_clauses_aux(1,K,Frames,FramesOut). | |
178 | propagate_clauses_aux(I,K,Frames,FramesOut) :- | |
179 | % for i = 1 .. K call propagate_clauses_on_level | |
180 | propagate_clauses_on_level(I,Frames,FramesTemp), | |
181 | (I < K -> J is I+1, propagate_clauses_aux(J,K,FramesTemp,FramesOut) ; true). | |
182 | propagate_clauses_on_level(I,Frames,FramesOut) :- | |
183 | % pushes from F_I to F_{I+1} | |
184 | IP1 is I + 1, | |
185 | clauses_on_level(I,Frames,_F_From,ClausesFrom), | |
186 | in_solver_on_level(I,Frames,IS), | |
187 | ||
188 | include(inductive_clause(IS),ClausesFrom,InductiveClauses), | |
189 | ||
190 | add_clauses_to_level(IP1,Frames,InductiveClauses,FramesOut). | |
191 | ||
192 | inductive_clause(F_I,ClauseDisjuncts) :- | |
193 | get_transition_predicate(T), | |
194 | disjunct_predicates(ClauseDisjuncts,Clause), | |
195 | % checks if a single clause is inductive relative to F_i | |
196 | % by verifying if F_i => C' (F_i includes T) | |
197 | % i.e. there is a contradiction in not C' & F_i | |
198 | create_negation(Clause,NegClause), | |
199 | prime_predicate(NegClause,NegCPrime), | |
200 | conjunct_predicates([NegCPrime,T|F_I],Pred), | |
201 | solve(Pred,Result), !, | |
202 | Result = contradiction_found(_). | |
203 | ||
204 | strengthen(K,Frames,Property,FramesOut) :- | |
205 | findall(op(OpName,Trans),get_single_transition_predicate(OpName,Trans),Transitions), | |
206 | strengthen_aux1(Transitions,K,Property,Frames,FramesOut). | |
207 | ||
208 | strengthen_aux1([],_K,_Property,Frames,Frames). | |
209 | strengthen_aux1([Transition|Transitions],K,Property,Frames,FramesOut) :- | |
210 | strengthen_loop(Transition,K,Property,Frames,FramesTemp), | |
211 | strengthen_aux1(Transitions,K,Property,FramesTemp,FramesOut). | |
212 | ||
213 | strengthen_loop(op(OpName,Transition),K,Property,FramesIn,FramesOut) :- | |
214 | get_preference(use_po,UsePO), | |
215 | strengthen_get_property(UsePO,OpName,Property,PrimedNegProperty), | |
216 | in_solver_on_level(K,FramesIn,IS), | |
217 | conjunct_predicates([Transition,PrimedNegProperty|IS],RPred), | |
218 | solve(RPred,Result), | |
219 | strengthen_aux(Result,Transition,K,Property,FramesIn,FramesOut). | |
220 | ||
221 | strengthen_get_property(false,_OpName,Property,PrimedNegProperty) :- | |
222 | create_negation(Property,NegProperty), | |
223 | prime_predicate(NegProperty,PrimedNegProperty). | |
224 | strengthen_get_property(true,OpName,FullProperty,Primed) :- | |
225 | (b_specialized_invariant_for_op(OpName,Property) | |
226 | -> true ; Property = FullProperty), | |
227 | (get_proven_invariant(OpName,Proven) | |
228 | -> true ; Proven = b(truth,pred,[])), | |
229 | create_negation(Property,NegProperty), | |
230 | conjunct_predicates([Proven,NegProperty],ToPrime), | |
231 | prime_predicate(ToPrime,Primed). | |
232 | ||
233 | % strengthen searches for a counter example to the property | |
234 | % it searches for a state that is permitted by F_i that is the predecessor | |
235 | % of a state that violates the property | |
236 | % if there is a contradiction, we are done, because there is no counter example | |
237 | strengthen_aux(contradiction_found(_),_TransitionPredicate,_K,_Property,Frames,Frames). | |
238 | % if there is a counter example, we need to strenghten the frames | |
239 | % in order to make it spurious, i.e. (1) - (4) have to be reestablished | |
240 | strengthen_aux(solution(Wit),TransitionPredicate,K,Property,Frames,FramesOut) :- | |
241 | % extract predecessor | |
242 | create_state_predicate(Wit,Pre), | |
243 | create_succ_state_predicate(Wit,SStatePred), | |
244 | create_primary_inputs_predicate(Wit,Inputs), | |
245 | K2 is K-2, | |
246 | % finds the highest N to which not Pre can be added | |
247 | % this can fail if the counter example is not spurious | |
248 | inductively_generalize(Pre,[Pre],K2,K,Frames,FramesT,N), !, | |
249 | N2 is N + 1, | |
250 | empty_heap(H), | |
251 | generalize_and_add(TransitionPredicate,H,N2,Frames,Pre,Inputs,[SStatePred],Obls), | |
252 | % Obls is a heap of proof obligations that need to hold | |
253 | % push generalization handles those that are on level =< K | |
254 | push_generalization(Obls,K,FramesT,FramesT2), | |
255 | strengthen(K,FramesT2,Property,FramesOut). | |
256 | ||
257 | % generalize Pre and / or split it into multiple obligations | |
258 | generalize_and_add(TP,HIn,N,_Frames,State,Inputs,SuccStates,HOut) :- | |
259 | use_generalization(none), !, | |
260 | add_to_heap(HIn,N,obligation(State,TP,Inputs,SuccStates),HOut). | |
261 | generalize_and_add(TP,HIn,N,Frames,State,Inputs,SuccStates,HOut) :- | |
262 | use_generalization(unsat_core), !, | |
263 | unsat_core_generalization(TP,HIn,N,Frames,State,Inputs,SuccStates,HOut). | |
264 | generalize_and_add(_,_,_,_,_,_,_,_) :- | |
265 | add_error_and_fail(strengthen_aux,'Invalid generalization method selected'). | |
266 | ||
267 | push_generalization(Obligations,_K,Frames,Frames) :- | |
268 | empty_heap(Obligations). % no proof obligations left | |
269 | push_generalization(Obligations,K,Frames,Frames) :- | |
270 | min_of_heap(Obligations,N,_S), | |
271 | N > K, !. % only proof obligations on a higher level left - fine for level K | |
272 | push_generalization(Obligations,K,Frames,FramesOut) :- | |
273 | % grep the proof obligations with the lowest level - somewhat a heuristic | |
274 | min_of_heap(Obligations,N,obligation(S,T,Inputs,Succs)), | |
275 | in_solver_on_level(N,Frames,IS), | |
276 | prime_predicate(S,SPrimed), | |
277 | conjunct_predicates([SPrimed,T|IS],Pred), | |
278 | solve(Pred,Result), !, | |
279 | push_generalization_aux(Result,N,obligation(S,T,Inputs,Succs),Obligations,K,Frames,FramesT,ObligationsOut), | |
280 | push_generalization(ObligationsOut,K,FramesT,FramesOut). | |
281 | ||
282 | push_generalization_aux(solution(Pre),N,obligation(S,T,Inputs,Succs),Obligations,K,Frames,FramesOut,ObligationsOut) :- | |
283 | N2 is N - 2, | |
284 | create_state_predicate(Pre,PrePred), !, | |
285 | inductively_generalize(PrePred,[S|Succs],N2,K,Frames,FramesOut,M), !, | |
286 | M2 is M + 1, | |
287 | generalize_and_add(T,Obligations,M2,Frames,PrePred,Inputs,[S|Succs],ObligationsOut). | |
288 | push_generalization_aux(contradiction_found(_),N,obligation(S,T,Inputs,Succs),Obligations,K,Frames,FramesOut,ObligationsOut) :- | |
289 | inductively_generalize(S,[S|Succs],N,K,Frames,FramesOut,M), !, | |
290 | delete_from_heap(Obligations,N,obligation(S,T,Inputs,Succs),ObligationsT), | |
291 | M2 is M + 1, | |
292 | add_to_heap(ObligationsT,M2,obligation(S,T,Inputs,Succs),ObligationsOut). | |
293 | ||
294 | :- public print_heap/1. % for debugging | |
295 | print_heap(Obls) :- | |
296 | heap_to_list(Obls,List), | |
297 | write('Content of Obligations Heap:'), nl, | |
298 | print_heap2(List). | |
299 | print_heap2([]). | |
300 | print_heap2([Key-Datum|T]) :- | |
301 | Datum = obligation(Predicate,_,_,Succs), | |
302 | translate_bexpression(Predicate,PPPredicate), | |
303 | format('On level ~w state predicate ~w~n',[Key,PPPredicate]), | |
304 | format('Successors:~n',[]), | |
305 | print_succs(Succs), | |
306 | print_heap2(T). | |
307 | print_succs([]). | |
308 | print_succs([P|T]) :- | |
309 | write(' '), print_bexpr(P),nl, | |
310 | print_succs(T). | |
311 | ||
312 | handle_counter_example(Solution,Succs) :- | |
313 | create_state_predicate(Solution,StatePred), | |
314 | create_succ_state_predicate(Solution,SuccStatePred), !, | |
315 | format('Counterexample found. Trace:~n',[]), | |
316 | print_succs([StatePred,SuccStatePred|Succs]), | |
317 | (create_constants_state_predicate(Solution,ConstantsStatePred) | |
318 | -> replay_counter_example([ConstantsStatePred,StatePred,SuccStatePred|Succs]) | |
319 | ; replay_counter_example([StatePred,SuccStatePred|Succs])). | |
320 | replay_counter_example(A) :- | |
321 | counter_example_can_be_replayed | |
322 | -> state_space_reset,replay_aux(A,_,0) ; true. | |
323 | :- use_module(probsrc(b_trace_checking),[find_successor_state/4]). | |
324 | replay_aux([],_,_). | |
325 | replay_aux([P|Ps],CurId,LineNr) :- | |
326 | find_successor_state(CurId,P,SuccID,LineNr), | |
327 | L1 is LineNr + 1, | |
328 | replay_aux(Ps,SuccID,L1). | |
329 | ||
330 | inductively_generalize(S,Succs,Min,_K,Frames,_FramesOut,_N) :- | |
331 | % if min < 0 and sat(F_0 & T & neg S & S') | |
332 | % fail to report counter example | |
333 | Min < 0, | |
334 | in_solver_on_level(0,Frames,IS), | |
335 | prime_predicate(S,SPrimed), | |
336 | create_negation(S,SNegated), | |
337 | get_transition_predicate(T), | |
338 | conjunct_predicates([SNegated,SPrimed,T|IS],Pred), | |
339 | solve(Pred,Result), | |
340 | Result = solution(Solution), !, | |
341 | throw(ic3_counter_example(Solution,Succs)). | |
342 | inductively_generalize(S,_,Min,K,Frames,FramesOut,N) :- | |
343 | I is max(1, Min + 1), | |
344 | inductively_generalize_aux(I,S,K,Frames,FramesOut,N). | |
345 | inductively_generalize_aux(I,S,K,Frames,FramesOut,N) :- | |
346 | I =< K, | |
347 | in_solver_on_level(I,Frames,IS), | |
348 | prime_predicate(S,SPrimed), | |
349 | create_negation(S,SNegated), | |
350 | get_transition_predicate(T), | |
351 | conjunct_predicates([SNegated,SPrimed,T|IS],Pred), | |
352 | solve(Pred,Result), !, | |
353 | (Result = solution(_) -> N is I - 1, generate_clause(S,N,K,Frames,FramesOut) ; | |
354 | Result = contradiction_found(_) -> I2 is I + 1, inductively_generalize_aux(I2,S,K,Frames,FramesOut,N)). | |
355 | inductively_generalize_aux(I,S,K,Frames,FramesOut,K) :- | |
356 | I > K, | |
357 | generate_clause(S,K,K,Frames,FramesOut). | |
358 | ||
359 | generate_clause(S,I,K,Frames,FramesOut) :- | |
360 | mic(S,I,K,Frames,FramesT,Q), | |
361 | translate_bexpression(S,PS), | |
362 | translate_bexpression(Q,PQ), | |
363 | format('minimal inductive clause computation:~n~w to~n~w~n',[PS,PQ]), | |
364 | conjunction_to_list(Q,Conjuncts), | |
365 | maplist(create_negation,Conjuncts,NegConjuncts), | |
366 | generate_clause_aux(1,NegConjuncts,I,FramesT,FramesOut). | |
367 | generate_clause_aux(J,NegS,I,Frames,FramesOut) :- | |
368 | J =< I + 1, !, | |
369 | add_clauses_to_level(J,Frames,[NegS],FramesT), | |
370 | J2 is J + 1, | |
371 | generate_clause_aux(J2,NegS,I,FramesT,FramesOut). | |
372 | generate_clause_aux(_,_,_,Frames,Frames). | |
373 | ||
374 | mic(S,I,_K,Frames,Frames,Q) :- | |
375 | mic_algorithm(down), !, | |
376 | conjunction_to_list(S,Conjuncts), | |
377 | down(Conjuncts,I,Frames,ConjunctsOut), | |
378 | conjunct_predicates(ConjunctsOut,Q). | |
379 | mic(S,I,K,FramesIn,FramesOut,Q) :- | |
380 | mic_algorithm(ctgDown), !, | |
381 | conjunction_to_list(S,Conjuncts), | |
382 | ctgDown(Conjuncts,I,K,FramesIn,FramesOut,ConjunctsOut), | |
383 | conjunct_predicates(ConjunctsOut,Q). | |
384 | ||
385 | check_convergence(Frames) :- | |
386 | avl_to_list(Frames,L), | |
387 | check_convergence_aux(L). | |
388 | check_convergence_aux([_-F1,K2-F2|Fs]) :- | |
389 | % the algorithm converges if two frames hold the same set of clauses | |
390 | F1 = frame(_,Clauses1), | |
391 | F2 = frame(_,Clauses2), | |
392 | % assumes ordered sets | |
393 | (Clauses1=Clauses2 %ordsets:ord_symdiff(Clauses1, Clauses2, []) | |
394 | ; check_convergence_aux([K2-F2|Fs])). |