%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1994-2012 The University of Melbourne.
% Copyright (C) 2014-2023 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, Args,
    %   CalledPredInfo, CalledProcInfo, !TVarSet, !VarTable, !RttiVarMaps,
    %   Goal):
    %
    % Given the universally quantified type variables in the caller's type,
    % the arguments to the call, the pred_info and proc_info for the called
    % goal 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.
    %
:- pred do_inline_call(module_info::in, list(tvar)::in, list(prog_var)::in,
    pred_info::in, proc_info::in, tvarset::in, tvarset::out,
    var_table::in, var_table::out, rtti_varmaps::in, rtti_varmaps::out,
    hlds_goal::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 check_hlds.type_util.
:- import_module hlds.goal_util.
:- import_module hlds.hlds_dependency_graph.
:- import_module hlds.hlds_proc_util.
:- import_module hlds.mark_tail_calls.
:- import_module hlds.passes_aux.
:- import_module hlds.pred_name.
:- import_module hlds.quantification.
:- 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_linear_tail_rec              ::
                                            maybe_inline_linear_tail_rec_sccs,

                ip_highlevel_code               :: bool,

                ip_linear_tail_rec_max_extra    :: int,
                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,
    LinearTailRec = OptTuple ^ ot_inline_linear_tail_rec_sccs,
    LinearTailRecMaxExtra =
        OptTuple ^ ot_inline_linear_tail_rec_sccs_max_extra,
    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, LinearTailRec,
        HighLevelCode, LinearTailRecMaxExtra, 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.
    % If LinearTailRec = no, we don't try to do anything special about
    % calls where the callee is in the *same* SCC as the caller.

    (
        LinearTailRec = do_not_inline_linear_tail_rec_sccs,
        module_info_ensure_dependency_info(!ModuleInfo, DepInfo)
    ;
        LinearTailRec = inline_linear_tail_rec_sccs,
        % For this, we need *accurate* information about SCCs.
        % I (zs) am not 100% certain that every pass before this one
        % that invalidates any existing dependency info also clobbers
        % that dependency info.
        module_info_rebuild_dependency_info(!ModuleInfo, DepInfo),
        mark_self_and_mutual_tail_rec_calls_in_module(DepInfo, !ModuleInfo)
    ),

    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 = [_, _ | _],
        LinearTailRec = Params ^ ip_linear_tail_rec,
        (
            LinearTailRec = do_not_inline_linear_tail_rec_sccs,
            inline_in_simple_non_singleton_scc(ProgressStream, Params,
                SCCProcs, !ShouldInlineProcs, !ModuleInfo)
        ;
            LinearTailRec = inline_linear_tail_rec_sccs,
            inline_in_maybe_linear_tail_rec_scc(ProgressStream, Params,
                SCCEntryPoints, SCCProcs, !ShouldInlineProcs, !ModuleInfo)
        )
    ).

