Files
mercury/compiler/simplify_goal_conj.m
2026-01-15 05:52:08 +11:00

1138 lines
50 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2014-2015, 2017, 2019-2026 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: simplify_goal_conj.m.
%
% This module handles simplification of both plain and parallel conjunctions.
%
%---------------------------------------------------------------------------%
:- module check_hlds.simplify.simplify_goal_conj.
:- interface.
:- import_module check_hlds.simplify.common.
:- import_module check_hlds.simplify.simplify_info.
:- import_module hlds.
:- import_module hlds.hlds_goal.
:- import_module hlds.instmap.
:- import_module list.
% Handle simplification of plain conjunctions.
%
:- pred simplify_goal_plain_conj(list(hlds_goal)::in, hlds_goal_expr::out,
hlds_goal_info::in, hlds_goal_info::out,
simplify_nested_context::in, instmap::in,
common_info::in, common_info::out,
simplify_info::in, simplify_info::out) is det.
% Handle simplification of parallel conjunctions.
%
:- pred simplify_goal_parallel_conj(list(hlds_goal)::in, hlds_goal_expr::out,
hlds_goal_info::in, hlds_goal_info::out,
simplify_nested_context::in, instmap::in,
common_info::in, common_info::out,
simplify_info::in, simplify_info::out) is det.
%---------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.simplify.simplify_goal.
:- import_module hlds.goal_refs.
:- import_module hlds.goal_util.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_out.
:- import_module hlds.hlds_out.hlds_out_goal.
:- import_module hlds.hlds_out.hlds_out_util.
:- import_module hlds.hlds_pred.
:- import_module hlds.hlds_rtti.
:- import_module hlds.make_goal.
:- import_module hlds.passes_aux.
:- import_module libs.
:- import_module libs.trace_params.
:- import_module parse_tree.
:- import_module parse_tree.parse_tree_out_info.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_detism.
:- import_module parse_tree.prog_rename.
:- import_module parse_tree.set_of_var.
:- import_module parse_tree.var_db.
:- import_module parse_tree.var_table.
:- import_module bool.
:- import_module cord.
:- import_module int.
:- import_module map.
:- import_module one_or_more.
:- import_module set.
:- import_module string.
:- import_module uint.
%---------------------------------------------------------------------------%
simplify_goal_plain_conj(Goals0, GoalExpr, GoalInfo0, GoalInfo,
NestedContext0, InstMap0, !Common, !Info) :-
( if simplify_do_excess_assign(!.Info) then
excess_assigns_in_conj(GoalInfo0, Goals0, Goals1, !Info)
else
Goals1 = Goals0
),
simplify_conj(cord.empty, Goals1, Goals, GoalInfo0,
NestedContext0, InstMap0, !Common, !Info),
(
Goals = [],
Context = goal_info_get_context(GoalInfo0),
hlds_goal(GoalExpr, GoalInfo) = true_goal_with_context(Context)
;
Goals = [SingleGoal],
% A singleton conjunction is equivalent to the goal itself.
SingleGoal = hlds_goal(SingleGoalExpr, SingleGoalInfo),
simplify_maybe_wrap_goal(GoalInfo0, SingleGoalInfo, SingleGoalExpr,
GoalExpr, GoalInfo, !Info)
;
Goals = [_, _ | _],
% Conjunctions that cannot produce solutions may nevertheless contain
% nondet and multi goals. If this happens, we put the conjunction
% inside a commit scope, since the code generators need to know
% where they should change the code's execution mechanism.
Detism = goal_info_get_determinism(GoalInfo0),
( if
% The condition that we expect to fail most frequently is first.
determinism_components(Detism, CanFail, at_most_zero),
simplify_do_mark_code_model_changes(!.Info),
contains_multisoln_goal(Goals)
then
determinism_components(InnerDetism, CanFail, at_most_many),
goal_info_set_determinism(InnerDetism, GoalInfo0, InnerInfo),
InnerGoal = hlds_goal(conj(plain_conj, Goals), InnerInfo),
GoalExpr = scope(commit(do_not_force_pruning), InnerGoal),
% We may have deleted goals that contain what could be the
% last references to variables. This may require adjustments
% to the nonlocals sets of not only this goal, but of the other
% goals containing it. Likewise, it may require adjustments
% of the instmap_deltas of such containing goals.
simplify_info_set_rerun_quant_instmap_delta(!Info)
else
GoalExpr = conj(plain_conj, Goals)
),
GoalInfo = GoalInfo0
),
trace [compile_time(flag("debug_simplify_conj")), io(!IO)] (
simplify_info_get_module_info(!.Info, ModuleInfo),
get_debug_output_stream(ModuleInfo, Stream, !IO),
simplify_info_get_var_table(!.Info, VarTable),
simplify_info_get_tvarset(!.Info, TVarSet),
simplify_info_get_inst_varset(!.Info, InstVarSet),
VarNameSrc = vns_var_table(VarTable),
DumpGoalNl = dump_goal_nl(Stream, ModuleInfo, VarNameSrc,
TVarSet, InstVarSet),
io.write_string(Stream, "\n------------------------\n", !IO),
io.write_string(Stream, "\nBEFORE SIMPLIFY_GOAL_PLAIN_CONJ\n\n", !IO),
list.foldl(DumpGoalNl, Goals0, !IO),
io.write_string(Stream, "\nAFTER EXCESS ASSIGN\n\n", !IO),
list.foldl(DumpGoalNl, Goals1, !IO),
io.write_string(Stream, "\nAFTER SIMPLIFY_CONJ\n\n", !IO),
list.foldl(DumpGoalNl, Goals, !IO),
io.flush_output(Stream, !IO)
).
:- pred contains_multisoln_goal(list(hlds_goal)::in) is semidet.
contains_multisoln_goal([Goal | Goals]) :-
Goal = hlds_goal(_GoalExpr, GoalInfo),
Detism = goal_info_get_determinism(GoalInfo),
determinism_components(Detism, _, SolnCount),
( if SolnCount = at_most_many then
true
else
contains_multisoln_goal(Goals)
).
%---------------------------------------------------------------------------%
:- pred simplify_conj(cord(hlds_goal)::in, list(hlds_goal)::in,
list(hlds_goal)::out, hlds_goal_info::in,
simplify_nested_context::in, instmap::in,
common_info::in, common_info::out,
simplify_info::in, simplify_info::out) is det.
simplify_conj(!.PrevGoals, [], Goals, _ConjInfo,
_NestedContext0, _InstMap0, !Common, !Info) :-
Goals = cord.list(!.PrevGoals).
simplify_conj(!.PrevGoals, [HeadGoal0 | TailGoals0], Goals, ConjInfo,
NestedContext0, InstMap0, !Common, !Info) :-
% Flatten nested conjunctions in the original code.
( if HeadGoal0 = hlds_goal(conj(plain_conj, HeadSubGoals0), _) then
HeadTailGoals1 = HeadSubGoals0 ++ TailGoals0,
simplify_conj(!.PrevGoals, HeadTailGoals1, Goals, ConjInfo,
NestedContext0, InstMap0, !Common, !Info)
else
Common0 = !.Common,
Info0 = !.Info,
simplify_goal(HeadGoal0, HeadGoal1, NestedContext0, InstMap0,
!Common, !Info),
HeadGoal1 = hlds_goal(HeadGoalExpr1, HeadGoalInfo1),
( if
% Flatten nested conjunctions in the transformed code.
HeadGoalExpr1 = conj(plain_conj, HeadSubGoals1)
then
% Note that this simplifies everything inside Goal1 AGAIN.
% We want some of this (for example, structures recorded
% in Common while processing HeadSubGoals1 can sometimes be used
% to optimize TailGoals0), but probably most of the work done
% by this resimplification is wasted.
%
% XXX Look for a way to test for the simplifications enabled
% by the change from HeadGoal0 to HeadGoal1, without trying to redo
% simplifications unaffected by that change.
% For example, we could record the goal_ids of goals that
% simplify.m left untouched in this pass, and return immediately
% if asked to simplify them again. Unfortunately, just figuring
% out which goals are touched and which are not is not easy.
% (Due to the rebuilding of hlds_goal structures from exprs and
% infos, the address may change even if the content does not.)
HeadTailGoals1 = HeadSubGoals1 ++ TailGoals0,
% Note that we cannot process HeadTailGoals1 using the Common
% derived from processing HeadGoal0. If we did that, and HeadGoal0
% contained X = f(Y1), then Common would remember that,
% and when processing HeadTailGoals1, simplify_conj would replace
% that unification with X := X, thus "defining" X in terms of X.
!:Common = Common0,
simplify_conj(!.PrevGoals, HeadTailGoals1, Goals, ConjInfo,
NestedContext0, InstMap0, !Common, !Info)
else
apply_goal_instmap_delta(HeadGoal1, InstMap0, InstMap1),
( if
% Delete unreachable goals.
(
% This test is here mostly for the sake of completeness.
% It rarely finds anything to delete, because
% - we get InstMap1 mostly from Goal1's instmap delta,
% - the delta is created during mode analysis, and
% - mode analysis itself deletes the unreachable conjuncts
% after a conjunct whose instmap delta is unreachable.
instmap_is_unreachable(InstMap1)
;
Detism1 = goal_info_get_determinism(HeadGoalInfo1),
determinism_components(Detism1, _, at_most_zero)
)
then
HeadGoal0 = hlds_goal(_, HeadGoalInfo0),
HeadGoalContext0 = goal_info_get_context(HeadGoalInfo0),
delete_tail_unreachable_goals(!.PrevGoals, HeadGoalContext0,
HeadGoal1, TailGoals0, Goals, !Info),
% We have deleted goals that contain what could be the last
% references to variables. This may require adjustments
% to the nonlocals sets of not only this goal, but of the
% other goals containing it. Likewise, it may require
% adjustments of the instmap_deltas of such containing goals.
simplify_info_set_rerun_quant_instmap_delta(!Info)
else
( if
simplify_do_merge_code_after_switch(!.Info),
HeadGoal1 = hlds_goal(HeadGoalExpr1, HeadGoalInfo1),
HeadGoalExpr1 = switch(_, _, _),
TailGoals0 = [HeadTailGoal0 | TailTailGoals0],
HeadTailGoal0 =
hlds_goal(HeadTailGoalExpr0, HeadTailGoalInfo0),
( HeadTailGoalExpr0 = unify(_, _, _, _, _)
; HeadTailGoalExpr0 = switch(_, _, _)
)
then
(
HeadTailGoalExpr0 = unify(_, _, _, _, _),
try_to_merge_unify_after_switch(!.Info, ConjInfo,
HeadGoalExpr1, HeadGoalInfo1,
HeadTailGoalExpr0, HeadTailGoalInfo0,
TailTailGoals0, MergeResult)
;
HeadTailGoalExpr0 = switch(_, _, _),
% Invoking try_to_merge_switch_after_switch with
% HeadGoalExpr1/HeadGoalInfo1 leads to Mantis bug #567.
% The reason for that is that the call to
% try_to_merge_switch_after_switch can return
% either merge_unsuccessful or
% merge_successful_new_code_not_simplified.
% In the latter case, we start again with Common0,
% which is from *before* we simplified HeadGoal0
% into HeadGoal1. This effectively throws away
% any updates made to !Common since we took Common0
% as a snapshot.
%
% If HeadGoal0 contains some code that constructs
% some ground terms, then the call to simplify_goal
% above can replace that code with a reference to
% a ground_term_const. By keeping the reference
% to this newly allocated ground_term_const in
% HeadGoal1 but throwing away any reference to it
% in !.Common, the reference becomes dangling.
% It can result in a compiler abort, if the code
% generator goes to look up the value of the
% ground_term_const and does not find it, or
% it can result in the dangling ground_term_const's
% id number being reused to hold some other ground
% term, resulting in the dangling reference being
% quietly redirected to another ground term, which is
% extremely unlikely to have the right value and is
% not even all that likely to have the right type.
% (In tests/hard_coded/bug567.m, the ground_term_const
% id is reused, for a term of the right type but wrong
% value.)
HeadGoal0 = hlds_goal(HeadGoalExpr0, HeadGoalInfo0),
( if HeadGoalExpr0 = switch(_, _, _) then
try_to_merge_switch_after_switch(!.Info,
HeadGoalExpr0, HeadGoalInfo0,
HeadTailGoalExpr0, HeadTailGoalInfo0,
MergeResult)
else
MergeResult = merge_unsuccessful
)
),
(
MergeResult = merge_unsuccessful,
cord.snoc(HeadGoal1, !PrevGoals),
TailGoals1 = TailGoals0,
TailInstMap = InstMap1
;
MergeResult = merge_successful_new_code_simplified(
HeadGoal2, !:Info),
cord.snoc(HeadGoal2, !PrevGoals),
% Let the recursive call to simplify_conj below
% process TailTailGoals0 instead of TailGoals0,
% since the effect of HeadTailGoal0 has now been
% folded into HeadGoal2.
TailGoals1 = TailTailGoals0,
TailInstMap = InstMap1
;
MergeResult = merge_successful_new_code_not_simplified(
HeadGoal2),
% HeadGoal2 replaces both HeadGoal1 and HeadTailGoal0
% in front of TailTailGoals0. We have processed
% the HeadGoal1 part of HeadGoal2, but not the
% HeadTailGoal0 part, so we must process HeadGoal2
% again. That means starting again, with the initial
% versions of the instmap, the common structure,
% and the simplify_info structure.
TailGoals1 = [HeadGoal2 | TailTailGoals0],
TailInstMap = InstMap0,
!:Common = Common0,
!:Info = Info0,
% We do know that the merge of the two switches
% requires the recomputation of nonlocals sets and
% of instmap deltas, and that sometimes the
% recomputed determinisms can be more precise as well.
simplify_info_set_rerun_quant_instmap_delta(!Info),
simplify_info_set_rerun_det(!Info)
)
else
cord.snoc(HeadGoal1, !PrevGoals),
TailGoals1 = TailGoals0,
TailInstMap = InstMap1
),
simplify_conj(!.PrevGoals, TailGoals1, Goals, ConjInfo,
NestedContext0, TailInstMap, !Common, !Info)
)
)
).
%---------------------%
:- pred delete_tail_unreachable_goals(cord(hlds_goal)::in,
prog_context::in, hlds_goal::in, list(hlds_goal)::in, list(hlds_goal)::out,
simplify_info::in, simplify_info::out) is det.
delete_tail_unreachable_goals(!.PrevGoals, HeadGoalContext0, HeadGoal1,
TailGoals0, Goals, !Info) :-
simplify_info_get_deleted_call_callees(!.Info, DeletedCallCallees0),
SubGoalCalledProcs = goals_proc_refs(TailGoals0),
set.union(SubGoalCalledProcs, DeletedCallCallees0, DeletedCallCallees),
simplify_info_set_deleted_call_callees(DeletedCallCallees, !Info),
cord.snoc(HeadGoal1, !PrevGoals),
( if
( HeadGoal1 = hlds_goal(disj([]), _)
; TailGoals0 = []
)
then
% XXX If TailGoals0 = [], why don't we add an explicit failure?
true
else
% We insert an explicit failure at the end of the non-succeeding
% conjunction. This is necessary, since the unreachability of the
% instmap could have been derived using inferred determinism
% information. Without the explicit fail goal, mode errors could
% result if mode analysis is rerun, since according to the language
% specification, mode analysis does not use inferred determinism
% information when deciding what can never succeed.
FailGoal = fail_goal_with_context(HeadGoalContext0),
cord.snoc(FailGoal, !PrevGoals)
),
Goals = cord.list(!.PrevGoals).
%---------------------%
:- type merge_code_after_switch_result
---> merge_unsuccessful
; merge_successful_new_code_simplified(
hlds_goal,
simplify_info
)
; merge_successful_new_code_not_simplified(
hlds_goal
).
% Look for situations like this:
%
% (
% ( X = a
% ; X = b
% ),
% Result = yes
% ;
% ( X = c
% ; X = d(_)
% ),
% Result = no
% ),
% Result = no
%
% and transform them into
%
% ( X = a
% ; X = b
% )
%
% The idea is to avoid the performance overhead of setting Result
% in such switches and testing it afterward.
%
% The point of switches like this, in which each arm does nothing
% except set a flag that is tested after the switch, is that if the
% type of X gets a new functor added to it, they get a message if
%
% - either the --inform-incomplete-switch option is given, or
% - the switch is wrapped in a require_complete_switch scope.
%
% In the latter case, the simplification of the scope containing the
% switch will remove the scope wrapper.
%
:- pred try_to_merge_unify_after_switch(simplify_info::in, hlds_goal_info::in,
hlds_goal_expr::in(goal_expr_switch), hlds_goal_info::in,
hlds_goal_expr::in(goal_expr_unify), hlds_goal_info::in,
list(hlds_goal)::in, merge_code_after_switch_result::out) is det.
try_to_merge_unify_after_switch(!.Info, ConjInfo,
HeadGoalExpr1, HeadGoalInfo1,
HeadTailGoalExpr0, _HeadTailGoalInfo0, TailTailGoals0, Result) :-
HeadGoalExpr1 = switch(SwitchVar, SwitchCanFail1, Cases1),
HeadTailGoalExpr0 = unify(_LHSVar, _RHS, _UniMode,
Unification, _UnifyContext),
( if
Unification = deconstruct(TestVar, TestConsId, TestArgs, _ArgModes,
DeconstructCanFail, _CanCGC),
TestArgs = [],
DeconstructCanFail = can_fail,
all_cases_construct_test_var(Cases1, TestVar, TestConsId,
[], RevTruncatedSameCases, [], RevDiffCases),
% If the procedure body can refer to TestVar anywhere other than
% in HeadGoal0 or HeadTailGoal0, then we cannot eliminate the
% assignment to TestVar. Since the mode system does not permit
% conjuncts before HeadGoal0 to refer to TestVar, the places
% we need to check are the conjuncts after HeadTailGoal0 and
% the code outside the conjunction as a whole.
%
% If there are outside references to TestVar, we could still delete
% RevDiffCases from the switch, while keeping the assignment of
% TestConsId to TestVar, either in the non-truncated originals
% of the RevTruncatedSameCases, or in a construct unification
% after the switch that would replace the original deconstruction
% in HeadTailGoal0. However, a bootcheck found no situations
% with outside references to TestVar, so that situation is probably
% too rare to be worth trying to optimize.
ConjNonLocals = goal_info_get_nonlocals(ConjInfo),
not set_of_var.contains(ConjNonLocals, TestVar),
no_conjunct_refers_to_var(TailTailGoals0, TestVar)
then
(
RevDiffCases = [],
TruncatedSwitchCanFail = SwitchCanFail1
;
RevDiffCases = [_ | _],
TruncatedSwitchCanFail = can_fail
),
list.reverse(RevTruncatedSameCases, TruncatedSameCases),
HeadGoalExpr2 =
switch(SwitchVar, TruncatedSwitchCanFail, TruncatedSameCases),
HeadGoal2 = hlds_goal(HeadGoalExpr2, HeadGoalInfo1),
% We need to update the determinism fields of the goals.
% We could try to do that here, but it is simpler to use
% the existing code for the job.
simplify_info_set_rerun_det(!Info),
Result = merge_successful_new_code_simplified(HeadGoal2, !.Info)
else
Result = merge_unsuccessful
).
% See the comment above the call to this predicate.
%
:- pred all_cases_construct_test_var(list(case)::in, prog_var::in, cons_id::in,
list(case)::in, list(case)::out, list(case)::in, list(case)::out)
is semidet.
all_cases_construct_test_var([], _, _, !RevTruncatedSameCases, !RevDiffCases).
all_cases_construct_test_var([Case | Cases], TestVar, TestConsId,
!RevTruncatedSameCases, !RevDiffCases) :-
Case = case(MainConsId, OtherConsIds, Goal),
Goal = hlds_goal(GoalExpr, GoalInfo),
GoalExpr = unify(_LHSVar, _RHS, _UniMode, Unification, _UnifyContext),
Unification = construct(TestVar, CaseConsId, CaseArgs,
_ArgModes, _HowToConstruct, _Unique, _SubInfo),
( if
CaseConsId = TestConsId,
CaseArgs = []
then
Context = goal_info_get_context(GoalInfo),
TrueGoal = true_goal_with_context(Context),
TruncatedCase = case(MainConsId, OtherConsIds, TrueGoal),
!:RevTruncatedSameCases = [TruncatedCase | !.RevTruncatedSameCases]
else
!:RevDiffCases = [Case | !.RevDiffCases]
),
all_cases_construct_test_var(Cases, TestVar, TestConsId,
!RevTruncatedSameCases, !RevDiffCases).
:- pred no_conjunct_refers_to_var(list(hlds_goal)::in, prog_var::in)
is semidet.
no_conjunct_refers_to_var([], _).
no_conjunct_refers_to_var([Goal | Goals], TestVar) :-
Goal = hlds_goal(_GoalExpr, GoalInfo),
NonLocals = goal_info_get_nonlocals(GoalInfo),
not set_of_var.contains(NonLocals, TestVar),
no_conjunct_refers_to_var(Goals, TestVar).
%---------------------%
:- inst merge_switch_switch for merge_code_after_switch_result/0
---> merge_unsuccessful
; merge_successful_new_code_not_simplified(ground).
% Look for situations like this:
%
% (
% ( X = a
% ; X = b
% ),
% ... code fragment 1 ...
% ;
% ( X = c
% ; X = d(_)
% ),
% ... code fragment 2 ...
% ),
% (
% ( X = a
% ; X = b
% ),
% ... code fragment 3 ...
% ;
% X = c
% ... code fragment 4 ...
% ;
% X = d(_)
% ... code fragment 5 ...
% ),
%
% and transform them into
%
% (
% ( X = a
% ; X = b
% ),
% ... code fragment 1 ...
% ... code fragment 3 ...
% ;
% X = c,
% ... code fragment 2 ...
% ... code fragment 4 ...
% ;
% X = d(_),
% ... code fragment 2 ...
% ... code fragment 5 ...
% ),
%
% The idea is that the one merged switch should require fewer branch
% instructions, and therefore less time, than the two original switches,
% if the two original switches have any commonality at all.
%
:- pred try_to_merge_switch_after_switch(simplify_info::in,
hlds_goal_expr::in(goal_expr_switch), hlds_goal_info::in,
hlds_goal_expr::in(goal_expr_switch), hlds_goal_info::in,
merge_code_after_switch_result::out(merge_switch_switch)) is det.
try_to_merge_switch_after_switch(Info, FirstGoalExpr, FirstGoalInfo,
SecondGoalExpr0, SecondGoalInfo0, Result) :-
trace [compile_time(flag("merge_switch_switch")), io(!IO)] (
simplify_info_get_progress_stream(Info, ProgressStream),
simplify_info_get_module_info(Info, ModuleInfo),
module_info_get_globals(ModuleInfo, Globals),
OutInfo = init_hlds_out_info(Globals, output_debug),
simplify_info_get_var_table(Info, VarTable),
simplify_info_get_tvarset(Info, TVarSet),
simplify_info_get_inst_varset(Info, InstVarSet),
FirstGoal = hlds_goal(FirstGoalExpr, FirstGoalInfo),
SecondGoal0 = hlds_goal(SecondGoalExpr0, SecondGoalInfo0),
io.write_string(ProgressStream,
"try_to_merge_switch_after_switch\n", !IO),
io.write_string(ProgressStream, "first switch\n", !IO),
write_goal_nl(OutInfo, ProgressStream, ModuleInfo,
vns_var_table(VarTable), print_name_and_num, TVarSet, InstVarSet,
1u, "\n", FirstGoal, !IO),
io.write_string(ProgressStream, "second switch\n", !IO),
write_goal_nl(OutInfo, ProgressStream, ModuleInfo,
vns_var_table(VarTable), print_name_and_num, TVarSet, InstVarSet,
1u, "\n", SecondGoal0, !IO),
io.flush_output(ProgressStream, !IO)
),
FirstGoalExpr = switch(FirstSwitchVar, FirstSwitchCanFail, FirstCases),
SecondGoalExpr0 =
switch(SecondSwitchVar, SecondSwitchCanFail, SecondCases),
( if
SecondSwitchVar = FirstSwitchVar,
build_maps_first_switch(FirstCases, 1u,
map.init, FirstSwitchGoalMap,
map.init, FirstSwitchConsIdMap),
build_maps_second_switch(SecondCases, 1u, FirstSwitchConsIdMap,
map.init, SecondSwitchGoalMap, map.init, CasesConsIdsMap),
list.length(FirstCases, FirstSwitchNumCases),
list.length(SecondCases, SecondSwitchNumCases),
map.count(CasesConsIdsMap, MergedNumCases),
MergedNumCases < FirstSwitchNumCases * SecondSwitchNumCases
then
( if
FirstSwitchCanFail = cannot_fail,
SecondSwitchCanFail = cannot_fail
then
CanFail = cannot_fail
else
CanFail = can_fail
),
map.foldl(
construct_and_add_merged_switch_case(Info,
FirstSwitchGoalMap, SecondSwitchGoalMap),
CasesConsIdsMap, [], Cases0),
list.sort(Cases0, Cases),
GoalExpr = switch(FirstSwitchVar, CanFail, Cases),
compute_goal_info_for_merged_goal(FirstGoalInfo, SecondGoalInfo0,
GoalInfo),
Goal = hlds_goal(GoalExpr, GoalInfo),
trace [compile_time(flag("merge_switch_switch")), io(!IO)] (
simplify_info_get_progress_stream(Info, ProgressStream),
simplify_info_get_module_info(Info, ModuleInfo),
module_info_get_globals(ModuleInfo, Globals),
OutInfo = init_hlds_out_info(Globals, output_debug),
simplify_info_get_var_table(Info, VarTable),
simplify_info_get_tvarset(Info, TVarSet),
simplify_info_get_inst_varset(Info, InstVarSet),
io.write_string(ProgressStream, "merge successful\n", !IO),
write_goal_nl(OutInfo, ProgressStream, ModuleInfo,
vns_var_table(VarTable), print_name_and_num,
TVarSet, InstVarSet, 1u, "\n", Goal, !IO),
io.flush_output(ProgressStream, !IO)
),
Result = merge_successful_new_code_not_simplified(Goal)
else
trace [compile_time(flag("merge_switch_switch")), io(!IO)] (
simplify_info_get_progress_stream(Info, ProgressStream),
io.write_string(ProgressStream, "merge unsuccessful\n", !IO),
io.flush_output(ProgressStream, !IO)
),
Result = merge_unsuccessful
).
%---------------------%
:- pred build_maps_first_switch(list(case)::in, uint::in,
map(uint, hlds_goal)::in, map(uint, hlds_goal)::out,
map(cons_id, uint)::in, map(cons_id, uint)::out) is det.
build_maps_first_switch([], _, !FirstSwitchGoalMap, !FirstSwitchConsIdMap).
build_maps_first_switch([Case | Cases], CurCaseNum,
!FirstSwitchGoalMap, !FirstSwitchConsIdMap) :-
Case = case(MainConsId, OtherConsIds, Goal),
map.det_insert(CurCaseNum, Goal, !FirstSwitchGoalMap),
map_rev_insert(CurCaseNum, MainConsId, !FirstSwitchConsIdMap),
list.foldl(map_rev_insert(CurCaseNum), OtherConsIds,
!FirstSwitchConsIdMap),
build_maps_first_switch(Cases, CurCaseNum + 1u,
!FirstSwitchGoalMap, !FirstSwitchConsIdMap).
:- pred map_rev_insert(V::in, K::in, map(K, V)::in, map(K, V)::out) is det.
map_rev_insert(Value, Key, !Map) :-
map.det_insert(Key, Value, !Map).
:- pred build_maps_second_switch(list(case)::in, uint::in,
map(cons_id, uint)::in,
map(uint, hlds_goal)::in, map(uint, hlds_goal)::out,
map({uint, uint}, one_or_more(cons_id))::in,
map({uint, uint}, one_or_more(cons_id))::out) is det.
build_maps_second_switch([], _, _, !SecondSwitchGoalMap, !CasesConsIdsMap).
build_maps_second_switch([Case | Cases], CurCaseNum, FirstSwitchConsIdMap,
!SecondSwitchGoalMap, !CasesConsIdsMap) :-
Case = case(MainConsId, OtherConsIds, Goal),
map.det_insert(CurCaseNum, Goal, !SecondSwitchGoalMap),
build_maps_second_switch_cons_id(FirstSwitchConsIdMap, CurCaseNum,
MainConsId, !CasesConsIdsMap),
list.foldl(
build_maps_second_switch_cons_id(FirstSwitchConsIdMap, CurCaseNum),
OtherConsIds, !CasesConsIdsMap),
build_maps_second_switch(Cases, CurCaseNum + 1u, FirstSwitchConsIdMap,
!SecondSwitchGoalMap, !CasesConsIdsMap).
:- pred build_maps_second_switch_cons_id(map(cons_id, uint)::in, uint::in,
cons_id::in,
map({uint, uint}, one_or_more(cons_id))::in,
map({uint, uint}, one_or_more(cons_id))::out) is det.
build_maps_second_switch_cons_id(FirstSwitchConsIdMap, SecondSwitchCaseNum,
ConsId, !CasesConsIdsMap) :-
( if map.search(FirstSwitchConsIdMap, ConsId, FirstSwitchCaseNum) then
CaseNums = {FirstSwitchCaseNum, SecondSwitchCaseNum},
( if map.search(!.CasesConsIdsMap, CaseNums, OldConsIds) then
NewConsIds = one_or_more.cons(ConsId, OldConsIds),
map.det_update(CaseNums, NewConsIds, !CasesConsIdsMap)
else
map.det_insert(CaseNums, one_or_more(ConsId, []), !CasesConsIdsMap)
)
else
% This cons_id exists in the second switch, but not in the first.
% This means one of two things.
%
% The first possibility is that the first switch is a can_fail switch.
% In such a situation, if the value of the switched-on variable
% is ConsId, then execution cannot reach the end of the first switch.
% Mode analysis will notice this fact before the compiler even
% discovers that the first switch *is* a switch, when it is still
% a disjunction. It will generate a warning about the unification
% of the same switched-on variable with ConsId in the disjunction
% that would turn into the second switch, and then delete that
% unification. So when switch detection gets around to that
% disjunction, it won't have a unification with ConsId to make
% ConsId one of the cons_ids of a switch arm. This means if execution
% gets here, then this possibility cannot actually have happened.
%
% For an example of this, have a look at the HLDS dumps of
% tests/hard_coded/bug570_can_fail.m from just before and just after
% the mode checking pass.
%
% The second possibility is that the first switch is a cannot_fail
% switch, but the inst of the switched-on variable at its start
% implies that the switched-on variable *cannot* be bound to ConsId
% there. Again, if the user writes code like this, the compiler will
% notice that the unification of the switched-on variable with ConsId
% in the disjunction that would turn into the second switch cannot
% succeed, generate a warning, and then delete that unification.
% This again would mean that execution cannot get here.
%
% However, there is a way in which execution *can* get here:
% if the code sequence containing the two switches, the first
% not having a ConsId arm but the second having such an arm,
% is constructed internally by the compiler *after* the mode analysis
% pass. This happens in tests/hard_coded/bug570, where this
% construction is done by the deforestation pass, which then
% invokes simplification on its output.
%
% The map.search above used to be a map.lookup, but that test case
% showed that it must be a map.search. The make_headers predicate in
% bug570.m consists of three successive switches on the same variable,
% Prepare, all of which handle all the function symbols of Prepare's
% type. Deforestation moves copies of those switches into each arm
% of the previous switch. In their new positions in the arms of the
% first switch, some arms of the copied second switch become
% unreachable, and are pruned away. Mantis bug #570 happened when
% deforestation tried to move copies of the third switch, which had
% an arm for Prepare = prepare_edit, into each arm of the merged
% first/second switch, one of which occurred in a context in which
% Prepare could *not* be bound to prepare_edit, because it was derived
% from the arm of the first switch on Prepare which was *not*
% prepare_edit.
true
).
%---------------------%
:- pred construct_and_add_merged_switch_case(simplify_info::in,
map(uint, hlds_goal)::in, map(uint, hlds_goal)::in,
{uint, uint}::in, one_or_more(cons_id)::in,
list(case)::in, list(case)::out) is det.
construct_and_add_merged_switch_case(Info,
FirstSwitchGoalMap, SecondSwitchGoalMap,
{FirstSwitchCaseNum, SecondSwitchCaseNum}, OoMConsIds, !Cases) :-
one_or_more.sort(OoMConsIds, SortedOoMConsIds),
SortedOoMConsIds = one_or_more(MainConsId, OtherConsIds),
map.lookup(FirstSwitchGoalMap, FirstSwitchCaseNum, FirstGoal),
map.lookup(SecondSwitchGoalMap, SecondSwitchCaseNum, SecondGoal),
FirstGoal = hlds_goal(FirstGoalExpr, FirstGoalInfo),
SecondGoal = hlds_goal(SecondGoalExpr, SecondGoalInfo),
( if FirstGoalExpr = conj(plain_conj, FirstSubGoals) then
FirstGoals = FirstSubGoals
else
FirstGoals = [FirstGoal]
),
( if SecondGoalExpr = conj(plain_conj, SecondSubGoals) then
SecondGoals = SecondSubGoals
else
SecondGoals = [SecondGoal]
),
GoalExpr = conj(plain_conj, FirstGoals ++ SecondGoals),
compute_goal_info_for_merged_goal(FirstGoalInfo, SecondGoalInfo, GoalInfo),
Goal = hlds_goal(GoalExpr, GoalInfo),
trace [compile_time(flag("simplify_merge_switch")), io(!IO)] (
io.stderr_stream(StdErr, !IO),
simplify_info_get_module_info(Info, ModuleInfo),
simplify_info_get_var_table(Info, VarTable),
VarNameSrc = vns_var_table(VarTable),
simplify_info_get_tvarset(Info, TVarSet),
simplify_info_get_inst_varset(Info, InstVarSet),
io.write_string(StdErr, "\nFirstGoal\n\n", !IO),
dump_goal_nl(StdErr, ModuleInfo, VarNameSrc, TVarSet, InstVarSet,
FirstGoal, !IO),
io.write_string(StdErr, "\nSecondGoal\n\n", !IO),
dump_goal_nl(StdErr, ModuleInfo, VarNameSrc, TVarSet, InstVarSet,
SecondGoal, !IO),
io.write_string(StdErr, "\nMERGED GOAL\n\n", !IO),
dump_goal_nl(StdErr, ModuleInfo, VarNameSrc, TVarSet, InstVarSet,
Goal, !IO)
),
Case = case(MainConsId, OtherConsIds, Goal),
!:Cases = [Case | !.Cases].
:- pred compute_goal_info_for_merged_goal(
hlds_goal_info::in, hlds_goal_info::in, hlds_goal_info::out) is det.
compute_goal_info_for_merged_goal(FirstGoalInfo, SecondGoalInfo, !:GoalInfo) :-
FirstDetism = goal_info_get_determinism(FirstGoalInfo),
SecondDetism = goal_info_get_determinism(SecondGoalInfo),
FirstPurity = goal_info_get_purity(FirstGoalInfo),
SecondPurity = goal_info_get_purity(SecondGoalInfo),
FirstInstMapDelta = goal_info_get_instmap_delta(FirstGoalInfo),
SecondInstMapDelta = goal_info_get_instmap_delta(SecondGoalInfo),
FirstNonLocals = goal_info_get_nonlocals(FirstGoalInfo),
SecondNonLocals = goal_info_get_nonlocals(SecondGoalInfo),
FirstFeatures = goal_info_get_features(FirstGoalInfo),
SecondFeatures = goal_info_get_features(SecondGoalInfo),
% When we are computing the goal_info for the merged switch as a whole,
% it is possible for Detism to be too conservative, though still correct.
%
% Consider a situation where
%
% - the first switch is det, having N det arms and one erroneous arm.
% - the second switch is semidet, having N det arms and one semidet arm;
%
% The conjunction of the two switches will be considered semidet.
% However, in the process of merging the two switches, we may discover
% that in the merged switch, the erroneous arm of the first switch
% is always followed by the semidet arm of the second switch.
% The determinism of the combined arm will then be erroneous.
% Since execution cannot reach the end of that combined arm,
% and since all the combined arms whose ends *can* be reached are det,
% the determinism of the merged switch is actually det.
%
% This over-conservatism should not affect the rest of the simplification
% pass, and code higher up in the call tree will ask determinism analysis
% to be rerun to provide accurate info for later passes.
det_conjunction_detism(FirstDetism, SecondDetism, Detism),
Purity = worst_purity(FirstPurity, SecondPurity),
instmap_delta_apply_instmap_delta(FirstInstMapDelta, SecondInstMapDelta,
test_size, InstMapDelta),
% The NonLocals we compute here may be an overestimate, since it is
% possible for a variable to occur ONLY in FirstGoal and in SecondGoal.
% The code of the simplification pass should not mind this, and code
% higher up in the call tree will ask for quantification to be rerun
% to provide accurate info for later passes.
set_of_var.union(FirstNonLocals, SecondNonLocals, NonLocals),
set.union(FirstFeatures, SecondFeatures, Features),
% It does not matter whether we take FirstGoalInfo or SecondGoalInfo
% as the basis for GoalInfo, because we explicitly set up all of
% the fields that the rest of the compiler invocation can pay attention to.
%
% - The egi_context field is not used by the rest of the simplification
% pass, because it belongs to a compound goal (either a merged
% conjunction or a merged switch) that we don't generate warnings for.
% And all the compiler passes after the first invocation of
% simplification that generate error or warning messages do so for
% for either procedures or calls to procedures, not compound goals.
%
% - The gi_goal_id and egi_rev_goal_path fields are considered valid
% only bwtween a pass that fills them in, and the next pass that
% can make any changes to the procedure's body goal. Simplification
% can make changes, so it would invalidate those fields even if
% we did merge switches.
%
% - The egi_goal_mode and egi_maybe_mode_constr field are not yet used.
%
% - The gi_code_gen_info field is used only by the LLDS backend,
% It is filled in only after the last invocation of simplification.
%
% - the egi_ho_value_map field is filled in by the closure analysis pass,
% and read by the exception analysis and termination passes. There are
% no invocations of the simplification pass between those passes.
%
% - the egi_maybe_ctgc, egi_maybe_rbmm and egi_maybe_dp fields
% are used only within their respective passes, and are not meaningful
% outside those passes.
!:GoalInfo = FirstGoalInfo,
goal_info_set_determinism(Detism, !GoalInfo),
goal_info_set_purity(Purity, !GoalInfo),
goal_info_set_instmap_delta(InstMapDelta, !GoalInfo),
goal_info_set_nonlocals(NonLocals, !GoalInfo),
goal_info_set_features(Features, !GoalInfo).
%---------------------------------------------------------------------------%
% XXX move below its callers
:- type var_renaming == map(prog_var, prog_var).
:- pred find_renamed_var(var_renaming::in, prog_var::in, prog_var::out) is det.
find_renamed_var(Subn, Var0, Var) :-
( if map.search(Subn, Var0, Var1) then
find_renamed_var(Subn, Var1, Var)
else
Var = Var0
).
% Collapse chains of renamings.
%
:- pred renaming_transitive_closure(var_renaming::in, var_renaming::out)
is det.
renaming_transitive_closure(VarRenaming0, VarRenaming) :-
map.map_values_only(find_renamed_var(VarRenaming0),
VarRenaming0, VarRenaming).
%---------------------------------------------------------------------------%
:- pred excess_assigns_in_conj(hlds_goal_info::in,
list(hlds_goal)::in, list(hlds_goal)::out,
simplify_info::in, simplify_info::out) is det.
excess_assigns_in_conj(ConjInfo, Goals0, Goals, !Info) :-
ConjNonLocals = goal_info_get_nonlocals(ConjInfo),
map.init(Subn0),
find_excess_assigns_in_conj(!.Info, ConjNonLocals, Goals0,
[], RevGoals, Subn0, Subn1),
( if map.is_empty(Subn1) then
Goals = Goals0
else
list.reverse(RevGoals, Goals1),
% NOTE Instead of doing the renaming here, we could assign
% a unique goal id to the conjunction and record the fact that
% we should perform Subn on this goal id, and then use
% incremental_rename_vars_in_goal to do all the renamings for
% all conjunctions in the procedure body at once.
%
% This would have the advantage of guaranteeing that the rename
% will NOT visit any part of the procedure body more than once.
% However, it would have the disadvantage of guaranteeing that
% the rename WILL visit EVERY part of the procedure body once.
% This would be a slowdown in the average case, because the average
% number of times that the current code visits a goal in the procedure
% body is significantly less than one, since most conjunctions
% do not have excess assigments. (The profiling data I am looking at
% shows less than 10,000 times we get to this branch in a compiler
% invocation that called simplify_proc_return_msgs more than 50,000
% times, which implies that the average number of times we get here
% per procedure simplification is less than 0.2.)
%
% Until we see a piece of code whose compilation suffers from
% the potential worst case of this approach being realized,
% we prefer to get its better performance in the usual case.
renaming_transitive_closure(Subn1, Subn),
rename_vars_in_goals(need_not_rename, Subn, Goals1, Goals),
map.sorted_keys(Subn0, RemovedVars),
simplify_info_get_var_table(!.Info, VarTable0),
delete_sorted_var_entries(RemovedVars, VarTable0, VarTable),
simplify_info_set_var_table(VarTable, !Info),
simplify_info_get_rtti_varmaps(!.Info, RttiVarMaps0),
apply_substitutions_to_rtti_varmaps(map.init, map.init, Subn,
RttiVarMaps0, RttiVarMaps),
simplify_info_set_rtti_varmaps(RttiVarMaps, !Info)
).
:- pred find_excess_assigns_in_conj(simplify_info::in, set_of_progvar::in,
list(hlds_goal)::in, list(hlds_goal)::in, list(hlds_goal)::out,
var_renaming::in, var_renaming::out) is det.
find_excess_assigns_in_conj(_, _, [], !RevGoals, !Subn).
find_excess_assigns_in_conj(Info, ConjNonLocals, [Goal | Goals],
!RevGoals, !Subn) :-
( if goal_is_excess_assign(Info, ConjNonLocals, Goal, !Subn) then
true
else
!:RevGoals = [Goal | !.RevGoals]
),
find_excess_assigns_in_conj(Info, ConjNonLocals, Goals, !RevGoals, !Subn).
:- pred goal_is_excess_assign(simplify_info::in, set_of_progvar::in,
hlds_goal::in, var_renaming::in, var_renaming::out) is semidet.
goal_is_excess_assign(Info, ConjNonLocals, Goal0, !Subn) :-
Goal0 = hlds_goal(unify(_, _, _, Unif, _), _),
Unif = assign(LeftVar0, RightVar0),
% Check if we have already substituted one or both of the variables.
find_renamed_var(!.Subn, LeftVar0, LeftVar),
find_renamed_var(!.Subn, RightVar0, RightVar),
CanElimLeft =
( if set_of_var.member(ConjNonLocals, LeftVar) then no else yes ),
CanElimRight =
( if set_of_var.member(ConjNonLocals, RightVar) then no else yes ),
simplify_info_get_var_table(Info, VarTable),
(
CanElimLeft = yes,
CanElimRight = yes,
% If we have a choice, try to eliminate an unnamed variable.
( if var_is_named(VarTable, LeftVar) then
ElimVar = RightVar,
ReplacementVar = LeftVar
else
ElimVar = LeftVar,
ReplacementVar = RightVar
)
;
CanElimLeft = yes,
CanElimRight = no,
ElimVar = LeftVar,
ReplacementVar = RightVar
;
CanElimLeft = no,
CanElimRight = yes,
ElimVar = RightVar,
ReplacementVar = LeftVar
;
CanElimLeft = no,
CanElimRight = no,
fail
),
simplify_info_get_eff_trace_level_optimized(Info, EffTraceLevel,
TraceOptimized),
% If the module is being compiled with `--trace deep' and
% `--no-trace-optimized', don't replace a meaningful variable name
% with `HeadVar__n' or an anonymous variable.
not (
eff_trace_level_needs_meaningful_var_names(EffTraceLevel) = yes,
TraceOptimized = not_trace_optimized,
var_is_named(VarTable, ElimVar),
not var_is_named(VarTable, ReplacementVar)
),
map.det_insert(ElimVar, ReplacementVar, !Subn).
:- pred var_is_named(var_table::in, prog_var::in) is semidet.
var_is_named(VarTable, Var) :-
lookup_var_entry(VarTable, Var, Entry),
Name = Entry ^ vte_name,
Name \= "",
not (
string.append("HeadVar__", Suffix, Name),
string.to_int(Suffix, _)
).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
simplify_goal_parallel_conj(Goals0, GoalExpr, GoalInfo0, GoalInfo,
NestedContext0, InstMap0, !Common, !Info) :-
(
Goals0 = [],
Context = goal_info_get_context(GoalInfo0),
hlds_goal(GoalExpr, GoalInfo) = true_goal_with_context(Context)
;
Goals0 = [SingleGoal0],
simplify_goal(SingleGoal0, hlds_goal(SingleGoal, SingleGoalInfo),
NestedContext0, InstMap0, !Common, !Info),
simplify_maybe_wrap_goal(GoalInfo0, SingleGoalInfo, SingleGoal,
GoalExpr, GoalInfo, !Info)
;
Goals0 = [_, _ | _],
( if simplify_do_ignore_par_conjunctions(!.Info) then
simplify_goal_plain_conj(Goals0, GoalExpr, GoalInfo0, GoalInfo,
NestedContext0, InstMap0, !Common, !Info)
else
GoalInfo = GoalInfo0,
simplify_par_conjuncts(Goals0, Goals,
NestedContext0, InstMap0, !.Common, !Info),
GoalExpr = conj(parallel_conj, Goals),
simplify_info_set_has_parallel_conj(has_parallel_conj, !Info)
)
).
% Simplify each conjunct in a parallel conjunction.
%
% We need the InitialCommonInfo because we want to start the simplification
% of each conjunct with the common_info from the start of the parallel
% conjunction as a whole. If we used the common_info from the end of a
% previous conjunct, then common.m could optimize away e.g. duplicate
% cell constructs and calls in the later conjunct, but it would
% replace them with cross-conjunct assignments. This would not only
% incur the need for extra synchronization code, but the later conjunct
% would also BLOCK on this synchronization code until the relevant
% previous conjunct reached the same synchronization point. It is mostly
% this blocking that we want to avoid. Not just the duplicate creation
% of a structure on the heap, but also the duplicate execution of e.g.
% a 10ms call may well be worthwhile if it avoid the need to block
% for 500ms.
%
% XXX: We should consider changing this decision, and allow the
% introduction of dependencies between conjuncts (with the attendant
% extra synchronization cost) if the payoff is large enough, and if
% we risk of blocking is low enough. This would require accurate
% profiling information.
%
% XXX: Even if we do not optimize away duplicate calls, we should
% generate warnings for them.
%
:- pred simplify_par_conjuncts(list(hlds_goal)::in, list(hlds_goal)::out,
simplify_nested_context::in, instmap::in, common_info::in,
simplify_info::in, simplify_info::out) is det.
simplify_par_conjuncts([], [], _NestedContext0, _InstMap0, _Common0, !Info).
simplify_par_conjuncts([Goal0 |Goals0], [Goal | Goals],
NestedContext0, InstMap0, Common0, !Info) :-
simplify_goal(Goal0, Goal,
NestedContext0, InstMap0, Common0, _Common1, !Info),
apply_goal_instmap_delta(Goal, InstMap0, InstMap1),
simplify_par_conjuncts(Goals0, Goals,
NestedContext0, InstMap1, Common0, !Info).
%---------------------------------------------------------------------------%
:- end_module check_hlds.simplify.simplify_goal_conj.
%---------------------------------------------------------------------------%