Termination Analysis part 1.2
Christopher Rodd SPEIRS
crs at students.cs.mu.oz.au
Sat Oct 4 13:30:42 AEST 1997
Part 1.2. This contains term_pass2, term_util and term_errors.
%-----------------------------------------------------------------------------
%
% Copyright (C) 1997 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: crs.
%
% This file contains the actual termination analysis. This file processes
% through each procedure, and sets the terminates property of each one.
%
% Termination analysis:
% This goes through each of the SCC's in turn, analysing them to determine
% which predicates terminate.
%
%-----------------------------------------------------------------------------
:- module term_pass2.
:- interface.
:- import_module io, hlds_module, term_util.
% Analyse each predicate in the order given by
% hlds_dependency_info_get_dependency_ordering to determine whether or not
% it terminates. Put the result of the analysis into the termination
% field of the proc_info structure..
:- pred termination(module_info, functor_info, module_info,
io__state, io__state).
:- mode termination(in, in, out, di, uo) is det.
:- implementation.
:- import_module bag, hlds_pred, std_util, int, list, relation, require.
:- import_module set, hlds_goal, term_util, term_errors, bool.
:- import_module globals, options, map, term, type_util.
% Used in termination_goal to keep track of the relative sizes of variables
% between the head of a predicate and any recursive calls.
:- type termination_call
---> tuple(pred_proc_id, % The called procedure
int, % The relative size of the active
% variables, in comparison with the
% size of the variables in the
% recursive call.
bag(var), % The bag of active variables.
maybe(var), % Only for single argrgument analysis.
% This stores which head variable
% is being traced by this tuple.
term__context). % Where the call occured.
termination(Module0, FunctorInfo, Module) -->
{ module_info_dependency_info(Module0, DependencyInfo) },
{ hlds_dependency_info_get_dependency_ordering(DependencyInfo,
DependencyOrdering) },
termination_module(Module0, DependencyOrdering, FunctorInfo, Module).
:- pred termination_module(module_info, dependency_ordering, functor_info,
module_info, io__state, io__state).
:- mode termination_module(in, in, in, out, di, uo) is det.
termination_module(Module, [], _FunctorInfo, Module) --> [].
termination_module(Module0, [SCC | SCCs], FunctorInfo, Module) -->
termination_module_check_terminates(SCC, SCC, Module0, Module1, Succ),
( { Succ = yes } ->
% Need to run termination checking on this SCC.
% Initialise the relation
{ relation__init(Relation0) },
% Add each procedure to the relation.
{ add_pred_procs_to_relation(SCC, Relation0, Relation1) },
% Analyse the current SCC.
{ init_used_args(SCC, Module1, InitUsedArgs) },
{ call_termination_scc(SCC, FunctorInfo, Module1, Res,
InitUsedArgs, Relation1, Relation2) },
(
{ Res = ok },
% Check that the relation returned is acyclic
% and set the termination property accordingly.
( { relation__tsort(Relation2, _) } ->
% All the procedures in this SCC terminate,
% set all termination properties to yes.
{ set_pred_proc_ids_terminates(SCC,
yes, no, Module1, Module3) }
;
( { SCC = [ proc(PredId, _) | _ ] } ->
{ module_info_pred_info(Module1,
PredId, PredInfo) },
{ pred_info_context(PredInfo,
Context) }
;
{ error("Unexpected empty list in term_pass2__termination_module/6")}
),
% Try doing single argument analysis.
termination_scc_single_args(SCC, FunctorInfo,
Context - not_dag, InitUsedArgs,
Module1, Module3)
)
;
{ Res = error(Error) },
% Normal analysis failed, try doing single argument
% analysis.
termination_scc_single_args(SCC, FunctorInfo,
Error, InitUsedArgs, Module1, Module3)
)
;
% All the termination properties are already set.
{ Module3 = Module1 }
),
termination_module(Module3, SCCs, FunctorInfo, Module).
% If this predicate returns Succ = yes, then termination analysis needs to be
% run on this SCC. If it returns Succ = no, then the termination
% properties of this SCC have already been set.
% XXX this can be improved. If ANY Terminates in the SCC is set to dont_know,
% then the whole SCC can be set to dont_know
:- pred termination_module_check_terminates(list(pred_proc_id),
list(pred_proc_id), module_info, module_info, bool,
io__state, io__state).
:- mode termination_module_check_terminates(in, in, in, out, out,
di, uo) is det.
termination_module_check_terminates([], _SCC, Module, Module, no) --> [].
termination_module_check_terminates([ PPId | PPIds ], SCC, Module0,
Module, Succ) -->
{ PPId = proc(PredId, ProcId) },
{ module_info_pred_proc_info(Module0, PredId, ProcId, _, ProcInfo) },
{ proc_info_termination(ProcInfo, Term) },
{ Term = term(_Const, Terminates, _UsedArgs, MaybeError) },
(
{ Terminates = not_set },
{ Succ = yes },
{ Module = Module0 }
;
{ Terminates = yes },
termination_module_check_terminates(PPIds, SCC, Module0,
Module, Succ)
;
{ Terminates = dont_know},
{ Succ = no },
( { MaybeError = yes(Error) } ->
do_ppid_check_terminates(SCC, Error, Module0, Module)
;
{ error("term_pass2.m: unexpected value in termination_module_check_terminates/6") }
)
).
% This predicate runs termination_scc until a fixed point for UsedArgs
% is reached.
:- pred call_termination_scc(list(pred_proc_id), functor_info, module_info,
term_util__result(term_errors__error), map(pred_proc_id, set(var)),
relation(pred_proc_id), relation(pred_proc_id)).
:- mode call_termination_scc(in, in, in, out, in, in, out) is det.
call_termination_scc(SCC, FunctorInfo, Module, Result, UsedArgs0,
Relation0, Relation) :-
termination_scc(SCC, FunctorInfo, Module, Res1, UsedArgs0, UsedArgs1,
Relation0, Relation1),
(
% If some other error prevented the analysis from proving
% termination, then finding a fixed point will not assist
% in proving termination, so we may as well stop here.
( Res1 = error(_ - not_subset(_, _, _))
; Res1 = error(_ - positive_value(_, _))
),
UsedArgs1 \= UsedArgs0 % If these are equal, then we are at
% a fixed point, so further
% analysis will not get any better
% results.
->
% We can use the new Used Args values, and try again.
call_termination_scc(SCC, FunctorInfo, Module, Result,
UsedArgs1, Relation0, Relation)
;
Relation = Relation1,
Result = Res1
).
% This initialises the used arguments to be the set of input variables.
:- pred init_used_args(list(pred_proc_id), module_info,
map(pred_proc_id, set(var))).
:- mode init_used_args(in, in, out) is det.
init_used_args([], _, InitMap) :-
map__init(InitMap).
init_used_args([PPId | PPIds], Module, UsedArgs) :-
init_used_args(PPIds, Module, UsedArgs0),
PPId = proc(PredId, ProcId),
module_info_pred_proc_info(Module, PredId, ProcId, _, ProcInfo),
proc_info_headvars(ProcInfo, Args),
proc_info_argmodes(ProcInfo, ArgModes),
partition_call_args(Module, ArgModes, Args, InArgs, _OutVars),
set__list_to_set(InArgs, ArgSet),
map__det_insert(UsedArgs0, PPId, ArgSet, UsedArgs).
% Given a list of all the arguments of a predicate, find out which
% arguments are unused, so that they can be removed from the used arguments
% list for that predicate.
:- pred termination_scc_calc_unused_args(list(var), list(termination_call),
list(var)).
:- mode termination_scc_calc_unused_args(in, in, out) is det.
termination_scc_calc_unused_args(Vars, [], Vars).
termination_scc_calc_unused_args(Vars0, [X | Xs], Vars) :-
X = tuple(_, _, VarBag, _, _),
termination_scc_calc_unused_args_2(Vars0, VarBag, Vars1),
termination_scc_calc_unused_args(Vars1, Xs, Vars).
:- pred termination_scc_calc_unused_args_2(list(var), bag(var), list(var)).
:- mode termination_scc_calc_unused_args_2(in, in, out) is det.
termination_scc_calc_unused_args_2([], _, []).
termination_scc_calc_unused_args_2([ X | Xs ], Vars, Ys) :-
( bag__contains(Vars, X) ->
% If the variable is in the bag, then it is used.
% Therefore, it should not be in the list of unused vars
termination_scc_calc_unused_args_2(Xs, Vars, Ys)
;
termination_scc_calc_unused_args_2(Xs, Vars, Ys0),
Ys = [X | Ys0]
).
% Process a whole SCC, to determine the termination property of each
% procedure in that SCC.
:- pred termination_scc(list(pred_proc_id), functor_info, module_info,
term_util__result(term_errors__error), map(pred_proc_id, set(var)),
map(pred_proc_id, set(var)), relation(pred_proc_id),
relation(pred_proc_id)).
:- mode termination_scc(in, in, in, out, in, out, in, out) is det.
termination_scc([], _, _Module, ok, UsedArgs, UsedArgs, Relation, Relation).
termination_scc([ PPId | PPIds ], FunctorInfo, Module, Result,
UsedArgs0, UsedArgs, Relation0, Relation) :-
% Get the goal info.
PPId = proc(PredId, ProcId),
module_info_pred_proc_info(Module, PredId, ProcId, _PredInfo, ProcInfo),
proc_info_termination(ProcInfo, Termination),
( Termination = term(_Const, dont_know, _, MaybeError) ->
% If the termination property is set to dont_know then
% MaybeError should contain an error.
( MaybeError = yes(Error) ->
Result = error(Error)
;
error("term_pass2.m: Unexpected value in term_pass2:termination_scc")
),
UsedArgs = UsedArgs0,
Relation = Relation0
;
proc_info_goal(ProcInfo, Goal),
proc_info_vartypes(ProcInfo, VarTypes),
Goal = GoalExpr - GoalInfo,
% Analyse goal info. This returns a list of ppid - size
% pairs.
UnifyInfo = VarTypes - FunctorInfo,
CallInfo = call_info(PPId, UsedArgs0, no),
termination_goal(GoalExpr, GoalInfo, Module, UnifyInfo,
CallInfo, Res0, [], Out),
proc_info_argmodes(ProcInfo, ArgModes),
proc_info_headvars(ProcInfo, Args),
partition_call_args(Module, ArgModes, Args, InVars, _OutVars),
bag__from_list(InVars, InVarsBag),
( Res0 = ok ->
termination_scc_calc_unused_args(Args, Out, UnUsedArgs),
map__lookup(UsedArgs0, PPId, OldUsedArgs),
set__delete_list(OldUsedArgs, UnUsedArgs, NewUsedArgs),
termination_add_arcs_to_relation(PPId, Out,
InVarsBag, Res1, Relation0, Relation1),
( Res1 = ok ->
termination_scc(PPIds, FunctorInfo, Module,
Result, UsedArgs0, UsedArgs1,
Relation1, Relation)
;
% We failed because of positive value, or
% because of not_subset. Keep analysing,
% to discover the used variables, of the other
% procedures.
termination_scc(PPIds, FunctorInfo, Module,
Res2, UsedArgs0, UsedArgs1,
Relation0, _Relation),
Relation = Relation0,
% We know that Res1 is not_subset or
% positive_value. If Res2 is ok, then we
% need to return Res1 (so that the calling
% predicate knows that the analysis
% failed). If Res2 is an error, then it
% needs to be returned. If Res2 is also
% not_subset or positive_value, then fine,
% the analysis will be re-run. If it is
% some other error, then re-running the
% analysis will not gain anything.
( Res2 = ok ->
Result = Res1
;
Result = Res2
)
),
% Add the used vars from the current pass to the
% UsedArgs map.
map__det_update(UsedArgs1, PPId, NewUsedArgs, UsedArgs)
;
UsedArgs = UsedArgs0,
Result = Res0,
Relation = Relation0
)
).
% This runs single argument analysis on the goal. This is only run if
% normal termination analysis failed.
:- pred termination_scc_single_args(list(pred_proc_id), functor_info,
term_errors__error, map(pred_proc_id, set(var)),
module_info, module_info, io__state, io__state).
:- mode termination_scc_single_args(in, in, in, in, in, out, di, uo) is det.
termination_scc_single_args([], _, _, _, Module, Module) -->
{ error("term_pass2__termination_scc_single_args: there should be\nat least 1 predicate in a SCC\n") }.
termination_scc_single_args([PPId | Rest], FunctorInfo, Error, UsedArgs,
Module0, Module) -->
globals__io_lookup_bool_option(termination_single_args, SingleArgs),
{ SCC = [PPId | Rest] },
{ PPId = proc(PredId, ProcId) },
{ set__init(InitSet) },
(
{ SingleArgs = yes },
{ Rest = [] },
{ Error \= _ - imported_pred }
% What other errors would prevent single argument analysis
% from succeeding?
->
% Can do single argument analysis.
{ module_info_pred_proc_info(Module0, PredId, ProcId,
_PredInfo, ProcInfo) },
{ proc_info_goal(ProcInfo, Goal) },
{ proc_info_vartypes(ProcInfo, VarTypes) },
{ Goal = GoalExpr - GoalInfo },
{ UnifyInfo = VarTypes - FunctorInfo },
{ CallInfo = call_info(PPId, UsedArgs, yes) },
{ termination_goal(GoalExpr, GoalInfo, Module0,
UnifyInfo, CallInfo, Res0, [], Out) },
( { Res0 = error(Error2) } ->
% The context of single_arg_failed needs to be the
% same as the context of the Normal analysis error
% message.
{ Error = Context - _ },
{ Error3 = Context - single_arg_failed(Error, Error2) },
do_ppid_check_terminates(SCC, Error3,
Module0, Module)
; { termination_scc_single_2(Out, InitSet, InitSet) } ->
% Single argument analysis succeded.
{ set_pred_proc_ids_terminates(SCC, yes, yes(Error),
Module0, Module) }
;
% Single argument analysis failed to prove
% termination.
{ Error = Context - _ },
{ Error2 = Context - single_arg_failed(Error) },
do_ppid_check_terminates(SCC, Error2,
Module0, Module)
)
;
% Cant do single argument analysis.
do_ppid_check_terminates(SCC, Error,
Module0, Module)
).
:- pred termination_scc_single_2(list(termination_call), set(var), set(var)).
:- mode termination_scc_single_2(in, in, in) is semidet.
termination_scc_single_2([], NegSet, NonNegSet) :-
set__difference(NegSet, NonNegSet, DiffSet),
% Check that there is at least one head variable that is always
% strictly decreasing in size between the head of the procedure and
% the recursive call.
set__remove_least(DiffSet, _, _).
termination_scc_single_2([Off | Offs], NegSet0, NonNegSet0) :-
Off = tuple(_, Int, VarBag0, MaybeVar, _Context),
(
MaybeVar = no,
error("termination.m: Maybevar should be yes in single argument analysis\n")
;
MaybeVar = yes(HeadVar),
(
Int < 0,
% Check that the variable that was recursed on did
% not change positions between the head and
% recursive call. I am not sure if the first call
% to bag__remove is actually required to succeed
bag__remove(VarBag0, HeadVar, VarBag1),
\+ bag__remove_smallest(VarBag1, _, _)
->
set__insert(NegSet0, HeadVar, NegSet),
termination_scc_single_2(Offs, NegSet, NonNegSet0)
;
set__insert(NonNegSet0, HeadVar, NonNegSet),
termination_scc_single_2(Offs, NegSet0, NonNegSet)
)
).
% This adds the information from termination_goal to the relation.
% The relation is between the procedures in the current SCC. An arc
% shows that one procedure calls another with the size of the variables
% unchanged between the head and the (possibly indirect) recursive call.
% Any loops in the relation show possible non-termination. This predicate
% also checks that the calculated input variables are subsets of the actual
% input variables
:- pred termination_add_arcs_to_relation(pred_proc_id, list(termination_call),
bag(var), term_util__result(term_errors__error),
relation(pred_proc_id), relation(pred_proc_id)).
:- mode termination_add_arcs_to_relation(in, in, in, out, in, out) is det.
termination_add_arcs_to_relation(_PPid, [], _Vars, ok, Relation, Relation).
termination_add_arcs_to_relation(PPId, [X | Xs], Vars, Result, Relation0,
Relation) :-
X = tuple(CalledPPId, Value, Bag, _Var, Context),
( bag__is_subbag(Bag, Vars) ->
compare(Res, Value, 0),
(
Res = (>),
Result = error(Context -
positive_value(PPId, CalledPPId)),
Relation = Relation0
;
Res = (=),
% Add the arc to the relation.
relation__lookup_element(Relation0, PPId, Key),
relation__lookup_element(Relation0, CalledPPId,
CalledKey),
relation__add(Relation0, Key, CalledKey, Relation1),
termination_add_arcs_to_relation(PPId, Xs, Vars,
Result, Relation1, Relation)
;
Res = (<),
termination_add_arcs_to_relation(PPId, Xs, Vars,
Result, Relation0, Relation)
)
;
Result = error(Context - not_subset(PPId, Bag, Vars)),
Relation = Relation0
).
:- pred add_pred_procs_to_relation(list(pred_proc_id), relation(pred_proc_id),
relation(pred_proc_id)).
:- mode add_pred_procs_to_relation(in, in, out) is det.
add_pred_procs_to_relation([], Relation, Relation).
add_pred_procs_to_relation([PPId | PPIds], Relation0, Relation) :-
relation__add_element(Relation0, PPId, _, Relation1),
add_pred_procs_to_relation(PPIds, Relation1, Relation).
%-----------------------------------------------------------------------------%
% termination_goal is the main section of the analysis for pass 2. It
% processes the goal of a single procedure, and returns a list of
% termination_call structures. Each termination_call structure represents
% a single (mutually or directly) recursive call, and also tracks the
% relative size of the input variables in the recursive call, in comparison
% to the variables in the head.
:- type call_info --->
call_info(
% The pred_proc_id of the predicate that we are
% currently processing.
pred_proc_id,
% A map from each procedure in the current SCC to
% the used-vars for that procedure. It is used to
% find whether or not a call is mutually recursive,
% and to find the used-variables of the predicate
% that is being called.
map(pred_proc_id, set(var)),
bool % are we doing single argument analysis?
).
:- pred termination_goal(hlds_goal_expr, hlds_goal_info, module_info,
unify_info, call_info, term_util__result(term_errors__error),
list(termination_call), list(termination_call)).
:- mode termination_goal(in, in, in, in, in, out, in, out) is det.
termination_goal(conj(Goals),
_GoalInfo, Module, UnifyInfo, CallInfo, Res, Out0, Out) :-
termination_conj(Goals, Module, UnifyInfo, CallInfo, Res,
Out0, Out).
% This processes calls when doing normal termination analysis (as opposed
% to single argument analysis).
termination_goal(call(CallPredId, CallProcId, Args, _IsBuiltin, _, _),
GoalInfo, Module, _UnifyInfo, call_info(PPId, UsedArgsMap, no),
Res, Out0, Out) :-
PPId = proc(PredId, ProcId),
CallPPId = proc(CallPredId, CallProcId),
module_info_pred_proc_info(Module, PredId, ProcId, _, ProcInfo),
module_info_pred_proc_info(Module, CallPredId, CallProcId, _,
CallProcInfo),
proc_info_vartypes(ProcInfo, VarType),
proc_info_argmodes(CallProcInfo, CallArgModes),
proc_info_termination(CallProcInfo, CallTermination),
CallTermination = term(CallTermConst, CallTerminates, CallUsedArgs, _),
goal_info_get_context(GoalInfo, Context),
partition_call_args(Module, CallArgModes, Args, InVars, OutVars),
bag__from_list(InVars, InVarBag0),
bag__from_list(OutVars, OutVarBag),
( CallUsedArgs = yes(UsedVars) ->
remove_unused_args(InVarBag0, Args, UsedVars,
InVarBag1)
;
InVarBag1 = InVarBag0
),
% Step 1 - modify Out0
(
CallTermConst = set(Int),
termination_goal_modify_out(InVarBag1, OutVarBag, Int,
Out0, Out1),
Res0 = ok
;
CallTermConst = not_set,
error("Unexpected Termination Constant in termination.m"),
Res0 = ok,
Out1 = Out0
;
CallTermConst = inf(_),
( termination_goal_check_intersect(Out0, OutVarBag) ->
% There is no intersection, so just continue
Res0 = ok
;
Res0 = error(Context -
inf_termination_const(PPId, CallPPId))
),
Out1 = Out0
),
% Step 2 - do we add another arc?
( CallTerminates = dont_know ->
Res = error(Context - dont_know_proc_called(PPId, CallPPId)),
Out = Out0
; \+ check_horder_args(Args, VarType) ->
Res = error(Context - horder_args(PPId, CallPPId)),
Out = Out0
;
( map__search(UsedArgsMap, CallPPId, RecursiveUsedArgs) ->
% The called procedure is in the map, so the call is
% recursive - add it to Out.
proc_info_headvars(CallProcInfo, HeadVars),
termination_call_2(Args, HeadVars,
RecursiveUsedArgs, Bag),
NewOutElem = tuple(CallPPId, 0, Bag, no, Context),
Out = [ NewOutElem | Out1 ]
;
% The call is not recursive
Out = Out1
),
Res = Res0
).
termination_goal(call(CallPredId, CallProcId, Args, _IsBuiltin, _, _),
GoalInfo, Module, _UnifyInfo,
call_info(PPId, _UsedArgsMap, yes), Res, Out0, Out) :-
PPId = proc(PredId, ProcId),
CallPPId = proc(CallPredId, CallProcId),
module_info_pred_proc_info(Module, PredId, ProcId, _, ProcInfo),
module_info_pred_proc_info(Module, CallPredId, CallProcId, _,
CallProcInfo),
proc_info_vartypes(ProcInfo, VarType),
proc_info_argmodes(CallProcInfo, CallArgModes),
proc_info_headvars(CallProcInfo, HeadVars),
proc_info_termination(CallProcInfo, CallTermination),
CallTermination = term(_, CallTerminates, _CallUsedArgs, _),
goal_info_get_context(GoalInfo, Context),
partition_call_args(Module, CallArgModes, Args, InVars, OutVars),
partition_call_args(Module, CallArgModes, HeadVars, InHeadVars, _),
bag__from_list(OutVars, OutVarBag),
% Step 1 - modify Out0
( termination_goal_check_intersect(Out0, OutVarBag) ->
% There is no intersection, so just continue.
Res0 = ok,
Out1 = Out0
;
% This analysis could be much better, but it will do for
% now.
Res0 = error(Context - call_in_single_arg(CallPPId)),
Out1 = Out0
),
% Step 2 - do we add another arc?
( CallTerminates = dont_know ->
Res = error(Context - dont_know_proc_called(PPId, CallPPId)),
Out = Out0
; \+ check_horder_args(Args, VarType) ->
Res = error(Context - horder_args(PPId, CallPPId)),
Out = Out0
;
( CallPPId = PPId ->
% The call is recursive - add it to Out
termination_call(InVars, InHeadVars, PPId,
Context, Out1, Out)
;
% The call is not recursive
Out = Out1
),
Res = Res0
).
termination_goal(higher_order_call(_, _, _, _, _, _),
GoalInfo, _Module, _UnifyInfo, _CallInfo, Res, Out0, Out) :-
goal_info_get_context(GoalInfo, Context),
Res = error(Context - horder_call),
Out = Out0.
termination_goal(switch(_Var, _CanFail, Cases, _StoreMap),
_GoalInfo, Module, UnifyInfo, CallInfo, Res, Out0, Out) :-
termination_switch(Cases, Module, UnifyInfo, CallInfo,
Res, Out0, Out).
termination_goal(unify(_Var, _RHS, _UniMode, Unification, _Context),
_GInfo, Module, UnifyInfo, _CallInfo, ok, Out0, Out) :-
(
Unification = construct(OutVar, ConsId, Args0, Modes0),
UnifyInfo = VarTypes - FunctorInfo,
map__lookup(VarTypes, OutVar, Type),
% length(Args) is not necessarily equal to length(Modes)
% for higher order constructions.
( type_is_higher_order(Type, _, _) ->
Out = Out0
;
( type_to_type_id(Type, TypeIdPrime, _) ->
TypeId = TypeIdPrime
;
error("variable type in termination_goal")
),
functor_norm(FunctorInfo, TypeId, ConsId, Module,
FunctorNorm, Args0, Args, Modes0, Modes),
split_unification_vars(Args, Modes, Module, InVarBag,
OutVarBag0),
bag__insert(OutVarBag0, OutVar, OutVarBag),
termination_goal_modify_out(InVarBag, OutVarBag,
FunctorNorm, Out0, Out)
)
;
Unification = deconstruct(InVar, ConsId, Args0, Modes0, _),
UnifyInfo = VarTypes - FunctorInfo,
map__lookup(VarTypes, InVar, Type),
( type_to_type_id(Type, TypeIdPrime, _) ->
TypeId = TypeIdPrime
;
error("variable type in termination analysis")
),
functor_norm(FunctorInfo, TypeId, ConsId, Module,
FunctorNorm, Args0, Args, Modes0, Modes),
split_unification_vars(Args, Modes, Module, InVarBag0,
OutVarBag),
bag__insert(InVarBag0, InVar, InVarBag),
termination_goal_modify_out(InVarBag, OutVarBag,
(- FunctorNorm), Out0, Out)
;
Unification = assign(OutVar, InVar),
bag__init(InitBag),
bag__insert(InitBag, InVar, InVarBag),
bag__insert(InitBag, OutVar, OutVarBag),
termination_goal_modify_out(InVarBag, OutVarBag, 0, Out0, Out)
;
Unification = simple_test(_InVar1, _InVar2),
Out = Out0
;
Unification = complicated_unify(_, _),
error("Unexpected complicated_unify in term_pass2.m")
).
termination_goal(disj(Goals, _StoreMap),
_GoalInfo, Module, UnifyInfo, CallInfo, Res, Out0, Out) :-
termination_disj(Goals, Module, UnifyInfo, CallInfo, Res, Out0, Out).
termination_goal(not(GoalExpr - GoalInfo),
_GoalInfo, Module, UnifyInfo, CallInfo, Res, Out0, Out) :-
termination_goal(GoalExpr, GoalInfo, Module, UnifyInfo, CallInfo,
Res, Out0, Out).
termination_goal(some(_Vars, GoalExpr - GoalInfo),
_GoalInfo, Module, UnifyInfo, CallInfo, Res, Out0, Out) :-
termination_goal(GoalExpr, GoalInfo, Module, UnifyInfo, CallInfo,
Res, Out0, Out).
termination_goal(if_then_else(_Vars, CondGoal, ThenGoal, ElseGoal, _),
_GoalInfo, Module, UnifyInfo, CallInfo, Res, Out0, Out) :-
CondGoal = CondExpr - CondGoalInfo,
ThenGoal = ThenExpr - ThenGoalInfo,
ElseGoal = ElseExpr - ElseGoalInfo,
termination_goal(ThenExpr, ThenGoalInfo, Module, UnifyInfo, CallInfo,
ThenRes, Out0, ThenOut),
termination_goal(ElseExpr, ElseGoalInfo, Module, UnifyInfo, CallInfo,
ElseRes, Out0, ElseOut),
( ThenRes = error(_) ->
Res = ThenRes,
Out = ThenOut
; ElseRes = error(_) ->
Res = ElseRes,
Out = ElseOut
;
% They both succeded - join the outs
list__append(ThenOut, ElseOut, Out1),
termination_goal(CondExpr, CondGoalInfo, Module,
UnifyInfo, CallInfo, Res, Out1, Out)
).
termination_goal(pragma_c_code(_, _, CallPredId, CallProcId, Args, _, _, _),
GoalInfo, Module, _UnifyInfo, _CallInfo, Res, Out, Out) :-
module_info_pred_proc_info(Module, CallPredId, CallProcId, _,
CallProcInfo),
proc_info_argmodes(CallProcInfo, CallArgModes),
partition_call_args(Module, CallArgModes, Args, _InVars, OutVars),
bag__from_list(OutVars, OutVarBag),
( termination_goal_check_intersect(Out, OutVarBag) ->
% c_code has no important output variables, so we
% dont need an error.
Res = ok
;
goal_info_get_context(GoalInfo, Context),
Res = error(Context - pragma_c_code)
).
%-----------------------------------------------------------------------------%
% These following predicates all support termination_goal.
:- pred termination_conj(list(hlds_goal), module_info,
unify_info, call_info, term_util__result(term_errors__error),
list(termination_call), list(termination_call)).
:- mode termination_conj(in, in, in, in, out, in, out) is det.
termination_conj([] , _Module, _UnifyInfo, _CallInfo, ok, Out, Out).
termination_conj([ Goal | Goals ], Module, UnifyInfo, CallInfo,
Res, Out0, Out) :-
Goal = GoalExpr - GoalInfo,
termination_conj(Goals, Module, UnifyInfo, CallInfo,
Res0, Out0, Out1),
( Res0 = ok ->
termination_goal(GoalExpr, GoalInfo, Module,
UnifyInfo, CallInfo, Res, Out1, Out)
;
Res = Res0,
Out = Out1
).
% Used by single argument analysis to make a seperate Out for each input
% variable in a recursive call.
:- pred termination_call(list(var), list(var), pred_proc_id,
term__context, list(termination_call), list(termination_call)).
:- mode termination_call(in, in, in, in, in, out) is det.
termination_call([], [], _, _, Out, Out).
termination_call([_|_], [], _, _, Out, Out) :-
error("term_pass2__termination_call: Unmatched variables\n").
termination_call([], [_|_], _, _, Out, Out) :-
error("term_pass2:termination_call: Unmatched variables\n").
termination_call([ Var | Vars ], [ HeadVar | HeadVars ], PPId,
Context, Outs0, Outs) :-
bag__init(Bag0),
bag__insert(Bag0, Var, Bag),
Out = tuple(PPId, 0, Bag, yes(HeadVar), Context),
termination_call(Vars, HeadVars, PPId, Context,
[Out | Outs0], Outs).
% Used to set the bag of input variables for a recursive call, according to
% the set of used arguments.
:- pred termination_call_2(list(var), list(var), set(var), bag(var)).
:- mode termination_call_2(in, in, in, out) is det.
termination_call_2([], [], _, Out) :-
bag__init(Out).
termination_call_2([_|_], [], _, _Out) :-
error("Unmatched vars in termination_call_2\n").
termination_call_2([], [_|_], _, _Out) :-
error("Unmatched vars in termination_call_2\n").
termination_call_2([Var | Vars], [HeadVar | HeadVars],
UsedSet, OutBag) :-
termination_call_2(Vars, HeadVars, UsedSet, OutBag0),
( set__member(HeadVar, UsedSet) ->
bag__insert(OutBag0, Var, OutBag)
;
OutBag = OutBag0
).
:- pred termination_switch(list(case), module_info,
unify_info, call_info, term_util__result(term_errors__error),
list(termination_call), list(termination_call)).
:- mode termination_switch(in, in, in, in, out, in, out) is det.
termination_switch([] , _Module, _UnifyInfo, _CallInfo, ok, Out, Out) :-
error("term_pass2:termination_switch: unexpected empty switch\n").
termination_switch([ Case | Cases ], Module, UnifyInfo,
CallInfo, Res, Out0, Out):-
Case = case(_, Goal),
Goal = GoalExpr - GoalInfo,
( Cases = [] ->
Res1 = ok,
Out1 = Out0
;
termination_switch(Cases, Module, UnifyInfo, CallInfo,
Res1, Out0, Out1)
),
( Res1 = ok ->
termination_goal(GoalExpr, GoalInfo,
Module, UnifyInfo, CallInfo, Res, Out0, Out2),
list__append(Out1, Out2, Out)
;
Res = Res1,
Out = Out1
).
:- pred termination_disj(list(hlds_goal), module_info,
unify_info, call_info, term_util__result(term_errors__error),
list(termination_call), list(termination_call)).
:- mode termination_disj(in, in, in, in, out, in, out) is det.
termination_disj([] , _Module, _UnifyInfo, _CallInfo, ok, Out, Out):-
( Out = [] ->
true
;
error("term_pass2:termination_disj: Unexpected value after disjunction\n")
).
termination_disj([ Goal | Goals ], Module, UnifyInfo,
CallInfo, Res, Out0, Out) :-
Goal = GoalExpr - GoalInfo,
( Goals = [] ->
Res1 = ok,
Out1 = Out0
;
termination_disj(Goals, Module, UnifyInfo, CallInfo,
Res1, Out0, Out1)
),
( Res1 = ok ->
termination_goal(GoalExpr, GoalInfo, Module,
UnifyInfo, CallInfo, Res, Out0, Out2),
list__append(Out1, Out2, Out)
;
Res = Res1,
Out = Out1
).
:- pred termination_goal_check_intersect(list(termination_call), bag(var)).
:- mode termination_goal_check_intersect(in, in) is semidet.
% termination_goal_check_intersect succeeds if there is no intersection
% between any one of the Outs and the OutVarBag.
termination_goal_check_intersect([], _).
termination_goal_check_intersect([ Out | Outs ], OutVarBag) :-
Out = tuple(_PPId, _Const, OutBag, _Var, _Context),
\+ bag__intersect(OutBag, OutVarBag),
termination_goal_check_intersect(Outs, OutVarBag).
:- pred termination_goal_modify_out(bag(var), bag(var), int,
list(termination_call), list(termination_call)).
:- mode termination_goal_modify_out(in, in, in, in, out) is det.
termination_goal_modify_out(_, _, _, [], []).
termination_goal_modify_out(InVars, OutVars, Off,
[Out0 | Out0s], [Out | Outs]):-
Out0 = tuple(PPId, Int0, Vars0, Var, Context),
( bag__intersect(OutVars, Vars0) ->
% There is an intersection.
bag__subtract(Vars0, OutVars, Vars1),
bag__union(InVars, Vars1, Vars),
Int = Int0 + Off,
Out = tuple(PPId, Int, Vars, Var, Context)
;
% There is not an intersection.
Out = Out0
),
termination_goal_modify_out(InVars, OutVars, Off, Out0s, Outs).
%-----------------------------------------------------------------------------
%
% Copyright (C) 1997 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_util.m
% Main author: crs.
%
% This file contains utilities that are used by termination analysis.
%
%-----------------------------------------------------------------------------
:- module term_util.
:- interface.
:- import_module list, bool, bag, int, hlds_module, hlds_pred, hlds_data.
:- import_module term_errors, io, hlds_goal, term, prog_data, map.
% term(TermConstant, Terminates, UsedArgs, MaybeError)
% TermConstant See description below
% Terminates See description below
% UsedArgs This list of bool, when set, has a 1:1
% correspondance with the arguments of the
% procedure. It stores whether the argument
% is used in producing the output
% arguments.
% MaybeError If the analysis fails to prove termination
% of a procedure, then this value indicates
% the reason that the analysis failed.
:- type termination
---> term(term_util__constant, terminates, maybe(list(bool)),
maybe(term_errors__error)).
% term_util__constant defines the level by which a procedures arguments
% grow or shrink. It is used to determine the termination properties of a
% predicate. The termination constant defines the relative sizes of input
% and output arguments in the following equation:
% | input arguments | + constant >= | output arguments |
% where | | represents a semilinear norm.
:- type term_util__constant
---> inf(term_errors__error)
% The smallest constant that the analysis
% could prove was valid is infinity.
% Whenever a constant is set to infinity,
% an error is associated with it which
% states why the analysis failed to find a
% smaller constant.
; not_set % The termination constant has not been
% set yet. After
% term_pass1:proc_inequalities has been
% run, the constant should be set for all
% the procedures in the module. Failure to
% set the constant would indicate a
% programming error.
; set(int). % The termination constant has been
% set to int.
:- type terminates
---> dont_know % The analysis could not prove that the
% procedure terminates.
; not_set % Each procedure has its `terminates' set
% set to not_set initially.
% The termination analysis uses the fact
% that if a procedure is called whose
% terminates is not_set, then the call is
% mutually recursive. (If the call is not
% mutually recursive, then the analysis
% will have already processed the
% procedure, and would have set the
% constant). term_pass2:termination should
% set the terminates value of all
% procedures. Failure to do so indicates a
% software error.
; yes. % This procedure terminates for all
% possible inputs.
% The functor algorithm defines how a weight is assigned to each functor.
:- type functor_algorithm
---> simple % simple assigns all non-constant
% functors a norm of 1.
; total % All functors have a norm = arity of functor
% Use the weight table to find the
% weight of any constructor.
; use_map(weight_table)
% Use the weight table, and use its bool list to
% determine which arguments of any functor are to
% be considered when calculating the norm of a
% functor.
; use_map_and_args(weight_table).
:- type term_util__result(T)
---> ok
; error(T).
:- type unify_info == pair(map(var, type), functor_info).
:- type functor_info == functor_algorithm.
% These types are used to assign functor weights to functor symbols
% The weight_info is assigned to a cons_id. weight(Integer, BoolList) gives
% the weight to assign to this functor symbol, and the BoolList states
% whether or not each argument of this functor should be used to calculate
% the size of the output. That is, the BoolList has a 1:1 mapping with the
% arguments of the functor.
:- type weight_info ---> weight(int, list(bool)).
:- type weight_table == map(pair(type_id, cons_id), weight_info).
% This predicate provides an initialised termination structure.
:- pred term_util__init(termination).
:- mode term_util__init(out) is det.
% This predicate calculates a weight table according to the following
% rules:
% If a type is directly recursive, its weight is given by the number of
% directly recursive arguments of this cons id. For example, a list has
% size 1, a binary tree has size 2. Each element of the bool list is
% assigned yes if the corresponding argument is directly recursive, and no
% otherwise.
% If a type is not directly recursive, then it is assigned a weight of 1,
% and the bool list is set to be all yes.
%
% If the bool list is used (by setting functor_algorithm to
% use_map_and_args), then the effect of this norm is that the size
% of a data structure is given by the number of data elements in the data
% structure. If the bool list is ignored, then the size of all the
% arguments of any functor considered, then the size of a data structure is
% given by the total size of its data elements.
:- pred find_weights(module_info::in, weight_table::out) is det.
% This predicate is used to assign a norm (integer) to a functor, depending
% on its type. Depending on the functor_algorithm, this predicate may also
% modify the list of args and modes.
:- pred functor_norm(functor_info, type_id, cons_id, module_info, int,
list(var), list(var), list(uni_mode), list(uni_mode)).
:- mode functor_norm(in, in, in, in, out, in, out, in, out) is det.
% This predicate should be called whenever a procedure needs its termination
% set to dont_know. This predicate checks to see whether the termination
% is already set to dont_know, and if so it does nothing. If the
% termination is set to yes, or not_set, it changes the termination to
% dont_know, and checks whether a
% check_termination pragma has been defined for this predicate, and if so,
% this outputs a useful error message.
:- pred do_ppid_check_terminates(list(pred_proc_id), term_errors__error,
module_info, module_info, io__state, io__state).
:- mode do_ppid_check_terminates(in, in, in, out, di, uo) is det.
% Used to create lists of boolean values, which are used for used_args.
% make_bool_list(HeadVars, BoolIn, BoolOut) creates a bool list which is
% (length(HeadVars) - length(BoolIn)) `no' followed by BoolIn. This is
% used to set the used args for compiler generated predicates. The no's
% at the start are because the Type infos are not used. length(BoolIn)
% should equal the arity of the predicate, and the difference in length
% between the arity of the procedure and the arity of the predicate is
% the number of type infos.
:- pred term_util__make_bool_list(list(_T), list(bool), list(bool)).
:- mode term_util__make_bool_list(in, in, out) is det.
% This predicate partitions the arguments of a call into a list of input
% variables and a list of output variables,
:- pred partition_call_args(module_info, list(mode), list(var), list(var),
list(var)).
:- mode partition_call_args(in, in, in, out, out) is det.
% Removes variables from the InVarBag that are not used in the call.
% remove_unused_args(InVarBag0, VarList, BoolList, InVarBag)
% VarList and BoolList are corresponding lists. Any variable in VarList
% that has a `no' in the corresponding place in the BoolList is removed
% from InVarBag.
:- pred remove_unused_args(bag(var), list(var), list(bool), bag(var)).
:- mode remove_unused_args(in, in, in, out) is det.
% Given a list of pred_proc_ids, this predicate sets the termination
% constant of them all to the constant that is passed to it.
:- pred set_pred_proc_ids_const(list(pred_proc_id), term_util__constant,
module_info, module_info).
:- mode set_pred_proc_ids_const(in, in, in, out) is det.
% Given a list of pred_proc_ids, this predicate sets the error and
% terminates value of them all to the values that are passed to the
% predicate.
:- pred set_pred_proc_ids_terminates(list(pred_proc_id), terminates,
maybe(term_errors__error), module_info, module_info).
:- mode set_pred_proc_ids_terminates(in, in, in, in, out) is det.
% Fails if one or more variables in the list are higher order
:- pred check_horder_args(list(var), map(var, type)).
:- mode check_horder_args(in, in) is semidet.
% Given a list of variables from a unification, this predicate divides the
% list into a bag of input variables, and a bag of output variables.
:- pred split_unification_vars(list(var), list(uni_mode), module_info,
bag(var), bag(var)).
:- mode split_unification_vars(in, in, in, out, out) is det.
:- implementation.
:- import_module map, std_util, require, mode_util, prog_out, type_util.
:- import_module globals, options, inst_match, assoc_list.
term_util__init(term(not_set, not_set, no, no)).
check_horder_args([], _).
check_horder_args([Arg | Args], VarType) :-
map__lookup(VarType, Arg, Type),
\+ type_is_higher_order(Type, _, _),
check_horder_args(Args, VarType).
set_pred_proc_ids_const([], _Const, Module, Module).
set_pred_proc_ids_const([PPId | PPIds], Const, 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_termination(ProcInfo0, Termination0),
Termination0 = term(_Const, Term, UsedArgs, MaybeError),
Termination = term(Const, Term, UsedArgs, MaybeError),
proc_info_set_termination(ProcInfo0, Termination, 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_pred_proc_ids_const(PPIds, Const, Module1, Module).
set_pred_proc_ids_terminates([], _Terminates, _, Module, Module).
set_pred_proc_ids_terminates([PPId | PPIds], Terminates, MaybeError,
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_termination(ProcInfo0, Termination0),
Termination0 = term(Const, _Terminates, UsedArgs, _),
Termination = term(Const, Terminates, UsedArgs, MaybeError),
proc_info_set_termination(ProcInfo0, Termination, 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_pred_proc_ids_terminates(PPIds, Terminates, MaybeError,
Module1, Module).
remove_unused_args(Vars, [], [], Vars).
remove_unused_args(Vars, [], [_X | _Xs], Vars) :-
error("Unmatched variables in term_util:remove_unused_args").
remove_unused_args(Vars, [_X | _Xs], [], Vars) :-
error("Unmatched variables in term_util__remove_unused_args").
remove_unused_args(Vars0, [ Arg | Args ], [ UsedVar | UsedVars ], Vars) :-
( UsedVar = yes ->
% The variable is used, so leave it
remove_unused_args(Vars0, Args, UsedVars, Vars)
;
% The variable is not used in producing output vars, so
% dont include it as an input variable.
bag__delete(Vars0, Arg, Vars1),
remove_unused_args(Vars1, Args, UsedVars, Vars)
).
% For these next two predicates (split_unification_vars and
% partition_call_args) there is a problem of what needs to be done for
% partially instantiated data structures. The correct answer is that the
% system shoud use a norm such that the size of the uninstantiated parts of
% a partially instantiated structure have no effect on the size of the data
% structure according to the norm. For example when finding the size of a
% list-skeleton, list-length norm should be used. Therefore, the size of
% any term must be given by
% sizeof(term) = constant + sum of the size of each
% (possibly partly) instantiated subterm.
% It is probably easiest to implement this by modifying term_weights.
% The current implementation does not correctly handle partially
% instantiated data structures.
split_unification_vars([], Modes, _ModuleInfo, Vars, Vars) :-
bag__init(Vars),
( Modes = [] ->
true
;
error("term_util:split_unification_vars: Unmatched Variables")
).
split_unification_vars([Arg | Args], Modes, ModuleInfo,
InVars, OutVars):-
( Modes = [UniMode | UniModes] ->
split_unification_vars(Args, UniModes, ModuleInfo,
InVars0, OutVars0),
UniMode = ((_VarInit - ArgInit) -> (_VarFinal - ArgFinal)),
( % if
inst_is_bound(ModuleInfo, ArgInit)
->
% Variable is an input variable
bag__insert(InVars0, Arg, InVars),
OutVars = OutVars0
; % else if
inst_is_free(ModuleInfo, ArgInit),
inst_is_bound(ModuleInfo, ArgFinal)
->
% Variable is an output variable
InVars = InVars0,
bag__insert(OutVars0, Arg, OutVars)
; % else
InVars = InVars0,
OutVars = OutVars0
)
;
error("term_util__split_unification_vars: Unmatched Variables")
).
partition_call_args(_, [], [_ | _], _, _) :-
error("Unmatched variables in term_util:partition_call_args").
partition_call_args(_, [_ | _], [], _, _) :-
error("Unmatched variables in term_util__partition_call_args").
partition_call_args(_, [], [], [], []).
partition_call_args(ModuleInfo, [ArgMode | ArgModes], [Arg | Args],
InputArgs, OutputArgs) :-
partition_call_args(ModuleInfo, ArgModes, Args,
InputArgs1, OutputArgs1),
( mode_is_input(ModuleInfo, ArgMode) ->
InputArgs = [Arg | InputArgs1],
OutputArgs = OutputArgs1
; mode_is_output(ModuleInfo, ArgMode) ->
InputArgs = InputArgs1,
OutputArgs = [Arg | OutputArgs1]
;
InputArgs = InputArgs1,
OutputArgs = OutputArgs1
).
term_util__make_bool_list(HeadVars0, Bools, Out) :-
list__length(Bools, Arity),
( list__drop(Arity, HeadVars0, HeadVars1) ->
HeadVars = HeadVars1
;
error("Unmatched variables in term_util:make_bool_list")
),
term_util__make_bool_list_2(HeadVars, Bools, Out).
:- pred term_util__make_bool_list_2(list(_T), list(bool), list(bool)).
:- mode term_util__make_bool_list_2(in, in, out) is det.
term_util__make_bool_list_2([], Bools, Bools).
term_util__make_bool_list_2([ _ | Vars ], Bools, [no | Out]) :-
term_util__make_bool_list_2(Vars, Bools, Out).
%----------------------------------------------------------------------------%
% Although the module info is not used in either of these norms, it could
% be needed for other norms, so it should not be removed.
functor_norm(simple, _, ConsId, _, Int, Args, Args, Modes, Modes) :-
(
ConsId = cons(_, Arity),
Arity \= 0
->
Int = 1
;
Int = 0
).
functor_norm(total, _, ConsId, _Module, Int, Args, Args, Modes, Modes) :-
( ConsId = cons(_, Arity) ->
Int = Arity
;
Int = 0
).
functor_norm(use_map(ConsIdMap), TypeId, ConsId, _Module, Int,
Args, Args, Modes, Modes) :-
( map__search(ConsIdMap, TypeId - ConsId, WeightInfo) ->
WeightInfo = weight(Int, _)
;
Int = 0
).
functor_norm(use_map_and_args(ConsIdMap), TypeId, ConsId, _Module, Int,
Args0, Args, Modes0, Modes) :-
( map__search(ConsIdMap, TypeId - ConsId, WeightInfo) ->
WeightInfo = weight(Int, UseArgList),
(
functor_norm_remove_elems(UseArgList, Args0, Args1,
Modes0, Modes1)
->
Modes = Modes1,
Args = Args1
;
error("Unmatched lists in term_util__functor_norm_remove_elems.")
)
;
Int = 0,
Modes = Modes0,
Args = Args0
).
% This predicate will fail if the length of the input lists are not matched.
:- pred functor_norm_remove_elems(list(bool), list(var), list(var),
list(uni_mode), list(uni_mode)).
:- mode functor_norm_remove_elems(in, in, out, in, out) is semidet.
functor_norm_remove_elems([], [], [], [], []).
functor_norm_remove_elems([yes | Bools], [Arg0 | Args0], [Arg0 | Args],
[Mode0 | Modes0], [Mode0 | Modes]) :-
functor_norm_remove_elems(Bools, Args0, Args, Modes0, Modes).
functor_norm_remove_elems([no | Bools], [_Arg0 | Args0], Args,
[_Mode0 | Modes0], Modes) :-
functor_norm_remove_elems(Bools, Args0, Args, Modes0, Modes).
%----------------------------------------------------------------------------%
do_ppid_check_terminates([] , _Error, Module, Module) --> [].
do_ppid_check_terminates([ PPId | PPIds ], Error, Module0, Module) -->
% look up markers
{ 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_termination(ProcInfo0, Termination0) },
{ Termination0 = term(Const, Terminates, UsedArgs, _) },
( { Terminates = dont_know } ->
{ Module2 = Module0 }
;
{ Termination = term(Const, dont_know, UsedArgs, yes(Error)) },
{ proc_info_set_termination(ProcInfo0, Termination, 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) },
{ pred_info_get_marker_list(PredInfo, MarkerList) },
globals__io_lookup_bool_option(verbose_errors, VerboseErrors),
% If a check_terminates pragma exists, print out an error
% message.
% Note that this allows the one error to be printed out
% multiple times. This is because one error can cause a
% number of predicates to be non terminating, and if
% check_terminates is defined on all of the predicates,
% then the error is printed out for each of them.
(
{ list__member(request(check_termination), MarkerList) }
->
term_errors__output(PredId, ProcId, Module1,
Success),
% Success is only no if there was no error
% defined for this predicate. As we just set the
% error, term_errors__output should succeed.
{ require(unify(Success, yes), "term_util.m: Unexpected value in do_ppid_check_terminates") },
io__set_exit_status(1),
{ module_info_incr_errors(Module1, Module2) }
; % else if
{ \+ pred_info_is_imported(PredInfo) },
% Only output warnings of non-termination for
% important errors, unless verbose errors are
% enabled. Important errors are errors where the
% compiler analysed the code and was not able to
% prove termination. Unimportant warnings are
% created when code is used/called which the
% compiler was unable to analyse/prove termination
% of.
(
{ VerboseErrors = yes }
;
{ Error = _Context - TermError },
{ \+ simple_error(TermError) }
)
->
term_errors__output(PredId, ProcId, Module1,
Success),
% Success is only no if there was no error
% defined for this predicate. As we just set the
% error, term_errors__output should succeed.
{ require(unify(Success, yes), "term_util.m: Unexpected value in do_ppid_check_terminates") },
{ Module2 = Module1 }
;
% Even though the predicate does not terminate, no
% warning has been requested for it.
{ Module2 = Module1 }
)
),
do_ppid_check_terminates(PPIds, Error, Module2, Module).
%----------------------------------------------------------------------------%
%----------------------------------------------------------------------------%
% This predicate calculates a weight table according to the following
% rules:
% If a type is directly recursive, its weight is given by the number of
% directly recursive arguments of this cons id. For example, a list has
% size 1, a binary tree has size 2. Each element of the bool list is
% assigned yes if the corresponding argument is directly recursive, and no
% otherwise.
% If a type is not directly recursive, then it is assigned a weight of 1,
% and the bool list is set to be all yes.
%
% If the bool list is used, then the effect of this norm is that the size
% of a data structure is given by the number of data elements in the data
% structure. If the bool list is ignored, and the size of all the
% arguments are considered, then the size of a data structure is given by
% the total size of its data elements.
find_weights(ModuleInfo, Weights) :-
module_info_types(ModuleInfo, TypeTable),
map__to_assoc_list(TypeTable, TypeList),
map__init(Weights0),
find_weights_for_type_list(TypeList, Weights0, Weights).
:- pred find_weights_for_type_list(assoc_list(type_id, hlds_type_defn)::in,
weight_table::in, weight_table::out) is det.
find_weights_for_type_list([], Weights, Weights).
find_weights_for_type_list([TypeId - TypeDefn | TypeList], Weights0, Weights) :-
find_weights_for_type(TypeId, TypeDefn, Weights0, Weights1),
find_weights_for_type_list(TypeList, Weights1, Weights).
:- pred find_weights_for_type(type_id::in, hlds_type_defn::in,
weight_table::in, weight_table::out) is det.
find_weights_for_type(TypeId, TypeDefn, Weights0, Weights) :-
hlds_data__get_type_defn_body(TypeDefn, TypeBody),
(
TypeBody = du_type(Constructors, _, _, _),
hlds_data__get_type_defn_tparams(TypeDefn, TypeParams),
find_weights_for_cons_list(Constructors, TypeId, TypeParams,
Weights0, Weights)
;
TypeBody = uu_type(_),
error("undiscriminated union types not yet implemented")
;
% This type does not introduce any functors
TypeBody = eqv_type(_),
Weights = Weights0
;
% This type may introduce some functors,
% but we will never see them in this analysis
TypeBody = abstract_type,
Weights = Weights0
).
:- pred find_weights_for_cons_list(list(constructor)::in,
type_id::in, list(type_param)::in,
weight_table::in, weight_table::out) is det.
find_weights_for_cons_list([], _, _, Weights, Weights).
find_weights_for_cons_list([Constructor | Constructors], TypeId, Params,
Weights0, Weights) :-
find_weights_for_cons(Constructor, TypeId, Params, Weights0, Weights1),
find_weights_for_cons_list(Constructors, TypeId, Params,
Weights1, Weights).
:- pred find_weights_for_cons(constructor::in,
type_id::in, list(type_param)::in,
weight_table::in, weight_table::out) is det.
find_weights_for_cons(SymName - Args, TypeId, Params, Weights0, Weights) :-
list__length(Args, Arity),
( Arity > 0 ->
find_and_count_nonrec_args(Args, TypeId, Params,
NumNonRec, ArgInfos0),
( NumNonRec = 0 ->
Weight = 1,
list__duplicate(Arity, yes, ArgInfos)
;
Weight = NumNonRec,
ArgInfos = ArgInfos0
),
WeightInfo = weight(Weight, ArgInfos)
;
WeightInfo = weight(0, [])
),
ConsId = cons(SymName, Arity),
map__det_insert(Weights0, TypeId - ConsId, WeightInfo, Weights).
:- pred find_and_count_nonrec_args(list(constructor_arg)::in,
type_id::in, list(type_param)::in,
int::out, list(bool)::out) is det.
find_and_count_nonrec_args([], _, _, 0, []).
find_and_count_nonrec_args([Arg | Args], Id, Params, NonRecArgs, ArgInfo) :-
find_and_count_nonrec_args(Args, Id, Params, NonRecArgs0, ArgInfo0),
( is_arg_recursive(Arg, Id, Params) ->
NonRecArgs = NonRecArgs0,
ArgInfo = [yes | ArgInfo0]
;
NonRecArgs is NonRecArgs0 + 1,
ArgInfo = [no | ArgInfo0]
).
:- pred is_arg_recursive(constructor_arg::in,
type_id::in, list(type_param)::in) is semidet.
is_arg_recursive(Arg, Id, Params) :-
Arg = _Name - ArgType,
type_to_type_id(ArgType, ArgTypeId, ArgTypeParams),
Id = ArgTypeId,
list__perm(Params, ArgTypeParams).
%-----------------------------------------------------------------------------%
%
% Copyright (C) 1997 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_errors.m
% Main author: crs.
%
% This module prints out the various error messages that are produced by
% termination.m
%
%-----------------------------------------------------------------------------%
:- module term_errors.
:- interface.
:- import_module io, bag, std_util, term, list, bool.
:- import_module hlds_module.
% term_errors__output(PredId, ProcId, Module, Success, IO0, IO).
% This is used to print out the errors found by termination analysis.
% Success returns yes if an error was successfully printed out.
% Success will be no if there was no termination error for that
% procedure.
:- pred term_errors__output(pred_id, proc_id, module_info, bool,
io__state, io__state).
:- mode term_errors__output(in, in, in, out, di, uo) is det.
% term_errors__output_const_error(PredId, ProcId, Module, Success, IO0, IO).
% This prints out any errors which occured when trying to set the
% termination constant. An error message will only be available if
% the termination constant is set to 'inf'.
% Success returns yes if an error was successfully printed out.
% Success will be no if there was no termination error for that
% procedure.
:- pred term_errors__output_const_error(pred_id, proc_id, module_info, bool,
io__state, io__state).
:- mode term_errors__output_const_error(in, in, in, out, di, uo) is det.
% This is used to print out error messages for the hlds dumps. These are
% much more concise than the normal error messages.
:- pred term_errors__output_hlds(pred_id, proc_id, module_info,
io__state, io__state).
:- mode term_errors__output_hlds(in, in, in, di, uo) is det.
:- type term_errors__error == pair(term__context, termination_error).
% With these error messages, they do not necessarily need to involve the
% procedure that they are assigned to. It is possible that an error
% occured when processing other predicates in the same SCC, and therefore
% the termination (or termination constant) of this predicate was set to
% dont_know (or infinity), even though the error occured in a different
% predicate.
:- type termination_error
% A recursive call is made with variables that are
% strictly larger than in the head. Note that
% the recursive call may be indirect, so this does
% not neccessarily indicate non-termination.
% The first PPId is the calling proc. The second
% PPId is the called procedure.
---> positive_value(pred_proc_id, pred_proc_id)
; horder_call
; pragma_c_code
; imported_pred
% dont_know_proc_called(CallerProc, CalleeProc)
% A call was made from CallerProc to CalleeProc,
% where the termination constant of the CalleeProc
% is set to dont_know.
; dont_know_proc_called(pred_proc_id, pred_proc_id)
% horder_args(CallerProc, CalleeProc)
% This error message indicates that the CallerProc
% called the CalleeProc where some of the arguments
% are of a higher order type.
; horder_args(pred_proc_id, pred_proc_id)
; inf_termination_const(pred_proc_id, pred_proc_id)
% not_subset(Proc, SupplierVariables, InHeadVariables)
% This error occurs when the Supplier variables
% (either Recursive-input suppliers or Output
% suppliers, depending on whether the error was
% associated with a dont_know or with a constant of
% infinity) is not a subset of the input head
% variables.
; not_subset(pred_proc_id, bag(var), bag(var))
; not_dag
; no_eqns
; lpsolve_failed(eqn_soln)
; call_in_single_arg(pred_proc_id)
% single argument analysis did not find a head
% variable that was decreasing in size.
; single_arg_failed(term_errors__error)
% single_arg_failed(ReasonForNormalAnalysisFailing,
% ReasonForSingleArgAnalysisFailing)
; single_arg_failed(term_errors__error, term_errors__error)
% the termination constant of a builtin predicate
% is set to infinity if the types of the builtin
% predicate may have a norm greater than 0.
; is_builtin
; does_not_term_pragma(pred_id).
% eqn_soln are used to report the results from solving the equations
% created in the first pass. The first 4 (optimal - failure) represent
% output states from lp_solve.
:- type eqn_soln
---> optimal
; infeasible
; unbounded
; failure
; fatal_error % unable to open a file, or make a system call
; parse_error % unable to parse the output from lp_solve
; solved(list(pair(pred_proc_id, int))).
% An error is considered a simple error if it is likely that the error is
% caused by the analysis failing, instead of being due to a programming
% error.
:- pred simple_error(term_errors__termination_error).
:- mode simple_error(in) is semidet.
:- implementation.
:- import_module hlds_out, prog_out, hlds_pred, passes_aux, require.
:- import_module mercury_to_mercury, term_util, bag, options, globals.
simple_error(horder_call).
simple_error(pragma_c_code).
simple_error(imported_pred).
simple_error(dont_know_proc_called(_, _)).
simple_error(call_in_single_arg(_)).
simple_error(horder_args(_, _)).
simple_error(single_arg_failed(_Context - Error)) :- simple_error(Error).
simple_error(single_arg_failed(_Con1 - Err1, _Con2 - Err2)) :-
simple_error(Err1), simple_error(Err2).
simple_error(does_not_term_pragma(_)).
term_errors__output(PredId, ProcId, Module, Success) -->
{ module_info_pred_proc_info(Module, PredId, ProcId, _, ProcInfo) },
{ proc_info_termination(ProcInfo, Termination) },
{ Termination = term(_Const, _Terminates, _UsedArgs, MaybeError) },
( { MaybeError = yes(Context - Error) } ->
prog_out__write_context(Context),
io__write_string("Termination of "),
hlds_out__write_pred_id(Module, PredId),
io__nl,
prog_out__write_context(Context),
io__write_string(" not proved because "),
{ ConstErrorOutput = no },
{ ForHLDSDump = no },
term_errors__output_2(PredId, ProcId, Module, ConstErrorOutput,
ForHLDSDump, Context - Error),
{ Success = yes }
;
{ Success = no }
).
term_errors__output_const_error(PredId, ProcId, Module, Success) -->
{ module_info_pred_proc_info(Module, PredId, ProcId, _, ProcInfo) },
{ proc_info_termination(ProcInfo, Termination) },
{ Termination = term(Const, _Terminates, _UsedArgs, _MaybeError) },
( { Const = inf(Context - Error) } ->
prog_out__write_context(Context),
io__write_string("Termination constant of "),
hlds_out__write_pred_id(Module, PredId),
io__nl,
prog_out__write_context(Context),
io__write_string(" set to infinity because "),
{ ConstErrorOutput = yes },
{ ForHLDSDump = no },
term_errors__output_2(PredId, ProcId, Module, ConstErrorOutput,
ForHLDSDump, Context - Error),
{ Success = yes }
;
{ Success = no }
).
term_errors__output_hlds(PredId, ProcId, Module) -->
{ module_info_pred_proc_info(Module, PredId, ProcId, _, ProcInfo) },
{ proc_info_termination(ProcInfo, Termination) },
{ Termination = term(Const, _Terminates, _UsedArgs, MaybeError) },
{ ForHLDSDump = yes },
( { MaybeError = yes(TermContext - TermError) } ->
io__write_string("% "),
prog_out__write_context(TermContext),
io__write_string("Termination not proved because "),
{ ConstErrorOutput0 = no },
term_errors__output_2(PredId,ProcId, Module, ConstErrorOutput0,
ForHLDSDump, TermContext - TermError)
;
[]
),
( { Const = inf(ConstContext - ConstError) } ->
io__write_string("% "),
prog_out__write_context(ConstContext),
io__write_string("Termination const set to inf because "),
{ ConstErrorOutput1 = yes },
term_errors__output_2(PredId, ProcId, Module, ConstErrorOutput1,
ForHLDSDump, ConstContext - ConstError)
;
[]
).
:- pred term_errors__output_same_SCC(pred_id, module_info, term__context,
bool, io__state, io__state).
:- mode term_errors__output_same_SCC(in, in, in, in, di, uo) is det.
term_errors__output_same_SCC(PredId, Module, Context, ForHLDSDump) -->
io__write_string("it is in the same SCC as the\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" "),
hlds_out__write_pred_id(Module, PredId).
% term_errors__output_2(PredId, ProcId, Module, ConstErrorOutput,
% ForHLDSDump, Error, IO0, IO).
% If this predicate is called from term_errors__output_const_error, then
% ConstErrorOutput should be set to `yes' to indicate that the error
% message should describe why the constant was set to infinity.
% If ConstErrorOutput is set to `no' then term_errors__output_2 describes
% why the analysis could not prove termination.
%
% If ForHLDSDump is set to yes, then a % must be placed at the beginning of
% each line, because the output is for the HLDS dump.
%
% This predicate is used by both term_errors__output() and
% term_errors__output_const_error() to print out the reason for the error.
% Before calling output_2, term_errors__output prints out:
% myfile.m:300: Termination of predicate `myfile:yourpredicate/3'
% myfile.m:300: not proved because
%
% and term_errors__output_const_error prints out:
% myfile.m:300: Termination constant of function `myfile:myfunction/6'
% myfile.m:300: set to infinity because
%
:- pred term_errors__output_2(pred_id, proc_id, module_info, bool, bool,
term_errors__error, io__state, io__state).
:- mode term_errors__output_2(in, in, in, in, in, in, di, uo) is det.
term_errors__output_2(PredId, _ProcId, Module, _ConstErrorOutput, ForHLDSDump,
Context - positive_value(CallerPPId, CalledPPId)) -->
{ CalledPPId = proc(CalledPredId, _CalledProcId) },
{ CallerPPId = proc(CallerPredId, _CallerProcId) },
( { PredId = CallerPredId } ->
io__write_string("it contains a ")
;
term_errors__output_same_SCC(PredId, Module, Context,
ForHLDSDump),
io__write_string(" which contains a ")
),
( { PredId = CalledPredId } ->
io__write_string("directly\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" recursive call ")
;
io__write_string("recursive\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string("call to "),
hlds_out__write_pred_id(Module, CalledPredId),
io__nl,
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" ")
),
io__write_string("with the size of the variables increased.\n").
term_errors__output_2(_PredId, _ProcId, _Module, _ConstErrOutput, _ForHLDSDump,
_Context - horder_call) -->
io__write_string("it contains a higher order call\n").
term_errors__output_2(_PredId, _ProcId, _Module, _ConstErrOutput, _ForHLDSDump,
_Context - pragma_c_code) -->
io__write_string("it contains a pragma c_code() declaration\n").
term_errors__output_2(PredId, _ProcId, Module, _ConstErrorOutput, ForHLDSDump,
Context - dont_know_proc_called(CallerPPId, CalleePPId)) -->
{ CallerPPId = proc(CallerPredId, _CallerProcId) },
{ CalleePPId = proc(CalleePredId, _CalleeProcId) },
( { PredId = CallerPredId } ->
io__write_string("it calls the ")
;
term_errors__output_same_SCC(CallerPredId, Module, Context,
ForHLDSDump),
io__write_string("which calls the ")
),
io__nl,
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" "),
hlds_out__write_pred_id(Module, CalleePredId),
io__nl,
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" which could not be proved to terminate\n").
term_errors__output_2(_PredId, _ProcId, _Module, _ConstErrOutput, _ForHLDSDump,
_Context - imported_pred) -->
io__write_string("it was imported.\n").
term_errors__output_2(PredId, _ProcId, Module, _ConstErrorOutput, ForHLDSDump,
Context - horder_args(CallerPPId, CalleePPId)) -->
% OtherPPId may refer to the current Predicate, or it may refer to
% another predicate in the same SCC
{ CallerPPId = proc(CallerPredId, _CallerProcId) },
{ CalleePPId = proc(CalleePredId, _CalleeProcId) },
( { PredId = CallerPredId } ->
io__write_string("it calls the ")
;
term_errors__output_same_SCC(CallerPredId, Module, Context,
ForHLDSDump),
io__write_string("which calls the")
),
io__nl,
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" "),
hlds_out__write_pred_id(Module, CalleePredId),
io__nl,
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(
" where the call contains higher order argument(s)\n").
term_errors__output_2(PredId, _ProcId, Module, ConstErrorOutput, ForHLDSDump,
Context - inf_termination_const(CallerPPId, CalleePPId)) -->
{ CallerPPId = proc(CallerPredId, _CallerProcId) },
{ CalleePPId = proc(CalleePredId, CalleeProcId) },
( { PredId = CallerPredId } ->
io__write_string("it calls the ")
;
term_errors__output_same_SCC(CallerPredId, Module, Context,
ForHLDSDump),
io__write_string("which calls the ")
),
io__nl,
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" "),
hlds_out__write_pred_id(Module, CalleePredId),
io__nl,
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(
" which has a termination constant of infinity\n"),
(
{ ForHLDSDump = no },
{ ConstErrorOutput = no }
->
{ module_info_pred_proc_info(Module, CalleePredId,
CalleeProcId, _, CalleeProcInfo) },
{ proc_info_termination(CalleeProcInfo, CalleeTermination) },
{ CalleeTermination = term(CalleeConst, _, _, _) },
globals__io_lookup_bool_option(verbose_errors, VerboseErrors),
(
{ CalleeConst = inf(CalleeContext - CalleeConstError)},
(
{ \+ simple_error(CalleeConstError) }
;
{ VerboseErrors = yes }
)
->
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(CalleeContext),
io__write_string(" The termination constant of that predicate was set to\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(CalleeContext),
io__write_string(" infinity because "),
{ NewConstErrorOutput = yes },
term_errors__output_2(CalleePredId, CalleeProcId,
Module, NewConstErrorOutput, ForHLDSDump,
CalleeContext - CalleeConstError)
;
[]
)
;
[]
).
% If hlds dump, should print out variables in numerical form
% If verbose errors requested (-E), should list variables
term_errors__output_2(PredId, _ProcId, Module, ConstErrorOutput, ForHLDSDump,
Context - not_subset(OtherPPId,
SupplierVarsBag, HeadVarsBag)) -->
{ OtherPPId = proc(OtherPredId, OtherProcId) },
{ module_info_pred_proc_info(Module, OtherPredId, OtherProcId,
OtherPredInfo, OtherProcInfo) },
{ pred_info_get_is_pred_or_func(OtherPredInfo, OtherPredOrFunc) },
( { OtherPredId = PredId } ->
[]
;
term_errors__output_same_SCC(OtherPredId, Module, Context,
ForHLDSDump),
( { ConstErrorOutput = yes } ->
io__write_string(
". The termination constant of that\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" "),
hlds_out__write_pred_or_func(OtherPredOrFunc),
io__write_string(" was set to infinity because ")
;
io__write_string(". Termination of that\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" "),
hlds_out__write_pred_or_func(OtherPredOrFunc),
io__write_string(" could not be proved because ")
)
),
io__write_string("the analysis\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" found that the set of "),
( { ConstErrorOutput = yes } ->
io__write_string("output ")
;
io__write_string("recursive input ")
),
io__write_string("supplier variables\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" was not a subset of the head variables of the "),
hlds_out__write_pred_or_func(OtherPredOrFunc),
io__write_string(".\n"),
% Print out the variables as calculated.
{ proc_info_variables(OtherProcInfo, VarSet) },
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" The "),
( { ConstErrorOutput = yes } ->
io__write_string("output ")
;
io__write_string("recursive input ")
),
io__write_string("supplier variables are\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" "),
{ bag__to_list(SupplierVarsBag, SupplierVars) },
mercury_output_vars(SupplierVars, VarSet, no),
io__nl,
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" The input head variables are\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" "),
{ bag__to_list(HeadVarsBag, HeadVars) },
mercury_output_vars(HeadVars, VarSet, no),
io__nl.
term_errors__output_2(_PredId, _ProcId, _Module, _ConstErrorOutput, ForHLDSDump,
Context - not_dag) -->
io__write_string("there was a cycle in the call graph of\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(
" this SCC where the variables did not decrease in size\n").
term_errors__output_2(_PredId, _ProcId, _Module, ConstErrorOutput, ForHLDSDump,
Context - no_eqns)-->
{ require(unify(ConstErrorOutput, yes),
"Unexpected value in term_errors__output_2(no_eqns)") },
io__write_string("the analysis was unable to form any\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(
" constraints between the arguments of the predicates\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" and functions in this SCC\n").
term_errors__output_2(_PredId, _ProcId, _Module, _ConstErrorOutput, ForHLDSDump,
Context - lpsolve_failed(Reason)) -->
io__write_string("the constraint solver "),
(
{ Reason = optimal },
{ error("term_errors:output_2 Unexpected return value from lp_solve") }
;
{ Reason = infeasible },
io__write_string("found the\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" constraints that the analysis produced to be infeasible\n")
;
{ Reason = unbounded },
io__write_string("found that the\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(
" constraints were not strong enough to put a\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(
" bound on the value of the change constants\n")
;
{ Reason = failure },
io__write_string("was unable to\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(
" solve the constraints that were produced\n")
;
{ Reason = fatal_error },
io__set_exit_status(1),
io__write_string("was unable to create and/or\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" remove the temporary files it required\n")
;
{ Reason = parse_error },
{ error("term_errors:output_2 Unexpected return value from lp_solve") }
;
{ Reason = solved(_) },
{ error("term_errors:output_2 Unexpected return value from lp_solve") }
).
% call_in_single_arg will only be printed out as the second part of the
% single_arg_failed(NormErr, SingleErr) error. Therefore, the following
% lines have already been printed out:
% Single argument termination analysis failed to
% prove termination because
term_errors__output_2(_PredId, _ProcId, Module, _ConstErrorOutput, ForHLDSDump,
Context - call_in_single_arg(PPId)) -->
{ PPId = proc(CallPredId, _CallProcId) },
io__write_string("it encountered a call to\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" "),
hlds_out__write_pred_id(Module, CallPredId),
io__nl,
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" which could not be processed.\n").
term_errors__output_2(PredId, ProcId, Module, ConstErrorOutput, ForHLDSDump,
Context - single_arg_failed(Error)) -->
term_errors__output_2(PredId, ProcId, Module, ConstErrorOutput,
ForHLDSDump, Error),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(
" Single argument analysis failed to find a head variable\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" that was always decreasing in size.\n").
term_errors__output_2(PredId, ProcId, Module, ConstErrorOutput, ForHLDSDump,
_Context - single_arg_failed(NormErr, SingleArgErr)) -->
{ NormErr = NormalContext - NormalError },
{ SingleArgErr = SingleArgContext - SingleArgError },
% single argument analysis is independent of finding the
% termination constant. The error for a termination
% constant should never be single_arg_failed.
{ require(unify(ConstErrorOutput, no),
"Unexpected value in term_errors__output_2") },
term_errors__output_2(PredId, ProcId, Module, ConstErrorOutput,
ForHLDSDump, NormalContext - NormalError),
globals__io_lookup_bool_option(verbose_errors, VerboseErrors),
(
{ SingleArgError \= NormalError },
{ VerboseErrors = yes }
->
% Only output a single argument error if verbose errors are
% enabled, and it is different from the normal error
% message.
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(SingleArgContext),
io__write_string(
" Single argument termination analysis failed to\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(SingleArgContext),
io__write_string(" prove termination because "),
term_errors__output_2(PredId, ProcId, Module, ConstErrorOutput,
ForHLDSDump, SingleArgContext - SingleArgError)
;
[]
).
term_errors__output_2(_PredId, _ProcId, _Module, ConstErrorOutput, _ForHLDSDump,
_Context - is_builtin) -->
{ require(unify(ConstErrorOutput, yes),
"Unexpected value in term_errors:output_2") },
io__write_string("it is a builtin predicate\n").
term_errors__output_2(PredId, _ProcId, Module, ConstErrorOutput, ForHLDSDump,
Context - does_not_term_pragma(OtherPredId)) -->
{ require(unify(ConstErrorOutput, no),
"Unexpected value in term_errors:output_2") },
io__write_string("there was a `does_not_terminate'\n"),
maybe_write_string(ForHLDSDump, "% "),
prog_out__write_context(Context),
io__write_string(" pragma defined on "),
( { PredId = OtherPredId } ->
{ module_info_pred_info(Module, PredId, PredInfo) },
{ pred_info_get_is_pred_or_func(PredInfo, PredOrFunc) },
io__write_string("this "),
hlds_out__write_pred_or_func(PredOrFunc),
io__nl
;
io__write_string("the "),
hlds_out__write_pred_id(Module, OtherPredId),
io__nl
).
More information about the developers
mailing list