mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 01:13:30 +00:00
The code that checks whether a bound inst wrapped around
a list of bound_functors matched the ground inst did several things
in a suboptimal fashion.
- It looked up the definition of the type constructor of the relevant type
(the type of the variable the inst is for) more than once. (This was
not easily visible because the lookups were in different predicates.)
This diff factors these out, not for the immesurably small speedup,
but to make possible the fixes for the next two issues.
- To simplify the "is there a bound_functor for each constructor in the type"
check, it sorted the constructors of the type by name and arity. (Lists of
bound_functors are always sorted by name and arity.) Given that most
modules contain more than one bound inst for any given type constructor,
any sorting after the first was unnecessarily repeated work. This diff
therefore extends the representation of du types, which until now has
include only a list of the data constructors in the type definition
in definition order, with a list of those exact same data constructors
in name/arity order.
- Even if a list of bound_functors lists all the constructors of a type,
the bound inst containing them is not equivalent to ground if the inst
of some argument of some bound_inst is not equivalent to ground.
This means that we need to know the actual argument of each constructor.
The du type definition lists argument types that refer to the type
constructor's type parameters; we need the instances of these argument types
that apply to type of the variable at hand, which usually binds concrete
types to those type parameters.
We used to apply the type-parameter-to-actual-type substitution to
each argument of each data constructor in the type before we compared
the resulting filled-in data constructor descriptions against the list of
bound_functors. However, in cases where the comparison fails, the
substitution applications to arguments beyond the point of failure
are all wasted work. This diff therefore applies the substitution
only when its result is about to be needed.
This diff leads to a speedup of about 3.5% on tools/speedtest,
and about 38% (yes, more than a third) when compiling options.m.
compiler/hlds_data.m:
Add the new field to the representation of du types.
Add a utility predicate that helps construct that field, since it is
now needed by two modules (add_type.m and equiv_type_hlds.m).
Delete two functions that were used only by det_check_switch.m,
which this diff moves to that module (in modified form).
compiler/inst_match.m:
Implement the first and third changes listed above, and take advantage
of the second.
The old call to all_du_ctor_arg_types, which this diff replaces,
effectively lied about the list of constructors it returned,
by simply not returning any constructors containing existentially
quantified types, on the grounds that they "were not handled yet".
We now fail explicitly when we find any such constructors.
Perform the check for one-to-one match between bound_functors and
constructors with less argument passing.
compiler/det_check_switch.m:
Move the code deleted from hlds_data.m here, and simplify it,
taking advantage of the new field in du types.
compiler/Mercury.options:
Specify --optimize-constructor-last-call for det_check_switch.m
to optimize the updated moved code.
compiler/add_foreign_enum.m:
compiler/add_special_pred.m:
compiler/add_type.m:
compiler/check_typeclass.m:
compiler/code_info.m:
compiler/dead_proc_elim.m:
compiler/direct_arg_in_out.m:
compiler/du_type_layout.m:
compiler/equiv_type_hlds.m:
compiler/hlds_out_type_table.m:
compiler/inst_check.m:
compiler/intermod.m:
compiler/intermod_decide.m:
compiler/lookup_switch_util.m:
compiler/ml_type_gen.m:
compiler/ml_unify_gen_test.m:
compiler/ml_unify_gen_util.m:
compiler/mlds.m:
compiler/post_term_analysis.m:
compiler/recompilation.usage.m:
compiler/resolve_unify_functor.m:
compiler/simplify_goal_ite.m:
compiler/table_gen.m:
compiler/tag_switch_util.m:
compiler/term_norm.m:
compiler/type_ctor_info.m:
compiler/type_util.m:
compiler/typecheck_coerce.m:
compiler/unify_proc.m:
compiler/unused_imports.m:
compiler/xml_documentation.m:
Conform to the changes above. This mostly means handling
the new field in du types (usually by ignoring it).
544 lines
22 KiB
Mathematica
544 lines
22 KiB
Mathematica
%----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%----------------------------------------------------------------------------%
|
|
% Copyright (C) 2014-2022, 2024-2025 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_ite.m.
|
|
%
|
|
% This module handles simplification of if-then-else goals and negations.
|
|
% (A negation is just like an if-then-else with `fail' as the then-part
|
|
% and `true' as the else-part).
|
|
%
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- module check_hlds.simplify.simplify_goal_ite.
|
|
:- 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.
|
|
|
|
% Handle simplifications of if-then-else goals.
|
|
%
|
|
:- pred simplify_goal_ite(
|
|
hlds_goal_expr::in(goal_expr_ite), 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 simplifications of negations.
|
|
%
|
|
:- pred simplify_goal_neg(
|
|
hlds_goal_expr::in(goal_expr_neg), 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_data.
|
|
:- import_module hlds.hlds_module.
|
|
:- import_module hlds.make_goal.
|
|
:- import_module hlds.type_util.
|
|
:- import_module libs.
|
|
:- import_module libs.options.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.error_spec.
|
|
:- import_module parse_tree.prog_data.
|
|
:- import_module parse_tree.prog_detism.
|
|
:- import_module parse_tree.var_table.
|
|
|
|
:- import_module bool.
|
|
:- import_module list.
|
|
:- import_module maybe.
|
|
:- import_module one_or_more.
|
|
:- import_module require.
|
|
:- import_module set.
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
simplify_goal_ite(GoalExpr0, GoalExpr, GoalInfo0, GoalInfo,
|
|
NestedContext0, InstMap0, Common0, Common, !Info) :-
|
|
% (if A then B else C) is logically equivalent to (A, B ; ~A, C).
|
|
% If the determinism of A means that one of these disjuncts cannot succeed,
|
|
% then we replace the if-then-else with the other disjunct. (We could
|
|
% also eliminate A, but we leave that to the recursive invocations.)
|
|
%
|
|
% Note however that rerunning determinism analysis, which we do
|
|
% at the end of simplification, may introduce more occurrences of these;
|
|
% since we don't iterate simplification and determinism analysis until
|
|
% a fixpoint is reached, we don't guarantee to eliminate all such
|
|
% if-then-elses. Hence the code generator must be prepared to handle
|
|
% the case when the condition of an if-then-else has determinism
|
|
% `det' or `failure'.
|
|
%
|
|
% The conjunction operator in the remaining disjunct ought to be
|
|
% a sequential conjunction, because Mercury's if-then-else always
|
|
% guarantees sequentiality, whereas conjunction only guarantees
|
|
% sequentiality if the --no-reorder-conj option is enabled.
|
|
%
|
|
% However, currently reordering is only done in mode analysis,
|
|
% not in the code generator, so we don't yet need a sequential
|
|
% conjunction construct. This will change when constraint pushing
|
|
% is finished, or when we start doing coroutining.
|
|
|
|
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
|
|
Cond0 = hlds_goal(_, CondInfo0),
|
|
CondDetism0 = goal_info_get_determinism(CondInfo0),
|
|
determinism_components(CondDetism0, CondCanFail0, CondSolns0),
|
|
(
|
|
CondCanFail0 = cannot_fail,
|
|
goal_to_conj_list(Cond0, CondGoals),
|
|
goal_to_conj_list(Then0, ThenGoals),
|
|
Goals = CondGoals ++ ThenGoals,
|
|
simplify_goal(hlds_goal(conj(plain_conj, Goals), GoalInfo0),
|
|
hlds_goal(GoalExpr, GoalInfo), NestedContext0, InstMap0,
|
|
Common0, Common, !Info),
|
|
maybe_warn_about_condition(GoalInfo0, NestedContext0, "cannot fail",
|
|
!Info),
|
|
|
|
simplify_info_get_deleted_call_callees(!.Info, DeletedCallCallees0),
|
|
SubGoalCalledProcs = goal_proc_refs(Else0),
|
|
set.union(SubGoalCalledProcs,
|
|
DeletedCallCallees0, DeletedCallCallees),
|
|
simplify_info_set_deleted_call_callees(DeletedCallCallees, !Info)
|
|
;
|
|
CondCanFail0 = can_fail,
|
|
(
|
|
CondSolns0 = at_most_zero,
|
|
% Optimize away the condition and the `then' part.
|
|
det_negation_det(CondDetism0, MaybeNegDetism),
|
|
( if
|
|
Cond0 = hlds_goal(negation(NegCond), _),
|
|
% XXX BUG! This optimization is only safe if it preserves mode
|
|
% correctness, which means in particular that the negated goal
|
|
% must not clobber any variables. For now I've just disabled
|
|
% the optimization.
|
|
semidet_fail
|
|
then
|
|
Cond = NegCond
|
|
else
|
|
( if
|
|
MaybeNegDetism = yes(NegDetism1),
|
|
(
|
|
NegDetism1 = detism_erroneous,
|
|
instmap_delta_init_unreachable(NegInstMapDelta1)
|
|
;
|
|
NegDetism1 = detism_det,
|
|
instmap_delta_init_reachable(NegInstMapDelta1)
|
|
)
|
|
then
|
|
NegDetism = NegDetism1,
|
|
NegInstMapDelta = NegInstMapDelta1
|
|
else
|
|
unexpected($pred, "cannot get negated determinism")
|
|
),
|
|
goal_info_set_determinism(NegDetism, CondInfo0, NegCondInfo0),
|
|
goal_info_set_instmap_delta(NegInstMapDelta,
|
|
NegCondInfo0, NegCondInfo),
|
|
Cond = hlds_goal(negation(Cond0), NegCondInfo)
|
|
),
|
|
goal_to_conj_list(Else0, ElseList),
|
|
List = [Cond | ElseList],
|
|
simplify_goal(hlds_goal(conj(plain_conj, List), GoalInfo0),
|
|
hlds_goal(GoalExpr, GoalInfo), NestedContext0, InstMap0,
|
|
Common0, Common, !Info),
|
|
maybe_warn_about_condition(GoalInfo0, NestedContext0,
|
|
"cannot succeed", !Info),
|
|
|
|
simplify_info_get_deleted_call_callees(!.Info,
|
|
DeletedCallCallees0),
|
|
SubGoalCalledProcs = goal_proc_refs(Then0),
|
|
set.union(SubGoalCalledProcs,
|
|
DeletedCallCallees0, DeletedCallCallees),
|
|
simplify_info_set_deleted_call_callees(DeletedCallCallees, !Info)
|
|
;
|
|
( CondSolns0 = at_most_one
|
|
; CondSolns0 = at_most_many
|
|
; CondSolns0 = at_most_many_cc
|
|
),
|
|
( if Else0 = hlds_goal(disj([]), _) then
|
|
% (if Cond then Then else fail) is equivalent to (Cond, Then)
|
|
goal_to_conj_list(Cond0, CondGoals),
|
|
goal_to_conj_list(Then0, ThenGoals),
|
|
Goals = CondGoals ++ ThenGoals,
|
|
simplify_goal(hlds_goal(conj(plain_conj, Goals), GoalInfo0),
|
|
hlds_goal(GoalExpr, GoalInfo), NestedContext0, InstMap0,
|
|
Common0, Common, !Info),
|
|
simplify_info_set_rerun_quant_instmap_delta(!Info),
|
|
simplify_info_set_rerun_det(!Info)
|
|
else
|
|
simplify_goal_ordinary_ite(Vars, Cond0, Then0, Else0, GoalExpr,
|
|
GoalInfo0, GoalInfo, NestedContext0, InstMap0,
|
|
Common0, Common, !Info)
|
|
)
|
|
)
|
|
).
|
|
|
|
:- pred maybe_warn_about_condition(hlds_goal_info::in,
|
|
simplify_nested_context::in, string::in,
|
|
simplify_info::in, simplify_info::out) is det.
|
|
|
|
maybe_warn_about_condition(GoalInfo0, NestedContext0, Problem, !Info) :-
|
|
InsideDuplForSwitch = NestedContext0 ^ snc_inside_dupl_for_switch,
|
|
( if
|
|
simplify_do_warn_dodgy_simple_code(!.Info),
|
|
% If InsideDuplForSwitch = yes, then not generate the warning,
|
|
% because it is quite likely to be spurious: though the condition
|
|
% cannot fail/succeed in this arm of the switch, it likely can
|
|
% fail/succeed in other arms that derive from the exact same piece
|
|
% of source code.
|
|
InsideDuplForSwitch = no
|
|
then
|
|
Context = goal_info_get_context(GoalInfo0),
|
|
Pieces = [words("Warning: the condition of this if-then-else")] ++
|
|
color_as_incorrect([words(Problem), suffix(".")]) ++ [nl],
|
|
Spec = spec($pred, severity_warning(warn_dodgy_simple_code),
|
|
phase_simplify(report_only_if_in_all_modes), Context, Pieces),
|
|
simplify_info_add_message(Spec, !Info)
|
|
else
|
|
true
|
|
),
|
|
simplify_info_set_rerun_quant_instmap_delta(!Info),
|
|
simplify_info_set_rerun_det(!Info).
|
|
|
|
%----------------------------------------------------------------------------%
|
|
|
|
:- pred simplify_goal_ordinary_ite(list(prog_var)::in,
|
|
hlds_goal::in, hlds_goal::in, 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.
|
|
|
|
simplify_goal_ordinary_ite(Vars, Cond0, Then0, Else0, GoalExpr,
|
|
GoalInfo0, GoalInfo, NestedContext0, InstMap0, Common0, Common,
|
|
!Info) :-
|
|
% Recursively simplify the sub-goals, and rebuild the resulting
|
|
% if-then-else.
|
|
|
|
simplify_goal(Cond0, Cond, NestedContext0, InstMap0,
|
|
Common0, AfterCondCommon, !Info),
|
|
apply_goal_instmap_delta(Cond, InstMap0, AfterCondInstMap0),
|
|
simplify_goal(Then0, Then, NestedContext0, AfterCondInstMap0,
|
|
AfterCondCommon, _AfterThenCommon, !Info),
|
|
|
|
simplify_goal(Else0, Else, NestedContext0, InstMap0,
|
|
Common0, _AfterElseCommon, !Info),
|
|
|
|
Cond = hlds_goal(_, CondInfo),
|
|
CondDelta = goal_info_get_instmap_delta(CondInfo),
|
|
Then = hlds_goal(_, ThenInfo),
|
|
ThenDelta = goal_info_get_instmap_delta(ThenInfo),
|
|
instmap_delta_apply_instmap_delta(CondDelta, ThenDelta, test_size,
|
|
CondThenDelta),
|
|
Else = hlds_goal(_, ElseInfo),
|
|
ElseDelta = goal_info_get_instmap_delta(ElseInfo),
|
|
NonLocals = goal_info_get_nonlocals(GoalInfo0),
|
|
some [!ModuleInfo] (
|
|
simplify_info_get_module_info(!.Info, !:ModuleInfo),
|
|
simplify_info_get_var_table(!.Info, VarTable),
|
|
merge_instmap_deltas(VarTable, NonLocals, InstMap0,
|
|
[CondThenDelta, ElseDelta], NewDelta, !ModuleInfo),
|
|
simplify_info_set_module_info(!.ModuleInfo, !Info)
|
|
),
|
|
goal_info_set_instmap_delta(NewDelta, GoalInfo0, GoalInfo1),
|
|
IfThenElseExpr = if_then_else(Vars, Cond, Then, Else),
|
|
|
|
IfThenElseDetism0 = goal_info_get_determinism(GoalInfo0),
|
|
determinism_components(IfThenElseDetism0, IfThenElseCanFail,
|
|
IfThenElseNumSolns),
|
|
|
|
CondDetism = goal_info_get_determinism(CondInfo),
|
|
determinism_components(CondDetism, CondCanFail, CondSolns),
|
|
( if
|
|
% Check again if we can apply one of the above simplifications
|
|
% after having simplified the sub-goals (we need to do this
|
|
% to ensure that the goal is fully simplified, to maintain the
|
|
% invariants that the MLDS backend depends on).
|
|
( CondCanFail = cannot_fail
|
|
; CondSolns = at_most_zero
|
|
; Else = hlds_goal(disj([]), _)
|
|
)
|
|
then
|
|
simplify_goal_expr(IfThenElseExpr, GoalExpr, GoalInfo1, GoalInfo,
|
|
NestedContext0, InstMap0, Common0, Common, !Info)
|
|
else
|
|
% Any structures generated in one branch won't be available after the
|
|
% if-the-else as a whole if execution takes the other branch.
|
|
Common = Common0,
|
|
|
|
simplify_info_get_module_info(!.Info, ModuleInfo),
|
|
warn_switch_for_ite_cond(ModuleInfo, VarTable, Cond,
|
|
cond_can_switch_uncommitted, CanSwitch),
|
|
(
|
|
CanSwitch = cond_can_switch_on(SwitchVar),
|
|
Context = goal_info_get_context(CondInfo),
|
|
lookup_var_entry(VarTable, SwitchVar, SwitchVarEntry),
|
|
SwitchVarName = SwitchVarEntry ^ vte_name,
|
|
( if SwitchVarName = "" then
|
|
OnPieces = []
|
|
else
|
|
OnPieces = [words("on"), quote(SwitchVarName)]
|
|
),
|
|
Pieces = [words("Warning: this if-then-else")] ++
|
|
color_as_incorrect([words("could be replaced by a switch")] ++
|
|
OnPieces ++ [suffix(".")]) ++
|
|
[nl],
|
|
Severity = severity_warning(warn_ite_instead_of_switch),
|
|
Spec = spec($pred, Severity, phase_simplify(report_in_any_mode),
|
|
Context, Pieces),
|
|
simplify_info_add_message(Spec, !Info)
|
|
;
|
|
CanSwitch = cond_can_switch_uncommitted
|
|
;
|
|
CanSwitch = cond_cannot_switch
|
|
),
|
|
( if
|
|
% If-then-elses that are det or semidet may nevertheless contain
|
|
% nondet or multi conditions. If this happens, we put the
|
|
% if-then-else inside a commit scope, since the code generators
|
|
% need to know where they should change the code's execution
|
|
% mechanism.
|
|
|
|
simplify_do_mark_code_model_changes(!.Info),
|
|
CondSolns = at_most_many,
|
|
IfThenElseNumSolns \= at_most_many
|
|
then
|
|
determinism_components(InnerDetism, IfThenElseCanFail,
|
|
at_most_many),
|
|
goal_info_set_determinism(InnerDetism, GoalInfo1, InnerInfo),
|
|
GoalExpr = scope(commit(do_not_force_pruning),
|
|
hlds_goal(IfThenElseExpr, InnerInfo))
|
|
else
|
|
GoalExpr = IfThenElseExpr
|
|
),
|
|
GoalInfo = GoalInfo1
|
|
).
|
|
|
|
:- type cond_can_switch
|
|
---> cond_can_switch_uncommitted
|
|
; cond_can_switch_on(prog_var)
|
|
; cond_cannot_switch.
|
|
|
|
:- pred warn_switch_for_ite_cond(module_info::in, var_table::in, hlds_goal::in,
|
|
cond_can_switch::in, cond_can_switch::out) is det.
|
|
|
|
warn_switch_for_ite_cond(ModuleInfo, VarTable, Cond, !CondCanSwitch) :-
|
|
Cond = hlds_goal(CondExpr, _CondInfo),
|
|
(
|
|
CondExpr = unify(_LHSVar, _RHS, _Mode, Unification, _UContext),
|
|
(
|
|
( Unification = construct(_, _, _, _, _, _, _)
|
|
; Unification = assign(_, _)
|
|
; Unification = simple_test(_, _)
|
|
),
|
|
!:CondCanSwitch = cond_cannot_switch
|
|
;
|
|
Unification = deconstruct(LHSVar, _ConsId, _Args, _ArgModes,
|
|
_CanFail, _CanCGC),
|
|
lookup_var_type(VarTable, LHSVar, LHSVarType),
|
|
( if type_to_type_defn_body(ModuleInfo, LHSVarType, TypeBody) then
|
|
CanSwitchOnType = can_switch_on_type(TypeBody),
|
|
(
|
|
CanSwitchOnType = no,
|
|
!:CondCanSwitch = cond_cannot_switch
|
|
;
|
|
CanSwitchOnType = yes,
|
|
(
|
|
!.CondCanSwitch = cond_can_switch_uncommitted,
|
|
!:CondCanSwitch = cond_can_switch_on(LHSVar)
|
|
;
|
|
!.CondCanSwitch = cond_can_switch_on(SwitchVar),
|
|
( if SwitchVar = LHSVar then
|
|
true
|
|
else
|
|
!:CondCanSwitch = cond_cannot_switch
|
|
)
|
|
;
|
|
!.CondCanSwitch = cond_cannot_switch
|
|
)
|
|
)
|
|
else
|
|
% You cannot have a switch on a type with no body (e.g. a
|
|
% builtin type such as int).
|
|
!:CondCanSwitch = cond_cannot_switch
|
|
)
|
|
;
|
|
Unification = complicated_unify(_, _, _),
|
|
unexpected($pred, "complicated unify")
|
|
)
|
|
;
|
|
CondExpr = disj(Disjuncts),
|
|
list.foldl(warn_switch_for_ite_cond(ModuleInfo, VarTable), Disjuncts,
|
|
!CondCanSwitch)
|
|
;
|
|
CondExpr = negation(SubGoal),
|
|
(
|
|
!.CondCanSwitch = cond_can_switch_uncommitted,
|
|
warn_switch_for_ite_cond(ModuleInfo, VarTable, SubGoal,
|
|
!CondCanSwitch)
|
|
;
|
|
!.CondCanSwitch = cond_can_switch_on(_),
|
|
% The condition cannot do both.
|
|
!:CondCanSwitch = cond_cannot_switch
|
|
;
|
|
!.CondCanSwitch = cond_cannot_switch
|
|
)
|
|
;
|
|
( CondExpr = plain_call(_, _, _, _, _, _)
|
|
; CondExpr = generic_call(_, _, _, _, _)
|
|
; CondExpr = call_foreign_proc(_, _, _, _, _, _, _)
|
|
; CondExpr = conj(_, _)
|
|
; CondExpr = switch(_, _, _)
|
|
; CondExpr = scope(_, _)
|
|
; CondExpr = if_then_else(_, _, _, _)
|
|
),
|
|
!:CondCanSwitch = cond_cannot_switch
|
|
;
|
|
CondExpr = shorthand(ShortHand),
|
|
(
|
|
ShortHand = atomic_goal(_, _, _, _, _, _, _),
|
|
!:CondCanSwitch = cond_cannot_switch
|
|
;
|
|
ShortHand = try_goal(_, _, _),
|
|
!:CondCanSwitch = cond_cannot_switch
|
|
;
|
|
ShortHand = bi_implication(_, _),
|
|
unexpected($pred, "shorthand")
|
|
)
|
|
).
|
|
|
|
:- func can_switch_on_type(hlds_type_body) = bool.
|
|
|
|
can_switch_on_type(TypeBody) = CanSwitchOnType :-
|
|
(
|
|
TypeBody = hlds_du_type(TypeBodyDu),
|
|
TypeBodyDu = type_body_du(Ctors, _AlphaSortedCtors, _MaybeSuperType,
|
|
_MaybeUserEq, _MaybeRepn, _MaybeForeignType),
|
|
% We don't care about _MaybeUserEq, since the unification with *any*
|
|
% functor of the type indicates that we are deconstructing the physical
|
|
% representation, not the logical value.
|
|
%
|
|
% We don't care about _MaybeRepn, since that contains only
|
|
% implementation details (which may be of interest when a code
|
|
% generator decides *how* to implement the switch, but which cannot
|
|
% affected *whether* we can implement the switch).
|
|
%
|
|
% We don't care about _MaybeForeignType, since the unification with
|
|
% *any* functor of the type means that either there is no foreign type
|
|
% version, or we are using the Mercury version of the type.
|
|
%
|
|
% We *do* care that the type has at least two constructors.
|
|
( if Ctors = one_or_more(_, [_ | _]) then
|
|
CanSwitchOnType = yes
|
|
else
|
|
CanSwitchOnType = no
|
|
)
|
|
;
|
|
TypeBody = hlds_eqv_type(_),
|
|
% The type of the variable should have had any equivalences expanded
|
|
% out of it before simplify.
|
|
unexpected($pred, "eqv type")
|
|
;
|
|
TypeBody = hlds_foreign_type(_),
|
|
% If the type is foreign, how can a Mercury unification use it?
|
|
unexpected($pred, "foreign type")
|
|
;
|
|
TypeBody = hlds_abstract_type(_),
|
|
% If the type is abstract, how can a Mercury unification use it?
|
|
unexpected($pred, "abstract type")
|
|
;
|
|
TypeBody = hlds_solver_type(_),
|
|
% Any unifications on constrained variables should be done on the
|
|
% representation type, and the type of the variable in the unification
|
|
% should be the representation type, not the solver type.
|
|
unexpected($pred, "solver type")
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
simplify_goal_neg(GoalExpr0, GoalExpr, GoalInfo0, GoalInfo,
|
|
NestedContext0, InstMap0, Common0, Common, !Info) :-
|
|
GoalExpr0 = negation(SubGoal0),
|
|
% Can't use calls or unifications seen within a negation,
|
|
% since non-local variables may not be bound within the negation.
|
|
simplify_goal(SubGoal0, SubGoal1, NestedContext0, InstMap0,
|
|
Common0, _Common1, !Info),
|
|
Context = goal_info_get_context(GoalInfo0),
|
|
InsideDuplForSwitch = NestedContext0 ^ snc_inside_dupl_for_switch,
|
|
( if
|
|
simplify_do_warn_dodgy_simple_code(!.Info),
|
|
% We don't want to generate either of the warnings below for code
|
|
% that has been duplicated for a switch, since the warned-about
|
|
% condition may not (and typically does not) exist in the other copies.
|
|
InsideDuplForSwitch = no
|
|
then
|
|
SubGoal1 = hlds_goal(_, SubGoalInfo1),
|
|
Detism = goal_info_get_determinism(SubGoalInfo1),
|
|
determinism_components(Detism, CanFail, MaxSoln),
|
|
( if CanFail = cannot_fail then
|
|
Pieces = [words("Warning: the negated goal")] ++
|
|
color_as_incorrect([words("cannot fail.")]) ++
|
|
[nl],
|
|
Spec = spec($pred, severity_warning(warn_dodgy_simple_code),
|
|
phase_simplify(report_only_if_in_all_modes), Context, Pieces),
|
|
simplify_info_add_message(Spec, !Info)
|
|
else if MaxSoln = at_most_zero then
|
|
Pieces = [words("Warning: the negated goal")] ++
|
|
color_as_incorrect([words("cannot succeed.")]) ++
|
|
[nl],
|
|
Spec = spec($pred, severity_warning(warn_dodgy_simple_code),
|
|
phase_simplify(report_only_if_in_all_modes), Context, Pieces),
|
|
simplify_info_add_message(Spec, !Info)
|
|
else
|
|
true
|
|
)
|
|
else
|
|
true
|
|
),
|
|
( if
|
|
% Replace `not true' with `fail'.
|
|
SubGoal1 = hlds_goal(conj(plain_conj, []), _)
|
|
then
|
|
hlds_goal(GoalExpr, GoalInfo) = fail_goal_with_context(Context)
|
|
else if
|
|
% Replace `not fail' with `true'.
|
|
SubGoal1 = hlds_goal(disj([]), _)
|
|
then
|
|
hlds_goal(GoalExpr, GoalInfo) = true_goal_with_context(Context)
|
|
else if
|
|
% Remove double negation.
|
|
SubGoal1 =
|
|
hlds_goal(negation(hlds_goal(SubSubGoal, SubSubGoalInfo)), _),
|
|
% XXX BUG! This optimization is only safe if it preserves
|
|
% mode correctness, which means in particular that the
|
|
% the negated goal must not clobber any variables.
|
|
% For now I've just disabled the optimization.
|
|
semidet_fail
|
|
then
|
|
simplify_maybe_wrap_goal(GoalInfo0, SubSubGoalInfo, SubSubGoal,
|
|
GoalExpr, GoalInfo, !Info)
|
|
else
|
|
GoalExpr = negation(SubGoal1),
|
|
GoalInfo = GoalInfo0
|
|
),
|
|
% Execution continues after the negation scope iff the goal inside the
|
|
% scope failed. In general, we don't know when it failed, so we cannot
|
|
% depend on it having created any structures.
|
|
Common = Common0.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
:- end_module check_hlds.simplify.simplify_goal_ite.
|
|
%---------------------------------------------------------------------------%
|