Files
mercury/compiler/term_constr_main.m
Zoltan Somogyi d6c140af1f Allow inlining of linear mutual tail recursions.
Lets say an SCC contains n procedures, P1 through Pn. The SCC is *linearly
tail recursive* if the set of tail recursive calls in the SCC is {P1 -> P2,
P2 -> P3, ... Pn-1 -> Pn, Pn -> P1}, i.e. each procedure calls the next one
and the last one calls the first. For each Pi that is called from above
the SCC, the new optimization inlines the callee at every tail recursive
call site except the one that calls Pi itself. For example, if Pi is P1,
it would inline the tail call to P2, the tail call to P3 inside P2,
the tail call to P4 inside P3, and so on. Since the only tail recursive
call left in Pi is to Pi, this scheme transforms mutual tail recursion,
which the MLDS backend cannot (yet) implement, into self tail recursion,
which it *can* implement.

We only perform the transformation if each procedure in the SCC
contains *exactly one* tail recursive call. This is because each extra
tail recursive call may *double* the size of the resulting code.

Any recursive calls that are not *tail* recursive are left alone.

compiler/options.m:
doc/user_guide.texi:
    Add a new option, --inline-linear-tail-rec-sccs, that calls for the
    new transformation.

NEWS:
    Announce the new option.

compiler/mercury_compile_middle_passes.m:
    Call inlining if the new option is set.

compiler/inlining.m:
    If the new option is given, implement the transformation described
    at the top.

    Fix several variable and predicate names that misleadingly implied
    that a predicate *has* been inlined when it has only been decided
    that it is *worth* inlining, with the actual inlining taking place later.

    Pass the needed_map inside the inline_params.

    Rename the top predicate to fit the naming scheme used in the rest
    of the module.

compiler/hlds_goal.m:
    Add a goal feature that designates the call that it decorates as being
    either a self or a mutual tail recursive call.

    Rename the existing goal features that apply only to self tail recursive
    calls to make clear that fact.

compiler/mark_tail_calls.m:
    Generalize the code to look either for

    - just self tail recursive calls, as at the moment, as needed by both
      the LLDS and the MLDS code generator (for different purposes), or for
    - both self and mutual tail recursive calls, as needed by the new
      kind of inlining.

    Give the top level predicates names that indicate their overall purpose.

    Change the representation of the at_tail type to separate out the
    flag that says whether we have or haven't seen a recursive call
    in the backward traversal so far. This yields cleaner code
    in several places.

    Store the list of generated error_specs, and the flag that says whether
    the traversal has found any recursive call, in the mark_tail_calls_info
    structure, instead of returning them as separate output arguments.
    This is better, since *most* places don't add error_specs or set the flag.

compiler/dependency_graph.m:
    Explicitly gather the list of edges in the graph we are building,
    and record that list. Inlining now needs this, because it needs to know
    not just that e.g. two procedures p and q in an SCC call each other,
    also *from how many call sites". The dependency graph does not contain
    that information, but the list of edges from which it is built does.

compiler/hlds_dependency_graph.m:
    For most purposes, we want to add an edge from p to q if

    - p calls q in a tail call,
    - p calls q in a non-tail call, or
    - p *refers* to q without calling it, e.g. by constructing a closure
      whose procedure is q.

    However, when constructing the *tail* SCC within a conventional SCC,
    we want to add an edge from p to q only in the first situation.
    Add an option that controls whether we add an edge just in the first
    situation or all three.

compiler/ml_tailcall.m:
    ZZZ

compiler/call_gen.m:
compiler/deep_profiling.m:
compiler/deforest.m:
compiler/mercury_compile_llds_back_end.m:
compiler/saved_vars.m:
compiler/term_constr_main.m:
    Conform to the changes above.

tests/hard_coded/tail_rec_scc.{m,exp}:
    A new test case for the new option.

tests/hard_coded/Mmakefile:
tests/hard_coded/Mercury.options:
    Enable the new test case.
2017-03-21 19:32:00 +11:00

