[m-dev.] Diff: Make Mercury cope with impure code (part 2/2)

Fergus Henderson fjh at cs.mu.oz.au
Sat Nov 29 00:00:16 AEDT 1997


Peter Schachte wrote:

reference_manual.texi:
>  There is another operational semantics of Mercury programs called
>  the @dfn{strict commutative} operational semantics.  This semantics
> -is equivalent to the strict sequential operation semantics except
> +is equivalent to the strict sequential operational semantics except
>  that there is no requirement that function calls, conjunctions and disjunctions 
>  be executed left-to-right; they may be executed in any order.
> + at c XXX May they be interleaved?
>  (The order may even be different each time a particular goal
>  is entered.)

Yes, they can be interleaved.
Please add that.

> + at node Impurity
> + at section Impurity declarations
> +
> +In order to efficiently implement certain predicates, it is sometimes
> +necessary to venture outside pure logic programming.

I suggest s/sometimes/occaisionally/

> +Other predicates
> +cannot be implemented at all within the paradigm of logic programming,
> +for example, all solutions predicates.  Such predicates are often
> +written using the C interface.  Sometimes, however, it would be more
> +convenient, or more efficient, to write such predicates using the
> +facilities of Mercury.  For example, it is much more convenient to
> +access arguments of compound Mercury terms in Mercury than in C, and the
> +ability of the Mercury compiler to specialize code can make higher-order
> +predicates written in Mercury significantly more efficient than similar
> +C code.
> +
> +One important aim of Mercury's impurity system is to make the
> +distinction between the pure and impure code very clear.  This is done
> +by requiring every impure predicate to be so declared, and by requiring
> +every call to an impure predicate to be flagged as such.  Predicates
> +that are implemented in terms of impure predictes are assumed to be
> +impure themselves unless they are explicitly promised to be pure.

I suggest you add the following paragraph here:

	Note that the facilities described here are needed only very rarely.
	The main intent is for implementing language primitives such as
	the all solutions predicates.  Any use of `impure' or `semipure'
	probably indicates either a weakness in the Mercury standard library,
	or the programmer's lack of familiarity with the standard library.
	Newcomers to Mercury are hence encouraged to
	@strong{skip this section}.

> +Most Mercury predicates are pure.  

I suggest s/Most/The vast majority of/

> +The operational semantics of a Mercury predicate which invokes impure
> +code is a modified form of the @emph{strict sequential} semantics
> +(@pxref{Semantics}).  Firstly, only impure goals may not be reordered
> +relative to other goals, pure and semipure goals may be reordered as
> +long as they are not moved across an impure goal.

I think it would be clearer if you delete the word "only", and
change the last comma to a semi-colon.

> Secondly, not even
> +``minimal'' reordering of impure goals is permitted; if such reordering
> +is needed, this is an error.

I would change that to

	Secondly, for impure goals not even ``minimal'' reordering
	as implied by the modes is permitted; if any such reordering
        is needed, this is a mode error.

And maybe delete the "Firstly, " and "Secondly, ".

> +A predicate is declared to be impure (semipure) by preceding the word
> + at code{predicate} in its @code{pred} declaration with @code{impure}
> +(@code{semipure}).  That is, a declaration of the form:

I think you should make the following changes:

	s/impure (semipure)/impure or semipure/
	s/@code{impure} (@code{semipure})/impure or semipure respectively/

and ditto elsewhere.

> + at node Impure calls
> + at subsection Marking a call as impure
> +
> +If a predicate is impure (semipure), all calls to it must be preceded
> +with the word @code{impure} (@code{semipure}).  This allows someone
> +reading the code to tell which goals are not pure, making code which
> +relies on side effects somewhat less mysterious.

and (more importantly) ensuring that if a call is @emph{not} preceded by
@code{impure} or @code{semipure}, then the reader can rely on
the call having a proper declarative semantics, without hidden
side-effects.


>See @ref{Impurity
> +Example} for an example of this.

Put this before the previous sentence, otherwise the referent of
"this" gets lost with the text I've added.

