mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-17 23:05:21 +00:00
Estimated hours taken: 18 Branches: main Move the univ, maybe, pair and unit types from std_util into their own modules. std_util still contains the general purpose higher-order programming constructs. library/std_util.m: Move univ, maybe, pair and unit (plus any other related types and procedures) into their own modules. library/maybe.m: New module. This contains the maybe and maybe_error types and the associated procedures. library/pair.m: New module. This contains the pair type and associated procedures. library/unit.m: New module. This contains the types unit/0 and unit/1. library/univ.m: New module. This contains the univ type and associated procedures. library/library.m: Add the new modules. library/private_builtin.m: Update the declaration of the type_ctor_info struct for univ. runtime/mercury.h: Update the declaration for the type_ctor_info struct for univ. runtime/mercury_mcpp.h: runtime/mercury_hlc_types.h: Update the definition of MR_Univ. runtime/mercury_init.h: Fix a comment: ML_type_name is now exported from type_desc.m. compiler/mlds_to_il.m: Update the the name of the module that defines univs (which are handled specially by the il code generator.) library/*.m: compiler/*.m: browser/*.m: mdbcomp/*.m: profiler/*.m: deep_profiler/*.m: Conform to the above changes. Import the new modules where they are needed; don't import std_util where it isn't needed. Fix formatting in lots of modules. Delete duplicate module imports. tests/*: Update the test suite to confrom to the above changes.
1988 lines
81 KiB
Mathematica
1988 lines
81 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 1999-2006 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: 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
|
|
% <http://www.cs.mu.oz.au/research/mercury/information/papers/stayl_hons.ps.gz>
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module transform_hlds.deforest.
|
|
:- interface.
|
|
|
|
:- import_module hlds.hlds_module.
|
|
|
|
:- import_module io.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred deforestation(module_info::in, module_info::out, io::di, io::uo)
|
|
is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module check_hlds.det_analysis.
|
|
:- import_module check_hlds.det_report.
|
|
:- import_module check_hlds.inst_match.
|
|
:- import_module check_hlds.mode_info.
|
|
:- import_module check_hlds.modes.
|
|
:- import_module check_hlds.mode_util.
|
|
:- import_module check_hlds.purity.
|
|
:- import_module check_hlds.simplify.
|
|
:- import_module check_hlds.unique_modes.
|
|
:- import_module hlds.goal_util.
|
|
:- import_module hlds.hlds_data.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module hlds.hlds_out.
|
|
:- import_module hlds.hlds_pred.
|
|
:- import_module hlds.instmap.
|
|
:- import_module hlds.passes_aux.
|
|
:- import_module hlds.quantification.
|
|
:- import_module libs.compiler_util.
|
|
:- import_module libs.globals.
|
|
:- import_module libs.options.
|
|
:- import_module mdbcomp.prim_data.
|
|
:- import_module parse_tree.prog_data.
|
|
:- import_module parse_tree.prog_out.
|
|
:- import_module parse_tree.prog_type_subst.
|
|
:- import_module parse_tree.prog_util.
|
|
:- import_module transform_hlds.dependency_graph.
|
|
:- 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_io.
|
|
:- import_module int.
|
|
:- import_module list.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
:- import_module pair.
|
|
:- import_module set.
|
|
:- import_module string.
|
|
:- import_module term.
|
|
:- import_module univ.
|
|
:- import_module varset.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
deforestation(!ModuleInfo, !IO) :-
|
|
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, UnivProcArgInfo0),
|
|
process_all_nonimported_procs(Task0, Task, !ModuleInfo, !IO),
|
|
(
|
|
Task = update_module_cookie(_, UnivProcArgInfo),
|
|
univ_to_type(UnivProcArgInfo, ProcArgInfo1)
|
|
->
|
|
ProcArgInfo = ProcArgInfo1
|
|
;
|
|
unexpected(this_file, "deforestation: 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),
|
|
module_info_dependency_info(!.ModuleInfo, DepInfo),
|
|
hlds_dependency_info_get_dependency_ordering(DepInfo, DepOrdering),
|
|
list.condense(DepOrdering, DepList),
|
|
|
|
pd_info_init(!.ModuleInfo, ProcArgInfo, PDInfo0),
|
|
list.foldl2(deforest_proc, DepList, PDInfo0, PDInfo, !IO),
|
|
pd_info_get_module_info(PDInfo, !:ModuleInfo),
|
|
module_info_clobber_dependency_info(!ModuleInfo),
|
|
pd_info_get_versions(PDInfo, VersionIndex),
|
|
|
|
map.keys(VersionIndex, Versions),
|
|
|
|
globals.io_lookup_bool_option(constraint_propagation,
|
|
Constraints, !IO),
|
|
(
|
|
Constraints = yes,
|
|
Versions = [_ | _]
|
|
->
|
|
% 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),
|
|
module_info_get_num_errors(!.ModuleInfo, Errors5),
|
|
|
|
disable_det_warnings(OptionsToRestore, !IO),
|
|
determinism_pass(!ModuleInfo, !IO),
|
|
restore_det_warnings(OptionsToRestore, !IO),
|
|
|
|
module_info_get_num_errors(!.ModuleInfo, Errors),
|
|
expect(unify(Errors5, Errors), this_file,
|
|
"determinism errors after deforestation")
|
|
;
|
|
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, _, at_most_many_cc) ->
|
|
% `cc_multi' or `cc_nondet' determinisms are never inferred,
|
|
% so resetting the determinism would cause determinism errors.
|
|
true
|
|
;
|
|
proc_info_set_inferred_determinism(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(pred_id::in, proc_id::in,
|
|
proc_info::in, proc_info::out, univ::in, univ::out,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
get_branch_vars_proc(PredId, ProcId, ProcInfo, ProcInfo,
|
|
UnivProcArgInfo0, UnivProcArgInfo, !ModuleInfo) :-
|
|
( univ_to_type(UnivProcArgInfo0, ProcArgInfo0) ->
|
|
pd_util.get_branch_vars_proc(proc(PredId, ProcId), ProcInfo,
|
|
ProcArgInfo0, ProcArgInfo, !ModuleInfo),
|
|
type_to_univ(ProcArgInfo, UnivProcArgInfo)
|
|
;
|
|
unexpected(this_file, "get_branch_vars_proc")
|
|
).
|
|
|
|
:- pred deforest_proc(pred_proc_id::in, pd_info::in, pd_info::out,
|
|
io::di, io::uo) is det.
|
|
|
|
deforest_proc(PredProcId, !PDInfo, !IO) :-
|
|
deforest_proc_2(PredProcId, _, _, !PDInfo, !IO).
|
|
|
|
:- pred deforest_proc_2(pred_proc_id::in, int::out, int::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
deforest_proc_2(proc(PredId, ProcId), CostDelta, SizeDelta, !PDInfo, !IO) :-
|
|
some [!ModuleInfo, !PredInfo, !ProcInfo, !Goal] (
|
|
pd_info_get_module_info(!.PDInfo, !:ModuleInfo),
|
|
write_proc_progress_message("% Deforesting ",
|
|
PredId, ProcId, !.ModuleInfo, !IO),
|
|
module_info_pred_proc_info(!.ModuleInfo, PredId, ProcId,
|
|
!:PredInfo, !:ProcInfo),
|
|
pd_info_init_unfold_info(proc(PredId, ProcId), !.PredInfo, !.ProcInfo,
|
|
!PDInfo),
|
|
proc_info_get_goal(!.ProcInfo, !:Goal),
|
|
|
|
% Inlining may have created some opportunities for simplification.
|
|
globals.io_get_globals(Globals, !IO),
|
|
simplify.find_simplifications(no, Globals, Simplifications),
|
|
pd_util.simplify_goal(Simplifications, !Goal, !PDInfo, !IO),
|
|
|
|
pd_util.propagate_constraints(!Goal, !PDInfo, !IO),
|
|
|
|
pd_debug_output_goal(!.PDInfo, "after constraints\n", !.Goal, !IO),
|
|
deforest_goal(!Goal, !PDInfo, !IO),
|
|
|
|
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(!ProcInfo),
|
|
proc_info_get_goal(!.ProcInfo, !:Goal),
|
|
proc_info_get_initial_instmap(!.ProcInfo, !.ModuleInfo, InstMap0),
|
|
proc_info_get_vartypes(!.ProcInfo, VarTypes),
|
|
proc_info_get_inst_varset(!.ProcInfo, InstVarSet),
|
|
recompute_instmap_delta(yes, !Goal, VarTypes,
|
|
InstVarSet, InstMap0, !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(PredId, ProcId,
|
|
!.PredInfo, !.ProcInfo, !ModuleInfo),
|
|
|
|
pd_info_get_rerun_det(!.PDInfo, RerunDet),
|
|
|
|
(
|
|
RerunDet = yes,
|
|
% 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(PredId, ProcId, !ModuleInfo, Globals, _, _, _)
|
|
;
|
|
RerunDet = no
|
|
),
|
|
|
|
% Recompute the branch_info for the procedure.
|
|
pd_info_get_proc_arg_info(!.PDInfo, ProcArgInfo0),
|
|
pd_util.get_branch_vars_proc(proc(PredId, ProcId), !.ProcInfo,
|
|
ProcArgInfo0, ProcArgInfo, !ModuleInfo),
|
|
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(PredId, ProcId,
|
|
!.PredInfo, !.ProcInfo, !ModuleInfo),
|
|
pd_info_set_module_info(!.ModuleInfo, !PDInfo)
|
|
),
|
|
|
|
pd_info_get_module_info(!.PDInfo, !:ModuleInfo),
|
|
write_proc_progress_message("% Finished deforesting ",
|
|
PredId, ProcId, !.ModuleInfo, !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, io::di, io::uo) is det.
|
|
|
|
deforest_goal(conj(ConjType, !.Goals) - Info, conj(ConjType, !:Goals) - Info,
|
|
!PDInfo, !IO) :-
|
|
(
|
|
ConjType = plain_conj,
|
|
pd_info_get_instmap(!.PDInfo, InstMap0),
|
|
partially_evaluate_conj_goals(!.Goals, [], !:Goals, !PDInfo, !IO),
|
|
pd_info_set_instmap(InstMap0, !PDInfo),
|
|
goal_info_get_nonlocals(Info, NonLocals),
|
|
globals.io_lookup_bool_option(deforestation, Deforestation, !IO),
|
|
(
|
|
Deforestation = yes,
|
|
compute_goal_infos(!Goals, !PDInfo),
|
|
pd_info_set_instmap(InstMap0, !PDInfo),
|
|
deforest_conj(!.Goals, NonLocals, [], !:Goals, !PDInfo, !IO)
|
|
;
|
|
Deforestation = no
|
|
),
|
|
globals.io_lookup_bool_option(constraint_propagation, Constraints,
|
|
!IO),
|
|
pd_info_set_instmap(InstMap0, !PDInfo),
|
|
(
|
|
Constraints = yes,
|
|
propagate_conj_constraints(!.Goals, NonLocals, [], !:Goals,
|
|
!PDInfo, !IO)
|
|
;
|
|
Constraints = no
|
|
),
|
|
pd_info_set_instmap(InstMap0, !PDInfo)
|
|
;
|
|
ConjType = parallel_conj
|
|
% XXX cannot deforest across parallel_conjunctions!
|
|
).
|
|
|
|
deforest_goal(disj(Goals0) - Info, disj(Goals) - Info, !PDInfo, !IO) :-
|
|
deforest_disj(Goals0, Goals, !PDInfo, !IO).
|
|
|
|
deforest_goal(if_then_else(Vars, Cond0, Then0, Else0) - Info,
|
|
if_then_else(Vars, Cond, Then, Else) - Info, !PDInfo, !IO) :-
|
|
pd_info_get_instmap(!.PDInfo, InstMap0),
|
|
deforest_goal(Cond0, Cond, !PDInfo, !IO),
|
|
pd_info_update_goal(Cond, !PDInfo),
|
|
deforest_goal(Then0, Then, !PDInfo, !IO),
|
|
pd_info_set_instmap(InstMap0, !PDInfo),
|
|
deforest_goal(Else0, Else, !PDInfo, !IO),
|
|
pd_info_set_instmap(InstMap0, !PDInfo).
|
|
|
|
deforest_goal(switch(Var, CanFail, Cases0) - Info,
|
|
switch(Var, CanFail, Cases) - Info, !PDInfo, !IO) :-
|
|
deforest_cases(Var, Cases0, Cases, !PDInfo, !IO).
|
|
|
|
deforest_goal(Goal, Goal, !PDInfo, !IO) :-
|
|
Goal = foreign_proc(_, _, _, _, _, _) - _.
|
|
|
|
deforest_goal(Goal, Goal, !PDInfo, !IO) :-
|
|
Goal = generic_call(_, _, _, _) - _.
|
|
|
|
deforest_goal(not(Goal0) - Info, not(Goal) - Info, !PDInfo, !IO) :-
|
|
deforest_goal(Goal0, Goal, !PDInfo, !IO).
|
|
|
|
deforest_goal(scope(Reason, Goal0) - Info,
|
|
scope(Reason, Goal) - Info, !PDInfo, !IO) :-
|
|
deforest_goal(Goal0, Goal, !PDInfo, !IO).
|
|
|
|
deforest_goal(Goal0, Goal, !PDInfo, !IO) :-
|
|
Goal0 = call(PredId, ProcId, Args, BuiltinState, _, Name) - _,
|
|
deforest_call(PredId, ProcId, Args, Name, BuiltinState, Goal0, Goal,
|
|
!PDInfo, !IO).
|
|
|
|
deforest_goal(Goal, Goal, !PDInfo, !IO) :-
|
|
Goal = unify(_, _, _, _, _) - _.
|
|
|
|
deforest_goal(shorthand(_) - _, _, !PDInfo, !IO) :-
|
|
% these should have been expanded out by now
|
|
unexpected(this_file, "goal: unexpected shorthand").
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred deforest_disj(list(hlds_goal)::in, list(hlds_goal)::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
deforest_disj([], [], !PDInfo, !IO).
|
|
deforest_disj([Goal0 | Goals0], [Goal | Goals], !PDInfo, !IO) :-
|
|
pd_info_get_instmap(!.PDInfo, InstMap0),
|
|
deforest_goal(Goal0, Goal, !PDInfo, !IO),
|
|
pd_info_set_instmap(InstMap0, !PDInfo),
|
|
deforest_disj(Goals0, Goals, !PDInfo, !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred deforest_cases(prog_var::in, list(case)::in, list(case)::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
deforest_cases(_, [], [], !PDInfo, !IO).
|
|
deforest_cases(Var, [case(ConsId, Goal0) | Cases0],
|
|
[case(ConsId, Goal) | Cases], !PDInfo, !IO) :-
|
|
% Bind Var to ConsId in the instmap before processing this case.
|
|
pd_info_get_instmap(!.PDInfo, InstMap0),
|
|
pd_info_bind_var_to_functor(Var, ConsId, !PDInfo),
|
|
deforest_goal(Goal0, Goal, !PDInfo, !IO),
|
|
pd_info_set_instmap(InstMap0, !PDInfo),
|
|
deforest_cases(Var, Cases0, Cases, !PDInfo, !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% 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, io::di, io::uo) is det.
|
|
|
|
partially_evaluate_conj_goals([], RevGoals, Goals, !PDInfo, !IO) :-
|
|
list.reverse(RevGoals, Goals).
|
|
partially_evaluate_conj_goals([Goal0 | Goals0], RevGoals0, Goals,
|
|
!PDInfo, !IO) :-
|
|
deforest_goal(Goal0, Goal1, !PDInfo, !IO),
|
|
pd_info_update_goal(Goal1, !PDInfo),
|
|
( Goal1 = conj(plain_conj, Goals1) - _ ->
|
|
list.reverse(Goals1, RevGoals1),
|
|
list.append(RevGoals1, RevGoals0, RevGoals2)
|
|
;
|
|
RevGoals2 = [Goal1 | RevGoals0]
|
|
),
|
|
partially_evaluate_conj_goals(Goals0, RevGoals2, Goals, !PDInfo, !IO).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% 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 = GoalExpr - _,
|
|
( goal_util.goal_is_branched(GoalExpr) ->
|
|
pd_util.get_branch_vars_goal(Goal, MaybeBranchInfo, !PDInfo)
|
|
; GoalExpr = call(PredId, ProcId, Args, _, _, _) ->
|
|
pd_info_get_proc_arg_info(!.PDInfo, ProcBranchInfos),
|
|
( map.search(ProcBranchInfos, proc(PredId, ProcId), BranchInfo0) ->
|
|
% Rename the branch_info for the called procedure
|
|
% onto the argument variables.
|
|
pd_util.convert_branch_info(BranchInfo0, Args, BranchInfo),
|
|
MaybeBranchInfo = yes(BranchInfo)
|
|
;
|
|
MaybeBranchInfo = no
|
|
)
|
|
;
|
|
MaybeBranchInfo = no
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred propagate_conj_constraints(list(hlds_goal)::in,
|
|
set(prog_var)::in, list(hlds_goal)::in, list(hlds_goal)::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
propagate_conj_constraints([], _, RevGoals, Goals, !PDInfo, !IO) :-
|
|
list.reverse(RevGoals, Goals).
|
|
propagate_conj_constraints([Goal0 | Goals0],
|
|
NonLocals, RevGoals0, Goals, !PDInfo, !IO) :-
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo),
|
|
(
|
|
% constraint.m ensures that only constraints relevant
|
|
% to this goal are placed adjacent to it.
|
|
Goal0 = call(PredId, _ProcId, _Args, _, _, SymName) - _,
|
|
module_info_pred_info(ModuleInfo, PredId, PredInfo),
|
|
\+ pred_info_is_imported(PredInfo),
|
|
list.takewhile((pred(CnstrGoal::in) is semidet :-
|
|
CnstrGoal = _ - CnstrGoalInfo,
|
|
goal_info_has_feature(CnstrGoalInfo, constraint)
|
|
), Goals0, Constraints, Goals1),
|
|
Constraints \= []
|
|
->
|
|
mdbcomp.prim_data.sym_name_to_string(SymName, SymNameString),
|
|
pd_debug_message("propagating constraints into call to %s\n",
|
|
[s(SymNameString)], !IO),
|
|
|
|
get_sub_conj_nonlocals(NonLocals, RevGoals0, [],
|
|
Goal0, Constraints, no, [], Goals1, ConjNonLocals),
|
|
call_call(ConjNonLocals, Goal0, Constraints, no,
|
|
MaybeGoal, !PDInfo, !IO),
|
|
(
|
|
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, !IO)
|
|
;
|
|
MaybeGoal = no,
|
|
pd_info_update_goal(Goal0, !PDInfo),
|
|
propagate_conj_constraints(Goals0, NonLocals,
|
|
[Goal0 | RevGoals0], Goals, !PDInfo, !IO)
|
|
)
|
|
;
|
|
pd_info_update_goal(Goal0, !PDInfo),
|
|
propagate_conj_constraints(Goals0, NonLocals,
|
|
[Goal0 | RevGoals0], Goals, !PDInfo, !IO)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type annotated_conj ==
|
|
assoc_list(hlds_goal, maybe(pd_branch_info(prog_var))).
|
|
|
|
:- pred deforest_conj(annotated_conj::in, set(prog_var)::in,
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
deforest_conj([], _, RevGoals, Goals, !PDInfo, !IO) :-
|
|
list.reverse(RevGoals, Goals).
|
|
deforest_conj([Goal0 - MaybeBranchInfo | Goals0], NonLocals,
|
|
RevGoals0, RevGoals, !PDInfo, !IO) :-
|
|
(
|
|
% Look for a goal later in the conjunction to deforest with.
|
|
MaybeBranchInfo = yes(GoalBranchInfo),
|
|
detect_deforestation(Goal0, GoalBranchInfo, Goals0, Goals1,
|
|
DeforestInfo)
|
|
->
|
|
handle_deforestation(NonLocals, DeforestInfo,
|
|
RevGoals0, RevGoals1, Goals1, Goals2, Optimized, !PDInfo, !IO),
|
|
(
|
|
Optimized = yes,
|
|
deforest_conj(Goals2, NonLocals, RevGoals1, RevGoals, !PDInfo, !IO)
|
|
;
|
|
Optimized = no,
|
|
pd_info_update_goal(Goal0, !PDInfo),
|
|
RevGoals2 = [Goal0 | RevGoals0],
|
|
deforest_conj(Goals0, NonLocals, RevGoals2, RevGoals, !PDInfo, !IO)
|
|
)
|
|
;
|
|
pd_info_update_goal(Goal0, !PDInfo),
|
|
RevGoals1 = [Goal0 | RevGoals0],
|
|
deforest_conj(Goals0, NonLocals, RevGoals1, RevGoals, !PDInfo, !IO)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- 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) :-
|
|
(
|
|
Goal = LaterGoal - yes(LaterBranchInfo),
|
|
potential_deforestation(EarlierBranchInfo,
|
|
LaterBranchInfo, DeforestBranches)
|
|
->
|
|
list.reverse(RevBetweenGoals0, BetweenGoals1),
|
|
assoc_list.keys(BetweenGoals1, BetweenGoals),
|
|
Goals = Goals0,
|
|
DeforestInfo = deforest_info(EarlierGoal, EarlierBranchInfo,
|
|
BetweenGoals, LaterGoal, LaterBranchInfo, DeforestBranches)
|
|
;
|
|
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),
|
|
\+ 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(prog_var)::in, deforest_info::in,
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
annotated_conj::in, annotated_conj::out, bool::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
handle_deforestation(NonLocals, DeforestInfo0, !RevBeforeGoals, !AfterGoals,
|
|
Optimized, !PDInfo, !IO) :-
|
|
|
|
pd_info_get_instmap(!.PDInfo, InstMap0),
|
|
pd_info_get_created_versions(!.PDInfo, CreatedVersions0),
|
|
|
|
pd_info_get_depth(!.PDInfo, Depth0),
|
|
pd_debug_message("checking for deforestation at depth %i\n", [i(Depth0)],
|
|
!IO),
|
|
|
|
reorder_conj(DeforestInfo0, DeforestInfo,
|
|
BeforeIrrelevant, AfterIrrelevant, !.PDInfo, !IO),
|
|
|
|
get_sub_conj_nonlocals(NonLocals, DeforestInfo, !.RevBeforeGoals,
|
|
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),
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo),
|
|
|
|
DeforestInfo = deforest_info(EarlierGoal, _, BetweenGoals,
|
|
LaterGoal, _, DeforestBranches),
|
|
|
|
should_try_deforestation(DeforestInfo, ShouldOptimize, !PDInfo, !IO),
|
|
(
|
|
ShouldOptimize = no
|
|
->
|
|
Optimized0 = no,
|
|
Goals = []
|
|
;
|
|
EarlierGoal = call(PredId1, _, _, _, _, _) - _,
|
|
LaterGoal = call(PredId2, _, _, _, _, _) - _
|
|
->
|
|
% If both goals are calls create a new predicate for the conjunction
|
|
% to be deforested and process it.
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo0),
|
|
PredName1 = predicate_name(ModuleInfo0, PredId1),
|
|
PredName2 = predicate_name(ModuleInfo0, PredId2),
|
|
pd_debug_message("deforesting calls to %s and %s\n",
|
|
[s(PredName1), s(PredName2)], !IO),
|
|
call_call(ConjNonLocals, EarlierGoal, BetweenGoals,
|
|
yes(LaterGoal), MaybeGoal, !PDInfo, !IO),
|
|
(
|
|
MaybeGoal = yes(Goal),
|
|
Optimized0 = yes,
|
|
Goals = [Goal]
|
|
;
|
|
MaybeGoal = no,
|
|
Optimized0 = no,
|
|
Goals = []
|
|
)
|
|
;
|
|
% 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 = EarlierGoalExpr - _,
|
|
goal_util.goal_is_branched(EarlierGoalExpr),
|
|
LaterGoal = call(PredId, ProcId, _, _, _, _) - _,
|
|
PredProcId = proc(PredId, ProcId),
|
|
PredProcId \= CurrPredProcId,
|
|
\+ set.member(PredProcId, Parents0)
|
|
->
|
|
CurrPredName = predicate_name(ModuleInfo, PredId),
|
|
pd_debug_message("Pushing call to %s into goal\n",
|
|
[s(CurrPredName)], !IO),
|
|
set.insert(Parents0, proc(PredId, ProcId), Parents),
|
|
pd_info_set_parents(Parents, !PDInfo),
|
|
push_goal_into_goal(ConjNonLocals, DeforestBranches,
|
|
EarlierGoal, BetweenGoals, LaterGoal, Goal, !PDInfo, !IO),
|
|
Goals = [Goal],
|
|
Optimized0 = yes
|
|
;
|
|
% If both goals are branched, push the second into the branches
|
|
% of the first.
|
|
EarlierGoal = EarlierGoalExpr - _,
|
|
LaterGoal = LaterGoalExpr - _,
|
|
goal_util.goal_is_branched(EarlierGoalExpr),
|
|
goal_util.goal_is_branched(LaterGoalExpr)
|
|
->
|
|
pd_debug_message("Pushing goal into goal\n", [], !IO),
|
|
push_goal_into_goal(ConjNonLocals, DeforestBranches,
|
|
EarlierGoal, BetweenGoals, LaterGoal, Goal, !PDInfo, !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
|
|
;
|
|
pd_debug_message("not optimizing\n", [], !IO),
|
|
Goals = [],
|
|
Optimized0 = no
|
|
),
|
|
check_improvement(Optimized0, CostDelta0, SizeDelta0,
|
|
Optimized, !.PDInfo, !IO),
|
|
|
|
% 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)
|
|
),
|
|
pd_debug_message("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,
|
|
bool::out, pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
should_try_deforestation(DeforestInfo, ShouldTry, !PDInfo, !IO) :-
|
|
DeforestInfo = deforest_info(EarlierGoal, EarlierBranchInfo,
|
|
BetweenGoals, LaterGoal, _, _),
|
|
pd_info_get_useless_versions(!.PDInfo, UselessVersions),
|
|
(
|
|
EarlierGoal = call(PredId1, ProcId1, _, _, _, _) - _,
|
|
LaterGoal = call(PredId2, ProcId2, _, _, _, _) - _,
|
|
set.member(proc(PredId1, ProcId1) - proc(PredId2, ProcId2),
|
|
UselessVersions)
|
|
->
|
|
pd_debug_message("version tried before, not worthwhile\n", [], !IO),
|
|
ShouldTry = no
|
|
;
|
|
% 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 = _ - OpaqueGoalInfo,
|
|
goal_info_get_nonlocals(OpaqueGoalInfo, OpaqueNonLocals),
|
|
set.intersect(OpaqueNonLocals, OpaqueVars, UsedOpaqueVars),
|
|
\+ set.empty(UsedOpaqueVars)
|
|
->
|
|
pd_debug_message("later goals depend on opaque vars\n", [], !IO),
|
|
ShouldTry = no
|
|
;
|
|
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, io::di, io::uo) is det.
|
|
|
|
can_optimize_conj(EarlierGoal, BetweenGoals, MaybeLaterGoal, ShouldTry,
|
|
!PDInfo, !IO) :-
|
|
pd_info_get_pred_info(!.PDInfo, PredInfo),
|
|
globals.io_lookup_option(deforestation_depth_limit, DepthLimitOpt, !IO),
|
|
pd_info_get_depth(!.PDInfo, Depth0),
|
|
Depth = Depth0 + 1,
|
|
pd_info_set_depth(Depth, !PDInfo),
|
|
globals.io_lookup_int_option(deforestation_size_threshold, SizeLimit,
|
|
!IO),
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo),
|
|
globals.io_lookup_option(fully_strict, FullyStrictOp, !IO),
|
|
(
|
|
DepthLimitOpt = int(MaxDepth),
|
|
MaxDepth \= -1, % no depth limit set
|
|
Depth0 >= MaxDepth
|
|
->
|
|
% The depth limit was exceeded. This should not occur too often in
|
|
% practice - the depth limit is just a safety net.
|
|
pd_debug_message("\n\n*****Depth limit exceeded*****\n\n", [], !IO),
|
|
ShouldTry = no
|
|
;
|
|
% 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 = call(PredId, ProcId, _, _, _, _) - _
|
|
; MaybeLaterGoal = yes(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
|
|
->
|
|
pd_debug_message("goal too large\n", [], !IO),
|
|
ShouldTry = no
|
|
;
|
|
% Check whether either of the goals to be deforested can't be inlined.
|
|
( EarlierGoal = call(PredId, ProcId, _, BuiltinState, _, _) - _
|
|
; MaybeLaterGoal = yes(call(PredId, ProcId, _, BuiltinState, _, _) - _)
|
|
),
|
|
|
|
% We don't attempt to deforest predicates which are promised pure
|
|
% 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.
|
|
InlinePromisedPure = no,
|
|
pred_info_get_markers(PredInfo, CallerMarkers),
|
|
\+ inlining.can_inline_proc(PredId, ProcId, BuiltinState,
|
|
InlinePromisedPure, CallerMarkers, ModuleInfo)
|
|
->
|
|
pd_debug_message("non-inlineable calls\n", [], !IO),
|
|
ShouldTry = no
|
|
;
|
|
%
|
|
% Don't optimize if that would require duplicating
|
|
% branched goal structures.
|
|
%
|
|
\+ is_simple_goal_list(BetweenGoals)
|
|
->
|
|
pd_debug_message("between goals not simple enough\n", [], !IO),
|
|
ShouldTry = no
|
|
;
|
|
% 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 = _ - ImpureGoalInfo,
|
|
\+ goal_info_is_pure(ImpureGoalInfo)
|
|
->
|
|
pd_debug_message("goal list contains impure goal(s)\n", [], !IO),
|
|
ShouldTry = no
|
|
;
|
|
% 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
|
|
),
|
|
\+ goal_util.reordering_maintains_termination(ModuleInfo,
|
|
FullyStrict, EarlierGoal, OtherGoal)
|
|
->
|
|
pd_debug_message("interleaving execution could change " ++
|
|
"termination behaviour\n", [], !IO),
|
|
ShouldTry = no
|
|
;
|
|
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%.
|
|
%
|
|
:- pred check_improvement(bool::in, int::in, int::in, bool::out,
|
|
pd_info::in, io::di, io::uo) is det.
|
|
|
|
check_improvement(Optimized0, CostDelta0, SizeDelta0, Optimized, PDInfo,
|
|
!IO) :-
|
|
pd_info_get_cost_delta(PDInfo, CostDelta),
|
|
pd_info_get_size_delta(PDInfo, SizeDelta),
|
|
Improvement = CostDelta - CostDelta0,
|
|
SizeDifference = SizeDelta - SizeDelta0,
|
|
globals.io_lookup_int_option(deforestation_cost_factor, Factor, !IO),
|
|
(
|
|
Optimized0 = yes,
|
|
check_deforestation_improvement(Factor,
|
|
Improvement, SizeDifference)
|
|
->
|
|
Optimized = yes,
|
|
pd_debug_message("Enough improvement: cost(%i) size(%i)\n",
|
|
[i(Improvement), i(SizeDifference)], !IO)
|
|
;
|
|
Optimized = no,
|
|
pd_debug_message(
|
|
"Not enough improvement: cost(%i) size(%i)\n",
|
|
[i(Improvement), i(SizeDifference)], !IO)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Attempt deforestation on a pair of calls.
|
|
%
|
|
:- pred call_call(set(prog_var)::in, hlds_goal::in,
|
|
list(hlds_goal)::in, maybe(hlds_goal)::in, maybe(hlds_goal)::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
call_call(ConjNonLocals, EarlierGoal, BetweenGoals, MaybeLaterGoal, MaybeGoal,
|
|
!PDInfo, !IO) :-
|
|
can_optimize_conj(EarlierGoal, BetweenGoals, MaybeLaterGoal, ShouldTry,
|
|
!PDInfo, !IO),
|
|
(
|
|
ShouldTry = yes,
|
|
call_call(ConjNonLocals, EarlierGoal, BetweenGoals, MaybeLaterGoal,
|
|
MaybeGoal, !PDInfo, !IO)
|
|
;
|
|
ShouldTry = no,
|
|
MaybeGoal = no
|
|
).
|
|
|
|
% Attempt deforestation on a pair of calls.
|
|
%
|
|
:- pred call_call_2(set(prog_var)::in, hlds_goal::in,
|
|
list(hlds_goal)::in, maybe(hlds_goal)::in, maybe(hlds_goal)::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
call_call_2(ConjNonLocals, EarlierGoal, BetweenGoals,
|
|
MaybeLaterGoal, MaybeGoal, !PDInfo, !IO) :-
|
|
create_conj(EarlierGoal, BetweenGoals, MaybeLaterGoal, ConjNonLocals,
|
|
FoldGoal),
|
|
|
|
pd_info.search_version(!.PDInfo, FoldGoal, MaybeVersion, !IO),
|
|
pd_info_get_parent_versions(!.PDInfo, Parents),
|
|
(
|
|
MaybeVersion = version(_, VersionPredProcId,
|
|
VersionInfo, Renaming, TypeRenaming)
|
|
->
|
|
% If we see an opportunity to fold, take it.
|
|
VersionPredProcId = proc(VersionPredId, _),
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo0),
|
|
FoldPredName = predicate_name(ModuleInfo0, VersionPredId),
|
|
pd_debug_message("Folded with %s\n", [s(FoldPredName)], !IO),
|
|
( set.member(VersionPredProcId, Parents) ->
|
|
FoldCostDelta = cost_of_recursive_fold
|
|
;
|
|
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)
|
|
;
|
|
pd_info_get_global_term_info(!.PDInfo, TermInfo0),
|
|
pd_info_get_parent_versions(!.PDInfo, ParentVersions0),
|
|
|
|
pd_debug_do_io(io.write_string("Parents: "), !IO),
|
|
pd_debug_write(ParentVersions0, !IO),
|
|
pd_debug_do_io(io.nl, !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),
|
|
pd_debug_message("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, !IO)
|
|
;
|
|
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, !IO)
|
|
;
|
|
CheckResult = loop,
|
|
pd_debug_message("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, hlds_goals::in,
|
|
maybe(hlds_goal)::in, hlds_goal::in, set(prog_var)::in, bool::in,
|
|
proc_pair::in, int::in, maybe(pred_proc_id)::in,
|
|
maybe(hlds_goal)::out, pd_info::in, pd_info::out,
|
|
io::di, io::uo) is det.
|
|
|
|
create_deforest_goal(EarlierGoal, BetweenGoals, MaybeLaterGoal,
|
|
FoldGoal0, NonLocals, RunModes, ProcPair, Size,
|
|
MaybeGeneralised, MaybeCallGoal, !PDInfo, !IO) :-
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo0),
|
|
globals.io_lookup_int_option(deforestation_vars_threshold, VarsOpt, !IO),
|
|
(
|
|
EarlierGoal = call(PredId1, ProcId1, Args1, _, _, _) - _,
|
|
(
|
|
% No threshold set.
|
|
VarsOpt = -1
|
|
;
|
|
% Check that we're 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),
|
|
goal_util.goal_vars(CalledGoal1, GoalVars1),
|
|
set.to_sorted_list(GoalVars1, GoalVarsList1),
|
|
set.init(GoalVars2),
|
|
goal_util.goals_goal_vars(BetweenGoals, GoalVars2, GoalVars3),
|
|
set.to_sorted_list(GoalVars3, GoalVarsList3),
|
|
|
|
list.length(GoalVarsList1, NumVars1),
|
|
list.length(GoalVarsList3, NumVars3),
|
|
NumVars = NumVars1 + NumVars3,
|
|
NumVars < VarsOpt
|
|
)
|
|
->
|
|
% Create the goal for the new predicate,
|
|
% unfolding the first call.
|
|
|
|
pd_info_get_instmap(!.PDInfo, InstMap0),
|
|
pd_info_get_proc_info(!.PDInfo, ProcInfo0),
|
|
pd_debug_message("unfolding first call\n", [], !IO),
|
|
|
|
unfold_call(no, no, PredId1, ProcId1, Args1, EarlierGoal, UnfoldedCall,
|
|
DidUnfold, !PDInfo, !IO),
|
|
create_conj(UnfoldedCall, BetweenGoals, MaybeLaterGoal, NonLocals,
|
|
DeforestGoal0),
|
|
set.to_sorted_list(NonLocals, NonLocalsList),
|
|
|
|
(
|
|
DidUnfold = yes,
|
|
RunModes = yes
|
|
->
|
|
|
|
% 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.
|
|
|
|
pd_debug_message("running modes on deforest goal\n", [], !IO),
|
|
pd_util.unique_modecheck_goal(DeforestGoal0,
|
|
DeforestGoal, Errors1, !PDInfo, !IO),
|
|
pd_util.unique_modecheck_goal(FoldGoal0, FoldGoal,
|
|
Errors2, !PDInfo, !IO),
|
|
list.append(Errors1, Errors2, Errors)
|
|
;
|
|
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.
|
|
(
|
|
DidUnfold = yes,
|
|
Errors = []
|
|
->
|
|
% Create the new version.
|
|
|
|
pd_info.define_new_pred(created(deforestation),
|
|
DeforestGoal, PredProcId, CallGoal, !PDInfo),
|
|
PredProcId = proc(PredId, _),
|
|
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo),
|
|
|
|
PredName = predicate_name(ModuleInfo, PredId),
|
|
pd_debug_message("\nCreated predicate %s\n", [s(PredName)], !IO),
|
|
( MaybeLaterGoal = yes(call(PredId2, ProcId2, _, _, _, _) - _) ->
|
|
CalledPreds = [proc(PredId1, ProcId1), proc(PredId2, ProcId2)]
|
|
;
|
|
CalledPreds = [proc(PredId1, ProcId1)]
|
|
),
|
|
pd_info_get_parent_versions(!.PDInfo, Parents0),
|
|
|
|
pd_info_get_proc_info(!.PDInfo, ProcInfo1),
|
|
proc_info_get_vartypes(ProcInfo1, VarTypes),
|
|
map.apply_to_list(NonLocalsList, VarTypes, 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(Parents0, [PredProcId | CalledPreds], Parents),
|
|
pd_info_set_parent_versions(Parents, !PDInfo),
|
|
pd_info.register_version(PredProcId, VersionInfo, !PDInfo, !IO),
|
|
|
|
% Run deforestation on the new predicate to do the folding.
|
|
pd_info_get_unfold_info(!.PDInfo, UnfoldInfo),
|
|
deforest_proc_2(PredProcId, CostDelta, SizeDelta, !PDInfo, !IO),
|
|
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_pred_proc_id(!.PDInfo, proc(CurrPredId, CurrProcId)),
|
|
write_proc_progress_message("% Back in ",
|
|
CurrPredId, CurrProcId, ModuleInfo, !IO),
|
|
MaybeCallGoal = yes(CallGoal)
|
|
;
|
|
pd_debug_message("Generalisation produced mode errors\n", [],
|
|
!IO),
|
|
MaybeCallGoal = no
|
|
),
|
|
|
|
% The varset and vartypes fields were increased 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)
|
|
;
|
|
pd_debug_message("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) :-
|
|
OldArgs = 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_vartypes(ProcInfo0, VarTypes0),
|
|
proc_info_get_varset(ProcInfo0, VarSet0),
|
|
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_variable_renaming_to_type_list(TypeRenaming, ArgTypes0, ArgTypes1),
|
|
|
|
create_deforest_call_args(OldArgs, ArgTypes1, Renaming,
|
|
TypeSubn, Args, VarSet0, VarSet, VarTypes0, VarTypes),
|
|
proc_info_set_vartypes(VarTypes, ProcInfo0, ProcInfo1),
|
|
proc_info_set_varset(VarSet, ProcInfo1, ProcInfo),
|
|
pd_info_set_proc_info(ProcInfo, !PDInfo),
|
|
|
|
% Compute a goal_info.
|
|
proc_info_get_argmodes(CalledProcInfo, ArgModes),
|
|
instmap_delta_from_mode_list(Args, ArgModes, ModuleInfo, InstMapDelta),
|
|
proc_info_interface_determinism(ProcInfo, Detism),
|
|
set.list_to_set(Args, 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),
|
|
Goal = call(PredId, ProcId, Args, not_builtin, no,
|
|
qualified(PredModule, PredName)) - GoalInfo.
|
|
|
|
:- pred create_deforest_call_args(list(prog_var)::in, list(mer_type)::in,
|
|
map(prog_var, prog_var)::in, tsubst::in,
|
|
list(prog_var)::out, prog_varset::in, prog_varset::out,
|
|
vartypes::in, vartypes::out) is det.
|
|
|
|
create_deforest_call_args([], [], _, _, [], !VarSet, !VarTypes).
|
|
create_deforest_call_args([], [_|_], _, _, _, !VarSet, !VarTypes) :-
|
|
unexpected(this_file, "create_deforest_call_args: length mismatch").
|
|
create_deforest_call_args([_|_], [], _, _, _, !VarSet, !VarTypes) :-
|
|
unexpected(this_file, "create_deforest_call_args: length mismatch").
|
|
create_deforest_call_args([OldArg | OldArgs], [ArgType | ArgTypes],
|
|
Renaming, TypeSubn, [Arg | Args], !VarSet, !VarTypes) :-
|
|
( map.search(Renaming, OldArg, Arg0) ->
|
|
Arg = Arg0
|
|
;
|
|
% The variable is local to the call. Create a fresh variable.
|
|
varset.new_var(!.VarSet, Arg, !:VarSet),
|
|
apply_subst_to_type(TypeSubn, ArgType, SubnArgType),
|
|
map.det_insert(!.VarTypes, Arg, SubnArgType, !:VarTypes)
|
|
),
|
|
create_deforest_call_args(OldArgs, ArgTypes, Renaming,
|
|
TypeSubn, Args, !VarSet, !VarTypes).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% 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(prog_var)::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 = _ - EarlierGoalInfo,
|
|
goal_info_get_context(EarlierGoalInfo, EarlierContext),
|
|
goal_info_set_context(EarlierContext, ConjInfo0, ConjInfo),
|
|
FoldGoal = 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(prog_var)::in,
|
|
proc_pair::in, int::in, pred_proc_id::in, maybe(hlds_goal)::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
try_generalisation(EarlierGoal, BetweenGoals, MaybeLaterGoal,
|
|
FoldGoal, ConjNonLocals, ProcPair, Size,
|
|
CoveringPredProcId, MaybeGoal, !PDInfo, !IO) :-
|
|
pd_debug_message("trying generalisation\n", [], !IO),
|
|
pd_info_get_versions(!.PDInfo, VersionIndex),
|
|
map.lookup(VersionIndex, CoveringPredProcId, Version),
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo),
|
|
Version = version_info(VersionGoal, _, VersionArgs,
|
|
VersionArgTypes, VersionInstMap, _, _, _, _),
|
|
pd_info_get_versions(!.PDInfo, Versions),
|
|
pd_info_get_proc_info(!.PDInfo, ProcInfo),
|
|
proc_info_get_vartypes(ProcInfo, VarTypes),
|
|
(
|
|
pd_util.goals_match(ModuleInfo, VersionGoal, VersionArgs,
|
|
VersionArgTypes, FoldGoal, VarTypes, Renaming, _)
|
|
->
|
|
do_generalisation(VersionArgs, Renaming, VersionInstMap,
|
|
EarlierGoal, BetweenGoals, MaybeLaterGoal, FoldGoal, ConjNonLocals,
|
|
ProcPair, Size, CoveringPredProcId, MaybeGoal, !PDInfo, !IO)
|
|
;
|
|
% 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.
|
|
proc_info_get_varset(ProcInfo, VarSet),
|
|
match_generalised_version(ModuleInfo, VersionGoal,
|
|
VersionArgs, VersionArgTypes, EarlierGoal, BetweenGoals,
|
|
MaybeLaterGoal, ConjNonLocals, VarSet, VarTypes, Versions,
|
|
Renaming)
|
|
->
|
|
pd_debug_message("matched with generalised version\n", [], !IO),
|
|
do_generalisation(VersionArgs, Renaming, VersionInstMap,
|
|
EarlierGoal, BetweenGoals, MaybeLaterGoal, FoldGoal, ConjNonLocals,
|
|
ProcPair, Size, CoveringPredProcId, MaybeGoal, !PDInfo, !IO)
|
|
;
|
|
pd_debug_message("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(prog_var)::in, proc_pair::in, int::in,
|
|
pred_proc_id::in, maybe(hlds_goal)::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
do_generalisation(VersionArgs, Renaming, VersionInstMap, EarlierGoal,
|
|
BetweenGoals, MaybeLaterGoal, FoldGoal, ConjNonLocals,
|
|
ProcPair, Size, Generalised, MaybeGoal, !PDInfo, !IO) :-
|
|
pd_debug_message("goals match, trying MSG\n", [], !IO),
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo),
|
|
pd_info_get_instmap(!.PDInfo, InstMap0),
|
|
instmap.lookup_vars(VersionArgs, VersionInstMap, VersionInsts),
|
|
pd_util.inst_list_size(ModuleInfo, VersionInsts, VersionInstSizes),
|
|
set.to_sorted_list(ConjNonLocals, ConjNonLocalsList),
|
|
(
|
|
% Check whether we can do a most specific generalisation of insts
|
|
% of the non-locals.
|
|
try_MSG(ModuleInfo, VersionInstMap, VersionArgs, Renaming,
|
|
InstMap0, InstMap),
|
|
instmap.lookup_vars(ConjNonLocalsList, InstMap, ArgInsts),
|
|
pd_util.inst_list_size(ModuleInfo, ArgInsts, NewInstSizes),
|
|
NewInstSizes < VersionInstSizes
|
|
->
|
|
pd_debug_message("MSG succeeded", [], !IO),
|
|
pd_info_set_instmap(InstMap, !PDInfo),
|
|
create_deforest_goal(EarlierGoal, BetweenGoals,
|
|
MaybeLaterGoal, FoldGoal, ConjNonLocals, yes, ProcPair,
|
|
Size, yes(Generalised), MaybeGoal, !PDInfo, !IO)
|
|
;
|
|
pd_debug_message("MSG failed\n", [], !IO),
|
|
MaybeGoal = no
|
|
),
|
|
pd_info_set_instmap(InstMap0, !PDInfo).
|
|
|
|
:- pred try_MSG(module_info::in, instmap::in, list(prog_var)::in,
|
|
map(prog_var, prog_var)::in, instmap::in, instmap::out) is semidet.
|
|
|
|
try_MSG(_, _, [], _, !InstMap).
|
|
try_MSG(ModuleInfo, VersionInstMap, [VersionArg | VersionArgs],
|
|
Renaming, !InstMap) :-
|
|
instmap.lookup_var(VersionInstMap, VersionArg, VersionInst),
|
|
(
|
|
map.search(Renaming, VersionArg, Arg),
|
|
instmap.lookup_var(!.InstMap, Arg, VarInst),
|
|
inst_MSG(VersionInst, VarInst, ModuleInfo, Inst)
|
|
->
|
|
instmap.set(Arg, Inst, !InstMap)
|
|
;
|
|
true
|
|
),
|
|
try_MSG(ModuleInfo, VersionInstMap, VersionArgs,
|
|
Renaming, !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(prog_var)::in, prog_varset::in, vartypes::in,
|
|
version_index::in, map(prog_var, prog_var)::out) is semidet.
|
|
|
|
match_generalised_version(ModuleInfo, VersionGoal, VersionArgs,
|
|
VersionArgTypes, FirstGoal, BetweenGoals, MaybeLastGoal,
|
|
ConjNonLocals, !.VarSet, !.VarTypes, Versions, Renaming) :-
|
|
|
|
FirstGoal = call(FirstPredId, FirstProcId, FirstArgs, _, _, _) - _,
|
|
|
|
% Look up the version which the first goal calls.
|
|
map.search(Versions, proc(FirstPredId, FirstProcId), FirstVersionInfo),
|
|
FirstVersionInfo = version_info(FirstVersionGoal, _, FirstVersionArgs,
|
|
_, _, _, _, _, MaybeNonGeneralisedVersion),
|
|
MaybeNonGeneralisedVersion = yes(NonGeneralisedPredProcId),
|
|
map.from_corresponding_lists(FirstVersionArgs, FirstArgs, FirstRenaming0),
|
|
|
|
goal_util.goal_vars(FirstVersionGoal, FirstVersionVars0),
|
|
set.to_sorted_list(FirstVersionVars0, FirstVersionVars),
|
|
|
|
module_info_pred_proc_info(ModuleInfo, FirstPredId, FirstProcId,
|
|
_, FirstProcInfo),
|
|
proc_info_get_varset(FirstProcInfo, FirstVersionVarSet),
|
|
proc_info_get_vartypes(FirstProcInfo, FirstVersionVarTypes),
|
|
|
|
goal_util.create_variables(FirstVersionVars,
|
|
FirstVersionVarSet, FirstVersionVarTypes,
|
|
!VarSet, !VarTypes, FirstRenaming0, FirstRenaming),
|
|
goal_util.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 = [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, _,
|
|
NonGeneralisedArgs, NonGeneralisedArgTypes,_,_,_,_,_),
|
|
pd_util.goals_match(ModuleInfo, NonGeneralisedGoal,
|
|
NonGeneralisedArgs, NonGeneralisedArgTypes,
|
|
RenamedFirstVersionGoal, !.VarTypes, GeneralRenaming, TypeRenaming),
|
|
|
|
module_info_pred_info(ModuleInfo, NonGeneralisedPredId,
|
|
NonGeneralisedPredInfo),
|
|
pred_info_get_arg_types(NonGeneralisedPredInfo, NonGeneralisedArgTypes),
|
|
create_deforest_call_args(NonGeneralisedArgs,
|
|
NonGeneralisedArgTypes, GeneralRenaming, TypeRenaming, NewArgs,
|
|
!.VarSet, _, !.VarTypes, _),
|
|
|
|
% Only fill in as much as pd_util.goals_match actually looks at.
|
|
goal_info_init(GoalInfo),
|
|
NonGeneralFirstGoal = call(NonGeneralisedPredId, NonGeneralisedProcId,
|
|
NewArgs, not_builtin, no, unqualified("")) - GoalInfo,
|
|
create_conj(NonGeneralFirstGoal, BetweenGoals, MaybeLastGoal,
|
|
ConjNonLocals, GoalToMatch),
|
|
|
|
% Check whether the entire conjunction matches.
|
|
pd_util.goals_match(ModuleInfo, VersionGoal, VersionArgs,
|
|
VersionArgTypes, GoalToMatch, !.VarTypes, 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(prog_var)::in, deforest_info::in,
|
|
list(hlds_goal)::in, list(hlds_goal)::in, list(hlds_goal)::in,
|
|
annotated_conj::in, set(prog_var)::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),
|
|
get_sub_conj_nonlocals(NonLocals0, RevBeforeGoals,
|
|
BeforeIrrelevant, EarlierGoal, BetweenGoals, yes(LaterGoal),
|
|
AfterIrrelevant, AfterGoals, SubConjNonLocals).
|
|
|
|
:- pred get_sub_conj_nonlocals(set(prog_var)::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(prog_var)::out) is det.
|
|
|
|
get_sub_conj_nonlocals(!.NonLocals, RevBeforeGoals, BeforeIrrelevant,
|
|
EarlierGoal, BetweenGoals, MaybeLaterGoal,
|
|
AfterIrrelevant, AfterGoals, !:SubConjNonLocals) :-
|
|
AddGoalNonLocals = (pred(Goal::in, Vars0::in, Vars::out) is det :-
|
|
Goal = _ - GoalInfo,
|
|
goal_info_get_nonlocals(GoalInfo, GoalNonLocals),
|
|
set.union(Vars0, GoalNonLocals, 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.init, !:SubConjNonLocals),
|
|
( MaybeLaterGoal = yes(LaterGoal) ->
|
|
call(AddGoalNonLocals, LaterGoal, !SubConjNonLocals)
|
|
;
|
|
true
|
|
),
|
|
set.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,
|
|
io::di, io::uo) is det.
|
|
|
|
reorder_conj(DeforestInfo0, DeforestInfo,
|
|
BeforeIrrelevant, AfterIrrelevant, PDInfo, !IO) :-
|
|
pd_debug_message("Reordering conjunction\n", [], !IO),
|
|
DeforestInfo0 = deforest_info(EarlierGoal, EarlierBranchInfo,
|
|
BetweenGoals0, LaterGoal, LaterBranchInfo, DeforestBranches),
|
|
|
|
pd_info_get_module_info(PDInfo, ModuleInfo),
|
|
globals.io_lookup_bool_option(fully_strict, FullyStrict, !IO),
|
|
|
|
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,
|
|
hlds_goals::in, hlds_goals::in, hlds_goals::out,
|
|
hlds_goal::in, hlds_goals::in, hlds_goals::out) is det.
|
|
|
|
move_goals(_, _, _, [], !BetweenGoals, _, !MovedGoal).
|
|
move_goals(CanMove, ModuleInfo, FullyStrict, [BetweenGoal | RevBetweenGoals0],
|
|
!BetweenGoals, EndGoal, !MovedGoals) :-
|
|
(
|
|
call(CanMove, ModuleInfo, FullyStrict, BetweenGoal,
|
|
[EndGoal | !.BetweenGoals])
|
|
->
|
|
!:MovedGoals = [BetweenGoal | !.MovedGoals]
|
|
;
|
|
!:BetweenGoals = [BetweenGoal | !.BetweenGoals]
|
|
),
|
|
move_goals(CanMove, ModuleInfo, FullyStrict,
|
|
RevBetweenGoals0, !BetweenGoals, EndGoal, !MovedGoals).
|
|
|
|
:- type can_move == pred(module_info, bool, hlds_goal, hlds_goals).
|
|
:- mode can_move == (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_util.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_util.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(prog_var)::in, set(int)::in,
|
|
hlds_goal::in, hlds_goals::in, hlds_goal::in, hlds_goal::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
push_goal_into_goal(NonLocals, DeforestInfo, EarlierGoal,
|
|
BetweenGoals, LaterGoal, Goal, !PDInfo, !IO) :-
|
|
pd_info_get_instmap(!.PDInfo, InstMap0),
|
|
EarlierGoal = EarlierGoalExpr - _,
|
|
( EarlierGoalExpr = switch(Var1, CanFail1, Cases1) ->
|
|
set.insert(NonLocals, Var1, CaseNonLocals),
|
|
append_goal_to_cases(Var1, BetweenGoals, LaterGoal,
|
|
CaseNonLocals, 1, DeforestInfo, Cases1, Cases, !PDInfo, !IO),
|
|
GoalExpr = switch(Var1, CanFail1, Cases)
|
|
; EarlierGoalExpr = if_then_else(Vars, Cond, Then0, Else0) ->
|
|
pd_info_update_goal(Cond, !PDInfo),
|
|
Cond = _ - CondInfo,
|
|
goal_info_get_nonlocals(CondInfo, CondNonLocals),
|
|
set.union(CondNonLocals, NonLocals, ThenNonLocals),
|
|
append_goal(Then0, BetweenGoals, LaterGoal,
|
|
ThenNonLocals, 1, DeforestInfo, Then, !PDInfo, !IO),
|
|
pd_info_set_instmap(InstMap0, !PDInfo),
|
|
append_goal(Else0, BetweenGoals, LaterGoal,
|
|
NonLocals, 2, DeforestInfo, Else, !PDInfo, !IO),
|
|
GoalExpr = if_then_else(Vars, Cond, Then, Else)
|
|
; EarlierGoalExpr = disj(Disjuncts0) ->
|
|
append_goal_to_disjuncts(BetweenGoals, LaterGoal,
|
|
NonLocals, 1, DeforestInfo, Disjuncts0, Disjuncts, !PDInfo, !IO),
|
|
GoalExpr = disj(Disjuncts)
|
|
;
|
|
unexpected(this_file, "push_goal_into_goal")
|
|
),
|
|
pd_info_set_instmap(InstMap0, !PDInfo),
|
|
goal_list_instmap_delta([EarlierGoal | BetweenGoals], Delta0),
|
|
LaterGoal = _ - LaterInfo,
|
|
goal_info_get_instmap_delta(LaterInfo, Delta1),
|
|
instmap_delta_apply_instmap_delta(Delta0, Delta1, test_size, Delta2),
|
|
instmap_delta_restrict(NonLocals, Delta2, Delta),
|
|
goal_list_determinism([EarlierGoal | BetweenGoals], Detism0),
|
|
goal_info_get_determinism(LaterInfo, Detism1),
|
|
det_conjunction_detism(Detism0, Detism1, Detism),
|
|
goal_list_purity([EarlierGoal | BetweenGoals], Purity0),
|
|
infer_goal_info_purity(LaterInfo, Purity1),
|
|
worst_purity(Purity0, Purity1) = Purity,
|
|
goal_info_init(NonLocals, Delta, Detism, Purity, GoalInfo),
|
|
Goal2 = GoalExpr - GoalInfo,
|
|
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo),
|
|
module_info_get_globals(ModuleInfo, Globals),
|
|
simplify.find_simplifications(no, Globals, Simplifications0),
|
|
SimpList0 = simplifications_to_list(Simplifications0),
|
|
% Be a bit more aggressive with common structure elimination.
|
|
% This helps achieve folding in some cases.
|
|
SimpList = [extra_common_struct | SimpList0],
|
|
Simplifications = list_to_simplifications(SimpList),
|
|
pd_util.simplify_goal(Simplifications, Goal2, Goal3, !PDInfo, !IO),
|
|
pd_info_set_instmap(InstMap0, !PDInfo),
|
|
|
|
% Perform any folding which may now be possible.
|
|
deforest_goal(Goal3, Goal, !PDInfo, !IO),
|
|
pd_info_set_instmap(InstMap0, !PDInfo).
|
|
|
|
:- pred append_goal_to_disjuncts(hlds_goals::in, hlds_goal::in,
|
|
set(prog_var)::in, int::in, set(int)::in, hlds_goals::in, hlds_goals::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
append_goal_to_disjuncts(_, _, _, _, _, [], [], !PDInfo, !IO).
|
|
append_goal_to_disjuncts(BetweenGoals, GoalToAppend, NonLocals,
|
|
CurrBranch, Branches, [Goal0 | Goals0], [Goal | Goals],
|
|
!PDInfo, !IO) :-
|
|
pd_info_get_instmap(!.PDInfo, InstMap0),
|
|
append_goal(Goal0, BetweenGoals, GoalToAppend,
|
|
NonLocals, CurrBranch, Branches, Goal, !PDInfo, !IO),
|
|
NextBranch = CurrBranch + 1,
|
|
pd_info_set_instmap(InstMap0, !PDInfo),
|
|
append_goal_to_disjuncts(BetweenGoals, GoalToAppend,
|
|
NonLocals, NextBranch, Branches, Goals0, Goals, !PDInfo, !IO).
|
|
|
|
:- pred append_goal_to_cases(prog_var::in, hlds_goals::in,
|
|
hlds_goal::in, set(prog_var)::in, int::in, set(int)::in,
|
|
list(case)::in,list(case)::out, pd_info::in, pd_info::out,
|
|
io::di, io::uo) is det.
|
|
|
|
append_goal_to_cases(_, _, _, _, _, _, [], [], !PDInfo, !IO).
|
|
append_goal_to_cases(Var, BetweenGoals, GoalToAppend, NonLocals,
|
|
CurrCase, Branches, [case(ConsId, Goal0) | Cases0],
|
|
[case(ConsId, Goal) | Cases], !PDInfo, !IO) :-
|
|
pd_info_get_instmap(!.PDInfo, InstMap0),
|
|
pd_info_bind_var_to_functor(Var, ConsId, !PDInfo),
|
|
append_goal(Goal0, BetweenGoals, GoalToAppend, NonLocals,
|
|
CurrCase, Branches, Goal, !PDInfo, !IO),
|
|
NextCase = CurrCase + 1,
|
|
pd_info_set_instmap(InstMap0, !PDInfo),
|
|
append_goal_to_cases(Var, BetweenGoals, GoalToAppend,
|
|
NonLocals, NextCase, Branches, Cases0, Cases, !PDInfo, !IO).
|
|
|
|
:- pred append_goal(hlds_goal::in, hlds_goals::in,
|
|
hlds_goal::in, set(prog_var)::in, int::in, set(int)::in,
|
|
hlds_goal::out, pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
append_goal(Goal0, BetweenGoals, GoalToAppend0,
|
|
NonLocals0, CurrBranch, Branches, Goal, !PDInfo, !IO) :-
|
|
( set.member(CurrBranch, Branches) ->
|
|
% Unfold the call.
|
|
pd_info_get_instmap(!.PDInfo, InstMap0),
|
|
list.foldl(pd_info_update_goal, [Goal0 | BetweenGoals], !PDInfo),
|
|
deforest_goal(GoalToAppend0, GoalToAppend, !PDInfo, !IO),
|
|
pd_info_set_instmap(InstMap0, !PDInfo)
|
|
;
|
|
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.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 = 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, io::di, io::uo) is det.
|
|
|
|
deforest_call(PredId, ProcId, Args, SymName, BuiltinState, Goal0, Goal,
|
|
!PDInfo, !IO) :-
|
|
pd_info_get_proc_arg_info(!.PDInfo, ProcArgInfos),
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo),
|
|
pd_info_get_instmap(!.PDInfo, InstMap),
|
|
unqualify_name(SymName, Name),
|
|
list.length(Args, Arity),
|
|
Goal0 = GoalExpr0 - GoalInfo0,
|
|
goal_info_get_context(GoalInfo0, Context),
|
|
|
|
pd_info_get_local_term_info(!.PDInfo, LocalTermInfo0),
|
|
|
|
pd_info_get_pred_info(!.PDInfo, PredInfo),
|
|
globals.io_lookup_int_option(deforestation_size_threshold,
|
|
SizeThreshold, !IO),
|
|
pred_info_get_markers(PredInfo, CallerMarkers),
|
|
(
|
|
% Check for extra information to the call.
|
|
map.search(ProcArgInfos, proc(PredId, ProcId), ProcArgInfo),
|
|
ProcArgInfo = pd_branch_info(_, LeftArgs, _),
|
|
set.member(LeftArg, LeftArgs),
|
|
list.index1_det(Args, LeftArg, Arg),
|
|
instmap.lookup_var(InstMap, Arg, ArgInst),
|
|
inst_is_bound_to_functors(ModuleInfo, ArgInst, [_]),
|
|
|
|
% We don't attempt to deforest predicates which are
|
|
% promised pure 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.
|
|
InlinePromisedPure = no,
|
|
inlining.can_inline_proc(PredId, ProcId, BuiltinState,
|
|
InlinePromisedPure, CallerMarkers, ModuleInfo),
|
|
|
|
% 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
|
|
)
|
|
->
|
|
pd_debug_message(Context,
|
|
"Found extra information for call to %s/%i\n",
|
|
[s(Name), i(Arity)], !IO),
|
|
(
|
|
pd_term.local_check(ModuleInfo, Goal0, InstMap,
|
|
LocalTermInfo0, LocalTermInfo)
|
|
->
|
|
pd_debug_message("Local termination check succeeded\n", [], !IO),
|
|
pd_info_set_local_term_info(LocalTermInfo, !PDInfo),
|
|
unfold_call(yes, yes, PredId, ProcId,
|
|
Args, Goal0, Goal1, Optimized, !PDInfo, !IO),
|
|
(
|
|
Optimized = yes,
|
|
deforest_goal(Goal1, Goal, !PDInfo, !IO)
|
|
;
|
|
Optimized = no,
|
|
Goal = Goal1
|
|
),
|
|
pd_info_set_local_term_info(LocalTermInfo0, !PDInfo)
|
|
;
|
|
pd_debug_message("Local termination check failed\n", [], !IO),
|
|
Goal = GoalExpr0 - GoalInfo0
|
|
)
|
|
;
|
|
pd_debug_message(Context, "No extra information for call to %s/%i\n",
|
|
[s(Name), i(Arity)], !IO),
|
|
Goal = Goal0
|
|
).
|
|
|
|
:- pred unfold_call(bool::in, bool::in, pred_id::in, proc_id::in,
|
|
list(prog_var)::in, hlds_goal::in, hlds_goal::out, bool::out,
|
|
pd_info::in, pd_info::out, io::di, io::uo) is det.
|
|
|
|
unfold_call(CheckImprovement, CheckVars, PredId, ProcId, Args,
|
|
Goal0, Goal, Optimized, !PDInfo, !IO) :-
|
|
globals.io_lookup_int_option(deforestation_vars_threshold, VarsOpt, !IO),
|
|
pd_info_get_proc_info(!.PDInfo, ProcInfo0),
|
|
proc_info_get_varset(ProcInfo0, VarSet0),
|
|
varset.vars(VarSet0, Vars),
|
|
list.length(Vars, NumVars),
|
|
(
|
|
% Check that we haven't already got too many variables.
|
|
(
|
|
CheckVars = no
|
|
;
|
|
VarsOpt = -1
|
|
;
|
|
VarsOpt = MaxVars,
|
|
NumVars < MaxVars
|
|
)
|
|
->
|
|
pd_info_get_pred_info(!.PDInfo, PredInfo0),
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo0),
|
|
module_info_pred_proc_info(ModuleInfo0, PredId, ProcId,
|
|
CalledPredInfo, CalledProcInfo),
|
|
pred_info_get_typevarset(PredInfo0, TypeVarSet0),
|
|
pred_info_get_univ_quant_tvars(PredInfo0, UnivQVars),
|
|
proc_info_get_vartypes(ProcInfo0, VarTypes0),
|
|
proc_info_get_rtti_varmaps(ProcInfo0, RttiVarMaps0),
|
|
inlining.do_inline_call(UnivQVars, Args, CalledPredInfo,
|
|
CalledProcInfo, VarSet0, VarSet, VarTypes0, VarTypes,
|
|
TypeVarSet0, TypeVarSet, RttiVarMaps0, RttiVarMaps, Goal1),
|
|
pred_info_set_typevarset(TypeVarSet, PredInfo0, PredInfo),
|
|
proc_info_set_varset(VarSet, ProcInfo0, ProcInfo1),
|
|
proc_info_set_vartypes(VarTypes, ProcInfo1, ProcInfo2),
|
|
proc_info_set_rtti_varmaps(RttiVarMaps, ProcInfo2, ProcInfo),
|
|
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 = _ - GoalInfo1,
|
|
goal_info_get_nonlocals(GoalInfo1, NonLocals1),
|
|
set.list_to_set(Args, NonLocals),
|
|
( \+ set.equal(NonLocals1, NonLocals) ->
|
|
pd_util.requantify_goal(NonLocals, Goal1, Goal2, !PDInfo)
|
|
;
|
|
Goal2 = Goal1
|
|
),
|
|
|
|
% Push the extra information from the call through the goal.
|
|
pd_debug_message("Running unique modes\n", [], !IO),
|
|
proc_info_arglives(CalledProcInfo, ModuleInfo0, ArgLives),
|
|
get_live_vars(Args, ArgLives, LiveVars0),
|
|
set.list_to_set(LiveVars0, LiveVars1),
|
|
set.intersect(NonLocals, LiveVars1, LiveVars),
|
|
pd_util.unique_modecheck_goal(LiveVars, Goal2, Goal3, Errors,
|
|
!PDInfo, !IO),
|
|
|
|
(
|
|
Errors = [],
|
|
Optimized0 = yes
|
|
;
|
|
Errors = [_ | _],
|
|
% This can happen because common.m does not maintain unique mode
|
|
% correctness. This should eventually be fixed.
|
|
Optimized0 = no
|
|
),
|
|
|
|
pd_debug_message("Running simplify\n", [], !IO),
|
|
pd_info_get_module_info(!.PDInfo, ModuleInfo),
|
|
module_info_get_globals(ModuleInfo, Globals),
|
|
simplify.find_simplifications(no, Globals, Simplifications),
|
|
pd_util.simplify_goal(Simplifications, Goal3, Goal4, !PDInfo, !IO),
|
|
|
|
pd_info_get_cost_delta(!.PDInfo, CostDelta1),
|
|
CostDelta = CostDelta1 - CostDelta0,
|
|
goal_size(Goal4, GoalSize),
|
|
SizeDelta = GoalSize - cost_of_call,
|
|
globals.io_lookup_int_option(deforestation_cost_factor, Factor, !IO),
|
|
(
|
|
Optimized0 = yes,
|
|
(
|
|
CheckImprovement = no
|
|
;
|
|
CheckImprovement = yes,
|
|
% XXX Should this test Goal4? zs
|
|
( is_simple_goal(Goal3) ->
|
|
true
|
|
;
|
|
check_improvement(Factor, GoalSize, OriginalCost,
|
|
CostDelta)
|
|
)
|
|
)
|
|
->
|
|
Goal = Goal4,
|
|
pd_debug_message("inlined: cost(%i) size(%i)\n",
|
|
[i(CostDelta), i(SizeDelta)], !IO),
|
|
pd_info_incr_size_delta(SizeDelta, !PDInfo),
|
|
pd_info_set_changed(yes, !PDInfo),
|
|
Goal0 = _ - GoalInfo0,
|
|
goal_info_get_determinism(GoalInfo0, Det0),
|
|
Goal = _ - GoalInfo,
|
|
goal_info_get_determinism(GoalInfo, Det),
|
|
|
|
% Rerun determinism analysis later if the determinism of any of
|
|
% the sub-goals changes - this avoids problems with inlining
|
|
% erroneous predicates.
|
|
( Det = Det0 ->
|
|
true
|
|
;
|
|
pd_info_set_rerun_det(yes, !PDInfo)
|
|
),
|
|
|
|
Optimized = yes
|
|
;
|
|
pd_debug_message("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
|
|
)
|
|
;
|
|
pd_debug_message("too many variables - not inlining\n", [], !IO),
|
|
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(Goal - _) :-
|
|
(
|
|
goal_is_atomic(Goal)
|
|
;
|
|
Goal = not(Goal1),
|
|
% Handle a call or builtin + tests on the output.
|
|
goal_to_conj_list(Goal1, GoalList1),
|
|
is_simple_goal_list(GoalList1)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Very rough heuristics for checking improvement. This should lean
|
|
% towards allowing optimizations.
|
|
%
|
|
:- pred check_improvement(int::in, int::in, int::in, int::in) is semidet.
|
|
|
|
check_improvement(_Factor, Size, OriginalCost, CostDelta) :-
|
|
( Size =< 5 ->
|
|
% For small increases in size, accept any amount of optimization.
|
|
CostDelta > 0
|
|
;
|
|
PercentChange = CostDelta * 100 // OriginalCost,
|
|
PercentChange >= 5
|
|
).
|
|
|
|
:- pred check_deforestation_improvement(int::in, int::in, int::in)
|
|
is semidet.
|
|
|
|
check_deforestation_improvement(Factor, CostDelta, SizeChange) :-
|
|
( SizeChange =< 5 ->
|
|
% For small increases in size, accept any amount of optimization.
|
|
CostDelta > 0
|
|
;
|
|
% 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.
|
|
ExpectedCostDelta = 1000 * cost_of_heap_incr * SizeChange // 3,
|
|
FudgedCostDelta = CostDelta * Factor,
|
|
FudgedCostDelta >= ExpectedCostDelta
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- func this_file = string.
|
|
|
|
this_file = "deforest.m".
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module deforest.
|
|
%-----------------------------------------------------------------------------%
|