[m-rev.] For review: solver-types

Zoltan Somogyi zs at cs.mu.OZ.AU
Wed Aug 18 15:46:31 AEST 2004


On 18-Aug-2004, Ralph Becket <rafe at cs.mu.OZ.AU> wrote:
> +equiv_type__replace_in_type_defn(EqvMap, TypeCtor,
> +		solver_type(SolverTypeDetails0, MaybeUserEqComp),
> +		solver_type(SolverTypeDetails,  MaybeUserEqComp),
> +		ContainsCirc, !VarSet, !Info) :-
> +	RepresentationType0 =
> +		SolverTypeDetails0 ^ representation_type,
> +	equiv_type__replace_in_type_2(EqvMap, [TypeCtor], 
> +		RepresentationType0, RepresentationType,
> +		_, ContainsCirc, !VarSet, !Info),
> +	SolverTypeDetails =
> +		SolverTypeDetails0 ^ representation_type :=
> +			RepresentationType.

You didn't address my comment here; you should list all fields of
SolverTypeDetails0 here, instead of using field access notation.

> --- equiv_type_hlds.m	14 Jun 2004 04:16:02 -0000	1.6
> +++ equiv_type_hlds.m	17 Aug 2004 03:50:10 -0000
> @@ -129,11 +129,19 @@
>  			TVarSet0, TVarSet, EquivTypeInfo0, EquivTypeInfo),
>  		Body = eqv_type(Type)
>  	;
> -		Body0 = foreign_type(_, _),
> +		Body0 = foreign_type(_),
>  		EquivTypeInfo = EquivTypeInfo0,
>  		Body = Body0,
>  		TVarSet = TVarSet0
>  	;
> +		Body0 = solver_type(SolverTypeDetails0, UserEq),
> +		RepnType0 = SolverTypeDetails0 ^ representation_type,
> +		equiv_type__replace_in_type(EqvMap, RepnType0, RepnType, _,
> +			TVarSet0, TVarSet, EquivTypeInfo0, EquivTypeInfo),
> +		SolverTypeDetails =
> +			SolverTypeDetails0 ^ representation_type := RepnType,
> +		Body = solver_type(SolverTypeDetails, UserEq)
> +	;
>  		Body0 = abstract_type(_),
>  		EquivTypeInfo = EquivTypeInfo0,
>  		Body = Body0,

Same here.

>  maybe_make_shared_inst_list([_|_], [], _, _, _) :-
>  	error("maybe_make_shared_inst_list: length mismatch").
>  
> +
>  make_shared_inst_list([], ModuleInfo, [], ModuleInfo).
>  make_shared_inst_list([Inst0 | Insts0], ModuleInfo0,
>  		[Inst | Insts], ModuleInfo) :-
> @@ -1408,6 +1415,7 @@
>  		inst_merge_2(InstA, InstB, MaybeType, ModuleInfo1, Inst0,
>  			ModuleInfo2),
>  
> +

I systematically replace sequences of two or more blank lines with one blank
line in the files I touch to make limited height windows more effective.

> +	% A solver type t defined with
> +	%
> +	% :- solver type st
> +	% 	where representation is rt,		% type
> +	% 	      initialisation is ip,		% pred
> +	% 	      ground         is gi,		% inst
> +	% 	      any            is ai, ...		% inst
> +	%
> +	% causes the following to be introduced:
> +	%
> +	% :- impure func 'representation of st'(st)      = rt.
> +	% :-        mode 'representation of st'(in)      = out(gi) is det.
> +	% :-        mode 'representation of st'(in(any)) = out(ai) is det.

Are the circumstances in which having these two procedures be from the same
function is advantageous? Having them be from two different functions would,
I think, make HLDS dumps more easily readable, and would make the design
more symmetrical. If there are reasons for doing things the current way,
then document them.

