[m-dev.] implementation of `freeze' (coroutining)

Fergus Henderson fjh at cs.mu.oz.au
Thu Sep 18 19:08:30 AEST 1997


Fergus Henderson, you wrote:
> 
> Here's an implementation of freeze/2 in Mercury,
> and a coroutining Mercury version of N-queens that uses it.

Sorry, that mail was incomplete (someone was rebooting mubboon, so I just
shot it off without finishing it).  Let me try again.

Here's the following:

	var.m: Prolog-style variables + freeze/2

	unsafe.m: unsafe_perform_io/1, setarg/3, and unsafe_cast_to_ground/1
		(these are used by var.m)

	vqueens.m: a coroutining N-queens problem implemented using var.m.

I plan to put the first two in extras/trailed_update,
and to put vqueens.m in a new subdirectory extras/trailed_update/samples.

TODO:
	- modify freeze/2 so that we detect floundering
	- implement var:'=='/2
	- implement freeze/3
	- improve documentation

%-----------------------------------------------------------------------------%

:- module var.
:- interface.

:- type var(T).

	% `init(Var)' can be used to initialize
	% the inst of a variable to `any'.
:- pred init(var(T)::out(any)) is det.

	% `Var = var(Value)' unifies a variable with its value.
	% This can be used in several ways:
	% to bind a variable to a particular value, `X = var(42)';
	% to extract the value of that variable, `X = var(Y)';
	% or (NYI) to initialize the inst of a variable to any, `X = var(_)'.
:- func var(T) = var(T).
:- mode var(in) = out is det.
:- mode var(in) = in(any) is semidet.
:- mode var(in) = in is semidet.
:- mode var(out) = in is det.

	% `Var1 == Var2' can be used to unify two variables.
