mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-14 13:23:53 +00:00
Estimated hours taken: 11 Finish off the centralization of the file name handling code, and add support for generating intermediate files in subdirectories. scripts/mmake.in: Add a new boolean option `--use-subdirs'; if enabled, it just sets the environment variable MMAKE_USE_SUBDIRS to `yes' before invoking Make. Also add negative versions of the various options (`--no-use-subdirs', `--no-verbose', `--no-save-makefile'). scripts/Mmake.rules: Add code to handle generating intermediate file names in subdirectories. If MMAKE_USE_SUBDIRS=yes, we add `--use-subdirs' to MCFLAGS, and most of the pattern rules are changed to use subdirectories. Note that getting this to work required a bit of a hack: due to what seem to be bugs in GNU Make, `mmake depend' needs to do `mmc --make-short-interface *.m' to get things started. But once the int3s are there, the dependencies seem to work OK. compiler/options.m: Add a new boolean option `--use-subdirs'. compiler/modules.m: Add new predicate `fact_table_file_name'. Add an extra argument to `module_name_to_file_name', specifying whether or not to create any directories needed for the file name. Change all the file-name-creating predicates here to go through a single new predicate `choose_file_name', and add code in that predicate to handle the `--use-subdirs' option. Also if the `--use-subdirs' option is set, don't use the compact dependencies format, since it can't work in that case. compiler/fact_table.m: Call `fact_table_file_name' rather than using `string__append'. compiler/mercury_compile.m: Change `link_module_list' and `join_module_list' so that they create file names by calling `module_name_to_file_name'. compiler/export.m: compiler/intermod.m: compiler/llds_out.m: compiler/mercury_compile.m: compiler/module_qual.m: compiler/modules.m: compiler/termination.m: compiler/trans_opt.m: compiler/unused_args.m: Change all calls to `module_name_to_file_name' to pass the extra argument specifying whether or not to make any directories needed for the file name. library/Mmakefile: Change the rule for `mmake install_ints' so that it creates a `Mercury' subdirectory with symbolic links `ints', `int2s', `int3s', `opts', and `trans_opts' which just point to `..'. This is needed for the `--use-subdirs' option to work, because Mmake currently does not support mixing libraries compiled with and without `--use-subdirs'. doc/user_guide.texi: Document the above changes.
815 lines
30 KiB
Mathematica
815 lines
30 KiB
Mathematica
%----------------------------------------------------------------------------%
|
|
% Copyright (C) 1997-1998 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, hlds_pred, 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, term__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, dependency_graph.
|
|
:- import_module mode_util, hlds_out, code_util, prog_out, prog_util.
|
|
:- import_module mercury_to_mercury, varset, 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) :-
|
|
globals__io_lookup_bool_option(make_optimization_interface,
|
|
MakeOptInt, State0, State1),
|
|
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
|
|
;
|
|
( ImportStatus = exported
|
|
; ImportStatus = local
|
|
; ImportStatus = pseudo_exported
|
|
)
|
|
->
|
|
( check_marker(Markers, terminates) ->
|
|
change_procs_termination_info(ProcIds, yes,
|
|
cannot_loop, ProcTable0, ProcTable2)
|
|
;
|
|
ProcTable2 = ProcTable0
|
|
)
|
|
;
|
|
( ImportStatus = imported
|
|
; ImportStatus = opt_imported
|
|
; ImportStatus = pseudo_imported % should this be here?
|
|
)
|
|
->
|
|
% 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)
|
|
;
|
|
% ( ImportStatus = abstract_imported
|
|
% ; ImportStatus = abstract_exported
|
|
% ),
|
|
% This should not happen, as procedures are being processed
|
|
% here, and these import_status' refer to abstract types.
|
|
error("termination__check_preds: Unexpected import status")
|
|
),
|
|
( 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, State1, State).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
% This predicate checks each ProcId in the list to see if it is a compiler
|
|
% generated predicate, or a mercury_builtin predicate. If it is, then the
|
|
% compiler sets the termination property of the ProcIds accordingly.
|
|
|
|
:- 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(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),
|
|
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)
|
|
;
|
|
% failed to open the .opt file for processing
|
|
io__write_strings(["Cannot open `",
|
|
OptFileName, "' for output\n"]),
|
|
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, term__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(")")
|
|
;
|
|
[]
|
|
)
|
|
).
|