mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-20 16:31:04 +00:00
Estimated hours taken: 4
Move some declarations around and make some other minor changes in order
to reduce the number and complexity of the intermodule dependencies.
In particular, ensure that prog_data.m does not need to import
hlds*.m, purity.m, rl.m, or term_util.m.
Since we expect to have only one input language, but potentially many
different target languages, the general design principle that we have adopted
in order to minimize the number of dependencies, particularly cyclic ones,
is that modules which belong to an earlier stage of compilation should
not depend on modules which belong to a later stage of compilation.
However, that design principle has not been adhered to in many cases.
This change reduces the number of such cases.
compiler/prog_data.m:
compiler/hlds_pred.m:
Move definition of the types `eval_method' and `pred_or_func'
from hlds_pred.m to prog_data.m.
Add new type `mode_num' to prog_data.m, defined by
`type mode_num == int', and change the proc_id argument of
the `unused_args' pragma to instead use this type instead of proc_id.
Delete the import of module hlds_pred from prog_data.m.
compiler/prog_data.m:
compiler/hlds_data.m:
Move the definition of the type `determinism' from hlds_data.m to
prog_data.m. Delete the import of module hlds_data from prog_data.m.
compiler/prog_data.m:
compiler/purity.m:
Move the definition of the type `purity' from purity.m to prog_data.m.
Add import of module prog_data to purity.m.
Delete the import of module purity from prog_data.m.
This is needed because purity.m imports hlds*.m and so by
importing purity.m, prog_data.m was indirectly importing hlds*.m.
(An possible alternative here would be to split purity.m into two
parts, called say purity.m and check_purity.m; of these, only the
second would import hlds*.m. But that would be a significantly
more complicated change.)
compiler/prog_data.m:
compiler/rl.m:
Move the definition of the `index_spec' and `index_type' types from
rl.m to prog_data.m. Delete the import of module rl from prog_data.m.
compiler/prog_data.m:
Add new types `pragma_arg_size_info' and `pragma_termination_info'
to prog_data.m. These are similar to the existing types
`arg_size_info' and `termination_info' defined in term_util.m,
except that they correspond more directly to what is actually
used in `pragma termination_info' declarations.
compiler/term_util.m:
Add new predicates `add_context_to_arg_size_info' and
`add_context_to_pragma_termination_info' for converting
the prog_data types to their corresponding term_util types.
compiler/make_hlds.m:
compiler/mercury_to_mercury.m:
compiler/prog_io_pragma.m:
compiler/unused_args.m:
Minor modifications to handle the change in the type of the
mode number argument in `unused_args' pragmas, and in the types
of the arg_size_info and termination_info arguments in
`termination_info' pragmas.
compiler/mercury_to_mercury.m:
compiler/prog_io_pragma.m:
compiler/inst.m:
compiler/make_hlds.m:
compiler/call_gen.m:
compiler/common.m:
compiler/continuation_info.m:
compiler/det_report.m:
compiler/hlds_pred.m:
compiler/lambda.m:
compiler/magic_util.m:
compiler/modecheck_call.m:
compiler/prog_io_goal.m:
compiler/prog_io_util.m:
compiler/prog_util.m:
compiler/termination.m:
Delete imports of modules purity, rl, and hlds_pred, or
move them from the interface section to the implementation section.
compiler/inst.m:
compiler/prog_io_util.m:
Add an XXX comment explaining why we need to import hlds_data.m.
818 lines
30 KiB
Mathematica
818 lines
30 KiB
Mathematica
%----------------------------------------------------------------------------%
|
|
% Copyright (C) 1997-1999 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_util.m To change the functor_norm predicate and change the
|
|
% functor_alg type.
|
|
%
|
|
% termination.m To change the set_functor_info predicate.
|
|
%
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- module termination.
|
|
|
|
:- interface.
|
|
|
|
:- import_module io, bool, std_util, list.
|
|
:- import_module prog_data, hlds_module, term_util.
|
|
|
|
% Perform termination analysis on the module.
|
|
|
|
:- pred termination__pass(module_info::in, module_info::out,
|
|
io__state::di, io__state::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__state::di, io__state::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__state::di, io__state::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__state::di, io__state::uo) is det.
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module term_pass1, term_pass2, term_errors.
|
|
:- import_module inst_match, passes_aux, options, globals.
|
|
:- import_module hlds_data, hlds_goal, hlds_pred, dependency_graph, varset.
|
|
:- import_module mode_util, hlds_out, code_util, prog_out, prog_util.
|
|
:- import_module mercury_to_mercury, type_util, special_pred.
|
|
:- import_module modules.
|
|
|
|
:- import_module map, int, char, string, relation.
|
|
:- import_module require, bag, set, term.
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
termination__pass(Module0, Module) -->
|
|
|
|
% Find out what norm we should use, and set up for using it
|
|
globals__io_get_termination_norm(TermNorm),
|
|
{ set_functor_info(TermNorm, Module0, FunctorInfo) },
|
|
globals__io_lookup_int_option(termination_error_limit, MaxErrors),
|
|
globals__io_lookup_int_option(termination_path_limit, MaxPaths),
|
|
{ PassInfo = pass_info(FunctorInfo, MaxErrors, MaxPaths) },
|
|
|
|
% Process builtin and compiler-generated predicates,
|
|
% and user-supplied pragmas.
|
|
{ module_info_predids(Module0, PredIds) },
|
|
check_preds(PredIds, Module0, Module1),
|
|
|
|
% Process all the SCCs of the call graph in a bottom up order.
|
|
{ module_info_ensure_dependency_info(Module1, Module2) },
|
|
{ module_info_dependency_info(Module2, DepInfo) },
|
|
{ hlds_dependency_info_get_dependency_ordering(DepInfo, SCCs) },
|
|
termination__process_all_sccs(SCCs, Module2, PassInfo, Module),
|
|
|
|
globals__io_lookup_bool_option(make_optimization_interface,
|
|
MakeOptInt),
|
|
( { MakeOptInt = yes } ->
|
|
termination__make_opt_int(PredIds, Module)
|
|
;
|
|
[]
|
|
).
|
|
|
|
% This predicate sets the functor info depending on the value of the
|
|
% termination_norm option. The functor info field stores the weight which
|
|
% is associated with each functor, and may contain information about which
|
|
% subterms contribute to the size of that functor.
|
|
|
|
:- pred set_functor_info(globals__termination_norm, module_info, functor_info).
|
|
:- mode set_functor_info(in, in, out) is det.
|
|
|
|
set_functor_info(total, _Module, total).
|
|
set_functor_info(simple, _Module, simple).
|
|
set_functor_info(num_data_elems, Module, use_map_and_args(WeightMap)) :-
|
|
find_weights(Module, WeightMap).
|
|
set_functor_info(size_data_elems, Module, use_map(WeightMap)) :-
|
|
find_weights(Module, WeightMap).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- pred termination__process_all_sccs(list(list(pred_proc_id)), module_info,
|
|
pass_info, module_info, io__state, io__state).
|
|
:- mode termination__process_all_sccs(in, in, in, out, di, uo) is det.
|
|
|
|
termination__process_all_sccs([], Module, _, Module) --> [].
|
|
termination__process_all_sccs([SCC | SCCs], Module0, PassInfo, Module) -->
|
|
termination__process_scc(SCC, Module0, PassInfo, Module1),
|
|
termination__process_all_sccs(SCCs, Module1, PassInfo, Module).
|
|
|
|
% 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(list(pred_proc_id), module_info, pass_info,
|
|
module_info, io__state, io__state).
|
|
:- mode termination__process_scc(in, in, in, out, di, uo) is det.
|
|
|
|
termination__process_scc(SCC, Module0, PassInfo, Module) -->
|
|
{ IsArgSizeKnown = lambda([PPId::in] is semidet, (
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_pred_proc_info(Module0, PredId, ProcId,
|
|
_, ProcInfo),
|
|
proc_info_get_maybe_arg_size_info(ProcInfo, yes(_))
|
|
)) },
|
|
{ list__filter(IsArgSizeKnown, SCC,
|
|
_SCCArgSizeKnown, SCCArgSizeUnknown) },
|
|
( { SCCArgSizeUnknown = [] } ->
|
|
{ ArgSizeErrors = [] },
|
|
{ TermErrors = [] },
|
|
{ Module1 = Module0 }
|
|
;
|
|
find_arg_sizes_in_scc(SCCArgSizeUnknown, Module0, PassInfo,
|
|
ArgSizeResult, TermErrors),
|
|
{
|
|
ArgSizeResult = ok(Solutions, OutputSupplierMap),
|
|
set_finite_arg_size_infos(Solutions,
|
|
OutputSupplierMap, Module0, Module1),
|
|
ArgSizeErrors = []
|
|
;
|
|
ArgSizeResult = error(Errors),
|
|
set_infinite_arg_size_infos(SCCArgSizeUnknown,
|
|
infinite(Errors), Module0, Module1),
|
|
ArgSizeErrors = Errors
|
|
}
|
|
),
|
|
{ IsTerminationKnown = lambda([PPId::in] is semidet, (
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_pred_proc_info(Module0, PredId, ProcId,
|
|
_, ProcInfo),
|
|
proc_info_get_maybe_arg_size_info(ProcInfo, yes(_))
|
|
)) },
|
|
{ list__filter(IsTerminationKnown, SCC,
|
|
_SCCTerminationKnown, SCCTerminationUnknown) },
|
|
( { SCCTerminationUnknown = [] } ->
|
|
{ Module = Module1 }
|
|
;
|
|
{ IsFatal = lambda([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),
|
|
{ prove_termination_in_scc(SCCTerminationUnknown,
|
|
Module1, PassInfo, SingleArgs,
|
|
TerminationResult) }
|
|
),
|
|
{ set_termination_infos(SCCTerminationUnknown,
|
|
TerminationResult, Module1, Module2) },
|
|
( { TerminationResult = can_loop(TerminationErrors) } ->
|
|
report_termination_errors(SCC, TerminationErrors,
|
|
Module2, Module)
|
|
;
|
|
{ Module = Module2 }
|
|
)
|
|
).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
% 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, Module).
|
|
set_finite_arg_size_infos([Soln | Solns], OutputSupplierMap, Module0, Module) :-
|
|
Soln = PPId - Gamma,
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_preds(Module0, 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(ProcInfo, yes(ArgSizeInfo),
|
|
ProcInfo1),
|
|
map__set(ProcTable, ProcId, ProcInfo1, ProcTable1),
|
|
pred_info_set_procedures(PredInfo, ProcTable1, PredInfo1),
|
|
map__set(PredTable0, PredId, PredInfo1, PredTable),
|
|
module_info_set_preds(Module0, PredTable, Module1),
|
|
set_finite_arg_size_infos(Solns, OutputSupplierMap, Module1, 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, Module).
|
|
set_infinite_arg_size_infos([PPId | PPIds], ArgSizeInfo, Module0, Module) :-
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_preds(Module0, PredTable0),
|
|
map__lookup(PredTable0, PredId, PredInfo),
|
|
pred_info_procedures(PredInfo, ProcTable),
|
|
map__lookup(ProcTable, ProcId, ProcInfo),
|
|
proc_info_set_maybe_arg_size_info(ProcInfo, yes(ArgSizeInfo),
|
|
ProcInfo1),
|
|
map__set(ProcTable, ProcId, ProcInfo1, ProcTable1),
|
|
pred_info_set_procedures(PredInfo, ProcTable1, PredInfo1),
|
|
map__set(PredTable0, PredId, PredInfo1, PredTable),
|
|
module_info_set_preds(Module0, PredTable, Module1),
|
|
set_infinite_arg_size_infos(PPIds, ArgSizeInfo, Module1, 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, Module).
|
|
set_termination_infos([PPId | PPIds], TerminationInfo, Module0, Module) :-
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_preds(Module0, PredTable0),
|
|
map__lookup(PredTable0, PredId, PredInfo0),
|
|
pred_info_procedures(PredInfo0, ProcTable0),
|
|
map__lookup(ProcTable0, ProcId, ProcInfo0),
|
|
proc_info_set_maybe_termination_info(ProcInfo0, yes(TerminationInfo),
|
|
ProcInfo),
|
|
map__det_update(ProcTable0, ProcId, ProcInfo, ProcTable),
|
|
pred_info_set_procedures(PredInfo0, ProcTable, PredInfo),
|
|
map__det_update(PredTable0, PredId, PredInfo, PredTable),
|
|
module_info_set_preds(Module0, PredTable, Module1),
|
|
set_termination_infos(PPIds, TerminationInfo, Module1, Module).
|
|
|
|
:- pred report_termination_errors(list(pred_proc_id)::in,
|
|
list(term_errors__error)::in, module_info::in, module_info::out,
|
|
io__state::di, io__state::uo) is det.
|
|
|
|
report_termination_errors(SCC, Errors, Module0, Module) -->
|
|
globals__io_lookup_bool_option(check_termination,
|
|
NormalErrors),
|
|
globals__io_lookup_bool_option(verbose_check_termination,
|
|
VerboseErrors),
|
|
(
|
|
{ IsCheckTerm = lambda([PPId::in] is semidet, (
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_pred_proc_info(Module0, PredId, ProcId,
|
|
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, Module0),
|
|
io__set_exit_status(1),
|
|
{ module_info_incr_errors(Module0, Module) }
|
|
;
|
|
{ IsNonImported = lambda([PPId::in] is semidet, (
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_pred_proc_info(Module0, PredId, ProcId,
|
|
PredInfo, _),
|
|
\+ pred_info_is_imported(PredInfo)
|
|
)) },
|
|
{ list__filter(IsNonImported, SCC, NonImportedPPIds) },
|
|
{ NonImportedPPIds = [_ | _] },
|
|
|
|
% Only output warnings of non-termination for
|
|
% direct errors, unless verbose errors are
|
|
% enabled. Direct errors are errors where the
|
|
% compiler analysed the code and was not able to
|
|
% prove termination. Indirect warnings are
|
|
% created when code is used/called which the
|
|
% compiler was unable to analyse/prove termination of.
|
|
{ VerboseErrors = yes ->
|
|
PrintErrors = Errors
|
|
; NormalErrors = yes ->
|
|
IsNonSimple = lambda([ContextError::in] is semidet, (
|
|
ContextError = _Context - Error,
|
|
\+ indirect_error(Error)
|
|
)),
|
|
list__filter(IsNonSimple, Errors, PrintErrors)
|
|
;
|
|
fail
|
|
}
|
|
->
|
|
term_errors__report_term_errors(SCC, PrintErrors, Module0),
|
|
{ Module = Module0 }
|
|
;
|
|
{ Module = Module0 }
|
|
).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- pred check_preds(list(pred_id), module_info, module_info,
|
|
io__state, io__state).
|
|
:- mode check_preds(in, in, out, di, 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, Module, State, State).
|
|
check_preds([PredId | PredIds] , Module0, Module, State0, State) :-
|
|
write_pred_progress_message("% Checking ", PredId, Module0,
|
|
State0, State1),
|
|
globals__io_lookup_bool_option(make_optimization_interface,
|
|
MakeOptInt, State1, State2),
|
|
module_info_preds(Module0, 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),
|
|
map__keys(ProcTable0, ProcIds),
|
|
(
|
|
% 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,
|
|
Module0, 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(PredInfo0, ProcTable, PredInfo),
|
|
map__set(PredTable0, PredId, PredInfo, PredTable),
|
|
module_info_set_preds(Module0, PredTable, Module1),
|
|
check_preds(PredIds, Module1, Module, State2, State).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
% 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, list(proc_id), pred_id,
|
|
module_info, proc_table, proc_table).
|
|
:- mode set_compiler_gen_terminates(in, in, in, in, in, out) is semidet.
|
|
|
|
set_compiler_gen_terminates(PredInfo, ProcIds, PredId, Module,
|
|
ProcTable0, ProcTable) :-
|
|
(
|
|
code_util__predinfo_is_builtin(PredInfo)
|
|
->
|
|
set_builtin_terminates(ProcIds, PredId, PredInfo, Module,
|
|
ProcTable0, ProcTable)
|
|
;
|
|
pred_info_name(PredInfo, Name),
|
|
pred_info_arity(PredInfo, Arity),
|
|
(
|
|
special_pred_name_arity(SpecPredId0, Name, _, Arity),
|
|
pred_info_module(PredInfo, ModuleName),
|
|
( mercury_private_builtin_module(ModuleName)
|
|
; mercury_public_builtin_module(ModuleName)
|
|
)
|
|
->
|
|
SpecialPredId = SpecPredId0
|
|
;
|
|
special_pred_name_arity(SpecialPredId, _, Name, Arity)
|
|
)
|
|
->
|
|
set_generated_terminates(ProcIds, SpecialPredId,
|
|
ProcTable0, ProcTable)
|
|
;
|
|
fail
|
|
).
|
|
|
|
:- pred set_generated_terminates(list(proc_id), special_pred_id,
|
|
proc_table, proc_table).
|
|
:- mode set_generated_terminates(in, in, in, out) is det.
|
|
|
|
set_generated_terminates([], _, ProcTable, ProcTable).
|
|
set_generated_terminates([ProcId | ProcIds], SpecialPredId,
|
|
ProcTable0, ProcTable) :-
|
|
map__lookup(ProcTable0, ProcId, ProcInfo0),
|
|
proc_info_headvars(ProcInfo0, HeadVars),
|
|
special_pred_id_to_termination(SpecialPredId, HeadVars,
|
|
ArgSize, Termination),
|
|
proc_info_set_maybe_arg_size_info(ProcInfo0, yes(ArgSize), ProcInfo1),
|
|
proc_info_set_maybe_termination_info(ProcInfo1, yes(Termination),
|
|
ProcInfo),
|
|
map__det_update(ProcTable0, ProcId, ProcInfo, ProcTable1),
|
|
set_generated_terminates(ProcIds, SpecialPredId,
|
|
ProcTable1, 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), pred_id, pred_info, module_info,
|
|
proc_table, proc_table).
|
|
:- mode set_builtin_terminates(in, in, in, in, in, out) is det.
|
|
|
|
set_builtin_terminates([], _, _, _, ProcTable, ProcTable).
|
|
set_builtin_terminates([ProcId | ProcIds], PredId, PredInfo, Module,
|
|
ProcTable0, ProcTable) :-
|
|
map__lookup(ProcTable0, 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(ProcInfo0, ArgSizeInfo, ProcInfo1),
|
|
proc_info_set_maybe_termination_info(ProcInfo1, yes(cannot_loop),
|
|
ProcInfo),
|
|
map__det_update(ProcTable0, ProcId, ProcInfo, ProcTable1),
|
|
set_builtin_terminates(ProcIds, PredId, PredInfo, Module,
|
|
ProcTable1, ProcTable).
|
|
|
|
:- pred all_args_input_or_zero_size(module_info, pred_info, proc_info).
|
|
:- mode all_args_input_or_zero_size(in, in, 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), list(mode), module_info).
|
|
:- mode all_args_input_or_zero_size_2(in, in, in) is semidet.
|
|
|
|
all_args_input_or_zero_size_2([], [], _).
|
|
all_args_input_or_zero_size_2([], [_|_], _) :-
|
|
error("all_args_input_or_zero_size_2: Unmatched variables.").
|
|
all_args_input_or_zero_size_2([_|_], [], _) :-
|
|
error("all_args_input_or_zero_size_2: Unmatched variables").
|
|
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, ProcTable).
|
|
change_procs_arg_size_info([ProcId | ProcIds], Override, ArgSize,
|
|
ProcTable0, ProcTable) :-
|
|
map__lookup(ProcTable0, ProcId, ProcInfo0),
|
|
(
|
|
(
|
|
Override = yes
|
|
;
|
|
proc_info_get_maybe_arg_size_info(ProcInfo0, no)
|
|
)
|
|
->
|
|
proc_info_set_maybe_arg_size_info(ProcInfo0,
|
|
yes(ArgSize), ProcInfo),
|
|
map__det_update(ProcTable0, ProcId, ProcInfo, ProcTable1)
|
|
;
|
|
ProcTable1 = ProcTable0
|
|
),
|
|
change_procs_arg_size_info(ProcIds, Override, ArgSize,
|
|
ProcTable1, 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, ProcTable).
|
|
change_procs_termination_info([ProcId | ProcIds], Override, Termination,
|
|
ProcTable0, ProcTable) :-
|
|
map__lookup(ProcTable0, ProcId, ProcInfo0),
|
|
(
|
|
(
|
|
Override = yes
|
|
;
|
|
proc_info_get_maybe_termination_info(ProcInfo0, no)
|
|
)
|
|
->
|
|
proc_info_set_maybe_termination_info(ProcInfo0,
|
|
yes(Termination), ProcInfo),
|
|
map__det_update(ProcTable0, ProcId, ProcInfo, ProcTable1)
|
|
;
|
|
ProcTable1 = ProcTable0
|
|
),
|
|
change_procs_termination_info(ProcIds, Override, Termination,
|
|
ProcTable1, 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), module_info, io__state,
|
|
io__state).
|
|
:- mode termination__make_opt_int(in, in, di, uo) is det.
|
|
|
|
termination__make_opt_int(PredIds, Module) -->
|
|
{ module_info_name(Module, ModuleName) },
|
|
module_name_to_file_name(ModuleName, ".opt.tmp", no, OptFileName),
|
|
globals__io_lookup_bool_option(verbose, Verbose),
|
|
maybe_write_string(Verbose,
|
|
"% Appending termination_info pragmas to `"),
|
|
maybe_write_string(Verbose, OptFileName),
|
|
maybe_write_string(Verbose, "'..."),
|
|
maybe_flush_output(Verbose),
|
|
|
|
io__open_append(OptFileName, OptFileRes),
|
|
( { OptFileRes = ok(OptFile) },
|
|
io__set_output_stream(OptFile, OldStream),
|
|
termination__make_opt_int_preds(PredIds, Module),
|
|
io__set_output_stream(OldStream, _),
|
|
io__close_output(OptFile),
|
|
maybe_write_string(Verbose, " done.\n")
|
|
; { OptFileRes = error(IOError) },
|
|
% failed to open the .opt file for processing
|
|
maybe_write_string(Verbose, " failed!\n"),
|
|
{ io__error_message(IOError, IOErrorMessage) },
|
|
io__write_strings(["Error opening file `",
|
|
OptFileName, "' for output: ", IOErrorMessage]),
|
|
io__set_exit_status(1)
|
|
).
|
|
|
|
:- pred termination__make_opt_int_preds(list(pred_id), module_info,
|
|
io__state, io__state).
|
|
:- mode termination__make_opt_int_preds(in, in, di, uo) is det.
|
|
|
|
termination__make_opt_int_preds([], _Module) --> [].
|
|
termination__make_opt_int_preds([ PredId | PredIds ], Module) -->
|
|
{ module_info_preds(Module, PredTable) },
|
|
{ map__lookup(PredTable, PredId, PredInfo) },
|
|
{ pred_info_import_status(PredInfo, ImportStatus) },
|
|
(
|
|
{ ImportStatus = exported },
|
|
{ \+ code_util__compiler_generated(PredInfo) }
|
|
->
|
|
{ pred_info_name(PredInfo, PredName) },
|
|
{ pred_info_procedures(PredInfo, ProcTable) },
|
|
{ pred_info_procids(PredInfo, ProcIds) },
|
|
{ pred_info_get_is_pred_or_func(PredInfo, PredOrFunc) },
|
|
{ pred_info_module(PredInfo, ModuleName) },
|
|
{ pred_info_context(PredInfo, Context) },
|
|
{ SymName = qualified(ModuleName, PredName) },
|
|
termination__make_opt_int_procs(PredId, ProcIds, ProcTable,
|
|
PredOrFunc, SymName, Context)
|
|
;
|
|
[]
|
|
),
|
|
termination__make_opt_int_preds(PredIds, Module).
|
|
|
|
:- pred termination__make_opt_int_procs(pred_id, list(proc_id), proc_table,
|
|
pred_or_func, sym_name, prog_context, io__state, io__state).
|
|
:- mode termination__make_opt_int_procs(in, in, in, in, in, in, di, uo) is det.
|
|
|
|
termination__make_opt_int_procs(_PredId, [], _, _, _, _) --> [].
|
|
termination__make_opt_int_procs(PredId, [ ProcId | ProcIds ], ProcTable,
|
|
PredOrFunc, SymName, Context) -->
|
|
{ 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),
|
|
termination__make_opt_int_procs(PredId, ProcIds, ProcTable,
|
|
PredOrFunc, SymName, Context).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
% 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__write_string(":- pragma termination_info("),
|
|
{ varset__init(InitVarSet) },
|
|
(
|
|
{ PredOrFunc = predicate },
|
|
mercury_output_pred_mode_subdecl(InitVarSet, SymName,
|
|
ModeList, no, Context)
|
|
;
|
|
{ PredOrFunc = function },
|
|
{ pred_args_to_func_args(ModeList, FuncModeList, RetMode) },
|
|
mercury_output_func_mode_subdecl(InitVarSet, SymName,
|
|
FuncModeList, RetMode, no, Context)
|
|
),
|
|
io__write_string(", "),
|
|
termination__write_maybe_arg_size_info(MaybeArgSize, no),
|
|
io__write_string(", "),
|
|
termination__write_maybe_termination_info(MaybeTermination, no),
|
|
io__write_string(").\n").
|
|
|
|
termination__write_maybe_arg_size_info(MaybeArgSizeInfo, Verbose) -->
|
|
(
|
|
{ MaybeArgSizeInfo = no },
|
|
io__write_string("not_set")
|
|
;
|
|
{ MaybeArgSizeInfo = yes(infinite(Error)) },
|
|
io__write_string("infinite"),
|
|
( { Verbose = yes } ->
|
|
io__write_string("("),
|
|
io__write(Error),
|
|
io__write_string(")")
|
|
;
|
|
[]
|
|
)
|
|
;
|
|
{ MaybeArgSizeInfo = yes(finite(Const, UsedArgs)) },
|
|
io__write_string("finite("),
|
|
io__write_int(Const),
|
|
io__write_string(", "),
|
|
termination__write_used_args(UsedArgs),
|
|
io__write_string(")")
|
|
).
|
|
|
|
:- pred termination__write_used_args(list(bool)::in,
|
|
io__state::di, io__state::uo) is det.
|
|
|
|
termination__write_used_args([]) -->
|
|
io__write_string("[]").
|
|
termination__write_used_args([UsedArg | UsedArgs]) -->
|
|
io__write_string("["),
|
|
io__write(UsedArg),
|
|
termination__write_used_args_2(UsedArgs),
|
|
io__write_string("]").
|
|
|
|
:- pred termination__write_used_args_2(list(bool)::in,
|
|
io__state::di, io__state::uo) is det.
|
|
|
|
termination__write_used_args_2([]) --> [].
|
|
termination__write_used_args_2([ UsedArg | UsedArgs ]) -->
|
|
io__write_string(", "),
|
|
io__write(UsedArg),
|
|
termination__write_used_args_2(UsedArgs).
|
|
|
|
termination__write_maybe_termination_info(MaybeTerminationInfo, Verbose) -->
|
|
(
|
|
{ MaybeTerminationInfo = no },
|
|
io__write_string("not_set")
|
|
;
|
|
{ MaybeTerminationInfo = yes(cannot_loop) },
|
|
io__write_string("cannot_loop")
|
|
;
|
|
{ MaybeTerminationInfo = yes(can_loop(Error)) },
|
|
io__write_string("can_loop"),
|
|
( { Verbose = yes } ->
|
|
io__write_string("("),
|
|
io__write(Error),
|
|
io__write_string(")")
|
|
;
|
|
[]
|
|
)
|
|
).
|