[m-dev.] Tdiff: termination analysis (BIG!)

Fergus Henderson fjh at cs.mu.oz.au
Wed Apr 23 15:50:47 AEST 1997


The final installment for this round of reviewing...

Christopher Rodd SPEIRS, you wrote:
> 
> %-----------------------------------------------------------------------------%
> % support functions for goal_inequality
> 
> :- pred join_offsets(list(term_pass1__equation), list(term_pass1__equation),
> 	list(term_pass1__equation)).
> :- mode join_offsets(in, in, out) is det.

Please document this predicate.

> join_offsets(In1, In2, Out) :-
> 	( In2 = [] ->
> 		Out = In1
> 	; 	
> 		(	
> 			In1 = [],
> 			Out = In2
> 		;
> 			In1 = [X | Xs],
> 			list__map(eqn_add(X), In2, Out1),
> 			( Xs = [] ->
> 				Out = Out1
> 			;
> 				join_offsets(Xs, In2, Out0),
> 				list__append(Out0, Out1, Out)
> 			)
> 			%list__merge_and_remove_dups(Out0, Out1, Out)

There's no need to special-case Xs = [] here.

> % Solve the list of constraints 
> 
> % output is of the form required by lp_solve (not yet)

I don't understand the "(not yet)" comment.

> 		io__get_environment_var("LPSOLVE", Lpsolve0),

Please prefix all environment variable names with `MERCURY_', to avoid
name clashes.

> 			;
> 				% Damn, it failed, interpret result, 
> 				% and were done

s/it failed,/it failed;/
s/were/we're/

> 		% dont forget to close, and delete files!!
> 		io__remove_file(ConstraintFile, Res1),
> 		( { Res1 = error(Error1) } ->
> 			{ io__error_message(Error1, Msg1) },
> 			io__write_strings([
> 				ProgName,
> 				": Error unlinking temporary file `",

s/unlinking/deleting/
(Ditto elsewhere)

> :- pred solve_eqns_parse_output_file(eqn_soln, io__state, io__state).
> :- mode solve_eqns_parse_output_file(out, di, uo) is det.
> solve_eqns_parse_output_file(Soln) -->
> 	io__read_line(Res1),
> 	( { Res1 = ok(_) } ->
> 		solve_eqns_parse_output_file_2(Soln0, MaybeBVal),
> 		( { Soln0 = solved(Result0) } ->
> 			( { MaybeBVal = yes(BVal) } ->
> 				{ solve_eqns_output_file_2(Result0, BVal,
> 					Result) },
> 				{ Soln = solved(Result) }
> 			;
> 				io__write_string("no bval\n"),
> 				{ Soln = parse_error }
> 			)
> 		;
> 			io__write_string(
> 				"parse_output_file returned not solved\n"),
> 			{ Soln = parse_error }
> 		)
> 	;
> 		{ Soln = parse_error }
> 	).

Should those calls to io__write_string really be there?

> :- pred solve_eqns_output_file_2(list(pair(pred_proc_id, int)), int,
> 	list(pair(pred_proc_id, int))).
> :- mode solve_eqns_output_file_2(in, in, out) is det.
> solve_eqns_output_file_2([], _, []). 
> solve_eqns_output_file_2([X | Xs], BVal, [Y | Ys]) :-
> 	X = PPId - XVal, 	% pair deconstruction
> 	YVal = XVal - BVal,	% subtraction

s/=/is/  (otherwise it won't work with NU-Prolog or SICStus Prolog).

> :- pred solve_eqns_parse_output_file_2(eqn_soln, maybe(int), io__state,
> 	io__state).
> :- mode solve_eqns_parse_output_file_2(out, out, di, uo) is det.
> solve_eqns_parse_output_file_2(Soln, BVal) -->
> 	io__read_line(Res1),
> 	( { Res1 = ok([ X | Xs ]) } ->
> 		( 
> 			{ X = 'a' },

What does the magic character 'a' mean?
Please at least document this better.

> 		; 
> 			{ X = 'b' },

Ditto for 'b'.

> :- pred char_list_remove_int(list(char), int, list(char)).
> :- mode char_list_remove_int(in, out, out) is semidet.
> char_list_remove_int([X | Xs], Int, ListOut) :-
> 	char__is_digit(X),
> 	char__to_int(X, Int0),
> 	Int1 = Int0 - 48,   % char__to_int('0', 48).
> 	char_list_remove_int_2(Xs, Int1, Int, ListOut).

Now where did we put the gun after we shot the last summer student...  ;-)

Magic numbers are evil.  Please avoid them.

> :- pred char_list_remove_int_2(list(char), int, int, list(char)).
> :- mode char_list_remove_int_2(in, in, out, out) is semidet.
> 
> char_list_remove_int_2([], Int, Int, []).
> char_list_remove_int_2([X | Xs], Int0, Int, ListOut) :-
> 	( char__is_digit(X) ->
> 		char__to_int(X, Int1),
> 		Int2 = Int0 * 10 + Int1 - 48,

Ditto.

> :- pred lpsolve_ret_val(int, eqn_soln).
> :- mode lpsolve_ret_val(in, out) is det.
> lpsolve_ret_val(Int, Result) :-
> 	( Int = -1	->	Result = fatal_error
> 	; Int = 0	->	Result = optimal
> 	; Int = 1	->	Result = failure
> 	; Int = 2	->	Result = infeasible
> 	; Int = 3	->	Result = unbounded
> 	; Int = 4	->	Result = failure
> 	; Int = 5	->	Result = failure
> 	; 			Result = failure
> 	).

Why all the different failure cases?  Please document them.

> 		{ Succ = no}	% XXX is this correct, or should we return 
> 				% the fact that we didnt output a constraint
> 				% currently if we dont print out any constraints
> 				% then lp_solve fails.

Please punctuate those sentences.

> % Copyright (C) 1995 University of Melbourne.

Please check the copyright dates on all the files you're adding.

> % used in termination_3_goal to keep track of the relative sizes of variables
> % between the head of a pred and any recursive calls.
> % the last argument is only used when doing single argument analysis.  It
> % stores which headvar was initially placed in this tuple.
> :- type termination_3
> 	--->	tuple(pred_proc_id, 	% The called proc
> 			int, 		% the relative size of the active
> 					% vars, in comparison with the 
> 					% recursive call
> 			bag(var), 	% tha bag of active vars
> 			maybe(var), 	% only for single arg. analysis
> 					% stores which headvar is being
> 					% traced by this tuple
> 			term__context).	% Where the call occured

It would be helpful if you could use better names than `termination_3'
and `tuple'.

> :- pred termination_2(module_info, dependency_ordering, module_info, io__state,
> 	io__state).
> :- mode termination_2(in, in, out, di, uo) is det.
> termination_2(Module, [], Module) --> [].
> termination_2(Module0, [SCC | SCCs], Module) -->
> 			% YYY
> 			%{ c_puts("termination_2") },

You need to fix that % YYY comment.

> :- pred termination_3(list(pred_proc_id), module_info, 
> 	term_util__result(term_error__error), relation(pred_proc_id), 
> 	relation(pred_proc_id)).
> :- mode termination_3(in, in, out, in, out) is det.

Some documentation for this pred would be good idea.
Also more informative names would help.

> 	(
> 		{ SingleArgs = yes },
> 		{ Rest = [] },
> 		{ Error \= _ - dont_know_proc(_) }
> 		% and Error=???

That comment is quite confusing.

> 			% single arg failed - bummer

Please use a term that wouldn't possibly offend anyone, rather than "bummer".
Remember that this code may be read by people from different cultures...

> 			Int < 0,
> 			% check that the var that was recursed on did not 
> 			% changeplace between the head and recursive call.  
> 			% I am not sure if the first call is actually 
> 			% required to succeed

s/changeplace/change place/

> 			bag__remove(VarBag0, HeadVar, VarBag1),
> 			\+ bag__remove_smallest(VarBag1, _, _)

Hmm, wouldn't `\+ bag__is_empty(VarBag1)' be better?

> 	;
> 		CallTermConst = not_set,
> 		error("Unexpected Termination Constant in termination.m"),
> 		Res0 = ok,
> 		Out1 = Out0

Delete the assignments to Res0 and Out1, they're not needed.
A comment here explaining why this condition was impossible
would be helpful.

> 	% step 1 - modify Out0

Could you please expand on that comment a bit?
Modify Out0 in what way?  What is this code trying to achieve?

> termination_3_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_3_check_intersect(Out, OutVarBag) ->
> 		% c_code has no important output vars, so we need no error
> 		Res = ok
> 	;
> 		goal_info_get_context(GoalInfo, Context),
> 		Res = error(Context - pragma_c_code)
> 	).

A comment here explaining how you treat pragma c_codes would be helpful.

> %-----------------------------------------------------------------------------%
> % These following predicates all support termination_3_goal. 

s/all support/are all subroutines of/

> :- pred termination_3_modify_out(bag(var), bag(var), int, list(termination_3), 
> 	list(termination_3)).
> :- mode termination_3_modify_out(in, in, in, in, out) is det.
> termination_3_modify_out(_, _, _, [], []).
> termination_3_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_3_modify_out(InVars, OutVars, Off, Out0s, Outs).

What is the variable "Off" supposed to represent here?
More meaningful variable names and/or better documentation would help here.

> :- module term_util.
> 
> :- interface.
> 
> :- import_module list, bool, bag, int, hlds_module, hlds_pred, hlds_data.
> :- import_module term_error, io, hlds_goal.
> 
> :- type termination 
> 	--->	 term(term_util__constant, terminates, maybe(list(bool)),
> 		maybe(term_error__error)).
> 	%   the final list has a 1:1 correspondance with the arguments of the
> 	%   proc.  it stores whether the arg is used in producing the 
> 	%   output args.  

What is the type `termination' used for?
Please document that.
Also, what do you mean by the "final" list?

> % 
> :- pred term_util__make_bool_list(proc_info, list(bool), list(bool)).
> :- mode term_util__make_bool_list(in, in, out) is det.
> 
> :- pred term_util__make_bool_list_2(list(var), list(bool), list(bool)).
> :- mode term_util__make_bool_list_2(in, in, out) is det.

Please document these.  Also, if something is exported from a module,
it should have a more meaningful name than `..._2'.

> :- 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.

Please document this.

> % removes vars from the InVarBag that are not used in the call
> :- pred remove_unused_args(bag(var), list(var), list(bool), bag(var)).
> :- mode remove_unused_args(in, in, in, out) is det.

The documentation here is not clear.  What are the intended
semantics of each argument?

> :- 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.
> 
> :- pred set_pred_proc_ids_terminates(list(pred_proc_id), terminates,
> 	maybe(term_error__error), module_info, module_info).
> :- mode set_pred_proc_ids_terminates(in, in, in, in, out) is det.

> :- 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.

Please document these.

> split_unification_vars([], Modes, _ModuleInfo, Vars, Vars) :-
> 	bag__init(Vars),
> 	( Modes = [] ->
> 		true
> 	;
> 		error("Error in termination.m")
> 	).

This is in term_util.m, so the error message shouldn't say termination.m.
Please add a comment explaining why this is an error.

> split_unification_vars([Arg | Args], Modes, ModuleInfo,
> 		InVars,OutVars):-

s/,/, /

> 
> 	( Modes = [UniMode | UniModes] ->
> 		split_unification_vars(Args, UniModes, ModuleInfo,
> 			InVars0, OutVars0),
> 		UniMode = ((_VarInit - ArgInit) -> (_VarFinal - ArgFinal)),
> 		( %if
> 			inst_is_bound(ModuleInfo, ArgInit) 
> 		->
> 			(
> 				% is input var
> 				bag__insert(InVars0, Arg, InVars),
> 				OutVars = OutVars0
> 			)
> 		; %elseif
> 			( 
> 				inst_is_free(ModuleInfo, ArgInit),
> 				inst_is_bound(ModuleInfo, ArgFinal) 
> 			) 
> 		->
> 			%is outputvar
> 			InVars = InVars0,
> 			bag__insert(OutVars0, Arg, OutVars)
> 		; %else
> 			InVars = InVars0,
> 			OutVars = OutVars0
> 		)
> 	;
> 		error("Error in termination.m")
> 	).

This is buggy, as we discussed.

> 	proc_info_termination(ProcInfo0, Termination0),
> 	Termination0 = term(_Const, Term, UsedArgs, MaybeError),
> 	Termination = term(Const, Term, UsedArgs, MaybeError),
> 	% YYY
> 	%c_put_term(PPId, Termination),
> 	proc_info_set_termination(ProcInfo0, Termination, ProcInfo),

That % YYY comment needs fixing.

> 	Termination0 = term(Const, _Terminates, UsedArgs, _),
> 	Termination = term(Const, Terminates, UsedArgs, MaybeError),
> 	% YYY
> 	%c_put_term(PPId, Termination),
> 	proc_info_set_termination(ProcInfo0, Termination, ProcInfo),

Ditto.

> 	error("Unmatched Vars in termination.m").

s/Vars/vars/

> 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
> 	).

Should those be calls to mode_is_fully_input and mode_is_fully_output?

> functor_norm(simple, ConsId, _, Int) :-
> 	( 
> 		ConsId = cons(_, Arity),
> 		Arity \= 0
> 	->
> 		Int = 1
> 	;
> 		Int = 0
> 	).

That doesn't match the documentation, which says "assigns all functors
a norm of 1".  You should change the documentation to say "assigns all
functors a norm of 1 and all constants a norm of zero".

> functor_norm(total, ConsId, _Module, Int) :-
> 	( ConsId = cons(_, Arity) ->
> 		Int = Arity
> 	;
> 		Int = 1
> 	).

What about pred_consts?

> :- type term_error__error == pair(term__context, termination_error).
> :- type termination_error
> 	--->	positive_value(pred_proc_id, pred_proc_id)
> 			% 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 proc, and the context is the
> 			% line where the call occurs.

s/indirect/conditional/ ?

> 	;	not_subset(bag(var), bag(var))
> 	;	horder_call
> 	;	pragma_c_code
> 	;	dont_know_proc(pred_proc_id)
> 	;	dont_know_proc_called(pred_proc_id)
> 	;	horder_args(pred_proc_id)
> 	;	inf_termination_const(pred_proc_id)
> 	;	ite_wrong_vars(bag(var), bag(var))
> 	;       switch_wrong_vars(bag(var), bag(var))
> 	;       disj_wrong_vars(bag(var), bag(var))
> 	;	not_dag
> 	;	no_eqns
> 	;	lpsolve_failed(eqn_soln)
> 	;	call_in_single_arg(pred_proc_id)
> 	;	single_arg_failed(term_error__error)
> 	;	single_arg_failed(term_error__error, term_error__error).

Please document these.  E.g. what does `not_subset' indicate,
and what do the arguments mean (what is the correct argument order?).
What does `dont_know_proc' mean, and when could it occur?
What do `inf', `ite', and `dag' stand for?  What does wrong_vars mean?
etc.

> % 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))).

What's the difference between infeasible, unbounded, and failure?
What's the difference between optimal and solved?

> :- implementation.
> 
> :- import_module hlds_out, prog_out, hlds_pred.
> 
> term_error__output(MaybePredId, Module, 
> 		Context - positive_value(CallerPPId, CalledPPId)) -->	
> 	( { MaybePredId = yes(PredId) } ->
> 		{ CalledPPId = proc(CalledPredId, _CalledProcId) },
> 		{ CallerPPId = proc(CallerPredId, _CallerProcId) },
> 		prog_out__write_context(Context),
> 		io__write_string("Unable to check termination in predicate:\n"),

s/://

> 		prog_out__write_context(Context),
> 		hlds_out__write_pred_id(Module, PredId),

> 		( { PredId = CallerPredId } ->
> 			io__write_string("as it contains a\n")

s/as/ as/

Please add test cases to exercise all these errors and warnings.

> 		;
> 			io__nl,
> 			prog_out__write_context(Context),
> 			io__write_string(
> 				"as it is in the same SCC as the following predicate\n"
> 				),

s/as/  as/

You need to indent the second and subsequent lines of error messages by
two spaces.

> 			prog_out__write_context(Context),
> 			hlds_out__write_pred_id(Module, CallerPredId),

Add
 			io__write_string("  "),

before the call to write_pred_id.

> 		prog_out__write_context(Context),
> 		( { PredId = CalledPredId } ->
> 			io__write_string("directly recursive call")
> 		;
> 			io__write_string("recursive call to"),
> 			hlds_out__write_pred_id(Module, CalledPredId),
> 			io__nl,
> 			prog_out__write_context(Context)
> 		),
> 		io__write_string("with the size of the variables increased.\n")

More buggy formatting.
Please fix it.

> 	;
> 		% only for hlds_dump
> 		{ CalledPPId = proc(CalledPredId, _CalledProcId) },
> 		{ CallerPPId = proc(CallerPredId, _CallerProcId) },
> 		prog_out__write_context(Context),
> 		io__write_string("this SCC contains a recursive call from "),
> 		hlds_out__write_pred_id(Module, CallerPredId),
> 		io__write_string("to "),
> 		hlds_out__write_pred_id(Module, CalledPredId),
> 		io__write_string("with the size of the variables increased.\n")
> 	).
> term_error__output(MaybePredId, Module, Context - horder_call) -->
> 	prog_out__write_context(Context),
> 	( { MaybePredId = yes(PredId) } ->
> 		hlds_out__write_pred_id(Module, PredId)
> 	;
> 		[]
> 	),
> 	io__write_string("horder_call\n").

Does that string ("horder_call") get output in messages that the user will see?
If so, you need to write it in English, not cryptic abbreviations.
Ditto for all the remaining clauses for this predicate.

I would like to see test cases that exercise all these errors and warnings.

-- 
Fergus Henderson <fjh at cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh at 128.250.37.3         |     -- the last words of T. S. Garp.



More information about the developers mailing list