Files
mercury/compiler/deforest.m
Zoltan Somogyi 44d1f0db9c Give some predicates shorter names.
compiler/prog_type_subst.m:
compiler/type_util.m:
    Apply s/apply_variable_renaming_to_/apply_renaming_to_/ and
    s/_to_x_list/_to_xs/ to the names of predicate.

    Conform to the change in hlds_class.m below.

compiler/hlds_class.m:
    This module used to define types named (a) hlds_constraint, and
    (b) hlds_constraints, and the latter was NOT a list of items
    of type hlds_constraint. Rename the latter to hlds_constraint_db
    to free up the name apply_renaming_to_constraints to apply
    to list(hlds_constraint). However, the rename also makes code
    operating on hlds_constraint_dbs easier to understand. Before
    this diff, several modules used variables named Constraints
    to refer to a list(hlds_constraint) in some places and to
    what is now a hlds_constraint_db in other places, which is confusing;
    the latter are now named ConstraintDb.

compiler/type_assign.m:
    Conform to the changes above.

    Add an XXX about some existing variable names that *look* right
    but turn out to be subtly misleading.

compiler/add_pragma_type_spec.m:
compiler/add_type.m:
compiler/check_typeclass.m:
compiler/comp_unit_interface.m:
compiler/cse_detection.m:
compiler/ctgc.util.m:
compiler/decide_type_repn.m:
compiler/deforest.m:
compiler/equiv_type.m:
compiler/equiv_type_hlds.m:
compiler/higher_order.higher_order_global_info.m:
compiler/higher_order.make_specialized_preds.m:
compiler/higher_order.specialize_calls.m:
compiler/hlds_rtti.m:
compiler/inlining.m:
compiler/modecheck_coerce.m:
compiler/old_type_constraints.m:
compiler/polymorphism_clause.m:
compiler/polymorphism_goal.m:
compiler/polymorphism_type_class_info.m:
compiler/prog_type_unify.m:
compiler/qual_info.m:
compiler/recompilation.version.m:
compiler/resolve_unify_functor.m:
compiler/typecheck.m:
compiler/typecheck_clauses.m:
compiler/typecheck_cons_infos.m:
compiler/typecheck_debug.m:
compiler/typecheck_error_type_assign.m:
compiler/typecheck_errors.m:
compiler/typecheck_unify_var_functor.m:
compiler/typecheck_util.m:
compiler/typeclasses.m:
compiler/unify_proc.m:
compiler/var_table.m:
compiler/vartypes.m:
    Conform to the changes above.
2025-10-21 18:21:35 +11:00

