[m-dev.] for review: retry across I/O

Fergus Henderson fjh at cs.mu.OZ.AU
Wed Nov 8 03:36:41 AEDT 2000


On 06-Nov-2000, Zoltan Somogyi <zs at cs.mu.OZ.AU> wrote:
>  eval_method_requires_ground_args(eval_normal) = no.
>  eval_method_requires_ground_args(eval_loop_check) = yes.
> +eval_method_requires_ground_args(eval_table_io) = yes.
>  eval_method_requires_ground_args(eval_memo) = yes.
>  eval_method_requires_ground_args(eval_minimal) = yes.

Hmm, the error message that you will get for violations of this will
be from report_eval_method_requires_ground_args in modes.m, which will
output the following message:

	foo.m:123: Sorry, not implemented: `pragma table_io'
	foo.m:123:   declaration not allowed for procedure with
	foo.m:123:   partially instantiated modes.

This is misleading since there's no `pragma table_io' declaration.

options.m:
> @@ -1678,6 +1684,9 @@
>  %		"\tSuppress the named aspects of the execution tracing system.",
>  		"--trace-optimized",
>  		"\tDo not disable optimizations that can change the trace.",
> +		"--trace-table-io",
> +		"\tEnable the tabling of I/O actions, to allow the debugger",
> +		"\tto execute retry commands across I/O actions.",

The log message said "These options are for developers only for now.",
so that documentation should be commented out.

You should put some documentation for the `--trace-table-io-states'
option in the comments here too.

> +++ compiler/table_gen.m	2000/11/06 03:19:29
> +% Example of transformation for tabling I/O, for I/O primitives that have
> +% tabled_for_io:

It would be helpful to explain here what it means for an I/O primitive to
"have tabled_for_io", and when this will happen.  Also it would help to
explain what you mean by "I/O primitive".

> +%	:- pred p(int, string, io__state, io__state).
> +%	:- mode p(in, out, di, uo) is det.
> +%
> +%	p(A, B, S0, S) :- $pragma(...)
> +%
> +%	The transformed code would be :
> +%
> +%	p(A, B, S0, S) :-
> +%		(if
> +%				% Get the global I/O table, the global I/O
> +%				% counter, and the starting point for tabling
> +%				% I/O actions, if we are in the tabled range.
> +%			table_io_in_range(T0, Counter, Start)
> +%		then
> +%				% Look up the input arguments.
> +%			impure table_lookup_insert_start_int(T0, Counter,
> +%				Start, T),
> +%			(if
> +%				semipure table_io_has_occurred(T)
> +%			then
> +%				impure table_restore_string_ans(T, 0, B)
> +%				impure table_restore_any_ans(T, 1, S)

Is the line immediately above only output if --trace-table-io-states
is enabled?  If so, it would help if you documented that here.

> +%			else
> +%				(
> +%					%
> +%					% Call original procedure
> +%					%
> +%				),
> +%					% Save the answers in the table.
> +%				impure table_io_create_ans_block(T, 2, AB),
> +%				impure table_save_string_ans(AB, 0, B)
> +%				impure table_save_any_ans(AB, 1, S)

Likewise for the line immediately above.

> +%			)
> +%		else
> +%			%
> +%			% Call original procedure
> +%			%
> +%		).
> +%
> +% For I/O primitives that have not_tabled_for_io, we should require that they
> +% do not do any I/O in their own code, meaning that all their I/O is inside
> +% the Mercury code they call. We can then leave such primitives untransformed;
> +% the I/O primitives called from the inner Mercury engine will do the right
> +% thing.

Again, it would be helpful to explain here what it means for an I/O primitive to
"have not_tabled_for_io", and when this can happen.  Is that the default?
If so, lots of stuff will break this requirement...

> @@ -250,27 +311,39 @@
>  	proc_info_goal(ProcInfo0, OrigGoal),
>  	proc_info_argmodes(ProcInfo0, ArgModes),
>  
> +	( EvalMethod = eval_table_io ->
> +		require(unify(CodeModel, model_det),
> +			"tabled io proc not det"),

The compiler front-end doesn't guarantee that invariant, I think.
So with this code the compiler might abort, even on valid input.

> +	list__filter(table_gen__var_is_io_state(Module, VarTypes), InputVars,
> +		IoStateAssignFromVars, _SavedInputVars),
> +	(
> +		TableIoStates = yes,
> +		RestoreAnsGoal = RestoreAnsGoal0
> +	;
> +		TableIoStates = no,
> +		(
> +			IoStateAssignFromVars = [IoStateAssignFromVarPrime],
> +			IoStateAssignToVars = [IoStateAssignToVarPrime]
> +		->
> +			IoStateAssignFromVar = IoStateAssignFromVarPrime,
> +			IoStateAssignToVar = IoStateAssignToVarPrime
> +		;
> +			error("create_new_io_goal: one in / one out violation")
> +		),

The compiler front-end doesn't guarantee that invariant, I think.

> @@ -683,6 +898,23 @@
...
> +:- pred table_gen__type_is_io_state(module_info::in, (type)::in) is semidet.
> +
> +table_gen__type_is_io_state(ModuleInfo, Type) :-
> +	type_to_type_id(Type, TypeCtor, []),
> +	type_util__type_id_module(ModuleInfo, TypeCtor, unqualified("io")),
> +	type_util__type_id_name(ModuleInfo, TypeCtor, "state"),
> +	type_util__type_id_arity(ModuleInfo, TypeCtor, 0).

Use type_util__type_is_io_state.

> Index: library/io.m
> @@ -1365,7 +1365,7 @@
>  
>  :- pragma c_code(io__read_line_as_string_2(File::in, Res :: out,
>  			RetString::out, IO0::di, IO::uo),
> -		[will_not_call_mercury, thread_safe],
> +		[will_not_call_mercury, tabled_for_io, thread_safe],
>  "
>  #define ML_IO_READ_LINE_GROW(n)	((n) * 3 / 2)
>  #define ML_IO_BYTES_TO_WORDS(n)	(((n) + sizeof(MR_Word) - 1) / sizeof(MR_Word))
> @@ -1511,7 +1511,7 @@
>  % same as ANSI C's clearerr().
>  
>  :- pragma c_code(io__clear_err(Stream::in, _IO0::di, _IO::uo),
> -		[will_not_call_mercury, thread_safe],
> +		[will_not_call_mercury, tabled_for_io, thread_safe],
>  "{
>  	MercuryFile *f = (MercuryFile *) Stream;
>  
... and so on for about 700 lines of diff.

The log message says

	library/io.m:
		Mark the relevant procedures with the tabled_for_io feature.

What are the criteria which determine which procedures are "relevant"
here? Are these criteria something that could be automatically applied
by the compiler?  If so, then I think doing it that way would be a
better approach, rather than manually marking them all as
`tabled_for_io'.  If not, then the criteria should be documented.