:- pred inline_in_maybe_linear_tail_rec_scc(io.text_output_stream::in,
    inline_params::in, scc_with_entry_points::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_maybe_linear_tail_rec_scc(ProgressStream, Params,
        SCCEntryPoints, SCCProcs, !ShouldInlineProcs, !ModuleInfo) :-
    SCCEntryPoints = scc_with_entry_points(SCC,
        CalledFromHigherSCCs, Exported),
    TSCCDepInfo =
        build_proc_dependency_graph(!.ModuleInfo, SCC, only_tail_calls),
    get_bottom_up_sccs_with_entry_points(!.ModuleInfo, TSCCDepInfo,
        TSCCsEntries),
    % Read the comments on the following code assuming that
    % LinearTailRecMaxExtra is zero, until you get to the part that explains
    % what happens if it is not.
    LinearTailRecMaxExtra = Params ^ ip_linear_tail_rec_max_extra,
    ( if
        % If there is only one TSSC, which means that every procedure
        % in the SCC is reachable from every other procedure via
        % *tail* recursive calls, ...
        TSCCsEntries = [_],
        % ... and the number of tail recursive calls in the SCC is equal
        % to the number of procedures in the SCC, ...
        TSCCArcs = dependency_info_get_arcs(TSCCDepInfo),
        list.length(TSCCArcs, NumTSCCArcs),
        list.length(SCCProcs, NumSCCProcs),
        NumTSCCArcs =< NumSCCProcs + LinearTailRecMaxExtra
    then
        % ... then every procedure in the SCC is called from exactly
        % one call site in some other procedure in the SCC.
        %
        % The optimization we apply to such SCCs is the following.
        %
        % Suppose the SCC consists of procedures A, B, C and D,
        % with the tail calls among them being A->B->C->D->A,
        % with A and B being entry points.
        %
        % For each entry point procedure of the SCC, such as A:
        %
        %   Inline the single tail recursive call site in its body.
        %   In this case, this replaces the tail recursive call to B
        %   with the body of B, which includes a single tail recursive
        %   call to C.
        %
        %   We keep doing the same thing until the only tail recursive
        %   call remaining is to the procedure we started with,
        %   in this case A.
        %
        % This makes the code of each entry point self-tail-recursive.
        %
        % This algorithm leaves recursive calls in the SCC that are not
        % tail calls unchanged. Each such call will consume a stack frame.
        %
        % This transformation may leave the procedures in the SCC
        % that are *not* entry points in the SCC unused, after all
        % calls to them have been inlined. They will be deleted by
        % dead procedure elimination in the usual course of events.
        %
        % Any increase in the value of LinearTailRecMaxExtra allows the
        % SCC to contain one more mutually-tail-recursive call site that
        % we will inline. For example, having LinearTailRecMaxExtra = 1
        % would allow the example SCC above to contain either two A->B calls,
        % or two B->C calls, or two C->D calls, or two D->A calls.
        %
        % Suppose we are doing inlining inside A. With two D->A calls,
        % there will be no code size increase, since recurive calls to A
        % won't be inlined. With two C->D calls, there will be two copies
        % of the body of D. With two B->C calls, there will be two copies
        % of the bodies of both C and D, since the call to D will be inlined
        % in both copies of C. With two A->B calls, there will be two copies
        % of B, C and D. If the body of A itself is small, this may mean
        % that the total size of the code after inlining may be close to
        % double what it would be without the extra tail call permitted
        % by LinearTailRecMaxExtra = 1.
        %
        % In general, the size of the resulting code may grow almost as
        % fast as 2^LinearTailRecMaxExtra.
        %
        % (Since a TSCC with N procedures must have at least N tail recursive
        % calls, negative values of LinearTailRecMaxExtra *will* cause the
        % test above to fail, so execution will never get here.)
        %
        set.union(CalledFromHigherSCCs, Exported, EntryPoints),
        list.foldl(
            inline_in_linear_tail_rec_proc(Params, !.ShouldInlineProcs,
                SCC, EntryPoints),
            SCCProcs, !ModuleInfo)
    else
        inline_in_simple_non_singleton_scc(ProgressStream, Params, SCCProcs,
            !ShouldInlineProcs, !ModuleInfo)
    ).

:- pred inline_in_linear_tail_rec_proc(inline_params::in,
    set(pred_proc_id)::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_linear_tail_rec_proc(Params, ShouldInlineProcs, SCC, EntryPoints,
        PredProcId, !ModuleInfo) :-
    ( if set.member(PredProcId, EntryPoints) then
        % We should inline every tail recursive call in the body of
        % procedure PredProcId, except the one that calls PredProcId itself.
        set.delete(PredProcId, SCC, ShouldInlineTailProcs)
    else
        set.init(ShouldInlineTailProcs)
    ),
    inline_in_proc_if_allowed(Params, ShouldInlineProcs, ShouldInlineTailProcs,
        PredProcId, !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(PredId, ProcId, ArgVars, _Builtin, _Context, _SymName),
    module_info_pred_proc_info(ModuleInfo, PredId, ProcId, PredInfo, ProcInfo),
    % 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(ProcInfo, CalleeVarTable),
            var_table_count(CalleeVarTable, NumVarsInCallee),
            TotalNumVars = NumVarsInVarTable + NumVarsInCallee,
            TotalNumVars =< VarThresh
        ),
        % XXX Work around bug #142.
        not may_encounter_bug_142(ProcInfo, ArgVars)
    then
        do_inline_call(ModuleInfo, ExternalTypeParams, ArgVars,
            PredInfo, ProcInfo, TypeVarSet0, TypeVarSet, VarTable0, VarTable,
            RttiVarMaps0, RttiVarMaps, Goal1),

        DidInlining = we_have_inlined,

        proc_info_get_has_parallel_conj(ProcInfo, 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, ArgVars,
        CalleePredInfo, CalleeProcInfo,
        TypeVarSet0, TypeVarSet, VarTable0, VarTable,
        RttiVarMaps0, RttiVarMaps, Goal) :-
    proc_info_get_goal(CalleeProcInfo, CalledGoal),

    % 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),
    apply_variable_renaming_to_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, CalledGoal, 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
            check_marker(Markers, marker_user_marked_inline)
        then
            UserReq = user_req
        else
            UserReq = not_user_req
        ),
        ( if
            ( TailRec = tail_rec
            ; UserReq = user_req
            ; check_marker(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.
%---------------------------------------------------------------------------%