336 lines
13 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 1997,2002-2011 The University of Melbourne.
% Copyright (C) 2017 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: term_constr_main.m.
% Main author: juliensf.
%
% This module contains the top level of a termination analyser for Mercury.
% It is responsible for setting up the relevant data structures and invoking
% all the other passes.
%
% --------
% Overview
% --------
%
% The main data structures used by this analysis are defined in
% term_constr_data.m.
%
% The analysis is carried out in three passes:
%
% * Pass 0 - initial pass.
% Setup information for builtin predicates and process any information
% from user annotations like termination pragmas and foreign proc attributes.
% Also, set up information imported from `.opt' and `.trans_opt' files.
% (See term_constr_initial.m.)
%
% Each of passes 1 and 2 are run consecutively on each SCC of the
% program call-graph. This is done in bottom-up order.
%
% * Pass 1 - interargument size relationship (IR) analysis.
%
% (a) Build pass. Convert HLDS to the abstract representation (AR)
% defined in term_constr_data.m. (See term_constr_build.m.)
%
% (b) Fixpoint pass. Perform fixpoint calculation to derive IR
% information. (See term_constr_fixpoint.m.)
%
% * Pass 2 - termination.
% Use the information from pass 1 to attempt to find a proof that the
% procedures in a SCC terminate. There is an example of such a proof
% finder in term_constr_pass2.m, although we will probably end up with
% several such modules, each of which tries a different approach.
%
% In addition there are some utility procedures in term_constr_util.m
% and some predicates for handling error messages in term_constr_error.m.
%
% The machinery for handling convex constraints (polyhedra) is in:
%
% * polyhedron.m
% * lp_rational.m
% * rat.m
%
% TODO:
%
% * add support for higher-order code and intermodule mutual recursion.
%
% * add support for user-defined IR constraints for foreign code,
% :- pragma arg_size_relation(...) or something.
%
% * experiment with different types of widening.
%
% * experiment with different representation for the polyhedra.
%
%----------------------------------------------------------------------------%
:- module transform_hlds.term_constr_main.
:- interface.
:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module parse_tree.
:- import_module parse_tree.error_util.
:- import_module list.
%----------------------------------------------------------------------------%
% Perform termination analysis on a module.
%
:- pred term2_analyse_module(module_info::in, module_info::out,
list(error_spec)::out) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module hlds.hlds_dependency_graph.
:- import_module hlds.hlds_pred.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.options.
:- import_module parse_tree.prog_data_pragma.
:- import_module transform_hlds.term_constr_build.
:- import_module transform_hlds.term_constr_data.
:- import_module transform_hlds.term_constr_errors.
:- import_module transform_hlds.term_constr_fixpoint.
:- import_module transform_hlds.term_constr_initial.
:- import_module transform_hlds.term_constr_main_types.
:- import_module transform_hlds.term_constr_pass2.
:- import_module transform_hlds.term_norm.
:- import_module bool.
:- import_module maybe.
:- import_module std_util.
:- import_module set.
%----------------------------------------------------------------------------%
%
% Main pass.
%
term2_analyse_module(!ModuleInfo, Specs) :-
% Get options required by the build pass.
% These are:
% - which norm we are using.
% - whether we are propagating failure constraints.
module_info_get_globals(!.ModuleInfo, Globals),
globals.get_termination2_norm(Globals, Norm),
FunctorInfo = set_functor_info(!.ModuleInfo, Norm),
globals.lookup_bool_option(Globals, propagate_failure_constrs, Fail),
globals.lookup_bool_option(Globals, arg_size_analysis_only, ArgSizeOnly),
BuildOptions = term_build_options_init(FunctorInfo, Fail, ArgSizeOnly),
% Get options required by the fixpoint pass.
% These are:
% - what cutoff value we are using
% - the maximum allowable matrix size
% (this is also used in pass 2).
globals.lookup_int_option(Globals, term2_maximum_matrix_size, MaxSize),
globals.lookup_int_option(Globals, widening_limit, CutOff),
% NOTE: We may eventually allow other types of widening.
Widening = after_fixed_cutoff(CutOff),
FixpointOptions = fixpoint_options_init(Widening, MaxSize),
% Get options required by pass 2.
% Currently this is just the maximum matrix size.
Pass2Options = pass2_options_init(MaxSize),
% Setup termination information for builtins and compiler generated
% predicates. Setup information from termination pragmas and foreign_proc
% attributes.
term2_preprocess_module(!ModuleInfo),
% Analyse the module SCC-bySCC in a bottom-up order.
module_info_ensure_dependency_info(!ModuleInfo, DepInfo),
get_bottom_up_sccs_with_entry_points(!.ModuleInfo, DepInfo,
BottomUpSCCsEntryPoints),
list.foldl2(
term2_analyse_scc(BuildOptions, FixpointOptions, Pass2Options),
BottomUpSCCsEntryPoints, !ModuleInfo, [], Specs),
% Write termination2_info pragmas to `.opt' files.
module_info_get_proc_analysis_kinds(!.ModuleInfo, ProcAnalysisKinds0),
set.insert(pak_termination2, ProcAnalysisKinds0, ProcAnalysisKinds),
module_info_set_proc_analysis_kinds(ProcAnalysisKinds, !ModuleInfo).
%----------------------------------------------------------------------------%
%
% Analyse a single SCC.
%
% Analyse a single SCC of the call graph. This will require results
% concerning SCCs lower down the call graph.
%
% The normal procedure for analysing a SCC is:
%
% (1) Build the abstract representation (AR) of the procedures in
% the SCC. (term_constr_build.m).
%
% (2) Use a fixpoint iteration to derive interargument size
% relationships for the procedures in the SCC.
% (term_constr_fixpoint.m).
%
% (3) Use this information to try and find a proof that the procedures
% in the SCC terminate. (term_constr_pass2.m).
%
% Exactly which of the above steps gets carried out depends upon
% what (if any) additional information the user has supplied.
%
% For Mercury procedures:
% If the procedure is marked as terminating/non-terminating, we still
% carry out steps (1) and (2). If a procedure has been supplied with
% an argument size constraint we carry out steps (1) and (3). If both
% have been supplied we do not carry out any steps.
%
% The usual caveats regarding the termination pragmas and mutual
% recursion apply (see the reference manual).
%
% XXX What do we do about pragma arg_size_info and mutual recursion?
% (Nothing yet, because it isn't implemented :-( )
%
% For procedures implemented as foreign code:
% The usual defaults (see reference manual) apply.
% If no argument size constraint is supplied then non-zero arguments
% are assumed to have unbounded size.
%
:- pred term2_analyse_scc(term_build_options::in, fixpoint_options::in,
pass2_options::in, scc_with_entry_points::in,
module_info::in, module_info::out,
list(error_spec)::in, list(error_spec)::out) is det.
term2_analyse_scc(BuildOpts, FixpointOpts, Pass2Opts, SCCEntryPoints,
!ModuleInfo, !Specs) :-
SCC = SCCEntryPoints ^ swep_scc_procs,
% Since all of the passes are potentially optional, we need to initialise
% the size_var_maps separately. If they are left uninitialised, intermodule
% optimization will not work.
set.filter(proc_needs_ar_built(!.ModuleInfo), SCC, NeedsAR),
% Build the abstract representation for those procedures that require it.
% The procedures that require it are those that do not already have
% both argument size information and termination information.
NeedsAREntryPoints = SCCEntryPoints ^ swep_scc_procs := NeedsAR,
term_constr_build_abstract_scc(BuildOpts, NeedsAREntryPoints,
BuildErrors, !ModuleInfo),
% We only perform the fixpoint computation for those procedures where
% we can gain meaningful information from it. We do not do it when:
% - the information is already known, or
% - the argument size relationships depend upon higher-order calls.
set.filter(isnt(has_arg_size_info(!.ModuleInfo)), NeedsAR, NeedsArgSize),
term_constr_fixpoint.do_fixpoint_calculation(FixpointOpts,
NeedsArgSize, 1, FixpointErrors, !ModuleInfo),
% Errors detected during pass 1 always result in the rest of the analysis
% being aborted for the SCC under consideration.
Pass1Errors = BuildErrors ++ FixpointErrors,
(
Pass1Errors = [],
MaybeEarlyPass2Result = no
;
Pass1Errors = [_ | _],
MaybeEarlyPass2Result = yes(can_loop(Pass1Errors))
),
module_info_get_globals(!.ModuleInfo, Globals),
globals.lookup_bool_option(Globals, arg_size_analysis_only, ArgSizeOnly),
(
ArgSizeOnly = no,
set.filter(isnt(has_term_info(!.ModuleInfo)), NeedsAR, NeedsTerm),
% Some procedures may not have arg_size_info, but may have termination
% info, i.e. those that have a pragma terminates or does_not_terminate
% declaration.
(
MaybeEarlyPass2Result = yes(Pass2Result)
;
MaybeEarlyPass2Result = no,
term_constr_pass2.prove_termination_in_scc(Pass2Opts,
NeedsTerm, !.ModuleInfo, Pass2Result)
),
% Set the termination property for this procedure, and report
% any errors that we found during pass 2.
set_termination_info_for_procs(NeedsTerm, Pass2Result, !ModuleInfo),
(
Pass2Result = can_loop(Errors),
maybe_report_term2_errors(!.ModuleInfo, NeedsTerm, Errors, !Specs)
;
Pass2Result = cannot_loop(_)
)
;
ArgSizeOnly = yes
).
%-----------------------------------------------------------------------------%
%
% Procedures for storing 'termination2_info' in the HLDS.
%
:- pred set_termination_info_for_procs(set(pred_proc_id)::in,
constr_termination_info::in, module_info::in, module_info::out) is det.
set_termination_info_for_procs(PPIds, TerminationInfo, !ModuleInfo) :-
set.foldl(set_termination_info_for_proc(TerminationInfo), PPIds,
!ModuleInfo).
:- pred set_termination_info_for_proc(constr_termination_info::in,
pred_proc_id::in, module_info::in, module_info::out) is det.
set_termination_info_for_proc(TerminationInfo, PPId, !ModuleInfo) :-
module_info_pred_proc_info(!.ModuleInfo, PPId, PredInfo, ProcInfo0),
proc_info_get_termination2_info(ProcInfo0, Term2Info0),
term2_info_set_term_status(yes(TerminationInfo), Term2Info0, Term2Info),
proc_info_set_termination2_info(Term2Info, ProcInfo0, ProcInfo),
module_info_set_pred_proc_info(PPId, PredInfo, ProcInfo, !ModuleInfo).
%----------------------------------------------------------------------------%
%
% Utility predicates.
%
% These test whether various fields in the termination2_info struct have
% been set.
% Succeeds iff the given procedure has argument size constraints
% associated with it.
%
:- pred has_arg_size_info(module_info::in, pred_proc_id::in) is semidet.
has_arg_size_info(ModuleInfo, PPId) :-
module_info_pred_proc_info(ModuleInfo, PPId, _, ProcInfo),
proc_info_get_termination2_info(ProcInfo, TermInfo),
term2_info_get_success_constrs(TermInfo) = yes(_).
% Succeeds iff the termination status of the given procedure has been set.
%
:- pred has_term_info(module_info::in, pred_proc_id::in) is semidet.
has_term_info(ModuleInfo, PPId) :-
module_info_pred_proc_info(ModuleInfo, PPId, _, ProcInfo),
proc_info_get_termination2_info(ProcInfo, TermInfo),
term2_info_get_term_status(TermInfo) = yes(_).
:- pred proc_needs_ar_built(module_info::in, pred_proc_id::in) is semidet.
proc_needs_ar_built(ModuleInfo, PPId) :-
module_info_pred_proc_info(ModuleInfo, PPId, _, ProcInfo),
proc_info_get_termination2_info(ProcInfo, TermInfo),
(
term2_info_get_success_constrs(TermInfo) = no
;
term2_info_get_term_status(TermInfo) = no
).
%-----------------------------------------------------------------------------%
:- end_module transform_hlds.term_constr_main.
%-----------------------------------------------------------------------------%