> Note that only predicate calls need to
> +be prefixed with @code{impure} or @code{semipure}, compound goals never
> +need this.
> +
> +
> + at node Promising purity
> + at subsection Promising a predicate is pure
> +
> +Some predicates which call impure or semipure predicates are themselves
> +pure.  In fact, the main purpose of the Mercury impurity system is to
> +allow users to write pure predicates using impure ones, while protecting
> +the procedural implementation from aggressive compiler optimizations.
> +Of course, the Mercury compiler cannot verify that a predicate is pure,
> +so this is the user's responsibility.
> +
> +The user may promise that a predicate is pure using the
> + at code{promise_pure} pragma:

s/user/programmer/g

This is typically supposed to be used only by _implementors_, not users.

> + at node Impurity Example
> + at subsection An example using impurity
[...]
> + at example
> +:- pragma c_header_code("int max;").

s/int/Integer/

> +:- impure pred init_max is det.
> +:- pragma c_code(init_max,
> +        will_not_call_mercury,
> +        "max = (int)(~(((unsigned)(~0))>>1));").

As I said last time: why not "max = INT_MIN" instead?
(You'd also need `:- pragma c_header_code("#include <limits.h>")'.)

> +++ transition_guide.texi	1997/10/01 00:53:09
> @@ -154,6 +154,7 @@
>  end_module      fx              1199
>  if              fx              1160
>  import_module   fx              1199
> +impure          fy              1199
>  inst            fx              1199
>  is              xfx             700
>  mod             xfx             300
> @@ -163,6 +164,7 @@
>  or              xfy             740
>  pred            fx              1180
>  rule            fx              1199
> +semipure        fy              1199
>  some            fxy             950
>  then            xfx             1150
>  type            fx              1180

You should also change the precedence of `pred' and `func'.

> %  XXX The current implementation doesn't handle impure functions.  The main
> %      reason is that handling of nested functions is likely to get pretty 
> %      confusing.  Because impure functions can't be reordered, the execution
> %      order would have to be strictly innermost-first, left-to-right, and 
> %      predicate arguments would always have to be evaluated before the
> %      predicate call.  Implied modes are right out.  All in all, they just
> %      won't be as natural as one might think at first.

True, but semipure functions might be quite desirable...

How much extra work would it be to implement impure/semipure functions?

purity.m:
> :- implementation.
> 
> :- import_module make_hlds, hlds_data, hlds_pred, hlds_goal, prog_io_util.
> :- import_module type_util, mode_util, code_util, prog_data, unify_proc.
> :- import_module globals, options, mercury_to_mercury, hlds_out, int, set.
> :- import_module passes_aux, typecheck, module_qual, clause_to_proc.
> :- import_module modecheck_unify, modecheck_call, inst_util.
> :- import_module list, map, varset, term, prog_out, string, require, std_util.
> :- import_module assoc_list, bool.

Please keep the library modules and compiler modules in separate lists.
`prog_out', `int', and `set' are in the wrong list.

> %			Check purity of a single predicate
> %
> %  Purity checking is quite simple.  Since impurity /must/ be declared, we can
> %  perform a single pass checking that the actual purity of each predicate
> %  matches the declared (or implied) purity.  A predicate is just as pure as
> %  its least pure goal.  While we're doing this, we attach a `feature' to each
> %  goal that is not pure, including non-atomic goals, indicating its purity.
> %  This information must be maintained by later compilation passes, at least
> %  until after the last pass that may perform transformations that would not
> %  be correct for impure code.

