Files
mercury/compiler/simplify.m
Zoltan Somogyi 4e6c603d30 Fix two bugs that caused test case failures in deep profiling grades.
Estimated hours taken: 3
Branches: main

compiler/lookup_switch.m:
	Fix two bugs that caused test case failures in deep profiling grades.

	One bug was that an acquired register wasn't being released before
	the creation of a resume point, which rebuilds the code generator state
	(and thus forgets about acquired registers).

	The other bug was that is_lookup_switch wasn't performing the actions
	generate_goal would have when processing goals. In particular, it
	wasn't invoking pre_goal_update and post_goal_update on disjunctions
	inside the switch.

compiler/lookup_util.m:
	Do not standardize goals by removing scopes from around other goals,
	because this could also remove the effects of the code generator
	annotations (e.g. liveness changes such as pre-births) on the scope
	goal.

compiler/simplify.m:
	Eliminate those redundant scopes if asked to do so. Since this is done
	before the code generator annotations are put on goals, this is safe.

compiler/code_gen.m:
compiler/proc_gen.m:
	Divide the old code_gen.m into two modules: the new code_gen.m
	concerned with generating code for goals, and the new module proc_gen.m
	concerned with generating code for procedures. Without this, the code
	for handling goals is lost inside the old code_gen.m module.

compiler/ll_backend.m:
	Include the new module.

compiler/mercury_compile.m:
	Import proc_gen instead of code_gen, and ask simplify to eliminate
	unnecessary scopes before code generation.

compiler/middle_rec.m:
	Update a reference to a predicate now in proc_gen.m.

compiler/notes/compiler_design.html:
	Document the new module.
2006-05-03 06:46:20 +00:00