> +	ToRepnSymName     = solver_to_repn_symname(TypeSymName, Arity),
> +	ToRepnArgTypes    = [type_only(SolverType), type_only(RepnType)],
> +	ToRepnTypeSigItem =
> +		pred_or_func(TVarSet, InstVarSet, ExistQTVars,
> +			function,
> +			ToRepnSymName,
> +			ToRepnArgTypes,
> +			no,	/* no `with_type` ... */
> +			no,	/* no `with_inst` ... */
> +			no,	/* no determinism */
> +			true,	/* no `where ...' */
> +			(impure), 
> +			constraints([], []) /* no type class constraints */
> +		),
> +	add_item_decl_pass_1(ToRepnTypeSigItem, Context,
> +		!Status, !Module, _, !IO),

You should add a sanity check on the third last argument, here and in the
later calls to add_item_decl_pass_1.

> +
> +	% Obtain the solver type conversion function sym_names from
> +	% the solver type sym_name.
> +	%
> +:- func solver_to_repn_symname(sym_name, arity) = sym_name.
> +
> +solver_to_repn_symname(unqualified(Name), Arity) =
> +	unqualified(
> +		"representation of " ++ Name ++ "/" ++ int_to_string(Arity)
> +	).
> +solver_to_repn_symname(qualified(ModuleNames, Name), Arity) =
> +	qualified(ModuleNames,
> +		"representation of " ++ Name ++ "/" ++ int_to_string(Arity)
> +	).

Eventually, you will need to add these patterns to both the C and Mercury
versions of the demanglers (util/mdemangle.c, profiler/demangle.m and
deep_profiler/read_profile.m), but for now it is sufficient to add a XXX
to both files.

> +:- func repn_to_any_solver_symname(sym_name, arity) = sym_name.
> +
> +repn_to_any_solver_symname(SymName, Arity) =
> +	repn_to_solver_symname("any", SymName, Arity).
> +
> +
> +:- func repn_to_ground_solver_symname(sym_name, arity) = sym_name.
> +
> +repn_to_ground_solver_symname(SymName, Arity) =
> +	repn_to_solver_symname("ground", SymName, Arity).
> +
> +
> +:- func repn_to_solver_symname(string, sym_name, arity) = sym_name.
> +
> +repn_to_solver_symname(AnyOrGround, unqualified(Name), Arity) =
> +	unqualified(
> +		string.format("representation to %s %s/%d",
> +			[s(AnyOrGround), s(Name), i(Arity)])
> +	).
> +repn_to_solver_symname(AnyOrGround, qualified(ModuleNames, Name), Arity) =
> +	qualified(ModuleNames,
> +		string.format("representation to %s %s/%d",
> +			[s(AnyOrGround), s(Name), i(Arity)])
> +	).

And likewise these patterns.

> @@ -2295,26 +2670,13 @@
>  	% default to having an is_solver_type field of `non_solver_type'.
>  	% If another declaration for the type has a `solver' annotation then
>  	% we must update the foreign_type body to reflect this.
> +	%
> +	% rafe: XXX think it should be an error for foreign types to
> +	% be solver types.
> +	%

Are you sure? In some cases, a pointer to a solver data structure may be
a more natural representation than an index into a solver table.