:- pred var(T) == var(T).
:- mode in == in is semidet.
:- mode in == out is det.
:- mode out == in is det.
:- mode in(any) == in(any) is semidet.
:- mode in(any) == out(any) is det.
:- mode out(any) == out(any) is det.
:- mode out(any) == in(any) is det.

	% `freeze(Var, Pred)' can be used to delay execution of a goal
	% until a variable is ground.
	% Often the freeze/3 version is more useful, though,
	% since this version doesn't allow `Pred' to have any outputs.
	% (XXX the compiler doesn't check that yet - this is a bug!)
	% Declaratively, freeze(Var, Pred) is true iff Pred(Var) is true.
	% Operationally, freeze(Var, Pred) delays until Var becomes ground
	% and then calls Pred(Var).
:- pred freeze(var(T), pred(T)).
:- mode freeze(in(any), pred(in) is semidet) is semidet.
:- mode freeze(out(any), pred(in) is semidet) is semidet.

	% `debug_freeze(Message, Var, Pred)'
	% is the same as `freeze(Var, Pred)' except
	% that it also prints out some debugging information.
	% IMPORTANT: this is a non-logical hack, use only for debugging!
:- pred debug_freeze(string, var(T), pred(T)).
:- mode debug_freeze(in, in(any), pred(in) is semidet) is semidet.

/*****
NOT YET IMPLEMENTED -- the implementation requires existential types.
	% `freeze(Var1, Pred, Var2)' can be used to delay
	% execution of a goal until a variable is ground.
	% This version is more flexible than freeze/2, since
	% it allows the delayed goal to have an output.
	% Declaratively, freeze(X, Pred, Y) is true iff Pred(X, Y) is true.
	% Operationally, freeze(X, Pred, Y) delays until X becomes ground
	% and then calls Pred(X, Y).
:- pred freeze(var(T1), pred(T1, T2), var(T2)).
:- mode freeze(in(any), pred(in, out) is semidet, in(any)) is semidet.
:- mode freeze(out(any), pred(in, out) is det, out(any)) is semidet.

******/

%---------------------------------------------------------------------------%
:- implementation.
:- import_module list, std_util.
:- import_module unsafe, io.

%---------------------------------------------------------------------------%
%
% The implementation is mostly written in Mercury, although we do use
% setarg/3 from the `unsafe' module.  The C interface is used only
% as a means of providing different implementations for different
% modes of a predicate and for doing unchecked type/inst casts to
% cast values of type and inst `var(T)::any' to `var_rep(T)::ground'
% and back again -- that is, to convert from variables to their concrete
% representation and back.
%
%---------------------------------------------------------------------------%

:- type var(T) ---> var_rep(var_rep(T))
	where equality is (==).

:- type var_rep(T)
	--->	free
	;	free(list(pred(T)))	% list of delayed goals
	;	alias(var_rep(T))
	;	ground(T).

:- inst var_rep_any =
	bound(	free
	;	free(list_skel(pred(in) is semidet))
	;	alias(var_rep_any)
	;	ground(ground)
	).
:- inst var_rep_ground =
	bound(	alias(var_rep_ground)
	;	ground(ground)
	).
:- inst var_rep_deref =
	bound(	free
	;	free(list_skel(pred(in) is semidet))
	;	ground(ground)
	).
:- inst ptr(I) = bound(alias(I)).
:- inst uniq_ptr(I) = unique(alias(I)).

%-----------------------------------------------------------------------------%

:- pragma c_code(init(Var::out(any)), may_call_mercury, "
	Var = ML_var_alias(TypeInfo_for_T, ML_var_free(TypeInfo_for_T));
").

/*
% The compiler generates wrong code for this -- the output is not unique.
% It puts the `alias(free)' term in read-only memory.  Hence, to avoid this,
% we use separate calls to functions for alias/1 and free/0.
:- pred var__rep_init(var_rep(T)::out(uniq_ptr(var_rep_any))) is det.
:- pragma export(var__rep_init(out(uniq_ptr(var_rep_any))), "ML_var_init").
var__rep_init(alias(free)).
*/

:- func var__rep_free = (var_rep(T)::out(var_rep_any)) is det.
:- pragma export(var__rep_free = out(var_rep_any), "ML_var_free").
var__rep_free = free.

:- func var__rep_alias(var_rep(T)::in(var_rep_any)) =
		(var_rep(T)::out(var_rep_any)) is det.
:- pragma export(var__rep_alias(in(var_rep_any)) = out(var_rep_any),
		"ML_var_alias").
var__rep_alias(T) = alias(T).

%-----------------------------------------------------------------------------%

/****
:- pragma c_code( var(Value::(free -> clobbered_any)) = (Var::out(any)), % det
	may_call_mercury,
"
	* Value unused *
	ML_var_init(&Var);
").
****/

:- pragma c_code( var(Value::in) = (Var::out) /* det */,
	may_call_mercury,
"
	ML_var_init_with_value(TypeInfo_for_T, Value, &Var);
").

:- pred var__rep_init_with_value(T::in, var_rep(T)::out(var_rep_ground)) is det.
:- pragma export(var__rep_init_with_value(in, out(var_rep_ground)),
	"ML_var_init_with_value").
var__rep_init_with_value(Value, ground(Value)).

:- pragma c_code( var(Value::out) = (Var::in) /* det */, may_call_mercury,
"
	ML_var_get_value(TypeInfo_for_T, Var, &Value);
").

:- pred var__rep_get_value(var_rep(T)::in(var_rep_ground), T::out) is det.
:- pragma export(var__rep_get_value(in(var_rep_ground), out),
	"ML_var_get_value").
var__rep_get_value(ground(Value), Value).
var__rep_get_value(alias(Var), Value) :-
	var__rep_get_value(Var, Value).

:- pragma c_code( var(Value::in) = (Var::in) /* semidet */,
	may_call_mercury,
"
	SUCCESS_INDICATOR = ML_var_test_value(TypeInfo_for_T, Var, Value);
").

:- pred var__rep_test_value(var_rep(T)::in(var_rep_ground), T::in) is semidet.
:- pragma export(var__rep_test_value(in(var_rep_ground), in),
	"ML_var_test_value").
var__rep_test_value(Var, Value) :-
	var__rep_get_value(Var, VarValue),
	Value = VarValue.

:- pragma c_code( var(Value::in) = (Var::in(any)) /* semidet */,
	may_call_mercury,
"
	SUCCESS_INDICATOR = ML_var_unify_with_val(TypeInfo_for_T, Value, Var);
").

:- pred var__rep_unify_with_val(T, var_rep(T)).
:- mode var__rep_unify_with_val(in, in(ptr(var_rep_any))) is semidet.
:- pragma export(var__rep_unify_with_val(in, in(ptr(var_rep_any))),
	"ML_var_unify_with_val").
var__rep_unify_with_val(Value, Var) :-
	deref_var(Var, DereffedVar),
	DereffedVar = alias(OldVarRep),
	( OldVarRep = ground(OldValue) ->
		Value = OldValue
	;
		var__rep_bind_var(OldVarRep, Value, NewVarRep),
		destructively_update_binding(DereffedVar, NewVarRep)
	).

:- pred var__rep_bind_var(var_rep(T)::in(var_rep_any), T::in,
				var_rep(T)::out(var_rep_ground)) is semidet.
var__rep_bind_var(free, Value, ground(Value)).
var__rep_bind_var(free(DelayedGoals), Value, ground(Value)) :-
	call_delayed_goals(DelayedGoals, Value).

%-----------------------------------------------------------------------------%

:- pred call_delayed_goals(list(pred(T)), T).
:- mode call_delayed_goals(in(list_skel(pred(in) is semidet)), in) is semidet.

call_delayed_goals([], _).
call_delayed_goals([Goal|Goals], Value) :-
	call_delayed_goals(Goals, Value),
	call(Goal, Value).

%-----------------------------------------------------------------------------%

	% Beware, the implementation of this is rather unsafe,
	% if the compiler does strange things... 
	% might be better to just implement it in C.
:- pred deref_var(var_rep(T), var_rep(T)).
:- mode deref_var(in(ptr(var_rep_any)), out(ptr(var_rep_any))).
			       % really out(ptr(var_rep_deref)).
deref_var(Var, DerefVar) :-
	Var = alias(Deref1),
	(if Deref1 = alias(_) then
		deref_var(Deref1, DerefVar)
	else
		DerefVar = Var
	).

%-----------------------------------------------------------------------------%

/*
:- pred freeze(var(T), pred(T)).
:- mode freeze(in(any), pred(in) is semidet) is semidet.
:- mode freeze(out(any), pred(in) is semidet) is semidet.
*/

:- pragma c_code(
	freeze(Var::in(any), Pred::(pred(in) is semidet)) /* semidet */,
	may_call_mercury,
"
	ML_var_freeze_in(TypeInfo_for_T, Var, Pred);
").
:- pragma c_code(
	freeze(Var::out(any), Pred::(pred(in) is semidet)) /* semidet */,
	may_call_mercury,
"
	ML_var_freeze_out(TypeInfo_for_T, &Var, Pred);
").


:- pred var__rep_freeze_out(var_rep(T), pred(T)).
:- mode var__rep_freeze_out(out(ptr(var_rep_any)), pred(in) is semidet)
	is semidet.
:- pragma export(
	var__rep_freeze_out(out(ptr(var_rep_any)), pred(in) is semidet),
	"ML_var_freeze_out").

var__rep_freeze_out(Var, Pred) :-
	Var = alias(free([Pred])).

:- pred var__rep_freeze_in(var_rep(T), pred(T)).
:- mode var__rep_freeze_in(in(ptr(var_rep_any)), pred(in) is semidet) is semidet.
:- pragma export(
	var__rep_freeze_in(in(ptr(var_rep_any)), pred(in) is semidet),
	"ML_var_freeze_in").

var__rep_freeze_in(Var, Pred) :-
	deref_var(Var, DereffedVar),
	DereffedVar = alias(OldRep),
	( OldRep = ground(Value) ->
		call(Pred, Value)
	;
		( OldRep = free(OldPreds) ->
			NewPreds = [Pred | OldPreds]
		;
			NewPreds = [Pred]
		),
		NewRep = free(NewPreds),
		/* impure */ destructively_update_binding(DereffedVar, NewRep)
	).

%-----------------------------------------------------------------------------%

:- import_module require.
_ == _ :-
	error("var:'==' not yet implemented").

%-----------------------------------------------------------------------------%

/* impure */
:- pred destructively_update_binding(var_rep(T), var_rep(T)).
:- mode destructively_update_binding(in(ptr(var_rep_any)), in(var_rep_any))
	is det.

destructively_update_binding(DereffedVar, NewBinding) :-
	setarg(DereffedVar, 1, NewBinding).

%-----------------------------------------------------------------------------%

debug_freeze(Msg, Var, Pred) :-
	unsafe_perform_io(print("freezing: ")),
	unsafe_perform_io(print(Msg)),
	unsafe_perform_io(print(": ")),
	unsafe_perform_io(print_any(Var)),
	unsafe_perform_io(nl),
	( freeze(Var, debug_pred(Msg, Pred)) ->
		unsafe_perform_io(print("frozen: ")),
		unsafe_perform_io(print(Msg)),
		unsafe_perform_io(print(": ")),
		unsafe_perform_io(print_any(Var)),
		unsafe_perform_io(nl)
	;
		unsafe_perform_io(print("freeze failed: ")),
		unsafe_perform_io(print(Msg)),
		unsafe_perform_io(print(": ")),
		unsafe_perform_io(print_any(Var)),
		unsafe_perform_io(nl),
		semidet_fail
	).


:- pred debug_pred(string::in, pred(T)::(pred(in) is semidet), T::in)
	is semidet.

debug_pred(Msg, Pred, Var) :-
	unsafe_perform_io(print("woke: ")),
	unsafe_perform_io(print(Msg)),
	unsafe_perform_io(print(": ")),
	unsafe_perform_io(print(Var)),
	unsafe_perform_io(nl),
	( call(Pred, Var) ->
		unsafe_perform_io(print("succeeded: ")),
		unsafe_perform_io(print(Msg)),
		unsafe_perform_io(print(": ")),
		unsafe_perform_io(print(Var)),
		unsafe_perform_io(nl)
	;
		unsafe_perform_io(print("failed: ")),
		unsafe_perform_io(print(Msg)),
		unsafe_perform_io(print(": ")),
		unsafe_perform_io(print(Var)),
		unsafe_perform_io(nl),
		semidet_fail
	).

:- pred print_any(T::in(any), io__state::di, io__state::uo) is det.
print_any(X) -->
	print(unsafe_cast_to_ground(X)).

%-----------------------------------------------------------------------------%

%-----------------------------------------------------------------------------%
:- module unsafe.
%-----------------------------------------------------------------------------%

/*
** WARNING: the procedures defined in this module are non-logical.
**          They may have side effects, they may violate type safety,
**	    they may interfere with certain memory management strategies,
**	    and in general they may do lots of nasty things.
**	    They may not work with future release of the Mercury compiler,
**	    or with other Mercury implementations.
**          Use only as a last resort, and only with great care!
**
** You have been warned.
*/

%-----------------------------------------------------------------------------%

:- interface.
:- import_module io.

/*
** unsafe_perform_io/1 performs I/O, in an unsafe manner.
** It can be used to call a goal that does I/O or has
** side effects from a context where you do not have an io__state.
** It can be useful for printf-style debugging.
** But backtracking over a call to `unsafe_perform_io'
** can be very dangerous indeed, because with certain
** memory allocation policies it can result in dangling pointers.
*/
:- pred unsafe_perform_io(pred(io__state, io__state)).
:- mode unsafe_perform_io(pred(di, uo) is det) is det.
:- mode unsafe_perform_io(pred(di, uo) is cc_multi) is det.

/*
** setarg/3 provides non-logical backtrackable destructive update.
** `setarg(Term, N, Value)' destructively modifies the Nth
** argument of `Term' to be `Value'.  The modification will be undone
** on backtracking.
**
** WARNING: setarg/3 uses side-effects and is not type-safe!
**          Also it does not work for types with exactly one
**	    functor that has exactly one arg.
**	    It may not work with future release of the Mercury compiler,
**	    or with other Mercury implementations.
**          Use only with great care!
*/
:- pred setarg(T1, int, T2).
:- mode setarg(in(any), in, in(any)) is det.

/*
** The function unsafe_cast_to_ground/1 can be used to assert to the
** compiler that a particular value of inst `any' is in fact ground.
** The assertion is *not* checked.  If it is false, all hell will break out.
*/
:- func unsafe_cast_to_ground(T::in(any)) = (T::out) is det.

%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%

:- implementation.

%-----------------------------------------------------------------------------%

:- pragma c_code(unsafe_cast_to_ground(X::in(any)) = (Y::out), "Y = X;").

%-----------------------------------------------------------------------------%

:- pragma c_code(
unsafe_perform_io(P::(pred(di, uo) is det)),
	may_call_mercury,
"{
	Word dummy_io_state = 0;
	call_io_pred_det(P, dummy_io_state, &dummy_io_state);
}").
:- pragma c_code(
unsafe_perform_io(P::(pred(di, uo) is cc_multi)),
	may_call_mercury,
"{
	Word dummy_io_state = 0;
	call_io_pred_cc_multi(P, dummy_io_state, &dummy_io_state);
}").

:- pred call_io_pred(pred(io__state, io__state), io__state, io__state).
:- mode call_io_pred(pred(di, uo) is det, di, uo) is det.
:- mode call_io_pred(pred(di, uo) is cc_multi, di, uo) is cc_multi.

:- pragma export(call_io_pred(pred(di, uo) is det, di, uo),
		"call_io_pred_det").
:- pragma export(call_io_pred(pred(di, uo) is cc_multi, di, uo),
		"call_io_pred_cc_multi").

call_io_pred(P) --> P.

%-----------------------------------------------------------------------------%

:- pragma c_header_code("

/* XXX should change the name in runtime/mercury_trail.h */
#ifndef MR_trail_current_value
  #define MR_trail_current_value(addr) MR_trail_value_at_address((addr))
#endif

/* XXX this should go in runtime/tags.h */
#ifndef strip_tag
  #ifdef HIGH_TAGS
    #define strip_tag(w)	((w) & ((~(Word)0) >> TAGBITS))
  #else
    #define strip_tag(w)	((w) & ((~(Word)0) << TAGBITS))
  #endif
#endif

").

:- pragma c_code(
	setarg(MercuryTerm::in(any), ArgNum::in, NewValue::in(any)),
	will_not_call_mercury,
"{
	Word *ptr = (Word *) strip_tag(MercuryTerm); /* strip off tag bits */
	MR_trail_current_value(&ptr[ArgNum - 1]);
	ptr[ArgNum - 1] = NewValue;
}").

%-----------------------------------------------------------------------------%

% 9-queens program -- using the `var' module and freeze/2.

:- module queens.

:- interface.

:- import_module list, int, io.

:- pred main1(list(int)).
:- mode main1(out) is nondet.

:- pred main(io__state, io__state).
:- mode main(di, uo) is cc_multi.

:- implementation.
:- import_module var, unsafe, std_util.

main1(Out) :-	
	data(Data),
	queen(Data, Out).

main -->
	( { data(Data), queen(Data, Out) } ->
		print(Out), nl
	;
		io__write_string("No solution\n")
	).

:- pred data(list(int)).
:- mode data(out) is det.

data([1,2,3,4,5,6,7,8,9]).

:- pred queen(list(int), list(int)).
:- mode queen(in, out) is nondet.

queen(Data, Out) :-
	same_len(Data, Posn),
	safe(Posn),
	qperm(Data, Posn, Posn2),
	conv_posn(Posn2, Out).

:- pred same_len(list(int)::in, list(var(int))::out(list_skel(any))) is det.
same_len([], []).
same_len([_|Xs], [N|Ys]) :- init(N), same_len(Xs, Ys).

:- pred conv_posn(list(var(int))::in, list(int)::out) is det.
conv_posn([], []).
conv_posn([var(N)|Xs], [N|Ys]) :- conv_posn(Xs, Ys).

:- pred qperm(list(int), list(var(int)), list(var(int))).
:- mode qperm(in, in(list_skel(any)), out) is nondet.

qperm([], [], []).
qperm([X|Y], [var(U)|V], [var(U)|V2]) :-
	qdelete(U, [X|Y], Z),
	qperm(Z, V, V2).

:- pred qdelete(int, list(int), list(int)).
:- mode qdelete(out, in, out) is nondet.

qdelete(A, [A|L], L).
qdelete(X, [A|Z], [A|R]) :-
	qdelete(X, Z, R).

:- pred safe(list(var(int))).
:- mode safe(in(list_skel(any))) is semidet.

safe([]).
safe([NVar|L]) :-
	freeze(NVar, (pred(N::in) is semidet :- nodiag(N, 1, L))),
	safe(L).

:- pred nodiag(int, int, list(var(int))).
:- mode nodiag(in, in, in(list_skel(any))) is semidet.

nodiag(_, _, []).
nodiag(B, D, [NVar|L]) :-
	freeze(NVar, (pred(N::in) is semidet :- D \= N - B, D \= B - N)),
	nodiag(B, D + 1, L).

%-----------------------------------------------------------------------------%

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