1 % (c) 2009-2023 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(delay,[delay_setof_wf/7,
6 delay_setof_check_wf/9,
7 delay_setof_list/4,
8 %delay_call/2, delay_call/3,
9 %delay_not/3, compute_wait_variables/3
10 not_with_enum_warning/4
11 ]).
12
13 % meta_predicate annotations should appear before loading any code:
14 :- meta_predicate my_findall(-,0,-,-).
15 :- meta_predicate my_findall_catch(-,0,-,-,-).
16 :- meta_predicate my_findall_catch(-,0,-,-,-,-).
17 :- meta_predicate my_findall_check(-,0,-,-,0,0,-,-).
18
19 :- meta_predicate delay_setof_wf(-,0,-,-,-,-,-).
20 %:- meta_predicate block_my_findall_catch_wf(-,-,0,-,-,-).
21
22 :- meta_predicate delay_setof_check_wf(-,0,-,-,-,0,0,-,-).
23 :- meta_predicate block_findall_check(-,-,0,-,0,0,-,-,-,-).
24
25 :- meta_predicate delay_setof_list(-,0,-,-).
26
27
28 %:- meta_predicate delay_call(0,-,-).
29 %:- meta_predicate delay_call(0,-).
30
31 %:- meta_predicate delay_not(0,-,-).
32 :- meta_predicate not_with_enum_warning(0,-,?,?).
33 :- meta_predicate not_with_enum_warning2(0,-,-,-).
34
35 % --------------
36
37
38
39 :- use_module(module_information,[module_info/2]).
40 :- module_info(group,kernel).
41 :- module_info(description,'Utilities to delay calls until sufficiently instantiated.').
42
43 %:- use_module(self_check).
44
45 :- use_module(tools).
46 :- use_module(debug). /* so that calls can call unqualified debug_prints */
47
48 :- use_module(error_manager).
49 :- use_module(kernel_tools).
50 :- use_module(kernel_waitflags,[add_wd_error_span_now/4, init_wait_flags/2,ground_wait_flags/1,
51 pending_abort_error/1]).
52
53
54 :- use_module(tools_printing,[print_term_summary/1]).
55 :- load_files(library(system), [when(compile_time), imports([environ/2])]).
56 :- use_module(tools_meta,[call_residue/2]).
57
58 :- if(environ(prob_debug_watch_flag,true)).
59 :- meta_predicate call_residue_check(0).
60 call_residue_check(Call) :- call(Call).
61 my_findall(X,P,L,_) :-
62 statistics(runtime,[Start,_]),
63 findall(X,P,L),
64 statistics(runtime,[End,_]),
65 Tot is End-Start,
66 (Tot>50 -> nl, %nl,print(Call),nl,
67 print('*** FINDALL: '),print_term_summary(P),
68 print('*** exceeded limit: '), print(Tot), print(' ms'),nl,
69 length(L,Len),
70 print('*** SOLUTIONS: '), print(Len), nl
71 ; true).
72 :- elif(environ(prob_safe_mode,true)). % not enabled by default; performance impact seems small; except for test 469
73 % for test 469 we have a call like this: delay:custom_explicit_sets:test_closure_and_convert/5: [_zzzz_unary]: [integer]: PRED(disjunct/2,z_ : #3906:{1,3,...,3906,3907} or z_ : 3908 .. 19532): VARIABLE: _348117: no_wf_available
74 % with 19531 solutions
75 :- meta_predicate call_residue_check(0).
76 call_residue_check(Call) :- % print_term_summary(Call),nl,
77 call_residue(Call,Residue),
78 (Residue = [] -> true
79 ; add_internal_error('Call had residue: ',residue(Residue)/call(Call)),
80 print_goal(Residue),nl
81 ).
82 my_findall(X,P,L,ExpectedFinalResult) :-
83 (ExpectedFinalResult==[]
84 -> \+(call_residue_check(P)),
85 L=[] % we know the end result to be empty; not necessary to try and find all solutions; <--- we should probably treat this somewhere else to ensure that we do not throw all_solutions enumeration warnings
86 ; findall(X,call_residue_check(P),L)
87 ).
88 :- else.
89 my_findall(X,P,L,ExpectedFinalResult) :-
90 (ExpectedFinalResult==[]
91 -> \+(P),L=[] % we know the end result to be empty; not necessary to try and find all solutions; <--- we should probably treat this somewhere else to ensure that we do not throw all_solutions enumeration warnings
92 ; findall(X,P,L)).
93 %; findall(X,(P,print(sol(X)),nl,nl,trace),L)).
94 :- endif.
95
96
97
98
99 my_findall_catch(X,P,L,ExpectedFinalResult,Span) :-
100 my_findall_catch(X,P,L,ExpectedFinalResult,EnumWarningWhichOcc,Span),
101 (enum_warning_occ(EnumWarningWhichOcc) -> throw(EnumWarningWhichOcc) ; true).
102
103 enum_warning_occ(EnumWarningWhichOcc) :-
104 nonvar(EnumWarningWhichOcc),EnumWarningWhichOcc=enumeration_warning(_,_,_,_,_).
105
106 my_findall_catch(X,P,L,ExpectedFinalResult,EnumWarningWhichOcc,Span) :-
107 enter_new_error_scope(Level,my_findall_catch),
108 throw_enumeration_warnings_in_scope(Level,critical,Span), % critical enumeration warnings are thrown straight away
109 call_cleanup(
110 my_findall(X,P,L,ExpectedFinalResult),
111 (event_occurred_in_error_scope(enumeration_warning(A,B,C,D,E))
112 -> exit_error_scope(Level,_,my_findall_catch_error),
113 % do we need to throw the enumeration warning ?
114 EnumWarningWhichOcc=enumeration_warning(A,B,C,D,E),
115 debug_println(9,my_findall_catch(enumeration_warning(A,B,C,D,E))),
116 true %throw(enumeration_warning(A,B,C,D,E))
117 ; exit_error_scope(Level,_,my_findall_catch_no_error),
118 EnumWarningWhichOcc=false)).
119
120 :- use_module(runtime_profiler,[observe_runtime_wf/4]).
121 my_findall_check(X,P,L,ExpectedFinalResult,TimeOutCode,VirtualTimeOutCode,WF,Span) :-
122 statistics(runtime,[Start,_]),
123 get_total_number_of_errors(Nr1), % now also does count stored wd errors, see WD_SetCompr_Error
124 %format('Start closure expansion at ~w ms~n',[Start]),print_message_span(Span),nl,
125 call_cleanup((my_findall_catch(X,P,L,ExpectedFinalResult,EnumWarningWhichOcc,Span),Ok=true),
126 (Ok==true,EnumWarningWhichOcc==false
127 -> get_total_number_of_errors(Nr2),
128 (Nr2>Nr1 -> add_set_compr_err(Nr1,Nr2,Span,WF),
129 %TODO: this message is probably less useful / redundant now due to call stack info display
130 fail % TO DO: see comment below in delay_setof_wf
131 ; observe_runtime_wf(Start,'==PERF-MESSAGE==> Long closure expansion: ',Span,WF) %, print(done),nl
132 %,statistics(runtime,[End,_]), Tot is End-Start,
133 %(Tot>10 -> length(L,Len),add_message(delay,'Long closure expansion:',ms_sols(Tot,Len),Span) ; true)
134 )
135 ; /* failure or time-out; --> time-out as findall should never fail */
136 statistics(runtime,[End,_]), Tot is End-Start,
137 (enum_warning_occ(EnumWarningWhichOcc)
138 -> (VirtualTimeOutCode = _:true -> true
139 ; print('### VIRTUAL TIME-OUT after: '), print(Tot), print(' ms'),nl,
140 call(VirtualTimeOutCode)
141 ), %print(throw(EnumWarningWhichOcc)),nl,
142 throw(EnumWarningWhichOcc) % ??? this is a set-comprehension expression; failure would mean not-well-defined
143 ; Tot>1000 -> % TO DO: could also be CTRL-C ! we could use catch_interrupt_assertion_call(my_findall_catch) or catch(my_finall_catch,user_interrupt_signal)
144 call(TimeOutCode), % Warning: this call is also called if an outer-time-out is triggered !
145 format('Runtime for set comprehension expansion until TIME-OUT: ~w ms~n',[Tot])
146 % WHY DON'T WE FAIL HERE ---> because exception is passed on to outer calls after call_cleanup
147 ; true) % check if the findall is probably responsible for the time-out
148 )).
149
150 add_set_compr_err(Nr1,Nr2,Span,WF) :-
151 NrErrs is Nr2-Nr1,
152 (NrErrs=1 -> Msg= 'Error occurred during expansion of set comprehension'
153 ; ajoin(['Errors (',NrErrs,') occurred during expansion of set comprehension'],Msg)
154 ),
155 add_wd_error_span_now(Msg,'',Span,WF).
156 % add_error_wf(well_definedness_error,Msg,'',Span,WF).
157
158 /* ----------------------------------------- */
159
160 /* Below is a findall that delays until all
161 non-output variables have become ground.
162 Ideally, one would want a delay_findall that
163 figures out by itself when it has all the answers
164 (by looking at call_residue) and that may
165 even progressively instantiate the output list
166 such as in:
167 delay_findall(X,delay_member(X,[a|T]),R), delay_member(Z,R)
168 But this will require more involved machinery.
169 */
170
171
172 :- use_module(kernel_objects,[equal_object_wf/4]).
173 :- use_module(kernel_tools,[ground_value_check/2]).
174
175 % the following is called by expand_normal_closure_direct
176 delay_setof_wf(V,G,FullSetResult,WVars,Done,WF,Span) :- %print(delay_set_of(V)),nl,
177 ground_value_check(WVars,GW),
178 block_my_findall_catch_wf2(GW,V,G,FullSetResult,Done,WF,Span).
179 % comment in to perform idling to allow outer co-routines to run; useful when WD condition attached to Body G
180 %:- use_module(kernel_waitflags,[get_idle_wait_flag/3]).
181 %:- block block_my_findall_catch_wf(-,?,?,?,?,?).
182 %block_my_findall_catch_wf(_,V,G,FullSetResult,Done,WF) :-
183 % get_idle_wait_flag(delay_setof_wf,WF,LWF),
184 % print(idling(WF,LWF)),nl,
185 % block_my_findall_catch_wf2(LWF,V,G,FullSetResult,Done,WF,Span).
186 :- block block_my_findall_catch_wf2(-,?,?,?,?,?,?).
187 block_my_findall_catch_wf2(_,V,G,FullSetResult,Done,WF,Span) :-
188 get_total_number_of_errors(Nr1),
189 my_findall_catch(V,G,RRes,FullSetResult,Span),
190 transform_result_into_set(RRes,SetRes),
191 get_total_number_of_errors(Nr2), %print(f(Nr1,Nr2)),nl,
192 (Nr2>Nr1
193 -> add_set_compr_err(Nr1,Nr2,Span,WF), % should we use add_wd_error_span('...: ','@compr'(..),Span,WF)
194 fail
195 % TO DO?: we could add_abort error here instead of adding a real error
196 % for this, however, the inner abort_errors should only be recorded, not yet raised for the user
197 ; true),
198 ? equal_object_wf(SetRes,FullSetResult,delay_setof_wf,WF),
199 Done=true.
200
201
202 delay_setof_check_wf(V,G,FullSetResult,WVars,Done,TimeOutCode,VirtualTimeOutCode,WF,Span) :- %print(delay_set_of(V)),nl,
203 %% print(wait_vars(WVars,G)),nl, %%
204 ground_value_check(WVars,GW),
205 block_findall_check(GW,V,G,FullSetResult,TimeOutCode,VirtualTimeOutCode,Done,FullSetResult,WF,Span).
206
207 :- block block_findall_check(-,?,?,?,?,?,?,?,?,?).
208 block_findall_check(_,V,G,FullSetResult,TimeOutCode,VirtualTimeOutCode,Done,FullSetResult,WF,Span) :-
209 my_findall_check(V,G,RRes,FullSetResult,TimeOutCode,VirtualTimeOutCode,WF,Span),
210 transform_result_into_set(RRes,SetRes),
211 % will generate AVL tree
212 % print(delay_setof_result(SetRes,FullSetResult)),nl, translate:print_bvalue(SetRes),nl,
213 equal_object_wf(SetRes,FullSetResult,delay_setof_check_wf,WF),
214 Done=true.
215
216 :- use_module(tools_printing,[print_term_summary/1]).
217 :- use_module(custom_explicit_sets,[convert_to_avl/2]).
218 :- use_module(closures,[is_symbolic_closure/1]).
219 transform_result_into_set([ONE],SetRes) :- is_symbolic_closure(ONE),!,
220 debug_println(9,not_expanding_singleton_delay_result),
221 % do not convert_to_avl; would lead to enumeration warning (see test 2157)
222 SetRes = [ONE].
223 transform_result_into_set(RRes,SetRes) :- % print(transform_result_into_set(RRes,SetRes)),
224 (convert_to_avl(RRes,SetRes) % we are sure that RRes is ground !!??
225 -> true
226 ; print_term_summary(convert_to_avl_failed(RRes,SetRes)),
227 convert_list_of_expressions_into_set(RRes,SetRes)).
228
229 :- use_module(b_interpreter,[convert_list_of_expressions_into_set_wf/4]).
230 convert_list_of_expressions_into_set(List,Set) :-
231 init_wait_flags(WF,[convert_list_of_expressions_into_set]),
232 convert_list_of_expressions_into_set_wf(List,Set,set(any),WF),
233 ground_wait_flags(WF).
234
235 /* same as above but Result is a list of elements; */
236 /* the list should not be interpreted as a set, but each element is a value for a parameter */
237 /* There is a very small chance that the same value is represented twice in the list (e.g., once as closure once as explicit list) */
238 % last argument is simple waitflag
239
240 :- block delay_setof_list(?,?,?,-).
241 delay_setof_list(V,G,FullSetResult,_) :-
242 %print_quoted(findall(V,G,RRes)),nl,
243 my_findall(V,G,RRes,FullSetResult),
244 %% print_message(delay_seotof_list_rres(RRes)), %%
245 ll_norm(RRes,NormRRes),
246 sort(NormRRes,SNormRRes), % removes duplicates
247 FullSetResult=SNormRRes.
248
249
250 % normalise a list of list of values; the inner lists are not sets but values for parameters
251 ll_norm([],[]).
252 ll_norm([El1|T1],[El2|T2]) :- l_check_norm_required(El1,El2,NormRequired),
253 (var(NormRequired) -> T1=T2 % no normalisation required: simply copy rest of list
254 % TO DO: we could also statically determine based on parameter types whether normalising is required
255 ; ll_norm2(T1,T2)).
256
257 :- use_module(store,[l_normalise_values/2]).
258 ll_norm2([],[]).
259 ll_norm2([El1|T1],[El2|T2]) :- l_normalise_values(El1,El2), ll_norm2(T1,T2).
260
261 % normalise a value and return norm_required in last argument if it is needed
262 l_check_norm_required([],[],_).
263 l_check_norm_required([H|T],[NH|NT],NormRequired) :-
264 check_norm_required(H,NH,NormRequired),
265 l_check_norm_required(T,NT,NormRequired).
266
267 check_norm_required(pred_true,R,_) :- !, R=pred_true.
268 check_norm_required(pred_false,R,_) :- !, R=pred_false.
269 check_norm_required(int(S),R,_) :- !, R=int(S).
270 check_norm_required(fd(X,T),R,_) :- !, R=fd(X,T).
271 check_norm_required(string(S),R,_) :- !, R=string(S).
272 check_norm_required((A,B),(NA,NB),NormRequired) :- !,
273 check_norm_required(A,NA,NormRequired), check_norm_required(B,NB,NormRequired).
274 % to do: add record and freeval
275 check_norm_required(V,NV,norm_required) :-
276 store:normalise_value(V,NV). % does not normalise closure/3 values
277
278
279
280
281
282 %:- use_module(tools,[remove_variables/3]).
283 %compute_wait_variables(WaitTerm,OutputVars,WaitVars) :-
284 % term_variables(WaitTerm,Vars),
285 % term_variables(OutputVars,RealOutputVars),
286 % remove_variables(Vars,RealOutputVars,WaitVars).
287
288
289 %delay_call(Call,WaitTerm,OutputVars) :-
290 % compute_wait_variables(WaitTerm,OutputVars,WaitVars),
291 %print_message(delay(Call,WaitVars)),
292 % when(ground(WaitVars),Call).
293
294 %delay_call(Call,OutputVars) :-
295 % delay_call(Call,Call,OutputVars).
296
297 % LocalWF: created by create_inner_wait_flags; enumeration finished waitflag should not be grounded here
298 %delay_not(Call,OutputVars, LocalWF) :- % print_message(informational,delay_not(Call,OutputVars,LocalWF)),
299 % delay_call( not_with_enum_warning(Call, LocalWF) , OutputVars).
300
301 :- use_module(probsrc(kernel_waitflags),[add_message_wf/5]).
302 not_with_enum_warning(Call, LocalWF, PP, Span) :-
303 %% print_term_summary(neg_call(Call, LocalWF)), %%
304 enter_new_clean_error_scope(Level),
305 call_cleanup((not_with_enum_warning2(Call,Level,LocalWF,Span) -> Ok=true ; Ok=false),
306 ((var(Ok), silent_mode(off)
307 -> add_message_wf(delay,'TIME OUT or Exception inside: ',PP,Span,LocalWF)
308 ; true),
309 exit_error_scope(Level,_ErrOccured,not_with_enum_warn))),
310 Ok==true.
311
312 :- use_module(tools_printing,[print_goal/1]).
313 :- use_module(preferences,[get_preference/2]).
314 :- use_module(kernel_waitflags,[add_error_wf/5, add_warning_wf/5]).
315 not_with_enum_warning2(Call,Level,LocalWF,Span) :-
316 (pending_abort_error(LocalWF)
317 -> add_warning_wf(not_with_enum_warning,'Abort error pending before call:',Level,Span,LocalWF),nl
318 ; true),
319 ? (call_residue(Call,Residue) ->
320 % Call has succeeded, negation thus fails
321 (pending_abort_error(LocalWF)
322 -> write(pending_abort_error(LocalWF)),nl,
323 PENDING_ABORT = true
324 % we do not fail in this case, so that abort errors can be raised
325 % relevant for test 1966 or predicate f={1|->2} & not(#s.(s:1..2 & f(s)=3)) with -p TRY_FIND_ABORT TRUE
326 ; Residue \= [] ->
327 (get_preference(use_chr_solver,true)
328 -> add_warning_wf(not_with_enum_warning,'Call (in negation) had residue (possibly due to use of CHR making call_residue imprecise): ',residue(Residue)/call(Call),Span,LocalWF)
329 ; add_error_wf(not_with_enum_warning,
330 'Call (in negation) had residue: ',residue(Residue)/call(Call),Span,LocalWF),
331 print_goal(Residue),nl
332 ),
333 fail
334 ; clear_enumeration_warning_in_error_scope(Level), fail
335 % call has succeeded without pending abort errors
336 )
337 ; true
338 ),
339 % Note: everything else is ground; so Call should not trigger any other co-routines/enumeration ?!
340 Event = enumeration_warning(_,_,_,_,_),
341 (event_occurred_in_error_scope(Event) % critical ? only at this level ? or also below ?
342 -> PENDING_ABORT==true, %print(grd(LocalWF)),nl, kernel_waitflags:portray_waitflags(LocalWF),nl,
343 ground_wait_flags(LocalWF), % raise any potential abort errors
344 debug_println(19,grounded_wait_flag_for_pending_abort_error), % THIS print is important due to issue SPRM-20473 in SICStus Prolog which will otherwise *not* activate the pending co-routines attached to LocalWF !
345 fail
346 %, print_message(throwing(Event)), throw(Event) % or simply re-post enum warning ?
347 % we could also simply fail and raise an enumeration warning; meaning we havent explored all possibilities
348 % Note: at the moment the error is copied into the outer scope by copy_error_scope_events
349 ; true
350 ).
351
352
353