2257 lines
92 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 1999-2012 The University of Melbourne.
% Copyright (C) 2014-2017, 2019-2025 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%
%
% File: deforest.m.
% Main author: stayl.
%
% Deforestation attempts to remove multiple traversals over data structures,
% and construction followed by immediate deconstruction of data structures.
% It does this by combining the bodies of pairs of called procedures in a
% conjunction where the top-level functor of one of the argument variables of
% the first called procedure is known at the end of some of the branches of
% the body of that procedure, and the second called procedure switches on that
% variable.
%
% The deforestation pass also inlines calls for which the top-level goal in
% the called procedure is a switch and the functor of the switched-on variable
% is known. This allows simplify.m to prune away the failing branches.
%
% The constraint propagation pass, which is called from the deforestation
% pass, transforms the code so that goals which could fail are executed as
% early as possible.
%
% For a more detailed description, see Simon Taylor's Honours thesis,
% available from the papers page of mercurylang.org.
%
%-----------------------------------------------------------------------------%
:- module transform_hlds.deforest.
:- interface.
:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module io.
%-----------------------------------------------------------------------------%
:- pred deforest_module(io.text_output_stream::in,
module_info::in, module_info::out) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.
:- import_module check_hlds.det_analysis.
:- import_module check_hlds.recompute_instmap_deltas.
:- import_module check_hlds.simplify.
:- import_module check_hlds.simplify.simplify_tasks.
:- import_module hlds.goal_form.
:- import_module hlds.goal_reorder.
:- import_module hlds.goal_util.
:- import_module hlds.goal_vars.
:- import_module hlds.hlds_dependency_graph.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_markers.
:- import_module hlds.hlds_pred.
:- import_module hlds.hlds_proc_util.
:- import_module hlds.inst_test.
:- import_module hlds.instmap.
:- import_module hlds.mode_util.
:- import_module hlds.passes_aux.
:- import_module hlds.quantification.
:- import_module hlds.var_table_hlds.
:- import_module libs.
:- import_module libs.dependency_graph.
:- import_module libs.globals.
:- import_module libs.optimization_options.
:- import_module libs.options.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.
:- import_module parse_tree.error_util.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_detism.
:- import_module parse_tree.prog_type_subst.
:- import_module parse_tree.set_of_var.
:- import_module parse_tree.var_table.
:- import_module transform_hlds.inlining.
:- import_module transform_hlds.pd_cost.
:- import_module transform_hlds.pd_debug.
:- import_module transform_hlds.pd_info.
:- import_module transform_hlds.pd_term.
:- import_module transform_hlds.pd_util.
:- import_module assoc_list.
:- import_module bool.
:- import_module getopt.
:- import_module int.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module set.
:- import_module string.
:- import_module term.
:- import_module univ.
:- import_module varset.
%-----------------------------------------------------------------------------%
deforest_module(ProgressStream, !ModuleInfo) :-
proc_arg_info_init(ProcArgInfo0),
type_to_univ(ProcArgInfo0, UnivProcArgInfo0),
% Find out which arguments of each procedure are switched on at the top
% level or are constructed in a way which is possibly deforestable.
Task0 = update_module_cookie(get_branch_vars_proc_univ, UnivProcArgInfo0),
process_valid_nonimported_procs_update(Task0, Task, !ModuleInfo),
( if
Task = update_module_cookie(_, UnivProcArgInfo),
univ_to_type(UnivProcArgInfo, ProcArgInfo1)
then
ProcArgInfo = ProcArgInfo1
else
unexpected($pred, "passes_aux stuffed up")
),
% We process the module bottom-up to make estimation of the
% cost improvement of new versions a little more accurate and
% also to avoid redoing optimizations.
module_info_ensure_dependency_info(!ModuleInfo, DepInfo),
DepList = dependency_info_get_condensed_bottom_up_sccs(DepInfo),
pd_info_init(ProgressStream, !.ModuleInfo, ProcArgInfo, PDInfo0),
list.foldl(deforest_proc, DepList, PDInfo0, PDInfo),
pd_info_get_module_info(PDInfo, !:ModuleInfo),
module_info_clobber_dependency_info(!ModuleInfo),
pd_info_get_versions(PDInfo, VersionIndex),
map.keys(VersionIndex, Versions),
module_info_get_globals(!.ModuleInfo, Globals),
globals.get_opt_tuple(Globals, OptTuple),
PropConstraints = OptTuple ^ ot_prop_constraints,
( if
PropConstraints = prop_constraints,
Versions = [_ | _]
then
% We can sometimes improve efficiency by rerunning determinism
% inference on the specialized versions after constraint propagation,
% because some nondet predicates will have become semidet.
list.foldl(reset_inferred_proc_determinism, Versions, !ModuleInfo),
disable_det_warnings(_OptionsToRestore, Globals, NoWarnGlobals),
module_info_set_globals(NoWarnGlobals, !ModuleInfo),
determinism_pass(ProgressStream, Specs, !ModuleInfo),
module_info_set_globals(Globals, !ModuleInfo),
FoundErrors = contains_errors(Globals, Specs),
expect(unify(FoundErrors, no), $pred,
"determinism errors after deforestation")
else
true
).
:- pred reset_inferred_proc_determinism(pred_proc_id::in,
module_info::in, module_info::out) is det.
reset_inferred_proc_determinism(PredProcId, !ModuleInfo) :-
module_info_pred_proc_info(!.ModuleInfo, PredProcId, PredInfo, ProcInfo0),
proc_info_get_inferred_determinism(ProcInfo0, Detism0),
determinism_components(Detism0, _, MaxSolns),
(
MaxSolns = at_most_many_cc
% `cc_multi' or `cc_nondet' determinisms are never inferred,
% so resetting the determinism would cause determinism errors.
;
( MaxSolns = at_most_zero
; MaxSolns = at_most_one
; MaxSolns = at_most_many
),
proc_info_set_inferred_determinism(detism_erroneous,
ProcInfo0, ProcInfo),
module_info_set_pred_proc_info(PredProcId, PredInfo, ProcInfo,
!ModuleInfo)
).
:- pred proc_arg_info_init(map(pred_proc_id, pd_proc_arg_info)::out) is det.
proc_arg_info_init(ProcArgInfo0) :-
map.init(ProcArgInfo0).
:- pred get_branch_vars_proc_univ(pred_proc_id::in,
proc_info::in, proc_info::out, module_info::in, module_info::out,
univ::in, univ::out) is det.
get_branch_vars_proc_univ(PredProcId, ProcInfo, ProcInfo,
!ModuleInfo, UnivProcArgInfo0, UnivProcArgInfo) :-
det_univ_to_type(UnivProcArgInfo0, ProcArgInfo0),
pd_util.get_branch_vars_proc(PredProcId, ProcInfo, !ModuleInfo,
ProcArgInfo0, ProcArgInfo),
type_to_univ(ProcArgInfo, UnivProcArgInfo).
:- pred deforest_proc(pred_proc_id::in, pd_info::in, pd_info::out) is det.
deforest_proc(PredProcId, !PDInfo) :-
deforest_proc_deltas(PredProcId, _, _, !PDInfo).
:- pred deforest_proc_deltas(pred_proc_id::in, int::out, int::out,
pd_info::in, pd_info::out) is det.
deforest_proc_deltas(PredProcId, CostDelta, SizeDelta, !PDInfo) :-
some [!ModuleInfo, !PredInfo, !ProcInfo, !Goal] (
pd_info_get_progress_stream(!.PDInfo, ProgressStream),
pd_info_get_module_info(!.PDInfo, !:ModuleInfo),
trace [io(!IO)] (
maybe_write_proc_progress_message(ProgressStream, !.ModuleInfo,
"Deforesting", PredProcId, !IO)
),
module_info_pred_proc_info(!.ModuleInfo, PredProcId,
!:PredInfo, !:ProcInfo),
pd_info_init_unfold_info(PredProcId, !.PredInfo, !.ProcInfo, !PDInfo),
proc_info_get_goal(!.ProcInfo, !:Goal),
% Inlining may have created some opportunities for simplification.
module_info_get_globals(!.ModuleInfo, Globals),
find_simplify_tasks(Globals, do_not_generate_warnings, SimplifyTasks),
pd_util.pd_simplify_goal("deforest_proc_deltas", SimplifyTasks,
!Goal, !PDInfo),
pd_util.propagate_constraints(!Goal, !PDInfo),
trace [io(!IO)] (
pd_debug_output_goal(!.PDInfo, "deforest_proc_deltas",
"after constraints\n", !.Goal, !IO)
),
deforest_goal(!Goal, !PDInfo),
pd_info_get_proc_info(!.PDInfo, !:ProcInfo),
proc_info_set_goal(!.Goal, !ProcInfo),
pd_info_get_changed(!.PDInfo, Changed),
(
Changed = yes,
pd_info_get_module_info(!.PDInfo, !:ModuleInfo),
requantify_proc_general(ord_nl_no_lambda, !ProcInfo),
proc_info_get_goal(!.ProcInfo, !:Goal),
proc_info_get_initial_instmap(!.ModuleInfo, !.ProcInfo, InstMap0),
proc_info_get_var_table(!.ProcInfo, VarTable),
proc_info_get_inst_varset(!.ProcInfo, InstVarSet),
recompute_instmap_delta(recomp_atomics, VarTable, InstVarSet,
InstMap0, !Goal, !ModuleInfo),
pd_info_set_module_info(!.ModuleInfo, !PDInfo),
pd_info_get_pred_info(!.PDInfo, !:PredInfo),
proc_info_set_goal(!.Goal, !ProcInfo),
module_info_set_pred_proc_info(PredProcId,
!.PredInfo, !.ProcInfo, !ModuleInfo),
pd_info_get_rerun_det(!.PDInfo, RerunDet),
(
RerunDet = yes,
PredProcId = proc(PredId, ProcId),
% If the determinism of some sub-goals has changed,
% then we re-run determinism analysis. As with inlining.m,
% this avoids problems with inlining erroneous procedures.
det_infer_proc_ignore_msgs(ProgressStream, PredId, ProcId,
!ModuleInfo)
;
RerunDet = no
),
% Recompute the branch_info for the procedure.
pd_info_get_proc_arg_info(!.PDInfo, ProcArgInfo0),
pd_util.get_branch_vars_proc(PredProcId, !.ProcInfo,
!ModuleInfo, ProcArgInfo0, ProcArgInfo),
pd_info_set_proc_arg_info(ProcArgInfo, !PDInfo),
pd_info_set_module_info(!.ModuleInfo, !PDInfo)
;
Changed = no,
pd_info_get_module_info(!.PDInfo, !:ModuleInfo),
pd_info_get_pred_info(!.PDInfo, !:PredInfo),
module_info_set_pred_proc_info(PredProcId, !.PredInfo, !.ProcInfo,
!ModuleInfo),
pd_info_set_module_info(!.ModuleInfo, !PDInfo)
),
pd_info_get_module_info(!.PDInfo, !:ModuleInfo),
trace [io(!IO)] (
maybe_write_proc_progress_message(ProgressStream, !.ModuleInfo,
"Finished deforesting", PredProcId, !IO)
),
pd_info_get_cost_delta(!.PDInfo, CostDelta),
pd_info_get_size_delta(!.PDInfo, SizeDelta),
pd_info_unset_unfold_info(!PDInfo)
).
:- pred deforest_goal(hlds_goal::in, hlds_goal::out,
pd_info::in, pd_info::out) is det.
deforest_goal(Goal0, Goal, !PDInfo) :-
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
deforest_goal_expr(GoalExpr0, GoalExpr, GoalInfo0, GoalInfo, !PDInfo),
Goal = hlds_goal(GoalExpr, GoalInfo).
:- pred deforest_goal_expr(hlds_goal_expr::in, hlds_goal_expr::out,
hlds_goal_info::in, hlds_goal_info::out, pd_info::in, pd_info::out) is det.
deforest_goal_expr(GoalExpr0, GoalExpr, !GoalInfo, !PDInfo) :-
(
GoalExpr0 = conj(ConjType, Goals0),
some [!Goals] (
!:Goals = Goals0,
(
ConjType = plain_conj,
pd_info_get_instmap(!.PDInfo, InstMap0),
partially_evaluate_conj_goals(!.Goals, [], !:Goals, !PDInfo),
pd_info_set_instmap(InstMap0, !PDInfo),
NonLocals = goal_info_get_nonlocals(!.GoalInfo),
pd_info_get_module_info(!.PDInfo, ModuleInfo),
module_info_get_globals(ModuleInfo, Globals),
globals.get_opt_tuple(Globals, OptTuple),
Deforest = OptTuple ^ ot_deforest,
(
Deforest = deforest,
compute_goal_infos(!Goals, !PDInfo),
pd_info_set_instmap(InstMap0, !PDInfo),
deforest_conj(!.Goals, NonLocals, [], !:Goals, !PDInfo)
;
Deforest = do_not_deforest
),
PropConstraints = OptTuple ^ ot_prop_constraints,
pd_info_set_instmap(InstMap0, !PDInfo),
(
PropConstraints = prop_constraints,
propagate_conj_constraints(!.Goals, NonLocals, [], !:Goals,
!PDInfo)
;
PropConstraints = do_not_prop_constraints
),
pd_info_set_instmap(InstMap0, !PDInfo)
;
ConjType = parallel_conj
% XXX cannot deforest across parallel_conjunctions!
),
Goals = !.Goals
),
GoalExpr = conj(ConjType, Goals)
;
GoalExpr0 = disj(Goals0),
deforest_disj(Goals0, Goals, !PDInfo),
GoalExpr = disj(Goals)
;
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
pd_info_get_instmap(!.PDInfo, InstMap0),
deforest_goal(Cond0, Cond, !PDInfo),
pd_info_update_goal(Cond, !PDInfo),
deforest_goal(Then0, Then, !PDInfo),
pd_info_set_instmap(InstMap0, !PDInfo),
deforest_goal(Else0, Else, !PDInfo),
pd_info_set_instmap(InstMap0, !PDInfo),
GoalExpr = if_then_else(Vars, Cond, Then, Else)
;
GoalExpr0 = switch(Var, CanFail, Cases0),
deforest_cases(Var, Cases0, Cases, !PDInfo),
GoalExpr = switch(Var, CanFail, Cases)
;
GoalExpr0 = negation(SubGoal0),
deforest_goal(SubGoal0, SubGoal, !PDInfo),
GoalExpr = negation(SubGoal)
;
GoalExpr0 = scope(Reason, SubGoal0),
( if
Reason = from_ground_term(_, FGT),
( FGT = from_ground_term_construct
; FGT = from_ground_term_deconstruct
)
then
SubGoal = SubGoal0
else
deforest_goal(SubGoal0, SubGoal, !PDInfo)
),
GoalExpr = scope(Reason, SubGoal)
;
GoalExpr0 = plain_call(PredId, ProcId, Args, BuiltinState, _, Name),
deforest_call(PredId, ProcId, Args, Name, BuiltinState,
hlds_goal(GoalExpr0, !.GoalInfo), hlds_goal(GoalExpr, !:GoalInfo),
!PDInfo)
;
( GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _)
; GoalExpr0 = generic_call(_, _, _, _, _)
; GoalExpr = unify(_, _, _, _, _)
),
GoalExpr = GoalExpr0
;
GoalExpr0 = shorthand(_),
% These should have been expanded out by now.
unexpected($pred, "shorthand")
).
%-----------------------------------------------------------------------------%
:- pred deforest_disj(list(hlds_goal)::in, list(hlds_goal)::out,
pd_info::in, pd_info::out) is det.
deforest_disj([], [], !PDInfo).
deforest_disj([Goal0 | Goals0], [Goal | Goals], !PDInfo) :-
pd_info_get_instmap(!.PDInfo, InstMap0),
deforest_goal(Goal0, Goal, !PDInfo),
pd_info_set_instmap(InstMap0, !PDInfo),
deforest_disj(Goals0, Goals, !PDInfo).
%-----------------------------------------------------------------------------%
:- pred deforest_cases(prog_var::in, list(case)::in, list(case)::out,
pd_info::in, pd_info::out) is det.
deforest_cases(_, [], [], !PDInfo).
deforest_cases(Var, [Case0 | Cases0], [Case | Cases], !PDInfo) :-
Case0 = case(MainConsId, OtherConsIds, Goal0),
% Bind Var to MainConsId or one of the OtherConsIds in the instmap
% before processing this case.
pd_info_get_instmap(!.PDInfo, InstMap0),
pd_info_bind_var_to_functors(Var, MainConsId, OtherConsIds, !PDInfo),
deforest_goal(Goal0, Goal, !PDInfo),
Case = case(MainConsId, OtherConsIds, Goal),
pd_info_set_instmap(InstMap0, !PDInfo),
deforest_cases(Var, Cases0, Cases, !PDInfo).
%-----------------------------------------------------------------------------%
% Perform partial evaluation on the goals of a conjunction.
%
:- pred partially_evaluate_conj_goals(list(hlds_goal)::in,
list(hlds_goal)::in, list(hlds_goal)::out,
pd_info::in, pd_info::out) is det.
partially_evaluate_conj_goals([], RevGoals, Goals, !PDInfo) :-
list.reverse(RevGoals, Goals).
partially_evaluate_conj_goals([Goal0 | Goals0], RevGoals0, Goals, !PDInfo) :-
deforest_goal(Goal0, Goal1, !PDInfo),
pd_info_update_goal(Goal1, !PDInfo),
( if Goal1 = hlds_goal(conj(plain_conj, Goals1), _) then
list.reverse(Goals1, RevGoals1),
list.append(RevGoals1, RevGoals0, RevGoals2)
else
RevGoals2 = [Goal1 | RevGoals0]
),
partially_evaluate_conj_goals(Goals0, RevGoals2, Goals, !PDInfo).
%-----------------------------------------------------------------------------%
% Compute the branch info for each goal in a conjunction.
%
:- pred compute_goal_infos(list(hlds_goal)::in, annotated_conj::out,
pd_info::in, pd_info::out) is det.
compute_goal_infos([], [], !PDInfo).
compute_goal_infos([Goal | Goals0], [Goal - MaybeBranchInfo | Goals],
!PDInfo) :-
deforest_get_branch_vars_goal(Goal, MaybeBranchInfo, !PDInfo),
pd_info_update_goal(Goal, !PDInfo),
compute_goal_infos(Goals0, Goals, !PDInfo).
:- pred deforest_get_branch_vars_goal(hlds_goal::in,
maybe(pd_branch_info(prog_var))::out, pd_info::in, pd_info::out) is det.
deforest_get_branch_vars_goal(Goal, MaybeBranchInfo, !PDInfo) :-
Goal = hlds_goal(GoalExpr, _),
(
( GoalExpr = disj(_)
; GoalExpr = switch(_, _, _)
; GoalExpr = if_then_else(_, _, _, _)
),
pd_util.get_branch_vars_goal(Goal, MaybeBranchInfo, !PDInfo)
;
( GoalExpr = unify(_, _, _, _, _)
; GoalExpr = generic_call(_, _, _, _, _)
; GoalExpr = call_foreign_proc(_, _, _, _, _, _, _)
; GoalExpr = conj(_, _)
; GoalExpr = negation(_)
; GoalExpr = scope(_, _)
),
MaybeBranchInfo = no
;
GoalExpr = plain_call(PredId, ProcId, Args, _, _, _),
pd_info_get_proc_arg_info(!.PDInfo, ProcBranchInfos),
( if
map.search(ProcBranchInfos, proc(PredId, ProcId), BranchInfo0)
then
% Rename the branch_info for the called procedure
% onto the argument variables.
pd_util.convert_branch_info(BranchInfo0, Args, BranchInfo),
MaybeBranchInfo = yes(BranchInfo)
else
MaybeBranchInfo = no
)
;
GoalExpr = shorthand(_),
unexpected($pred, "shorthand")
).
%-----------------------------------------------------------------------------%
:- pred propagate_conj_constraints(list(hlds_goal)::in,
set_of_progvar::in, list(hlds_goal)::in, list(hlds_goal)::out,
pd_info::in, pd_info::out) is det.
propagate_conj_constraints([], _, RevGoals, Goals, !PDInfo) :-
list.reverse(RevGoals, Goals).
propagate_conj_constraints([Goal0 | Goals0], NonLocals, RevGoals0, Goals,
!PDInfo) :-
pd_info_get_module_info(!.PDInfo, ModuleInfo),
( if
% constraint.m ensures that only constraints relevant
% to this goal are placed adjacent to it.
Goal0 = hlds_goal(GoalExpr0, _GoalInfo0),
GoalExpr0 = plain_call(PredId, _ProcId, _Args, _, _, SymName),
module_info_pred_info(ModuleInfo, PredId, PredInfo),
not pred_info_is_imported(PredInfo),
list.take_while(
( pred(CnstrGoal::in) is semidet :-
CnstrGoal = hlds_goal(_, CnstrGoalInfo),
goal_info_has_feature(CnstrGoalInfo, feature_constraint)
), Goals0, Constraints, Goals1),
Constraints = [_ | _]
then
SymNameString = sym_name_to_string(SymName),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "propagate_conj_constraints",
"propagating constraints into call to %s\n",
[s(SymNameString)], !IO)
),
do_get_sub_conj_nonlocals(NonLocals, RevGoals0, [],
Goal0, Constraints, no, [], Goals1, ConjNonLocals),
call_call(ConjNonLocals, Goal0, Constraints, no, MaybeGoal, !PDInfo),
(
MaybeGoal = yes(Goal),
pd_info_set_rerun_det(yes, !PDInfo),
pd_info_update_goal(Goal, !PDInfo),
propagate_conj_constraints(Goals1, NonLocals,
[Goal | RevGoals0], Goals, !PDInfo)
;
MaybeGoal = no,
pd_info_update_goal(Goal0, !PDInfo),
propagate_conj_constraints(Goals0, NonLocals,
[Goal0 | RevGoals0], Goals, !PDInfo)
)
else
pd_info_update_goal(Goal0, !PDInfo),
propagate_conj_constraints(Goals0, NonLocals,
[Goal0 | RevGoals0], Goals, !PDInfo)
).
%-----------------------------------------------------------------------------%
:- type annotated_conj ==
assoc_list(hlds_goal, maybe(pd_branch_info(prog_var))).
:- pred deforest_conj(annotated_conj::in, set_of_progvar::in,
list(hlds_goal)::in, list(hlds_goal)::out,
pd_info::in, pd_info::out) is det.
deforest_conj([], _, RevGoals, Goals, !PDInfo) :-
list.reverse(RevGoals, Goals).
deforest_conj([Goal0 - MaybeBranchInfo | Goals0], NonLocals,
RevGoals0, RevGoals, !PDInfo) :-
( if
% Look for a goal later in the conjunction to deforest with.
MaybeBranchInfo = yes(GoalBranchInfo),
detect_deforestation(Goal0, GoalBranchInfo, Goals0, Goals1,
DeforestInfo)
then
handle_deforestation(NonLocals, DeforestInfo,
RevGoals0, Goals1, Goals2, Optimized, !PDInfo),
(
Optimized = yes,
deforest_conj(Goals2, NonLocals, RevGoals0, RevGoals, !PDInfo)
;
Optimized = no,
pd_info_update_goal(Goal0, !PDInfo),
RevGoals1 = [Goal0 | RevGoals0],
deforest_conj(Goals0, NonLocals, RevGoals1, RevGoals, !PDInfo)
)
else
pd_info_update_goal(Goal0, !PDInfo),
RevGoals1 = [Goal0 | RevGoals0],
deforest_conj(Goals0, NonLocals, RevGoals1, RevGoals, !PDInfo)
).
%-----------------------------------------------------------------------------%
:- type deforest_info
---> deforest_info(
hlds_goal, % earlier goal in conjunction
pd_branch_info(prog_var),
% branch_info for earlier goal
list(hlds_goal), % goals in between
hlds_goal, % later goal in conjunction
pd_branch_info(prog_var),
% branch_info for later goal
set(int) % branches for which there is
% extra information about the
% second goal, numbering starts
% at 1.
).
% Search backwards through the conjunction for the last goal which contains
% extra information about the variable being switched on.
%
:- pred detect_deforestation(hlds_goal::in,
pd_branch_info(prog_var)::in, annotated_conj::in,
annotated_conj::out, deforest_info::out) is semidet.
detect_deforestation(EarlierGoal, BranchInfo, !Goals, DeforestInfo) :-
search_for_deforest_goal(EarlierGoal, BranchInfo, [], !Goals,
DeforestInfo).
:- pred search_for_deforest_goal(hlds_goal::in,
pd_branch_info(prog_var)::in, annotated_conj::in,
annotated_conj::in, annotated_conj::out,
deforest_info::out) is semidet.
search_for_deforest_goal(EarlierGoal, EarlierBranchInfo, RevBetweenGoals0,
[Goal | Goals0], Goals, DeforestInfo) :-
( if
Goal = LaterGoal - yes(LaterBranchInfo),
potential_deforestation(EarlierBranchInfo,
LaterBranchInfo, DeforestBranches)
then
list.reverse(RevBetweenGoals0, BetweenGoals1),
assoc_list.keys(BetweenGoals1, BetweenGoals),
Goals = Goals0,
DeforestInfo = deforest_info(EarlierGoal, EarlierBranchInfo,
BetweenGoals, LaterGoal, LaterBranchInfo, DeforestBranches)
else
search_for_deforest_goal(EarlierGoal, EarlierBranchInfo,
[Goal | RevBetweenGoals0], Goals0, Goals, DeforestInfo)
).
% Look for a variable in the second branch_info for which we have more
% information in the first than in the instmap. Get the branches in the
% first goal which contain this extra information.
%
:- pred potential_deforestation(pd_branch_info(prog_var)::in,
pd_branch_info(prog_var)::in, set(int)::out) is semidet.
potential_deforestation(Info1, Info2, DeforestBranches) :-
Info1 = pd_branch_info(VarMap1, _, _),
Info2 = pd_branch_info(_, LeftVars2, _),
map.select(VarMap1, LeftVars2, VarMap),
map.to_assoc_list(VarMap, VarAssoc),
not map.is_empty(VarMap),
% Work out which branches of the first goal should contain
% unfolded versions of the second goal.
GetBranches =
( pred(VarInfo::in, Branches0::in, Branches::out) is det :-
VarInfo = _ - Branches1,
set.union(Branches0, Branches1, Branches)
),
set.init(DeforestBranches0),
list.foldl(GetBranches, VarAssoc, DeforestBranches0, DeforestBranches).
%-----------------------------------------------------------------------------%
% Take the part of a conjunction found to have potential
% for deforestation and attempt the optimization.
%
:- pred handle_deforestation(set_of_progvar::in, deforest_info::in,
list(hlds_goal)::in,
annotated_conj::in, annotated_conj::out, bool::out,
pd_info::in, pd_info::out) is det.
handle_deforestation(NonLocals, DeforestInfo0, RevBeforeGoals0, !AfterGoals,
Optimized, !PDInfo) :-
pd_info_get_module_info(!.PDInfo, ModuleInfo),
pd_info_get_instmap(!.PDInfo, InstMap0),
pd_info_get_created_versions(!.PDInfo, CreatedVersions0),
pd_info_get_depth(!.PDInfo, Depth0),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "handle_deforestation",
"checking for deforestation at depth %i\n", [i(Depth0)], !IO)
),
reorder_conj(DeforestInfo0, DeforestInfo,
BeforeIrrelevant, AfterIrrelevant, !.PDInfo),
get_sub_conj_nonlocals(NonLocals, DeforestInfo, RevBeforeGoals0,
BeforeIrrelevant, AfterIrrelevant, !.AfterGoals, ConjNonLocals),
% Update the instmap.
list.foldl(pd_info_update_goal, BeforeIrrelevant, !PDInfo),
pd_info_get_pred_proc_id(!.PDInfo, CurrPredProcId),
pd_info_get_parents(!.PDInfo, Parents0),
pd_info_get_cost_delta(!.PDInfo, CostDelta0),
pd_info_get_size_delta(!.PDInfo, SizeDelta0),
DeforestInfo = deforest_info(EarlierGoal, _, BetweenGoals,
LaterGoal, _, DeforestBranches),
should_try_deforestation(DeforestInfo, !.PDInfo, ShouldOptimize),
(
ShouldOptimize = no,
Optimized0 = no,
Goals = []
;
ShouldOptimize = yes,
( if
EarlierGoal = hlds_goal(plain_call(PredId1, _, _, _, _, _), _),
LaterGoal = hlds_goal(plain_call(PredId2, _, _, _, _, _), _)
then
% If both goals are calls, create a new predicate for the
% conjunction to be deforested, and process it.
PredName1 = predicate_name(ModuleInfo, PredId1),
PredName2 = predicate_name(ModuleInfo, PredId2),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "handle_deforestation",
"deforesting calls to %s and %s\n",
[s(PredName1), s(PredName2)], !IO)
),
call_call(ConjNonLocals, EarlierGoal, BetweenGoals,
yes(LaterGoal), MaybeGoal, !PDInfo),
(
MaybeGoal = yes(Goal),
Optimized0 = yes,
Goals = [Goal]
;
MaybeGoal = no,
Optimized0 = no,
Goals = []
)
else if
% If the first goal is branched and the second goal is a call,
% attempt to push the call into the branches. Don't push a
% recursive call or a call to a predicate we have already pushed
% into a switch, since it is difficult to stop the process.
EarlierGoal = hlds_goal(EarlierGoalExpr, _),
goal_util.goal_is_branched(EarlierGoalExpr),
LaterGoal = hlds_goal(plain_call(PredId, ProcId, _, _, _, _), _),
PredProcId = proc(PredId, ProcId),
PredProcId \= CurrPredProcId,
not set.member(PredProcId, Parents0)
then
CurrPredName = predicate_name(ModuleInfo, PredId),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "handle_deforestation",
"pushing call to %s into goal\n",
[s(CurrPredName)], !IO)
),
set.insert(proc(PredId, ProcId), Parents0, Parents),
pd_info_set_parents(Parents, !PDInfo),
push_goal_into_goal(ConjNonLocals, DeforestBranches,
EarlierGoal, BetweenGoals, LaterGoal, Goal, !PDInfo),
Goals = [Goal],
Optimized0 = yes
else if
% If both goals are branched, push the second into the branches
% of the first.
EarlierGoal = hlds_goal(EarlierGoalExpr, _),
LaterGoal = hlds_goal(LaterGoalExpr, _),
goal_util.goal_is_branched(EarlierGoalExpr),
goal_util.goal_is_branched(LaterGoalExpr)
then
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "handle_deforestation",
"pushing goal into goal\n", [], !IO),
pd_debug_output_goal(!.PDInfo, "handle_deforestation",
"first goal being pushed into", EarlierGoal, !IO),
(
BetweenGoals = [],
pd_debug_message(!.PDInfo, "handle_deforestation",
"no goal between first/second goals\n", [], !IO)
;
BetweenGoals = [BetweenGoal],
pd_debug_output_goal(!.PDInfo, "handle_deforestation",
"one goal between first/second goals",
BetweenGoal, !IO)
;
BetweenGoals = [_, _ | _],
pd_debug_output_goals(!.PDInfo, "handle_deforestation",
"goals beteween first/second goals",
BetweenGoals, !IO)
),
pd_debug_output_goal(!.PDInfo, "handle_deforestation",
"second goal being pushed", LaterGoal, !IO)
),
push_goal_into_goal(ConjNonLocals, DeforestBranches,
EarlierGoal, BetweenGoals, LaterGoal, Goal, !PDInfo),
trace [io(!IO)] (
pd_debug_output_goal(!.PDInfo, "handle_deforestation",
"result of pushing goal into goal", Goal, !IO)
),
Goals = [Goal],
goals_size([EarlierGoal | BetweenGoals], ConjSize1),
goal_size(LaterGoal, ConjSize2),
goal_size(Goal, NewSize),
SizeDiff = NewSize - ConjSize1 - ConjSize2,
pd_info_incr_size_delta(SizeDiff, !PDInfo),
Optimized0 = yes
else
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "handle_deforestation",
"not optimizing\n", [], !IO)
),
Goals = [],
Optimized0 = no
)
),
Optimized = is_any_improvement_worth_while(!.PDInfo, Optimized0,
CostDelta0, SizeDelta0),
% Clean up.
pd_info_set_depth(Depth0, !PDInfo),
pd_info_set_instmap(InstMap0, !PDInfo),
(
Optimized = no,
% XXX Currently this only attempts to deforest the first goal
% with the first matching goal later in the conjunction. If the
% deforestation failed, other later goals should be tried.
%
% Return everything to the state it was in before the attempted
% optimization.
pd_info_set_cost_delta(CostDelta0, !PDInfo),
pd_info_set_size_delta(SizeDelta0, !PDInfo),
% Remove any versions which were created.
pd_info_get_created_versions(!.PDInfo, CreatedVersions),
set.difference(CreatedVersions, CreatedVersions0, NewVersions0),
set.to_sorted_list(NewVersions0, NewVersions),
list.foldl(pd_info_remove_version, NewVersions, !PDInfo)
% AfterGoals will be restored properly in conj.
;
Optimized = yes,
% We want to reprocess the deforested goal to see if it can be
% deforested with other goals later in the conjunction.
list.condense([BeforeIrrelevant, Goals, AfterIrrelevant],
GoalsToProcess),
compute_goal_infos(GoalsToProcess, GoalsAndInfo, !PDInfo),
list.append(GoalsAndInfo, !AfterGoals),
pd_info_set_instmap(InstMap0, !PDInfo),
pd_info_set_changed(yes, !PDInfo),
pd_info_set_rerun_det(yes, !PDInfo)
),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "handle_deforestation",
"finished deforestation at depth %i\n", [i(Depth0)], !IO)
),
pd_info_set_parents(Parents0, !PDInfo).
% Check whether deforestation is legal and worthwhile.
%
:- pred should_try_deforestation(deforest_info::in, pd_info::in,
bool::out) is det.
should_try_deforestation(DeforestInfo, PDInfo, ShouldTry) :-
DeforestInfo = deforest_info(EarlierGoal, EarlierBranchInfo,
BetweenGoals, LaterGoal, _, _),
pd_info_get_useless_versions(PDInfo, UselessVersions),
( if
EarlierGoal = hlds_goal(plain_call(PredId1, ProcId1, _, _, _, _), _),
LaterGoal = hlds_goal(plain_call(PredId2, ProcId2, _, _, _, _), _),
set.member(proc(PredId1, ProcId1) - proc(PredId2, ProcId2),
UselessVersions)
then
trace [compile_time(flag("debug_deforest")), io(!IO)] (
pd_debug_message(PDInfo, "should_try_deforestation",
"version tried before, not worthwhile\n", [], !IO)
),
ShouldTry = no
else if
% If some later goal depends on a variable such as an io.state
% for which the construction cannot be reversed, recursive
% folding will be impossible, so give up on the optimization.
EarlierBranchInfo = pd_branch_info(_, _, OpaqueVars),
( list.member(OpaqueGoal, BetweenGoals)
; OpaqueGoal = LaterGoal
),
OpaqueGoal = hlds_goal(_, OpaqueGoalInfo),
OpaqueNonLocals = goal_info_get_nonlocals(OpaqueGoalInfo),
OpaqueVarsSet = set_of_var.set_to_bitset(OpaqueVars),
set_of_var.intersect(OpaqueNonLocals, OpaqueVarsSet, UsedOpaqueVars),
set_of_var.is_non_empty(UsedOpaqueVars)
then
trace [compile_time(flag("debug_deforest")), io(!IO)] (
pd_debug_message(PDInfo, "should_try_deforestation",
"later goals depend on opaque vars\n", [], !IO)
),
ShouldTry = no
else if
% A disjunction can be semidet only if it binds no variables.
% If we push a goal which binds a variable into a semidet disjunction,
% it won't be semidet anymore.
%
% We assume that LaterGoal can bind variables, because we don't know
% whether it can or not. Its instmap_delta doesn't tell us, because
% it tells us only the NEW inst of the variables whose insts it
% changes. Without knowing their old insts as well, we don't know
% whether the inst change reflects the variable being bound, or just
% the gathering of knowledge about what its initial value could have
% been.
EarlierGoal = hlds_goal(disj(_), EarlierGoalInfo),
EarlierGoalDetism = goal_info_get_determinism(EarlierGoalInfo),
determinism_components(EarlierGoalDetism, _, EarlierGoalMaxSolns),
EarlierGoalMaxSolns \= at_most_many
then
ShouldTry = no
else
ShouldTry = yes
).
:- pred can_optimize_conj(hlds_goal::in, list(hlds_goal)::in,
maybe(hlds_goal)::in, bool::out, pd_info::in, pd_info::out) is det.
can_optimize_conj(EarlierGoal, BetweenGoals, MaybeLaterGoal, ShouldTry,
!PDInfo) :-
pd_info_get_depth(!.PDInfo, Depth0),
pd_info_get_module_info(!.PDInfo, ModuleInfo),
module_info_get_globals(ModuleInfo, Globals),
globals.get_opt_tuple(Globals, OptTuple),
MaxDepth = OptTuple ^ ot_deforestation_depth_limit,
Depth = Depth0 + 1,
pd_info_set_depth(Depth, !PDInfo),
SizeLimit = OptTuple ^ ot_deforestation_size_threshold,
globals.lookup_option(Globals, fully_strict, FullyStrictOp),
( if
MaxDepth \= -1, % no depth limit set
% XXX The *default* value of the depth_limit is 4,
% so -1 does NOT mean that there is no depth limit set.
Depth0 >= MaxDepth
then
% The depth limit was exceeded. This should not occur too often in
% practice - the depth limit is just a safety net.
trace [compile_time(flag("debug_deforest")), io(!IO)] (
pd_debug_message(!.PDInfo, "can_optimize_conj",
"\n\n*****Depth limit exceeded*****\n\n", [], !IO)
),
ShouldTry = no
else if
% Check whether either of the goals to be deforested is too large.
% XXX This is probably a bit too crude, especially for LaterGoal,
% which should be reduced in size in the specialized version
% (the specialized version will only include one branch of the
% top-level switch).
SizeLimit \= -1,
(
EarlierGoal = hlds_goal(plain_call(PredId, ProcId, _, _, _, _), _)
;
MaybeLaterGoal = yes(
hlds_goal(plain_call(PredId, ProcId, _, _, _, _), _))
),
module_info_pred_proc_info(ModuleInfo, PredId, ProcId,
_, CalledProcInfo),
proc_info_get_goal(CalledProcInfo, CalledGoal),
goal_size(CalledGoal, CalledGoalSize),
SizeLimit \= -1,
CalledGoalSize > SizeLimit
then
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "can_optimize_conj",
"goal too large\n", [], !IO)
),
ShouldTry = no
else if
% Check whether either of the goals to be deforested can't be inlined.
(
EarlierGoal = hlds_goal(EarlierGoalExpr, _),
EarlierGoalExpr = plain_call(PredId, ProcId, _, BuiltinState, _, _)
;
MaybeLaterGoal = yes(hlds_goal(LaterGoalExpr, _)),
LaterGoalExpr = plain_call(PredId, ProcId, _, BuiltinState, _, _)
),
% We don't attempt to deforest predicates which have purity promises
% because the extra impurity propagated through the goal when such
% predicates are inlined will defeat any attempt at deforestation.
% XXX We should probably allow deforestation of semipure goals.
not inlining.can_inline_proc(ModuleInfo, PredId, ProcId, BuiltinState,
may_not_inline_purity_promised_pred)
then
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "can_optimize_conj",
"non-inlineable calls\n", [], !IO)
),
ShouldTry = no
else if
%
% Don't optimize if that would require duplicating
% branched goal structures.
%
not is_simple_goal_list(BetweenGoals)
then
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "can_optimize_conj",
"between goals not simple enough\n", [], !IO)
),
ShouldTry = no
else if
% Give up if there are any impure goals involved.
% XXX We should probably allow deforestation of semipure goals.
( list.member(ImpureGoal, BetweenGoals)
; ImpureGoal = EarlierGoal
; MaybeLaterGoal = yes(ImpureGoal)
),
ImpureGoal = hlds_goal(_, ImpureGoalInfo),
not goal_info_get_purity(ImpureGoalInfo) = purity_pure
then
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "can_optimize_conj",
"goal list contains impure goal(s)\n", [], !IO)
),
ShouldTry = no
else if
% Check whether interleaving the execution of the goals could alter
% the termination behaviour in a way which is illegal according to the
% semantics options.
%
FullyStrictOp = bool(FullyStrict),
(
list.member(OtherGoal, BetweenGoals)
;
MaybeLaterGoal = yes(LaterGoal),
OtherGoal = LaterGoal
),
not reordering_maintains_termination_old(ModuleInfo, FullyStrict,
EarlierGoal, OtherGoal)
then
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "can_optimize_conj",
"interleaving execution could change termination behaviour\n",
[], !IO)
),
ShouldTry = no
else
ShouldTry = yes
).
% Check that the code size increase is justified by the estimated
% performance increase. This should err towards allowing optimization
% - without any check at all the code size of the library only increases
% ~10%.
%
:- func is_any_improvement_worth_while(pd_info, bool, int, int) = bool.
is_any_improvement_worth_while(PDInfo, Optimized0, CostDelta0, SizeDelta0)
= Optimized :-
(
Optimized0 = no,
Optimized = no
;
Optimized0 = yes,
pd_info_get_cost_delta(PDInfo, CostDelta),
pd_info_get_size_delta(PDInfo, SizeDelta),
CostDiff = CostDelta - CostDelta0,
SizeDiff = SizeDelta - SizeDelta0,
pd_info_get_module_info(PDInfo, ModuleInfo),
module_info_get_globals(ModuleInfo, Globals),
globals.get_opt_tuple(Globals, OptTuple),
Factor = OptTuple ^ ot_deforestation_cost_factor,
( if
( if SizeDiff =< 5 then
% For small increases in size, accept any amount of
% optimization.
CostDiff > 0
else
% Accept the optimization if we save the equivalent of
% a heap increment per 3 extra atomic goals. Note that
% folding is heavily rewarded by pd_cost.m, so this
% isn't very restrictive if a fold occurs.
ExpectedCostDiff = 1000 * cost_of_heap_incr * SizeDiff // 3,
FudgedCostDiff = CostDiff * Factor,
FudgedCostDiff >= ExpectedCostDiff
)
then
Optimized = yes,
trace [io(!IO)] (
pd_debug_message(PDInfo, "is_improvement_worth_while",
"enough improvement: cost(%i) size(%i)\n",
[i(CostDiff), i(SizeDiff)], !IO)
)
else
Optimized = no,
trace [io(!IO)] (
pd_debug_message(PDInfo, "is_improvement_worth_while",
"not enough improvement: cost(%i) size(%i)\n",
[i(CostDiff), i(SizeDiff)], !IO)
)
)
).
%-----------------------------------------------------------------------------%
% Attempt deforestation on a pair of calls.
%
:- pred call_call(set_of_progvar::in, hlds_goal::in,
list(hlds_goal)::in, maybe(hlds_goal)::in, maybe(hlds_goal)::out,
pd_info::in, pd_info::out) is det.
call_call(ConjNonLocals, EarlierGoal, BetweenGoals, MaybeLaterGoal, MaybeGoal,
!PDInfo) :-
can_optimize_conj(EarlierGoal, BetweenGoals, MaybeLaterGoal, ShouldTry,
!PDInfo),
(
ShouldTry = yes,
disable_warning [suspicious_recursion] (
call_call(ConjNonLocals, EarlierGoal, BetweenGoals,
MaybeLaterGoal, MaybeGoal, !PDInfo)
)
;
ShouldTry = no,
MaybeGoal = no
).
% Attempt deforestation on a pair of calls.
%
:- pred call_call_2(set_of_progvar::in, hlds_goal::in,
list(hlds_goal)::in, maybe(hlds_goal)::in, maybe(hlds_goal)::out,
pd_info::in, pd_info::out) is det.
:- pragma consider_used(pred(call_call_2/7)).
call_call_2(ConjNonLocals, EarlierGoal, BetweenGoals, MaybeLaterGoal,
MaybeGoal, !PDInfo) :-
create_conj(EarlierGoal, BetweenGoals, MaybeLaterGoal, ConjNonLocals,
FoldGoal),
pd_info_search_version(!.PDInfo, FoldGoal, MaybeVersion),
pd_info_get_parent_versions(!.PDInfo, Parents),
( if
MaybeVersion = version(_, VersionPredProcId,
VersionInfo, Renaming, TypeRenaming)
then
% If we see an opportunity to fold, take it.
VersionPredProcId = proc(VersionPredId, _),
pd_info_get_module_info(!.PDInfo, ModuleInfo0),
FoldPredName = predicate_name(ModuleInfo0, VersionPredId),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "call_call_2",
"Folded with %s\n", [s(FoldPredName)], !IO)
),
( if set.member(VersionPredProcId, Parents) then
FoldCostDelta = cost_of_recursive_fold
else
FoldCostDelta = cost_of_fold
),
pd_info_incr_cost_delta(FoldCostDelta, !PDInfo),
goals_size([EarlierGoal | BetweenGoals], NegSizeDelta),
SizeDelta = - NegSizeDelta,
pd_info_incr_size_delta(SizeDelta, !PDInfo),
create_call_goal(VersionPredProcId, VersionInfo, Renaming,
TypeRenaming, Goal, !PDInfo),
MaybeGoal = yes(Goal)
else
pd_info_get_global_term_info(!.PDInfo, TermInfo0),
pd_info_get_parent_versions(!.PDInfo, ParentVersions0),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "call_call_2",
"Parents: %s\n", [s(string.string(ParentVersions0))], !IO)
),
pd_info_get_module_info(!.PDInfo, ModuleInfo),
pd_info_get_versions(!.PDInfo, Versions),
pd_info_get_instmap(!.PDInfo, InstMap),
pd_term.global_check(ModuleInfo, EarlierGoal, BetweenGoals,
MaybeLaterGoal, InstMap, Versions, TermInfo0, TermInfo,
CheckResult),
(
CheckResult = ok(ProcPair, Size),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "call_call_2",
"global termination check succeeded - " ++
"creating new version\n",
[], !IO)
),
pd_info_set_global_term_info(TermInfo, !PDInfo),
RunModes = no,
MaybeGeneralised = no,
create_deforest_goal(EarlierGoal, BetweenGoals,
MaybeLaterGoal, FoldGoal, ConjNonLocals, RunModes, ProcPair,
Size, MaybeGeneralised, MaybeGoal, !PDInfo)
;
CheckResult = possible_loop(ProcPair, Size, CoveringPredProcId),
% The termination check found the same pair of end-points with the
% same length goal. If the goal matches the goal for the "covering"
% predicate, perform a most specific generalisation on the insts
% then keep on going.
try_generalisation(EarlierGoal, BetweenGoals,
MaybeLaterGoal, FoldGoal, ConjNonLocals, ProcPair, Size,
CoveringPredProcId, MaybeGoal, !PDInfo)
;
CheckResult = loop,
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "call_call_2",
"global termination check failed\n", [], !IO)
),
MaybeGoal = no
),
pd_info_set_global_term_info(TermInfo0, !PDInfo)
).
%-----------------------------------------------------------------------------%
% Create a new procedure for a conjunction to be deforested, then
% recursively process that procedure.
%
:- pred create_deforest_goal(hlds_goal::in, list(hlds_goal)::in,
maybe(hlds_goal)::in, hlds_goal::in, set_of_progvar::in, bool::in,
proc_pair::in, int::in, maybe(pred_proc_id)::in, maybe(hlds_goal)::out,
pd_info::in, pd_info::out) is det.
create_deforest_goal(EarlierGoal, BetweenGoals, MaybeLaterGoal,
FoldGoal0, NonLocals, RunModes, ProcPair, Size,
MaybeGeneralised, MaybeCallGoal, !PDInfo) :-
pd_info_get_module_info(!.PDInfo, ModuleInfo0),
module_info_get_globals(ModuleInfo0, Globals),
globals.get_opt_tuple(Globals, OptTuple),
VarsThreshold = OptTuple ^ ot_deforestation_vars_threshold,
( if
EarlierGoal = hlds_goal(EarlierGoalExpr, _),
EarlierGoalExpr = plain_call(PredId1, ProcId1, Args1, _, _, _),
(
% No threshold set.
VarsThreshold = -1
% XXX The *default* value of the vars threshold is 200,
% so -1 does NOT mean that there is no threshold set.
;
% Check that we are not creating a procedure with a massive number
% of variables. We assume that all the variables in the first
% called goal are present in the final version. If the number
% of variables in the first called goal plus the number of
% variables in BetweenGoals is less than
% --deforestation-vars-threshold, go ahead and optimize.
module_info_pred_proc_info(ModuleInfo0, PredId1, ProcId1, _,
CalledProcInfo1),
proc_info_get_goal(CalledProcInfo1, CalledGoal1),
vars_in_goal(CalledGoal1, GoalVars1),
set_of_var.to_sorted_list(GoalVars1, GoalVarsList1),
vars_in_goals(BetweenGoals, GoalVars2),
set_of_var.to_sorted_list(GoalVars2, GoalVarsList2),
list.length(GoalVarsList1, NumVars1),
list.length(GoalVarsList2, NumVars2),
NumVars = NumVars1 + NumVars2,
NumVars < VarsThreshold
)
then
% Create the goal for the new predicate, unfolding the first call.
pd_info_get_instmap(!.PDInfo, InstMap0),
pd_info_get_proc_info(!.PDInfo, ProcInfo0),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "create_deforest_goal",
"unfolding first call\n", [], !IO)
),
maybe_unfold_call(no, no, PredId1, ProcId1, Args1, DidUnfold,
EarlierGoal, UnfoldedCall, !PDInfo),
create_conj(UnfoldedCall, BetweenGoals, MaybeLaterGoal, NonLocals,
DeforestGoal0),
set_of_var.to_sorted_list(NonLocals, NonLocalsList),
( if
DidUnfold = yes,
RunModes = yes
then
% If we did a generalisation step when creating this version,
% we need to modecheck to propagate through the new insts.
% If this causes mode errors, don't create the new version.
% This can happen if a procedure expected an input to be bound
% to a particular functor but the extra information was
% generalised away.
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "create_deforest_goal",
"running modes on deforest goal\n", [], !IO)
),
pd_util.unique_modecheck_goal(DeforestGoal0, DeforestGoal,
Errors1, !PDInfo),
pd_util.unique_modecheck_goal(FoldGoal0, FoldGoal,
Errors2, !PDInfo),
Errors = Errors1 ++ Errors2
else
DeforestGoal = DeforestGoal0,
FoldGoal = FoldGoal0,
Errors = []
),
% We must have been able to unfold the first call to proceed
% with the optimization, otherwise we will introduce an
% infinite loop in the generated code.
( if
DidUnfold = yes,
Errors = []
then
% Create the new version.
pd_info_define_new_pred(DeforestGoal, PredProcId, CallGoal,
!PDInfo),
PredProcId = proc(PredId, _),
pd_info_get_module_info(!.PDInfo, ModuleInfo),
PredName = predicate_name(ModuleInfo, PredId),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "create_deforest_goal",
"created predicate %s\n", [s(PredName)], !IO)
),
( if
MaybeLaterGoal = yes(hlds_goal(LaterGoalExpr, _)),
LaterGoalExpr = plain_call(PredId2, ProcId2, _, _, _, _)
then
CalledPreds = [proc(PredId1, ProcId1), proc(PredId2, ProcId2)]
else
CalledPreds = [proc(PredId1, ProcId1)]
),
pd_info_get_parent_versions(!.PDInfo, Parents0),
pd_info_get_proc_info(!.PDInfo, ProcInfo1),
proc_info_get_var_table(ProcInfo1, VarTable),
lookup_var_types(VarTable, NonLocalsList, ArgTypes),
VersionInfo = version_info(FoldGoal, CalledPreds, NonLocalsList,
ArgTypes, InstMap0, 0, 0, Parents0, MaybeGeneralised),
pd_info_get_global_term_info(!.PDInfo, TermInfo0),
pd_term.update_global_term_info(ProcPair, PredProcId,
Size, TermInfo0, TermInfo),
pd_info_set_global_term_info(TermInfo, !PDInfo),
set.insert_list([PredProcId | CalledPreds], Parents0, Parents),
pd_info_set_parent_versions(Parents, !PDInfo),
pd_info_register_version(PredProcId, VersionInfo, !PDInfo),
% Run deforestation on the new predicate to do the folding.
pd_info_get_unfold_info(!.PDInfo, UnfoldInfo),
deforest_proc_deltas(PredProcId, CostDelta, SizeDelta, !PDInfo),
pd_info_set_unfold_info(UnfoldInfo, !PDInfo),
pd_info_incr_cost_delta(CostDelta, !PDInfo),
pd_info_incr_size_delta(SizeDelta, !PDInfo),
pd_info_set_parent_versions(Parents0, !PDInfo),
pd_info_get_progress_stream(!.PDInfo, ProgressStream),
pd_info_get_pred_proc_id(!.PDInfo, CurPredProcId),
trace [io(!IO)] (
maybe_write_proc_progress_message(ProgressStream, ModuleInfo,
"Back in", CurPredProcId, !IO)
),
MaybeCallGoal = yes(CallGoal)
else
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "create_deforest_goal",
"generalisation produced mode errors\n", [], !IO)
),
MaybeCallGoal = no
),
% The var_table field was updated when we unfolded the first call,
% but all the new variables are only used in the new version,
% so it is safe to reset the proc_info.
pd_info_set_proc_info(ProcInfo0, !PDInfo),
pd_info_set_instmap(InstMap0, !PDInfo)
else
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "create_deforest_goal",
"vars threshold exceeded\n", [], !IO)
),
MaybeCallGoal = no
).
%-----------------------------------------------------------------------------%
% Create a goal to call a newly created version.
%
:- pred create_call_goal(pred_proc_id::in, version_info::in,
map(prog_var, prog_var)::in, tsubst::in, hlds_goal::out,
pd_info::in, pd_info::out) is det.
create_call_goal(proc(PredId, ProcId), VersionInfo, Renaming, TypeSubn, Goal,
!PDInfo) :-
OldArgVars = VersionInfo ^ version_arg_vars,
pd_info_get_module_info(!.PDInfo, ModuleInfo),
module_info_pred_proc_info(ModuleInfo, PredId, ProcId,
CalledPredInfo, CalledProcInfo),
pred_info_get_arg_types(CalledPredInfo, CalledTVarSet, _CalledExistQVars,
ArgTypes0),
% Rename the arguments in the version.
pd_info_get_proc_info(!.PDInfo, ProcInfo0),
pd_info_get_pred_info(!.PDInfo, PredInfo0),
proc_info_get_var_table(ProcInfo0, VarTable0),
pred_info_get_typevarset(PredInfo0, TVarSet0),
% Rename the argument types using the current pred's tvarset.
tvarset_merge_renaming(TVarSet0, CalledTVarSet, TVarSet, TypeRenaming),
pred_info_set_typevarset(TVarSet, PredInfo0, PredInfo),
pd_info_set_pred_info(PredInfo, !PDInfo),
apply_renaming_to_types(TypeRenaming, ArgTypes0, ArgTypes1),
create_deforest_call_arg_vars(ModuleInfo, Renaming, TypeSubn,
OldArgVars, ArgTypes1, ArgVars, VarTable0, VarTable),
proc_info_set_var_table(VarTable, ProcInfo0, ProcInfo),
pd_info_set_proc_info(ProcInfo, !PDInfo),
% Compute a goal_info.
proc_info_get_argmodes(CalledProcInfo, ArgModes),
instmap_delta_from_mode_list(ModuleInfo, ArgVars, ArgModes, InstMapDelta),
proc_info_interface_determinism(ProcInfo, Detism),
set_of_var.list_to_set(ArgVars, NonLocals),
pred_info_get_purity(CalledPredInfo, Purity),
goal_info_init(NonLocals, InstMapDelta, Detism, Purity, GoalInfo),
PredModule = pred_info_module(CalledPredInfo),
PredName = pred_info_name(CalledPredInfo),
GoalExpr = plain_call(PredId, ProcId, ArgVars, not_builtin, no,
qualified(PredModule, PredName)),
Goal = hlds_goal(GoalExpr, GoalInfo).
:- pred create_deforest_call_arg_vars(module_info::in,
map(prog_var, prog_var)::in, tsubst::in,
list(prog_var)::in, list(mer_type)::in, list(prog_var)::out,
var_table::in, var_table::out) is det.
create_deforest_call_arg_vars(_, _, _, [], [], [], !VarTable).
create_deforest_call_arg_vars(_, _, _, [], [_ | _], _, !VarTable) :-
unexpected($pred, "length mismatch").
create_deforest_call_arg_vars(_, _, _, [_ | _], [], _, !VarTable) :-
unexpected($pred, "length mismatch").
create_deforest_call_arg_vars(ModuleInfo, Renaming, TypeSubn,
[OldArgVar | OldArgVars], [ArgType | ArgTypes], [ArgVar | ArgVars],
!VarTable) :-
( if map.search(Renaming, OldArgVar, ArgVarPrime) then
ArgVar = ArgVarPrime
else
% The variable is local to the call. Create a fresh variable.
apply_subst_to_type(TypeSubn, ArgType, SubnArgType),
create_fresh_var(ModuleInfo, SubnArgType, ArgVar, !VarTable)
),
create_deforest_call_arg_vars(ModuleInfo, Renaming, TypeSubn,
OldArgVars, ArgTypes, ArgVars, !VarTable).
%-----------------------------------------------------------------------------%
% Combine the two goals to be deforested and the goals in between
% into a conjunction.
%
:- pred create_conj(hlds_goal::in, list(hlds_goal)::in,
maybe(hlds_goal)::in, set_of_progvar::in, hlds_goal::out) is det.
create_conj(EarlierGoal, BetweenGoals, MaybeLaterGoal, NonLocals, FoldGoal) :-
(
MaybeLaterGoal = yes(LaterGoal),
list.append([EarlierGoal | BetweenGoals], [LaterGoal], DeforestConj)
;
MaybeLaterGoal = no,
DeforestConj = [EarlierGoal | BetweenGoals]
),
goal_list_determinism(DeforestConj, Detism),
goal_list_instmap_delta(DeforestConj, InstMapDelta0),
instmap_delta_restrict(NonLocals, InstMapDelta0, InstMapDelta),
goal_list_purity(DeforestConj, Purity),
goal_info_init(NonLocals, InstMapDelta, Detism, Purity, ConjInfo0),
% Give the conjunction a context so that the generated predicate
% name points to the location of the first goal.
EarlierGoal = hlds_goal(_, EarlierGoalInfo),
EarlierContext = goal_info_get_context(EarlierGoalInfo),
goal_info_set_context(EarlierContext, ConjInfo0, ConjInfo),
FoldGoal = hlds_goal(conj(plain_conj, DeforestConj), ConjInfo).
%-----------------------------------------------------------------------------%
% "Round-off" some of the extra information that caused the termination
% check to fail and/or the insts of the versions not to match in an attempt
% to achieve folding.
%
:- pred try_generalisation(hlds_goal::in, list(hlds_goal)::in,
maybe(hlds_goal)::in, hlds_goal::in, set_of_progvar::in,
proc_pair::in, int::in, pred_proc_id::in, maybe(hlds_goal)::out,
pd_info::in, pd_info::out) is det.
try_generalisation(EarlierGoal, BetweenGoals, MaybeLaterGoal,
FoldGoal, ConjNonLocals, ProcPair, Size,
CoveringPredProcId, MaybeGoal, !PDInfo) :-
pd_info_get_module_info(!.PDInfo, ModuleInfo),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "try_generalisation",
"start\n", [], !IO)
),
pd_info_get_versions(!.PDInfo, VersionIndex),
map.lookup(VersionIndex, CoveringPredProcId, Version),
Version = version_info(VersionGoal, _, VersionArgVars,
VersionArgTypes, VersionInstMap, _, _, _, _),
pd_info_get_versions(!.PDInfo, Versions),
pd_info_get_proc_info(!.PDInfo, ProcInfo),
proc_info_get_var_table(ProcInfo, VarTable),
( if
pd_util.goals_match(ModuleInfo, VersionGoal, VersionArgVars,
VersionArgTypes, FoldGoal, VarTable, Renaming, _)
then
do_generalisation(VersionArgVars, Renaming, VersionInstMap,
EarlierGoal, BetweenGoals, MaybeLaterGoal, FoldGoal, ConjNonLocals,
ProcPair, Size, CoveringPredProcId, MaybeGoal, !PDInfo)
else if
% If the earlier goal is a generalisation of another version, try
% matching against that. This happens when attempting two
% deforestations in a row and the first deforestation required
% generalisation.
match_generalised_version(ModuleInfo, VersionGoal,
VersionArgVars, VersionArgTypes, EarlierGoal, BetweenGoals,
MaybeLaterGoal, ConjNonLocals, VarTable, Versions,
Renaming)
then
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "try_generalisation",
"matched with generalised version\n", [], !IO)
),
do_generalisation(VersionArgVars, Renaming, VersionInstMap,
EarlierGoal, BetweenGoals, MaybeLaterGoal, FoldGoal, ConjNonLocals,
ProcPair, Size, CoveringPredProcId, MaybeGoal, !PDInfo)
else
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "try_generalisation",
"goals don't match\n", [], !IO)
),
MaybeGoal = no
).
:- pred do_generalisation(list(prog_var)::in,
map(prog_var, prog_var)::in, instmap::in, hlds_goal::in,
list(hlds_goal)::in, maybe(hlds_goal)::in, hlds_goal::in,
set_of_progvar::in, proc_pair::in, int::in,
pred_proc_id::in, maybe(hlds_goal)::out,
pd_info::in, pd_info::out) is det.
do_generalisation(VersionArgVars, Renaming, VersionInstMap, EarlierGoal,
BetweenGoals, MaybeLaterGoal, FoldGoal, ConjNonLocals,
ProcPair, Size, Generalised, MaybeGoal, !PDInfo) :-
pd_info_get_module_info(!.PDInfo, ModuleInfo),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "do_generalisation",
"goals match, trying MSG\n", [], !IO)
),
pd_info_get_instmap(!.PDInfo, InstMap0),
instmap_lookup_vars(VersionInstMap, VersionArgVars, VersionInsts),
pd_util.inst_list_size(ModuleInfo, VersionInsts, VersionInstSizes),
set_of_var.to_sorted_list(ConjNonLocals, ConjNonLocalsList),
pd_info_get_proc_info(!.PDInfo, ProcInfo),
proc_info_get_var_table(ProcInfo, VarTable),
( if
% Check whether we can do a most specific generalisation of insts
% of the non-locals.
try_MSG(ModuleInfo, VarTable, VersionInstMap, Renaming, VersionArgVars,
InstMap0, InstMap),
instmap_lookup_vars(InstMap, ConjNonLocalsList, ArgInsts),
pd_util.inst_list_size(ModuleInfo, ArgInsts, NewInstSizes),
NewInstSizes < VersionInstSizes
then
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "do_generalisation",
"MSG succeeded", [], !IO)
),
pd_info_set_instmap(InstMap, !PDInfo),
create_deforest_goal(EarlierGoal, BetweenGoals,
MaybeLaterGoal, FoldGoal, ConjNonLocals, yes, ProcPair,
Size, yes(Generalised), MaybeGoal, !PDInfo)
else
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "do_generalisation",
"MSG failed\n", [], !IO)
),
MaybeGoal = no
),
pd_info_set_instmap(InstMap0, !PDInfo).
:- pred try_MSG(module_info::in, var_table::in, instmap::in,
map(prog_var, prog_var)::in, list(prog_var)::in,
instmap::in, instmap::out) is semidet.
try_MSG(_, _, _, _, [], !InstMap).
try_MSG(ModuleInfo, VarTable, VersionInstMap, Renaming,
[VersionArgVar | VersionArgVars], !InstMap) :-
lookup_var_type(VarTable, VersionArgVar, VersionType),
instmap_lookup_var(VersionInstMap, VersionArgVar, VersionInst),
( if
map.search(Renaming, VersionArgVar, ArgVar),
instmap_lookup_var(!.InstMap, ArgVar, VarInst),
inst_MSG(ModuleInfo, VersionType, VersionInst, VarInst, Inst)
then
instmap_set_var(ArgVar, Inst, !InstMap)
else
true
),
try_MSG(ModuleInfo, VarTable, VersionInstMap, Renaming,
VersionArgVars, !InstMap).
%-----------------------------------------------------------------------------%
% If the global termination check and generalisation failed and
% the first goal in the conjunction to be specialised is a generalisation
% of another version, try matching and generalising using that
% (non-generalised) version.
%
% This predicate maps the call to the generalised predicate back
% onto the non-generalised version. This makes the goal match
% with the previous conjunction, so the generalisation can be
% reapplied to the entire conjunction.
%
% XXX This only undoes one level of generalisation.
%
:- pred match_generalised_version(module_info::in,
hlds_goal::in, list(prog_var)::in, list(mer_type)::in,
hlds_goal::in, list(hlds_goal)::in, maybe(hlds_goal)::in,
set_of_progvar::in, var_table::in,
version_index::in, map(prog_var, prog_var)::out) is semidet.
match_generalised_version(ModuleInfo, VersionGoal, VersionArgVars,
VersionArgTypes, FirstGoal, BetweenGoals, MaybeLastGoal,
ConjNonLocals, !.VarTable, Versions, Renaming) :-
FirstGoal = hlds_goal(FirstGoalExpr, _),
FirstGoalExpr =
plain_call(FirstPredId, FirstProcId, FirstArgVars, _, _, _),
% Look up the version which the first goal calls.
map.search(Versions, proc(FirstPredId, FirstProcId), FirstVersionInfo),
FirstVersionInfo = version_info(FirstVersionGoal, _, FirstVersionArgVars,
_, _, _, _, _, MaybeNonGeneralisedVersion),
MaybeNonGeneralisedVersion = yes(NonGeneralisedPredProcId),
map.from_corresponding_lists(FirstVersionArgVars, FirstArgVars,
FirstRenaming0),
vars_in_goal(FirstVersionGoal, FirstVersionVars0),
set_of_var.to_sorted_list(FirstVersionVars0, FirstVersionVars),
module_info_pred_proc_info(ModuleInfo, FirstPredId, FirstProcId,
_, FirstProcInfo),
proc_info_get_var_table(FirstProcInfo, FirstVersionVarTable),
clone_variables(FirstVersionVars, FirstVersionVarTable,
!VarTable, FirstRenaming0, FirstRenaming),
must_rename_vars_in_goal(FirstRenaming,
FirstVersionGoal, RenamedFirstVersionGoal),
% Look up the version which was generalised to create the version
% which the first goal calls.
NonGeneralisedPredProcId = proc(NonGeneralisedPredId,
NonGeneralisedProcId),
goal_to_conj_list(VersionGoal, VersionGoalList),
VersionGoalList = [hlds_goal(
plain_call(NonGeneralisedPredId, NonGeneralisedProcId, _, _, _, _), _)
| _],
% Find a renaming from the argument variables of the generalised
% version to the version which was generalised.
map.search(Versions, NonGeneralisedPredProcId,
NonGeneralisedVersion),
NonGeneralisedVersion = version_info(NonGeneralisedGoal, _,
NonGeneralisedArgVars, NonGeneralisedArgTypes, _, _, _, _, _),
pd_util.goals_match(ModuleInfo, NonGeneralisedGoal,
NonGeneralisedArgVars, NonGeneralisedArgTypes,
RenamedFirstVersionGoal, !.VarTable,
GeneralRenaming, TypeRenaming),
module_info_pred_info(ModuleInfo, NonGeneralisedPredId,
NonGeneralisedPredInfo),
pred_info_get_arg_types(NonGeneralisedPredInfo, NonGeneralisedArgTypes),
create_deforest_call_arg_vars(ModuleInfo, GeneralRenaming, TypeRenaming,
NonGeneralisedArgVars, NonGeneralisedArgTypes, NewArgVars,
!.VarTable, _),
% Only fill in as much as pd_util.goals_match actually looks at.
goal_info_init(GoalInfo),
NonGeneralFirstGoalExpr = plain_call(NonGeneralisedPredId,
NonGeneralisedProcId, NewArgVars, not_builtin, no, unqualified("")),
NonGeneralFirstGoal = hlds_goal(NonGeneralFirstGoalExpr, GoalInfo),
create_conj(NonGeneralFirstGoal, BetweenGoals, MaybeLastGoal,
ConjNonLocals, GoalToMatch),
% Check whether the entire conjunction matches.
pd_util.goals_match(ModuleInfo, VersionGoal, VersionArgVars,
VersionArgTypes, GoalToMatch, !.VarTable, Renaming, _).
%-----------------------------------------------------------------------------%
% Work out the nonlocals of a sub-conjunction from the non-locals of the
% entire conjunction and the goals before and after the sub-conjunction.
% This is needed to ensure that the temporary list in double_append is
% found to be local to the conjunction and can be removed.
%
:- pred get_sub_conj_nonlocals(set_of_progvar::in, deforest_info::in,
list(hlds_goal)::in, list(hlds_goal)::in, list(hlds_goal)::in,
annotated_conj::in, set_of_progvar::out) is det.
get_sub_conj_nonlocals(NonLocals0, DeforestInfo,
RevBeforeGoals, BeforeIrrelevant, AfterIrrelevant,
AfterGoals0, SubConjNonLocals) :-
DeforestInfo = deforest_info(EarlierGoal, _, BetweenGoals, LaterGoal,
_, _),
assoc_list.keys(AfterGoals0, AfterGoals),
do_get_sub_conj_nonlocals(NonLocals0, RevBeforeGoals,
BeforeIrrelevant, EarlierGoal, BetweenGoals, yes(LaterGoal),
AfterIrrelevant, AfterGoals, SubConjNonLocals).
:- pred do_get_sub_conj_nonlocals(set_of_progvar::in,
list(hlds_goal)::in, list(hlds_goal)::in, hlds_goal::in,
list(hlds_goal)::in, maybe(hlds_goal)::in, list(hlds_goal)::in,
list(hlds_goal)::in, set_of_progvar::out) is det.
do_get_sub_conj_nonlocals(!.NonLocals, RevBeforeGoals, BeforeIrrelevant,
EarlierGoal, BetweenGoals, MaybeLaterGoal,
AfterIrrelevant, AfterGoals, !:SubConjNonLocals) :-
AddGoalNonLocals =
( pred(Goal::in, Vars0::in, Vars::out) is det :-
Goal = hlds_goal(_, GoalInfo),
GoalNonLocals = goal_info_get_nonlocals(GoalInfo),
set_of_var.union(GoalNonLocals, Vars0, Vars)
),
list.foldl(AddGoalNonLocals, RevBeforeGoals, !NonLocals),
list.foldl(AddGoalNonLocals, BeforeIrrelevant, !NonLocals),
list.foldl(AddGoalNonLocals, AfterIrrelevant, !NonLocals),
list.foldl(AddGoalNonLocals, AfterGoals, !NonLocals),
list.foldl(AddGoalNonLocals, [EarlierGoal | BetweenGoals],
set_of_var.init, !:SubConjNonLocals),
(
MaybeLaterGoal = yes(LaterGoal),
call(AddGoalNonLocals, LaterGoal, !SubConjNonLocals)
;
MaybeLaterGoal = no
),
set_of_var.intersect(!.NonLocals, !SubConjNonLocals).
%-----------------------------------------------------------------------------%
% Attempt to move irrelevant goals out of the conjunction. This does a safe
% re-ordering that is guaranteed not to require rescheduling of the
% conjunction, since it does not reorder goals that depend on each other.
% We favor moving goals backward to avoid removing tail recursion.
%
:- pred reorder_conj(deforest_info::in, deforest_info::out,
list(hlds_goal)::out, list(hlds_goal)::out, pd_info::in) is det.
reorder_conj(DeforestInfo0, DeforestInfo,
BeforeIrrelevant, AfterIrrelevant, PDInfo) :-
pd_info_get_module_info(PDInfo, ModuleInfo),
module_info_get_globals(ModuleInfo, Globals),
trace [io(!IO)] (
pd_debug_message(PDInfo, "reorder_conj", "starting\n", [], !IO)
),
DeforestInfo0 = deforest_info(EarlierGoal, EarlierBranchInfo,
BetweenGoals0, LaterGoal, LaterBranchInfo, DeforestBranches),
globals.lookup_bool_option(Globals, fully_strict, FullyStrict),
move_goals(deforest.can_move_goal_backward, ModuleInfo,
FullyStrict, BetweenGoals0, [], RevBetweenGoals1, EarlierGoal,
[], RevBeforeIrrelevant),
move_goals(deforest.can_move_goal_forward,
ModuleInfo, FullyStrict, RevBetweenGoals1,
[], BetweenGoals, LaterGoal, [], AfterIrrelevant),
list.reverse(RevBeforeIrrelevant, BeforeIrrelevant),
DeforestInfo = deforest_info(EarlierGoal, EarlierBranchInfo,
BetweenGoals, LaterGoal, LaterBranchInfo, DeforestBranches).
:- pred move_goals(can_move::can_move, module_info::in, bool::in,
list(hlds_goal)::in, list(hlds_goal)::in, list(hlds_goal)::out,
hlds_goal::in, list(hlds_goal)::in, list(hlds_goal)::out) is det.
move_goals(_, _, _, [], !BetweenGoals, _, !MovedGoal).
move_goals(CanMove, ModuleInfo, FullyStrict, [BetweenGoal | RevBetweenGoals0],
!BetweenGoals, EndGoal, !MovedGoals) :-
( if
call(CanMove, ModuleInfo, FullyStrict, BetweenGoal,
[EndGoal | !.BetweenGoals])
then
!:MovedGoals = [BetweenGoal | !.MovedGoals]
else
!:BetweenGoals = [BetweenGoal | !.BetweenGoals]
),
move_goals(CanMove, ModuleInfo, FullyStrict,
RevBetweenGoals0, !BetweenGoals, EndGoal, !MovedGoals).
:- type can_move == pred(module_info, bool, hlds_goal, list(hlds_goal)).
:- mode can_move == in(pred(in, in, in, in) is semidet).
% Check all goals occurring later in the conjunction to see if they depend
% on the current goal. A goal depends on the current goal if any of the
% non-locals of the later goal have their instantiatedness changed
% by the current goal.
%
:- pred can_move_goal_forward(module_info::in, bool::in,
hlds_goal::in, list(hlds_goal)::in) is semidet.
can_move_goal_forward(ModuleInfo, FullyStrict, ThisGoal, Goals) :-
(
list.member(LaterGoal, Goals)
=>
pd_can_reorder_goals(ModuleInfo, FullyStrict, ThisGoal, LaterGoal)
).
% Check all goals occurring earlier in the conjunction to see
% if the current goal depends on them.
%
:- pred can_move_goal_backward(module_info::in, bool::in,
hlds_goal::in, list(hlds_goal)::in) is semidet.
can_move_goal_backward(ModuleInfo, FullyStrict, ThisGoal, Goals) :-
(
list.member(EarlierGoal, Goals)
=>
pd_can_reorder_goals(ModuleInfo, FullyStrict,
EarlierGoal, ThisGoal)
).
%-----------------------------------------------------------------------------%
% Tack the second goal and the goals in between onto the end of each branch
% of the first goal, unfolding the second goal in the branches which have
% extra information about the arguments.
%
:- pred push_goal_into_goal(set_of_progvar::in, set(int)::in,
hlds_goal::in, list(hlds_goal)::in, hlds_goal::in, hlds_goal::out,
pd_info::in, pd_info::out) is det.
push_goal_into_goal(NonLocals, DeforestInfo, EarlierGoal,
BetweenGoals, LaterGoal, Goal, !PDInfo) :-
pd_info_get_instmap(!.PDInfo, InstMap0),
EarlierGoal = hlds_goal(EarlierGoalExpr, _),
(
EarlierGoalExpr = switch(Var1, CanFail1, Cases1),
set_of_var.insert(Var1, NonLocals, CaseNonLocals),
append_goal_to_cases(Var1, BetweenGoals, LaterGoal,
CaseNonLocals, 1, DeforestInfo, Cases1, Cases, !PDInfo),
GoalExpr = switch(Var1, CanFail1, Cases)
;
EarlierGoalExpr = if_then_else(Vars, Cond, Then0, Else0),
pd_info_update_goal(Cond, !PDInfo),
Cond = hlds_goal(_, CondInfo),
CondNonLocals = goal_info_get_nonlocals(CondInfo),
set_of_var.union(CondNonLocals, NonLocals, ThenNonLocals),
append_goal(Then0, BetweenGoals, LaterGoal,
ThenNonLocals, 1, DeforestInfo, Then, !PDInfo),
pd_info_set_instmap(InstMap0, !PDInfo),
append_goal(Else0, BetweenGoals, LaterGoal,
NonLocals, 2, DeforestInfo, Else, !PDInfo),
GoalExpr = if_then_else(Vars, Cond, Then, Else)
;
EarlierGoalExpr = disj(Disjuncts0),
append_goal_to_disjuncts(BetweenGoals, LaterGoal,
NonLocals, 1, DeforestInfo, Disjuncts0, Disjuncts, !PDInfo),
GoalExpr = disj(Disjuncts)
;
( EarlierGoalExpr = unify(_, _, _, _, _)
; EarlierGoalExpr = plain_call(_, _, _, _, _, _)
; EarlierGoalExpr = generic_call(_, _, _, _, _)
; EarlierGoalExpr = call_foreign_proc(_, _, _, _, _, _, _)
; EarlierGoalExpr = conj(_, _)
; EarlierGoalExpr = negation(_)
; EarlierGoalExpr = scope(_, _)
; EarlierGoalExpr = shorthand(_)
),
unexpected($pred, "unexpected goal type")
),
pd_info_set_instmap(InstMap0, !PDInfo),
goal_list_instmap_delta([EarlierGoal | BetweenGoals], Delta0),
LaterGoal = hlds_goal(_, LaterInfo),
Delta1 = goal_info_get_instmap_delta(LaterInfo),
instmap_delta_apply_instmap_delta(Delta0, Delta1, test_size, Delta2),
instmap_delta_restrict(NonLocals, Delta2, Delta),
goal_list_determinism([EarlierGoal | BetweenGoals], Detism0),
Detism1 = goal_info_get_determinism(LaterInfo),
det_conjunction_detism(Detism0, Detism1, Detism),
goal_list_purity([EarlierGoal | BetweenGoals], Purity0),
Purity1 = goal_info_get_purity(LaterInfo),
worst_purity(Purity0, Purity1) = Purity,
goal_info_init(NonLocals, Delta, Detism, Purity, GoalInfo),
Goal2 = hlds_goal(GoalExpr, GoalInfo),
pd_info_get_module_info(!.PDInfo, ModuleInfo),
module_info_get_globals(ModuleInfo, Globals),
find_simplify_tasks(Globals, do_not_generate_warnings, SimplifyTasks0),
SimpList0 = simplify_tasks_to_list(SimplifyTasks0),
% Be a bit more aggressive with common structure elimination.
% This helps achieve folding in some cases.
SimpList = [simptask_extra_common_structs | SimpList0],
SimplifyTasks = list_to_simplify_tasks(Globals, SimpList),
pd_util.pd_simplify_goal("push_goal_into_goal", SimplifyTasks,
Goal2, Goal3, !PDInfo),
pd_info_set_instmap(InstMap0, !PDInfo),
% Perform any folding which may now be possible.
deforest_goal(Goal3, Goal, !PDInfo),
pd_info_set_instmap(InstMap0, !PDInfo).
:- pred append_goal_to_disjuncts(list(hlds_goal)::in, hlds_goal::in,
set_of_progvar::in, int::in, set(int)::in,
list(hlds_goal)::in, list(hlds_goal)::out, pd_info::in, pd_info::out)
is det.
append_goal_to_disjuncts(_, _, _, _, _, [], [], !PDInfo).
append_goal_to_disjuncts(BetweenGoals, GoalToAppend, NonLocals,
CurrBranch, Branches, [Goal0 | Goals0], [Goal | Goals], !PDInfo) :-
pd_info_get_instmap(!.PDInfo, InstMap0),
append_goal(Goal0, BetweenGoals, GoalToAppend,
NonLocals, CurrBranch, Branches, Goal, !PDInfo),
NextBranch = CurrBranch + 1,
pd_info_set_instmap(InstMap0, !PDInfo),
append_goal_to_disjuncts(BetweenGoals, GoalToAppend,
NonLocals, NextBranch, Branches, Goals0, Goals, !PDInfo).
:- pred append_goal_to_cases(prog_var::in, list(hlds_goal)::in,
hlds_goal::in, set_of_progvar::in, int::in, set(int)::in,
list(case)::in, list(case)::out, pd_info::in, pd_info::out) is det.
append_goal_to_cases(_, _, _, _, _, _, [], [], !PDInfo).
append_goal_to_cases(Var, BetweenGoals, GoalToAppend, NonLocals,
CurrCase, Branches, [Case0 | Cases0], [Case | Cases], !PDInfo) :-
Case0 = case(MainConsId, OtherConsIds, Goal0),
pd_info_get_instmap(!.PDInfo, InstMap0),
pd_info_bind_var_to_functors(Var, MainConsId, OtherConsIds, !PDInfo),
append_goal(Goal0, BetweenGoals, GoalToAppend, NonLocals,
CurrCase, Branches, Goal, !PDInfo),
Case = case(MainConsId, OtherConsIds, Goal),
NextCase = CurrCase + 1,
pd_info_set_instmap(InstMap0, !PDInfo),
append_goal_to_cases(Var, BetweenGoals, GoalToAppend,
NonLocals, NextCase, Branches, Cases0, Cases, !PDInfo).
:- pred append_goal(hlds_goal::in, list(hlds_goal)::in,
hlds_goal::in, set_of_progvar::in, int::in, set(int)::in,
hlds_goal::out, pd_info::in, pd_info::out) is det.
append_goal(Goal0, BetweenGoals, GoalToAppend0, NonLocals0,
CurrBranch, Branches, Goal, !PDInfo) :-
( if set.member(CurrBranch, Branches) then
% Unfold the call.
pd_info_get_instmap(!.PDInfo, InstMap0),
list.foldl(pd_info_update_goal, [Goal0 | BetweenGoals], !PDInfo),
deforest_goal(GoalToAppend0, GoalToAppend, !PDInfo),
pd_info_set_instmap(InstMap0, !PDInfo)
else
GoalToAppend = GoalToAppend0
),
goal_to_conj_list(Goal0, GoalList0),
goal_to_conj_list(GoalToAppend, GoalListToAppend),
list.condense([GoalList0, BetweenGoals, GoalListToAppend], Goals),
goal_list_nonlocals(Goals, SubNonLocals),
set_of_var.intersect(NonLocals0, SubNonLocals, NonLocals),
goal_list_instmap_delta(Goals, Delta0),
instmap_delta_restrict(NonLocals, Delta0, Delta),
goal_list_determinism(Goals, Detism),
goal_list_purity(Goals, Purity),
goal_info_init(NonLocals, Delta, Detism, Purity, GoalInfo),
Goal = hlds_goal(conj(plain_conj, Goals), GoalInfo).
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- pred deforest_call(pred_id::in, proc_id::in, list(prog_var)::in,
sym_name::in, builtin_state::in, hlds_goal::in, hlds_goal::out,
pd_info::in, pd_info::out) is det.
deforest_call(PredId, ProcId, Args, SymName, BuiltinState, Goal0, Goal,
!PDInfo) :-
pd_info_get_proc_arg_info(!.PDInfo, ProcArgInfos),
pd_info_get_module_info(!.PDInfo, ModuleInfo),
pd_info_get_instmap(!.PDInfo, InstMap),
Name = unqualify_name(SymName),
list.length(Args, Arity),
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
Context = goal_info_get_context(GoalInfo0),
pd_info_get_local_term_info(!.PDInfo, LocalTermInfo0),
module_info_get_globals(ModuleInfo, Globals),
globals.get_opt_tuple(Globals, OptTuple),
SizeThreshold = OptTuple ^ ot_deforestation_size_threshold,
( if
% Check for extra information to the call.
map.search(ProcArgInfos, proc(PredId, ProcId), ProcArgInfo),
ProcArgInfo = pd_branch_info(_, LeftArgs, _),
set.member(LeftArg, LeftArgs),
list.det_index1(Args, LeftArg, Arg),
instmap_lookup_var(InstMap, Arg, ArgInst),
inst_is_bound_to_functors(ModuleInfo, ArgInst, [_]),
% We don't attempt to deforest predicates which have purity promises
% because the extra impurity propagated through the goal when such
% predicates are inlined will defeat any attempt at deforestation.
% XXX We should probably allow deforestation of semipure goals.
inlining.can_inline_proc(ModuleInfo, PredId, ProcId, BuiltinState,
may_not_inline_purity_promised_pred),
% Check the goal size.
module_info_pred_proc_info(ModuleInfo, PredId, ProcId, _,
CalledProcInfo),
proc_info_get_goal(CalledProcInfo, CalledGoal),
goal_size(CalledGoal, CalledGoalSize),
( SizeThreshold = -1
; CalledGoalSize < SizeThreshold
)
then
trace [io(!IO)] (
pd_debug_message_context(!.PDInfo, "deforest_call", Context,
"Found extra information for call to %s/%i\n",
[s(Name), i(Arity)], !IO)
),
( if
pd_term.local_check(ModuleInfo, Goal0, InstMap,
LocalTermInfo0, LocalTermInfo)
then
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "deforest_call",
"Local termination check succeeded\n", [], !IO)
),
pd_info_set_local_term_info(LocalTermInfo, !PDInfo),
maybe_unfold_call(yes, yes, PredId, ProcId, Args, Optimized,
Goal0, Goal1, !PDInfo),
(
Optimized = yes,
deforest_goal(Goal1, Goal, !PDInfo)
;
Optimized = no,
Goal = Goal1
),
pd_info_set_local_term_info(LocalTermInfo0, !PDInfo)
else
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "deforest_call",
"Local termination check failed\n", [], !IO)
),
Goal = hlds_goal(GoalExpr0, GoalInfo0)
)
else
trace [io(!IO)] (
pd_debug_message_context(!.PDInfo, "deforest_call", Context,
"No extra information for call to %s/%i\n",
[s(Name), i(Arity)], !IO)
),
Goal = Goal0
).
:- pred maybe_unfold_call(bool::in, bool::in, pred_id::in, proc_id::in,
list(prog_var)::in, bool::out, hlds_goal::in, hlds_goal::out,
pd_info::in, pd_info::out) is det.
maybe_unfold_call(CheckImprovement, CheckVars, PredId, ProcId, Args,
Optimized, Goal0, Goal, !PDInfo) :-
pd_info_get_module_info(!.PDInfo, ModuleInfo),
module_info_get_globals(ModuleInfo, Globals),
globals.get_opt_tuple(Globals, OptTuple),
VarsThreshold = OptTuple ^ ot_deforestation_vars_threshold,
pd_info_get_proc_info(!.PDInfo, ProcInfo0),
proc_info_get_var_table(ProcInfo0, VarTable0),
var_table_count(VarTable0, NumVars),
( if
% Check that we haven't already got too many variables.
(
CheckVars = no
;
VarsThreshold = -1
% XXX The *default* value of the vars threshold is 200,
% so -1 does NOT mean that there is no threshold set.
;
NumVars < VarsThreshold
)
then
try_to_unfold_call(ModuleInfo, Globals, VarTable0, CheckImprovement,
PredId, ProcId, ProcInfo0, Args, Optimized, Goal0, Goal, !PDInfo)
else
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "maybe_unfold_call",
"too many variables - not inlining\n", [], !IO)
),
Goal = Goal0,
Optimized = no
).
:- pred try_to_unfold_call(module_info::in, globals::in, var_table::in,
bool::in, pred_id::in, proc_id::in, proc_info::in, list(prog_var)::in,
bool::out, hlds_goal::in, hlds_goal::out,
pd_info::in, pd_info::out) is det.
try_to_unfold_call(ModuleInfo, Globals, VarTable0, CheckImprovement,
PredId, ProcId, ProcInfo0, Args, Optimized, Goal0, Goal, !PDInfo) :-
Goal0 = hlds_goal(_, GoalInfo0),
CallContext = goal_info_get_context(GoalInfo0),
pd_info_get_pred_info(!.PDInfo, PredInfo0),
module_info_pred_proc_info(ModuleInfo, PredId, ProcId,
CalledPredInfo, CalledProcInfo),
pred_info_get_typevarset(PredInfo0, TypeVarSet0),
pred_info_get_univ_quant_tvars(PredInfo0, UnivQVars),
proc_info_get_rtti_varmaps(ProcInfo0, RttiVarMaps0),
inlining.do_inline_call(ModuleInfo, UnivQVars, CallContext,
CalledPredInfo, CalledProcInfo, Args, Goal1, TypeVarSet0, TypeVarSet,
VarTable0, VarTable, RttiVarMaps0, RttiVarMaps),
pred_info_set_typevarset(TypeVarSet, PredInfo0, PredInfo),
proc_info_get_has_parallel_conj(CalledProcInfo, CalledHasParallelConj),
proc_info_set_var_table(VarTable, ProcInfo0, ProcInfo1),
proc_info_set_rtti_varmaps(RttiVarMaps, ProcInfo1, ProcInfo2),
(
CalledHasParallelConj = has_parallel_conj,
proc_info_set_has_parallel_conj(has_parallel_conj,
ProcInfo2, ProcInfo)
;
CalledHasParallelConj = has_no_parallel_conj,
% Leave the has_parallel_conj field of the proc_info as it is.
ProcInfo = ProcInfo2
),
pd_info_set_pred_info(PredInfo, !PDInfo),
pd_info_set_proc_info(ProcInfo, !PDInfo),
goal_cost(Goal1, OriginalCost),
pd_info_get_cost_delta(!.PDInfo, CostDelta0),
pd_info_get_size_delta(!.PDInfo, SizeDelta0),
pd_info_get_changed(!.PDInfo, Changed0),
% Update the quantification if not all the output arguments are used.
Goal1 = hlds_goal(_, GoalInfo1),
NonLocals1 = goal_info_get_nonlocals(GoalInfo1),
set_of_var.list_to_set(Args, NonLocals),
( if set_of_var.equal(NonLocals1, NonLocals) then
Goal2 = Goal1
else
pd_requantify_goal(NonLocals, Goal1, Goal2, !PDInfo)
),
% Push the extra information from the call through the goal.
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "try_to_unfold_call",
"running unique modes\n", [], !IO)
),
proc_info_arglives(ModuleInfo, CalledProcInfo, ArgLives),
get_live_vars(Args, ArgLives, LiveVars0),
set_of_var.list_to_set(LiveVars0, LiveVarsSet0),
set_of_var.intersect(NonLocals, LiveVarsSet0, LiveVarsSet),
pd_util.unique_modecheck_goal_live_vars(LiveVarsSet, Goal2, Goal3,
ModeErrors, !PDInfo),
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "try_to_unfold_call",
"running simplify\n", [], !IO)
),
find_simplify_tasks(Globals, do_not_generate_warnings, SimplifyTasks),
pd_util.pd_simplify_goal("try_to_unfold_call", SimplifyTasks,
Goal3, Goal4, !PDInfo),
choose_original_or_inlined(CheckImprovement, PredInfo0, ProcInfo0,
GoalInfo0, Changed0, ModeErrors, OriginalCost, CostDelta0, SizeDelta0,
Optimized, Goal0, Goal4, Goal, !PDInfo).
:- pred choose_original_or_inlined(bool::in, pred_info::in, proc_info::in,
hlds_goal_info::in, bool::in, list(T)::in, int::in, int::in, int::in,
bool::out, hlds_goal::in, hlds_goal::in, hlds_goal::out,
pd_info::in, pd_info::out) is det.
choose_original_or_inlined(CheckImprovement, PredInfo0, ProcInfo0, GoalInfo0,
Changed0, ModeErrors, OriginalCost, CostDelta0, SizeDelta0, Optimized,
Goal0, OptGoal, Goal, !PDInfo) :-
pd_info_get_cost_delta(!.PDInfo, CostDelta1),
CostDelta = CostDelta1 - CostDelta0,
goal_size(OptGoal, OptGoalSize),
SizeDelta = OptGoalSize - cost_of_call,
( if
ModeErrors = [],
(
CheckImprovement = no
;
CheckImprovement = yes,
( if is_simple_goal(OptGoal) then
true
else
% Very rough heuristics for checking improvement.
% This should lean towards allowing optimizations.
%
% XXX We should pay attention to ot_deforestation_cost_factor
% in OptTuple.
( if OptGoalSize =< 5 then
% For small increases in code size, accept any amount
% of optimization.
CostDelta > 0
else
PercentChange = CostDelta * 100 // OriginalCost,
PercentChange >= 5
)
)
)
then
Goal = OptGoal,
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "choose_original_or_inlined",
"inlined: cost(%i) size(%i)\n",
[i(CostDelta), i(SizeDelta)], !IO)
),
pd_info_incr_size_delta(SizeDelta, !PDInfo),
pd_info_set_changed(yes, !PDInfo),
Det0 = goal_info_get_determinism(GoalInfo0),
Goal = hlds_goal(_, GoalInfo),
Det = goal_info_get_determinism(GoalInfo),
% Rerun determinism analysis later if the determinism of any of
% the sub-goals changes - this avoids problems with inlining
% erroneous predicates.
( if Det = Det0 then
true
else
pd_info_set_rerun_det(yes, !PDInfo)
),
Optimized = yes
else
trace [io(!IO)] (
pd_debug_message(!.PDInfo, "choose_original_or_inlined",
"not enough improvement - " ++
"not inlining: cost(%i) size(%i)\n",
[i(CostDelta), i(SizeDelta)], !IO)
),
pd_info_set_pred_info(PredInfo0, !PDInfo),
pd_info_set_proc_info(ProcInfo0, !PDInfo),
pd_info_set_size_delta(SizeDelta0, !PDInfo),
pd_info_set_cost_delta(CostDelta0, !PDInfo),
pd_info_set_changed(Changed0, !PDInfo),
Goal = Goal0,
Optimized = no
).
%-----------------------------------------------------------------------------%
:- pred is_simple_goal_list(list(hlds_goal)::in) is semidet.
is_simple_goal_list([]).
is_simple_goal_list([Goal | Goals]) :-
is_simple_goal(Goal),
is_simple_goal_list(Goals).
:- pred is_simple_goal(hlds_goal::in) is semidet.
is_simple_goal(hlds_goal(GoalExpr, _)) :-
(
goal_expr_has_subgoals(GoalExpr) = does_not_have_subgoals
;
GoalExpr = negation(Goal1),
% Handle a call or builtin + tests on the output.
goal_to_conj_list(Goal1, GoalList1),
is_simple_goal_list(GoalList1)
).
%-----------------------------------------------------------------------------%
:- end_module transform_hlds.deforest.
%-----------------------------------------------------------------------------%