> Index: library/table_builtin.m
> ===================================================================
> RCS file: /home/mercury1/repository/mercury/library/table_builtin.m,v
> retrieving revision 1.2
> diff -u -b -r1.2 table_builtin.m
> --- library/table_builtin.m	2000/10/16 01:33:52	1.2
> +++ library/table_builtin.m	2000/11/01 08:16:00
> @@ -316,6 +316,112 @@
>  
>  :- interface.
>  
> +:- import_module io.
> +
> +:- impure pred table_io_in_range(ml_table::out, int::out, int::out) is semidet.
> +
> +:- impure pred table_io_has_occurred(ml_table::in) is semidet.
> +
> +:- pred table_io_copy_io_state(io__state::di, io__state::uo) is det.

You should document what those procedures are supposed to do.

Also please include a comment "N.B. interface continued below"
like in the other interface sections.

> +:- pragma c_code(table_io_copy_io_state(S0::di, S::uo),
> +		[will_not_call_mercury],
> +"
> +	S = S0;
> +").

What's the point of this procedure?
I notice now that table_gen generates calls to it,
but the documentation in table_gen doesn't say anything about it.

> Index: runtime/mercury_wrapper.c
> +
> +/*
> +** These variables are documented in library/table_builtin.m.
> +*/
> +
> +int		MR_io_tabling_phase = 0;
> +bool		MR_io_tabling_enabled = FALSE;
> +MR_TableNode	MR_io_tabling_pointer = { 0 };
> +MR_Unsigned	MR_io_tabling_counter = 0;
> +MR_Unsigned	MR_io_tabling_counter_hwm = 0;
> +MR_Unsigned	MR_io_tabling_start = 0;
> +MR_Unsigned	MR_io_tabling_end = 0;
>  
>  #ifdef USE_GCC_NONLOCAL_GOTOS
>  
> Index: runtime/mercury_wrapper.h
> ===================================================================
> RCS file: /home/mercury1/repository/mercury/runtime/mercury_wrapper.h,v
> retrieving revision 1.37
> diff -u -b -r1.37 mercury_wrapper.h
> --- runtime/mercury_wrapper.h	2000/11/05 12:04:19	1.37
> +++ runtime/mercury_wrapper.h	2000/11/06 01:49:57
> @@ -153,6 +153,14 @@
>  */
>  extern	void		(*MR_register_module_layout)(const MR_Module_Layout *);
>  
> +extern	int		MR_io_tabling_phase;
> +extern	bool		MR_io_tabling_enabled;
> +extern	MR_TableNode	MR_io_tabling_pointer;
> +extern	MR_Unsigned	MR_io_tabling_counter;
> +extern	MR_Unsigned	MR_io_tabling_counter_hwm;
> +extern	MR_Unsigned	MR_io_tabling_start;
> +extern	MR_Unsigned	MR_io_tabling_end;

Why are these defined in mercury_wrapper.{c,h}?
Wouldn't mercury_trace_base.{c,h} be a better place?

The pointer to the documentation in library/table_builtin.m
should be put in the header file too.

I think it would be worth including some brief comments
here explaining the meaning of each variable, in
addition to the documentation in library/table_builtin.m.
E.g.

	/*
	** This variable records which phase we're in:
	** Phase 0: program start          to first debugger event.
	** Phase 1: first debugger event   to io_tabling_start event.
	** Phase 2: io_tabling_start event to io_tabling_end event.
	** Phase 3: io_tabling_end event   to end of program.
	** See the comments in library/table_builtin.m for more details.
	*/
	extern	int		MR_io_tabling_phase;

	/* True if I/O tabling has been enabled */
	extern	bool		MR_io_tabling_enabled;

	/* The root of the trie that we use for tabling I/O */
	extern	MR_TableNode	MR_io_tabling_pointer;

	/* The current I/O state number */
	extern	MR_Unsigned	MR_io_tabling_counter;

	/* The highest I/O state number reached ("hwm" = "high water mark") */
	extern	MR_Unsigned	MR_io_tabling_counter_hwm;

	/* The event number at which to start I/O tabling */
	extern	MR_Unsigned	MR_io_tabling_start;

	/* The event number at which to stop I/O tabling */
	extern	MR_Unsigned	MR_io_tabling_end;

It might also help to do

	typedef MR_Unsigned MR_IO_StateNumber;
	typedef MR_Unsigned MR_EventNumber;

and to use those typedefs in appropriate places.
And it might also be worth using a enum for the phase.

> Index: tests/debugger/tabled_read.inp
> ===================================================================
> RCS file: tabled_read.inp
> diff -N tabled_read.inp
> --- /dev/null	Thu Sep  2 15:00:04 1999
> +++ tabled_read.inp	Tue Oct 31 10:10:16 2000
> @@ -0,0 +1,13 @@
> +echo on
> +register --quiet
> +break tabled_read__test
> +table_io start
> +continue
> +finish -n
> +print *
> +table_io
> +retry
> +print *
> +finish -n
> +print *
> +continue -S

Is there currently a way to turn tabled I/O off?
If so, it would be a good idea to test that.

> Index: tests/debugger/tabled_read.m
> ===================================================================
> RCS file: tabled_read.m
> diff -N tabled_read.m
> --- /dev/null	Thu Sep  2 15:00:04 1999
> +++ tabled_read.m	Wed Oct 25 16:49:30 2000
...
> +% We define our own version of io__read_char_code, in case the library
> +% was compiled without IO tabling.
> +
> +:- pred tabled_read__read_char_code(io__input_stream::in, int::out,
> +	io__state::di, io__state::uo) is det.
> +
> +:- pragma c_code(tabled_read__read_char_code(File::in, CharCode::out,
> +		IO0::di, IO::uo), [will_not_call_mercury], "

Don't you need `tabled_for_io' here?
Why/why not?

> +	extern	int	mercury_getc(MercuryFile* mf);
> +	CharCode = mercury_getc((MercuryFile *) File);
> +	IO = IO0;

It would be better to just use getchar() rather than mercury_getc().
That will ensure that this test case is robust against changes
to the Mercury stream implementation.

> +% % We define our own version of io__write_string, in case the library
> +% % was compiled without IO tabling.
> +% 
> +% :- pragma c_code(tabled_read__write_string(Stream::in, Message::in,
> +% 		IO0::di, IO::uo), [may_call_mercury, thread_safe],
> +% "{
> +% 	MercuryFile *stream = (MercuryFile *) Stream;
> +% 	mercury_print_string(stream, Message);
> +% 	update_io(IO0, IO);
> +% }").

The same two points apply here (with s/getchar/printf/
and s/mercury_getc/mercury_print_string).

> Index: trace/mercury_trace.c
...
> +static MR_Unsigned
> +MR_find_saved_io_counter(const MR_Stack_Layout_Label *call_label,
> +	MR_Word *base_sp, MR_Word *base_curfr)
> +{
> +	const MR_Stack_Layout_Entry	*level_layout;
> +	MR_Unsigned			saved_io_counter;
> +
> +	level_layout = call_label->MR_sll_entry;
> +	if (level_layout->MR_sle_maybe_io_seq <= 0) {
> +		MR_fatal_error("MR_trace_retry: "
> +			"missing io seq number slot");
> +	}
> +
> +	if (MR_DETISM_DET_STACK(level_layout->MR_sle_detism)) {
> +		saved_io_counter = MR_based_stackvar(base_sp,
> +			level_layout->MR_sle_maybe_io_seq);
> +	} else {
> +		saved_io_counter = MR_based_framevar(base_curfr,
> +			level_layout->MR_sle_maybe_io_seq);
> +	}

It would be cleaner if some of that was abstracted out into a macro
(or function), i.e.

	#define MR_based_slot(layout, base_sp, base_curfr, slot)	\
		( MR_DETISM_DET_STACK((layout)->MR_sle_detism) ?	\
			MR_based_stackvar((base_sp), (slot))		\
		:							\
			MR_based_framevar((base_curfr), (slot))		\
		)							\

or perhaps

	#define MR_based_slot(detism, base_sp, base_curfr, slot)	\
		( MR_DETISM_DET_STACK(detism) ?				\
			MR_based_stackvar((base_sp), (slot))		\
		:							\
			MR_based_framevar((base_curfr), (slot))		\
		)							\

This is also used once in runtime/mercury_stack_trace.c.

> +++ trace/mercury_trace_internal.c	2000/11/06 03:47:17
> @@ -2003,6 +2077,23 @@
...
> +static void
> +MR_print_unsigned_var(FILE *fp, const char *var, MR_Unsigned value)
> +{
> +	fprintf(fp, "%s = ", var);
> +	MR_print_unsigned(fp, value);
> +	fprintf(fp, "\n");
> +}
> +
> +static void
> +MR_print_unsigned(FILE *fp, MR_Unsigned value)
> +{
> +	char	buf[20];
> +
> +	sprintf(buf, "%%%su", MR_INTEGER_LENGTH_MODIFIER);
> +	fprintf(fp, buf, value);
> +}

A simpler method is to use ANSI/ISO C string concatenation:

	static void
	MR_print_unsigned_var(FILE *fp, const char *var, MR_Unsigned value)
	{
		fprintf(fp, "%s = %" MR_INTEGER_LENGTH_MODIFIER "u\n",
			var, value);
	}

----------

That's it for this review.

-- 
Fergus Henderson <fjh at cs.mu.oz.au>  |  "I have always known that the pursuit
                                    |  of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh>  |     -- the last words of T. S. Garp.
--------------------------------------------------------------------------
mercury-developers mailing list
Post messages to:       mercury-developers at cs.mu.oz.au
Administrative Queries: owner-mercury-developers at cs.mu.oz.au
Subscriptions:          mercury-developers-request at cs.mu.oz.au
--------------------------------------------------------------------------



More information about the developers mailing list