[m-dev.] for review: accumulator introduction

Simon Taylor stayl at cs.mu.OZ.AU
Fri Jun 4 17:01:19 AEST 1999


> Add a new pass to the compiler, that attempts to introduce accumulators
> into a procedure so as to make that procedure tail recursive.
 
A test case or two in tests/general would be nice.

> Index: compiler/accumulator.m
> ===================================================================

> +	% Given a dynamic variable, from which dynamic variable was it
> +	% descended (ie which variable in Y0s).
> +:- type orig_dynvar_map == map(prog_var, prog_var).

What does it mean for a variable to be descended from another?

I think you need to explain here what is important about the distinction
between static and dynamic variables.

> +%-----------------------------------------------------------------------------%
> +
> +	%
> +	% This predicate is meant to take the original goal and
> +	% rearrange it into a standard form that can be used in the rest
> +	% of module.
> +	%
> +:- pred accumulator__rearrange_goal(pred_id::in, proc_id::in,
> +		hlds_goal::in, instmap::in, module_info::in,
> +		top_level::out, base_goal::out, rec_goal::out) is semidet.
> +
> +accumulator__rearrange_goal(PredId, ProcId, Goal, InitialInstMap, ModuleInfo,
> +		Type, Base, Rec) :-
> +	(
> +		Goal = switch(_Var, _CanFail, Cases, _StoreMap) - _GoalInfo,
> +		Cases = [case(_IdA, GoalA), case(_IdB, GoalB)],
> +		goal_to_conj_list(GoalA, GoalAList),
> +		goal_to_conj_list(GoalB, GoalBList)
> +	->
> +		(
> +			accumulator__split_recursive_case(PredId, ProcId,
> +					InitialInstMap, ModuleInfo,
> +					GoalAList, Rec0)
> +		->
> +			Type = switch_rec_base,
> +			Base = base(GoalBList),
> +			Rec = Rec0
> +		;
> +			accumulator__split_recursive_case(PredId, ProcId,
> +					InitialInstMap, ModuleInfo,
> +					GoalBList, Rec0)
> +		->
> +			Type = switch_base_rec,
> +			Base = base(GoalAList),
> +			Rec = Rec0
> +		;
> +			fail
> +		)
> +	;
> +		fail
> +	).

Do you need to check that the goal identified as the base case
by this predicate does not contain a recursive call?

> +
> +	%
> +	% accumulator__split_recursive_case(Gs, DP, P, C)
> +	%
> +	% Split the goals, Gs, which make up the recursive case into the
> +	% decompose and process list of goals, DP, and the compose
> +	% goals, C, and the recursive goal P.
> +	%
> +:- pred accumulator__split_recursive_case(pred_id::in, proc_id::in,
> +		instmap::in, module_info::in,
> +		hlds_goals::in, rec_goal::out) is semidet.
> +
> +accumulator__split_recursive_case(PredId, ProcId,
> +		InitialInstMap, ModuleInfo, Goals, RecGoal) :-

The argument list in the comment looks a bit out of date.

> +:- pred accumulator__split_goals(hlds_goals::in, pred_id::in, proc_id::in,
> +		split_result::out) is nondet.
> +
> +accumulator__split_goals(Goals, PredId, ProcId, RecGoal) :-
> +	list__append(DecomposeProcess, [RecursiveCall | Compose], Goals),
> +	RecursiveCall = call(PredId, ProcId, _, _, _, _) - _,
> +
> +		% An empty compose means the predicate is already tail
> +		% recursive.
> +	Compose \= [],
> +	RecGoal = recursive(DecomposeProcess, RecursiveCall, Compose).

Does it make sense to do the transformation if the conjunction
contains a disjunction containing a recursive call?

> +	%
> +	% Given a list of goals and an instmap before the list of goals,
> +	% work out what the instmap at the end of the goals is.
> +	%
> +:- pred calculate_instmap(hlds_goals::in, instmap::in, instmap::out) is det.
> +
> +calculate_instmap(Goals, InstMap0, InstMap) :-
> +	GetGoalInfos = (pred(Goal::in, GoalInfo::out) is det :-
> +			Goal = _ - GoalInfo),
> +	list__map(GetGoalInfos, Goals, GoalInfos),
> +	list__map(goal_info_get_instmap_delta, GoalInfos, InstMapDeltas),
> +	ApplyInstMapDelta = (pred(Delta::in, Map0::in, Map::out) is det :-
> +			instmap__apply_instmap_delta(Map0, Delta, Map)),
> +	list__foldl(ApplyInstMapDelta, InstMapDeltas, InstMap0, InstMap).

You could do this as:
	goal_list_instmap_delta(Goals, InstMapDelta),
	instmap__apply_instmap_delta(InstMap0, InstMapDelta, InstMap).

> +	%
> +	% wrapper to test my code.
> +	%
> +:- pred checked_goal_depends_on_earlier_goal(hlds_goal::in,
> +		hlds_goal::in, instmap::in, module_info::in) is semidet.

