mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-19 03:13:40 +00:00
Given a switch arm that matches several cons_ids, and which contains
one or more switches on the *same* variable, such as the arm for
f1/f2/f3/f4 below,
(
(X = f1 ; X = f2 ; X = f3 ; X = f4),
...
(
X = f1,
...
;
(X = f2 ; X = f3),
...
;
X = f4,
...
),
...
;
...
)
this new optimization
- partitions the set of cons_id in that arm (in this case, {f1,f2,f3,f4})
as finely as needed by any other the switches on X in that arm
(in this case, that is three partitions containing {f1}, {f2,f3} and {f4}),
- splits that original switch arm into N arms, one arm for each partition,
making a copy of the switch arm's goal for each partition,
- restricts any switches on the original switch variable (in this case, X)
inside the copy of the arm goal inside each new case to only the cons_ids
in that case's partition, and then replacing any resulting one-arm switches
with the just goal inside that one arm.
The code resulting from these three steps will include some code duplication
(some of the pieces of code denoted by ... in the example above would be
duplicated), but it will need to execute fewer transfers of control.
This is worthwhile because (a) the branch instructions used to implement
switches are hard to predict unless most paths through the nested switches
are rarely if ever taken, and (b) the pipeline breaks caused by branches
that are not correctly predicted are one of the two major contributors
to the runtime of Mercury programs. (The other major contributors are
data cache misses.)
The implementation of this option has two major parts.
- Part 1 consists of discovering whether a procedure body contains
any code in which a switch on a variable is nested inside an arm
of another switch on that same variable. For any instance of such
a pair of switches, it records the identity of the variable and
the set of cons_ids of the outermost arm.
- Part 2 consists of actually transforming the procedure body
by splitting each outermost switch arm thus recorded. (This part
contains all three of the steps above.)
We integrate part 1 with the usual procedure body traversal of the
simplification pass, which makes it quite cheap. In most procedure bodies,
it won't find any nested switches meeting its criteria. We execute part 2,
which is relatively expensive, only if it does.
compiler/simplify_info.m:
Add a new type, switch_arm, which represents one arm of a switch.
Add a new field to the simplify_nested_context type. Its type
is list(switch_arm), and it represents the stack of switch arms (if any)
that the goal currently being simplified is inside. simplify_goal_switch.m
uses this field to detect switches that occur inside an arm of an
ancestor goal that is also a switch on the same variable.
Add a new field to the simplify_info, a set of switch_arms,
that denotes the set of switch arms from which that detection
has actually happened, and which should therefore be
partitioned and split.
compiler/simplify_goal_switch.m:
Add the code for doing the detection and recording mentioned just above.
This is the Part 1 mentioned above.
compiler/split_switch_arms.m:
This new module implements the splitting up process.
This is the Part 2 mentioned above.
compiler/simplify.m:
Include the new module in the simplify subpackage of the check_hlds
package.
compiler/notes/compiler_design.html:
Document the new module.
compiler/simplify_proc.m:
Invoke split_switch_arms.m (part 2) if the part of simplify_goal_switch.m
implementing part 1 has found any work for it to do.
compiler/simplify_tasks.m:
Add split_switch_arms as one of the tasks that simplification
may be asked to do. Set its default value from the value of
a new optimization option that, when specified, calls for it to be done.
compiler/options.m:
Add this option, --split-switch-arms.
Change the internal name of an existing option,
everything_in_one_c_function, to the one expected by
tools/make_optimization_options_middle.
Replace the part of this file that is constructed by
tools/make_optimization_options_middle.
doc/user_guide.texi:
Document the new option.
tools/make_optimization_options_db:
Add --split-switch-arms to the optimization tuple.
Fix software rot by
- renaming optimize_tailcalls to optimize_mlds_tailcalls inside the
optimization tuple, as it has been in options.m, and
- deleting erlang_switch_on_strings_as_atoms from the opt_tuple,
as it has been from options.m.
tools/make_optimization_options_end:
Enable the new option by default at optimization level 2.
tools/make_optimization_options_middle:
Fix bugs in this script, which generates compiler/optimization_options.m.
One bug was that it referred to the opt_level and opt_space options
by the wrong name (optopt_level and optopt_space respectively).
The other bug was that it expected opt_level to be a string_special option,
when it is an int_special option.
Make the handler_file this script generates easier to put into options.m
by not generating a line that (a) already exists in options.m, and
(b) would need to have a comma put after it, if one wanted this line
to replace the copy already in options.m.
compiler/optimization_options.m:
Rebuild this file after the changes above.
compiler/simplify_goal_unify.m:
Conform to the changes above.
compiler/inst_merge.m:
Mark two predicates, inst_merge_[34], to be inlined. The intention
is that inst_merge_4 should be inlined into inst_merge_3, and then
inst_merge_3 should be inlined inside inst_merge_2; that would then
generate six levels of switches, three on one of the insts to be
merged and three on the other, which the new optimization could
then flatten. Unfortunately, inlining does not do this yet.
tests/hard_coded/test_split_switch_arms.{m,exp}:
A new test case for the the new transformation.
tests/hard_coded/Mmakefile:
tests/hard_coded/Mercury.options:
Enable the new test case, and specify the new option for it.
603 lines
24 KiB
Mathematica
603 lines
24 KiB
Mathematica
%----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%----------------------------------------------------------------------------%
|
|
% Copyright (C) 2023 The Mercury team.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%----------------------------------------------------------------------------%
|
|
%
|
|
% File: split_switch_arms.m.
|
|
%
|
|
% This module implements a program transformation that replaces code like this:
|
|
%
|
|
% (
|
|
% ( X = f1
|
|
% ; X = f2
|
|
% ; X = f3
|
|
% ; X = f4
|
|
% ),
|
|
% <initial code to handle f1,f2,f3,f4>,
|
|
% (
|
|
% X = f1,
|
|
% <code to handle f1>
|
|
% ;
|
|
% ( X = f2
|
|
% ; X = f3
|
|
% ),
|
|
% <code to handle f2,f3>
|
|
% ;
|
|
% X = f4,
|
|
% <code to handle f4>
|
|
% ),
|
|
% <final code to handle f1,f2,f3,f4>
|
|
% ;
|
|
% X = f5,
|
|
% <code to handle f5>
|
|
% )
|
|
%
|
|
% with code like this:
|
|
% (
|
|
% X = f1,
|
|
% <initial code to handle f1,f2,f3,f4>,
|
|
% <code to handle f1>,
|
|
% <final code to handle f1,f2,f3,f4>
|
|
% ;
|
|
% ( X = f2
|
|
% ; X = f3
|
|
% ),
|
|
% <initial code to handle f1,f2,f3,f4>,
|
|
% <code to handle f2,f3>,
|
|
% <final code to handle f1,f2,f3,f4>
|
|
% ;
|
|
% X = f4,
|
|
% <initial code to handle f1,f2,f3,f4>,
|
|
% <code to handle f4>,
|
|
% <final code to handle f1,f2,f3,f4>
|
|
% ;
|
|
% X = f5,
|
|
% <code to handle f5>
|
|
% )
|
|
%
|
|
% The idea is that if inside a switch on a variable, there are other switches
|
|
% on that same variable that subdivide the set of cons_ids even further, then
|
|
% we do the following.
|
|
%
|
|
% - We partition the cons_ids of the top-level switch arm to make all the
|
|
% distinctions between its cons_ids that any of the (one or more) switches
|
|
% on the same variable inside the top-level switch arm make,
|
|
%
|
|
% In this case, we would partition {f1,f2,f3,f4} into three sets:
|
|
% {f1}, {f2,f3} and {f4}.
|
|
%
|
|
% - We replace the original switch arm with a separate switch arm for each
|
|
% partition.
|
|
%
|
|
% In this case, this would replace the arm for {f1,f2,f3,f4}
|
|
% with three arms for {f1}, {f2,f3} and {f4} respectively.
|
|
% The three arms would initially contain the same code as the original
|
|
% switch arm. This would mean that each would contain the whole of
|
|
% nested switch on X with the three arms for {f1}, {f2,f3} and {f4}.
|
|
%
|
|
% - We restrict the goal in each of the resulting switch arms to refer to
|
|
% only the cons_ids of its own partion in any nested switches on the
|
|
% original switched-on variable, and we then simplify away any trivial
|
|
% switches generated by this process.
|
|
%
|
|
% In this case, in the new {f1}-only arm of the top switch, would replace
|
|
% the inner switch on X with the arms for {f1}, {f2,f3} and {f4}
|
|
% with just the arm for {f1}, and then optimize away the now-unnecessary
|
|
% switch wrapper around the goal inside that arm. We would do the same
|
|
% with the other two arms.
|
|
%
|
|
% The code resulting from the above steps will include some code duplication
|
|
% (most of the pieces of code denoted by <code to handle ...> in the example
|
|
% above would be duplicated), but it will need to execute fewer transfers
|
|
% of control. This is worthwhile because
|
|
%
|
|
% - the branch instructions used to implement switches are hard to predict
|
|
% unless most paths through the nested switches are rarely if ever taken, and
|
|
%
|
|
% - the pipeline breaks caused by branches that are not correctly predicted
|
|
% are one of the two major contributors to the runtime of Mercury programs.
|
|
% (The other major contributors are data cache misses.)
|
|
%
|
|
% Nevertheless, the main point of this transformation is not performance,
|
|
% since the transformation performed by this module could also be done
|
|
% manually by humans. Instead, the main point is that having the transformatio
|
|
% done by a machine is better from a software engineering point of view.
|
|
% One software engineering advantage is that the compiler can do the
|
|
% transformation more quickly, more cheaply, and more reliably than
|
|
% programmers can. Another advantage is that it allows programmers to maintain
|
|
% the pre-transform version of the code. The pre-transform version may be
|
|
% clearer and easier to maintain than the post-transform version, due to
|
|
% the code duplication required by the transform.
|
|
%
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- module check_hlds.simplify.split_switch_arms.
|
|
:- interface.
|
|
|
|
:- import_module check_hlds.simplify.simplify_info.
|
|
:- import_module hlds.
|
|
:- import_module hlds.hlds_goal.
|
|
|
|
:- import_module set.
|
|
|
|
% split_switch_arms_in_goal(ToSplitArms, Goal0, Goal):
|
|
%
|
|
% This predicate looks for the top-level switch arms listed in ToSplitArms,
|
|
% and when it finds them, it calls
|
|
%
|
|
% - the predicates that partition the cons_ids of the arm
|
|
% as described above, and
|
|
%
|
|
% - the predicates that replace the one original arm with several arms,
|
|
% one for each partition.
|
|
%
|
|
:- pred split_switch_arms_in_goal(set(switch_arm)::in,
|
|
hlds_goal::in, hlds_goal::out) is det.
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module hlds.make_goal.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.prog_data.
|
|
|
|
:- import_module cord.
|
|
:- import_module list.
|
|
:- import_module require.
|
|
|
|
split_switch_arms_in_goal(ToSplitArms, Goal0, Goal) :-
|
|
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
|
|
(
|
|
( GoalExpr0 = unify(_, _, _, _, _)
|
|
; GoalExpr0 = plain_call(_, _, _, _, _, _)
|
|
; GoalExpr0 = generic_call(_, _, _, _, _)
|
|
; GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _)
|
|
),
|
|
GoalExpr = GoalExpr0
|
|
;
|
|
GoalExpr0 = switch(Var, CanFail, Cases0),
|
|
split_switch_arms_in_cases(ToSplitArms, Var, Cases0,
|
|
cord.init, CasesCord),
|
|
Cases = cord.list(CasesCord),
|
|
GoalExpr = switch(Var, CanFail, Cases)
|
|
;
|
|
GoalExpr0 = conj(ConjType, Goals0),
|
|
split_switch_arms_in_goals(ToSplitArms, Goals0, Goals),
|
|
GoalExpr = conj(ConjType, Goals)
|
|
;
|
|
GoalExpr0 = disj(Goals0),
|
|
split_switch_arms_in_goals(ToSplitArms, Goals0, Goals),
|
|
GoalExpr = disj(Goals)
|
|
;
|
|
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
|
|
split_switch_arms_in_goal(ToSplitArms, Cond0, Cond),
|
|
split_switch_arms_in_goal(ToSplitArms, Then0, Then),
|
|
split_switch_arms_in_goal(ToSplitArms, Else0, Else),
|
|
GoalExpr = if_then_else(Vars, Cond, Then, Else)
|
|
;
|
|
GoalExpr0 = negation(SubGoal0),
|
|
split_switch_arms_in_goal(ToSplitArms, SubGoal0, SubGoal),
|
|
GoalExpr = negation(SubGoal)
|
|
;
|
|
GoalExpr0 = scope(Reason, SubGoal0),
|
|
split_switch_arms_in_goal(ToSplitArms, SubGoal0, SubGoal),
|
|
GoalExpr = scope(Reason, SubGoal)
|
|
;
|
|
GoalExpr0 = shorthand(ShortHand0),
|
|
(
|
|
ShortHand0 = atomic_goal(GoalType, Outer, Inner,
|
|
MaybeOutputVars, MainGoal0, OrElseGoals0, OrElseInners),
|
|
split_switch_arms_in_goal(ToSplitArms, MainGoal0, MainGoal),
|
|
split_switch_arms_in_goals(ToSplitArms, OrElseGoals0, OrElseGoals),
|
|
ShortHand = atomic_goal(GoalType, Outer, Inner,
|
|
MaybeOutputVars, MainGoal, OrElseGoals, OrElseInners)
|
|
;
|
|
ShortHand0 = try_goal(_, _, _),
|
|
% These should have been expanded out by now.
|
|
unexpected($pred, "try_goal")
|
|
;
|
|
ShortHand0 = bi_implication(_, _),
|
|
% These should have been expanded out by now.
|
|
unexpected($pred, "bi_implication")
|
|
),
|
|
GoalExpr = shorthand(ShortHand)
|
|
),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0).
|
|
|
|
:- pred split_switch_arms_in_goals(set(switch_arm)::in,
|
|
list(hlds_goal)::in, list(hlds_goal)::out) is det.
|
|
|
|
split_switch_arms_in_goals(_ToSplitArms, [], []).
|
|
split_switch_arms_in_goals(ToSplitArms, [Goal0 | Goals0], [Goal | Goals]) :-
|
|
split_switch_arms_in_goal(ToSplitArms, Goal0, Goal),
|
|
split_switch_arms_in_goals(ToSplitArms, Goals0, Goals).
|
|
|
|
:- pred split_switch_arms_in_cases(set(switch_arm)::in, prog_var::in,
|
|
list(case)::in, cord(case)::in, cord(case)::out) is det.
|
|
|
|
split_switch_arms_in_cases(_ToSplitArms, _Var, [], !CasesCord).
|
|
split_switch_arms_in_cases(ToSplitArms, Var, [Case0 | Cases0], !CasesCord) :-
|
|
split_switch_arms_in_case(ToSplitArms, Var, Case0, !CasesCord),
|
|
split_switch_arms_in_cases(ToSplitArms, Var, Cases0, !CasesCord).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% split_switch_arms_in_case(ToSplitArms, Var, Case0, !CasesCord):
|
|
%
|
|
% This is the active part of the split_switch_arms_in_X family
|
|
% of predicates; the rest of the code in that family is needed
|
|
% only to let us arrive here.
|
|
%
|
|
% If this case represents a switch arm that we should split, then
|
|
%
|
|
% - find out how its cons_ids should be partitioned,
|
|
% - perform the partitioning itself into several switch arms, and
|
|
% - restrict the code each resulting switch arms to refer to only
|
|
% the cons_id in its own partition.
|
|
%
|
|
% The first step is done by partition_cons_ids_for_var_in_goal,
|
|
% the second and third are done by gather_partition_restricted_cases.
|
|
%
|
|
:- pred split_switch_arms_in_case(set(switch_arm)::in, prog_var::in,
|
|
case::in, cord(case)::in, cord(case)::out) is det.
|
|
|
|
split_switch_arms_in_case(ToSplitArms, Var, Case0, !CasesCord) :-
|
|
Case0 = case(MainConsId, OtherConsIds, Goal0),
|
|
set.list_to_set([MainConsId | OtherConsIds], ConsIds),
|
|
Arm = switch_arm(Var, ConsIds),
|
|
( if set.contains(ToSplitArms, Arm) then
|
|
% Split up any switch arms that we can split up on *other* variables.
|
|
% It is better to do so before this arm is split up, because
|
|
% we can do so once and copy the results, rather than having to
|
|
% repeat the same work N times if Partitions below has N elements.
|
|
%
|
|
% The switch arms on Var itself inside Goal0 will be broken up
|
|
% as finely as possibly by gather_partition_restricted_cases below.
|
|
set.filter(switch_arm_is_on_var(Var), ToSplitArms,
|
|
_ToSplitArmsOnVar, ToSplitArmsNotOnVar),
|
|
( if set.is_empty(ToSplitArmsNotOnVar) then
|
|
Goal = Goal0
|
|
else
|
|
split_switch_arms_in_goal(ToSplitArmsNotOnVar, Goal0, Goal)
|
|
),
|
|
|
|
% Start with the initial set of partitions containing only one set,
|
|
% the set of all cons_ids in this arm. Then refine this partitioning
|
|
% based on the switches on Var found in Goal0.
|
|
Partitions0 = set.make_singleton_set(ConsIds),
|
|
partition_cons_ids_for_var_in_goal(Var, Goal, Partitions0, Partitions),
|
|
|
|
( if set.count(Partitions, 1) then
|
|
% Normally, an inner switch on Var inside an arm of a outer switch
|
|
% on Var makes distinctions that the outer switch does not,
|
|
% but it does not *have* to, and in this case, it doesn't.
|
|
% In this case, there is no basis on which to split up this arm.
|
|
%
|
|
% This code gets the same result as the else part would,
|
|
% only faster.
|
|
Case = case(MainConsId, OtherConsIds, Goal),
|
|
cord.snoc(Case, !CasesCord)
|
|
else
|
|
% Replace the otiginal Case with one case for each partition in
|
|
% Partitions.
|
|
set.fold(gather_partition_restricted_cases(Var, Goal), Partitions,
|
|
!CasesCord)
|
|
)
|
|
else
|
|
split_switch_arms_in_goal(ToSplitArms, Goal0, Goal),
|
|
Case = case(MainConsId, OtherConsIds, Goal),
|
|
cord.snoc(Case, !CasesCord)
|
|
).
|
|
|
|
:- pred switch_arm_is_on_var(prog_var::in, switch_arm::in) is semidet.
|
|
|
|
switch_arm_is_on_var(Var, Arm) :-
|
|
Arm = switch_arm(ArmVar, _),
|
|
ArmVar = Var.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% partition_cons_ids_for_var_in_goal(Var, Goal, !Partitions):
|
|
%
|
|
% Refine !Partitions based on all the distinctions made between its
|
|
% cons_ids by switches on Var inside Goal.
|
|
%
|
|
:- pred partition_cons_ids_for_var_in_goal(prog_var::in, hlds_goal::in,
|
|
set(set(cons_id))::in, set(set(cons_id))::out) is det.
|
|
|
|
partition_cons_ids_for_var_in_goal(Var, Goal, !Partitions) :-
|
|
Goal = hlds_goal(GoalExpr, _GoalInfo),
|
|
(
|
|
( GoalExpr = unify(_, _, _, _, _)
|
|
; GoalExpr = plain_call(_, _, _, _, _, _)
|
|
; GoalExpr = generic_call(_, _, _, _, _)
|
|
; GoalExpr = call_foreign_proc(_, _, _, _, _, _, _)
|
|
)
|
|
;
|
|
GoalExpr = switch(SwitchVar, _CanFail, Cases),
|
|
( if Var = SwitchVar then
|
|
partition_cons_ids_of_cases(Cases, !Partitions)
|
|
else
|
|
true
|
|
),
|
|
partition_cons_ids_for_var_in_cases(Var, Cases, !Partitions)
|
|
;
|
|
GoalExpr = conj(_ConjType, Goals),
|
|
partition_cons_ids_for_var_in_goals(Var, Goals, !Partitions)
|
|
;
|
|
GoalExpr = disj(Goals),
|
|
partition_cons_ids_for_var_in_goals(Var, Goals, !Partitions)
|
|
;
|
|
GoalExpr = if_then_else(_Vars, Cond, Then, Else),
|
|
partition_cons_ids_for_var_in_goal(Var, Cond, !Partitions),
|
|
partition_cons_ids_for_var_in_goal(Var, Then, !Partitions),
|
|
partition_cons_ids_for_var_in_goal(Var, Else, !Partitions)
|
|
;
|
|
GoalExpr = negation(SubGoal),
|
|
partition_cons_ids_for_var_in_goal(Var, SubGoal, !Partitions)
|
|
;
|
|
GoalExpr = scope(_Reason, SubGoal),
|
|
partition_cons_ids_for_var_in_goal(Var, SubGoal, !Partitions)
|
|
;
|
|
GoalExpr = shorthand(ShortHand),
|
|
(
|
|
ShortHand = atomic_goal(_GoalType, _Outer, _Inner,
|
|
_MaybeOutputVars, MainGoal, OrElseGoals, _OrElseInners),
|
|
partition_cons_ids_for_var_in_goal(Var, MainGoal, !Partitions),
|
|
partition_cons_ids_for_var_in_goals(Var, OrElseGoals, !Partitions)
|
|
;
|
|
ShortHand = try_goal(_, _, _),
|
|
% These should have been expanded out by now.
|
|
unexpected($pred, "try_goal")
|
|
;
|
|
ShortHand = bi_implication(_, _),
|
|
% These should have been expanded out by now.
|
|
unexpected($pred, "bi_implication")
|
|
)
|
|
).
|
|
|
|
:- pred partition_cons_ids_for_var_in_goals(prog_var::in, list(hlds_goal)::in,
|
|
set(set(cons_id))::in, set(set(cons_id))::out) is det.
|
|
|
|
partition_cons_ids_for_var_in_goals(_Var, [], !Partitions).
|
|
partition_cons_ids_for_var_in_goals(Var, [Goal | Goals], !Partitions) :-
|
|
partition_cons_ids_for_var_in_goal(Var, Goal, !Partitions),
|
|
partition_cons_ids_for_var_in_goals(Var, Goals, !Partitions).
|
|
|
|
:- pred partition_cons_ids_for_var_in_cases(prog_var::in, list(case)::in,
|
|
set(set(cons_id))::in, set(set(cons_id))::out) is det.
|
|
|
|
partition_cons_ids_for_var_in_cases(_Var, [], !Partitions).
|
|
partition_cons_ids_for_var_in_cases(Var, [Case | Cases], !Partitions) :-
|
|
Case = case(_MainConsId, _OtherConsIds, Goal),
|
|
partition_cons_ids_for_var_in_goal(Var, Goal, !Partitions),
|
|
partition_cons_ids_for_var_in_cases(Var, Cases, !Partitions).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% partition_cons_ids_of_cases(Cases, !Partitions):
|
|
%
|
|
% Refine !Partitions by making all the distinctions between its cons_ids
|
|
% that Cases makes. In other words, if two cons_ids are in different cases,
|
|
% then ensure that those two cons_ids are in different partitions in
|
|
% !:Partitions, even if they were in the same partition in !.Partitions.
|
|
%
|
|
:- pred partition_cons_ids_of_cases(list(case)::in,
|
|
set(set(cons_id))::in, set(set(cons_id))::out) is det.
|
|
|
|
partition_cons_ids_of_cases([], !Partitions).
|
|
partition_cons_ids_of_cases([Case | Cases], !Partitions) :-
|
|
Case = case(MainConsId, OtherConsIds, _Goal),
|
|
set.list_to_set([MainConsId | OtherConsIds], ArmConsIds),
|
|
set.fold(add_in_and_or_out_cons_ids(ArmConsIds), !.Partitions,
|
|
set.init, !:Partitions),
|
|
partition_cons_ids_of_cases(Cases, !Partitions).
|
|
|
|
:- pred add_in_and_or_out_cons_ids(set(cons_id)::in,set(cons_id)::in,
|
|
set(set(cons_id))::in, set(set(cons_id))::out) is det.
|
|
|
|
add_in_and_or_out_cons_ids(ArmConsIds, Partition0, !Partitions) :-
|
|
divide_by_set(ArmConsIds, Partition0, PartitionIn, PartitionOut),
|
|
% Since Partition0 has to be nonempty, at least one of Partition{In,Out}
|
|
% also has to be nonempty, but both can be nonempty, and the partitioning
|
|
% process makes progress only if they both *are* nonempty.
|
|
( if set.is_empty(PartitionIn) then
|
|
true
|
|
else
|
|
set.insert(PartitionIn, !Partitions)
|
|
),
|
|
( if set.is_empty(PartitionOut) then
|
|
true
|
|
else
|
|
set.insert(PartitionOut, !Partitions)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% gather_partition_restricted_cases(Var, Goal0, Partition, !CasesCord):
|
|
%
|
|
% Restrict Goal0 to refer only to the cons_ids in Partition in its
|
|
% switches on Var, and add the resulting goal as a switch arm
|
|
% to !CasesCord for the cons_ids in Partition.
|
|
%
|
|
:- pred gather_partition_restricted_cases(prog_var::in, hlds_goal::in,
|
|
set(cons_id)::in, cord(case)::in, cord(case)::out) is det.
|
|
|
|
gather_partition_restricted_cases(Var, Goal0, Partition, !CasesCord) :-
|
|
set.to_sorted_list(Partition, ConsIds),
|
|
(
|
|
ConsIds = [],
|
|
unexpected($pred, "Partition is empty")
|
|
;
|
|
ConsIds = [MainConsId | OtherConsIds],
|
|
restrict_switches_on_var_in_goal(Var, Partition, Goal0, Goal),
|
|
Case = case(MainConsId, OtherConsIds, Goal),
|
|
cord.snoc(Case, !CasesCord)
|
|
).
|
|
|
|
% restrict_switches_on_var_in_goal(Var, ConsIds, !Goal):
|
|
%
|
|
% Restrict any switches on Var in !.Goal to refer to only the cons_ids
|
|
% in ConsIds in it cases.
|
|
%
|
|
:- pred restrict_switches_on_var_in_goal(prog_var::in, set(cons_id)::in,
|
|
hlds_goal::in, hlds_goal::out) is det.
|
|
|
|
restrict_switches_on_var_in_goal(Var, Partition, Goal0, Goal) :-
|
|
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
|
|
(
|
|
( GoalExpr0 = unify(_, _, _, _, _)
|
|
; GoalExpr0 = plain_call(_, _, _, _, _, _)
|
|
; GoalExpr0 = generic_call(_, _, _, _, _)
|
|
; GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _)
|
|
),
|
|
GoalExpr = GoalExpr0
|
|
;
|
|
GoalExpr0 = switch(SwitchVar, CanFail, Cases0),
|
|
( if Var = SwitchVar then
|
|
restrict_switches_on_var_in_restricted_cases(Var, Partition,
|
|
Cases0, cord.init, CasesCord),
|
|
Cases = cord.list(CasesCord),
|
|
% This optimization effectively deletes the switch construct,
|
|
% leaving either one of its arms, or none.
|
|
(
|
|
Cases = [],
|
|
GoalExpr = fail_goal_expr
|
|
;
|
|
Cases = [Case],
|
|
Case = case(_, _, CaseGoal),
|
|
CaseGoal = hlds_goal(GoalExpr, _)
|
|
;
|
|
Cases = [_, _ | _],
|
|
% Every distinction made between the cons_ids of Cases
|
|
% should have been made by the top-level switch on Var
|
|
% found by split_switch_arms_in_goal, and each case
|
|
% that replaced that original top-level switch arm
|
|
% should be for a set of cons_ids that wholly fits
|
|
% within the cons_ids of ONE case. If execution gets here,
|
|
% then something in the above process must have gone wrong.
|
|
unexpected($pred, "Cases = [_, _ | _]")
|
|
)
|
|
else
|
|
restrict_switches_on_var_in_cases(Var, Partition, Cases0, Cases),
|
|
GoalExpr = switch(SwitchVar, CanFail, Cases)
|
|
)
|
|
;
|
|
GoalExpr0 = conj(ConjType, Goals0),
|
|
(
|
|
ConjType = plain_conj,
|
|
restrict_switches_on_var_in_conj(Var, Partition, Goals0, Goals)
|
|
;
|
|
ConjType = parallel_conj,
|
|
restrict_switches_on_var_in_goals(Var, Partition, Goals0, Goals)
|
|
),
|
|
GoalExpr = conj(ConjType, Goals)
|
|
;
|
|
GoalExpr0 = disj(Goals0),
|
|
restrict_switches_on_var_in_goals(Var, Partition, Goals0, Goals),
|
|
GoalExpr = disj(Goals)
|
|
;
|
|
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
|
|
restrict_switches_on_var_in_goal(Var, Partition, Cond0, Cond),
|
|
restrict_switches_on_var_in_goal(Var, Partition, Then0, Then),
|
|
restrict_switches_on_var_in_goal(Var, Partition, Else0, Else),
|
|
GoalExpr = if_then_else(Vars, Cond, Then, Else)
|
|
;
|
|
GoalExpr0 = negation(SubGoal0),
|
|
restrict_switches_on_var_in_goal(Var, Partition, SubGoal0, SubGoal),
|
|
GoalExpr = negation(SubGoal)
|
|
;
|
|
GoalExpr0 = scope(Reason, SubGoal0),
|
|
restrict_switches_on_var_in_goal(Var, Partition, SubGoal0, SubGoal),
|
|
GoalExpr = scope(Reason, SubGoal)
|
|
;
|
|
GoalExpr0 = shorthand(ShortHand0),
|
|
(
|
|
ShortHand0 = atomic_goal(GoalType, Outer, Inner,
|
|
MaybeOutputVars, MainGoal0, OrElseGoals0, OrElseInners),
|
|
restrict_switches_on_var_in_goal(Var, Partition,
|
|
MainGoal0, MainGoal),
|
|
restrict_switches_on_var_in_goals(Var, Partition,
|
|
OrElseGoals0, OrElseGoals),
|
|
ShortHand = atomic_goal(GoalType, Outer, Inner,
|
|
MaybeOutputVars, MainGoal, OrElseGoals, OrElseInners)
|
|
;
|
|
ShortHand0 = try_goal(_, _, _),
|
|
% These should have been expanded out by now.
|
|
unexpected($pred, "try_goal")
|
|
;
|
|
ShortHand0 = bi_implication(_, _),
|
|
% These should have been expanded out by now.
|
|
unexpected($pred, "bi_implication")
|
|
),
|
|
GoalExpr = shorthand(ShortHand)
|
|
),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0).
|
|
|
|
:- pred restrict_switches_on_var_in_conj(prog_var::in, set(cons_id)::in,
|
|
list(hlds_goal)::in, list(hlds_goal)::out) is det.
|
|
|
|
restrict_switches_on_var_in_conj(_Var, _Partition, [], []).
|
|
restrict_switches_on_var_in_conj(Var, Partition,
|
|
[Goal0 | Goals0], Goals) :-
|
|
restrict_switches_on_var_in_goal(Var, Partition, Goal0, HeadGoal),
|
|
restrict_switches_on_var_in_conj(Var, Partition, Goals0, TailGoals),
|
|
% Flatten sequential conjunctions.
|
|
( if HeadGoal = hlds_goal(conj(plain_conj, HeadConjuncts), _GoalInfo) then
|
|
Goals = HeadConjuncts ++ TailGoals
|
|
else
|
|
Goals = [HeadGoal | TailGoals]
|
|
).
|
|
|
|
:- pred restrict_switches_on_var_in_goals(prog_var::in, set(cons_id)::in,
|
|
list(hlds_goal)::in, list(hlds_goal)::out) is det.
|
|
|
|
restrict_switches_on_var_in_goals(_Var, _Partition, [], []).
|
|
restrict_switches_on_var_in_goals(Var, Partition,
|
|
[Goal0 | Goals0], [Goal | Goals]) :-
|
|
restrict_switches_on_var_in_goal(Var, Partition, Goal0, Goal),
|
|
restrict_switches_on_var_in_goals(Var, Partition, Goals0, Goals).
|
|
|
|
:- pred restrict_switches_on_var_in_cases(prog_var::in, set(cons_id)::in,
|
|
list(case)::in, list(case)::out) is det.
|
|
|
|
restrict_switches_on_var_in_cases(_Var, _Partition, [], []).
|
|
restrict_switches_on_var_in_cases(Var, Partition,
|
|
[Case0 | Cases0], [Case | Cases]) :-
|
|
Case0 = case(MainConsId, OtherPartition, Goal0),
|
|
restrict_switches_on_var_in_goal(Var, Partition, Goal0, Goal),
|
|
Case = case(MainConsId, OtherPartition, Goal),
|
|
restrict_switches_on_var_in_cases(Var, Partition, Cases0, Cases).
|
|
|
|
:- pred restrict_switches_on_var_in_restricted_cases(prog_var::in,
|
|
set(cons_id)::in, list(case)::in, cord(case)::in, cord(case)::out) is det.
|
|
|
|
restrict_switches_on_var_in_restricted_cases(_Var, _Partition, [], !CasesCord).
|
|
restrict_switches_on_var_in_restricted_cases(Var, Partition,
|
|
[Case0 | Cases0], !CasesCord) :-
|
|
Case0 = case(MainConsId0, OtherConsIds0, Goal0),
|
|
set.list_to_set([MainConsId0 | OtherConsIds0], ArmConsIds0),
|
|
set.intersect(Partition, ArmConsIds0, ArmConsIds),
|
|
set.to_sorted_list(ArmConsIds, ArmConsIdsList),
|
|
(
|
|
ArmConsIdsList = []
|
|
% Since none of the cons_ids of this case are allowed by the partition
|
|
% of the top switch arm for Var that we are in, delete this case
|
|
% by not adding it to !CasesCord.
|
|
;
|
|
ArmConsIdsList = [MainConsId | OtherConsIds],
|
|
restrict_switches_on_var_in_goal(Var, Partition, Goal0, Goal),
|
|
Case = case(MainConsId, OtherConsIds, Goal),
|
|
cord.snoc(Case, !CasesCord)
|
|
),
|
|
restrict_switches_on_var_in_restricted_cases(Var, Partition, Cases0,
|
|
!CasesCord).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
:- end_module check_hlds.simplify.split_switch_arms.
|
|
%---------------------------------------------------------------------------%
|