> @@ -3687,6 +4067,19 @@
>  					TVarSet, Type, TypeCtor, TypeBody,
>  					Context, Status0, !Module)
>  			;
> +				true
> +			)
> +		;
> +			SpecialPredId = initialise,
> +			(
> +				type_is_solver_type(!.Module, Type)
> +			->
> +				add_special_pred_for_real(SpecialPredId,
> +					TVarSet, Type, TypeCtor, TypeBody,
> +					Context, Status0, !Module)
> +			;
> +				% rafe: XXX Should this be an error?
> +				%
>  				true

An initialise predicate for a nonsolver type should of course be an error,
unless in your design solver types may be visible in other modules as
non-solver types.

> ===================================================================
> RCS file: /home/mercury1/repository/mercury/compiler/maybe_mlds_to_gcc.pp,v
> retrieving revision 1.8
> diff -u -r1.8 maybe_mlds_to_gcc.pp
> --- maybe_mlds_to_gcc.pp	14 Jun 2004 04:16:16 -0000	1.8
> +++ maybe_mlds_to_gcc.pp	14 Jul 2004 07:30:38 -0000
> @@ -58,6 +58,7 @@
>  
>  #else
>  
> +:- import_module hlds__passes_aux.
>  :- import_module parse_tree__prog_out.
>  :- import_module string.

This looks unnecessary.

> -			io__write_string(", ")
> +mercury_output_where_attributes(TVarSet,
> +		MaybeSolverTypeDetails, MaybeUserEqComp) -->
> +	(
> +		{ MaybeSolverTypeDetails = no },
> +		{ MaybeUserEqComp        = no }
> +	->
> +		[]
> +	;
> +		{ if
> +			MaybeUserEqComp = yes(unify_compare(MaybeUnifyPred0,
> +							    MaybeComparePred0))
> +		  then
> +			MaybeUnifyPred   = MaybeUnifyPred0,
> +			MaybeComparePred = MaybeComparePred0
> +		  else
> +			MaybeUnifyPred   = no,
> +			MaybeComparePred = no
> +		},

I don't like code that uses both -> and if. However, this if-then-else,
and other yes/no decisions, should be switches. I would also consider
using state variable notation instead of DCGs.

> --- modes.m	14 Jun 2004 04:16:23 -0000	1.279
> +++ modes.m	7 Jul 2004 06:48:46 -0000
> @@ -242,10 +242,10 @@
>  	mode_info::in, mode_info::out) is det.
>  
>  	% check that the final insts of the head vars of a lambda
> -	% goal matches their expected insts
> +	% goal matches their expected insts.
>  	%
>  :- pred modecheck_final_insts(list(prog_var)::in, list(inst)::in,
> -	mode_info::in, mode_info::out) is det.
> +	hlds_goal::in, hlds_goal::out, mode_info::in, mode_info::out) is det.

Since you are changing the signature of this predicate, you must update its
documentation to mention the new arguments.

> +:- pred prepend_initialisation_call(module_info::in,
> +		prog_var::in, (type)::in, (inst)::in,
> +		hlds_goal::in, hlds_goal::out) is det.
> +
> +prepend_initialisation_call(ModuleInfo, Var, VarType, InitialInst,
> +		Goal0, Goal) :-
> +	Goal0   = _GoalExpr0 - GoalInfo0,
> +	hlds_goal__goal_info_get_context(GoalInfo0, Context),
> +	construct_initialisation_call(ModuleInfo, Var, VarType, InitialInst,
> +		Context, no /* CallUnifyContext */, InitVarGoal),
> +	goal_to_conj_list(Goal0, ConjList0),
> +	conj_list_to_goal([InitVarGoal | ConjList0], GoalInfo0, Goal).

I think it would be simpler if instead of returning a modified goal,
you returned a list of goals, both here and its callers all the way to the code
that modechecks a call. This would remove the need to construct Goal here
with a mostly bogus goal_info, since most calls are part of conjunctions.

>  
>  % Modecheck a goal by abstractly interpreteting it, as explained

s/interpreteting/interpreting/

> +qualify_type_defn(solver_type(SolverTypeDetails0, MaybeUserEqComp),
> +		solver_type(SolverTypeDetails, MaybeUserEqComp),
> +		Info0, Info) -->
> +	{ RepnType0   = SolverTypeDetails0 ^ representation_type },
> +	{ GroundInst0 = SolverTypeDetails0 ^ ground_inst },
> +	{ AnyInst0    = SolverTypeDetails0 ^ any_inst },
> +	qualify_type(RepnType0, RepnType,     Info0, Info1),
> +	qualify_inst(GroundInst0, GroundInst, Info1, Info2),
> +	qualify_inst(AnyInst0, AnyInst,       Info2, Info ),
> +	{ SolverTypeDetails =
> +		(((SolverTypeDetails0
> +			^ representation_type := RepnType   )
> +			^ ground_inst         := GroundInst )
> +			^ any_inst            := AnyInst    ) }.