I suspect that you have just introduced a significant number of bugs :-(

Any ideas on how we can verify that the existing compiler passes
preserve the purity feature in goal_infos?

> As we check purity and attach impurity
> %  features, we also check that impure (semipure) atomic goals were marked in
> %  the source code as impure (semipure).  At this stage in the computation,
> %  this is indicated by already having the appropriate goal feature.  (During
> %  the translation from term to goal, calls have their purity attached to
> %  them, and in the translation from goal to hlds_goal, the attached purity is
> %  turned into the appropriate feature in the hlds_goal_info.

You're missing a ')'.

> compute_expr_purity(Unif0, Unif, GoalInfo, PredInfo, ModuleInfo, InClosure,
> 		pure, NumErrors0, NumErrors) -->
> 	{ Unif0 = unify(A,RHS0,C,D,E) },
> 	{ Unif  = unify(A,RHS,C,D,E) },
> 	error_if_body_purity_indicated(GoalInfo, NumErrors0, NumErrors1,
> 				       InClosure, "unification"),
> 	(   { RHS0 = lambda_goal(F, G, H, I, Goal0-Info0) } ->
> 		{ RHS = lambda_goal(F, G, H, I, Goal-Info0) },

Please s/-/ - /g

> compute_expr_purity(Ccode, Ccode, _, _, _, _, pure, NumErrors, NumErrors) -->
> 	{ Ccode = pragma_c_code(_,_,_,_,_,_,_,_) }.

Hmm, what if the pragma_c_code is for an impure pred?
That won't happen currently, because we don't inline pragma_c_codes until
_after_ purity checking, but I think it might be more robust to check it.
You just need to look up the pred_info for the pred (the pred_id is
one of the fields of `pragma_c_code(...)' HLDS goals).

> :- pred error_inconsistent_promise(module_info, pred_info, pred_id, purity,
> 				  io__state, io__state).
> :- mode error_inconsistent_promise(in, in, in, in, di, uo) is det.
> 
> error_inconsistent_promise(ModuleInfo, PredInfo, PredId, Purity) -->
> 	{ pred_info_context(PredInfo, Context) },
> 	write_context_and_pred_id(ModuleInfo, PredInfo, PredId),
> 	prog_out__write_context(Context),
> 	report_warning("  warning: declared "),
> 	write_purity(Purity),
> 	io__write_string(" but promised pure.\n"),

The message will probably be clearer if you insert some quotes:

 	report_warning("  warning: declared `"),
 	write_purity(Purity),
 	io__write_string("' but promised `pure'.\n"),

> warn_exaggerated_impurity_decl(ModuleInfo, PredInfo, PredId,
> 		DeclPurity, AcutalPurity) -->
> 	{ pred_info_context(PredInfo, Context) },
> 	write_context_and_pred_id(ModuleInfo, PredInfo, PredId),
> 	prog_out__write_context(Context),
> 	report_warning("  warning: declared "),
> 	write_purity(DeclPurity),
> 	io__write_string(" but actually "),
> 	write_purity(AcutalPurity),
> 	io__write_string(".\n").

Ditto.

> :- pred warn_unnecessary_promise_pure(module_info, pred_info, pred_id,
> 				  io__state, io__state).
> :- mode warn_unnecessary_promise_pure(in, in, in, di, uo) is det.
> 
> warn_unnecessary_promise_pure(ModuleInfo, PredInfo, PredId) -->
> 	{ pred_info_context(PredInfo, Context) },
> 	write_context_and_pred_id(ModuleInfo, PredInfo, PredId),
> 	prog_out__write_context(Context),
> 	report_warning("  warning: unnecessary `promise_pure' pragma.\n"),
> 	globals__io_lookup_bool_option(verbose_errors, VerboseErrors),
> 	( { VerboseErrors = yes } ->
> 		prog_out__write_context(Context),
> 		{ pred_info_get_is_pred_or_func(PredInfo, PredOrFunc) },
> 		io__write_string("  This "),
> 		hlds_out__write_pred_or_func(PredOrFunc),
> 		io__write_string(
> 		    " does not invoke any non-pure code,\n"
> 		),

"non-pure" is not very nice English, IMHO.
I suggest "impure or semipure" instead.

(Ditto in a couple of other places.  Do a grep for "non-pure".)

> New File: tests/hard_coded/purity.m
> ===================================================================
> % Various checks that impurity declarations are treated properly.
> 
> :- module purity.
> :- interface.
> :- import_module io.
> :- impure pred main(io__state::di, io__state::uo) is det.

I'm still not sure whether we should not allow main/2 to be impure.
One argument against allowing main/2 to be impure is that it would
make it a little bit harder for an implementation to use a different
calling convention for impure goals.

Oh, and one more thing -- can you add a test case for inlining of impure goals?

-- 
Fergus Henderson <fjh at cs.mu.oz.au>   WWW: <http://www.cs.mu.oz.au/~fjh>  
Note: due to some buggy software and a (probably accidental)
denial-of-service attack, any mail sent to me between
	Tue Nov 25 20:00:00 UTC (6am Wed, local time)
and	Wed Nov 26 06:00:00 UTC (4pm, local time)
may have gone into the bit-bucket.  Please re-send it.



More information about the developers mailing list