mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 09:23:44 +00:00
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.
1234 lines
48 KiB
Mathematica
1234 lines
48 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%---------------------------------------------------------------------------%
|
|
% Copyright (C) 1994-2012 The University of Melbourne.
|
|
% Copyright (C) 2014-2023, 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: inlining.m.
|
|
% Main author: conway.
|
|
%
|
|
% This module inlines
|
|
%
|
|
% * (--inline-simple and --inline-simple-threshold N)
|
|
% procedures whose size is below the given threshold,
|
|
% PLUS
|
|
% procedures that are flat (i.e. contain no branched structures)
|
|
% and are composed of inline builtins (eg arithmetic),
|
|
% and whose size is less than three times the given threshold
|
|
% (XXX shouldn't hard-code 3)
|
|
%
|
|
% * (--inline-compound-threshold N)
|
|
% procedures where the product of the number of calls to them
|
|
% and their size is below a given threshold.
|
|
%
|
|
% * (--inline-single-use)
|
|
% procedures which are called only once
|
|
%
|
|
% * procedures which have a `:- pragma inline(name/arity).'
|
|
%
|
|
% It will not inline procedures which have a `:- pragma no_inline(name/arity).'
|
|
%
|
|
% If inlining a procedure takes the total number of variables over a given
|
|
% threshold (from a command-line option), then the procedure is not inlined
|
|
% - note that this means that some calls to a procedure may inlined while
|
|
% others are not.
|
|
%
|
|
% It builds the call-graph (if necessary) works from the bottom of the
|
|
% call-graph towards the top, first performing inlining on a procedure,
|
|
% then deciding if calls to it (higher in the call-graph) should be inlined.
|
|
% SCCs get flattened and processed in the order returned by
|
|
% hlds_dependency_info_get_dependency_ordering.
|
|
%
|
|
% There are a couple of classes of procedure that we clearly want to inline
|
|
% because doing so *reduces* the size of the generated code:
|
|
%
|
|
% - access predicates that get or set one or more fields of a structure
|
|
% Inlining these is almost always a win because the infrastructure for the
|
|
% call to the procedure is almost always larger than the code to do the
|
|
% access. In the case of `get' accessors, the call usually becomes a single
|
|
% `field' expression to get the relevant field of the structure. In the case
|
|
% of `set' accessors, it is a bit more complicated since the code to copy
|
|
% the fields can be quite big if there are lots of fields. However, in the
|
|
% frequent case where several `set' accessors get called one after the other,
|
|
% inlining them all enables the code generator to avoid creating all the
|
|
% intermediate structures, which is usually a significant win.
|
|
%
|
|
% - arithmetic predicates where as above, the cost of the call will often
|
|
% outweigh the cost of the arithmetic.
|
|
%
|
|
% - det or semidet foreign_proc code, where the foreign code is often
|
|
% very small; inlining avoids a call and allows the target language
|
|
% compiler to do a better job of optimizing it.
|
|
%
|
|
% The threshold on the size of simple goals (which covers both of the first
|
|
% two cases above), is to prevent the inlining of large goals such as those
|
|
% that construct big terms where the duplication is usually inappropriate
|
|
% (for example in nrev).
|
|
%
|
|
% The threshold on the number of variables in a procedure is to prevent the
|
|
% problem of inlining lots of calls and having a resulting procedure with so
|
|
% many variables that the back end of the compiler gets bogged down (for
|
|
% example in the pseudoknot benchmark).
|
|
%
|
|
% Due to the way in which we generate code for model_non pragma_foreign_code,
|
|
% procedures whose body is such a pragma_foreign_code must NOT be inlined.
|
|
%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- module transform_hlds.inlining.
|
|
:- interface.
|
|
|
|
:- import_module hlds.
|
|
:- import_module hlds.hlds_clauses.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module hlds.hlds_module.
|
|
:- import_module hlds.hlds_pred.
|
|
:- import_module hlds.hlds_rtti.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.prog_data.
|
|
:- import_module parse_tree.var_table.
|
|
|
|
:- import_module io.
|
|
:- import_module list.
|
|
:- import_module map.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred inline_in_module(io.text_output_stream::in,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
% This heuristic is used for both local and intermodule inlining.
|
|
% XXX No, it isn't; it is not used in this module.
|
|
% The reason why I (zs) haven't moved it to intermod.m is that
|
|
% making this module and intermod.m use separate tests for what is
|
|
% inlineable is *not* the proper fix.
|
|
%
|
|
:- pred is_simple_clause_list(list(clause)::in, int::in) is semidet.
|
|
|
|
:- pred is_simple_goal(hlds_goal::in, int::in) is semidet.
|
|
|
|
% do_inline_call(ModuleInfo, UnivQVars, Context,
|
|
% CalleePredInfo, CalleeProcInfo, Args, Goal,
|
|
% !TVarSet, !VarTable, !RttiVarMaps):
|
|
%
|
|
% Given the universally quantified type variables in the caller's type,
|
|
% the pred_info and proc_info for the called procedure, the context
|
|
% and arguments to the call, and various information about the variables
|
|
% and types in the procedure currently being analysed, rename the goal
|
|
% for the called procedure so that it can be inlined.
|
|
% ZZZ
|
|
%
|
|
:- pred do_inline_call(module_info::in, list(tvar)::in, prog_context::in,
|
|
pred_info::in, proc_info::in, list(prog_var)::in, hlds_goal::out,
|
|
tvarset::in, tvarset::out, var_table::in, var_table::out,
|
|
rtti_varmaps::in, rtti_varmaps::out) is det.
|
|
|
|
% rename_goal(CalledProcHeadVars, CallArgs,
|
|
% CallerVarTypes0, CalleeVarTypes, CallerVarTypes,
|
|
% VarRenaming, CalledGoal, RenamedGoal).
|
|
%
|
|
:- pred rename_goal(list(prog_var)::in, list(prog_var)::in,
|
|
var_table::in, var_table::in, var_table::out,
|
|
map(prog_var, prog_var)::out, hlds_goal::in, hlds_goal::out) is det.
|
|
|
|
:- type may_inline_purity_promised_pred
|
|
---> may_not_inline_purity_promised_pred
|
|
; may_inline_purity_promised_pred.
|
|
|
|
% can_inline_proc(ModuleInfo, PredId, ProcId, BuiltinState,
|
|
% InlinePromisedPure):
|
|
%
|
|
% Determine whether a call to the given predicate can be inlined.
|
|
%
|
|
:- pred can_inline_proc(module_info::in, pred_id::in, proc_id::in,
|
|
builtin_state::in, may_inline_purity_promised_pred::in) is semidet.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module check_hlds.
|
|
:- import_module check_hlds.det_analysis.
|
|
:- import_module check_hlds.purity.
|
|
:- import_module check_hlds.recompute_instmap_deltas.
|
|
:- import_module hlds.goal_refs.
|
|
:- import_module hlds.goal_util.
|
|
:- import_module hlds.hlds_dependency_graph.
|
|
:- import_module hlds.hlds_markers.
|
|
:- import_module hlds.hlds_proc_util.
|
|
:- import_module hlds.passes_aux.
|
|
:- import_module hlds.pred_name.
|
|
:- import_module hlds.quantification.
|
|
:- import_module hlds.type_util.
|
|
:- 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.prog_data_foreign.
|
|
:- import_module parse_tree.prog_data_pragma.
|
|
:- import_module parse_tree.prog_type.
|
|
:- import_module parse_tree.prog_type_unify.
|
|
:- import_module parse_tree.set_of_var.
|
|
:- import_module transform_hlds.complexity.
|
|
:- import_module transform_hlds.dead_proc_elim.
|
|
|
|
:- import_module bool.
|
|
:- import_module int.
|
|
:- import_module maybe.
|
|
:- import_module multi_map.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
:- import_module set.
|
|
:- import_module term.
|
|
:- import_module varset.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% This structure holds the paramaters that direct the details of the
|
|
% inlining process. Most (but not all) of these fields hold the
|
|
% values of compiler invocation options.
|
|
%
|
|
:- type inline_params
|
|
---> inline_params(
|
|
ip_progress_stream :: io.text_output_stream,
|
|
ip_simple :: maybe_inline_simple,
|
|
ip_single_use :: maybe_inline_single_use,
|
|
|
|
ip_highlevel_code :: bool,
|
|
|
|
ip_call_cost :: int,
|
|
ip_compound_size_threshold :: int,
|
|
ip_simple_goal_threshold :: int,
|
|
ip_var_threshold :: int,
|
|
|
|
ip_needed_map :: needed_map
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% inline_info contains
|
|
%
|
|
% - the information we need as we process a goal for inlining, and
|
|
% - the information that is changed as a result of inlining.
|
|
%
|
|
% It is threaded through all the code that does inlining in procedure
|
|
% bodies, updated when necessary. When the process is done, each of the
|
|
% updateable fields should be acted upon, either by putting the updated
|
|
% value of the field back where it came from, or by performing the action
|
|
% that a flag calls for if it is set.
|
|
%
|
|
:- type inline_info
|
|
---> inline_info(
|
|
% The static fields.
|
|
i_module_info :: module_info,
|
|
|
|
% Variable threshold for inlining.
|
|
i_var_threshold :: int,
|
|
|
|
% Highlevel_code option.
|
|
i_highlevel_code :: bool,
|
|
|
|
% Universally quantified type vars occurring in the argument
|
|
% types for this predicate (the caller, not the callee).
|
|
% These are the ones that must not be bound.
|
|
i_univ_caller_tvars :: list(tvar),
|
|
|
|
% Markers for the current predicate.
|
|
% i_pred_markers :: pred_markers,
|
|
|
|
% The set of procedures in the current SCC, tail calls
|
|
% to which should be inlined.
|
|
i_should_inline_tail_calls :: set(pred_proc_id),
|
|
|
|
% The fields we can update between different inlining passes
|
|
% on a procedure body.
|
|
|
|
i_should_inline_procs :: set(pred_proc_id),
|
|
|
|
% The fields we can update while doing inlining in a goal.
|
|
|
|
i_tvarset :: tvarset,
|
|
i_var_table :: var_table,
|
|
|
|
% Information about locations of type_infos and
|
|
% typeclass_infos.
|
|
i_rtti_varmaps :: rtti_varmaps,
|
|
|
|
% Did we do any inlining in the proc?
|
|
i_done_any_inlining :: have_we_inlined,
|
|
|
|
% Did we inline any procs for which
|
|
% proc_info_get_has_parallel_conj returns `has_parallel_conj'?
|
|
i_inlined_parallel :: have_we_inlined_parallel_conj,
|
|
|
|
% Did we change the determinism of any subgoal?
|
|
i_changed_detism :: have_we_changed_detism,
|
|
|
|
% Did we change the purity of any subgoal?
|
|
i_changed_purity :: have_we_changed_purity
|
|
).
|
|
|
|
:- type have_we_inlined
|
|
---> we_have_not_inlined
|
|
; we_have_inlined.
|
|
|
|
:- type have_we_inlined_parallel_conj
|
|
---> we_have_not_inlined_parallel_conj
|
|
; we_have_inlined_parallel_conj.
|
|
|
|
:- type have_we_changed_detism
|
|
---> have_not_changed_detism
|
|
; may_have_changed_detism.
|
|
|
|
:- type have_we_changed_purity
|
|
---> have_not_changed_purity
|
|
; have_changed_purity.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
inline_in_module(ProgressStream, !ModuleInfo) :-
|
|
% Package up all the inlining options
|
|
% - whether to inline simple conj's of builtins
|
|
% - whether to inline predicates that are only called once
|
|
% - the threshold for determining whether to inline more complicated goals
|
|
% - the threshold for determining whether to inline the simple conj's
|
|
% - the upper limit on the number of variables we want in procedures;
|
|
% if inlining a procedure would cause the number of variables to exceed
|
|
% this threshold then we don't inline it.
|
|
% - whether we're in an MLDS grade
|
|
|
|
module_info_get_globals(!.ModuleInfo, Globals),
|
|
globals.get_opt_tuple(Globals, OptTuple),
|
|
Simple = OptTuple ^ ot_inline_simple,
|
|
SingleUse = OptTuple ^ ot_inline_single_use,
|
|
CallCost = OptTuple ^ ot_inline_call_cost,
|
|
CompoundThreshold = OptTuple ^ ot_inline_compound_threshold,
|
|
SimpleThreshold = OptTuple ^ ot_inline_simple_threshold,
|
|
VarThreshold = OptTuple ^ ot_inline_vars_threshold,
|
|
globals.lookup_bool_option(Globals, highlevel_code, HighLevelCode),
|
|
|
|
% Get the usage counts for predicates (but only if needed, i.e. only if
|
|
% --inline-single-use or --inline-compound-threshold has been specified).
|
|
( if
|
|
( SingleUse = inline_single_use
|
|
; CompoundThreshold > 0
|
|
)
|
|
then
|
|
dead_proc_analyze(!.ModuleInfo, NeededMap)
|
|
else
|
|
map.init(NeededMap)
|
|
),
|
|
Params = inline_params(ProgressStream, Simple, SingleUse, HighLevelCode,
|
|
CallCost, CompoundThreshold, SimpleThreshold, VarThreshold, NeededMap),
|
|
|
|
% Build the call graph and extract the list of SCCs. We process
|
|
% SCCs bottom up, so that if a caller wants to inline a callee
|
|
% in a lower SCC, it gets the *already optimized* version of the callee.
|
|
% We don't try to do anything special about calls where the callee
|
|
% is in the *same* SCC as the caller.
|
|
|
|
module_info_ensure_dependency_info(!ModuleInfo, DepInfo),
|
|
get_bottom_up_sccs_with_entry_points(!.ModuleInfo, DepInfo,
|
|
BottomUpSCCsEntryPoints),
|
|
set.init(ShouldInlineProcs0),
|
|
inline_in_sccs(ProgressStream, Params, BottomUpSCCsEntryPoints,
|
|
ShouldInlineProcs0, !ModuleInfo),
|
|
|
|
% The dependency graph is now out of date and needs to be rebuilt.
|
|
module_info_clobber_dependency_info(!ModuleInfo).
|
|
|
|
:- pred inline_in_sccs(io.text_output_stream::in, inline_params::in,
|
|
list(scc_with_entry_points)::in, set(pred_proc_id)::in,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
inline_in_sccs(_ProgressStream, _Params, [], _ShouldInlineProcs, !ModuleInfo).
|
|
inline_in_sccs(ProgressStream, Params, [SCCEntryPoints | SCCsEntryPoints],
|
|
!.ShouldInlineProcs, !ModuleInfo) :-
|
|
inline_in_scc(ProgressStream, Params, SCCEntryPoints,
|
|
!ShouldInlineProcs, !ModuleInfo),
|
|
inline_in_sccs(ProgressStream, Params, SCCsEntryPoints,
|
|
!.ShouldInlineProcs, !ModuleInfo).
|
|
|
|
:- pred inline_in_scc(io.text_output_stream::in, inline_params::in,
|
|
scc_with_entry_points::in,
|
|
set(pred_proc_id)::in, set(pred_proc_id)::out,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
inline_in_scc(ProgressStream, Params, SCCEntryPoints,
|
|
!ShouldInlineProcs, !ModuleInfo) :-
|
|
SCCEntryPoints =
|
|
scc_with_entry_points(SCC, _CalledFromHigherSCCs, _Exported),
|
|
SCCProcs = set.to_sorted_list(SCC),
|
|
(
|
|
SCCProcs = [],
|
|
unexpected($pred, "empty SCC")
|
|
;
|
|
SCCProcs = [SCCProc],
|
|
inline_in_proc_if_allowed(Params, !.ShouldInlineProcs,
|
|
set.init, SCCProc, !ModuleInfo),
|
|
maybe_mark_proc_to_be_inlined(ProgressStream, Params, !.ModuleInfo,
|
|
SCCProc, !ShouldInlineProcs)
|
|
;
|
|
SCCProcs = [_, _ | _],
|
|
inline_in_simple_non_singleton_scc(ProgressStream, Params,
|
|
SCCProcs, !ShouldInlineProcs, !ModuleInfo)
|
|
).
|
|
|
|
:- pred inline_in_simple_non_singleton_scc(io.text_output_stream::in,
|
|
inline_params::in, list(pred_proc_id)::in,
|
|
set(pred_proc_id)::in, set(pred_proc_id)::out,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
inline_in_simple_non_singleton_scc(ProgressStream, Params, SCCProcs,
|
|
!ShouldInlineProcs, !ModuleInfo) :-
|
|
% We decide whether to inline *any* of the SCC's procedures *before*
|
|
% we process any of them, so we can apply the results of the decision
|
|
% to *all* of them.
|
|
%
|
|
% This mostly means inlining two kinds of SCC members in other SCC members:
|
|
%
|
|
% - procedures whose definition is trivial, such as a call to a higher
|
|
% order predicate or function such as list.map or list.foldl, specifying
|
|
% a closure involving another member of the SCC as the higher order
|
|
% value; and
|
|
%
|
|
% - procedures that are only called from one call site.
|
|
%
|
|
list.filter(
|
|
should_proc_be_inlined(Params, !.ModuleInfo),
|
|
SCCProcs, ShouldInlineSCCProcs),
|
|
list.foldl(
|
|
mark_proc_to_be_inlined(ProgressStream, !.ModuleInfo),
|
|
ShouldInlineSCCProcs, !ShouldInlineProcs),
|
|
list.foldl(
|
|
inline_in_proc_if_allowed(Params, !.ShouldInlineProcs, set.init),
|
|
SCCProcs, !ModuleInfo).
|
|
|
|
% This predicate effectively adds implicit `pragma inline' directives
|
|
% for procedures that match its heuristic.
|
|
%
|
|
:- pred maybe_mark_proc_to_be_inlined(io.text_output_stream::in,
|
|
inline_params::in, module_info::in, pred_proc_id::in,
|
|
set(pred_proc_id)::in, set(pred_proc_id)::out) is det.
|
|
|
|
maybe_mark_proc_to_be_inlined(ProgressStream, Params, ModuleInfo, PredProcId,
|
|
!ShouldInlineProcs) :-
|
|
( if should_proc_be_inlined(Params, ModuleInfo, PredProcId) then
|
|
mark_proc_to_be_inlined(ProgressStream, ModuleInfo, PredProcId,
|
|
!ShouldInlineProcs)
|
|
else
|
|
true
|
|
).
|
|
|
|
:- pred mark_proc_to_be_inlined(io.text_output_stream::in, module_info::in,
|
|
pred_proc_id::in, set(pred_proc_id)::in, set(pred_proc_id)::out) is det.
|
|
|
|
mark_proc_to_be_inlined(ProgressStream, ModuleInfo, PredProcId,
|
|
!ShouldInlineProcs) :-
|
|
set.insert(PredProcId, !ShouldInlineProcs),
|
|
trace [io(!IO)] (
|
|
maybe_write_proc_progress_message(ProgressStream, ModuleInfo,
|
|
"Inlining", PredProcId, !IO)
|
|
).
|
|
|
|
:- pred should_proc_be_inlined(inline_params::in, module_info::in,
|
|
pred_proc_id::in) is semidet.
|
|
|
|
should_proc_be_inlined(Params, ModuleInfo, PredProcId) :-
|
|
module_info_pred_proc_info(ModuleInfo, PredProcId, PredInfo, ProcInfo),
|
|
proc_info_get_goal(ProcInfo, CalledGoal),
|
|
Entity = entity_proc(PredProcId),
|
|
|
|
% The heuristic represented by the following code could be improved.
|
|
(
|
|
Params ^ ip_simple = inline_simple,
|
|
SimpleThreshold = Params ^ ip_simple_goal_threshold,
|
|
is_simple_goal(CalledGoal, SimpleThreshold)
|
|
;
|
|
NeededMap = Params ^ ip_needed_map,
|
|
map.search(NeededMap, Entity, Needed),
|
|
Needed = maybe_eliminable(NumUses),
|
|
goal_size(CalledGoal, Size),
|
|
% The size increase due to inlining at a call site is not Size,
|
|
% but the difference between Size and the size of the call.
|
|
% CallCost is the user-provided approximation of the size of the call.
|
|
CallCost = Params ^ ip_call_cost,
|
|
CompoundThreshold = Params ^ ip_compound_size_threshold,
|
|
CompoundThreshold > 0,
|
|
(Size - CallCost) * NumUses =< CompoundThreshold
|
|
;
|
|
Params ^ ip_single_use = inline_single_use,
|
|
NeededMap = Params ^ ip_needed_map,
|
|
map.search(NeededMap, Entity, Needed),
|
|
Needed = maybe_eliminable(NumUses),
|
|
NumUses = 1
|
|
),
|
|
% Don't inline directly recursive predicates unless explicitly requested.
|
|
not goal_calls(CalledGoal, PredProcId),
|
|
|
|
pred_info_get_origin(PredInfo, Origin),
|
|
origin_involves_daio(Origin, does_not_involve_daio).
|
|
|
|
is_simple_clause_list(Clauses, SimpleThreshold) :-
|
|
clause_list_size(Clauses, Size),
|
|
(
|
|
Size < SimpleThreshold
|
|
;
|
|
Clauses = [Clause],
|
|
Goal = Clause ^ clause_body,
|
|
Size < SimpleThreshold * 3,
|
|
|
|
% For flat goals, we are more likely to be able to optimize stuff away,
|
|
% so we use a higher threshold.
|
|
% XXX This should be a separate option, we shouldn't hardcode
|
|
% the number `3' (which is just a guess).
|
|
|
|
is_flat_simple_goal(Goal)
|
|
).
|
|
|
|
is_simple_goal(CalledGoal, SimpleThreshold) :-
|
|
goal_size(CalledGoal, Size),
|
|
(
|
|
Size < SimpleThreshold
|
|
;
|
|
% For flat goals, we are more likely to be able to optimize stuff away,
|
|
% so we use a higher threshold.
|
|
% XXX this should be a separate option, we shouldn't hardcode
|
|
% the number `3' (which is just a guess).
|
|
|
|
Size < SimpleThreshold * 3,
|
|
is_flat_simple_goal(CalledGoal)
|
|
).
|
|
|
|
:- pred is_flat_simple_goal(hlds_goal::in) is semidet.
|
|
|
|
is_flat_simple_goal(hlds_goal(GoalExpr, _)) :-
|
|
(
|
|
GoalExpr = conj(plain_conj, Goals),
|
|
is_flat_simple_goal_list(Goals)
|
|
;
|
|
GoalExpr = negation(Goal),
|
|
is_flat_simple_goal(Goal)
|
|
;
|
|
GoalExpr = scope(Reason, Goal),
|
|
( if
|
|
Reason = from_ground_term(_, FGT),
|
|
( FGT = from_ground_term_construct
|
|
; FGT = from_ground_term_deconstruct
|
|
)
|
|
then
|
|
% These scopes are flat and simple by construction.
|
|
true
|
|
else
|
|
is_flat_simple_goal(Goal)
|
|
)
|
|
;
|
|
GoalExpr = plain_call(_, _, _, inline_builtin, _, _)
|
|
;
|
|
GoalExpr = unify(_, _, _, _, _)
|
|
).
|
|
|
|
:- pred is_flat_simple_goal_list(list(hlds_goal)::in) is semidet.
|
|
|
|
is_flat_simple_goal_list([]).
|
|
is_flat_simple_goal_list([Goal | Goals]) :-
|
|
is_flat_simple_goal(Goal),
|
|
is_flat_simple_goal_list(Goals).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred inline_in_proc_if_allowed(inline_params::in,
|
|
set(pred_proc_id)::in, set(pred_proc_id)::in, pred_proc_id::in,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
inline_in_proc_if_allowed(Params, ShouldInlineProcs, ShouldInlineTailProcs,
|
|
PredProcId, !ModuleInfo) :-
|
|
PredProcId = proc(PredId, _ProcId),
|
|
module_info_pred_info(!.ModuleInfo, PredId, PredInfo),
|
|
pred_info_get_origin(PredInfo, Origin),
|
|
origin_involves_daio(Origin, InvolvesDAIO),
|
|
(
|
|
InvolvesDAIO = does_not_involve_daio,
|
|
inline_in_proc(Params, ShouldInlineProcs, ShouldInlineTailProcs,
|
|
PredProcId, !ModuleInfo)
|
|
;
|
|
InvolvesDAIO = does_involve_daio
|
|
).
|
|
|
|
:- pred inline_in_proc(inline_params::in,
|
|
set(pred_proc_id)::in, set(pred_proc_id)::in, pred_proc_id::in,
|
|
module_info::in, module_info::out) is det.
|
|
|
|
inline_in_proc(Params, ShouldInlineProcs, ShouldInlineTailProcs, PredProcId,
|
|
!ModuleInfo) :-
|
|
some [!PredInfo, !ProcInfo] (
|
|
VarThresh = Params ^ ip_var_threshold,
|
|
HighLevelCode = Params ^ ip_highlevel_code,
|
|
|
|
PredProcId = proc(PredId, ProcId),
|
|
|
|
module_info_pred_info(!.ModuleInfo, PredId, !:PredInfo),
|
|
pred_info_proc_info(!.PredInfo, ProcId, !:ProcInfo),
|
|
|
|
pred_info_get_univ_quant_tvars(!.PredInfo, UnivQTVars),
|
|
pred_info_get_typevarset(!.PredInfo, TypeVarSet0),
|
|
|
|
proc_info_get_goal(!.ProcInfo, Goal0),
|
|
proc_info_get_var_table(!.ProcInfo, VarTypes0),
|
|
proc_info_get_rtti_varmaps(!.ProcInfo, RttiVarMaps0),
|
|
|
|
InlineInfo0 = inline_info(!.ModuleInfo, VarThresh, HighLevelCode,
|
|
UnivQTVars, ShouldInlineTailProcs, ShouldInlineProcs,
|
|
TypeVarSet0, VarTypes0, RttiVarMaps0,
|
|
we_have_not_inlined, we_have_not_inlined_parallel_conj,
|
|
have_not_changed_detism, have_not_changed_purity),
|
|
|
|
inlining_in_goal(Goal0, Goal, InlineInfo0, InlineInfo),
|
|
|
|
InlineInfo = inline_info(_, _, _, _, _, _,
|
|
TypeVarSet, VarTypes, RttiVarMaps,
|
|
DidInlining, InlinedParallel, DetChanged, PurityChanged),
|
|
|
|
pred_info_set_typevarset(TypeVarSet, !PredInfo),
|
|
|
|
proc_info_set_var_table(VarTypes, !ProcInfo),
|
|
proc_info_set_rtti_varmaps(RttiVarMaps, !ProcInfo),
|
|
proc_info_set_goal(Goal, !ProcInfo),
|
|
|
|
(
|
|
InlinedParallel = we_have_inlined_parallel_conj,
|
|
proc_info_set_has_parallel_conj(has_parallel_conj, !ProcInfo)
|
|
;
|
|
InlinedParallel = we_have_not_inlined_parallel_conj
|
|
),
|
|
|
|
(
|
|
DidInlining = we_have_inlined,
|
|
% We want to requantify the procedure body if we did any inlining.
|
|
% If the body of an inlined call did not use some of the
|
|
% call's input arg vars, and this was the only use of the
|
|
% corresponding caller variables, this will tell the simplification
|
|
% pass we invoke before code generation that the goal(s) that
|
|
% generate those caller variables can be optimized away.
|
|
requantify_proc_general(ord_nl_no_lambda, !ProcInfo),
|
|
recompute_instmap_delta_proc(recomp_atomics,
|
|
!ProcInfo, !ModuleInfo)
|
|
;
|
|
DidInlining = we_have_not_inlined
|
|
),
|
|
|
|
pred_info_set_proc_info(ProcId, !.ProcInfo, !PredInfo),
|
|
|
|
(
|
|
PurityChanged = have_changed_purity,
|
|
repuritycheck_proc(!.ModuleInfo, PredProcId, !PredInfo)
|
|
;
|
|
PurityChanged = have_not_changed_purity
|
|
),
|
|
|
|
module_info_set_pred_info(PredId, !.PredInfo, !ModuleInfo),
|
|
|
|
% If the determinism of some subgoals has changed, then we rerun
|
|
% determinism analysis, because propagating the determinism information
|
|
% through the procedure may lead to more efficient code.
|
|
(
|
|
DetChanged = may_have_changed_detism,
|
|
ProgressStream = Params ^ ip_progress_stream,
|
|
det_infer_proc_ignore_msgs(ProgressStream, PredId, ProcId,
|
|
!ModuleInfo)
|
|
;
|
|
DetChanged = have_not_changed_detism
|
|
)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred inlining_in_goal(hlds_goal::in, hlds_goal::out,
|
|
inline_info::in, inline_info::out) is det.
|
|
|
|
inlining_in_goal(Goal0, Goal, !Info) :-
|
|
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
|
|
(
|
|
GoalExpr0 = plain_call(_, _, _, _, _, _),
|
|
inlining_in_call(GoalExpr0, GoalInfo0, Goal, !Info)
|
|
;
|
|
( GoalExpr0 = generic_call(_, _, _, _, _)
|
|
; GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _)
|
|
; GoalExpr0 = unify(_, _, _, _, _)
|
|
),
|
|
Goal = Goal0
|
|
;
|
|
GoalExpr0 = conj(ConjType, Goals0),
|
|
(
|
|
ConjType = plain_conj,
|
|
inlining_in_conj(Goals0, Goals, !Info)
|
|
;
|
|
ConjType = parallel_conj,
|
|
inlining_in_par_conj(Goals0, Goals, !Info)
|
|
),
|
|
GoalExpr = conj(ConjType, Goals),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
;
|
|
GoalExpr0 = disj(Goals0),
|
|
inlining_in_disjuncts(Goals0, Goals, !Info),
|
|
GoalExpr = disj(Goals),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
;
|
|
GoalExpr0 = switch(Var, Det, Cases0),
|
|
inlining_in_cases(Cases0, Cases, !Info),
|
|
GoalExpr = switch(Var, Det, Cases),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
;
|
|
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
|
|
inlining_in_goal(Cond0, Cond, !Info),
|
|
inlining_in_goal(Then0, Then, !Info),
|
|
inlining_in_goal(Else0, Else, !Info),
|
|
GoalExpr = if_then_else(Vars, Cond, Then, Else),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
;
|
|
GoalExpr0 = negation(SubGoal0),
|
|
inlining_in_goal(SubGoal0, SubGoal, !Info),
|
|
GoalExpr = negation(SubGoal),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
;
|
|
GoalExpr0 = scope(Reason, SubGoal0),
|
|
( if
|
|
Reason = from_ground_term(_, FGT),
|
|
( FGT = from_ground_term_construct
|
|
; FGT = from_ground_term_deconstruct
|
|
)
|
|
then
|
|
% The scope has no calls to inline.
|
|
Goal = Goal0
|
|
else
|
|
inlining_in_goal(SubGoal0, SubGoal, !Info),
|
|
GoalExpr = scope(Reason, SubGoal),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo0)
|
|
)
|
|
;
|
|
GoalExpr0 = shorthand(_),
|
|
% These should have been expanded out by now.
|
|
unexpected($pred, "shorthand")
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred inlining_in_disjuncts(list(hlds_goal)::in, list(hlds_goal)::out,
|
|
inline_info::in, inline_info::out) is det.
|
|
|
|
inlining_in_disjuncts([], [], !Info).
|
|
inlining_in_disjuncts([Goal0 | Goals0], [Goal | Goals], !Info) :-
|
|
inlining_in_goal(Goal0, Goal, !Info),
|
|
inlining_in_disjuncts(Goals0, Goals, !Info).
|
|
|
|
:- pred inlining_in_cases(list(case)::in, list(case)::out,
|
|
inline_info::in, inline_info::out) is det.
|
|
|
|
inlining_in_cases([], [], !Info).
|
|
inlining_in_cases([Case0 | Cases0], [Case | Cases], !Info) :-
|
|
Case0 = case(MainConsId, OtherConsIds, Goal0),
|
|
inlining_in_goal(Goal0, Goal, !Info),
|
|
Case = case(MainConsId, OtherConsIds, Goal),
|
|
inlining_in_cases(Cases0, Cases, !Info).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred inlining_in_conj(list(hlds_goal)::in, list(hlds_goal)::out,
|
|
inline_info::in, inline_info::out) is det.
|
|
|
|
inlining_in_conj([], [], !Info).
|
|
inlining_in_conj([HeadGoal0 | TailGoals0], Goals, !Info) :-
|
|
inlining_in_goal(HeadGoal0, HeadGoal, !Info),
|
|
inlining_in_conj(TailGoals0, TailGoals, !Info),
|
|
% Since a single goal may become a conjunction,
|
|
% we flatten the conjunction as we go.
|
|
goal_to_conj_list(HeadGoal, HeadGoalList),
|
|
Goals = HeadGoalList ++ TailGoals.
|
|
|
|
:- pred inlining_in_par_conj(list(hlds_goal)::in, list(hlds_goal)::out,
|
|
inline_info::in, inline_info::out) is det.
|
|
|
|
inlining_in_par_conj([], [], !Info).
|
|
inlining_in_par_conj([HeadGoal0 | TailGoals0], Goals, !Info) :-
|
|
inlining_in_goal(HeadGoal0, HeadGoal, !Info),
|
|
inlining_in_par_conj(TailGoals0, TailGoals, !Info),
|
|
% Since a single goal may become a parallel conjunction,
|
|
% we flatten the conjunction as we go.
|
|
% Note that this is *much* less likely to happen for parallel conjunctions
|
|
% than for sequential ones.
|
|
goal_to_par_conj_list(HeadGoal, HeadGoalList),
|
|
Goals = HeadGoalList ++ TailGoals.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred inlining_in_call(hlds_goal_expr::in(goal_expr_plain_call),
|
|
hlds_goal_info::in, hlds_goal::out,
|
|
inline_info::in, inline_info::out) is det.
|
|
|
|
inlining_in_call(GoalExpr0, GoalInfo0, Goal, !Info) :-
|
|
!.Info = inline_info(ModuleInfo, VarThresh, HighLevelCode,
|
|
ExternalTypeParams, ShouldInlineTailProcs, ShouldInlineProcs,
|
|
TypeVarSet0, VarTable0, RttiVarMaps0, _DidInlining0,
|
|
InlinedParallel0, DetChanged0, PurityChanged0),
|
|
GoalExpr0 = plain_call(CalleePredId, CalleeProcId, ArgVars, _Builtin,
|
|
_UnifyContext, _SymName),
|
|
module_info_pred_proc_info(ModuleInfo, CalleePredId, CalleeProcId,
|
|
CalleePredInfo, CalleeProcInfo),
|
|
% Should we inline this call?
|
|
should_inline_at_call_site(!.Info, GoalExpr0, GoalInfo0, ShouldInline),
|
|
( if
|
|
ShouldInline = should_inline(TailRec, UserReq),
|
|
(
|
|
UserReq = user_req
|
|
;
|
|
UserReq = not_user_req,
|
|
% Okay, but will we exceed the number-of-variables threshold?
|
|
var_table_count(VarTable0, NumVarsInVarTable),
|
|
|
|
% We need to find out how many variables the Callee has.
|
|
proc_info_get_var_table(CalleeProcInfo, CalleeVarTable),
|
|
var_table_count(CalleeVarTable, NumVarsInCallee),
|
|
TotalNumVars = NumVarsInVarTable + NumVarsInCallee,
|
|
TotalNumVars =< VarThresh
|
|
),
|
|
% XXX Work around bug #142.
|
|
not may_encounter_bug_142(CalleeProcInfo, ArgVars)
|
|
then
|
|
CallContext = goal_info_get_context(GoalInfo0),
|
|
do_inline_call(ModuleInfo, ExternalTypeParams, CallContext,
|
|
CalleePredInfo, CalleeProcInfo, ArgVars, Goal1,
|
|
TypeVarSet0, TypeVarSet, VarTable0, VarTable,
|
|
RttiVarMaps0, RttiVarMaps),
|
|
|
|
DidInlining = we_have_inlined,
|
|
|
|
proc_info_get_has_parallel_conj(CalleeProcInfo, HasParallelConj),
|
|
(
|
|
HasParallelConj = has_parallel_conj,
|
|
InlinedParallel = we_have_inlined_parallel_conj
|
|
;
|
|
HasParallelConj = has_no_parallel_conj,
|
|
InlinedParallel = InlinedParallel0
|
|
),
|
|
|
|
Goal1 = hlds_goal(_, GoalInfo1),
|
|
% If the determinism of the call is the same as the determinism
|
|
% of the callee (which it should be, unless something has changed
|
|
% since determinism analysis) *and* all the argument variables are
|
|
% used outside the call, then there is no need to rerun determinism
|
|
% analysis. We *do* have to rerun it if we have not met one of the
|
|
% above preconditions.
|
|
Determinism0 = goal_info_get_determinism(GoalInfo0),
|
|
Determinism1 = goal_info_get_determinism(GoalInfo1),
|
|
ArgVarSet = set_of_var.list_to_set(ArgVars),
|
|
NonLocals = goal_info_get_nonlocals(GoalInfo0),
|
|
( if
|
|
Determinism0 = Determinism1,
|
|
set_of_var.subset(ArgVarSet, NonLocals)
|
|
then
|
|
DetChanged = DetChanged0
|
|
else
|
|
DetChanged = may_have_changed_detism
|
|
),
|
|
|
|
Purity0 = goal_info_get_purity(GoalInfo0),
|
|
Purity1 = goal_info_get_purity(GoalInfo1),
|
|
( if Purity0 = Purity1 then
|
|
PurityChanged = PurityChanged0
|
|
else
|
|
PurityChanged = have_changed_purity
|
|
),
|
|
|
|
!:Info = inline_info(ModuleInfo, VarThresh, HighLevelCode,
|
|
ExternalTypeParams, ShouldInlineTailProcs, ShouldInlineProcs,
|
|
TypeVarSet, VarTable, RttiVarMaps, DidInlining,
|
|
InlinedParallel, DetChanged, PurityChanged),
|
|
|
|
(
|
|
TailRec = not_tail_rec,
|
|
Goal = Goal1
|
|
;
|
|
TailRec = tail_rec,
|
|
inlining_in_goal(Goal1, Goal, !Info)
|
|
)
|
|
else
|
|
Goal = hlds_goal(GoalExpr0, GoalInfo0)
|
|
).
|
|
|
|
:- pred may_encounter_bug_142(proc_info::in, list(prog_var)::in) is semidet.
|
|
|
|
may_encounter_bug_142(CalleeProcInfo, ArgVars) :-
|
|
proc_info_get_rtti_varmaps(CalleeProcInfo, RttiVarMaps),
|
|
proc_info_get_headvars(CalleeProcInfo, HeadVars),
|
|
multi_map.from_corresponding_lists(ArgVars, HeadVars, MultiMap),
|
|
some [ArgVar] (
|
|
list.member(ArgVar, ArgVars),
|
|
multi_map.lookup(MultiMap, ArgVar, HeadVarsForArgVar),
|
|
HeadVarsForArgVar = [_ | _],
|
|
tci_vars_different_constraints(RttiVarMaps, HeadVarsForArgVar)
|
|
).
|
|
|
|
:- pred tci_vars_different_constraints(rtti_varmaps::in, list(prog_var)::in)
|
|
is semidet.
|
|
|
|
tci_vars_different_constraints(RttiVarMaps, [VarA, VarB | Vars]) :-
|
|
(
|
|
rtti_varmaps_var_info(RttiVarMaps, VarA, VarInfoA),
|
|
rtti_varmaps_var_info(RttiVarMaps, VarB, VarInfoB),
|
|
VarInfoA = typeclass_info_var(ConstraintA),
|
|
VarInfoB = typeclass_info_var(ConstraintB),
|
|
ConstraintA \= ConstraintB
|
|
;
|
|
tci_vars_different_constraints(RttiVarMaps, [VarB | Vars])
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
do_inline_call(ModuleInfo, ExternalTypeParams, CallContext,
|
|
CalleePredInfo, CalleeProcInfo, ArgVars, Goal,
|
|
TypeVarSet0, TypeVarSet, VarTable0, VarTable,
|
|
RttiVarMaps0, RttiVarMaps) :-
|
|
proc_info_get_goal(CalleeProcInfo, CalleeBodyGoal),
|
|
|
|
% Look up the rest of the info for the called procedure.
|
|
|
|
pred_info_get_typevarset(CalleePredInfo, CalleeTypeVarSet),
|
|
proc_info_get_headvars(CalleeProcInfo, HeadVars),
|
|
proc_info_get_var_table(CalleeProcInfo, CalleeVarTable0),
|
|
proc_info_get_rtti_varmaps(CalleeProcInfo, CalleeRttiVarMaps0),
|
|
|
|
% Substitute the appropriate types into the type mapping of the called
|
|
% procedure. For example, if we call `:- pred foo(T)' with an argument
|
|
% of type `int', then we need to replace all occurrences of type `T'
|
|
% with type `int' when we inline it. Conversely, in the case of
|
|
% existentially typed preds, we may need to bind type variables in the
|
|
% caller. For example, if we call `:- pred some [T] foo(T)', and the
|
|
% definition of `foo' binds `T' to `int', then we need to replace all
|
|
% occurrences of type `T' with type `int' in the caller.
|
|
|
|
% First, rename apart the type variables in the callee. (We can almost
|
|
% throw away the new typevarset, since we are about to substitute away
|
|
% any new type variables, but any unbound type variables in the callee
|
|
% will not be substituted away.)
|
|
|
|
tvarset_merge_renaming(TypeVarSet0, CalleeTypeVarSet, TypeVarSet,
|
|
TypeRenaming),
|
|
rename_vars_in_var_table(TypeRenaming, CalleeVarTable0, CalleeVarTable1),
|
|
|
|
% Next, compute the type substitution and then apply it.
|
|
|
|
% Note: there is no need to update the type_info locations maps,
|
|
% either for the caller or callee, since for any type vars in the
|
|
% callee which get bound to type vars in the caller, the type_info
|
|
% location will be given by the entry in the caller's type_info
|
|
% locations map (and vice versa). It doesn't matter if the final
|
|
% type_info locations map contains some entries for type variables
|
|
% which have been substituted away, because those entries simply
|
|
% won't be used.
|
|
|
|
lookup_var_types(CalleeVarTable1, HeadVars, HeadTypes),
|
|
lookup_var_types(VarTable0, ArgVars, ArgTypes),
|
|
|
|
pred_info_get_exist_quant_tvars(CalleePredInfo, CalleeExistQVars),
|
|
compute_caller_callee_type_substitution(HeadTypes, ArgTypes,
|
|
ExternalTypeParams, CalleeExistQVars, TypeSubn),
|
|
|
|
% Update types in the callee.
|
|
apply_rec_subst_to_var_table(is_type_a_dummy(ModuleInfo), TypeSubn,
|
|
CalleeVarTable1, CalleeVarTable),
|
|
% Handle the common case of non-existentially typed preds specially,
|
|
% since we can do things more efficiently in that case.
|
|
(
|
|
CalleeExistQVars = [],
|
|
VarTable1 = VarTable0
|
|
;
|
|
CalleeExistQVars = [_ | _],
|
|
% Update types in the caller.
|
|
apply_rec_subst_to_var_table(is_type_a_dummy(ModuleInfo), TypeSubn,
|
|
VarTable0, VarTable1)
|
|
),
|
|
|
|
% Now rename apart the variables in the called goal.
|
|
rename_goal(HeadVars, ArgVars, VarTable1, CalleeVarTable, VarTable,
|
|
Subn, CalleeBodyGoal, Goal0),
|
|
goal_set_context(CallContext, Goal0, Goal),
|
|
|
|
apply_substitutions_to_rtti_varmaps(TypeRenaming, TypeSubn, Subn,
|
|
CalleeRttiVarMaps0, CalleeRttiVarMaps1),
|
|
|
|
% Prefer the type_info_locn from the caller.
|
|
% The type_infos or typeclass_infos passed to the callee may have been
|
|
% produced by extracting type_infos or typeclass_infos from
|
|
% typeclass_infos in the caller, so they won't necessarily be the same.
|
|
rtti_varmaps_overlay(CalleeRttiVarMaps1, RttiVarMaps0, RttiVarMaps).
|
|
|
|
rename_goal(HeadVars, ArgVars, VarTable0, CalleeVarTable, VarTable,
|
|
Renaming, CalledGoal, Goal) :-
|
|
map.from_corresponding_lists(HeadVars, ArgVars, Renaming0),
|
|
var_table_vars(CalleeVarTable, CalleeListOfVars),
|
|
clone_variables(CalleeListOfVars, CalleeVarTable,
|
|
VarTable0, VarTable, Renaming0, Renaming),
|
|
must_rename_vars_in_goal(Renaming, CalledGoal, Goal).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type maybe_user_req
|
|
---> not_user_req
|
|
; user_req.
|
|
|
|
:- type maybe_tail_rec
|
|
---> not_tail_rec
|
|
; tail_rec.
|
|
|
|
:- type maybe_should_inline
|
|
---> should_not_inline
|
|
; should_inline(maybe_tail_rec, maybe_user_req).
|
|
|
|
% Check to see if we should inline the callee at a call site.
|
|
%
|
|
% Returns should_not_inline if the called predicate cannot be inlined,
|
|
% e.g. because it is a builtin, we don't have code for it, etc,
|
|
% or if the callee is simply not in the set of procedures
|
|
% that we have earlier decided we should inline.
|
|
%
|
|
% Returns should_inline(TailRec, UserReq) if the called procedure
|
|
% is inlinable, and we have earlier decided that it *should* be inlined.
|
|
%
|
|
:- pred should_inline_at_call_site(inline_info::in,
|
|
hlds_goal_expr::in(goal_expr_plain_call), hlds_goal_info::in,
|
|
maybe_should_inline::out) is det.
|
|
|
|
should_inline_at_call_site(Info, GoalExpr0, GoalInfo0, ShouldInline) :-
|
|
Info = inline_info(ModuleInfo, _VarThresh, HighLevelCode,
|
|
_ExternalTypeParams, ShouldInlineTailProcs, ShouldInlineProcs,
|
|
_TypeVarSet, _VarTypes, _RttiVarMaps, _DidInlining,
|
|
_InlinedParallel, _DetChanged, _PurityChanged),
|
|
GoalExpr0 =
|
|
plain_call(PredId, ProcId, _ArgVars, Builtin, _Context, _SymName),
|
|
PredProcId = proc(PredId, ProcId),
|
|
( if
|
|
set.member(PredProcId, ShouldInlineTailProcs),
|
|
goal_info_has_feature(GoalInfo0, feature_self_or_mutual_tail_rec_call)
|
|
then
|
|
TailRec = tail_rec
|
|
else
|
|
TailRec = not_tail_rec
|
|
),
|
|
( if
|
|
can_inline_proc_2(ModuleInfo, PredId, ProcId, Builtin,
|
|
HighLevelCode, may_inline_purity_promised_pred)
|
|
then
|
|
% OK, we could inline it - but should we? Apply our heuristics.
|
|
module_info_pred_info(ModuleInfo, PredId, PredInfo),
|
|
pred_info_get_markers(PredInfo, Markers),
|
|
( if marker_is_present(Markers, marker_user_marked_inline) then
|
|
UserReq = user_req
|
|
else
|
|
UserReq = not_user_req
|
|
),
|
|
( if
|
|
( TailRec = tail_rec
|
|
; UserReq = user_req
|
|
; marker_is_present(Markers, marker_heuristic_inline)
|
|
; set.member(PredProcId, ShouldInlineProcs)
|
|
)
|
|
then
|
|
ShouldInline = should_inline(TailRec, UserReq)
|
|
else
|
|
ShouldInline = should_not_inline
|
|
)
|
|
else
|
|
ShouldInline = should_not_inline
|
|
).
|
|
|
|
can_inline_proc(ModuleInfo, PredId, ProcId, BuiltinState,
|
|
MayInlinePromisedPure) :-
|
|
module_info_get_globals(ModuleInfo, Globals),
|
|
globals.lookup_bool_option(Globals, highlevel_code, HighLevelCode),
|
|
can_inline_proc_2(ModuleInfo, PredId, ProcId, BuiltinState, HighLevelCode,
|
|
MayInlinePromisedPure).
|
|
|
|
:- pred can_inline_proc_2(module_info::in, pred_id::in, proc_id::in,
|
|
builtin_state::in, bool::in, may_inline_purity_promised_pred::in)
|
|
is semidet.
|
|
|
|
can_inline_proc_2(ModuleInfo, PredId, ProcId, BuiltinState, HighLevelCode,
|
|
MayInlinePurityPromisedPred) :-
|
|
% Don't inline builtins, the code generator will handle them.
|
|
BuiltinState = not_builtin,
|
|
module_info_pred_proc_info(ModuleInfo, PredId, ProcId, PredInfo, ProcInfo),
|
|
|
|
% Don't try to inline imported predicates, since we don't
|
|
% have the code for them.
|
|
not pred_info_is_imported(PredInfo),
|
|
|
|
% This next line catches the case of locally defined unification predicates
|
|
% for imported types.
|
|
not (
|
|
pred_info_is_pseudo_imported(PredInfo),
|
|
hlds_pred.in_in_unification_proc_id(ProcId)
|
|
),
|
|
|
|
% Only try to inline procedures which are evaluated using normal
|
|
% evaluation. Currently we can't inline procs evaluated using any of the
|
|
% other methods because the code generator for the methods can only handle
|
|
% whole procedures not code fragments.
|
|
proc_info_get_eval_method(ProcInfo, eval_normal),
|
|
|
|
% Don't inline anything we have been specifically requested not to inline.
|
|
not pred_info_requested_no_inlining(PredInfo),
|
|
|
|
% Don't inline any procedure whose complexity we are trying to determine,
|
|
% since the complexity transformation can't transform *part* of a
|
|
% procedure.
|
|
module_info_get_maybe_complexity_proc_map(ModuleInfo,
|
|
MaybeComplexityProcMap),
|
|
(
|
|
MaybeComplexityProcMap = no
|
|
;
|
|
MaybeComplexityProcMap = yes(_ - ComplexityProcMap),
|
|
IsInComplexityMap = is_in_complexity_proc_map(
|
|
ComplexityProcMap, ModuleInfo, PredId, ProcId),
|
|
IsInComplexityMap = no
|
|
),
|
|
|
|
proc_info_get_goal(ProcInfo, CalledGoal),
|
|
CalledGoal = hlds_goal(CalledGoalExpr, _),
|
|
( if
|
|
CalledGoalExpr = call_foreign_proc(ForeignAttributes, _, _, _, _, _, _)
|
|
then
|
|
% Only inline a foreign_proc if it is appropriate for the target
|
|
% language.
|
|
module_info_get_globals(ModuleInfo, Globals),
|
|
globals.get_target(Globals, Target),
|
|
(
|
|
ForeignLanguage = get_foreign_language(ForeignAttributes)
|
|
=>
|
|
ok_to_inline_language(ForeignLanguage, Target)
|
|
),
|
|
|
|
% Don't inline a foreign_proc if it is has been marked with the
|
|
% attribute that requests the code not be duplicated.
|
|
(
|
|
MaybeMayDuplicate = get_may_duplicate(ForeignAttributes)
|
|
=>
|
|
(
|
|
MaybeMayDuplicate = no
|
|
;
|
|
MaybeMayDuplicate = yes(proc_may_duplicate)
|
|
)
|
|
),
|
|
|
|
% For the LLDS back-end, under no circumstances inline model_non
|
|
% foreign_procs. The resulting code would not work properly.
|
|
% XXX We should not have any model_non foreign_procs anymore.
|
|
not (
|
|
HighLevelCode = no,
|
|
proc_info_interface_determinism(ProcInfo, Detism),
|
|
( Detism = detism_non
|
|
; Detism = detism_multi
|
|
)
|
|
)
|
|
else
|
|
true
|
|
),
|
|
|
|
(
|
|
MayInlinePurityPromisedPred = may_inline_purity_promised_pred
|
|
;
|
|
% For some optimizations (such as deforestation) we don't want to
|
|
% inline predicates which are promised pure because the extra impurity
|
|
% propagated through the goal will defeat any attempts at optimization.
|
|
%
|
|
% XXX For such procedures, we could wrap a purity promise scope
|
|
% promising the same purity around the procedure body.
|
|
MayInlinePurityPromisedPred = may_not_inline_purity_promised_pred,
|
|
pred_info_get_promised_purity(PredInfo, MaybePromisedPurity),
|
|
MaybePromisedPurity = no
|
|
).
|
|
|
|
% Succeed iff it is appropriate to inline `pragma foreign_proc'
|
|
% in the specified language for the given compilation_target.
|
|
% Generally that will only be the case if the target directly
|
|
% supports inline code in that language.
|
|
%
|
|
:- pred ok_to_inline_language(foreign_language::in, compilation_target::in)
|
|
is semidet.
|
|
|
|
ok_to_inline_language(lang_c, target_c).
|
|
ok_to_inline_language(lang_java, target_java).
|
|
ok_to_inline_language(lang_csharp, target_csharp).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% The direct_arg_in_out transformation (daio for short) creates code
|
|
% with insts that are good enough to be accepted by the code generators,
|
|
% but too fragile to be processed by transformations such as inlining.
|
|
% Invoking inlining on a predicate created by the daio transformation
|
|
% was responsible for Mantis bug #542.
|
|
%
|
|
|
|
:- type maybe_involves_daio
|
|
---> does_not_involve_daio
|
|
; does_involve_daio.
|
|
|
|
:- pred origin_involves_daio(pred_origin::in,
|
|
maybe_involves_daio::out) is det.
|
|
|
|
origin_involves_daio(Origin, InvolvesDAIO) :-
|
|
(
|
|
( Origin = origin_user(_)
|
|
; Origin = origin_compiler(_)
|
|
),
|
|
InvolvesDAIO = does_not_involve_daio
|
|
;
|
|
Origin = origin_pred_transform(_Transform, SubOrigin, _PredId),
|
|
origin_involves_daio(SubOrigin, InvolvesDAIO)
|
|
;
|
|
Origin = origin_proc_transform(Transform, SubOrigin, _PredId, _ProcId),
|
|
( if
|
|
( origin_proc_transform_involves_daio(Transform, does_involve_daio)
|
|
; origin_involves_daio(SubOrigin, does_involve_daio)
|
|
)
|
|
then
|
|
InvolvesDAIO = does_involve_daio
|
|
else
|
|
InvolvesDAIO = does_not_involve_daio
|
|
)
|
|
).
|
|
|
|
:- pred origin_proc_transform_involves_daio(proc_transform::in,
|
|
maybe_involves_daio::out) is det.
|
|
|
|
origin_proc_transform_involves_daio(Transform, InvolvesDAIO) :-
|
|
(
|
|
( Transform = proc_transform_user_type_spec(_, _)
|
|
; Transform = proc_transform_higher_order_spec(_)
|
|
; Transform = proc_transform_unused_args(_)
|
|
; Transform = proc_transform_accumulator(_, _)
|
|
; Transform = proc_transform_loop_inv(_, _)
|
|
; Transform = proc_transform_tuple(_, _)
|
|
; Transform = proc_transform_untuple(_, _)
|
|
; Transform = proc_transform_dep_par_conj(_)
|
|
; Transform = proc_transform_par_loop_ctrl
|
|
; Transform = proc_transform_lcmc(_, _)
|
|
; Transform = proc_transform_io_tabling
|
|
; Transform = proc_transform_stm_expansion
|
|
),
|
|
InvolvesDAIO = does_not_involve_daio
|
|
;
|
|
Transform = proc_transform_direct_arg_in_out,
|
|
InvolvesDAIO = does_involve_daio
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
:- end_module transform_hlds.inlining.
|
|
%---------------------------------------------------------------------------%
|