The name of this predicate is a little ambiguous (does `checked' apply
to the goal or to `goal_depends_on_earlier_goal').
Maybe `goal_depends_on_earlier_goal_with_error_check'.

> +	%
> +	% If the earlier goal changes the instatiatedness of a variable
> +	% that is used in the later goal, then the later goal depends on
> +	% the earlier goal.
> +	%
> +	% This code doesn't work on the alias branch.
> +	%
> +:- pred goal_depends_on_earlier_goal(hlds_goal::in, hlds_goal::in) is semidet.

> +	%
> +	% If the earlier goal changes the instatiatedness of a variable
> +	% that is used in the later goal, then the later goal depends on
> +	% the earlier goal.
> +	%
> +	% This code does work on the alias branch.
> +	%
> +:- pred goal_depends_on_earlier_goal_2(hlds_goal::in,
> +		hlds_goal::in, instmap::in, module_info::in) is semidet.

pd_util__can_reorder_goals does something similar to this.
Could you use that instead? Do you need to check whether the
termination behaviour of the goal can change if reordering is done?

> +	%
> +	% accumulator__vars_to_accumulate(HV, C, VA)
> +	%
> +	% Given the list of goals, C, which represent the compose
> +	% goal and the list of the head variables, HV, determine the
> +	% head variables that will need to be accumulated, VA.
> +	%
> +	% The variables that will need to be accumulated are simply
> +	% those which instantiatedness change in the compose goals and
> +	% that are headvars.

Grammar - "those which instantiatedeness change"

> +	%
> +:- pred accumulator__vars_to_accumulate(prog_vars::in, a_goals::in,
> +		module_info::in, prog_vars::out) is det.
> +
> +accumulator__vars_to_accumulate(HeadVars, C, ModuleInfo, VarsToAccumulate) :-
> +	C = goal(ComposeGoals, InstMapBeforeCompose),
> +		
> +		% Determine the instmap-delta of the goal list.
> +	GetGoalInfos = (pred(Goal::in, GoalInfo::out) is det :-
> +				Goal = _ - GoalInfo),
> +	list__map(GetGoalInfos, ComposeGoals, GoalInfos),
> +	list__map(goal_info_get_instmap_delta, GoalInfos, InstMapDeltas),
> +	instmap_delta_init_reachable(EmptyInstMapDelta),
> +	list__foldl(instmap_delta_apply_instmap_delta,
> +			InstMapDeltas, EmptyInstMapDelta, InstMapDelta),

Use `goal_list_instmap_delta(ComposeGoals, InstMapDelta)'.

 +	%
> +	% accumulator__extra_vars_for_recursive_call(DP, C, Hs)
> +	%
> +	% If the decompose/process list of goals, DP, produce a value
> +	% that is needed in the compose list of goals, C, then that
> +	% value will need to be passed via the introduced recursive
> +	% call.  This predicate identifies these variables, Hs.
> +	%
> +:- pred accumulator__extra_vars_for_recursive_call(a_goals::in,
> +		a_goals::in, prog_vars::out) is det.
> +
> +accumulator__extra_vars_for_recursive_call(
> +		goal(DecomposeProcess, _InstMapBeforeDecomposeProcess),
> +		goal(Compose, _InstMapBeforeCompose), Vars) :-
> +	GetGoalInfos = (pred(Goal::in, GoalInfo::out) is det :-
> +				Goal = _ - GoalInfo),
> +	list__map(GetGoalInfos, DecomposeProcess, DPGoalInfos),
> +	list__map(goal_info_get_nonlocals, DPGoalInfos, DPNonLocals),
> +	set__list_to_set(DPNonLocals, DPNonLocalsPowerSet),
> +	set__power_union(DPNonLocalsPowerSet, DPNonLocalsSet),

This can be done as `goal_list_nonlocals(DecomposeProcess, DPNonLocalsSet)'.

> +
> +	list__map(GetGoalInfos, Compose, CGoalInfos),
> +	list__map(goal_info_get_nonlocals, CGoalInfos, CNonLocals),
> +	set__list_to_set(CNonLocals, CNonLocalsPowerSet),
> +	set__power_union(CNonLocalsPowerSet, CNonLocalsSet),

Ditto.

> +%-----------------------------------------------------------------------------%
> +
> +	%
> +	% accumulator__static_vars_in_recursive_call
> +	%
> +	% Identify the variables which are static only appear in the
> +	% recursive call.

Word missing.

> +	%
> +	% This predicate is currently unused.
> +	%
> +:- pred accumulator__static_vars_in_recursive_call(a_goal::in,
> +		a_goals::in, module_info::in, prog_vars::out) is det.
> +
> +accumulator__static_vars_in_recursive_call(Recursive, Compose,
> +		ModuleInfo, Vars) :-
> +
> +	Recursive = goal(_RecGoalExpr - RecGoalInfo, InstMapBeforeRec),
> +	Compose = goal(ComposeGoals, _InstMapBeforeCompose),
> +
> +	goal_info_get_instmap_delta(RecGoalInfo, RecInstMapDelta),
> +	goal_info_get_nonlocals(RecGoalInfo, RecNonLocals),
> +	instmap__apply_instmap_delta(InstMapBeforeRec, RecInstMapDelta,
> +			InstMapAfterRec),
> +	changed_vars(InstMapBeforeRec, InstMapAfterRec,
> +			ModuleInfo, ChangedVars),
> +
> +	set__difference(RecNonLocals, ChangedVars, PossibleStaticVars),
> +
> +	GetGoalInfos = (pred(Goal::in, GoalInfo::out) is det :-
> +				Goal = _ - GoalInfo),
> +	list__map(GetGoalInfos, ComposeGoals, CGoalInfos),
> +	list__map(goal_info_get_nonlocals, CGoalInfos, CNonLocals),
> +	set__list_to_set(CNonLocals, CNonLocalsPowerSet),
> +	set__power_union(CNonLocalsPowerSet, CNonLocalsSet),

goal_list_nonlocals.

> +	%
> +	% accumulator__orig_base_case
> +	%
> +	% Determine the base case of the original goal.
> +	%
> +:- pred accumulator__orig_base_case(hlds_goals::in, hlds_goal::out) is det.
> +
> +accumulator__orig_base_case(BaseGoalList, BaseGoal) :-
> +	calculate_goal_info(conj(BaseGoalList), BaseGoal).


Will the computed goal_info be any different to that from the original
goal? If not, it would be clearer to just use the old one.

> +
> +%-----------------------------------------------------------------------------%
> +
> +	%
> +	% accumulator__orig_recursive_case
> +	%
> +	% Determine the recursive case of the original goal.
> +	%

This might be clearer as:
	% Work out a new recursive case for the original predicate
	% which replaces the recursive call with a call to the new
	% predicate.

> +accumulator__new_recursive_case(DP, C, R0, DoLCO,
> +		ModuleInfo, PredId, ProcId, Name, Hs, HeadVars,
> +		Y0stoYs_Subst, HstoAs_Subst, Goal) :-
> +	DP = goal(DecomposeProcess, _InstMapBeforeDecomposeProcess),
> +	C  = goal(Compose, InstMapBeforeCompose),
> +	R0 = goal(Recursive0, _InstMapBeforeRecursive0),
> +
> +	assoc_info_init(ModuleInfo, HeadVars, DP, C, R0,
> +		Y0stoYs_Subst, HstoAs_Subst, AssocInfo0),
> +        accumulator__check_assoc(Compose, InstMapBeforeCompose, ModuleInfo,
> +			PreRecGoal0, PostRecGoal0, AssocInfo0, AssocInfo),

Replace the spaces with a tab.

> +	%
> +	% accumulator__check_post_rec_goals
> +	%
> +	% Ensure that each goal which is to be placed after the
> +	% recursive goal is construction unification.

is _a_ construction unification


> +:- pred calculate_goal_info(hlds_goal_expr::in, hlds_goal::out) is det.
> +
> +calculate_goal_info(GoalExpr, GoalExpr - GoalInfo) :-
> +	(
> +		GoalExpr = conj(GoalList)
> +	->
> +		goal_list_nonlocals(GoalList, NonLocals),
> +		goal_list_instmap_delta(GoalList, InstMapDelta),
> +		goal_list_determinism(GoalList, Determinism),
> +
> +		goal_info_init(NonLocals, InstMapDelta, Determinism, GoalInfo)
> +	;
> +		error("calculate_goal_info: not a conj.")
> +	).


All the uses of this predicate pass in a conj(Goals).
Would it be better just to pass in Goals, and avoid the error check.
This should probably go in hlds_goal.m.

> +/*
> +	XXX this no longer works.  We need to go back to my original code for
> +	this to work.
> +assoc_fact(unqualified("set"), "insert", 3, [TypeInfoIn, In, In, Out], 
> +		Moduleinfo, [TypeInfo, A, B, C], 
> +		[TypeInfo, A, B, C], PossibleStaticVars, no) :-

The comment should include some explanation of what the original code did,
and why it doesn't work now.

> +
> +	% 
> +	% accumulator__obey_heuristic
> +	%
> +	% for calls which rearrange the order of variables ensure that
> +	% the call obeys the heuristic that the static variables are in 
> +	% certain positions.
> +	%
> +:- pred accumulator__obey_heuristic(pred_id::in, module_info::in,
> +		prog_vars::in, set(prog_var)::in) is semidet.

I think this needs more explanation. 

> Index: compiler/options.m
> ===================================================================
> @@ -942,6 +944,7 @@
> @@ -2009,6 +2012,9 @@
>  		"\t`--optimize-higher-order' and `--type-specialization'.",
>  		"\tGoal size is measured as the number of calls, unifications",
>  		"\tand branched goals.",
> +		"--introduce-accumulators",
> +		"\tAttempt to introduce accumulating variables into",
> +		"\tprocedures, so as to make the procedure tail recursive.",
>  		"--optimize-constructor-last-call",

"Attempt to make predicates tail recursive by transforming them
to use accumulator arguments."?

I'd like to see some test cases and a relative diff.

Thanks,
Simon.
--------------------------------------------------------------------------
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