mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-13 21:04:00 +00:00
to the current coding standards. compiler/termination.m: Use state variables where appropriate. Use ho preds where appropriate. Reorder arguments where necessary to facilitate the above. Replace calls to module_info_pred_proc_info/5 with calls to module_info_pred_proc_info/4 where appropriate. Use predmode syntax. Replace calls to error/1 with calls to unexpected/2. Rewrite some of the error messages as some of them are a bit inaccurate. Fix the indentation in a few spots. Add an end_module declaration.
933 lines
34 KiB
Mathematica
933 lines
34 KiB
Mathematica
%----------------------------------------------------------------------------%
|
|
% Copyright (C) 1997-2001, 2003-2004 The University of Melbourne.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%----------------------------------------------------------------------------%
|
|
%
|
|
% termination.m
|
|
%
|
|
% Main author: crs.
|
|
% Significant modifications by zs.
|
|
%
|
|
% This termination analysis is based on the algorithm given by Gerhard Groeger
|
|
% and Lutz Plumer in their paper "Handling of Mutual Recursion in Automatic
|
|
% Termination Proofs for Logic Programs" which was printed in JICSLP '92
|
|
% (the proceedings of the Joint International Conference and Symposium on
|
|
% Logic Programming 1992) pages 336 - 350.
|
|
%
|
|
% Details about this implementation are covered in:
|
|
% Chris Speirs, Zoltan Somogyi, and Harald Sondergaard. Termination
|
|
% analysis for Mercury. In P. Van Hentenryck, editor, Static Analysis:
|
|
% Proceedings of the 4th International Symposium, Lecture Notes in Computer
|
|
% Science. Springer, 1997. A more detailed version is available for
|
|
% download from http://www.cs.mu.oz.au/publications/tr_db/mu_97_09.ps.gz
|
|
%
|
|
% Currently, this implementation assumes that all c_code terminates.
|
|
% It also fails to prove termination for any predicate that involves higher
|
|
% order calls.
|
|
%
|
|
% The termination analysis may use a number of different norms to calculate
|
|
% the size of a term. These are set by using the --termination-norm string
|
|
% option. To add a new norm, the following files must be modified:
|
|
%
|
|
% globals.m To change the termination_norm type and
|
|
% convert_termination_norm predicate.
|
|
%
|
|
% handle_options.m To change the error message that is produced when
|
|
% an incorrect argument is given to --termination-norm.
|
|
%
|
|
% term_norm.m To change the functor_norm predicate and change the
|
|
% functor_alg type.
|
|
%
|
|
% termination.m To change the set_functor_info predicate.
|
|
%
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- module transform_hlds__termination.
|
|
|
|
:- interface.
|
|
|
|
:- import_module hlds__hlds_module.
|
|
:- import_module hlds__hlds_pred.
|
|
:- import_module parse_tree__prog_data.
|
|
:- import_module transform_hlds__term_util.
|
|
|
|
:- import_module io, bool, std_util, list.
|
|
|
|
% Perform termination analysis on the module.
|
|
|
|
:- pred termination__pass(module_info::in, module_info::out,
|
|
io::di, io::uo) is det.
|
|
|
|
% Write the given arg size info; verbose if the second arg is yes.
|
|
|
|
:- pred termination__write_maybe_arg_size_info(maybe(arg_size_info)::in,
|
|
bool::in, io::di, io::uo) is det.
|
|
|
|
% Write the given termination info; verbose if the second arg is yes.
|
|
|
|
:- pred termination__write_maybe_termination_info(maybe(termination_info)::in,
|
|
bool::in, io::di, io::uo) is det.
|
|
|
|
% Write out a termination_info pragma for the predicate if it
|
|
% is exported, it is not a builtin and it is not a predicate used
|
|
% to force type specialization.
|
|
:- pred termination__write_pred_termination_info(module_info::in, pred_id::in,
|
|
io::di, io::uo) is det.
|
|
|
|
% This predicate outputs termination_info pragmas;
|
|
% such annotations can be part of .opt and .trans_opt files.
|
|
|
|
:- pred termination__write_pragma_termination_info(pred_or_func::in,
|
|
sym_name::in, list(mode)::in, prog_context::in,
|
|
maybe(arg_size_info)::in, maybe(termination_info)::in,
|
|
io::di, io::uo) is det.
|
|
|
|
%----------------------------------------------------------------------------%
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module check_hlds__inst_match.
|
|
:- import_module check_hlds__mode_util.
|
|
:- import_module check_hlds__type_util.
|
|
:- import_module hlds__error_util.
|
|
:- import_module hlds__hlds_data.
|
|
:- import_module hlds__hlds_goal.
|
|
:- import_module hlds__hlds_out.
|
|
:- import_module hlds__passes_aux.
|
|
:- import_module hlds__special_pred.
|
|
:- import_module libs__globals.
|
|
:- import_module libs__options.
|
|
:- import_module parse_tree__mercury_to_mercury.
|
|
:- import_module parse_tree__modules.
|
|
:- import_module parse_tree__prog_out.
|
|
:- import_module parse_tree__prog_util.
|
|
:- import_module transform_hlds__dependency_graph.
|
|
:- import_module transform_hlds__term_errors.
|
|
:- import_module transform_hlds__term_norm.
|
|
:- import_module transform_hlds__term_pass1.
|
|
:- import_module transform_hlds__term_pass2.
|
|
|
|
:- import_module map, int, char, string, relation.
|
|
:- import_module require, bag, set, term.
|
|
:- import_module varset.
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
termination__pass(!Module, !IO) :-
|
|
|
|
% Find out what norm we should use, and set up for using it
|
|
globals__io_get_termination_norm(TermNorm, !IO),
|
|
set_functor_info(TermNorm, !.Module, FunctorInfo),
|
|
globals__io_lookup_int_option(termination_error_limit, MaxErrors, !IO),
|
|
globals__io_lookup_int_option(termination_path_limit, MaxPaths, !IO),
|
|
PassInfo = pass_info(FunctorInfo, MaxErrors, MaxPaths),
|
|
|
|
% Process builtin and compiler-generated predicates,
|
|
% and user-supplied pragmas.
|
|
module_info_predids(!.Module, PredIds),
|
|
check_preds(PredIds, !Module, !IO),
|
|
|
|
% Process all the SCCs of the call graph in a bottom up order.
|
|
module_info_ensure_dependency_info(!Module),
|
|
module_info_dependency_info(!.Module, DepInfo),
|
|
hlds_dependency_info_get_dependency_ordering(DepInfo, SCCs),
|
|
|
|
% Ensure that termination pragmas for a proc. do conflict
|
|
% with termination pragmas for other procs. in the same SCC.
|
|
check_pragmas_are_consistent(SCCs, !Module, !IO),
|
|
|
|
list__foldl2(process_scc(PassInfo), SCCs, !Module, !IO),
|
|
|
|
globals__io_lookup_bool_option(make_optimization_interface,
|
|
MakeOptInt, !IO),
|
|
( MakeOptInt = yes ->
|
|
termination__make_opt_int(PredIds, !.Module, !IO)
|
|
;
|
|
true
|
|
).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
% Check that any user-supplied termination information (from pragma
|
|
% terminates/does_not_terminate) is consistent for each SCC in the program.
|
|
%
|
|
% The information will not be consistent if:
|
|
% (1) One or more procs. in the SCC has a terminates pragma *and* one or more
|
|
% procs. in the SCC has a does_not_terminate pragma.
|
|
% (2) One or more procs. in the SCC has a termination pragma (of either sort),
|
|
% *and* the termination status of one or more procs. in the SCC is
|
|
% unknown. (We check this after checking for the first case, so the
|
|
% termination info. for the known procs. will be consistent.)
|
|
%
|
|
% In the first case set the termination for all procs. in the SCC to
|
|
% can_loop and emit a warning. In the second, set the termination of any
|
|
% procs. whose termination status is unknown to be the same as those whose
|
|
% termination status is known.
|
|
|
|
:- pred check_pragmas_are_consistent(list(list(pred_proc_id))::in,
|
|
module_info::in, module_info::out, io::di, io::uo) is det.
|
|
|
|
check_pragmas_are_consistent(SCCs, !Module, !IO) :-
|
|
list__foldl2(check_scc_pragmas_are_consistent, SCCs, !Module, !IO).
|
|
|
|
:- pred check_scc_pragmas_are_consistent(list(pred_proc_id)::in,
|
|
module_info::in, module_info::out, io::di, io::uo) is det.
|
|
|
|
check_scc_pragmas_are_consistent(SCC, !Module, !IO) :-
|
|
list__filter(is_termination_known(!.Module), SCC, SCCTerminationKnown,
|
|
SCCTerminationUnknown),
|
|
(
|
|
SCCTerminationKnown = []
|
|
;
|
|
SCCTerminationKnown = [KnownPPId | _],
|
|
module_info_pred_proc_info(!.Module, KnownPPId, _,
|
|
KnownProcInfo),
|
|
proc_info_get_maybe_termination_info(KnownProcInfo,
|
|
MaybeKnownTerm),
|
|
(
|
|
MaybeKnownTerm = no,
|
|
unexpected(this_file, "No termination info. found.")
|
|
;
|
|
MaybeKnownTerm = yes(KnownTermStatus)
|
|
),
|
|
(
|
|
check_procs_known_term(KnownTermStatus,
|
|
SCCTerminationKnown, !.Module)
|
|
->
|
|
% Force any procs. in the SCC whose termination
|
|
% status is unknown to have the same termination
|
|
% status as those that are known.
|
|
set_termination_infos(SCCTerminationUnknown,
|
|
KnownTermStatus, !Module)
|
|
;
|
|
% There is a conflict between the user-supplied
|
|
% termination information for two or more procs.
|
|
% in this SCC. Emit a warning and then assume
|
|
% that they all loop.
|
|
get_context_from_scc(SCCTerminationKnown, !.Module,
|
|
Context),
|
|
NewTermStatus =
|
|
can_loop([Context - inconsistent_annotations]),
|
|
set_termination_infos(SCC, NewTermStatus, !Module),
|
|
|
|
PredIds = list__map((func(proc(PredId, _)) = PredId),
|
|
SCCTerminationKnown),
|
|
error_util__describe_several_pred_names(!.Module,
|
|
PredIds, PredNames),
|
|
Piece1 = words(
|
|
"are mutually recursive but some of their"),
|
|
Piece2 = words(
|
|
"termination pragmas are inconsistent."),
|
|
Components = [words("Warning:")] ++ PredNames ++
|
|
[Piece1, Piece2],
|
|
error_util__report_warning(Context, 0, Components, !IO)
|
|
)
|
|
).
|
|
|
|
% Check that all procedures in an SCC whose termination status is known
|
|
% have the same termination status.
|
|
:- pred check_procs_known_term(termination_info::in, list(pred_proc_id)::in,
|
|
module_info::in) is semidet.
|
|
|
|
check_procs_known_term(_, [], _).
|
|
check_procs_known_term(Status, [PPId | PPIds], Module) :-
|
|
module_info_pred_proc_info(Module, PPId, _, ProcInfo),
|
|
proc_info_get_maybe_termination_info(ProcInfo, MaybeTerm),
|
|
(
|
|
MaybeTerm = no,
|
|
unexpected(this_file, "No termination info for procedure.")
|
|
;
|
|
MaybeTerm = yes(PPIdStatus)
|
|
),
|
|
(
|
|
Status = cannot_loop,
|
|
PPIdStatus = cannot_loop
|
|
;
|
|
Status = can_loop(_),
|
|
PPIdStatus = can_loop(_)
|
|
),
|
|
check_procs_known_term(Status, PPIds, Module).
|
|
|
|
% Succeeds iff the termination status of a procedure is known.
|
|
:- pred is_termination_known(module_info::in, pred_proc_id::in) is semidet.
|
|
|
|
is_termination_known(Module, PPId) :-
|
|
module_info_pred_proc_info(Module, PPId, _, ProcInfo),
|
|
proc_info_get_maybe_termination_info(ProcInfo, yes(_)).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
% For each SCC, we first find out the relationships among
|
|
% the sizes of the arguments of the procedures of the SCC,
|
|
% and then attempt to prove termination of the procedures.
|
|
|
|
:- pred termination__process_scc(pass_info::in, list(pred_proc_id)::in,
|
|
module_info::in, module_info::out, io::di, io::uo) is det.
|
|
|
|
termination__process_scc(PassInfo, SCC, !Module, !IO) :-
|
|
IsArgSizeKnown = (pred(PPId::in) is semidet :-
|
|
module_info_pred_proc_info(!.Module, PPId, _, ProcInfo),
|
|
proc_info_get_maybe_arg_size_info(ProcInfo, yes(_))
|
|
),
|
|
list__filter(IsArgSizeKnown, SCC, _SCCArgSizeKnown, SCCArgSizeUnknown),
|
|
( SCCArgSizeUnknown = [] ->
|
|
ArgSizeErrors = [],
|
|
TermErrors = []
|
|
;
|
|
find_arg_sizes_in_scc(SCCArgSizeUnknown, !.Module, PassInfo,
|
|
ArgSizeResult, TermErrors, !IO),
|
|
(
|
|
ArgSizeResult = ok(Solutions, OutputSupplierMap),
|
|
set_finite_arg_size_infos(Solutions,
|
|
OutputSupplierMap, !Module),
|
|
ArgSizeErrors = []
|
|
;
|
|
ArgSizeResult = error(Errors),
|
|
set_infinite_arg_size_infos(SCCArgSizeUnknown,
|
|
infinite(Errors), !Module),
|
|
ArgSizeErrors = Errors
|
|
)
|
|
),
|
|
list__filter(is_termination_known(!.Module), SCC,
|
|
_SCCTerminationKnown, SCCTerminationUnknown),
|
|
( SCCTerminationUnknown = [] ->
|
|
%
|
|
% We may possibly have encountered inconsistent
|
|
% terminates/does_not_terminate pragmas for this SCC,
|
|
% so we need to report errors here as well.
|
|
true
|
|
;
|
|
IsFatal = (pred(ContextError::in) is semidet :-
|
|
ContextError = _Context - Error,
|
|
( Error = horder_call
|
|
; Error = horder_args(_, _)
|
|
; Error = imported_pred
|
|
)
|
|
),
|
|
list__filter(IsFatal, ArgSizeErrors, FatalErrors),
|
|
list__append(TermErrors, FatalErrors, BothErrors),
|
|
( BothErrors = [_ | _] ->
|
|
% These errors prevent pass 2 from proving termination
|
|
% in any case, so we may as well not prove it quickly.
|
|
PassInfo = pass_info(_, MaxErrors, _),
|
|
list__take_upto(MaxErrors, BothErrors,
|
|
ReportedErrors),
|
|
TerminationResult = can_loop(ReportedErrors)
|
|
;
|
|
globals__io_lookup_int_option(termination_single_args,
|
|
SingleArgs, !IO),
|
|
prove_termination_in_scc(SCCTerminationUnknown,
|
|
!.Module, PassInfo, SingleArgs,
|
|
TerminationResult)
|
|
),
|
|
set_termination_infos(SCCTerminationUnknown,
|
|
TerminationResult, !Module),
|
|
( TerminationResult = can_loop(TerminationErrors) ->
|
|
report_termination_errors(SCC, TerminationErrors,
|
|
!Module, !IO)
|
|
;
|
|
true
|
|
)
|
|
).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
% This predicate takes the results from solve_equations
|
|
% and inserts these results into the module info.
|
|
|
|
:- pred set_finite_arg_size_infos(list(pair(pred_proc_id, int))::in,
|
|
used_args::in, module_info::in, module_info::out) is det.
|
|
|
|
set_finite_arg_size_infos([], _, !Module).
|
|
set_finite_arg_size_infos([Soln | Solns], OutputSupplierMap, !Module) :-
|
|
Soln = PPId - Gamma,
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_preds(!.Module, PredTable0),
|
|
map__lookup(PredTable0, PredId, PredInfo),
|
|
pred_info_procedures(PredInfo, ProcTable),
|
|
map__lookup(ProcTable, ProcId, ProcInfo),
|
|
map__lookup(OutputSupplierMap, PPId, OutputSuppliers),
|
|
ArgSizeInfo = finite(Gamma, OutputSuppliers),
|
|
proc_info_set_maybe_arg_size_info(yes(ArgSizeInfo),
|
|
ProcInfo, ProcInfo1),
|
|
map__set(ProcTable, ProcId, ProcInfo1, ProcTable1),
|
|
pred_info_set_procedures(ProcTable1, PredInfo, PredInfo1),
|
|
map__set(PredTable0, PredId, PredInfo1, PredTable),
|
|
module_info_set_preds(PredTable, !Module),
|
|
set_finite_arg_size_infos(Solns, OutputSupplierMap, !Module).
|
|
|
|
:- pred set_infinite_arg_size_infos(list(pred_proc_id)::in,
|
|
arg_size_info::in, module_info::in, module_info::out) is det.
|
|
|
|
set_infinite_arg_size_infos([], _, !Module).
|
|
set_infinite_arg_size_infos([PPId | PPIds], ArgSizeInfo, !Module) :-
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_preds(!.Module, PredTable0),
|
|
map__lookup(PredTable0, PredId, PredInfo),
|
|
pred_info_procedures(PredInfo, ProcTable),
|
|
map__lookup(ProcTable, ProcId, ProcInfo),
|
|
proc_info_set_maybe_arg_size_info(yes(ArgSizeInfo),
|
|
ProcInfo, ProcInfo1),
|
|
map__set(ProcTable, ProcId, ProcInfo1, ProcTable1),
|
|
pred_info_set_procedures(ProcTable1, PredInfo, PredInfo1),
|
|
map__set(PredTable0, PredId, PredInfo1, PredTable),
|
|
module_info_set_preds(PredTable, !Module),
|
|
set_infinite_arg_size_infos(PPIds, ArgSizeInfo, !Module).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- pred set_termination_infos(list(pred_proc_id)::in, termination_info::in,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
set_termination_infos([], _, !Module).
|
|
set_termination_infos([PPId | PPIds], TerminationInfo, !Module) :-
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_preds(!.Module, PredTable0),
|
|
map__lookup(PredTable0, PredId, PredInfo0),
|
|
pred_info_procedures(PredInfo0, ProcTable0),
|
|
map__lookup(ProcTable0, ProcId, ProcInfo0),
|
|
proc_info_set_maybe_termination_info(yes(TerminationInfo),
|
|
ProcInfo0, ProcInfo),
|
|
map__det_update(ProcTable0, ProcId, ProcInfo, ProcTable),
|
|
pred_info_set_procedures(ProcTable, PredInfo0, PredInfo),
|
|
map__det_update(PredTable0, PredId, PredInfo, PredTable),
|
|
module_info_set_preds(PredTable, !Module),
|
|
set_termination_infos(PPIds, TerminationInfo, !Module).
|
|
|
|
:- pred report_termination_errors(list(pred_proc_id)::in,
|
|
list(term_errors__error)::in, module_info::in, module_info::out,
|
|
io::di, io::uo) is det.
|
|
|
|
report_termination_errors(SCC, Errors, !Module, !IO) :-
|
|
globals__io_lookup_bool_option(check_termination,
|
|
NormalErrors, !IO),
|
|
globals__io_lookup_bool_option(verbose_check_termination,
|
|
VerboseErrors, !IO),
|
|
(
|
|
IsCheckTerm = (pred(PPId::in) is semidet :-
|
|
module_info_pred_proc_info(!.Module, PPId, PredInfo, _),
|
|
\+ pred_info_is_imported(PredInfo),
|
|
pred_info_get_markers(PredInfo, Markers),
|
|
check_marker(Markers, check_termination)
|
|
),
|
|
list__filter(IsCheckTerm, SCC, CheckTermPPIds),
|
|
CheckTermPPIds = [_ | _]
|
|
->
|
|
% If any procedure in the SCC has a check_terminates pragma,
|
|
% print out one error message for the whole SCC and indicate
|
|
% an error.
|
|
term_errors__report_term_errors(SCC, Errors, !.Module, !IO),
|
|
io__set_exit_status(1, !IO),
|
|
module_info_incr_errors(!Module)
|
|
;
|
|
IsNonImported = (pred(PPId::in) is semidet :-
|
|
module_info_pred_proc_info(!.Module, PPId, PredInfo, _),
|
|
\+ pred_info_is_imported(PredInfo)
|
|
),
|
|
list__filter(IsNonImported, SCC, NonImportedPPIds),
|
|
NonImportedPPIds = [_ | _],
|
|
|
|
% Only output warnings of non-termination for direct
|
|
% errors. If there are no direct errors then output
|
|
% the indirect errors - this is better than giving
|
|
% no reason at all. If verbose errors is enabled then
|
|
% output both sorts of error.
|
|
% (See term_errors.m for details of direct and indirect
|
|
% errors).
|
|
|
|
( VerboseErrors = yes ->
|
|
PrintErrors = Errors
|
|
; NormalErrors = yes ->
|
|
IsNonSimple = (pred(ContextError::in) is semidet :-
|
|
ContextError = _Context - Error,
|
|
\+ indirect_error(Error)
|
|
),
|
|
list__filter(IsNonSimple, Errors, PrintErrors0),
|
|
% If there were no direct errors then use
|
|
% the indirect errors.
|
|
( if PrintErrors0 = []
|
|
then PrintErrors = Errors
|
|
else PrintErrors = PrintErrors0
|
|
)
|
|
;
|
|
fail
|
|
)
|
|
->
|
|
term_errors__report_term_errors(SCC, PrintErrors, !.Module, !IO)
|
|
;
|
|
true
|
|
).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- pred check_preds(list(pred_id)::in, module_info::in, module_info::out,
|
|
io::di, io::uo) is det.
|
|
|
|
% This predicate processes each predicate and sets the termination property
|
|
% if possible. This is done as follows: Set the termination to yes if:
|
|
% - there is a terminates pragma defined for the predicate
|
|
% - there is a `check_termination' pragma defined for the predicate, and it
|
|
% is imported, and the compiler is not currently generating the
|
|
% intermodule optimization file.
|
|
% - the predicate is a builtin predicate or is compiler generated (This
|
|
% also sets the termination constant and UsedArgs).
|
|
%
|
|
% Set the termination to dont_know if:
|
|
% - there is a `does_not_terminate' pragma defined for this predicate.
|
|
% - the predicate is imported and there is no other source of information
|
|
% about it (termination_info pragmas, terminates pragmas,
|
|
% check_termination pragmas, builtin/compiler generated).
|
|
|
|
check_preds([], !Module, !IO).
|
|
check_preds([PredId | PredIds], !Module, !IO) :-
|
|
write_pred_progress_message("% Checking ", PredId, !.Module, !IO),
|
|
globals__io_lookup_bool_option(make_optimization_interface,
|
|
MakeOptInt, !IO),
|
|
module_info_preds(!.Module, PredTable0),
|
|
map__lookup(PredTable0, PredId, PredInfo0),
|
|
pred_info_import_status(PredInfo0, ImportStatus),
|
|
pred_info_context(PredInfo0, Context),
|
|
pred_info_procedures(PredInfo0, ProcTable0),
|
|
pred_info_get_markers(PredInfo0, Markers),
|
|
ProcIds = pred_info_procids(PredInfo0),
|
|
(
|
|
% It is possible for compiler generated/mercury builtin
|
|
% predicates to be imported or locally defined, so they
|
|
% must be covered here, separately.
|
|
set_compiler_gen_terminates(PredInfo0, ProcIds, PredId,
|
|
!.Module, ProcTable0, ProcTable1)
|
|
->
|
|
ProcTable2 = ProcTable1
|
|
;
|
|
status_defined_in_this_module(ImportStatus, yes)
|
|
->
|
|
( check_marker(Markers, terminates) ->
|
|
change_procs_termination_info(ProcIds, yes,
|
|
cannot_loop, ProcTable0, ProcTable2)
|
|
;
|
|
ProcTable2 = ProcTable0
|
|
)
|
|
;
|
|
% Not defined in this module.
|
|
|
|
% All of the predicates that are processed in this section
|
|
% are imported in some way.
|
|
% With imported predicates, any 'check_termination'
|
|
% pragmas will be checked by the compiler when it compiles
|
|
% the relevant source file (that the predicate was imported
|
|
% from). When making the intermodule optimizations, the
|
|
% check_termination will not be checked when the relevant
|
|
% source file is compiled, so it cannot be depended upon.
|
|
(
|
|
(
|
|
check_marker(Markers, terminates)
|
|
;
|
|
MakeOptInt = no,
|
|
check_marker(Markers, check_termination)
|
|
)
|
|
->
|
|
change_procs_termination_info(ProcIds, yes,
|
|
cannot_loop, ProcTable0, ProcTable1)
|
|
;
|
|
TerminationError = Context - imported_pred,
|
|
TerminationInfo = can_loop([TerminationError]),
|
|
change_procs_termination_info(ProcIds, no,
|
|
TerminationInfo, ProcTable0, ProcTable1)
|
|
),
|
|
ArgSizeError = imported_pred,
|
|
ArgSizeInfo = infinite([Context - ArgSizeError]),
|
|
change_procs_arg_size_info(ProcIds, no, ArgSizeInfo,
|
|
ProcTable1, ProcTable2)
|
|
),
|
|
( check_marker(Markers, does_not_terminate) ->
|
|
RequestError = Context - does_not_term_pragma(PredId),
|
|
RequestTerminationInfo = can_loop([RequestError]),
|
|
change_procs_termination_info(ProcIds, yes,
|
|
RequestTerminationInfo, ProcTable2, ProcTable)
|
|
;
|
|
ProcTable = ProcTable2
|
|
),
|
|
pred_info_set_procedures(ProcTable, PredInfo0, PredInfo),
|
|
map__set(PredTable0, PredId, PredInfo, PredTable),
|
|
module_info_set_preds(PredTable, !Module),
|
|
check_preds(PredIds, !Module, !IO).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
% This predicate checks each ProcId in the list to see if it is a compiler
|
|
% generated predicate, or a predicate from builtin.m or private_builtin.m.
|
|
% If it is, then the compiler sets the termination property of the ProcIds
|
|
% accordingly.
|
|
|
|
% XXX This does the wrong thing for calls to unify/2,
|
|
% which might not terminate in the case of user-defined equality predicates.
|
|
|
|
:- pred set_compiler_gen_terminates(pred_info::in, list(proc_id)::in,
|
|
pred_id::in, module_info::in, proc_table::in, proc_table::out)
|
|
is semidet.
|
|
|
|
set_compiler_gen_terminates(PredInfo, ProcIds, PredId, Module, !ProcTable) :-
|
|
(
|
|
pred_info_is_builtin(PredInfo)
|
|
->
|
|
set_builtin_terminates(ProcIds, PredId, PredInfo, Module,
|
|
!ProcTable)
|
|
;
|
|
(
|
|
ModuleName = pred_info_module(PredInfo),
|
|
Name = pred_info_name(PredInfo),
|
|
Arity = pred_info_arity(PredInfo),
|
|
special_pred_name_arity(SpecPredId0, Name, Arity),
|
|
any_mercury_builtin_module(ModuleName)
|
|
->
|
|
SpecialPredId = SpecPredId0
|
|
;
|
|
pred_info_get_maybe_special_pred(PredInfo,
|
|
MaybeSpecial),
|
|
MaybeSpecial = yes(SpecialPredId - _)
|
|
)
|
|
->
|
|
set_generated_terminates(ProcIds, SpecialPredId, !ProcTable)
|
|
;
|
|
fail
|
|
).
|
|
|
|
:- pred set_generated_terminates(list(proc_id)::in, special_pred_id::in,
|
|
proc_table::in, proc_table::out) is det.
|
|
|
|
set_generated_terminates([], _, !ProcTable).
|
|
set_generated_terminates([ProcId | ProcIds], SpecialPredId, !ProcTable) :-
|
|
map__lookup(!.ProcTable, ProcId, ProcInfo0),
|
|
proc_info_headvars(ProcInfo0, HeadVars),
|
|
special_pred_id_to_termination(SpecialPredId, HeadVars,
|
|
ArgSize, Termination),
|
|
proc_info_set_maybe_arg_size_info(yes(ArgSize), ProcInfo0, ProcInfo1),
|
|
proc_info_set_maybe_termination_info(yes(Termination),
|
|
ProcInfo1, ProcInfo),
|
|
map__det_update(!.ProcTable, ProcId, ProcInfo, !:ProcTable),
|
|
set_generated_terminates(ProcIds, SpecialPredId, !ProcTable).
|
|
|
|
:- pred special_pred_id_to_termination(special_pred_id::in,
|
|
list(prog_var)::in, arg_size_info::out, termination_info::out) is det.
|
|
|
|
special_pred_id_to_termination(compare, HeadVars, ArgSize, Termination) :-
|
|
term_util__make_bool_list(HeadVars, [no, no, no], OutList),
|
|
ArgSize = finite(0, OutList),
|
|
Termination = cannot_loop.
|
|
special_pred_id_to_termination(unify, HeadVars, ArgSize, Termination) :-
|
|
term_util__make_bool_list(HeadVars, [yes, yes], OutList),
|
|
ArgSize = finite(0, OutList),
|
|
Termination = cannot_loop.
|
|
special_pred_id_to_termination(index, HeadVars, ArgSize, Termination) :-
|
|
term_util__make_bool_list(HeadVars, [no, no], OutList),
|
|
ArgSize = finite(0, OutList),
|
|
Termination = cannot_loop.
|
|
|
|
% The list of proc_ids must refer to builtin predicates. This predicate
|
|
% sets the termination information of builtin predicates.
|
|
|
|
:- pred set_builtin_terminates(list(proc_id)::in, pred_id::in, pred_info::in,
|
|
module_info::in, proc_table::in, proc_table::out) is det.
|
|
|
|
set_builtin_terminates([], _, _, _, !ProcTable).
|
|
set_builtin_terminates([ProcId | ProcIds], PredId, PredInfo, Module,
|
|
!ProcTable) :-
|
|
map__lookup(!.ProcTable, ProcId, ProcInfo0),
|
|
( all_args_input_or_zero_size(Module, PredInfo, ProcInfo0) ->
|
|
% The size of the output arguments will all be 0,
|
|
% independent of the size of the input variables.
|
|
% UsedArgs should be set to yes([no, no, ...]).
|
|
proc_info_headvars(ProcInfo0, HeadVars),
|
|
term_util__make_bool_list(HeadVars, [], UsedArgs),
|
|
ArgSizeInfo = yes(finite(0, UsedArgs))
|
|
;
|
|
pred_info_context(PredInfo, Context),
|
|
Error = is_builtin(PredId),
|
|
ArgSizeInfo = yes(infinite([Context - Error]))
|
|
),
|
|
proc_info_set_maybe_arg_size_info(ArgSizeInfo, ProcInfo0, ProcInfo1),
|
|
proc_info_set_maybe_termination_info(yes(cannot_loop), ProcInfo1,
|
|
ProcInfo),
|
|
map__det_update(!.ProcTable, ProcId, ProcInfo, !:ProcTable),
|
|
set_builtin_terminates(ProcIds, PredId, PredInfo, Module, !ProcTable).
|
|
|
|
:- pred all_args_input_or_zero_size(module_info::in, pred_info::in,
|
|
proc_info::in) is semidet.
|
|
|
|
all_args_input_or_zero_size(Module, PredInfo, ProcInfo) :-
|
|
pred_info_arg_types(PredInfo, TypeList),
|
|
proc_info_argmodes(ProcInfo, ModeList),
|
|
all_args_input_or_zero_size_2(TypeList, ModeList, Module).
|
|
|
|
:- pred all_args_input_or_zero_size_2(list(type)::in, list(mode)::in,
|
|
module_info::in) is semidet.
|
|
|
|
all_args_input_or_zero_size_2([], [], _).
|
|
all_args_input_or_zero_size_2([], [_|_], _) :-
|
|
unexpected(this_file, "all_args_input_or_size_2/3 - unmatched lists.").
|
|
all_args_input_or_zero_size_2([_|_], [], _) :-
|
|
unexpected(this_file, "all_args_input_or_size_2/3 - unmatched lists.").
|
|
all_args_input_or_zero_size_2([Type | Types], [Mode | Modes], Module) :-
|
|
( mode_is_input(Module, Mode) ->
|
|
% The variable is an input variables, so its size is
|
|
% irrelevant.
|
|
all_args_input_or_zero_size_2(Types, Modes, Module)
|
|
;
|
|
zero_size_type(Type, Module),
|
|
all_args_input_or_zero_size_2(Types, Modes, Module)
|
|
).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
% This predicate sets the arg_size_info property of the given list
|
|
% of procedures.
|
|
%
|
|
% change_procs_arg_size_info(ProcList, Override, TerminationInfo,
|
|
% ProcTable, ProcTable)
|
|
%
|
|
% If Override is yes, then this predicate overrides any existing arg_size
|
|
% information. If Override is no, then it leaves the proc_info of a procedure
|
|
% unchanged unless the proc_info had no arg_size information (i.e. the
|
|
% maybe(arg_size_info) field was set to "no").
|
|
|
|
:- pred change_procs_arg_size_info(list(proc_id)::in, bool::in,
|
|
arg_size_info::in, proc_table::in, proc_table::out) is det.
|
|
|
|
change_procs_arg_size_info([], _, _, !ProcTable).
|
|
change_procs_arg_size_info([ProcId | ProcIds], Override, ArgSize, !ProcTable) :-
|
|
map__lookup(!.ProcTable, ProcId, ProcInfo0),
|
|
(
|
|
(
|
|
Override = yes
|
|
;
|
|
proc_info_get_maybe_arg_size_info(ProcInfo0, no)
|
|
)
|
|
->
|
|
proc_info_set_maybe_arg_size_info(yes(ArgSize),
|
|
ProcInfo0, ProcInfo),
|
|
map__det_update(!.ProcTable, ProcId, ProcInfo, !:ProcTable)
|
|
;
|
|
true
|
|
),
|
|
change_procs_arg_size_info(ProcIds, Override, ArgSize, !ProcTable).
|
|
|
|
% This predicate sets the termination_info property of the given list
|
|
% of procedures.
|
|
%
|
|
% change_procs_termination_info(ProcList, Override, TerminationInfo,
|
|
% ProcTable, ProcTable)
|
|
%
|
|
% If Override is yes, then this predicate overrides any existing termination
|
|
% information. If Override is no, then it leaves the proc_info of a procedure
|
|
% unchanged unless the proc_info had no termination information (i.e. the
|
|
% maybe(termination_info) field was set to "no").
|
|
|
|
:- pred change_procs_termination_info(list(proc_id)::in, bool::in,
|
|
termination_info::in, proc_table::in, proc_table::out) is det.
|
|
|
|
change_procs_termination_info([], _, _, !ProcTable).
|
|
change_procs_termination_info([ProcId | ProcIds], Override, Termination,
|
|
!ProcTable) :-
|
|
map__lookup(!.ProcTable, ProcId, ProcInfo0),
|
|
(
|
|
(
|
|
Override = yes
|
|
;
|
|
proc_info_get_maybe_termination_info(ProcInfo0, no)
|
|
)
|
|
->
|
|
proc_info_set_maybe_termination_info(yes(Termination),
|
|
ProcInfo0, ProcInfo),
|
|
map__det_update(!.ProcTable, ProcId, ProcInfo, !:ProcTable)
|
|
;
|
|
true
|
|
),
|
|
change_procs_termination_info(ProcIds, Override, Termination,
|
|
!ProcTable).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
%----------------------------------------------------------------------------%
|
|
|
|
% These predicates are used to add the termination_info pragmas to the .opt
|
|
% file. It is often better to use the .trans_opt file, as it gives
|
|
% much better accuracy. The two files are not mutually exclusive, and
|
|
% termination information may be stored in both.
|
|
|
|
:- pred termination__make_opt_int(list(pred_id)::in, module_info::in,
|
|
io::di, io::uo) is det.
|
|
|
|
termination__make_opt_int(PredIds, Module, !IO) :-
|
|
module_info_name(Module, ModuleName),
|
|
module_name_to_file_name(ModuleName, ".opt.tmp", no, OptFileName, !IO),
|
|
globals__io_lookup_bool_option(verbose, Verbose, !IO),
|
|
maybe_write_string(Verbose,
|
|
"% Appending termination_info pragmas to `", !IO),
|
|
maybe_write_string(Verbose, OptFileName, !IO),
|
|
maybe_write_string(Verbose, "'...", !IO),
|
|
maybe_flush_output(Verbose, !IO),
|
|
|
|
io__open_append(OptFileName, OptFileRes, !IO),
|
|
(
|
|
OptFileRes = ok(OptFile),
|
|
io__set_output_stream(OptFile, OldStream, !IO),
|
|
list__foldl(termination__write_pred_termination_info(Module),
|
|
PredIds, !IO),
|
|
io__set_output_stream(OldStream, _, !IO),
|
|
io__close_output(OptFile, !IO),
|
|
maybe_write_string(Verbose, " done.\n", !IO)
|
|
;
|
|
OptFileRes = error(IOError),
|
|
% failed to open the .opt file for processing
|
|
maybe_write_string(Verbose, " failed!\n", !IO),
|
|
io__error_message(IOError, IOErrorMessage),
|
|
io__write_strings(["Error opening file `",
|
|
OptFileName, "' for output: ", IOErrorMessage], !IO),
|
|
io__set_exit_status(1, !IO)
|
|
).
|
|
|
|
termination__write_pred_termination_info(Module, PredId, !IO) :-
|
|
module_info_pred_info(Module, PredId, PredInfo),
|
|
pred_info_import_status(PredInfo, ImportStatus),
|
|
module_info_type_spec_info(Module, TypeSpecInfo),
|
|
TypeSpecInfo = type_spec_info(_, TypeSpecForcePreds, _, _),
|
|
|
|
(
|
|
(
|
|
ImportStatus = exported
|
|
;
|
|
ImportStatus = opt_exported
|
|
),
|
|
\+ is_unify_or_compare_pred(PredInfo),
|
|
|
|
% XXX These should be allowed, but the predicate
|
|
% declaration for the specialized predicate is not produced
|
|
% before the termination pragmas are read in, resulting
|
|
% in an undefined predicate error.
|
|
\+ set__member(PredId, TypeSpecForcePreds)
|
|
->
|
|
PredName = pred_info_name(PredInfo),
|
|
ProcIds = pred_info_procids(PredInfo),
|
|
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
|
|
ModuleName = pred_info_module(PredInfo),
|
|
pred_info_procedures(PredInfo, ProcTable),
|
|
pred_info_context(PredInfo, Context),
|
|
SymName = qualified(ModuleName, PredName),
|
|
termination__make_opt_int_procs(PredId, ProcIds, ProcTable,
|
|
PredOrFunc, SymName, Context, !IO)
|
|
;
|
|
true
|
|
).
|
|
|
|
:- pred termination__make_opt_int_procs(pred_id::in, list(proc_id)::in,
|
|
proc_table::in, pred_or_func::in, sym_name::in, prog_context::in,
|
|
io::di, io::uo) is det.
|
|
|
|
termination__make_opt_int_procs(_PredId, [], _, _, _, _, !IO).
|
|
termination__make_opt_int_procs(PredId, [ ProcId | ProcIds ], ProcTable,
|
|
PredOrFunc, SymName, Context, !IO) :-
|
|
map__lookup(ProcTable, ProcId, ProcInfo),
|
|
proc_info_get_maybe_arg_size_info(ProcInfo, ArgSize),
|
|
proc_info_get_maybe_termination_info(ProcInfo, Termination),
|
|
proc_info_declared_argmodes(ProcInfo, ModeList),
|
|
termination__write_pragma_termination_info(PredOrFunc, SymName,
|
|
ModeList, Context, ArgSize, Termination, !IO),
|
|
termination__make_opt_int_procs(PredId, ProcIds, ProcTable,
|
|
PredOrFunc, SymName, Context, !IO).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
% These predicates are used to print out the termination_info pragmas.
|
|
% If they are changed, then prog_io_pragma.m must also be changed so that
|
|
% it can parse the resulting pragma termination_info declarations.
|
|
|
|
termination__write_pragma_termination_info(PredOrFunc, SymName, ModeList,
|
|
Context, MaybeArgSize, MaybeTermination, !IO) :-
|
|
io__write_string(":- pragma termination_info(", !IO),
|
|
varset__init(InitVarSet),
|
|
(
|
|
PredOrFunc = predicate,
|
|
mercury_output_pred_mode_subdecl(InitVarSet, SymName,
|
|
ModeList, no, Context, !IO)
|
|
;
|
|
PredOrFunc = function,
|
|
pred_args_to_func_args(ModeList, FuncModeList, RetMode),
|
|
mercury_output_func_mode_subdecl(InitVarSet, SymName,
|
|
FuncModeList, RetMode, no, Context, !IO)
|
|
),
|
|
io__write_string(", ", !IO),
|
|
termination__write_maybe_arg_size_info(MaybeArgSize, no, !IO),
|
|
io__write_string(", ", !IO),
|
|
termination__write_maybe_termination_info(MaybeTermination, no, !IO),
|
|
io__write_string(").\n", !IO).
|
|
|
|
termination__write_maybe_arg_size_info(MaybeArgSizeInfo, Verbose, !IO) :-
|
|
(
|
|
MaybeArgSizeInfo = no,
|
|
io__write_string("not_set", !IO)
|
|
;
|
|
MaybeArgSizeInfo = yes(infinite(Error)),
|
|
io__write_string("infinite", !IO),
|
|
( Verbose = yes ->
|
|
io__write_string("(", !IO),
|
|
io__write(Error, !IO),
|
|
io__write_string(")", !IO)
|
|
;
|
|
true
|
|
)
|
|
;
|
|
MaybeArgSizeInfo = yes(finite(Const, UsedArgs)),
|
|
io__write_string("finite(", !IO),
|
|
io__write_int(Const, !IO),
|
|
io__write_string(", ", !IO),
|
|
termination__write_used_args(UsedArgs, !IO),
|
|
io__write_string(")", !IO)
|
|
).
|
|
|
|
:- pred termination__write_used_args(list(bool)::in, io::di, io::uo) is det.
|
|
|
|
termination__write_used_args([], !IO) :-
|
|
io__write_string("[]", !IO).
|
|
termination__write_used_args([UsedArg | UsedArgs], !IO) :-
|
|
io__write_string("[", !IO),
|
|
io__write(UsedArg, !IO),
|
|
termination__write_used_args_2(UsedArgs, !IO),
|
|
io__write_string("]", !IO).
|
|
|
|
:- pred termination__write_used_args_2(list(bool)::in, io::di, io::uo) is det.
|
|
|
|
termination__write_used_args_2([], !IO).
|
|
termination__write_used_args_2([ UsedArg | UsedArgs ], !IO) :-
|
|
io__write_string(", ", !IO),
|
|
io__write(UsedArg, !IO),
|
|
termination__write_used_args_2(UsedArgs, !IO).
|
|
|
|
termination__write_maybe_termination_info(MaybeTerminationInfo, Verbose, !IO) :-
|
|
(
|
|
MaybeTerminationInfo = no,
|
|
io__write_string("not_set", !IO)
|
|
;
|
|
MaybeTerminationInfo = yes(cannot_loop),
|
|
io__write_string("cannot_loop", !IO)
|
|
;
|
|
MaybeTerminationInfo = yes(can_loop(Error)),
|
|
io__write_string("can_loop", !IO),
|
|
( Verbose = yes ->
|
|
io__write_string("(", !IO),
|
|
io__write(Error, !IO),
|
|
io__write_string(")", !IO)
|
|
;
|
|
true
|
|
)
|
|
).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- func this_file = string.
|
|
|
|
this_file = "termination.m".
|
|
|
|
%----------------------------------------------------------------------------%
|
|
:- end_module termination.
|
|
%----------------------------------------------------------------------------%
|