mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-20 00:15:27 +00:00
Estimated hours taken: 220
Aditi update syntax, type and mode checking.
Change the hlds_goal for constructions in preparation for
structure reuse to avoid making multiple conflicting changes.
compiler/hlds_goal.m:
Merge `higher_order_call' and `class_method_call' into a single
`generic_call' goal type. This also has alternatives for the
various Aditi builtins for which type declarations can't
be written.
Remove the argument types field from higher-order/class method calls.
It wasn't used often, and wasn't updated by optimizations
such as inlining. The types can be obtained from the vartypes
field of the proc_info.
Add a `lambda_eval_method' field to lambda_goals.
Add a field to constructions to identify which RL code fragment should
be used for an top-down Aditi closure.
Add fields to constructions to hold structure reuse information.
This is currently ignored -- the changes to implement structure
reuse will be committed to the alias branch.
This is included here to avoid lots of CVS conflicts caused by
changing the definition of `hlds_goal' twice.
Add a field to `some' goals to specify whether the quantification
can be removed. This is used to make it easier to ensure that
indexes are used for updates.
Add a field to lambda_goals to describe whether the modes were
guessed by the compiler and may need fixing up after typechecking
works out the argument types.
Add predicate `hlds_goal__generic_call_id' to work out a call_id
for a generic call for use in error messages.
compiler/purity.m:
compiler/post_typecheck.m:
Fill in the modes of Aditi builtin calls and closure constructions.
This needs to know which are the `aditi__state' arguments, so
it must be done after typechecking.
compiler/prog_data.m:
Added `:- type sym_name_and_arity ---> sym_name/arity'.
Add a type `lambda_eval_method', which describes how a closure
is to be executed. The alternatives are normal Mercury execution,
bottom-up execution by Aditi and top-down execution by Aditi.
compiler/prog_out.m:
Add predicate `prog_out__write_sym_name_and_arity', which
replaces duplicated inline code in a few places.
compiler/hlds_data.m:
Add a `lambda_eval_method' field to `pred_const' cons_ids and
`pred_closure_tag' cons_tags.
compiler/hlds_pred.m:
Remove type `pred_call_id', replace it with type `simple_call_id',
which combines a `pred_or_func' and a `sym_name_and_arity'.
Add a type `call_id' which describes all the different types of call,
including normal calls, higher-order and class-method calls
and Aditi builtins.
Add `aditi_top_down' to the type `marker'.
Remove `aditi_interface' from type `marker'. Interfacing to
Aditi predicates is now handled by `generic_call' hlds_goals.
Add a type `rl_exprn_id' which identifies a predicate to
be executed top-down by Aditi.
Add a `maybe(rl_exprn_id)' field to type `proc_info'.
Add predicate `adjust_func_arity' to convert between the arity
of a function to its arity as a predicate.
Add predicates `get_state_args' and `get_state_args_det' to
extract the DCG state arguments from an argument list.
Add predicate `pred_info_get_call_id' to get a `simple_call_id'
for a predicate for use in error messages.
compiler/hlds_out.m:
Write the new representation for call_ids.
Add a predicate `hlds_out__write_call_arg_id' which
replaces similar code in mode_errors.m and typecheck.m.
compiler/prog_io_goal.m:
Add support for `aditi_bottom_up' and `aditi_top_down' annotations
on pred expressions.
compiler/prog_io_util.m:
compiler/prog_io_pragma.m:
Add predicates
- `prog_io_util:parse_name_and_arity' to parse `SymName/Arity'
(moved from prog_io_pragma.m).
- `prog_io_util:parse_pred_or_func_name_and_arity to parse
`pred SymName/Arity' or `func SymName/Arity'.
- `prog_io_util:parse_pred_or_func_and_args' to parse terms resembling
a clause head (moved from prog_io_pragma.m).
compiler/type_util.m:
Add support for `aditi_bottom_up' and `aditi_top_down' annotations
on higher-order types.
Add predicates `construct_higher_order_type',
`construct_higher_order_pred_type' and
`construct_higher_order_func_type' to avoid some code duplication.
compiler/mode_util.m:
Add predicate `unused_mode/1', which returns `builtin:unused'.
Add functions `aditi_di_mode/0', `aditi_ui_mode/0' and
`aditi_uo_mode/0' which return `in', `in', and `out', but will
be changed to return `di', `ui' and `uo' when alias tracking
is implemented.
compiler/goal_util.m:
Add predicate `goal_util__generic_call_vars' which returns
any arguments to a generic_call which are not in the argument list,
for example the closure passed to a higher-order call or
the typeclass_info for a class method call.
compiler/llds.m:
compiler/exprn_aux.m:
compiler/dupelim.m:
compiler/llds_out.m:
compiler/opt_debug.m:
Add builtin labels for the Aditi update operations.
compiler/hlds_module.m:
Add predicate predicate_table_search_pf_sym, used for finding
possible matches for a call with the wrong number of arguments.
compiler/intermod.m:
Don't write predicates which build `aditi_top_down' goals,
because there is currently no way to tell importing modules
which RL code fragment to use.
compiler/simplify.m:
Obey the `cannot_remove' field of explicit quantification goals.
compiler/make_hlds.m:
Parse Aditi updates.
Don't typecheck clauses for which syntax errors in Aditi updates
are found - this avoids spurious "undefined predicate `aditi_insert/3'"
errors.
Factor out some common code to handle terms of the form `Head :- Body'.
Factor out common code in the handling of pred and func expressions.
compiler/typecheck.m:
Typecheck Aditi builtins.
Allow the argument types of matching predicates to be adjusted
when typechecking the higher-order arguments of Aditi builtins.
Change `typecheck__resolve_pred_overloading' to take a list of
argument types rather than a `map(var, type)' and a list of
arguments to allow a transformation to be performed on the
argument types before passing them.
compiler/error_util.m:
Move the part of `report_error_num_args' which writes
"wrong number of arguments (<x>; expected <y>)" from
typecheck.m for use by make_hlds.m when reporting errors
for Aditi builtins.
compiler/modes.m:
compiler/unique_modes.m:
compiler/modecheck_call.m:
Modecheck Aditi builtins.
compiler/lambda.m:
Handle the markers for predicates introduced for
`aditi_top_down' and `aditi_bottom_up' lambda expressions.
compiler/polymorphism.m:
Add extra type_infos to `aditi_insert' calls
describing the tuple to insert.
compiler/call_gen.m:
Generate code for Aditi builtins.
compiler/unify_gen.m:
compiler/bytecode_gen.m:
Abort on `aditi_top_down' and `aditi_bottom_up' lambda
expressions - code generation for them is not yet implemented.
compiler/magic.m:
Use the `aditi_call' generic_call rather than create
a new procedure for each Aditi predicate called from C.
compiler/rl_out.pp:
compiler/rl_gen.m:
compiler/rl.m:
Move some utility code used by magic.m and call_gen.m into rl.m.
Remove an XXX comment about reference counting being not yet
implemented - Evan has fixed that.
library/ops.m:
compiler/mercury_to_mercury.m:
doc/transition_guide.texi:
Add unary prefix operators `aditi_bottom_up' and `aditi_top_down',
used as qualifiers on lambda expressions.
Add infix operator `==>' to separate the tuples in an
`aditi_modify' call.
compiler/follow_vars.m:
Thread a `map(prog_var, type)' through, needed because
type information is no longer held in higher-order call goals.
compiler/table_gen.m:
Use the `make_*_construction' predicates in hlds_goal.m
to construct constants.
compiler/*.m:
Trivial changes to add extra fields to hlds_goal structures.
doc/reference_manual.texi:
Document Aditi updates.
Use @samp{pragma base_relation} instead of
@samp{:- pragma base_relation} throughout the Aditi documentation
to be consistent with other parts of the reference manual.
tests/valid/Mmakefile:
tests/valid/aditi_update.m:
tests/valid/aditi.m:
Test case.
tests/valid/Mmakefile:
Remove some hard-coded --intermodule-optimization rules which are
no longer needed because `mmake depend' is now run in this directory.
tests/invalid/*.err_exp:
Fix expected output for changes in reporting of call_ids
in typecheck.m.
tests/invalid/Mmakefile
tests/invalid/aditi_update_errors.{m,err_exp}:
tests/invalid/aditi_update_mode_errors.{m,err_exp}:
Test error messages for Aditi updates.
tests/valid/aditi.m:
tests/invalid/aditi.m:
Cut down version of extras/aditi/aditi.m to provide basic declarations
for Aditi compilation such as `aditi__state' and the modes
`aditi_di', `aditi_uo' and `aditi_ui'. Installing extras/aditi/aditi.m
somewhere would remove the need for these.
765 lines
28 KiB
Mathematica
765 lines
28 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 1994-1999 The University of Melbourne.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Main author: conway.
|
|
|
|
:- module inlining.
|
|
|
|
% This module inlines
|
|
%
|
|
% * (--inline-simple and --inline-simple-threshold N)
|
|
% procedures whose size is below the given threshold,
|
|
% PLUS
|
|
% procedures that are flat (ie 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 perfoming inlining on a
|
|
% procedure then deciding if calls to it (higher in the call-graph)
|
|
% should be inlined. SCCs get flattend 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 case
|
|
% where several `set' accessors get called one after the other,
|
|
% inlining them enables the code generator to avoid creating
|
|
% the intermediate structures which is often a win).
|
|
%
|
|
% - arithmetic predicates where the as above, the cost of the
|
|
% call will often outweigh the cost of the arithmetic.
|
|
%
|
|
% - det or semi pragma C code, where often the C operation is
|
|
% very small, inlining avoids a call and allows the C 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_c_code,
|
|
% procedures whose body is such a pragma_c_code must NOT be inlined.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- interface.
|
|
|
|
:- import_module hlds_goal, hlds_module, hlds_pred, prog_data.
|
|
:- import_module io, list, map.
|
|
|
|
:- pred inlining(module_info, module_info, io__state, io__state).
|
|
:- mode inlining(in, out, di, uo) is det.
|
|
|
|
:- pred inlining__is_simple_goal(hlds_goal, int).
|
|
:- mode inlining__is_simple_goal(in, in) is semidet.
|
|
|
|
% inlining__do_inline_call(UnivQVars, Args,
|
|
% CalledPredInfo, CalledProcInfo,
|
|
% VarSet0, VarSet, VarTypes0, VarTypes, TVarSet0, TVarSet,
|
|
% TypeInfoMap0, TypeInfoMap).
|
|
%
|
|
% 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 inlining__do_inline_call(list(tvar), list(prog_var),
|
|
pred_info, proc_info, prog_varset, prog_varset,
|
|
map(prog_var, type), map(prog_var, type),
|
|
tvarset, tvarset, map(tvar, type_info_locn),
|
|
map(tvar, type_info_locn), hlds_goal).
|
|
:- mode inlining__do_inline_call(in, in, in, in, in, out, in, out,
|
|
in, out, in, out, out) is det.
|
|
|
|
% inlining__get_type_substitution(CalleeArgTypes, CallerArgTypes,
|
|
% HeadTypeParams, CalleeExistQTVars, TypeSubn).
|
|
%
|
|
% Work out a type substitution to map the callee's argument
|
|
% types into the caller's.
|
|
:- pred inlining__get_type_substitution(list(type), list(type),
|
|
head_type_params, list(tvar), map(tvar, type)).
|
|
:- mode inlining__get_type_substitution(in, in, in, in, out) is det.
|
|
|
|
% inlining__rename_goal(CalledProcHeadVars, CallArgs,
|
|
% CallerVarSet0, CalleeVarSet, CallerVarSet,
|
|
% CallerVarTypes0, CalleeVarTypes, CallerVarTypes,
|
|
% VarRenaming, CalledGoal, RenamedGoal).
|
|
:- pred inlining__rename_goal(list(prog_var), list(prog_var), prog_varset,
|
|
prog_varset, prog_varset, map(prog_var, type),
|
|
map(prog_var, type), map(prog_var, type),
|
|
map(prog_var, prog_var), hlds_goal, hlds_goal).
|
|
:- mode inlining__rename_goal(in, in, in, in, out,
|
|
in, in, out, out, in, out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module globals, options, llds.
|
|
:- import_module term, varset.
|
|
:- import_module dead_proc_elim, type_util, mode_util, goal_util.
|
|
:- import_module passes_aux, code_aux, quantification, det_analysis, prog_data.
|
|
|
|
:- import_module bool, int, list, assoc_list, set, std_util.
|
|
:- import_module require, hlds_data, dependency_graph.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type inline_params ---> params(bool, bool, int, int, int).
|
|
% simple, single_use,
|
|
% size-threshold, simple-goal-threshold
|
|
% var-threshold
|
|
|
|
inlining(ModuleInfo0, 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.
|
|
%
|
|
globals__io_lookup_bool_option(inline_simple, Simple),
|
|
globals__io_lookup_bool_option(inline_single_use, SingleUse),
|
|
globals__io_lookup_int_option(inline_compound_threshold,
|
|
CompoundThreshold),
|
|
globals__io_lookup_int_option(inline_simple_threshold, SimpleThreshold),
|
|
globals__io_lookup_int_option(inline_vars_threshold, VarThreshold),
|
|
{ Params = params(Simple, SingleUse, CompoundThreshold,
|
|
SimpleThreshold, VarThreshold) },
|
|
|
|
%
|
|
% Get the usage counts for predicates
|
|
% (but only if needed, i.e. only if --inline-single-use
|
|
% or --inline-compound-threshold has been specified)
|
|
%
|
|
(
|
|
( { SingleUse = yes }
|
|
; { CompoundThreshold > 0 }
|
|
)
|
|
->
|
|
{ dead_proc_elim__analyze(ModuleInfo0, NeededMap) }
|
|
;
|
|
{ map__init(NeededMap) }
|
|
),
|
|
|
|
% build the call graph and extract the topological sort
|
|
% Note: the topological sort returns a list of SCCs.
|
|
% Clearly, we want to process the SCCs bottom to top
|
|
% (which is the order that they are returned), but it
|
|
% is not easy to guess the best way to flatten each SCC
|
|
% to achieve the best result. The current implementation
|
|
% just uses the ordering of the list returned by the
|
|
% topological sort. A more sophisticated approach would be
|
|
% to break the cycle so that the procedure(s) that are called
|
|
% by higher SCCs are processed last, but we do not implement
|
|
% that yet.
|
|
{ module_info_ensure_dependency_info(ModuleInfo0, ModuleInfo1) },
|
|
{ module_info_dependency_info(ModuleInfo1, DepInfo) },
|
|
{ hlds_dependency_info_get_dependency_ordering(DepInfo, SCCs) },
|
|
{ list__condense(SCCs, PredProcs) },
|
|
{ set__init(InlinedProcs0) },
|
|
inlining__do_inlining(PredProcs, NeededMap, Params, InlinedProcs0,
|
|
ModuleInfo1, ModuleInfo2),
|
|
|
|
% The dependency graph is now out of date and
|
|
% needs to be rebuilt.
|
|
{ module_info_clobber_dependency_info(ModuleInfo2, ModuleInfo) }.
|
|
|
|
:- pred inlining__do_inlining(list(pred_proc_id), needed_map, inline_params,
|
|
set(pred_proc_id), module_info, module_info,
|
|
io__state, io__state).
|
|
:- mode inlining__do_inlining(in, in, in, in, in, out, di, uo) is det.
|
|
|
|
inlining__do_inlining([], _Needed, _Params, _Inlined, Module, Module) --> [].
|
|
inlining__do_inlining([PPId|PPIds], Needed, Params, Inlined0,
|
|
Module0, Module) -->
|
|
inlining__in_predproc(PPId, Inlined0, Params, Module0, Module1),
|
|
inlining__mark_predproc(PPId, Needed, Params, Module1,
|
|
Inlined0, Inlined1),
|
|
inlining__do_inlining(PPIds, Needed, Params, Inlined1, Module1, Module).
|
|
|
|
:- pred inlining__mark_predproc(pred_proc_id, needed_map, inline_params,
|
|
module_info, set(pred_proc_id), set(pred_proc_id),
|
|
io__state, io__state).
|
|
:- mode inlining__mark_predproc(in, in, in, in, in, out, di, uo) is det.
|
|
|
|
inlining__mark_predproc(PredProcId, NeededMap, Params, ModuleInfo,
|
|
InlinedProcs0, InlinedProcs) -->
|
|
(
|
|
{ Params = params(Simple, SingleUse, CompoundThreshold,
|
|
SimpleThreshold, _VarThreshold) },
|
|
{ PredProcId = proc(PredId, ProcId) },
|
|
{ module_info_pred_info(ModuleInfo, PredId, PredInfo) },
|
|
{ pred_info_procedures(PredInfo, Procs) },
|
|
{ map__lookup(Procs, ProcId, ProcInfo) },
|
|
{ proc_info_goal(ProcInfo, CalledGoal) },
|
|
{ Entity = proc(PredId, ProcId) },
|
|
|
|
%
|
|
% the heuristic represented by the following code
|
|
% could be improved
|
|
%
|
|
(
|
|
{ Simple = yes },
|
|
{ inlining__is_simple_goal(CalledGoal,
|
|
SimpleThreshold) }
|
|
;
|
|
{ CompoundThreshold > 0 },
|
|
{ map__search(NeededMap, Entity, Needed) },
|
|
{ Needed = yes(NumUses) },
|
|
{ goal_size(CalledGoal, Size) },
|
|
{ Size * NumUses =< CompoundThreshold }
|
|
;
|
|
{ SingleUse = yes },
|
|
{ map__search(NeededMap, Entity, Needed) },
|
|
{ Needed = yes(NumUses) },
|
|
{ NumUses = 1 }
|
|
),
|
|
% Don't inline recursive predicates
|
|
{ \+ goal_calls(CalledGoal, PredProcId) },
|
|
|
|
% Under no circumstances inline model_non pragma c codes.
|
|
% The resulting code would not work properly.
|
|
\+ {
|
|
CalledGoal = pragma_c_code(_,_,_,_,_,_,_) - _,
|
|
proc_info_interface_code_model(ProcInfo, model_non)
|
|
},
|
|
|
|
% Don't inline memoed Aditi predicates.
|
|
{ pred_info_get_markers(PredInfo, Markers) },
|
|
{ \+ check_marker(Markers, aditi_memo) }
|
|
->
|
|
inlining__mark_proc_as_inlined(PredProcId, ModuleInfo,
|
|
InlinedProcs0, InlinedProcs)
|
|
;
|
|
{ InlinedProcs = InlinedProcs0 }
|
|
).
|
|
|
|
% this heuristic is used for both local and intermodule inlining
|
|
|
|
inlining__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,
|
|
inlining__is_flat_simple_goal(CalledGoal)
|
|
).
|
|
|
|
:- pred inlining__is_flat_simple_goal(hlds_goal::in) is semidet.
|
|
|
|
inlining__is_flat_simple_goal(conj(Goals) - _) :-
|
|
inlining__is_flat_simple_goal_list(Goals).
|
|
inlining__is_flat_simple_goal(not(Goal) - _) :-
|
|
inlining__is_flat_simple_goal(Goal).
|
|
inlining__is_flat_simple_goal(some(_, _, Goal) - _) :-
|
|
inlining__is_flat_simple_goal(Goal).
|
|
inlining__is_flat_simple_goal(call(_, _, _, BuiltinState, _, _) - _) :-
|
|
BuiltinState = inline_builtin.
|
|
inlining__is_flat_simple_goal(unify(_, _, _, _, _) - _).
|
|
|
|
:- pred inlining__is_flat_simple_goal_list(hlds_goals::in) is semidet.
|
|
|
|
inlining__is_flat_simple_goal_list([]).
|
|
inlining__is_flat_simple_goal_list([Goal | Goals]) :-
|
|
inlining__is_flat_simple_goal(Goal),
|
|
inlining__is_flat_simple_goal_list(Goals).
|
|
|
|
:- pred inlining__mark_proc_as_inlined(pred_proc_id, module_info,
|
|
set(pred_proc_id), set(pred_proc_id), io__state, io__state).
|
|
:- mode inlining__mark_proc_as_inlined(in, in, in, out, di, uo) is det.
|
|
|
|
inlining__mark_proc_as_inlined(proc(PredId, ProcId), ModuleInfo,
|
|
InlinedProcs0, InlinedProcs) -->
|
|
{ set__insert(InlinedProcs0, proc(PredId, ProcId), InlinedProcs) },
|
|
{ module_info_pred_info(ModuleInfo, PredId, PredInfo) },
|
|
( { pred_info_requested_inlining(PredInfo) } ->
|
|
[]
|
|
;
|
|
write_proc_progress_message("% Inlining ", PredId, ProcId,
|
|
ModuleInfo)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% inline_info contains the information that is changed
|
|
% as a result of inlining. It is threaded through the
|
|
% inlining process, and when finished, contains the
|
|
% updated information associated with the new goal.
|
|
%
|
|
% It also stores some necessary information that is not
|
|
% updated.
|
|
|
|
:- type inline_info
|
|
---> inline_info(
|
|
int, % variable threshold for inlining
|
|
set(pred_proc_id), % inlined procs
|
|
module_info, % module_info
|
|
list(tvar), % 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.
|
|
pred_markers, % markers for the current predicate
|
|
|
|
% the following fields are updated as a result
|
|
% of inlining
|
|
prog_varset, % varset
|
|
map(prog_var, type), % variable types
|
|
tvarset, % type variables
|
|
map(tvar, type_info_locn),% type_info varset, a mapping from
|
|
% type variables to variables
|
|
% where their type_info is
|
|
% stored.
|
|
bool % Did we change the determinism
|
|
% of any subgoal?
|
|
).
|
|
|
|
:- pred inlining__in_predproc(pred_proc_id, set(pred_proc_id), inline_params,
|
|
module_info, module_info, io__state, io__state).
|
|
:- mode inlining__in_predproc(in, in, in, in, out, di, uo) is det.
|
|
|
|
inlining__in_predproc(PredProcId, InlinedProcs, Params,
|
|
ModuleInfo0, ModuleInfo, IoState0, IoState) :-
|
|
Params = params(_Simple, _SingleUse, _CompoundThreshold,
|
|
_SimpleThreshold, VarThresh),
|
|
PredProcId = proc(PredId, ProcId),
|
|
|
|
module_info_preds(ModuleInfo0, PredTable0),
|
|
map__lookup(PredTable0, PredId, PredInfo0),
|
|
pred_info_procedures(PredInfo0, ProcTable0),
|
|
map__lookup(ProcTable0, ProcId, ProcInfo0),
|
|
|
|
pred_info_get_univ_quant_tvars(PredInfo0, UnivQTVars),
|
|
pred_info_typevarset(PredInfo0, TypeVarSet0),
|
|
pred_info_get_markers(PredInfo0, Markers),
|
|
|
|
proc_info_goal(ProcInfo0, Goal0),
|
|
proc_info_varset(ProcInfo0, VarSet0),
|
|
proc_info_vartypes(ProcInfo0, VarTypes0),
|
|
proc_info_typeinfo_varmap(ProcInfo0, TypeInfoVarMap0),
|
|
|
|
DetChanged0 = no,
|
|
|
|
InlineInfo0 = inline_info(
|
|
VarThresh, InlinedProcs, ModuleInfo0, UnivQTVars, Markers,
|
|
VarSet0, VarTypes0, TypeVarSet0, TypeInfoVarMap0, DetChanged0),
|
|
|
|
inlining__inlining_in_goal(Goal0, Goal, InlineInfo0, InlineInfo),
|
|
|
|
InlineInfo = inline_info(_, _, _, _, _, VarSet, VarTypes, TypeVarSet,
|
|
TypeInfoVarMap, DetChanged),
|
|
|
|
pred_info_set_typevarset(PredInfo0, TypeVarSet, PredInfo1),
|
|
|
|
proc_info_set_varset(ProcInfo0, VarSet, ProcInfo1),
|
|
proc_info_set_vartypes(ProcInfo1, VarTypes, ProcInfo2),
|
|
proc_info_set_typeinfo_varmap(ProcInfo2, TypeInfoVarMap, ProcInfo3),
|
|
proc_info_set_goal(ProcInfo3, Goal, ProcInfo),
|
|
|
|
map__det_update(ProcTable0, ProcId, ProcInfo, ProcTable),
|
|
pred_info_set_procedures(PredInfo1, ProcTable, PredInfo),
|
|
map__det_update(PredTable0, PredId, PredInfo, PredTable),
|
|
module_info_set_preds(ModuleInfo0, PredTable, ModuleInfo1),
|
|
|
|
% If the determinism of some sub-goals has changed,
|
|
% then we re-run determinism analysis, because
|
|
% propagating the determinism information through
|
|
% the procedure may lead to more efficient code.
|
|
( DetChanged = yes,
|
|
globals__io_get_globals(Globals, IoState0, IoState),
|
|
det_infer_proc(PredId, ProcId, ModuleInfo1, ModuleInfo,
|
|
Globals, _, _, _)
|
|
; DetChanged = no,
|
|
ModuleInfo = ModuleInfo1,
|
|
IoState = IoState0
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred inlining__inlining_in_goal(hlds_goal, hlds_goal, inline_info,
|
|
inline_info).
|
|
:- mode inlining__inlining_in_goal(in, out, in, out) is det.
|
|
|
|
inlining__inlining_in_goal(conj(Goals0) - GoalInfo, conj(Goals) - GoalInfo) -->
|
|
inlining__inlining_in_conj(Goals0, Goals).
|
|
|
|
inlining__inlining_in_goal(par_conj(Goals0, SM) - GoalInfo,
|
|
par_conj(Goals, SM) - GoalInfo) -->
|
|
inlining__inlining_in_disj(Goals0, Goals).
|
|
|
|
inlining__inlining_in_goal(disj(Goals0, SM) - GoalInfo,
|
|
disj(Goals, SM) - GoalInfo) -->
|
|
inlining__inlining_in_disj(Goals0, Goals).
|
|
|
|
inlining__inlining_in_goal(switch(Var, Det, Cases0, SM) - GoalInfo,
|
|
switch(Var, Det, Cases, SM) - GoalInfo) -->
|
|
inlining__inlining_in_cases(Cases0, Cases).
|
|
|
|
inlining__inlining_in_goal(
|
|
if_then_else(Vars, Cond0, Then0, Else0, SM) - GoalInfo,
|
|
if_then_else(Vars, Cond, Then, Else, SM) - GoalInfo) -->
|
|
inlining__inlining_in_goal(Cond0, Cond),
|
|
inlining__inlining_in_goal(Then0, Then),
|
|
inlining__inlining_in_goal(Else0, Else).
|
|
|
|
inlining__inlining_in_goal(not(Goal0) - GoalInfo, not(Goal) - GoalInfo) -->
|
|
inlining__inlining_in_goal(Goal0, Goal).
|
|
|
|
inlining__inlining_in_goal(some(Vars, CanRemove, Goal0) - GoalInfo,
|
|
some(Vars, CanRemove, Goal) - GoalInfo) -->
|
|
inlining__inlining_in_goal(Goal0, Goal).
|
|
|
|
inlining__inlining_in_goal(call(PredId, ProcId, ArgVars, Builtin, Context,
|
|
Sym) - GoalInfo0, Goal - GoalInfo, InlineInfo0, InlineInfo) :-
|
|
|
|
InlineInfo0 = inline_info(VarThresh, InlinedProcs, ModuleInfo,
|
|
HeadTypeParams, Markers,
|
|
VarSet0, VarTypes0, TypeVarSet0, TypeInfoVarMap0, DetChanged0),
|
|
|
|
% should we inline this call?
|
|
(
|
|
inlining__should_inline_proc(PredId, ProcId, Builtin,
|
|
InlinedProcs, Markers, ModuleInfo),
|
|
% okay, but will we exceed the number-of-variables
|
|
% threshold?
|
|
varset__vars(VarSet0, ListOfVars),
|
|
list__length(ListOfVars, ThisMany),
|
|
% We need to find out how many variables the
|
|
% Callee has
|
|
module_info_pred_proc_info(ModuleInfo, PredId, ProcId,
|
|
PredInfo, ProcInfo),
|
|
proc_info_varset(ProcInfo, CalleeVarSet),
|
|
varset__vars(CalleeVarSet, CalleeListOfVars),
|
|
list__length(CalleeListOfVars, CalleeThisMany),
|
|
TotalVars is ThisMany + CalleeThisMany,
|
|
TotalVars =< VarThresh
|
|
->
|
|
inlining__do_inline_call(HeadTypeParams, ArgVars, PredInfo,
|
|
ProcInfo, VarSet0, VarSet, VarTypes0, VarTypes,
|
|
TypeVarSet0, TypeVarSet, TypeInfoVarMap0,
|
|
TypeInfoVarMap, Goal - GoalInfo),
|
|
|
|
% If the inferred determinism of the called
|
|
% goal differs from the declared determinism,
|
|
% flag that we should re-run determinism analysis
|
|
% on this proc.
|
|
goal_info_get_determinism(GoalInfo0, Determinism0),
|
|
goal_info_get_determinism(GoalInfo, Determinism),
|
|
( Determinism0 = Determinism ->
|
|
DetChanged = DetChanged0
|
|
;
|
|
DetChanged = yes
|
|
)
|
|
;
|
|
Goal = call(PredId, ProcId, ArgVars, Builtin, Context, Sym),
|
|
GoalInfo = GoalInfo0,
|
|
VarSet = VarSet0,
|
|
VarTypes = VarTypes0,
|
|
TypeVarSet = TypeVarSet0,
|
|
TypeInfoVarMap = TypeInfoVarMap0,
|
|
DetChanged = DetChanged0
|
|
),
|
|
InlineInfo = inline_info(
|
|
VarThresh, InlinedProcs, ModuleInfo, HeadTypeParams, Markers,
|
|
VarSet, VarTypes, TypeVarSet, TypeInfoVarMap, DetChanged).
|
|
|
|
inlining__inlining_in_goal(generic_call(A, B, C, D) - GoalInfo,
|
|
generic_call(A, B, C, D) - GoalInfo) --> [].
|
|
|
|
inlining__inlining_in_goal(unify(A, B, C, D, E) - GoalInfo,
|
|
unify(A, B, C, D, E) - GoalInfo) --> [].
|
|
|
|
inlining__inlining_in_goal(pragma_c_code(A, B, C, D, E, F, G) - GoalInfo,
|
|
pragma_c_code(A, B, C, D, E, F, G) - GoalInfo) --> [].
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
inlining__do_inline_call(HeadTypeParams, ArgVars, PredInfo, ProcInfo,
|
|
VarSet0, VarSet, VarTypes0, VarTypes, TypeVarSet0, TypeVarSet,
|
|
TypeInfoVarMap0, TypeInfoVarMap, Goal) :-
|
|
|
|
proc_info_goal(ProcInfo, CalledGoal),
|
|
|
|
% look up the rest of the info for the called procedure.
|
|
|
|
pred_info_typevarset(PredInfo, CalleeTypeVarSet),
|
|
proc_info_headvars(ProcInfo, HeadVars),
|
|
proc_info_vartypes(ProcInfo, CalleeVarTypes0),
|
|
proc_info_varset(ProcInfo, CalleeVarSet),
|
|
proc_info_typeinfo_varmap(ProcInfo, CalleeTypeInfoVarMap0),
|
|
|
|
% 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)
|
|
|
|
varset__merge_subst(TypeVarSet0, CalleeTypeVarSet,
|
|
TypeVarSet, TypeRenaming),
|
|
apply_substitution_to_type_map(CalleeVarTypes0, TypeRenaming,
|
|
CalleeVarTypes1),
|
|
|
|
% next, compute the type substitution and then apply it
|
|
|
|
% Note: there's 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.
|
|
|
|
map__apply_to_list(HeadVars, CalleeVarTypes1, HeadTypes),
|
|
map__apply_to_list(ArgVars, VarTypes0, ArgTypes),
|
|
|
|
pred_info_get_exist_quant_tvars(PredInfo, CalleeExistQVars),
|
|
inlining__get_type_substitution(HeadTypes, ArgTypes, HeadTypeParams,
|
|
CalleeExistQVars, TypeSubn),
|
|
|
|
% handle the common case of non-existentially typed preds specially,
|
|
% since we can do things more efficiently in that case
|
|
( CalleeExistQVars = [] ->
|
|
% update types in callee only
|
|
apply_rec_substitution_to_type_map(CalleeVarTypes1,
|
|
TypeSubn, CalleeVarTypes),
|
|
VarTypes1 = VarTypes0
|
|
;
|
|
% update types in callee
|
|
apply_rec_substitution_to_type_map(CalleeVarTypes1,
|
|
TypeSubn, CalleeVarTypes),
|
|
% update types in caller
|
|
apply_rec_substitution_to_type_map(VarTypes0,
|
|
TypeSubn, VarTypes1)
|
|
),
|
|
|
|
% Now rename apart the variables in the called goal.
|
|
inlining__rename_goal(HeadVars, ArgVars, VarSet0, CalleeVarSet,
|
|
VarSet, VarTypes1, CalleeVarTypes, VarTypes, Subn,
|
|
CalledGoal, Goal),
|
|
|
|
apply_substitutions_to_var_map(CalleeTypeInfoVarMap0,
|
|
TypeRenaming, TypeSubn, Subn, CalleeTypeInfoVarMap1),
|
|
map__merge(TypeInfoVarMap0, CalleeTypeInfoVarMap1,
|
|
TypeInfoVarMap).
|
|
|
|
inlining__get_type_substitution(HeadTypes, ArgTypes,
|
|
HeadTypeParams, CalleeExistQVars, TypeSubn) :-
|
|
( CalleeExistQVars = [] ->
|
|
( type_list_subsumes(HeadTypes, ArgTypes, TypeSubn0) ->
|
|
TypeSubn = TypeSubn0
|
|
;
|
|
% The head types should always be unifiable with the
|
|
% actual argument types, otherwise it is a type error
|
|
% that should have been detected by typechecking.
|
|
% But polymorphism.m introduces type-incorrect code --
|
|
% e.g. compare(Res, EnumA, EnumB) gets converted
|
|
% into builtin_compare_int(Res, EnumA, EnumB), which
|
|
% is a type error since it assumes that an enumeration
|
|
% is an int. In those cases, we don't need to
|
|
% worry about the type substitution.
|
|
% (Perhaps it would be better if polymorphism introduced
|
|
% calls to unsafe_type_cast/2 for such cases.)
|
|
map__init(TypeSubn)
|
|
)
|
|
;
|
|
% for calls to existentially type preds, we may need to
|
|
% bind type variables in the caller, not just those in
|
|
% the callee
|
|
(
|
|
map__init(TypeSubn0),
|
|
type_unify_list(HeadTypes, ArgTypes, HeadTypeParams,
|
|
TypeSubn0, TypeSubn1)
|
|
->
|
|
TypeSubn = TypeSubn1
|
|
;
|
|
error("inlining.m: type unification failed")
|
|
)
|
|
).
|
|
|
|
inlining__rename_goal(HeadVars, ArgVars, VarSet0, CalleeVarSet,
|
|
VarSet, VarTypes1, CalleeVarTypes, VarTypes, Subn,
|
|
CalledGoal, Goal) :-
|
|
map__from_corresponding_lists(HeadVars, ArgVars, Subn0),
|
|
varset__vars(CalleeVarSet, CalleeListOfVars),
|
|
goal_util__create_variables(CalleeListOfVars, VarSet0,
|
|
VarTypes1, Subn0, CalleeVarTypes, CalleeVarSet,
|
|
VarSet, VarTypes, Subn),
|
|
goal_util__must_rename_vars_in_goal(CalledGoal, Subn, Goal).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% inlining__inlining_in_disj is used for both disjunctions and
|
|
% parallel conjunctions.
|
|
|
|
:- pred inlining__inlining_in_disj(list(hlds_goal), list(hlds_goal),
|
|
inline_info, inline_info).
|
|
:- mode inlining__inlining_in_disj(in, out, in, out) is det.
|
|
|
|
inlining__inlining_in_disj([], []) --> [].
|
|
inlining__inlining_in_disj([Goal0 | Goals0], [Goal | Goals]) -->
|
|
inlining__inlining_in_goal(Goal0, Goal),
|
|
inlining__inlining_in_disj(Goals0, Goals).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred inlining__inlining_in_cases(list(case), list(case), inline_info,
|
|
inline_info).
|
|
:- mode inlining__inlining_in_cases(in, out, in, out) is det.
|
|
|
|
inlining__inlining_in_cases([], []) --> [].
|
|
inlining__inlining_in_cases([case(Cons, Goal0) | Goals0],
|
|
[case(Cons, Goal) | Goals]) -->
|
|
inlining__inlining_in_goal(Goal0, Goal),
|
|
inlining__inlining_in_cases(Goals0, Goals).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred inlining__inlining_in_conj(list(hlds_goal), list(hlds_goal),
|
|
inline_info, inline_info).
|
|
:- mode inlining__inlining_in_conj(in, out, in, out) is det.
|
|
|
|
% Since a single goal may become a conjunction,
|
|
% we flatten the conjunction as we go.
|
|
|
|
inlining__inlining_in_conj([], []) --> [].
|
|
inlining__inlining_in_conj([Goal0 | Goals0], Goals) -->
|
|
inlining__inlining_in_goal(Goal0, Goal1),
|
|
{ goal_to_conj_list(Goal1, Goal1List) },
|
|
inlining__inlining_in_conj(Goals0, Goals1),
|
|
{ list__append(Goal1List, Goals1, Goals) }.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Check to see if we should inline a call.
|
|
%
|
|
% Fails if the called predicate is a builtin or is imported.
|
|
%
|
|
% Succeeds if the called predicate has an annotation
|
|
% indicating that it should be inlined, or if the goal
|
|
% is a conjunction of builtins.
|
|
|
|
:- pred inlining__should_inline_proc(pred_id, proc_id, builtin_state,
|
|
set(pred_proc_id), pred_markers, module_info).
|
|
:- mode inlining__should_inline_proc(in, in, in, in, in, in) is semidet.
|
|
|
|
inlining__should_inline_proc(PredId, ProcId, BuiltinState, InlinedProcs,
|
|
CallingPredMarkers, ModuleInfo) :-
|
|
|
|
% don't inline builtins, the code generator will handle them
|
|
|
|
BuiltinState = not_builtin,
|
|
|
|
% don't try to inline imported predicates, since we don't
|
|
% have the code for them.
|
|
|
|
module_info_pred_proc_info(ModuleInfo, PredId, ProcId, PredInfo,
|
|
ProcInfo),
|
|
\+ pred_info_is_imported(PredInfo),
|
|
% this next line catches the case of locally defined
|
|
% unification predicates for imported types.
|
|
\+ (
|
|
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_eval_method(ProcInfo, eval_normal),
|
|
|
|
% don't inlining anything we have been specifically requested
|
|
% not to inline.
|
|
|
|
\+ pred_info_requested_no_inlining(PredInfo),
|
|
|
|
% Don't inline Aditi procedures into non-Aditi procedures,
|
|
% since this could result in joins being performed by
|
|
% backtracking rather than by more efficient methods in
|
|
% the database.
|
|
|
|
pred_info_get_markers(PredInfo, CalledPredMarkers),
|
|
\+ (
|
|
\+ check_marker(CallingPredMarkers, aditi),
|
|
check_marker(CalledPredMarkers, aditi)
|
|
),
|
|
|
|
% OK, we could inline it - but should we? Apply our heuristic.
|
|
|
|
(
|
|
pred_info_requested_inlining(PredInfo)
|
|
;
|
|
set__member(proc(PredId, ProcId), InlinedProcs)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|