Again, you should list match SolverTypeDetails0 against a function symbol
instead of using field access notation, to guard against the future addition
of more fields.

>  		TypeDefn = abstract_type(IsSolverType)
>  	;
> +		TypeDefn = solver_type(_, _),
> +		% rafe: XXX we need to also export the details of the
> +		% forwarding type for the representation and the forwarding
> +		% pred for initialization.
> +		IsSolverType = solver_type
> +	;
>  		TypeDefn = eqv_type(_),
> +		% rafe: XXX what *should* IsSolverType be here?  We need
> +		% to know properly.
> +		IsSolverType = non_solver_type,
>  		% For the `.int2' files, we need the full definitions of
>  		% equivalence types. They are needed to ensure that
>  		% non-abstract equivalence types always get fully expanded

I guess you'll fix these XXXs soon, so they are ok for now.

>  	(
> -		TypeDefn0 = du_type(Constructors, IsSolverType,
> -			yes(_UnifyCompare)),
> -		TypeDefn  = du_type(Constructors, IsSolverType,
> -			yes(abstract_noncanonical_type))
> -	;
> -		TypeDefn0 = foreign_type(ForeignType, yes(_), Assertions),
> -		TypeDefn = foreign_type(ForeignType,
> -			yes(abstract_noncanonical_type), Assertions)
> +		TypeDefn0 = du_type(Constructors, yes(_UserEqComp)),
> +		TypeDefn  = du_type(Constructors, yes(
> +				abstract_noncanonical_type(non_solver_type)))
> +	;
> +		TypeDefn0 = foreign_type(ForeignType, yes(_UserEqComp),
> +				Assertions),
> +		TypeDefn  = foreign_type(ForeignType,
> +				yes(abstract_noncanonical_type(
> +					non_solver_type)),
> +				Assertions)

I prefer to have only a single extra indent when continuing lines, and this
is the perfect example why.

> +	get_condition(B, Body, Condition),
>  	get_is_solver_type(Attributes0, IsSolverType, Attributes),
> -	process_du_type(ModuleName, H, Body, IsSolverType, EqCompare, R0),
> -	check_no_attributes(R0, Attributes, R).
> +	(
> +		IsSolverType = solver_type,
> +		Result = error("a solver type cannot have data constructors",
> +				H)
> +	;
> +		IsSolverType = non_solver_type,
> +		du_type_rhs_ctors_and_where_terms(Body, CtorsTerm,
> +			MaybeWhereTerm),
> +		CtorsResult = convert_constructors(ModuleName, CtorsTerm),
> +		(
> +			CtorsResult = error(String, Term),
> +			Result      = error(String, Term)
> +		;
> +			CtorsResult = ok(Ctors),
> +			WhereResult = parse_type_decl_where_term(
> +					non_solver_type, ModuleName,
> +					MaybeWhereTerm),
> +			(
> +				WhereResult = error(String, Term),
> +				Result      = error(String, Term)
> +			;
> +				WhereResult = ok(_NoSolverTypeDetails,
> +						 MaybeUserEqComp),

That underscore in _NoSolverTypeDetails means you don't detect e.g.
"where representation" clauses on non-solver types; please fix that
oversight, or document why it isn't an oversight.

> +:- func parse_type_decl_where_term(is_solver_type, module_name, maybe(term)) =
> +		maybe2(maybe(solver_type_details), maybe(unify_compare)).

That looks like one too many maybes to me. Why do you need the outer maybe2?
If there is a good reason for it then document it, otherwise return just
a pair of maybes.

> +parse_where_is(Name, Parser, Term) = Result :-
> +	(
> +		Term = term__functor(term__atom("is"), [LHS, RHS], _Context1)
>  	->
> -		Body = Body1,
>  		(
> -			EqCompTerm = term__functor(
> +			LHS = term__functor(term__atom(Name), [], _Context2)
> +		->
> +			Result0 = Parser(RHS),
> +			(
> +				Result0 = ok(X),
> +				Result  = ok(yes(X))

Can you find a better variable name than X?

> +	(
> +		Term = term__functor(
>  				term__atom("type_is_abstract_noncanonical"),
> -				[], _Context2)
> +				[],
> +				_Context)
> +	->

Improve indentation.

> +parse_where_initialisation_is(ModuleName, Term) = Result :-
> +	Result0 = parse_where_is("initialisation",
> +			parse_where_pred_is(ModuleName), Term),
> +	(
> +		Result0 = ok(no)
> +	->
> +		Result  = parse_where_is("initialization",
> +				parse_where_pred_is(ModuleName), Term)
> +	;
> +		Result  = Result0
> +	).
> +
> +
> +:- func parse_where_pred_is(module_name, term) = maybe1(sym_name).
> +
> +parse_where_pred_is(ModuleName, Term) = Result :-
> +	parse_implicitly_qualified_symbol_name(ModuleName, Term, Result).
> +
> +
> +:- func parse_where_inst_is(module_name, term) = maybe1(inst).
> +
> +parse_where_inst_is(_ModuleName, Term) =
> +	( if
> +		prog_io_util__convert_inst(no_allow_constrained_inst_var,
> +			Term, Inst),
> +		not inst_util__inst_contains_unconstrained_var(Inst)
> +	  then ok(Inst)
> +	  else error("expected a ground, unconstrained inst", Term)
> +	).

Again, mixing -> and if.

As I told you earlier, this diff updates a fundamentally misdesigned parser.

> +solver_inst_cast_sym_name(TypeSymName, TypeArity) =
> +	unqualified_sym_name_with_prefix_suffix("inst cast ", TypeSymName,
> +		"/" ++ int_to_string(TypeArity)).
> +
> +
> +:- func solver_inst_sym_name(sym_name, arity) = sym_name.
> +
> +solver_inst_sym_name(TypeSymName, TypeArity) =
> +	unqualified_sym_name_with_prefix_suffix("", TypeSymName,
> +		"/" ++ int_to_string(TypeArity) ++ " inst").

What are these for?

> -	(
> -		convert_constructors(ModuleName, Body, Constrs)
> -	->
> -		% check that all type variables in the body
> -		% are either explicitly existentially quantified
> -		% or occur in the head.
> -		(
> -			list__member(Ctor, Constrs),
> -			Ctor = ctor(ExistQVars, _Constraints, _CtorName,
> -					CtorArgs),
> -			assoc_list__values(CtorArgs, CtorArgTypes),
> -			term__contains_var_list(CtorArgTypes, Var),
> -			\+ list__member(Var, ExistQVars),
> -			\+ term__contains_var_list(Args, Var)
> -		->
> -			Result = error(
> -			"free type parameter in RHS of type definition",
> -					Body)

It would be a lot easier to check this part of the diff if you used diff's -b
option.

> +convert_constructor_arg_list( ModuleName, [Term | Terms]) = Result :-

Why the space before ModuleName?

> -special_pred_name(unify, _TypeCtor) = "__Unify__".
> -special_pred_name(index, _TypeCtor) = "__Index__".
> -special_pred_name(compare, _TypeCtor) = "__Compare__".
> +special_pred_name(unify,      SymName - Arity) =
> +	string__format("__Unify__%s/%d",
> +		[s(sym_name_to_string(SymName)), i(Arity)]).
> +special_pred_name(index,      SymName - Arity) =
> +	string__format("__Index__%s/%d",
> +		[s(sym_name_to_string(SymName)), i(Arity)]).
> +special_pred_name(compare,    SymName - Arity) =
> +	string__format("__Compare__%s/%d",
> +		[s(sym_name_to_string(SymName)), i(Arity)]).
> +special_pred_name(initialise, SymName - Arity) =
> +	string__format("__Initialise__%s/%d",
> +		[s(sym_name_to_string(SymName)), i(Arity)]).

This change requires immediate updates to name_mangle.m (whose handling
of these names is now redundant) and to all three demanglers.

>  special_pred_is_generated_lazily(ModuleInfo, TypeCtor, Body, Status) :-
> +	% rafe: XXX Is there a cleaner way of doing this?  I don't
> +	% want special preds for solver types to be generated lazily
> +	% because I have to insert calls to their initialisation preds
> +	% during mode analysis and I therefore require the appropriate
> +	% names to appear in the symbol table.
> +	%

The answer to your quesion is "no", so delete that part of the comment
(or turn it into an enhancement request), but leave the rest.

> +		% rafe: XXX I think the [yes] here is correct.
> +		%
> +special_pred_id_to_termination(initialise, HeadVars, ArgSize, Termination) :-
> +	term_util__make_bool_list(HeadVars, [yes], OutList),
> +	ArgSize = finite(0, OutList),
> +	Termination = cannot_loop.

If the initialization predicate is user supplied, then whether it can loop
depends on the user supplied predicate. It looks like the code for unify and
compare is also wrong; it is wrong because it predates user-defined unify
and compare predicates. For now, leave a XXX.

> +				% We treat solver_types as being
> +				% equivalent to their representation
> +				% types for RTTI purposes.
> +				%
> +				% rafe: XXX Won't this cause trouble
> +				% with construct etc?

No more problems than are caused by construct on abstract types in general.

> +	% Obtain the type definition and type definition body respectively,
> +	% if known, for the principal type constructor of the given type.
> +	%
> +	% Fail if the given type is a type variable.
> +:- pred type_util__type_to_type_defn(module_info::in, (type)::in,
> +		hlds_type_defn::out) is semidet.
> +
> +:- pred type_util__type_to_type_defn_body(module_info::in, (type)::in,
> +		hlds_type_body::out) is semidet.
> +
>  	% Succeed iff there was either a `where equality is <predname>' or a
>  	% `where comparison is <predname>' declaration for the principal type
>  	% constructor of the specified type, and return the ids of the declared
> @@ -83,12 +93,23 @@
>  :- pred type_body_has_user_defined_equality_pred(module_info::in,
>  	hlds_type_body::in, unify_compare::out) is semidet.
>  
> -	% Succeed if the inst `any' can be considered `bound' for this type.
> +	% Succeed iff the principal type constructor for the given type
> +	% is a solver type.
> +	%
> +	% If the type is a type variable and thus has no principal type
> +	% constructor, fail.
> +:- pred type_util__type_is_solver_type(module_info::in, (type)::in) is semidet.
> +
> +:- pred type_util__type_has_solver_type_details(module_info::in, (type)::in,
> +		solver_type_details::out) is semidet.
> +
> +:- pred type_util__type_body_has_solver_type_details(module_info::in,
> +		hlds_type_body::in, solver_type_details::out) is semidet.
> +
>  :- pred type_util__is_solver_type(module_info::in, (type)::in) is semidet.

All these new predicates in type_util, except the one working on a
hlds_type_body, beg the question of what they should do if the type
supplied is a type variable, and *has* no principal type constructor.

I think you should leave it to the caller to extra the principal type
constructor with type_to_ctor_and_args, and redefine these predicates
to work on the type constructor.

In fact, the same should be done to a bunch of old predicates in this module
too, but that can wait.

You can commit after addressing these points, and testing the changes I asked
you to make to name manglers/demanglers for unify/compare predicate names.

Zoltan.
--------------------------------------------------------------------------
mercury-reviews mailing list
post:  mercury-reviews at cs.mu.oz.au
administrative address: owner-mercury-reviews at cs.mu.oz.au
unsubscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: unsubscribe
subscribe:   Address: mercury-reviews-request at cs.mu.oz.au Message: subscribe
--------------------------------------------------------------------------



More information about the reviews mailing list