2763 lines
110 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 1996-2006 The University of Melbourne.
% 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.m.
% Main authors: zs, stayl.
% The two jobs of the simplification module are
%
% to find and exploit opportunities for simplifying the internal form
% of the program, both to optimize the code and to massage the code
% into a form the code generator will accept, and
%
% to warn the programmer about any constructs that are so simple that
% they should not have been included in the program in the first place.
%
% Simplification is done in two passes. The first pass performs common
% structure and duplicate call elimination. The second pass performs excess
% assignment elimination and cleans up the code after the first pass.
%
% Two passes are required because the goal must be requantified after the
% optimizations in common.m are run so that excess assignment elimination
% works properly.
%
%-----------------------------------------------------------------------------%
:- module check_hlds.simplify.
:- interface.
:- import_module check_hlds.common.
:- import_module check_hlds.det_report.
:- import_module check_hlds.det_util.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_pred.
:- import_module hlds.hlds_rtti.
:- import_module hlds.instmap.
:- import_module libs.globals.
:- import_module bool.
:- import_module io.
:- import_module list.
%-----------------------------------------------------------------------------%
:- pred simplify_pred(simplifications::in, pred_id::in,
module_info::in, module_info::out, pred_info::in, pred_info::out,
int::out, int::out, io::di, io::uo) is det.
:- pred simplify_proc(simplifications::in, pred_id::in, proc_id::in,
module_info::in, module_info::out, proc_info::in, proc_info::out,
io::di, io::uo) is det.
:- pred simplify_proc_return_msgs(simplifications::in, pred_id::in,
proc_id::in, module_info::in, module_info::out,
proc_info::in, proc_info::out, set(context_det_msg)::out,
io::di, io::uo) is det.
:- pred simplify_process_goal(hlds_goal::in, hlds_goal::out,
simplify_info::in, simplify_info::out, io::di, io::uo) is det.
% Find out which simplifications should be run from the options table
% stored in the globals. The first argument states whether warnings
% should be issued during this pass of simplification.
%
:- pred find_simplifications(bool::in, globals::in, simplifications::out)
is det.
:- type simplification
---> warn_simple_code % --warn-simple-code
; warn_duplicate_calls % --warn-duplicate-calls
; warn_known_bad_format % --warn-known-bad-format-calls
; warn_unknown_format % --warn-unknown-format-calls
; warn_obsolete % --warn-obsolete
; do_once % run things that should be done once
; excess_assigns % remove excess assignment unifications
; elim_removable_scopes % remove scopes that do not need processing
% during llds code generation
; opt_duplicate_calls % optimize duplicate calls
; constant_prop % partially evaluate calls
; common_struct % common structure elimination
; extra_common_struct % do common structure elimination
% even when it might increase stack
% usage (used by deforestation).
.
:- type simplifications.
:- func simplifications_to_list(simplifications) = list(simplification).
:- func list_to_simplifications(list(simplification)) = simplifications.
:- type simplify_info.
:- pred simplify_do_warn_simple_code(simplify_info::in) is semidet.
:- pred simplify_do_warn_duplicate_calls(simplify_info::in) is semidet.
:- pred simplify_do_warn_known_bad_format(simplify_info::in) is semidet.
:- pred simplify_do_warn_unknown_format(simplify_info::in) is semidet.
:- pred simplify_do_warn_obsolete(simplify_info::in) is semidet.
:- pred simplify_do_once(simplify_info::in) is semidet.
:- pred simplify_do_excess_assign(simplify_info::in) is semidet.
:- pred simplify_do_elim_removable_scopes(simplify_info::in) is semidet.
:- pred simplify_do_opt_duplicate_calls(simplify_info::in) is semidet.
:- pred simplify_do_const_prop(simplify_info::in) is semidet.
:- pred simplify_do_common_struct(simplify_info::in) is semidet.
:- pred simplify_do_extra_common_struct(simplify_info::in) is semidet.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.det_analysis.
:- import_module check_hlds.format_call.
:- import_module check_hlds.inst_match.
:- import_module check_hlds.modes.
:- import_module check_hlds.mode_util.
:- import_module check_hlds.polymorphism.
:- import_module check_hlds.purity.
:- import_module check_hlds.type_util.
:- import_module check_hlds.unify_proc.
:- import_module hlds.goal_form.
:- import_module hlds.goal_util.
:- import_module hlds.hlds_data.
:- import_module hlds.hlds_module.
:- import_module hlds.passes_aux.
:- import_module hlds.pred_table.
:- import_module hlds.quantification.
:- import_module hlds.special_pred.
:- import_module libs.compiler_util.
:- import_module libs.options.
:- import_module libs.trace_params.
:- import_module mdbcomp.prim_data.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_mode.
:- import_module parse_tree.prog_out.
:- import_module parse_tree.prog_type.
:- import_module parse_tree.prog_type_subst.
:- import_module parse_tree.prog_util.
:- import_module transform_hlds.const_prop.
:- import_module transform_hlds.pd_cost.
:- import_module int.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module set.
:- import_module string.
:- import_module term.
:- import_module varset.
%-----------------------------------------------------------------------------%
:- type simplifications
---> simplifications(
do_warn_simple_code :: bool,
do_warn_duplicate_calls :: bool,
do_warn_known_bad_format :: bool,
do_warn_unknown_format :: bool,
do_warn_obsolete :: bool,
do_do_once :: bool,
do_excess_assign :: bool,
do_elim_removable_scopes :: bool,
do_opt_duplicate_calls :: bool,
do_constant_prop :: bool,
do_common_struct :: bool,
do_extra_common_struct :: bool
).
simplifications_to_list(Simplifications) = List :-
Simplifications = simplifications(WarnSimpleCode, WarnDupCalls,
WarnKnownBadFormat, WarnUnknownFormat, WarnObsolete, DoOnce,
ExcessAssign, ElimRemovableScopes, OptDuplicateCalls, ConstantProp,
CommonStruct, ExtraCommonStruct),
List =
( WarnSimpleCode = yes -> [warn_simple_code] ; [] ) ++
( WarnDupCalls = yes -> [warn_duplicate_calls] ; [] ) ++
( WarnKnownBadFormat = yes -> [warn_known_bad_format] ; [] ) ++
( WarnUnknownFormat = yes -> [warn_unknown_format] ; [] ) ++
( WarnObsolete = yes -> [warn_obsolete] ; [] ) ++
( DoOnce = yes -> [do_once] ; [] ) ++
( ExcessAssign = yes -> [excess_assigns] ; [] ) ++
( ElimRemovableScopes = yes -> [elim_removable_scopes] ; [] ) ++
( OptDuplicateCalls = yes -> [opt_duplicate_calls] ; [] ) ++
( ConstantProp = yes -> [constant_prop] ; [] ) ++
( CommonStruct = yes -> [common_struct] ; [] ) ++
( ExtraCommonStruct = yes -> [extra_common_struct] ; [] ).
list_to_simplifications(List) =
simplifications(
( list.member(warn_simple_code, List) -> yes ; no ),
( list.member(warn_duplicate_calls, List) -> yes ; no ),
( list.member(warn_known_bad_format, List) -> yes ; no ),
( list.member(warn_unknown_format, List) -> yes ; no ),
( list.member(warn_obsolete, List) -> yes ; no ),
( list.member(do_once, List) -> yes ; no ),
( list.member(excess_assigns, List) -> yes ; no ),
( list.member(elim_removable_scopes, List) -> yes ; no ),
( list.member(opt_duplicate_calls, List) -> yes ; no ),
( list.member(constant_prop, List) -> yes ; no ),
( list.member(common_struct, List) -> yes ; no ),
( list.member(extra_common_struct, List) -> yes ; no )
).
find_simplifications(WarnThisPass, Globals, Simplifications) :-
globals.lookup_bool_option(Globals, warn_simple_code, WarnSimple),
globals.lookup_bool_option(Globals, warn_duplicate_calls, WarnDupCalls),
globals.lookup_bool_option(Globals, warn_known_bad_format_calls,
WarnKnownBadFormat),
globals.lookup_bool_option(Globals, warn_unknown_format_calls,
WarnUnknownFormat),
globals.lookup_bool_option(Globals, warn_obsolete, WarnObsolete),
globals.lookup_bool_option(Globals, excess_assign, ExcessAssign),
globals.lookup_bool_option(Globals, common_struct, CommonStruct),
globals.lookup_bool_option(Globals, optimize_duplicate_calls,
OptDuplicateCalls),
globals.lookup_bool_option(Globals, constant_propagation, ConstantProp),
DoOnce = no,
ElimRemovableScopes = no,
ExtraCommonStruct = no,
Simplifications = simplifications(
( WarnSimple = yes, WarnThisPass = yes -> yes ; no),
( WarnDupCalls = yes, WarnThisPass = yes -> yes ; no),
( WarnKnownBadFormat = yes, WarnThisPass = yes -> yes ; no),
( WarnUnknownFormat = yes, WarnThisPass = yes -> yes ; no),
( WarnObsolete = yes, WarnThisPass = yes -> yes ; no),
DoOnce,
ExcessAssign,
ElimRemovableScopes,
OptDuplicateCalls,
ConstantProp,
CommonStruct,
ExtraCommonStruct
).
simplify_do_warn_simple_code(Info) :-
simplify_info_get_simplifications(Info, Simplifications),
Simplifications ^ do_warn_simple_code = yes.
simplify_do_warn_duplicate_calls(Info) :-
simplify_info_get_simplifications(Info, Simplifications),
Simplifications ^ do_warn_duplicate_calls = yes.
simplify_do_warn_known_bad_format(Info) :-
simplify_info_get_simplifications(Info, Simplifications),
Simplifications ^ do_warn_known_bad_format = yes.
simplify_do_warn_unknown_format(Info) :-
simplify_info_get_simplifications(Info, Simplifications),
Simplifications ^ do_warn_unknown_format = yes.
simplify_do_warn_obsolete(Info) :-
simplify_info_get_simplifications(Info, Simplifications),
Simplifications ^ do_warn_obsolete = yes.
simplify_do_once(Info) :-
simplify_info_get_simplifications(Info, Simplifications),
Simplifications ^ do_do_once = yes.
simplify_do_excess_assign(Info) :-
simplify_info_get_simplifications(Info, Simplifications),
Simplifications ^ do_excess_assign = yes.
simplify_do_elim_removable_scopes(Info) :-
simplify_info_get_simplifications(Info, Simplifications),
Simplifications ^ do_elim_removable_scopes = yes.
simplify_do_opt_duplicate_calls(Info) :-
simplify_info_get_simplifications(Info, Simplifications),
Simplifications ^ do_opt_duplicate_calls = yes.
simplify_do_const_prop(Info) :-
simplify_info_get_simplifications(Info, Simplifications),
Simplifications ^ do_constant_prop = yes.
simplify_do_common_struct(Info) :-
simplify_info_get_simplifications(Info, Simplifications),
Simplifications ^ do_common_struct = yes.
simplify_do_extra_common_struct(Info) :-
simplify_info_get_simplifications(Info, Simplifications),
Simplifications ^ do_extra_common_struct = yes.
%-----------------------------------------------------------------------------%
simplify_pred(Simplifications0, PredId, !ModuleInfo, !PredInfo,
WarnCnt, ErrCnt, !IO) :-
write_pred_progress_message("% Simplifying ", PredId, !.ModuleInfo, !IO),
ProcIds = pred_info_non_imported_procids(!.PredInfo),
% Don't warn for compiler-generated procedures.
( is_unify_or_compare_pred(!.PredInfo) ->
Simplifications = Simplifications0 ^ do_warn_simple_code := no
;
Simplifications = Simplifications0
),
MaybeMsgs0 = no,
simplify_procs(Simplifications, PredId, ProcIds, !ModuleInfo, !PredInfo,
MaybeMsgs0, MaybeMsgs, !IO),
(
MaybeMsgs = yes(Msgs0 - Msgs1),
set.union(Msgs0, Msgs1, Msgs2),
set.to_sorted_list(Msgs2, Msgs),
det_report_msgs(Msgs, !.ModuleInfo, WarnCnt, ErrCnt, !IO)
;
MaybeMsgs = no,
WarnCnt = 0,
ErrCnt = 0
),
globals.io_lookup_bool_option(detailed_statistics, Statistics, !IO),
maybe_report_stats(Statistics, !IO).
:- pred simplify_procs(simplifications::in, pred_id::in,
list(proc_id)::in, module_info::in, module_info::out,
pred_info::in, pred_info::out,
maybe(pair(set(context_det_msg)))::in,
maybe(pair(set(context_det_msg)))::out,
io::di, io::uo) is det.
simplify_procs(_, _, [], !ModuleInfo, !PredInfo, !Msgs, !IO).
simplify_procs(Simplifications, PredId, [ProcId | ProcIds], !ModuleInfo,
!PredInfo, !MaybeMsgs, !IO) :-
pred_info_get_procedures(!.PredInfo, Procs0),
map.lookup(Procs0, ProcId, Proc0),
simplify_proc_return_msgs(Simplifications, PredId, ProcId,
!ModuleInfo, Proc0, Proc, ProcMsgSet, !IO),
map.det_update(Procs0, ProcId, Proc, Procs),
pred_info_set_procedures(Procs, !PredInfo),
set.to_sorted_list(ProcMsgSet, ProcMsgs),
list.filter((pred(context_det_msg(_, Msg)::in) is semidet :-
det_msg_is_any_mode_msg(Msg, any_mode)
), ProcMsgs, ProcAnyModeMsgs, ProcAllModeMsgs),
set.sorted_list_to_set(ProcAnyModeMsgs, ProcAnyModeMsgSet),
set.sorted_list_to_set(ProcAllModeMsgs, ProcAllModeMsgSet),
(
!.MaybeMsgs = yes(AnyModeMsgSet0 - AllModeMsgSet0),
set.union(AnyModeMsgSet0, ProcAnyModeMsgSet, AnyModeMsgSet),
set.intersect(AllModeMsgSet0, ProcAllModeMsgSet, AllModeMsgSet),
!:MaybeMsgs = yes(AllModeMsgSet - AnyModeMsgSet)
;
!.MaybeMsgs = no,
!:MaybeMsgs = yes(ProcAnyModeMsgSet - ProcAllModeMsgSet)
),
simplify_procs(Simplifications, PredId, ProcIds, !ModuleInfo, !PredInfo,
!MaybeMsgs, !IO).
simplify_proc(Simplifications, PredId, ProcId, !ModuleInfo, !Proc, !IO) :-
write_pred_progress_message("% Simplifying ", PredId, !.ModuleInfo, !IO),
simplify_proc_return_msgs(Simplifications, PredId, ProcId, !ModuleInfo,
!Proc, _, !IO).
:- func turn_off_common_struct_threshold = int.
turn_off_common_struct_threshold = 1000.
simplify_proc_return_msgs(Simplifications0, PredId, ProcId, !ModuleInfo,
!ProcInfo, DetMsgs, !IO) :-
module_info_get_globals(!.ModuleInfo, Globals),
proc_info_get_vartypes(!.ProcInfo, VarTypes0),
NumVars = map.count(VarTypes0),
( NumVars > turn_off_common_struct_threshold ->
% If we have too many variables, common_struct takes so long that
% either the compiler runs out of memory or the user runs out of
% patience. The fact that we would generate better code if the
% compilation finished is therefore of limited interest.
Simplifications = Simplifications0 ^ do_common_struct := no
;
Simplifications = Simplifications0
),
det_info_init(!.ModuleInfo, VarTypes0, PredId, ProcId, Globals, DetInfo0),
proc_info_get_initial_instmap(!.ProcInfo, !.ModuleInfo, InstMap0),
simplify_info_init(DetInfo0, Simplifications, InstMap0, !.ProcInfo, Info0),
proc_info_get_goal(!.ProcInfo, Goal0),
simplify_info_get_pred_info(Info0, PredInfo),
pred_info_get_markers(PredInfo, Markers),
(
check_marker(Markers, mode_check_clauses),
Goal0 = GoalExpr0 - GoalInfo0,
( GoalExpr0 = disj(_)
; GoalExpr0 = switch(_, _, _)
)
->
goal_info_add_feature(mode_check_clauses_goal, GoalInfo0, GoalInfo1),
Goal1 = GoalExpr0 - GoalInfo1
;
Goal1 = Goal0
),
simplify_process_goal(Goal1, Goal, Info0, Info, !IO),
simplify_info_get_varset(Info, VarSet),
simplify_info_get_var_types(Info, VarTypes),
simplify_info_get_rtti_varmaps(Info, RttiVarMaps),
proc_info_set_varset(VarSet, !ProcInfo),
proc_info_set_vartypes(VarTypes, !ProcInfo),
proc_info_set_goal(Goal, !ProcInfo),
proc_info_set_rtti_varmaps(RttiVarMaps, !ProcInfo),
simplify_info_get_module_info(Info, !:ModuleInfo),
simplify_info_get_det_msgs(Info, DetMsgs0),
(
Info ^ format_calls = yes,
(
Simplifications ^ do_warn_known_bad_format = yes
;
Simplifications ^ do_warn_unknown_format = yes
)
->
% We must use the original goal, Goal0, here. This is because excess
% assignment optimization may delete some of the unifications that
% build the format strings or values, which means that the new version
% in Goal may not contain the information find_format_call_errors needs
% to avoid spurious messages about unknown format strings or values.
find_format_call_errors(!.ModuleInfo, Goal0, DetMsgs0, DetMsgs1)
;
% Either there are no calls to check or we would ignore the added
% messages anyway.
DetMsgs1 = DetMsgs0
),
pred_info_get_import_status(PredInfo, Status),
status_defined_in_this_module(Status, IsDefinedHere),
(
IsDefinedHere = no,
% Don't generate any warnings or even errors if the predicate isn't
% defined here; any such messages will be generated when we compile
% the module the predicate comes from.
set.init(DetMsgs)
;
IsDefinedHere = yes,
DetMsgs = DetMsgs1
).
simplify_process_goal(Goal0, Goal, !Info, !IO) :-
simplify_info_get_simplifications(!.Info, Simplifications0),
simplify_info_get_instmap(!.Info, InstMap0),
(
( simplify_do_common_struct(!.Info)
; simplify_do_opt_duplicate_calls(!.Info)
)
->
Simplifications1 = ((Simplifications0
^ do_do_once := no)
^ do_excess_assign := no),
simplify_info_set_simplifications(Simplifications1, !Info),
do_process_goal(Goal0, Goal1, !Info, !IO),
Simplifications2 = ((((Simplifications0
^ do_warn_simple_code := no)
^ do_warn_duplicate_calls := no)
^ do_common_struct := no)
^ do_opt_duplicate_calls := no),
simplify_info_reinit(Simplifications2, InstMap0, !Info)
;
Goal1 = Goal0
),
% On the second pass do excess assignment elimination and some cleaning up
% after the common structure pass.
do_process_goal(Goal1, Goal, !Info, !IO).
:- pred do_process_goal(hlds_goal::in, hlds_goal::out,
simplify_info::in, simplify_info::out, io::di, io::uo) is det.
do_process_goal(Goal0, Goal, !Info, !IO) :-
simplify_info_get_instmap(!.Info, InstMap0),
simplify_goal(Goal0, Goal1, !Info, !IO),
simplify_info_get_varset(!.Info, VarSet0),
simplify_info_get_var_types(!.Info, VarTypes0),
( simplify_info_requantify(!.Info) ->
Goal1 = _ - GoalInfo1,
goal_info_get_nonlocals(GoalInfo1, NonLocals),
implicitly_quantify_goal(NonLocals, _, Goal1, Goal2,
VarSet0, VarSet1, VarTypes0, VarTypes1),
simplify_info_set_varset(VarSet1, !Info),
simplify_info_set_var_types(VarTypes1, !Info),
% Always recompute instmap_deltas for atomic goals - this is safer
% in the case where unused variables should no longer be included
% in the instmap_delta for a goal.
% In the alias branch this is necessary anyway.
RecomputeAtomic = yes,
simplify_info_get_module_info(!.Info, ModuleInfo0),
recompute_instmap_delta(RecomputeAtomic, Goal2, Goal3, VarTypes1,
!.Info ^ inst_varset, InstMap0, ModuleInfo0, ModuleInfo1),
simplify_info_set_module_info(ModuleInfo1, !Info)
;
Goal3 = Goal1
),
( simplify_info_rerun_det(!.Info) ->
Goal0 = _ - GoalInfo0,
goal_info_get_determinism(GoalInfo0, Det),
det_get_soln_context(Det, SolnContext),
% det_infer_goal looks up the proc_info in the module_info
% for the vartypes, so we'd better stick them back in the module_info.
simplify_info_get_module_info(!.Info, ModuleInfo2),
simplify_info_get_varset(!.Info, VarSet2),
simplify_info_get_var_types(!.Info, VarTypes2),
simplify_info_get_det_info(!.Info, DetInfo2),
det_info_get_pred_id(DetInfo2, PredId),
det_info_get_proc_id(DetInfo2, ProcId),
module_info_pred_proc_info(ModuleInfo2, PredId, ProcId,
PredInfo, ProcInfo0),
proc_info_set_vartypes(VarTypes2, ProcInfo0, ProcInfo1),
proc_info_set_varset(VarSet2, ProcInfo1, ProcInfo),
module_info_set_pred_proc_info(PredId, ProcId,
PredInfo, ProcInfo, ModuleInfo2, ModuleInfo3),
simplify_info_set_module_info(ModuleInfo3, !Info),
simplify_info_get_det_info(!.Info, DetInfo),
det_infer_goal(Goal3, Goal, InstMap0, SolnContext, [], no, DetInfo,
_, _, _)
;
Goal = Goal3
).
%-----------------------------------------------------------------------------%
:- pred simplify_goal(hlds_goal::in, hlds_goal::out,
simplify_info::in, simplify_info::out, io::di, io::uo) is det.
simplify_goal(Goal0, GoalExpr - GoalInfo, !Info, !IO) :-
Goal0 = _ - GoalInfo0,
goal_info_get_determinism(GoalInfo0, Detism),
simplify_info_get_det_info(!.Info, DetInfo),
simplify_info_get_module_info(!.Info, ModuleInfo0),
goal_can_loop_or_throw(Goal0, Goal0CanLoopOrThrow, ModuleInfo0,
ModuleInfo, !IO),
simplify_info_set_module_info(ModuleInfo, !Info),
(
%
% if --no-fully-strict,
% replace goals with determinism failure with `fail'.
%
Detism = failure,
% ensure goal is pure or semipure
\+ goal_info_is_impure(GoalInfo0),
( det_info_get_fully_strict(DetInfo, no)
; Goal0CanLoopOrThrow = cannot_loop_or_throw
)
->
% Warn about this, unless the goal was an explicit `fail', call to
% `builtin.false/0' or some goal containing `fail' or a call to
% `builtin.false/0'.
%
goal_info_get_context(GoalInfo0, Context),
(
simplify_do_warn_simple_code(!.Info),
\+ (
goal_contains_goal(Goal0, SubGoal),
( SubGoal = disj([]) - _
; goal_is_call_to_builtin_false(SubGoal)
)
)
->
Msg = goal_cannot_succeed,
ContextMsg = context_det_msg(Context, Msg),
simplify_info_add_det_msg(ContextMsg, !Info)
;
true
),
% If the goal had any non-locals we should requantify.
goal_info_get_nonlocals(GoalInfo0, NonLocals0),
( set.empty(NonLocals0) ->
true
;
simplify_info_set_requantify(!Info)
),
goal_cost(Goal0, CostDelta),
simplify_info_incr_cost_delta(CostDelta, !Info),
Goal1 = fail_goal_with_context(Context)
;
%
% If --no-fully-strict, replace goals which cannot fail and have
% no output variables with `true'. However, we don't do this for
% erroneous goals, since these may occur in conjunctions where there
% are no producers for some variables, and the code generator would
% fail for these.
%
determinism_components(Detism, cannot_fail, MaxSoln),
MaxSoln \= at_most_zero,
goal_info_get_instmap_delta(GoalInfo0, InstMapDelta),
goal_info_get_nonlocals(GoalInfo0, NonLocalVars),
simplify_info_get_instmap(!.Info, InstMap0),
det_no_output_vars(NonLocalVars, InstMap0, InstMapDelta, DetInfo),
% ensure goal is pure or semipure
\+ goal_info_is_impure(GoalInfo0),
( det_info_get_fully_strict(DetInfo, no)
; Goal0CanLoopOrThrow = cannot_loop_or_throw
)
->
% The following warning is disabled, because it often results in spurious
% warnings. Sometimes predicate calls are used just to constrain the types,
% to avoid type ambiguities or unbound type variables, and in such cases,
% it is perfectly legitimate for a call to be det and to have no outputs.
% There's no simple way of telling those cases from cases for which we
% really ought to warn.
% XXX This hasn't really been true since we added `with_type`.
%
% % warn about this, if the goal wasn't `true', wasn't `!',
% % and wasn't a deconstruction unification.
% % We don't warn about deconstruction unifications
% % with no outputs that always succeed, because that
% % would result in bogus warnings, since switch detection
% % converts deconstruction unifications that can fail
% % into ones that always succeed by moving the test into
% % the switch.
% % We also don't warn about conjunctions or existential
% % quantifications, because it seems that warnings in those
% % cases are usually spurious.
% (
% simplify_do_warn_simple_code(!.Info),
% % Goal0 \= conj(plain_conj, []) - _,
% \+ (Goal0 = call(_, _, _, _, _, SymName) - _,
% unqualify_name(SymName, "!")),
% Goal0 \= conj(plain_conj, _) - _,
% Goal0 \= some(_, _) - _,
% \+ (Goal0 = unify(_, _, _, Unification, _) - _,
% Unification = deconstruct(_, _, _, _, _))
% ->
% Msg = det_goal_has_no_outputs,
% ContextMsg = context_det_msg(Context, Msg),
% simplify_info_add_det_msg(ContextMsg, !Info)
% ;
% true
% ),
% If the goal had any non-locals we should requantify.
goal_info_get_nonlocals(GoalInfo0, NonLocals0),
( set.empty(NonLocals0) ->
true
;
simplify_info_set_requantify(!Info)
),
goal_cost(Goal0, CostDelta),
simplify_info_incr_cost_delta(CostDelta, !Info),
goal_info_get_context(GoalInfo0, Context),
Goal1 = true_goal_with_context(Context)
;
Goal1 = Goal0
),
%
% Remove unnecessary explicit quantifications before working
% out whether the goal can cause a stack flush.
%
( Goal1 = scope(Reason1, SomeGoal1) - GoalInfo1 ->
nested_scopes(Reason1, SomeGoal1, GoalInfo1, Goal2)
;
Goal2 = Goal1
),
(
simplify_do_elim_removable_scopes(!.Info),
Goal2 = scope(Reason2, SomeGoal2) - _GoalInfo2,
( Reason2 = barrier(removable)
; Reason2 = from_ground_term(_)
)
->
Goal3 = SomeGoal2
;
Goal3 = Goal2
),
simplify_info_maybe_clear_structs(before, Goal3, !Info),
Goal3 = GoalExpr3 - GoalInfo3,
simplify_goal_2(GoalExpr3, GoalExpr, GoalInfo3, GoalInfo4, !Info, !IO),
simplify_info_maybe_clear_structs(after, GoalExpr - GoalInfo4, !Info),
enforce_invariant(GoalInfo4, GoalInfo, !Info).
% Ensure that the mode information and the determinism
% information say consistent things about unreachability.
%
:- pred enforce_invariant(hlds_goal_info::in, hlds_goal_info::out,
simplify_info::in, simplify_info::out) is det.
enforce_invariant(GoalInfo0, GoalInfo, !Info) :-
goal_info_get_determinism(GoalInfo0, Determinism0),
goal_info_get_instmap_delta(GoalInfo0, DeltaInstmap0),
determinism_components(Determinism0, CanFail0, NumSolns0),
(
NumSolns0 = at_most_zero,
instmap_delta_is_reachable(DeltaInstmap0)
->
instmap_delta_init_unreachable(UnreachableInstMapDelta),
goal_info_set_instmap_delta(UnreachableInstMapDelta,
GoalInfo0, GoalInfo),
simplify_info_set_rerun_det(!Info)
;
instmap_delta_is_unreachable(DeltaInstmap0),
NumSolns0 \= at_most_zero
->
determinism_components(Determinism, CanFail0, at_most_zero),
goal_info_set_determinism(Determinism, GoalInfo0, GoalInfo),
simplify_info_set_rerun_det(!Info)
;
GoalInfo = GoalInfo0
).
:- pred goal_is_call_to_builtin_false(hlds_goal::in) is semidet.
goal_is_call_to_builtin_false(Goal - _) :-
Goal = call(_, _, _, _, _, SymName),
SymName = qualified(mercury_public_builtin_module, "false").
%-----------------------------------------------------------------------------%
:- pred simplify_goal_2(hlds_goal_expr::in, hlds_goal_expr::out,
hlds_goal_info::in, hlds_goal_info::out,
simplify_info::in, simplify_info::out, io::di, io::uo) is det.
simplify_goal_2(conj(ConjType, Goals0), Goal, GoalInfo0, GoalInfo,
!Info, !IO) :-
(
ConjType = plain_conj,
simplify_info_get_instmap(!.Info, InstMap0),
excess_assigns_in_conj(GoalInfo0, Goals0, Goals1, !Info),
simplify_conj(Goals1, [], Goals, GoalInfo0, !Info, !IO),
simplify_info_set_instmap(InstMap0, !Info),
(
Goals = [],
goal_info_get_context(GoalInfo0, Context),
Goal - GoalInfo = true_goal_with_context(Context)
;
Goals = [SingleGoal - SingleGoalInfo],
% A singleton conjunction is equivalent to the goal itself.
maybe_wrap_goal(GoalInfo0, SingleGoalInfo, SingleGoal,
Goal, GoalInfo, !Info)
;
Goals = [_, _ | _],
%
% Conjunctions that cannot produce solutions may nevertheless
% contain nondet and multi goals. If this happens, the conjunction
% is put inside a `scope' to appease the code generator.
%
goal_info_get_determinism(GoalInfo0, Detism),
(
simplify_do_once(!.Info),
determinism_components(Detism, CanFail, at_most_zero),
contains_multisoln_goal(Goals)
->
determinism_components(InnerDetism, CanFail, at_most_many),
goal_info_set_determinism(InnerDetism, GoalInfo0, InnerInfo),
InnerGoal = conj(plain_conj, Goals) - InnerInfo,
Goal = scope(commit(dont_force_pruning), InnerGoal)
;
Goal = conj(plain_conj, Goals)
),
GoalInfo = GoalInfo0
)
;
ConjType = parallel_conj,
(
Goals0 = [],
goal_info_get_context(GoalInfo0, Context),
Goal - GoalInfo = true_goal_with_context(Context)
;
Goals0 = [SingleGoal0],
simplify_goal(SingleGoal0, SingleGoal - SingleGoalInfo, !Info, !IO),
maybe_wrap_goal(GoalInfo0, SingleGoalInfo, SingleGoal,
Goal, GoalInfo, !Info)
;
Goals0 = [_, _ | _],
GoalInfo = GoalInfo0,
simplify_par_conj(Goals0, Goals, !.Info, !Info, !IO),
Goal = conj(parallel_conj, Goals)
)
).
simplify_goal_2(disj(Disjuncts0), Goal, GoalInfo0, GoalInfo, !Info, !IO) :-
simplify_info_get_instmap(!.Info, InstMap0),
simplify_disj(Disjuncts0, [], Disjuncts, [], InstMaps, !.Info, !Info, !IO),
(
Disjuncts = [],
goal_info_get_context(GoalInfo0, Context),
Goal - GoalInfo = fail_goal_with_context(Context)
;
Disjuncts = [SingleGoal],
% A singleton disjunction is equivalent to the goal itself.
SingleGoal = Goal1 - GoalInfo1,
maybe_wrap_goal(GoalInfo0, GoalInfo1, Goal1, Goal, GoalInfo, !Info)
;
Disjuncts = [_, _ | _],
Goal = disj(Disjuncts),
( goal_info_has_feature(GoalInfo0, mode_check_clauses_goal) ->
% Recomputing the instmap delta would take very long
% and is very unlikely to get any better precision.
GoalInfo = GoalInfo0
;
simplify_info_get_module_info(!.Info, ModuleInfo1),
goal_info_get_nonlocals(GoalInfo0, NonLocals),
simplify_info_get_var_types(!.Info, VarTypes),
merge_instmap_deltas(InstMap0, NonLocals, VarTypes, InstMaps,
NewDelta, ModuleInfo1, ModuleInfo2),
simplify_info_set_module_info(ModuleInfo2, !Info),
goal_info_set_instmap_delta(NewDelta, GoalInfo0, GoalInfo)
)
),
list.length(Disjuncts, DisjunctsLength),
list.length(Disjuncts0, Disjuncts0Length),
( DisjunctsLength \= Disjuncts0Length ->
%
% If we pruned some disjuncts, variables used by those disjuncts
% may no longer be non-local to the disjunction. Also, the determinism
% may have changed (especially if we pruned all the disjuncts).
% If the disjunction now can't succeed, it is necessary to recompute
% instmap_deltas and rerun determinism analysis to avoid aborts
% in the code generator because the disjunction now cannot produce
% variables it did before.
%
simplify_info_set_requantify(!Info),
simplify_info_set_rerun_det(!Info)
;
true
).
simplify_goal_2(switch(Var, SwitchCanFail0, Cases0), Goal,
GoalInfo0, GoalInfo, !Info, !IO) :-
simplify_info_get_instmap(!.Info, InstMap0),
simplify_info_get_module_info(!.Info, ModuleInfo0),
instmap.lookup_var(InstMap0, Var, VarInst),
( inst_is_bound_to_functors(ModuleInfo0, VarInst, Functors) ->
functors_to_cons_ids(Functors, ConsIds0),
list.sort(ConsIds0, ConsIds),
delete_unreachable_cases(Cases0, ConsIds, Cases1),
MaybeConsIds = yes(ConsIds)
;
Cases1 = Cases0,
MaybeConsIds = no
),
simplify_switch(Var, Cases1, [], Cases, [], InstMaps,
SwitchCanFail0, SwitchCanFail, !.Info, !Info, !IO),
(
Cases = [],
% An empty switch always fails.
simplify_info_incr_cost_delta(cost_of_eliminate_switch, !Info),
goal_info_get_context(GoalInfo0, Context),
Goal - GoalInfo = fail_goal_with_context(Context)
;
Cases = [case(ConsId, SingleGoal)],
% A singleton switch is equivalent to the goal itself with a
% possibly can_fail unification with the functor on the front.
Arity = cons_id_arity(ConsId),
(
SwitchCanFail = can_fail,
MaybeConsIds \= yes([ConsId])
->
% Don't optimize in the case of an existentially typed constructor
% because currently create_test_unification does not handle the
% existential type variables in the types of the constructor
% arguments or their typeinfos.
simplify_info_get_var_types(!.Info, VarTypes1),
map.lookup(VarTypes1, Var, Type),
simplify_info_get_module_info(!.Info, ModuleInfo1),
( type_util.is_existq_cons(ModuleInfo1, Type, ConsId) ->
Goal = switch(Var, SwitchCanFail, Cases),
goal_info_get_nonlocals(GoalInfo0, NonLocals),
simplify_info_get_var_types(!.Info, VarTypes),
merge_instmap_deltas(InstMap0, NonLocals, VarTypes,
InstMaps, NewDelta, ModuleInfo1, ModuleInfo2),
simplify_info_set_module_info(ModuleInfo2, !Info),
goal_info_set_instmap_delta(NewDelta, GoalInfo0, GoalInfo)
;
create_test_unification(Var, ConsId, Arity, UnifyGoal, !Info),
% Conjoin the test and the rest of the case.
goal_to_conj_list(SingleGoal, SingleGoalConj),
GoalList = [UnifyGoal | SingleGoalConj],
% Work out the nonlocals, instmap_delta
% and determinism of the entire conjunction.
goal_info_get_nonlocals(GoalInfo0, NonLocals0),
set.insert(NonLocals0, Var, NonLocals),
goal_info_get_instmap_delta(GoalInfo0, InstMapDelta0),
simplify_info_get_instmap(!.Info, InstMap),
instmap_delta_bind_var_to_functor(Var, Type, ConsId,
InstMap, InstMapDelta0, InstMapDelta,
ModuleInfo1, ModuleInfo),
simplify_info_set_module_info(ModuleInfo, !Info),
goal_info_get_determinism(GoalInfo0, CaseDetism),
det_conjunction_detism(semidet, CaseDetism, Detism),
goal_list_purity(GoalList, Purity),
goal_info_init(NonLocals, InstMapDelta, Detism, Purity,
CombinedGoalInfo),
simplify_info_set_requantify(!Info),
Goal = conj(plain_conj, GoalList),
GoalInfo = CombinedGoalInfo
)
;
% The var can only be bound to this cons_id, so a test
% is unnecessary.
SingleGoal = Goal - GoalInfo
),
simplify_info_incr_cost_delta(cost_of_eliminate_switch, !Info)
;
Cases = [_, _ | _],
Goal = switch(Var, SwitchCanFail, Cases),
( goal_info_has_feature(GoalInfo0, mode_check_clauses_goal) ->
% Recomputing the instmap delta would take very long and is
% very unlikely to get any better precision.
GoalInfo = GoalInfo0
;
simplify_info_get_module_info(!.Info, ModuleInfo1),
goal_info_get_nonlocals(GoalInfo0, NonLocals),
simplify_info_get_var_types(!.Info, VarTypes),
merge_instmap_deltas(InstMap0, NonLocals, VarTypes, InstMaps,
NewDelta, ModuleInfo1, ModuleInfo2),
simplify_info_set_module_info(ModuleInfo2, !Info),
goal_info_set_instmap_delta(NewDelta, GoalInfo0, GoalInfo)
)
),
list.length(Cases0, Cases0Length),
list.length(Cases, CasesLength),
( CasesLength \= Cases0Length ->
% If we pruned some cases, variables used by those cases may no longer
% be non-local to the switch. Also, the determinism may have changed
% (especially if we pruned all the cases). If the switch now can't
% succeed, it is necessary to recompute instmap_deltas and rerun
% determinism analysis to avoid aborts in the code generator because
% the switch now cannot produce variables it did before.
simplify_info_set_requantify(!Info),
simplify_info_set_rerun_det(!Info)
;
true
).
simplify_goal_2(Goal0, Goal, GoalInfo, GoalInfo, !Info, !IO) :-
Goal0 = generic_call(GenericCall, Args, Modes, Det),
(
simplify_do_opt_duplicate_calls(!.Info),
% XXX We should do duplicate call elimination for
% class method calls here.
GenericCall = higher_order(Closure, Purity, _, _),
% XXX Should we handle semipure higher-order calls too?
Purity = purity_pure
->
common_optimise_higher_order_call(Closure, Args, Modes, Det,
GoalInfo, Goal0, Goal, !Info)
;
simplify_do_warn_duplicate_calls(!.Info),
GenericCall = higher_order(Closure, Purity, _, _),
% XXX Should we handle impure/semipure higher-order calls too?
Purity = purity_pure
->
% We need to do the pass, for the warnings, but we ignore
% the optimized goal and instead use the original one.
common_optimise_higher_order_call(Closure, Args, Modes, Det,
GoalInfo, Goal0, _Goal1, !Info),
Goal = Goal0
;
Goal = Goal0
).
simplify_goal_2(Goal0, Goal, GoalInfo0, GoalInfo, !Info, !IO) :-
Goal0 = call(PredId, ProcId, Args, IsBuiltin, _, _),
simplify_info_get_module_info(!.Info, ModuleInfo),
module_info_pred_info(ModuleInfo, PredId, PredInfo),
ModuleName = hlds_pred.pred_info_module(PredInfo),
Name = hlds_pred.pred_info_name(PredInfo),
( is_format_call(ModuleName, Name, Args, _, _) ->
simplify_info_set_format_calls(yes, !Info)
;
true
),
%
% Convert calls to builtin @=<, @<, @>=, @> into the corresponding
% calls to builtin.compare/3.
%
(
Args = [TI, X, Y],
ModuleName = mercury_public_builtin_module,
( Name = "@<", Inequality = "<", Invert = no
; Name = "@=<", Inequality = ">", Invert = yes
; Name = "@>=", Inequality = "<", Invert = yes
; Name = "@>", Inequality = ">", Invert = no
)
->
inequality_goal(TI, X, Y, Inequality, Invert, GoalInfo0,
Goal, GoalInfo, !Info)
;
call_goal(PredId, ProcId, Args, IsBuiltin, Goal0, Goal,
GoalInfo0, GoalInfo, !Info)
).
simplify_goal_2(Goal0, Goal, GoalInfo0, GoalInfo, !Info, !IO) :-
Goal0 = unify(LT0, RT0, M, U0, C),
(
% A unification of the form X = X can be safely
% optimised away.
RT0 = var(LT0)
->
goal_info_get_context(GoalInfo0, Context),
Goal - GoalInfo = true_goal_with_context(Context)
;
RT0 = lambda_goal(Purity, PredOrFunc, EvalMethod, NonLocals,
Vars, Modes, LambdaDeclaredDet, LambdaGoal0)
->
simplify_info_enter_lambda(!Info),
simplify_info_get_common_info(!.Info, Common1),
simplify_info_get_module_info(!.Info, ModuleInfo),
simplify_info_get_instmap(!.Info, InstMap1),
instmap.pre_lambda_update(ModuleInfo, Vars, Modes, InstMap1, InstMap2),
simplify_info_set_instmap(InstMap2, !Info),
% Don't attempt to pass structs into lambda_goals,
% since that could change the curried non-locals of the
% lambda_goal, and that would be difficult to fix up.
simplify_info_set_common_info(common_info_init, !Info),
% Don't attempt to pass structs out of lambda_goals.
simplify_goal(LambdaGoal0, LambdaGoal, !Info, !IO),
simplify_info_set_common_info(Common1, !Info),
simplify_info_set_instmap(InstMap1, !Info),
RT = lambda_goal(Purity, PredOrFunc, EvalMethod, NonLocals,
Vars, Modes, LambdaDeclaredDet, LambdaGoal),
simplify_info_leave_lambda(!Info),
Goal = unify(LT0, RT, M, U0, C),
GoalInfo = GoalInfo0
;
U0 = complicated_unify(UniMode, CanFail, TypeInfoVars)
->
( RT0 = var(V) ->
process_compl_unify(LT0, V, UniMode, CanFail, TypeInfoVars, C,
GoalInfo0, Goal1, !Info, !IO),
Goal1 = Goal - GoalInfo
;
unexpected(this_file, "invalid RHS for complicated unify")
)
;
simplify_do_common_struct(!.Info)
->
common_optimise_unification(U0, LT0, RT0, M, C,
Goal0, Goal, GoalInfo0, GoalInfo, !Info)
;
( simplify_do_opt_duplicate_calls(!.Info)
; simplify_do_warn_duplicate_calls(!.Info)
)
->
% We need to do the pass, to record the variable equivalences
% used for optimizing or warning about duplicate calls.
% But we don't want to perform the optimization, so we disregard
% the optimized goal and instead use the original one.
common_optimise_unification(U0, LT0, RT0, M, C,
Goal0, _Goal1, GoalInfo0, _GoalInfo1, !Info),
Goal = Goal0,
GoalInfo = GoalInfo0
;
Goal = Goal0,
GoalInfo = GoalInfo0
).
simplify_goal_2(if_then_else(Vars, Cond0, Then0, Else0), Goal,
GoalInfo0, GoalInfo, !Info, !IO) :-
% (A -> B ; 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 anaysis 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.
Cond0 = _ - CondInfo0,
goal_info_get_determinism(CondInfo0, CondDetism0),
determinism_components(CondDetism0, CondCanFail0, CondSolns0),
( CondCanFail0 = cannot_fail ->
goal_to_conj_list(Cond0, CondList),
goal_to_conj_list(Then0, ThenList),
list.append(CondList, ThenList, List),
simplify_goal(conj(plain_conj, List) - GoalInfo0, Goal - GoalInfo,
!Info, !IO),
goal_info_get_context(GoalInfo0, Context),
Msg = ite_cond_cannot_fail,
ContextMsg = context_det_msg(Context, Msg),
simplify_info_add_det_msg(ContextMsg, !Info),
simplify_info_set_requantify(!Info),
simplify_info_set_rerun_det(!Info)
; CondSolns0 = at_most_zero ->
% Optimize away the condition and the `then' part.
det_negation_det(CondDetism0, MaybeNegDetism),
(
Cond0 = not(NegCond) - _,
% 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
->
Cond = NegCond
;
(
MaybeNegDetism = yes(NegDetism1),
(
NegDetism1 = erroneous,
instmap_delta_init_unreachable(NegInstMapDelta1)
;
NegDetism1 = det,
instmap_delta_init_reachable(NegInstMapDelta1)
)
->
NegDetism = NegDetism1,
NegInstMapDelta = NegInstMapDelta1
;
unexpected(this_file, "goal_2: cannot get negated determinism")
),
goal_info_set_determinism(NegDetism, CondInfo0, NegCondInfo0),
goal_info_set_instmap_delta(NegInstMapDelta,
NegCondInfo0, NegCondInfo),
Cond = not(Cond0) - NegCondInfo
),
goal_to_conj_list(Else0, ElseList),
List = [Cond | ElseList],
simplify_goal(conj(plain_conj, List) - GoalInfo0, Goal - GoalInfo,
!Info, !IO),
goal_info_get_context(GoalInfo0, Context),
Msg = ite_cond_cannot_succeed,
ContextMsg = context_det_msg(Context, Msg),
simplify_info_add_det_msg(ContextMsg, !Info),
simplify_info_set_requantify(!Info),
simplify_info_set_rerun_det(!Info)
; Else0 = disj([]) - _ ->
% (A -> C ; fail) is equivalent to (A, C)
goal_to_conj_list(Cond0, CondList),
goal_to_conj_list(Then0, ThenList),
list.append(CondList, ThenList, List),
simplify_goal(conj(plain_conj, List) - GoalInfo0, Goal - GoalInfo,
!Info, !IO),
simplify_info_set_requantify(!Info),
simplify_info_set_rerun_det(!Info)
;
%
% recursively simplify the sub-goals,
% and rebuild the resulting if-then-else
%
Info0 = !.Info,
simplify_info_get_instmap(!.Info, InstMap0),
simplify_goal(Cond0, Cond, !Info, !IO),
simplify_info_update_instmap(Cond, !Info),
simplify_goal(Then0, Then, !Info, !IO),
simplify_info_post_branch_update(Info0, !Info),
simplify_goal(Else0, Else, !Info, !IO),
simplify_info_post_branch_update(Info0, !Info),
Cond = _ - CondInfo,
goal_info_get_instmap_delta(CondInfo, CondDelta),
Then = _ - ThenInfo,
goal_info_get_instmap_delta(ThenInfo, ThenDelta),
instmap_delta_apply_instmap_delta(CondDelta, ThenDelta,
test_size, CondThenDelta),
Else = _ - ElseInfo,
goal_info_get_instmap_delta(ElseInfo, ElseDelta),
goal_info_get_nonlocals(GoalInfo0, NonLocals),
simplify_info_get_module_info(!.Info, ModuleInfo0),
simplify_info_get_var_types(!.Info, VarTypes),
merge_instmap_deltas(InstMap0, NonLocals, VarTypes,
[CondThenDelta, ElseDelta], NewDelta,
ModuleInfo0, ModuleInfo1),
simplify_info_set_module_info(ModuleInfo1, !Info),
goal_info_set_instmap_delta(NewDelta, GoalInfo0, GoalInfo1),
IfThenElse = if_then_else(Vars, Cond, Then, Else),
goal_info_get_determinism(GoalInfo0, IfThenElseDetism0),
determinism_components(IfThenElseDetism0, IfThenElseCanFail,
IfThenElseNumSolns),
goal_info_get_determinism(CondInfo, CondDetism),
determinism_components(CondDetism, CondCanFail, CondSolns),
(
% 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 back-end depends on)
( CondCanFail = cannot_fail
; CondSolns = at_most_zero
; Else = disj([]) - _
)
->
simplify_info_undo_goal_updates(Info0, !Info),
simplify_goal_2(IfThenElse, Goal, GoalInfo1, GoalInfo, !Info,
!IO)
;
(
% If-then-elses that are det or semidet may nevertheless
% contain nondet or multidet conditions. If this happens,
% the if-then-else must be put inside a `scope' to appease the
% code generator. (Both the MLDS and LLDS back-ends rely
% on this.)
%
simplify_do_once(!.Info),
CondSolns = at_most_many,
IfThenElseNumSolns \= at_most_many
->
determinism_components(InnerDetism,
IfThenElseCanFail, at_most_many),
goal_info_set_determinism(InnerDetism, GoalInfo1, InnerInfo),
Goal = scope(commit(dont_force_pruning),
IfThenElse - InnerInfo)
;
Goal = IfThenElse
),
GoalInfo = GoalInfo1
)
).
simplify_goal_2(not(Goal0), Goal, GoalInfo0, GoalInfo, !Info, !IO) :-
% Can't use calls or unifications seen within a negation,
% since non-local variables may not be bound within the negation.
simplify_info_get_common_info(!.Info, Common),
simplify_goal(Goal0, Goal1, !Info, !IO),
simplify_info_set_common_info(Common, !Info),
Goal1 = _ - GoalInfo1,
goal_info_get_determinism(GoalInfo1, Detism),
determinism_components(Detism, CanFail, MaxSoln),
goal_info_get_context(GoalInfo0, Context),
( CanFail = cannot_fail ->
Msg = negated_goal_cannot_fail,
ContextMsg = context_det_msg(Context, Msg),
simplify_info_add_det_msg(ContextMsg, !Info)
; MaxSoln = at_most_zero ->
Msg = negated_goal_cannot_succeed,
ContextMsg = context_det_msg(Context, Msg),
simplify_info_add_det_msg(ContextMsg, !Info)
;
true
),
(
% replace `not true' with `fail'
Goal1 = conj(plain_conj, []) - _GoalInfo
->
Goal - GoalInfo = fail_goal_with_context(Context)
;
% replace `not fail' with `true'
Goal1 = disj([]) - _GoalInfo2
->
Goal - GoalInfo = true_goal_with_context(Context)
;
% remove double negation
Goal1 = not(SubGoal - SubGoalInfo) - _,
% 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
->
maybe_wrap_goal(GoalInfo0, SubGoalInfo, SubGoal, Goal, GoalInfo, !Info)
;
Goal = not(Goal1),
GoalInfo = GoalInfo0
).
simplify_goal_2(scope(Reason0, Goal1), GoalExpr, SomeInfo, GoalInfo,
!Info, !IO) :-
simplify_info_get_common_info(!.Info, Common),
simplify_goal(Goal1, Goal2, !Info, !IO),
nested_scopes(Reason0, Goal2, SomeInfo, Goal),
Goal = GoalExpr - GoalInfo,
( Goal = scope(FinalReason, _) - _ ->
(
FinalReason = promise_purity(_, _),
KeepCommon = yes
;
FinalReason = commit(_),
KeepCommon = no
;
FinalReason = from_ground_term(_),
KeepCommon = yes
;
FinalReason = barrier(removable),
KeepCommon = yes
;
FinalReason = barrier(not_removable),
KeepCommon = no
;
FinalReason = exist_quant(_),
KeepCommon = no
;
FinalReason = promise_solutions(_, _),
KeepCommon = no
),
(
KeepCommon = yes
;
KeepCommon = no,
% Replacing calls, constructions or deconstructions
% outside a commit with references to variables created
% inside the commit would increase the set of output
% variables of the goal inside the commit. This is not
% allowed because it could change the determinism.
%
% Thus we need to reset the common_info to what it
% was before processing the goal inside the commit,
% to ensure that we don't make any such replacements
% when processing the rest of the goal.
simplify_info_set_common_info(Common, !Info)
)
;
true
).
simplify_goal_2(Goal0, Goal, GoalInfo, GoalInfo, !Info, !IO) :-
Goal0 = foreign_proc(Attributes, PredId, ProcId, Args0, ExtraArgs0, Impl),
BoxPolicy = box_policy(Attributes),
(
BoxPolicy = native_if_possible,
Args = Args0,
ExtraArgs = ExtraArgs0,
Goal1 = Goal0
;
BoxPolicy = always_boxed,
Args = list.map(make_arg_always_boxed, Args0),
ExtraArgs = list.map(make_arg_always_boxed, ExtraArgs0),
Goal1 = foreign_proc(Attributes, PredId, ProcId, Args, ExtraArgs, Impl)
),
(
simplify_do_opt_duplicate_calls(!.Info),
goal_info_is_pure(GoalInfo),
ExtraArgs = []
->
ArgVars = list.map(foreign_arg_var, Args),
common_optimise_call(PredId, ProcId, ArgVars, GoalInfo, Goal1, Goal,
!Info)
;
Goal = Goal1
).
simplify_goal_2(shorthand(_), _, _, _, _, _, _, _) :-
% These should have been expanded out by now.
unexpected(this_file, "goal_2: unexpected shorthand").
:- func make_arg_always_boxed(foreign_arg) = foreign_arg.
make_arg_always_boxed(Arg) = Arg ^ arg_box_policy := always_boxed.
%-----------------------------------------------------------------------------%
:- pred inequality_goal(prog_var::in, prog_var::in, prog_var::in, string::in,
bool::in, hlds_goal_info::in, hlds_goal_expr::out, hlds_goal_info::out,
simplify_info::in, simplify_info::out) is det.
inequality_goal(TI, X, Y, Inequality, Invert, GoalInfo, GoalExpr, GoalInfo,
!Info) :-
% Construct the variable to hold the comparison result.
VarSet0 = !.Info ^ varset,
varset.new_var(VarSet0, R, VarSet),
!:Info = !.Info ^ varset := VarSet,
% We have to add the type of R to the var_types.
simplify_info_get_var_types(!.Info, VarTypes0),
VarTypes = VarTypes0 ^ elem(R) := comparison_result_type,
simplify_info_set_var_types(VarTypes, !Info),
% Construct the call to compare/3.
mercury_public_builtin_module(BuiltinModule),
hlds_goal.goal_info_get_context(GoalInfo, Context),
Args = [TI, R, X, Y],
simplify_info_get_instmap(!.Info, InstMap),
instmap.lookup_var(InstMap, X, XInst),
instmap.lookup_var(InstMap, Y, YInst),
simplify_info_get_module_info(!.Info, ModuleInfo),
ModeNo =
( if inst_is_unique(ModuleInfo, XInst) then
( if inst_is_unique(ModuleInfo, YInst) then 1 else 2 )
else
( if inst_is_unique(ModuleInfo, YInst) then 3 else 0 )
),
Unique = ground(unique, none),
ArgInsts = [R - Unique],
goal_util.generate_simple_call(BuiltinModule, "compare", predicate,
mode_no(ModeNo), det, Args, [], ArgInsts, ModuleInfo, Context,
CmpGoal0),
CmpGoal0 = CmpExpr - CmpInfo0,
goal_info_get_nonlocals(CmpInfo0, CmpNonLocals0),
goal_info_set_nonlocals(CmpNonLocals0 `insert` R, CmpInfo0, CmpInfo),
CmpGoal = CmpExpr - CmpInfo,
% Construct the unification R = Inequality.
ConsId = cons(qualified(BuiltinModule, Inequality), 0),
Bound = bound(shared, [functor(ConsId, [])]),
UMode = ((Unique -> Bound) - (Bound -> Bound)),
RHS = functor(ConsId, no, []),
UKind = deconstruct(R, ConsId, [], [], can_fail, cannot_cgc),
UContext = unify_context(implicit(
"replacement of inequality with call to compare/3"), []),
UfyExpr = unify(R, RHS, UMode, UKind, UContext),
goal_info_get_nonlocals(GoalInfo, UfyNonLocals0),
goal_info_set_nonlocals(UfyNonLocals0 `insert` R, GoalInfo, UfyInfo),
UfyGoal = UfyExpr - UfyInfo,
(
Invert = no,
GoalExpr = conj(plain_conj, [CmpGoal, UfyGoal])
;
Invert = yes,
GoalExpr = conj(plain_conj, [CmpGoal, not(UfyGoal) - UfyInfo])
).
%-----------------------------------------------------------------------------%
:- pred call_goal(pred_id::in, proc_id::in, list(prog_var)::in,
builtin_state::in, hlds_goal_expr::in, hlds_goal_expr::out,
hlds_goal_info::in, hlds_goal_info::out,
simplify_info::in, simplify_info::out) is det.
call_goal(PredId, ProcId, Args, IsBuiltin, Goal0, Goal, GoalInfo0, GoalInfo,
!Info) :-
simplify_info_get_module_info(!.Info, ModuleInfo),
module_info_pred_proc_info(ModuleInfo, PredId, ProcId, PredInfo, ProcInfo),
% Check for calls to predicates with `pragma obsolete' declarations.
(
simplify_do_warn_obsolete(!.Info),
pred_info_get_markers(PredInfo, Markers),
check_marker(Markers, obsolete),
simplify_info_get_det_info(!.Info, DetInfo0),
det_info_get_pred_id(DetInfo0, ThisPredId),
% Don't warn about directly recursive calls. (That would cause
% spurious warnings, particularly with builtin predicates,
% or preds defined using foreign_procs.)
PredId \= ThisPredId,
% Don't warn about calls from predicates that also have a
% `pramga obsolete' declaration. Doing so just results in
% spurious warnings.
module_info_pred_info(ModuleInfo, ThisPredId, ThisPredInfo),
pred_info_get_markers(ThisPredInfo, ThisPredMarkers),
not check_marker(ThisPredMarkers, obsolete)
->
goal_info_get_context(GoalInfo0, Context1),
ObsoleteMsg = warn_obsolete(PredId),
ObsoleteContextMsg = context_det_msg(Context1, ObsoleteMsg),
simplify_info_add_det_msg(ObsoleteContextMsg, !Info)
;
true
),
% Check for recursive calls with the same input arguments,
% and warn about them (since they will lead to infinite loops).
(
simplify_do_warn_simple_code(!.Info),
% Is this a (directly) recursive call, i.e. is the procedure being
% called the same as the procedure we're analyzing?
simplify_info_get_det_info(!.Info, DetInfo),
det_info_get_pred_id(DetInfo, PredId),
det_info_get_proc_id(DetInfo, ProcId),
% Don't count inline builtins. (The compiler generates code for
% builtins that looks recursive, so that you can take their address,
% but since the recursive call actually expands into inline code,
% it is not infinite recursion.)
IsBuiltin \= inline_builtin,
% Don't warn if we're inside a lambda goal, because the recursive call
% may not be executed.
\+ simplify_info_inside_lambda(!.Info),
% Are the input arguments the same (or equivalent)?
simplify_info_get_module_info(!.Info, ModuleInfo1),
module_info_pred_proc_info(ModuleInfo1, PredId, ProcId,
PredInfo1, ProcInfo1),
proc_info_get_headvars(ProcInfo1, HeadVars),
proc_info_get_argmodes(ProcInfo1, ArgModes),
simplify_info_get_common_info(!.Info, CommonInfo1),
input_args_are_equiv(Args, HeadVars, ArgModes,
CommonInfo1, ModuleInfo1),
% Don't warn if the input arguments' modes initial insts
% contain `any' insts, since the arguments might have become
% more constrained before the recursive call, in which case
% the recursion might eventually terminate.
%
% XXX The following check will only warn if the inputs are
% all fully ground; i.e. we won't warn in the case of
% partially instantiated insts such as list_skel(free).
% Still, it is better to miss warnings in that rare and
% unsupported case rather than to issue spurious warnings
% in cases involving `any' insts. We should only warn about
% definite nontermination here, not possible nontermination;
% warnings about possible nontermination should only be given
% if the termination analysis pass is enabled.
all [ArgMode] (
(
list.member(ArgMode, ArgModes),
mode_is_input(ModuleInfo1, ArgMode)
)
=>
mode_is_fully_input(ModuleInfo1, ArgMode)
),
% Don't count procs using minimal evaluation as they should always
% terminate if they have a finite number of answers.
\+ proc_info_get_eval_method(ProcInfo, eval_minimal(_)),
% Don't warn about impure procedures, since they may modify the state
% in ways not visible to us (unlike pure and semipure procedures).
pred_info_get_purity(PredInfo1, Purity),
\+ Purity = purity_impure
->
goal_info_get_context(GoalInfo0, Context2),
InfiniteRecMsg = warn_infinite_recursion,
InfiniteRecContextMsg = context_det_msg(Context2, InfiniteRecMsg),
simplify_info_add_det_msg(InfiniteRecContextMsg, !Info)
;
true
),
% Check for duplicate calls to the same procedure.
(
simplify_do_opt_duplicate_calls(!.Info),
goal_info_is_pure(GoalInfo0)
->
common_optimise_call(PredId, ProcId, Args, GoalInfo0, Goal0, Goal1,
!Info)
;
simplify_do_warn_duplicate_calls(!.Info),
goal_info_is_pure(GoalInfo0)
->
% We need to do the pass, for the warnings, but we ignore
% the optimized goal and instead use the original one.
common_optimise_call(PredId, ProcId, Args, GoalInfo0, Goal0, _Goal1,
!Info),
Goal1 = Goal0
;
Goal1 = Goal0
),
% Try to evaluate the call at compile-time.
( simplify_do_const_prop(!.Info) ->
simplify_info_get_instmap(!.Info, Instmap0),
simplify_info_get_module_info(!.Info, ModuleInfo2),
simplify_info_get_var_types(!.Info, VarTypes),
(
Goal1 = call(_, _, _, _, _, _),
const_prop.evaluate_call(PredId, ProcId, Args, VarTypes,
Instmap0, ModuleInfo2, Goal2, GoalInfo0, GoalInfo2)
->
Goal = Goal2,
GoalInfo = GoalInfo2,
simplify_info_set_module_info(ModuleInfo2, !Info),
simplify_info_set_requantify(!Info)
;
Goal = Goal1,
GoalInfo = GoalInfo0
)
;
Goal = Goal1,
GoalInfo = GoalInfo0
).
%-----------------------------------------------------------------------------%
:- pred process_compl_unify(prog_var::in, prog_var::in, uni_mode::in,
can_fail::in, list(prog_var)::in, unify_context::in, hlds_goal_info::in,
hlds_goal::out, simplify_info::in, simplify_info::out,
io::di, io::uo) is det.
process_compl_unify(XVar, YVar, UniMode, CanFail, _OldTypeInfoVars, Context,
GoalInfo0, Goal, !Info, !IO) :-
simplify_info_get_module_info(!.Info, ModuleInfo),
simplify_info_get_var_types(!.Info, VarTypes),
map.lookup(VarTypes, XVar, Type),
( Type = variable(TypeVar, Kind) ->
%
% Convert polymorphic unifications into calls to `unify/2',
% the general unification predicate, passing the appropriate type_info:
% unify(TypeInfoVar, X, Y)
% where TypeInfoVar is the type_info variable associated with
% the type of the variables that are being unified.
%
type_info_locn(TypeVar, Kind, TypeInfoVar, ExtraGoals, !Info),
call_generic_unify(TypeInfoVar, XVar, YVar, ModuleInfo, !.Info,
Context, GoalInfo0, Call)
; type_is_higher_order(Type, _, _, _, _) ->
% Convert higher-order unifications into calls to
% builtin_unify_pred (which calls error/1).
goal_info_get_context(GoalInfo0, GContext),
generate_simple_call(mercury_private_builtin_module,
"builtin_unify_pred", predicate, mode_no(0), semidet,
[XVar, YVar], [], [], ModuleInfo, GContext, Call0 - _),
simplify_goal_2(Call0, Call1, GoalInfo0, GoalInfo, !Info, !IO),
Call = Call1 - GoalInfo,
ExtraGoals = []
;
( type_to_ctor_and_args(Type, TypeCtorPrime, TypeArgsPrime) ->
TypeCtor = TypeCtorPrime,
TypeArgs = TypeArgsPrime
;
unexpected(this_file, "type_to_ctor_and_args failed")
),
determinism_components(Det, CanFail, at_most_one),
unify_proc.lookup_mode_num(ModuleInfo, TypeCtor, UniMode, Det,
ProcId),
module_info_get_globals(ModuleInfo, Globals),
globals.lookup_bool_option(Globals, special_preds, SpecialPreds),
(
hlds_pred.in_in_unification_proc_id(ProcId),
(
SpecialPreds = no
;
SpecialPreds = yes,
% For most imported types we only generate unification
% predicate declarations if they are needed for complicated
% unifications other than proc_id 0. higher_order.m will
% specialize these cases if possible.
%
special_pred_is_generated_lazily(ModuleInfo, TypeCtor)
)
->
make_type_info_vars([Type], TypeInfoVars, ExtraGoals, !Info),
( TypeInfoVars = [TypeInfoVarPrime] ->
TypeInfoVar = TypeInfoVarPrime
;
unexpected(this_file, "process_compl_unify: " ++
"more than one typeinfo for one type var")
),
call_generic_unify(TypeInfoVar, XVar, YVar, ModuleInfo, !.Info,
Context, GoalInfo0, Call)
;
% Convert other complicated unifications into calls to
% specific unification predicates, inserting extra typeinfo
% arguments if necessary.
make_type_info_vars(TypeArgs, TypeInfoVars, ExtraGoals, !Info),
call_specific_unify(TypeCtor, TypeInfoVars, XVar, YVar, ProcId,
ModuleInfo, Context, GoalInfo0, Call0, CallGoalInfo0),
simplify_goal_2(Call0, Call1, CallGoalInfo0, CallGoalInfo1, !Info,
!IO),
Call = Call1 - CallGoalInfo1
)
),
list.append(ExtraGoals, [Call], ConjList),
conj_list_to_goal(ConjList, GoalInfo0, Goal).
:- pred call_generic_unify(prog_var::in, prog_var::in, prog_var::in,
module_info::in, simplify_info::in, unify_context::in,
hlds_goal_info::in, hlds_goal::out) is det.
call_generic_unify(TypeInfoVar, XVar, YVar, ModuleInfo, _, _, GoalInfo,
Call) :-
ArgVars = [TypeInfoVar, XVar, YVar],
goal_info_get_context(GoalInfo, Context),
goal_util.generate_simple_call(mercury_public_builtin_module,
"unify", predicate, mode_no(0), semidet, ArgVars, [], [],
ModuleInfo, Context, Call).
:- pred call_specific_unify(type_ctor::in, list(prog_var)::in,
prog_var::in, prog_var::in, proc_id::in,
module_info::in, unify_context::in, hlds_goal_info::in,
hlds_goal_expr::out, hlds_goal_info::out) is det.
call_specific_unify(TypeCtor, TypeInfoVars, XVar, YVar, ProcId, ModuleInfo,
Context, GoalInfo0, CallExpr, CallGoalInfo) :-
% Create the new call goal.
list.append(TypeInfoVars, [XVar, YVar], ArgVars),
module_info_get_special_pred_map(ModuleInfo, SpecialPredMap),
map.lookup(SpecialPredMap, spec_pred_unify - TypeCtor, PredId),
module_info_pred_info(ModuleInfo, PredId, PredInfo),
ModuleName = pred_info_module(PredInfo),
PredName = pred_info_name(PredInfo),
SymName = qualified(ModuleName, PredName),
CallContext = call_unify_context(XVar, var(YVar), Context),
CallExpr = call(PredId, ProcId, ArgVars, not_builtin,
yes(CallContext), SymName),
% Add the extra type_info vars to the nonlocals for the call.
goal_info_get_nonlocals(GoalInfo0, NonLocals0),
set.insert_list(NonLocals0, TypeInfoVars, NonLocals),
goal_info_set_nonlocals(NonLocals, GoalInfo0, CallGoalInfo).
:- pred make_type_info_vars(list(mer_type)::in, list(prog_var)::out,
list(hlds_goal)::out, simplify_info::in, simplify_info::out) is det.
make_type_info_vars(Types, TypeInfoVars, TypeInfoGoals, !Info) :-
% Extract the information from simplify_info.
simplify_info_get_det_info(!.Info, DetInfo0),
simplify_info_get_varset(!.Info, VarSet0),
simplify_info_get_var_types(!.Info, VarTypes0),
simplify_info_get_rtti_varmaps(!.Info, RttiVarMaps0),
det_info_get_module_info(DetInfo0, ModuleInfo0),
det_info_get_pred_id(DetInfo0, PredId),
det_info_get_proc_id(DetInfo0, ProcId),
some [!PredInfo, !ProcInfo, !PolyInfo] (
% The varset, vartypes and rtti_varmaps get updated by the call to
% polymorphism.m, below. That module will work on the poly_info,
% however, which is derived from the information in the proc_info.
% Therefore we:
% - copy the info from the simplify_info to the proc_info,
% - create a poly_info from the proc_info,
% - do the polymorphism transformation,
% - extract info from the poly_info and put it in the proc_info,
% - copy the information from the proc_info back into the
% simplify_info.
module_info_pred_proc_info(ModuleInfo0, PredId, ProcId,
!:PredInfo, !:ProcInfo),
proc_info_set_vartypes(VarTypes0, !ProcInfo),
proc_info_set_varset(VarSet0, !ProcInfo),
proc_info_set_rtti_varmaps(RttiVarMaps0, !ProcInfo),
% Call polymorphism.m to create the type_infos.
create_poly_info(ModuleInfo0, !.PredInfo, !.ProcInfo, !:PolyInfo),
term.context_init(Context),
polymorphism.make_type_info_vars(Types, Context,
TypeInfoVars, TypeInfoGoals, !PolyInfo),
poly_info_extract(!.PolyInfo, !PredInfo, !ProcInfo, ModuleInfo1),
proc_info_get_vartypes(!.ProcInfo, VarTypes),
proc_info_get_varset(!.ProcInfo, VarSet),
proc_info_get_rtti_varmaps(!.ProcInfo, RttiVarMaps),
simplify_info_set_var_types(VarTypes, !Info),
simplify_info_set_varset(VarSet, !Info),
simplify_info_set_rtti_varmaps(RttiVarMaps, !Info),
% Put the new proc_info and pred_info back in the module_info
% and put the new module_info back in the simplify_info.
module_info_set_pred_proc_info(PredId, ProcId, !.PredInfo, !.ProcInfo,
ModuleInfo1, ModuleInfo)
),
simplify_info_set_module_info(ModuleInfo, !Info).
:- pred type_info_locn(tvar::in, kind::in, prog_var::out,
list(hlds_goal)::out, simplify_info::in, simplify_info::out) is det.
type_info_locn(TypeVar, Kind, TypeInfoVar, Goals, !Info) :-
simplify_info_get_rtti_varmaps(!.Info, RttiVarMaps),
rtti_lookup_type_info_locn(RttiVarMaps, TypeVar, TypeInfoLocn),
(
% If the typeinfo is available in a variable, just use it.
TypeInfoLocn = type_info(TypeInfoVar),
Goals = []
;
% If the typeinfo is in a typeclass_info then we need to extract it.
TypeInfoLocn = typeclass_info(TypeClassInfoVar, Index),
extract_type_info(TypeVar, Kind, TypeClassInfoVar, Index, Goals,
TypeInfoVar, !Info)
).
:- pred extract_type_info(tvar::in, kind::in, prog_var::in, int::in,
list(hlds_goal)::out, prog_var::out,
simplify_info::in, simplify_info::out) is det.
extract_type_info(TypeVar, Kind, TypeClassInfoVar, Index, Goals, TypeInfoVar,
!Info) :-
simplify_info_get_module_info(!.Info, ModuleInfo),
simplify_info_get_varset(!.Info, VarSet0),
simplify_info_get_var_types(!.Info, VarTypes0),
simplify_info_get_rtti_varmaps(!.Info, RttiVarMaps0),
polymorphism.gen_extract_type_info(TypeVar, Kind, TypeClassInfoVar, Index,
ModuleInfo, Goals, TypeInfoVar, VarSet0, VarSet, VarTypes0, VarTypes,
RttiVarMaps0, RttiVarMaps),
simplify_info_set_var_types(VarTypes, !Info),
simplify_info_set_varset(VarSet, !Info),
simplify_info_set_rtti_varmaps(RttiVarMaps, !Info).
%-----------------------------------------------------------------------------%
% input_args_are_equiv(Args, HeadVars, Modes, CommonInfo, ModuleInfo1):
%
% Succeeds if all the input arguments (determined by looking at `Modes')
% in `Args' are equivalent (according to the equivalence class specified
% by `CommonInfo') to the corresponding variables in HeadVars.
% HeadVars, Modes, and Args should all be lists of the same length.
%
:- pred input_args_are_equiv(list(prog_var)::in, list(prog_var)::in,
list(mer_mode)::in, common_info::in, module_info::in) is semidet.
input_args_are_equiv([], [], _, _, _).
input_args_are_equiv([Arg | Args], [HeadVar | HeadVars], [Mode | Modes],
CommonInfo, ModuleInfo) :-
( mode_is_input(ModuleInfo, Mode) ->
common_vars_are_equivalent(Arg, HeadVar, CommonInfo)
;
true
),
input_args_are_equiv(Args, HeadVars, Modes, CommonInfo, ModuleInfo).
%-----------------------------------------------------------------------------%
% replace nested `scope's with a single `scope',
%
:- pred nested_scopes(scope_reason::in, hlds_goal::in,
hlds_goal_info::in, hlds_goal::out) is det.
nested_scopes(Reason0, InnerGoal0, OuterGoalInfo, Goal) :-
nested_scopes_2(Reason0, Reason, InnerGoal0, InnerGoal),
InnerGoal = _ - GoalInfo,
(
Reason = exist_quant(_),
goal_info_get_determinism(GoalInfo, Detism),
goal_info_get_determinism(OuterGoalInfo, Detism)
->
% If the inner and outer detisms match the `some' scope is unnecessary.
Goal = InnerGoal
;
Goal = scope(Reason, InnerGoal) - OuterGoalInfo
).
:- pred nested_scopes_2(scope_reason::in, scope_reason::out,
hlds_goal::in, hlds_goal::out) is det.
nested_scopes_2(Reason0, Reason, Goal0, Goal) :-
(
Goal0 = scope(Reason1, Goal1) - _GoalInfo0,
(
Reason0 = exist_quant(Vars0),
Reason1 = exist_quant(Vars1)
->
Reason2 = exist_quant(Vars0 ++ Vars1)
;
Reason0 = from_ground_term(_)
->
Reason2 = Reason1
;
Reason1 = from_ground_term(_)
->
Reason2 = Reason0
;
Reason0 = barrier(Removable0),
Reason1 = barrier(Removable1)
->
(
Removable0 = removable,
Removable1 = removable
->
Removable2 = removable
;
Removable2 = not_removable
),
Reason2 = barrier(Removable2)
;
Reason0 = commit(ForcePruning0),
Reason1 = commit(ForcePruning1)
->
(
ForcePruning0 = dont_force_pruning,
ForcePruning1 = dont_force_pruning
->
ForcePruning2 = dont_force_pruning
;
ForcePruning2 = force_pruning
),
Reason2 = commit(ForcePruning2)
;
fail
)
->
nested_scopes_2(Reason2, Reason, Goal1, Goal)
;
Reason = Reason0,
Goal = Goal0
).
%-----------------------------------------------------------------------------%
% When removing a level of wrapping around a goal, if the determinisms
% are not the same, we really need to rerun determinism analysis on the
% procedure. I think this is a similar situation to inlining of erroneous
% goals. The safe thing to do is to wrap a `scope' around the inner goal
% if the inner and outer determinisms are not the same. It probably
% won't happen that often.
%
:- pred maybe_wrap_goal(hlds_goal_info::in, hlds_goal_info::in,
hlds_goal_expr::in, hlds_goal_expr::out, hlds_goal_info::out,
simplify_info::in, simplify_info::out) is det.
maybe_wrap_goal(OuterGoalInfo, InnerGoalInfo, Goal1, Goal, GoalInfo, !Info) :-
(
goal_info_get_determinism(InnerGoalInfo, Det),
goal_info_get_determinism(OuterGoalInfo, Det)
->
Goal = Goal1,
GoalInfo = InnerGoalInfo
;
Goal = scope(commit(dont_force_pruning), Goal1 - InnerGoalInfo),
GoalInfo = OuterGoalInfo,
simplify_info_set_rerun_det(!Info)
).
%-----------------------------------------------------------------------------%
:- pred simplify_conj(list(hlds_goal)::in, list(hlds_goal)::in,
list(hlds_goal)::out, hlds_goal_info::in,
simplify_info::in, simplify_info::out, io::di, io::uo) is det.
simplify_conj([], RevGoals, Goals, _, !Info, !IO) :-
list.reverse(RevGoals, Goals).
simplify_conj([Goal0 | Goals0], !.RevGoals, Goals, ConjInfo, !Info, !IO) :-
Info0 = !.Info,
% Flatten conjunctions.
( Goal0 = conj(plain_conj, SubGoals) - _ ->
list.append(SubGoals, Goals0, Goals1),
simplify_conj(Goals1, !.RevGoals, Goals, ConjInfo, !Info, !IO)
;
simplify_goal(Goal0, Goal1, !Info, !IO),
(
% Flatten conjunctions.
Goal1 = conj(plain_conj, SubGoals1) - _
->
simplify_info_undo_goal_updates(Info0, !Info),
list.append(SubGoals1, Goals0, Goals1),
simplify_conj(Goals1, !.RevGoals, Goals, ConjInfo, !Info, !IO)
;
% Delete unreachable goals.
(
simplify_info_get_instmap(!.Info, InstMap1),
instmap.is_unreachable(InstMap1)
;
Goal1 = _ - GoalInfo1,
goal_info_get_determinism(GoalInfo1, Detism1),
determinism_components(Detism1, _, at_most_zero)
)
->
conjoin_goal_and_rev_goal_list(Goal1, !RevGoals),
(
( Goal1 = disj([]) - _
; Goals0 = []
)
->
true
;
% 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.
Goal0 = _ - GoalInfo0,
goal_info_get_context(GoalInfo0, Context),
FailGoal = fail_goal_with_context(Context),
conjoin_goal_and_rev_goal_list(FailGoal, !RevGoals)
),
list.reverse(!.RevGoals, Goals)
;
conjoin_goal_and_rev_goal_list(Goal1, !RevGoals),
simplify_info_update_instmap(Goal1, !Info),
simplify_conj(Goals0, !.RevGoals, Goals, ConjInfo, !Info, !IO)
)
).
:- pred conjoin_goal_and_rev_goal_list(hlds_goal::in,
hlds_goals::in, hlds_goals::out) is det.
conjoin_goal_and_rev_goal_list(Goal, RevGoals0, RevGoals) :-
( Goal = conj(plain_conj, Goals) - _ ->
list.reverse(Goals, Goals1),
list.append(Goals1, RevGoals0, RevGoals)
;
RevGoals = [Goal | RevGoals0]
).
%-----------------------------------------------------------------------------%
:- pred simplify_par_conj(list(hlds_goal)::in, list(hlds_goal)::out,
simplify_info::in, simplify_info::in, simplify_info::out,
io::di, io::uo) is det.
simplify_par_conj([], [], _, !Info, !IO).
simplify_par_conj([Goal0 |Goals0], [Goal | Goals], Info0, !Info, !IO) :-
simplify_goal(Goal0, Goal, !Info, !IO),
simplify_info_post_branch_update(Info0, !Info),
simplify_par_conj(Goals0, Goals, Info0, !Info, !IO).
%-----------------------------------------------------------------------------%
:- 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) :-
( simplify_do_excess_assign(!.Info) ->
goal_info_get_nonlocals(ConjInfo, ConjNonLocals),
map.init(Subn0),
simplify_info_get_module_info(!.Info, ModuleInfo),
module_info_get_globals(ModuleInfo, Globals),
globals.get_trace_level(Globals, TraceLevel),
globals.lookup_bool_option(Globals, trace_optimized, TraceOptimized),
simplify_info_get_varset(!.Info, VarSet0),
find_excess_assigns_in_conj(TraceLevel, TraceOptimized,
VarSet0, ConjNonLocals, Goals0, [], RevGoals, Subn0, Subn1),
( map.is_empty(Subn1) ->
Goals = Goals0
;
renaming_transitive_closure(Subn1, Subn),
list.reverse(RevGoals, Goals1),
MustSub = no,
rename_vars_in_goals(MustSub, Subn, Goals1, Goals),
map.keys(Subn0, RemovedVars),
varset.delete_vars(VarSet0, RemovedVars, VarSet),
simplify_info_set_varset(VarSet, !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)
)
;
Goals = Goals0
).
:- type var_renaming == map(prog_var, prog_var).
:- pred find_excess_assigns_in_conj(trace_level::in, bool::in,
prog_varset::in, set(prog_var)::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(Trace, TraceOptimized, VarSet, ConjNonLocals,
[Goal | Goals], !RevGoals, !Subn) :-
(
goal_is_excess_assign(Trace, TraceOptimized, VarSet, ConjNonLocals,
Goal, !Subn)
->
true
;
!:RevGoals = [Goal | !.RevGoals]
),
find_excess_assigns_in_conj(Trace, TraceOptimized, VarSet, ConjNonLocals,
Goals, !RevGoals, !Subn).
:- pred goal_is_excess_assign(trace_level::in, bool::in, prog_varset::in,
set(prog_var)::in, hlds_goal::in, var_renaming::in,
var_renaming::out) is semidet.
goal_is_excess_assign(Trace, TraceOptimized, VarSet, ConjNonLocals, Goal0,
!Subn) :-
Goal0 = unify(_, _, _, Unif, _) - _,
Unif = assign(LeftVar0, RightVar0),
% Check if we've already substituted one or both of the variables.
find_renamed_var(!.Subn, LeftVar0, LeftVar),
find_renamed_var(!.Subn, RightVar0, RightVar),
CanElimLeft = ( set.member(LeftVar, ConjNonLocals) -> no ; yes ),
CanElimRight = ( set.member(RightVar, ConjNonLocals) -> no ; yes ),
% If we have a choice, eliminate an unnamed variable.
( CanElimLeft = yes, CanElimRight = yes ->
( var_is_named(VarSet, LeftVar) ->
ElimVar = RightVar,
ReplacementVar = LeftVar
;
ElimVar = LeftVar,
ReplacementVar = RightVar
)
; CanElimLeft = yes ->
ElimVar = LeftVar,
ReplacementVar = RightVar
; CanElimRight = yes ->
ElimVar = RightVar,
ReplacementVar = LeftVar
;
fail
),
map.det_insert(!.Subn, ElimVar, ReplacementVar, !:Subn),
% 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.
\+ (
trace_level_needs_meaningful_var_names(Trace) = yes,
TraceOptimized = no,
var_is_named(VarSet, ElimVar),
\+ var_is_named(VarSet, ReplacementVar)
).
:- pred var_is_named(prog_varset::in, prog_var::in) is semidet.
var_is_named(VarSet, Var) :-
varset.search_name(VarSet, Var, Name),
\+ (
string.append("HeadVar__", Suffix, Name),
string.to_int(Suffix, _)
).
:- pred find_renamed_var(var_renaming::in, prog_var::in, prog_var::out) is det.
find_renamed_var(Subn, Var0, Var) :-
( map.search(Subn, Var0, Var1) ->
find_renamed_var(Subn, Var1, Var)
;
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(
(pred(_::in, Value0::in, Value::out) is det :-
find_renamed_var(VarRenaming0, Value0, Value)
), VarRenaming0, VarRenaming).
%-----------------------------------------------------------------------------%
:- pred simplify_switch(prog_var::in, list(case)::in, list(case)::in,
list(case)::out, list(instmap_delta)::in, list(instmap_delta)::out,
can_fail::in, can_fail::out, simplify_info::in,
simplify_info::in, simplify_info::out, io::di, io::uo) is det.
simplify_switch(_, [], RevCases, Cases, !InstMaps, !CanFail, _, !Info, !IO) :-
list.reverse(RevCases, Cases).
simplify_switch(Var, [Case0 | Cases0], RevCases0, Cases, !InstMaps,
!CanFail, Info0, !Info, !IO) :-
simplify_info_get_instmap(Info0, InstMap0),
Case0 = case(ConsId, Goal0),
simplify_info_get_module_info(!.Info, ModuleInfo0),
simplify_info_get_var_types(!.Info, VarTypes),
map.lookup(VarTypes, Var, Type),
instmap.bind_var_to_functor(Var, Type, ConsId, InstMap0, InstMap1,
ModuleInfo0, ModuleInfo1),
simplify_info_set_module_info(ModuleInfo1, !Info),
simplify_info_set_instmap(InstMap1, !Info),
simplify_goal(Goal0, Goal, !Info, !IO),
% Remove failing branches.
( Goal = disj([]) - _ ->
RevCases = RevCases0,
!:CanFail = can_fail
;
Case = case(ConsId, Goal),
Goal = _ - GoalInfo,
%
% Make sure the switched on variable appears in the instmap delta.
% This avoids an abort in merge_instmap_delta if another branch
% further instantiates the switched-on variable. If the switched on
% variable does not appear in this branch's instmap_delta, the inst
% before the goal would be used, resulting in a mode error.
%
goal_info_get_instmap_delta(GoalInfo, InstMapDelta0),
simplify_info_get_module_info(!.Info, ModuleInfo2),
instmap_delta_bind_var_to_functor(Var, Type, ConsId,
InstMap0, InstMapDelta0, InstMapDelta, ModuleInfo2, ModuleInfo),
simplify_info_set_module_info(ModuleInfo, !Info),
!:InstMaps = [InstMapDelta | !.InstMaps],
RevCases = [Case | RevCases0]
),
simplify_info_post_branch_update(Info0, !Info),
simplify_switch(Var, Cases0, RevCases, Cases, !InstMaps, !CanFail, Info0,
!Info, !IO).
% Create a semidet unification at the start of a singleton case
% in a can_fail switch.
% This will abort if the cons_id is existentially typed.
%
:- pred create_test_unification(prog_var::in, cons_id::in, int::in,
hlds_goal::out, simplify_info::in, simplify_info::out) is det.
create_test_unification(Var, ConsId, ConsArity, ExtraGoal - ExtraGoalInfo,
!Info) :-
simplify_info_get_varset(!.Info, VarSet0),
simplify_info_get_var_types(!.Info, VarTypes0),
varset.new_vars(VarSet0, ConsArity, ArgVars, VarSet),
map.lookup(VarTypes0, Var, VarType),
simplify_info_get_module_info(!.Info, ModuleInfo),
type_util.get_cons_id_arg_types(ModuleInfo, VarType, ConsId, ArgTypes),
map.det_insert_from_corresponding_lists(VarTypes0, ArgVars,
ArgTypes, VarTypes),
simplify_info_set_varset(VarSet, !Info),
simplify_info_set_var_types(VarTypes, !Info),
simplify_info_get_instmap(!.Info, InstMap),
instmap.lookup_var(InstMap, Var, Inst0),
(
inst_expand(ModuleInfo, Inst0, Inst1),
get_arg_insts(Inst1, ConsId, ConsArity, ArgInsts1)
->
ArgInsts = ArgInsts1
;
unexpected(this_file, "create_test_unification - get_arg_insts failed")
),
InstToUniMode =
(pred(ArgInst::in, ArgUniMode::out) is det :-
ArgUniMode = ((ArgInst - free) -> (ArgInst - ArgInst))
),
list.map(InstToUniMode, ArgInsts, UniModes),
UniMode = (Inst0 -> Inst0) - (Inst0 -> Inst0),
UnifyContext = unify_context(explicit, []),
Unification = deconstruct(Var, ConsId, ArgVars, UniModes, can_fail,
cannot_cgc),
ExtraGoal = unify(Var, functor(ConsId, no, ArgVars),
UniMode, Unification, UnifyContext),
set.singleton_set(NonLocals, Var),
% The test can't bind any variables, so the InstMapDelta should be empty.
instmap_delta_init_reachable(InstMapDelta),
goal_info_init(NonLocals, InstMapDelta, semidet, purity_pure,
ExtraGoalInfo).
%-----------------------------------------------------------------------------%
:- pred simplify_disj(list(hlds_goal)::in, list(hlds_goal)::in,
list(hlds_goal)::out,
list(instmap_delta)::in, list(instmap_delta)::out,
simplify_info::in, simplify_info::in, simplify_info::out,
io::di, io::uo) is det.
simplify_disj([], RevGoals, Goals, !PostBranchInstMaps, _, !Info, !IO) :-
list.reverse(RevGoals, Goals).
simplify_disj([Goal0 | Goals0], RevGoals0, Goals, !PostBranchInstMaps,
Info0, !Info, !IO) :-
simplify_goal(Goal0, Goal, !Info, !IO),
Goal = _ - GoalInfo,
(
% Don't prune or warn about impure disjuncts that can't succeed.
\+ goal_info_is_impure(GoalInfo),
goal_info_get_determinism(GoalInfo, Detism),
determinism_components(Detism, _CanFail, MaxSolns),
MaxSolns = at_most_zero
->
(
simplify_do_warn_simple_code(!.Info),
% Don't warn where the initial goal was fail, since that can result
% from mode analysis pruning away cases in a switch which cannot
% succeed due to sub-typing in the modes.
Goal0 \= disj([]) - _
->
goal_info_get_context(GoalInfo, Context),
Msg = zero_soln_disjunct,
ContextMsg = context_det_msg(Context, Msg),
simplify_info_add_det_msg(ContextMsg, !Info)
;
true
),
% Prune away non-succeeding disjuncts where possible.
(
(
Goal0 = disj([]) - _
;
% Only remove disjuncts that might loop
% or call error/1 if --no-fully-strict.
simplify_info_get_det_info(!.Info, DetInfo),
det_info_get_fully_strict(DetInfo, no)
)
->
RevGoals1 = RevGoals0
;
RevGoals1 = [Goal | RevGoals0],
goal_info_get_instmap_delta(GoalInfo, InstMapDelta),
!:PostBranchInstMaps = [InstMapDelta | !.PostBranchInstMaps]
)
;
RevGoals1 = [Goal | RevGoals0],
goal_info_get_instmap_delta(GoalInfo, InstMapDelta),
!:PostBranchInstMaps = [InstMapDelta | !.PostBranchInstMaps]
),
simplify_info_post_branch_update(Info0, !Info),
simplify_disj(Goals0, RevGoals1, Goals, !PostBranchInstMaps, Info0, !Info,
!IO).
% Disjunctions that cannot succeed more than once when viewed from the
% outside generally need some fixing up, and/or some warnings to be issued.
%
% We previously converted them all to if-then-elses using the code below,
% however converting disjs that have output variables but that nevertheless
% cannot succeed more than one (e.g. cc_nondet or cc_multi disjs) into
% if-then-elses may cause problems with other parts of the compiler that
% assume that an if-then-else is mode-correct, i.e. that the condition
% doesn't bind variables.
%
% goal_info_get_determinism(GoalInfo, Detism),
% determinism_components(Detism, _CanFail, MaxSoln),
% MaxSoln \= at_most_many
% ->
% goal_info_get_instmap_delta(GoalInfo, DeltaInstMap),
% goal_info_get_nonlocals(GoalInfo, NonLocalVars),
% (
% det_no_output_vars(NonLocalVars, InstMap0,
% DeltaInstMap, DetInfo)
% ->
% OutputVars = no
% ;
% OutputVars = yes
% ),
% fixup_disj(Disjuncts, Detism, OutputVars, GoalInfo, InstMap0,
% DetInfo, Goal, MsgsA, Msgs)
% ;
%
:- pred fixup_disj(list(hlds_goal)::in, determinism::in, bool::in,
hlds_goal_info::in, hlds_goal_expr::out,
simplify_info::in, simplify_info::out, io::di, io::uo) is det.
fixup_disj(Disjuncts, _, _OutputVars, GoalInfo, Goal, !Info, !IO) :-
det_disj_to_ite(Disjuncts, GoalInfo, IfThenElse),
simplify_goal(IfThenElse, Simplified, !Info, !IO),
Simplified = Goal - _.
% det_disj_to_ite is used to transform disjunctions that occur
% in prunable contexts into if-then-elses.
% For example, it would transform
%
% ( Disjunct1
% ; Disjunct2
% ; Disjunct3
% )
% into
% ( Disjunct1 ->
% true
% ; Disjunct2 ->
% true
% ;
% Disjunct3
% ).
%
:- pred det_disj_to_ite(list(hlds_goal)::in, hlds_goal_info::in,
hlds_goal::out) is det.
det_disj_to_ite([], _GoalInfo, _) :-
unexpected(this_file, "reached base case of det_disj_to_ite").
det_disj_to_ite([Disjunct | Disjuncts], GoalInfo, Goal) :-
(
Disjuncts = [],
Goal = Disjunct
;
Disjuncts = [_ | _],
Cond = Disjunct,
Cond = _CondGoal - CondGoalInfo,
Then = true_goal,
det_disj_to_ite(Disjuncts, GoalInfo, Rest),
Rest = _RestGoal - RestGoalInfo,
goal_info_get_nonlocals(CondGoalInfo, CondNonLocals),
goal_info_get_nonlocals(RestGoalInfo, RestNonLocals),
set.union(CondNonLocals, RestNonLocals, NonLocals),
goal_info_set_nonlocals(NonLocals, GoalInfo, NewGoalInfo0),
goal_info_get_instmap_delta(GoalInfo, InstMapDelta0),
instmap_delta_restrict(NonLocals, InstMapDelta0, InstMapDelta),
goal_info_set_instmap_delta(InstMapDelta, NewGoalInfo0, NewGoalInfo1),
goal_info_get_determinism(CondGoalInfo, CondDetism),
goal_info_get_determinism(RestGoalInfo, RestDetism),
determinism_components(CondDetism, CondCanFail, CondMaxSoln),
determinism_components(RestDetism, RestCanFail, RestMaxSoln),
det_disjunction_canfail(CondCanFail, RestCanFail, CanFail),
det_disjunction_maxsoln(CondMaxSoln, RestMaxSoln, MaxSoln0),
( MaxSoln0 = at_most_many ->
MaxSoln = at_most_one
;
MaxSoln = MaxSoln0
),
determinism_components(Detism, CanFail, MaxSoln),
goal_info_set_determinism(Detism, NewGoalInfo1, NewGoalInfo),
Goal = if_then_else([], Cond, Then, Rest) - NewGoalInfo
).
%-----------------------------------------------------------------------------%
:- pred contains_multisoln_goal(list(hlds_goal)::in) is semidet.
contains_multisoln_goal(Goals) :-
list.member(_Goal - GoalInfo, Goals),
goal_info_get_determinism(GoalInfo, Detism),
determinism_components(Detism, _, at_most_many).
%-----------------------------------------------------------------------------%
:- type simplify_info
---> simplify_info(
det_info :: det_info,
msgs :: set(context_det_msg),
simplifications :: simplifications,
common_info :: common_info,
% Info about common subexpressions.
instmap :: instmap,
varset :: prog_varset,
inst_varset :: inst_varset,
requantify :: bool,
% Does the goal need requantification.
recompute_atomic :: bool,
% Do we need to recompute
% instmap_deltas for atomic goals
rerun_det :: bool,
% Does determinism analysis need to
% be rerun.
cost_delta :: int,
% Measure of the improvement in
% the goal from simplification.
lambdas :: int,
% Count of the number of lambdas
% which enclose the current goal.
rtti_varmaps :: rtti_varmaps,
% Information about type_infos and
% typeclass_infos.
format_calls :: bool
% Do we have any calls to
% string.format and io.format?
).
simplify_info_init(DetInfo, Simplifications, InstMap, ProcInfo, Info) :-
proc_info_get_varset(ProcInfo, VarSet),
proc_info_get_inst_varset(ProcInfo, InstVarSet),
proc_info_get_rtti_varmaps(ProcInfo, RttiVarMaps),
set.init(Msgs),
Info = simplify_info(DetInfo, Msgs, Simplifications,
common_info_init, InstMap, VarSet, InstVarSet,
no, no, no, 0, 0, RttiVarMaps, no).
% Reinitialise the simplify_info before reprocessing a goal.
%
:- pred simplify_info_reinit(simplifications::in, instmap::in,
simplify_info::in, simplify_info::out) is det.
simplify_info_reinit(Simplifications, InstMap0, !Info) :-
!:Info = !.Info ^ simplifications := Simplifications,
!:Info = !.Info ^ common_info := common_info_init,
!:Info = !.Info ^ instmap := InstMap0,
!:Info = !.Info ^ requantify := no,
!:Info = !.Info ^ recompute_atomic := no,
!:Info = !.Info ^ rerun_det := no,
!:Info = !.Info ^ lambdas := 0.
% exported for common.m
:- interface.
:- import_module parse_tree.prog_data.
:- import_module set.
:- pred simplify_info_init(det_info::in, simplifications::in,
instmap::in, proc_info::in, simplify_info::out) is det.
:- pred simplify_info_get_det_info(simplify_info::in, det_info::out) is det.
:- pred simplify_info_get_det_msgs(simplify_info::in,
set(context_det_msg)::out) is det.
:- pred simplify_info_get_simplifications(simplify_info::in,
simplifications::out) is det.
:- pred simplify_info_get_common_info(simplify_info::in, common_info::out)
is det.
:- pred simplify_info_get_instmap(simplify_info::in, instmap::out) is det.
:- pred simplify_info_get_varset(simplify_info::in, prog_varset::out) is det.
:- pred simplify_info_get_var_types(simplify_info::in, vartypes::out) is det.
:- pred simplify_info_requantify(simplify_info::in) is semidet.
:- pred simplify_info_recompute_atomic(simplify_info::in) is semidet.
:- pred simplify_info_rerun_det(simplify_info::in) is semidet.
:- pred simplify_info_get_cost_delta(simplify_info::in, int::out) is det.
:- pred simplify_info_get_rtti_varmaps(simplify_info::in, rtti_varmaps::out)
is det.
:- pred simplify_info_get_module_info(simplify_info::in, module_info::out)
is det.
:- pred simplify_info_get_pred_info(simplify_info::in, pred_info::out) is det.
:- pred simplify_info_set_common_info(common_info::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_set_requantify(
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_set_rerun_det(
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_set_rtti_varmaps(rtti_varmaps::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_do_add_det_msg(context_det_msg::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_incr_cost_delta(int::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_apply_type_substitution(tsubst::in,
simplify_info::in, simplify_info::out) is det.
:- implementation.
:- pred simplify_info_get_format_calls(simplify_info::in, bool::out) is det.
simplify_info_get_det_info(Info, Info ^ det_info).
simplify_info_get_det_msgs(Info, Info ^ msgs).
simplify_info_get_simplifications(Info, Info ^ simplifications).
simplify_info_get_common_info(Info, Info ^ common_info).
simplify_info_get_instmap(Info, Info ^ instmap).
simplify_info_get_varset(Info, Info ^ varset).
simplify_info_get_var_types(Info, VarTypes) :-
det_info_get_vartypes(Info ^ det_info, VarTypes).
simplify_info_requantify(Info) :-
Info ^ requantify = yes.
simplify_info_recompute_atomic(Info) :-
Info ^ recompute_atomic = yes.
simplify_info_rerun_det(Info) :-
Info ^ rerun_det = yes.
simplify_info_get_cost_delta(Info, Info ^ cost_delta).
simplify_info_get_rtti_varmaps(Info, Info ^ rtti_varmaps).
simplify_info_get_format_calls(Info, Info ^ format_calls).
simplify_info_get_module_info(Info, ModuleInfo) :-
simplify_info_get_det_info(Info, DetInfo),
det_info_get_module_info(DetInfo, ModuleInfo).
simplify_info_get_pred_info(Info, PredInfo) :-
simplify_info_get_det_info(Info, DetInfo),
det_info_get_module_info(DetInfo, ModuleInfo),
det_info_get_pred_id(DetInfo, PredId),
module_info_pred_info(ModuleInfo, PredId, PredInfo).
:- pred simplify_info_set_det_info(det_info::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_set_det_msgs(set(context_det_msg)::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_set_simplifications(simplifications::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_set_instmap(instmap::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_set_varset(prog_varset::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_set_var_types(vartypes::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_set_recompute_atomic(
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_set_format_calls(bool::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_add_det_msg(context_det_msg::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_set_cost_delta(int::in,
simplify_info::in, simplify_info::out) is det.
:- pred simplify_info_enter_lambda(simplify_info::in, simplify_info::out)
is det.
:- pred simplify_info_leave_lambda(simplify_info::in, simplify_info::out)
is det.
:- pred simplify_info_inside_lambda(simplify_info::in) is semidet.
:- pred simplify_info_set_module_info(module_info::in,
simplify_info::in, simplify_info::out) is det.
simplify_info_set_det_info(Det, Info, Info ^ det_info := Det).
simplify_info_set_det_msgs(Msgs, Info, Info ^ msgs := Msgs).
simplify_info_set_simplifications(Simp, Info, Info ^ simplifications := Simp).
simplify_info_set_instmap(InstMap, Info, Info ^ instmap := InstMap).
simplify_info_set_common_info(Common, Info, Info ^ common_info := Common).
simplify_info_set_varset(VarSet, Info, Info ^ varset := VarSet).
simplify_info_set_var_types(VarTypes, Info, Info ^ det_info := DetInfo) :-
det_info_set_vartypes(Info ^ det_info, VarTypes, DetInfo).
simplify_info_set_requantify(Info, Info ^ requantify := yes).
simplify_info_set_recompute_atomic(Info, Info ^ recompute_atomic := yes).
simplify_info_set_rerun_det(Info, Info ^ rerun_det := yes).
simplify_info_set_cost_delta(Delta, Info, Info ^ cost_delta := Delta).
simplify_info_set_rtti_varmaps(Rtti, Info, Info ^ rtti_varmaps := Rtti).
simplify_info_set_format_calls(FC, Info, Info ^ format_calls := FC).
simplify_info_incr_cost_delta(Incr, Info,
Info ^ cost_delta := Info ^ cost_delta + Incr).
simplify_info_add_det_msg(Msg, !Info) :-
( simplify_do_warn_simple_code(!.Info) ->
simplify_info_do_add_det_msg(Msg, !Info)
;
true
).
simplify_info_do_add_det_msg(Msg, !Info) :-
simplify_info_get_det_msgs(!.Info, Msgs0),
set.insert(Msgs0, Msg, Msgs),
simplify_info_set_det_msgs(Msgs, !Info).
simplify_info_enter_lambda(Info, Info ^ lambdas := Info ^ lambdas + 1).
simplify_info_leave_lambda(Info, Info ^ lambdas := LambdaCount) :-
LambdaCount1 = Info ^ lambdas - 1,
( LambdaCount1 >= 0 ->
LambdaCount = LambdaCount1
;
unexpected(this_file,
"simplify_info_leave_lambda: Left too many lambdas")
).
simplify_info_inside_lambda(Info) :-
Info ^ lambdas > 0.
simplify_info_set_module_info(ModuleInfo, !Info) :-
simplify_info_get_det_info(!.Info, DetInfo0),
det_info_set_module_info(DetInfo0, ModuleInfo, DetInfo),
simplify_info_set_det_info(DetInfo, !Info).
simplify_info_apply_type_substitution(TSubst, !Info) :-
simplify_info_get_var_types(!.Info, VarTypes0),
simplify_info_get_rtti_varmaps(!.Info, RttiVarMaps0),
ApplyTSubst = (pred(_::in, T0::in, T::out) is det :-
apply_rec_subst_to_type(TSubst, T0, T)
),
map.map_values(ApplyTSubst, VarTypes0, VarTypes),
apply_substitutions_to_rtti_varmaps(map.init, TSubst, map.init,
RttiVarMaps0, RttiVarMaps),
simplify_info_set_var_types(VarTypes, !Info),
simplify_info_set_rtti_varmaps(RttiVarMaps, !Info).
:- pred simplify_info_update_instmap(hlds_goal::in,
simplify_info::in, simplify_info::out) is det.
simplify_info_update_instmap(Goal, Info, Info ^ instmap := InstMap) :-
update_instmap(Goal, Info ^ instmap, InstMap).
:- type before_after
---> before
; after.
% Clear the common_info structs accumulated since the last goal that
% could cause a stack flush. This is done to avoid replacing a
% deconstruction with assignments to the arguments where this
% would cause more variables to be live across the stack flush.
% Calls and construction unifications are not treated in this
% way since it is nearly always better to optimize them away.
% When doing deforestation, it may be better to remove
% as many common structures as possible.
%
:- pred simplify_info_maybe_clear_structs(before_after::in, hlds_goal::in,
simplify_info::in, simplify_info::out) is det.
simplify_info_maybe_clear_structs(BeforeAfter, Goal, !Info) :-
(
simplify_do_common_struct(!.Info),
\+ simplify_do_extra_common_struct(!.Info),
Goal = GoalExpr - _,
will_flush(GoalExpr, BeforeAfter) = yes
->
simplify_info_get_common_info(!.Info, CommonInfo0),
common_info_clear_structs(CommonInfo0, CommonInfo),
simplify_info_set_common_info(CommonInfo, !Info)
;
true
).
% Succeed if execution of the given goal cannot encounter a context
% that causes any variable to be flushed to its stack slot or to a
% register at the specified time.
%
:- func will_flush(hlds_goal_expr, before_after) = bool.
will_flush(unify(_, _, _, Unify, _), _) = WillFlush :-
( Unify = complicated_unify(_, _, _) ->
WillFlush = yes
;
WillFlush = no
).
will_flush(call(_, _, _, BuiltinState, _, _), BeforeAfter) = WillFlush :-
( BuiltinState = inline_builtin ->
WillFlush = no
;
(
BeforeAfter = before,
WillFlush = no
;
BeforeAfter = after,
WillFlush = yes
)
).
will_flush(generic_call(GenericCall, _, _, _), BeforeAfter) = WillFlush :-
(
GenericCall = higher_order(_, _, _, _),
WillFlush0 = yes
;
GenericCall = class_method(_, _, _, _),
WillFlush0 = yes
;
GenericCall = cast(_),
WillFlush0 = no
),
(
BeforeAfter = before,
WillFlush = no
;
BeforeAfter = after,
WillFlush = WillFlush0
).
will_flush(foreign_proc(_, _, _, _, _, _), BeforeAfter) = WillFlush :-
(
BeforeAfter = before,
WillFlush = no
;
BeforeAfter = after,
WillFlush = yes
).
will_flush(conj(ConjType, _), _) = WillFlush :-
(
ConjType = plain_conj,
WillFlush = no
;
ConjType = parallel_conj,
WillFlush = yes
).
will_flush(switch(_, _, _), _) = no.
will_flush(disj(_), BeforeAfter) = WillFlush :-
(
BeforeAfter = before,
WillFlush = yes
;
BeforeAfter = after,
WillFlush = no
).
will_flush(if_then_else(_, _, _, _), BeforeAfter) = WillFlush :-
(
BeforeAfter = before,
WillFlush = yes
;
BeforeAfter = after,
WillFlush = no
).
will_flush(not(_), _) = yes.
will_flush(scope(_, _), _) = no.
will_flush(shorthand(_), _) = _ :-
% These should have been expanded out by now.
unexpected(this_file, "will_flush: unexpected shorthand").
% Reset the instmap and seen calls for the next branch.
%
:- pred simplify_info_post_branch_update(simplify_info::in, simplify_info::in,
simplify_info::out) is det.
simplify_info_post_branch_update(PreBranchInfo, PostBranchInfo0, Info) :-
simplify_info_get_instmap(PreBranchInfo, InstMap),
simplify_info_set_instmap(InstMap, PostBranchInfo0, PostBranchInfo1),
simplify_info_get_common_info(PreBranchInfo, Common),
simplify_info_set_common_info(Common, PostBranchInfo1, Info).
% Undo updates to the simplify_info before redoing simplification
% on a goal.
%
:- pred simplify_info_undo_goal_updates(simplify_info::in, simplify_info::in,
simplify_info::out) is det.
simplify_info_undo_goal_updates(Info0, !Info) :-
simplify_info_get_common_info(Info0, CommonInfo0),
simplify_info_set_common_info(CommonInfo0, !Info),
simplify_info_get_instmap(Info0, InstMap),
simplify_info_set_instmap(InstMap, !Info).
%-----------------------------------------------------------------------------%
:- func this_file = string.
this_file = "simplify.m".
%-----------------------------------------------------------------------------%
:- end_module simplify.
%-----------------------------------------------------------------------------%