mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-13 12:53:53 +00:00
Estimated hours taken: 50
Add support for nested modules.
- module names may themselves be module-qualified
- modules may contain `:- include_module' declarations
which name sub-modules
- a sub-module has access to all the declarations in the
parent module (including its implementation section).
This support is not yet complete; see the BUGS and LIMITATIONS below.
LIMITATIONS
- source file names must match module names
(just as they did previously)
- mmc doesn't allow path names on the command line any more
(e.g. `mmc --make-int ../library/foo.m').
- import_module declarations must use the fully-qualified module name
- module qualifiers must use the fully-qualified module name
- no support for root-qualified module names
(e.g. `:parent:child' instead of `parent:child').
- modules may not be physically nested (only logical nesting, via
`include_module').
BUGS
- doesn't check that the parent module is imported/used before allowing
import/use of its sub-modules.
- doesn't check that there is an include_module declaration in the
parent for each module claiming to be a child of that parent
- privacy of private modules is not enforced
-------------------
NEWS:
Mention that we support nested modules.
library/ops.m:
library/nc_builtin.nl:
library/sp_builtin.nl:
compiler/mercury_to_mercury.m:
Add `include_module' as a new prefix operator.
Change the associativity of `:' from xfy to yfx
(since this made parsing module qualifiers slightly easier).
compiler/prog_data.m:
Add new `include_module' declaration.
Change the `module_name' and `module_specifier' types
from strings to sym_names, so that module names can
themselves be module qualified.
compiler/modules.m:
Add predicates module_name_to_file_name/2 and
file_name_to_module_name/2.
Lots of changes to handle parent module dependencies,
to create parent interface (`.int0') files, to read them in,
to output correct dependencies information for them to the
`.d' and `.dep' files, etc.
Rewrite a lot of the code to improve the readability
(add comments, use subroutines, better variable names).
Also fix a couple of bugs:
- generate_dependencies was using the transitive implementation
dependencies rather than the transitive interface dependencies
to compute the `.int3' dependencies when writing `.d' files
(this bug was introduced during crs's changes to support
`.trans_opt' files)
- when creating the `.int' file, it was reading in the
interfaces for modules imported in the implementation section,
not just those in the interface section.
This meant that the compiler missed a lot of errors.
library/graph.m:
library/lexer.m:
library/term.m:
library/term_io.m:
library/varset.m:
compiler/*.m:
Add `:- import_module' declarations to the interface needed
by declarations in the interface. (The previous version
of the compiler did not detect these missing interface imports,
due to the above-mentioned bug in modules.m.)
compiler/mercury_compile.m:
compiler/intermod.m:
Change mercury_compile__maybe_grab_optfiles and
intermod__grab_optfiles so that they grab the opt files for
parent modules as well as the ones for imported modules.
compiler/mercury_compile.m:
Minor changes to handle parent module dependencies.
(Also improve the wording of the warning about trans-opt
dependencies.)
compiler/make_hlds.m:
compiler/module_qual.m:
Ignore `:- include_module' declarations.
compiler/module_qual.m:
A couple of small changes to handle nested module names.
compiler/prog_out.m:
compiler/prog_util.m:
Add new predicates string_to_sym_name/3 (prog_util.m) and
sym_name_to_string/{2,3} (prog_out.m).
compiler/*.m:
Replace many occurrences of `string' with `module_name'.
Change code that prints out module names or converts
them to strings or filenames to handle the fact that
module names are now sym_names intead of strings.
Also change a few places (e.g. in intermod.m, hlds_module.m)
where the code assumed that any qualified symbol was
fully-qualified.
compiler/prog_io.m:
compiler/prog_io_goal.m:
Move sym_name_and_args/3, parse_qualified_term/4 and
parse_qualified_term/5 preds from prog_io_goal.m to prog_io.m,
since they are very similar to the parse_symbol_name/2 predicate
already in prog_io.m. Rewrite these predicates, both
to improve maintainability, and to handle the newly
allowed syntax (module-qualified module names).
Rename parse_qualified_term/5 as `parse_implicit_qualified_term'.
compiler/prog_io.m:
Rewrite the handling of `:- module' and `:- end_module'
declarations, so that it can handle nested modules.
Add code to parse `include_module' declarations.
compiler/prog_util.m:
compiler/*.m:
Add new predicates mercury_public_builtin_module/1 and
mercury_private_builtin_module/1 in prog_util.m.
Change most of the hard-coded occurrences of "mercury_builtin"
to call mercury_private_builtin_module/1 or
mercury_public_builtin_module/1 or both.
compiler/llds_out.m:
Add llds_out__sym_name_mangle/2, for mangling module names.
compiler/special_pred.m:
compiler/mode_util.m:
compiler/clause_to_proc.m:
compiler/prog_io_goal.m:
compiler/lambda.m:
compiler/polymorphism.m:
Move the predicates in_mode/1, out_mode/1, and uo_mode/1
from special_pred.m to mode_util.m, and change various
hard-coded definitions to instead call these predicates.
compiler/polymorphism.m:
Ensure that the type names `type_info' and `typeclass_info' are
module-qualified in the generated code. This avoids a problem
where the code generated by polymorphism.m was not considered
type-correct, due to the type `type_info' not matching
`mercury_builtin:type_info'.
compiler/check_typeclass.m:
Simplify the code for check_instance_pred and
get_matching_instance_pred_ids.
compiler/mercury_compile.m:
compiler/modules.m:
Disallow directory names in command-line arguments.
compiler/options.m:
compiler/handle_options.m:
compiler/mercury_compile.m:
compiler/modules.m:
Add a `--make-private-interface' option.
The private interface file `<module>.int0' contains
all the declarations in the module; it is used for
compiling sub-modules.
scripts/Mmake.rules:
scripts/Mmake.vars.in:
Add support for creating `.int0' and `.date0' files
by invoking mmc with `--make-private-interface'.
doc/user_guide.texi:
Document `--make-private-interface' and the `.int0'
and `.date0' file extensions.
doc/reference_manual.texi:
Document nested modules.
util/mdemangle.c:
profiler/demangle.m:
Demangle names with multiple module qualifiers.
tests/general/Mmakefile:
tests/general/string_format_test.m:
tests/general/string_format_test.exp:
tests/general/string__format_test.m:
tests/general/string__format_test.exp:
tests/general/.cvsignore:
Change the `:- module string__format_test' declaration in
`string__format_test.m' to `:- module string_format_test',
because with the original declaration the `__' was taken
as a module qualifier, which lead to an error message.
Hence rename the file accordingly, to avoid the warning
about file name not matching module name.
tests/invalid/Mmakefile:
tests/invalid/missing_interface_import.m:
tests/invalid/missing_interface_import.err_exp:
Regression test to check that the compiler reports
errors for missing `import_module' in the interface section.
tests/invalid/*.err_exp:
tests/warnings/unused_args_test.exp:
tests/warnings/unused_import.exp:
Update the expected diagnostics output for the test cases to
reflect a few minor changes to the warning messages.
tests/hard_coded/Mmakefile:
tests/hard_coded/parent.m:
tests/hard_coded/parent.child.m:
tests/hard_coded/parent.exp:
tests/hard_coded/parent2.m:
tests/hard_coded/parent2.child.m:
tests/hard_coded/parent2.exp:
Two simple tests case for the use of nested modules with
separate compilation.
590 lines
20 KiB
Mathematica
590 lines
20 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.
|
|
%-----------------------------------------------------------------------------
|
|
%
|
|
% term_pass2.m
|
|
%
|
|
% Main author of original version: crs.
|
|
% Main author of this version: zs.
|
|
%
|
|
% This file contains the code that tries to prove that procedures terminate.
|
|
%
|
|
% For details, please refer to the papers mentioned in termination.m.
|
|
%-----------------------------------------------------------------------------
|
|
|
|
:- module term_pass2.
|
|
:- interface.
|
|
|
|
:- import_module hlds_module, hlds_pred, term_util.
|
|
:- import_module list.
|
|
|
|
:- pred prove_termination_in_scc(list(pred_proc_id)::in, module_info::in,
|
|
pass_info::in, int::in, termination_info::out) is det.
|
|
|
|
:- implementation.
|
|
|
|
:- import_module term_traversal, term_errors.
|
|
:- import_module hlds_goal, prog_data, type_util, mode_util.
|
|
|
|
:- import_module std_util, bool, int, assoc_list.
|
|
:- import_module set, bag, map, term, require.
|
|
|
|
:- type fixpoint_dir
|
|
---> up
|
|
; down.
|
|
|
|
:- type call_weight_info
|
|
== pair(list(term_errors__error), call_weight_graph).
|
|
|
|
|
|
:- type call_weight_graph
|
|
== map(pred_proc_id, % The max noninfinite weight
|
|
% call from this proc
|
|
map(pred_proc_id, % to this proc
|
|
pair(term__context, int))).
|
|
% is at this context and with
|
|
% this weight.
|
|
|
|
:- type pass2_result
|
|
---> ok(
|
|
call_weight_info,
|
|
used_args
|
|
)
|
|
; error(
|
|
list(term_errors__error)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------
|
|
|
|
prove_termination_in_scc(SCC, Module, PassInfo, SingleArgs, Termination) :-
|
|
init_rec_input_suppliers(SCC, Module, InitRecSuppliers),
|
|
prove_termination_in_scc_trial(SCC, InitRecSuppliers, down,
|
|
Module, PassInfo, Termination1),
|
|
(
|
|
Termination1 = can_loop(Errors),
|
|
(
|
|
% On large SCCs, single arg analysis can require
|
|
% many iterations, so we allow the user to limit
|
|
% the size of the SCCs we will try it on.
|
|
list__length(SCC, ProcCount),
|
|
ProcCount =< SingleArgs,
|
|
|
|
% Don't try single arg analysis if it cannot cure
|
|
% the reason for the failure of the main analysis.
|
|
\+ (
|
|
member(Error, Errors),
|
|
Error = _ - imported_pred
|
|
),
|
|
prove_termination_in_scc_single_arg(SCC,
|
|
Module, PassInfo)
|
|
->
|
|
Termination = cannot_loop
|
|
;
|
|
Termination = Termination1
|
|
)
|
|
;
|
|
Termination1 = cannot_loop,
|
|
Termination = Termination1
|
|
).
|
|
|
|
% Initialise the set of recursive input suppliers to be the set
|
|
% of all input variables in all procedures of the SCC.
|
|
|
|
:- pred init_rec_input_suppliers(list(pred_proc_id)::in, module_info::in,
|
|
used_args::out) is det.
|
|
|
|
init_rec_input_suppliers([], _, InitMap) :-
|
|
map__init(InitMap).
|
|
init_rec_input_suppliers([PPId | PPIds], Module, RecSupplierMap) :-
|
|
init_rec_input_suppliers(PPIds, Module, RecSupplierMap0),
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_pred_proc_info(Module, PredId, ProcId, _, ProcInfo),
|
|
proc_info_headvars(ProcInfo, HeadVars),
|
|
proc_info_argmodes(ProcInfo, ArgModes),
|
|
partition_call_args(Module, ArgModes, HeadVars, InArgs, _OutVars),
|
|
MapIsInput = lambda([HeadVar::in, Bool::out] is det,
|
|
(
|
|
( bag__contains(InArgs, HeadVar) ->
|
|
Bool = yes
|
|
;
|
|
Bool = no
|
|
)
|
|
)),
|
|
list__map(MapIsInput, HeadVars, BoolList),
|
|
map__det_insert(RecSupplierMap0, PPId, BoolList, RecSupplierMap).
|
|
|
|
%-----------------------------------------------------------------------------
|
|
|
|
% Perform single arg analysis on the SCC.
|
|
%
|
|
% We pick one procedure in the SCC (one of those with minimal arity).
|
|
% We set the recursive input suppliers of this procedure to contain
|
|
% only the first input argument, and the recursive input suppliers
|
|
% of the other procedures to the empty set, and try a fixpoint
|
|
% iteration. If it works, great, if not, try again with the next
|
|
% input arg of the selected procedure, until we run out of input
|
|
% arguments of that procedure.
|
|
%
|
|
% While the fixpoint iteration in the main algorithm looks for the
|
|
% greatest fixpoint, in which the recursive input supplier sets
|
|
% cannot increase, in single arg analysis we are looking for a
|
|
% smallest fixpoint starting from a given location, so we must
|
|
% make sure that the recursive input supplier sets cannot decrease.
|
|
|
|
:- pred prove_termination_in_scc_single_arg(list(pred_proc_id)::in,
|
|
module_info::in, pass_info::in) is semidet.
|
|
|
|
prove_termination_in_scc_single_arg(SCC, Module, PassInfo) :-
|
|
( SCC = [FirstPPId | LaterPPIds] ->
|
|
lookup_proc_arity(FirstPPId, Module, FirstArity),
|
|
find_min_arity_proc(LaterPPIds, FirstPPId, FirstArity, Module,
|
|
TrialPPId, RestSCC),
|
|
prove_termination_in_scc_single_arg_2(TrialPPId, RestSCC, 1,
|
|
Module, PassInfo)
|
|
;
|
|
error("empty SCC in prove_termination_in_scc_single_arg")
|
|
).
|
|
|
|
% Find a procedure of minimum arity among the given list and the
|
|
% tentative guess.
|
|
|
|
:- pred find_min_arity_proc(list(pred_proc_id)::in, pred_proc_id::in, int::in,
|
|
module_info::in, pred_proc_id::out, list(pred_proc_id)::out) is det.
|
|
|
|
find_min_arity_proc([], BestSofarPPId, _, _, BestSofarPPId, []).
|
|
find_min_arity_proc([PPId | PPIds], BestSofarPPId, BestSofarArity, Module,
|
|
BestPPId, RestSCC) :-
|
|
lookup_proc_arity(PPId, Module, Arity),
|
|
( Arity < BestSofarArity ->
|
|
find_min_arity_proc(PPIds, PPId, Arity,
|
|
Module, BestPPId, RestSCC0),
|
|
RestSCC = [BestSofarPPId | RestSCC0]
|
|
;
|
|
find_min_arity_proc(PPIds, BestSofarPPId, BestSofarArity,
|
|
Module, BestPPId, RestSCC0),
|
|
RestSCC = [PPId | RestSCC0]
|
|
).
|
|
|
|
% Perform single arg analysis on the SCC.
|
|
|
|
:- pred prove_termination_in_scc_single_arg_2(pred_proc_id::in,
|
|
list(pred_proc_id)::in, int::in, module_info::in, pass_info::in)
|
|
is semidet.
|
|
|
|
prove_termination_in_scc_single_arg_2(TrialPPId, RestSCC, ArgNum0,
|
|
Module, PassInfo) :-
|
|
init_rec_input_suppliers_single_arg(TrialPPId, RestSCC,
|
|
ArgNum0, Module, InitRecSuppliers),
|
|
prove_termination_in_scc_trial([TrialPPId | RestSCC], InitRecSuppliers,
|
|
up, Module, PassInfo, Termination),
|
|
( Termination = cannot_loop ->
|
|
true
|
|
;
|
|
ArgNum1 is ArgNum0 + 1,
|
|
prove_termination_in_scc_single_arg_2(TrialPPId, RestSCC,
|
|
ArgNum1, Module, PassInfo)
|
|
).
|
|
|
|
:- pred init_rec_input_suppliers_single_arg(pred_proc_id::in,
|
|
list(pred_proc_id)::in, int::in, module_info::in, used_args::out)
|
|
is semidet.
|
|
|
|
init_rec_input_suppliers_single_arg(TrialPPId, RestSCC, ArgNum, Module,
|
|
RecSupplierMap) :-
|
|
TrialPPId = proc(PredId, ProcId),
|
|
module_info_pred_proc_info(Module, PredId, ProcId, _, ProcInfo),
|
|
proc_info_argmodes(ProcInfo, ArgModes),
|
|
init_rec_input_suppliers_add_single_arg(ArgModes, ArgNum,
|
|
Module, TrialPPIdRecSuppliers),
|
|
map__init(RecSupplierMap0),
|
|
map__det_insert(RecSupplierMap0, TrialPPId, TrialPPIdRecSuppliers,
|
|
RecSupplierMap1),
|
|
init_rec_input_suppliers_single_arg_others(RestSCC, Module,
|
|
RecSupplierMap1, RecSupplierMap).
|
|
|
|
:- pred init_rec_input_suppliers_add_single_arg(list(mode)::in, int::in,
|
|
module_info::in, list(bool)::out) is semidet.
|
|
|
|
init_rec_input_suppliers_add_single_arg([Mode | Modes], ArgNum, Module,
|
|
BoolList) :-
|
|
(
|
|
mode_is_input(Module, Mode),
|
|
ArgNum = 1
|
|
->
|
|
MapToNo = lambda([_Mode::in, Bool::out] is det,
|
|
(
|
|
Bool = no
|
|
)),
|
|
list__map(MapToNo, Modes, BoolList1),
|
|
BoolList = [yes | BoolList1]
|
|
;
|
|
(
|
|
mode_is_output(Module, Mode)
|
|
->
|
|
NextArgNum = ArgNum
|
|
;
|
|
mode_is_input(Module, Mode),
|
|
ArgNum > 1
|
|
->
|
|
NextArgNum is ArgNum - 1
|
|
;
|
|
fail
|
|
)
|
|
->
|
|
init_rec_input_suppliers_add_single_arg(Modes, NextArgNum,
|
|
Module, BoolList1),
|
|
BoolList = [no | BoolList1]
|
|
;
|
|
fail
|
|
).
|
|
|
|
:- pred init_rec_input_suppliers_single_arg_others(list(pred_proc_id)::in,
|
|
module_info::in, used_args::in, used_args::out) is det.
|
|
|
|
init_rec_input_suppliers_single_arg_others([], _,
|
|
RecSupplierMap, RecSupplierMap).
|
|
init_rec_input_suppliers_single_arg_others([PPId | PPIds], Module,
|
|
RecSupplierMap0, RecSupplierMap) :-
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_pred_proc_info(Module, PredId, ProcId, _, ProcInfo),
|
|
proc_info_headvars(ProcInfo, HeadVars),
|
|
MapToNo = lambda([_HeadVar::in, Bool::out] is det,
|
|
(
|
|
Bool = no
|
|
)),
|
|
list__map(MapToNo, HeadVars, BoolList),
|
|
map__det_insert(RecSupplierMap0, PPId, BoolList, RecSupplierMap1),
|
|
init_rec_input_suppliers_single_arg_others(PPIds, Module,
|
|
RecSupplierMap1, RecSupplierMap).
|
|
|
|
:- pred lookup_proc_arity(pred_proc_id::in, module_info::in, int::out) is det.
|
|
|
|
lookup_proc_arity(PPId, Module, Arity) :-
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_pred_proc_info(Module, PredId, ProcId, _, ProcInfo),
|
|
proc_info_headvars(ProcInfo, HeadVars),
|
|
list__length(HeadVars, Arity).
|
|
|
|
%-----------------------------------------------------------------------------
|
|
|
|
:- pred prove_termination_in_scc_trial(list(pred_proc_id)::in, used_args::in,
|
|
fixpoint_dir::in, module_info::in, pass_info::in,
|
|
termination_info::out) is det.
|
|
|
|
prove_termination_in_scc_trial(SCC, InitRecSuppliers, FixDir, Module,
|
|
PassInfo, Termination) :-
|
|
prove_termination_in_scc_fixpoint(SCC, FixDir, Module, PassInfo,
|
|
InitRecSuppliers, Result),
|
|
(
|
|
Result = ok(CallInfo, _),
|
|
CallInfo = InfCalls - CallWeights,
|
|
(
|
|
InfCalls \= []
|
|
->
|
|
PassInfo = pass_info(_, MaxErrors, _),
|
|
list__take_upto(MaxErrors, InfCalls, ReportedInfCalls),
|
|
Termination = can_loop(ReportedInfCalls)
|
|
;
|
|
zero_or_positive_weight_cycles(CallWeights, Module,
|
|
Cycles),
|
|
Cycles \= []
|
|
->
|
|
PassInfo = pass_info(_, MaxErrors, _),
|
|
list__take_upto(MaxErrors, Cycles, ReportedCycles),
|
|
Termination = can_loop(ReportedCycles)
|
|
;
|
|
Termination = cannot_loop
|
|
)
|
|
;
|
|
Result = error(Errors),
|
|
Termination = can_loop(Errors)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------
|
|
|
|
:- pred prove_termination_in_scc_fixpoint(list(pred_proc_id)::in,
|
|
fixpoint_dir::in, module_info::in, pass_info::in, used_args::in,
|
|
pass2_result::out) is det.
|
|
|
|
prove_termination_in_scc_fixpoint(SCC, FixDir, Module, PassInfo,
|
|
RecSupplierMap0, Result) :-
|
|
% unsafe_perform_io(io__write_string("prove_termination_in_scc\n")),
|
|
% unsafe_perform_io(io__write(RecSupplierMap0)),
|
|
% unsafe_perform_io(io__write_string("\n")),
|
|
map__init(NewRecSupplierMap0),
|
|
map__init(CallWeightGraph0),
|
|
CallInfo0 = [] - CallWeightGraph0,
|
|
prove_termination_in_scc_pass(SCC, FixDir, Module, PassInfo,
|
|
RecSupplierMap0, NewRecSupplierMap0, CallInfo0, Result1),
|
|
(
|
|
Result1 = ok(_, RecSupplierMap1),
|
|
( RecSupplierMap1 = RecSupplierMap0 ->
|
|
% We are at a fixed point, so further analysis
|
|
% will not get any better results.
|
|
Result = Result1
|
|
;
|
|
prove_termination_in_scc_fixpoint(SCC, FixDir,
|
|
Module, PassInfo, RecSupplierMap1, Result)
|
|
)
|
|
;
|
|
Result1 = error(_),
|
|
Result = Result1
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------
|
|
|
|
% Process a whole SCC, to determine the termination property of each
|
|
% procedure in that SCC.
|
|
|
|
:- pred prove_termination_in_scc_pass(list(pred_proc_id)::in, fixpoint_dir::in,
|
|
module_info::in, pass_info::in, used_args::in, used_args::in,
|
|
call_weight_info::in, pass2_result::out) is det.
|
|
|
|
prove_termination_in_scc_pass([], _, _, _, _, NewRecSupplierMap, CallInfo,
|
|
ok(CallInfo, NewRecSupplierMap)).
|
|
prove_termination_in_scc_pass([PPId | PPIds], FixDir, Module, PassInfo,
|
|
RecSupplierMap, NewRecSupplierMap0, CallInfo0, Result) :-
|
|
% Get the goal info.
|
|
PPId = proc(PredId, ProcId),
|
|
module_info_pred_proc_info(Module, PredId, ProcId, PredInfo, ProcInfo),
|
|
pred_info_context(PredInfo, Context),
|
|
proc_info_goal(ProcInfo, Goal),
|
|
proc_info_vartypes(ProcInfo, VarTypes),
|
|
map__init(EmptyMap),
|
|
PassInfo = pass_info(FunctorInfo, MaxErrors, MaxPaths),
|
|
init_traversal_params(Module, FunctorInfo, PPId, Context, VarTypes,
|
|
EmptyMap, RecSupplierMap, MaxErrors, MaxPaths, Params),
|
|
set__init(PathSet0),
|
|
Info0 = ok(PathSet0, []),
|
|
traverse_goal(Goal, Params, Info0, Info),
|
|
(
|
|
Info = ok(Paths, CanLoop),
|
|
require(unify(CanLoop, []),
|
|
"can_loop detected in pass2 but not pass1"),
|
|
set__to_sorted_list(Paths, PathList),
|
|
upper_bound_active_vars(PathList, ActiveVars),
|
|
map__lookup(RecSupplierMap, PPId, RecSuppliers0),
|
|
proc_info_headvars(ProcInfo, Args),
|
|
bag__init(EmptyBag),
|
|
update_rec_input_suppliers(Args, ActiveVars, FixDir,
|
|
RecSuppliers0, RecSuppliers,
|
|
EmptyBag, RecSuppliers0Bag),
|
|
map__det_insert(NewRecSupplierMap0, PPId, RecSuppliers,
|
|
NewRecSupplierMap1),
|
|
add_call_arcs(PathList, RecSuppliers0Bag,
|
|
CallInfo0, CallInfo1),
|
|
prove_termination_in_scc_pass(PPIds, FixDir, Module,
|
|
PassInfo, RecSupplierMap,
|
|
NewRecSupplierMap1, CallInfo1, Result)
|
|
;
|
|
Info = error(Errors, CanLoop),
|
|
require(unify(CanLoop, []),
|
|
"can_loop detected in pass2 but not pass1"),
|
|
Result = error(Errors)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------
|
|
|
|
:- pred update_rec_input_suppliers(list(var)::in, bag(var)::in,
|
|
fixpoint_dir::in, list(bool)::in, list(bool)::out,
|
|
bag(var)::in, bag(var)::out) is det.
|
|
|
|
update_rec_input_suppliers([], _, _, [], [], RecBag, RecBag).
|
|
update_rec_input_suppliers([_ | _], _, _, [], [], _, _) :-
|
|
error("update_rec_input_suppliers: Unmatched variables").
|
|
update_rec_input_suppliers([], _, _, [_ | _], [], _, _) :-
|
|
error("update_rec_input_suppliers: Unmatched variables").
|
|
update_rec_input_suppliers([Arg | Args], ActiveVars, FixDir,
|
|
[RecInputSupplier0 | RecInputSuppliers0],
|
|
[RecInputSupplier | RecInputSuppliers],
|
|
RecBag0, RecBag) :-
|
|
(
|
|
RecInputSupplier0 = yes,
|
|
bag__insert(RecBag0, Arg, RecBag1)
|
|
;
|
|
RecInputSupplier0 = no,
|
|
RecBag1 = RecBag0
|
|
),
|
|
(
|
|
FixDir = down,
|
|
% This guarantees that the set of rec input suppliers
|
|
% can only decrease.
|
|
( bag__contains(ActiveVars, Arg) ->
|
|
RecInputSupplier = RecInputSupplier0
|
|
;
|
|
RecInputSupplier = no
|
|
)
|
|
;
|
|
FixDir = up,
|
|
% This guarantees that the set of rec input suppliers
|
|
% can only increase.
|
|
( bag__contains(ActiveVars, Arg) ->
|
|
RecInputSupplier = yes
|
|
;
|
|
RecInputSupplier = RecInputSupplier0
|
|
)
|
|
),
|
|
update_rec_input_suppliers(Args, ActiveVars, FixDir,
|
|
RecInputSuppliers0, RecInputSuppliers, RecBag1, RecBag).
|
|
|
|
%-----------------------------------------------------------------------------
|
|
|
|
% This adds the information from a stage 2 traversal to the graph.
|
|
% The graph's nodes are the procedures in the current SCC.
|
|
% The graph's edges represent calls from one procedure in the SCC to another.
|
|
% The number attached to the edge from p to q shows the upper bound
|
|
% on the difference between the size of the recursive input supplier arguments
|
|
% in the call to q and the size of the recursive input supplier arguments
|
|
% in the head of p. If there is no finite upper bound, then we insert the
|
|
% details of the call into the list of "infinite" calls.
|
|
|
|
:- pred add_call_arcs(list(path_info)::in,
|
|
bag(var)::in, call_weight_info::in, call_weight_info::out) is det.
|
|
|
|
add_call_arcs([], _RecInputSuppliers, CallInfo, CallInfo).
|
|
add_call_arcs([Path | Paths], RecInputSuppliers, CallInfo0, CallInfo) :-
|
|
Path = path_info(PPId, CallSite, GammaConst, GammaVars, ActiveVars),
|
|
( CallSite = yes(CallPPIdPrime - ContextPrime) ->
|
|
CallPPId = CallPPIdPrime,
|
|
Context = ContextPrime
|
|
;
|
|
error("no call site in path in stage 2")
|
|
),
|
|
( GammaVars = [] ->
|
|
true
|
|
;
|
|
error("gamma variables in path in stage 2")
|
|
),
|
|
CallInfo0 = InfCalls0 - CallWeights0,
|
|
( bag__is_subbag(ActiveVars, RecInputSuppliers) ->
|
|
( map__search(CallWeights0, PPId, NeighbourMap0) ->
|
|
( map__search(NeighbourMap0, CallPPId, OldEdgeInfo) ->
|
|
OldEdgeInfo = _OldContext - OldWeight,
|
|
( OldWeight >= GammaConst ->
|
|
EdgeInfo = OldEdgeInfo
|
|
;
|
|
EdgeInfo = Context - GammaConst
|
|
),
|
|
map__det_update(NeighbourMap0, CallPPId,
|
|
EdgeInfo, NeighbourMap)
|
|
;
|
|
map__det_insert(NeighbourMap0, CallPPId,
|
|
Context - GammaConst, NeighbourMap)
|
|
),
|
|
map__det_update(CallWeights0, PPId, NeighbourMap,
|
|
CallWeights1)
|
|
;
|
|
map__init(NeighbourMap0),
|
|
map__det_insert(NeighbourMap0, CallPPId,
|
|
Context - GammaConst, NeighbourMap),
|
|
map__det_insert(CallWeights0, PPId, NeighbourMap,
|
|
CallWeights1)
|
|
),
|
|
CallInfo1 = InfCalls0 - CallWeights1
|
|
;
|
|
InfCalls1 = [Context - inf_call(PPId, CallPPId) | InfCalls0],
|
|
CallInfo1 = InfCalls1 - CallWeights0
|
|
),
|
|
add_call_arcs(Paths, RecInputSuppliers, CallInfo1, CallInfo).
|
|
|
|
%-----------------------------------------------------------------------------
|
|
|
|
% We use a simple depth first search to find and return the list
|
|
% of all cycles in the call graph of the SCC where the change in
|
|
% the size of the recursive input supplier arguments of the procedure
|
|
% that serves as the start and end point of the circularity are
|
|
% not guaranteed to decrease.
|
|
%
|
|
% Finding one such cycle is enough for us to conclude that we
|
|
% cannot prove termination of the procedures in the SCC; we collect
|
|
% all cycles because it may be useful to print them out (if not
|
|
% all, then maybe a limited set).
|
|
|
|
:- pred zero_or_positive_weight_cycles(call_weight_graph::in,
|
|
module_info::in, list(term_errors__error)::out) is det.
|
|
|
|
zero_or_positive_weight_cycles(CallWeights, Module, Cycles) :-
|
|
map__keys(CallWeights, PPIds),
|
|
zero_or_positive_weight_cycles_2(PPIds, CallWeights, Module, Cycles).
|
|
|
|
:- pred zero_or_positive_weight_cycles_2(list(pred_proc_id)::in,
|
|
call_weight_graph::in, module_info::in,
|
|
list(term_errors__error)::out) is det.
|
|
|
|
zero_or_positive_weight_cycles_2([], _, _, []).
|
|
zero_or_positive_weight_cycles_2([PPId | PPIds], CallWeights, Module, Cycles) :-
|
|
zero_or_positive_weight_cycles_from(PPId, CallWeights, Module, Cycles1),
|
|
zero_or_positive_weight_cycles_2(PPIds, CallWeights, Module, Cycles2),
|
|
list__append(Cycles1, Cycles2, Cycles).
|
|
|
|
:- pred zero_or_positive_weight_cycles_from(pred_proc_id::in,
|
|
call_weight_graph::in, module_info::in,
|
|
list(term_errors__error)::out) is det.
|
|
|
|
zero_or_positive_weight_cycles_from(PPId, CallWeights, Module, Cycles) :-
|
|
map__lookup(CallWeights, PPId, NeighboursMap),
|
|
map__to_assoc_list(NeighboursMap, NeighboursList),
|
|
PPId = proc(PredId, _ProcId),
|
|
module_info_pred_info(Module, PredId, PredInfo),
|
|
pred_info_context(PredInfo, Context),
|
|
zero_or_positive_weight_cycles_from_neighbours(NeighboursList,
|
|
PPId, Context, 0, [], CallWeights, Cycles).
|
|
|
|
:- pred zero_or_positive_weight_cycles_from_neighbours(assoc_list(pred_proc_id,
|
|
pair(term__context, int))::in, pred_proc_id::in, term__context::in,
|
|
int::in, assoc_list(pred_proc_id, term__context)::in,
|
|
call_weight_graph::in, list(term_errors__error)::out) is det.
|
|
|
|
zero_or_positive_weight_cycles_from_neighbours([], _, _, _, _, _, []).
|
|
zero_or_positive_weight_cycles_from_neighbours([Neighbour | Neighbours],
|
|
LookforPPId, Context, WeightSoFar, VisitedCalls, CallWeights,
|
|
Cycles) :-
|
|
zero_or_positive_weight_cycles_from_neighbour(Neighbour, LookforPPId,
|
|
Context, WeightSoFar, VisitedCalls, CallWeights, Cycles1),
|
|
zero_or_positive_weight_cycles_from_neighbours(Neighbours, LookforPPId,
|
|
Context, WeightSoFar, VisitedCalls, CallWeights, Cycles2),
|
|
list__append(Cycles1, Cycles2, Cycles).
|
|
|
|
:- pred zero_or_positive_weight_cycles_from_neighbour(pair(pred_proc_id,
|
|
pair(term__context, int))::in, pred_proc_id::in, term__context::in,
|
|
int::in, assoc_list(pred_proc_id, term__context)::in,
|
|
call_weight_graph::in, list(term_errors__error)::out) is det.
|
|
|
|
zero_or_positive_weight_cycles_from_neighbour(CurPPId - (Context - EdgeWeight),
|
|
LookforPPId, ProcContext, WeightSoFar0, VisitedCalls,
|
|
CallWeights, Cycles) :-
|
|
WeightSoFar1 is WeightSoFar0 + EdgeWeight,
|
|
(
|
|
CurPPId = LookforPPId
|
|
->
|
|
% We have a cycle on the looked for ppid.
|
|
( WeightSoFar1 >= 0 ->
|
|
FinalVisitedCalls = [CurPPId - Context | VisitedCalls],
|
|
list__reverse(FinalVisitedCalls, RevFinalVisitedCalls),
|
|
Cycles = [ProcContext -
|
|
cycle(LookforPPId, RevFinalVisitedCalls)]
|
|
;
|
|
Cycles = []
|
|
)
|
|
;
|
|
assoc_list__keys(VisitedCalls, VisitedPPIds),
|
|
list__member(CurPPId, VisitedPPIds)
|
|
->
|
|
% We have a cycle, but not on the looked for ppid.
|
|
% We ignore it here; it will be picked up when we process
|
|
% that ppid.
|
|
Cycles = []
|
|
;
|
|
% No cycle; try all possible edges from this node.
|
|
NewVisitedCalls = [CurPPId - Context | VisitedCalls],
|
|
map__lookup(CallWeights, CurPPId, NeighboursMap),
|
|
map__to_assoc_list(NeighboursMap, NeighboursList),
|
|
zero_or_positive_weight_cycles_from_neighbours(NeighboursList,
|
|
LookforPPId, ProcContext, WeightSoFar1,
|
|
NewVisitedCalls, CallWeights, Cycles)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------
|