Files
mercury/compiler/structure_reuse.versions.m
2018-04-07 18:25:43 +10:00

530 lines
22 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ff=unix ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 2006-2012 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.
%-----------------------------------------------------------------------------%
%
% File: structure_reuse.versions.m.
% Main authors: nancy.
%
% Provide the functionality to create optimised versions of those procedures
% for which reuse was detected.
%
%-----------------------------------------------------------------------------%
:- module structure_reuse.versions.
:- interface.
:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_pred.
:- import_module transform_hlds.ctgc.structure_reuse.domain.
%-----------------------------------------------------------------------------%
% For each of the entries in the reuse table:
%
% * if the listed reuse is conditional, then duplicate the
% pred-info/proc-info of the original procedure, changing all potential
% reuse annotation to real reuses;
% * if the listed reuse is unconditional, then no duplication is needed,
% yet the goal needs to be traversed to correctly replace all
% procedure calls to calls to reuse versions whenever needed.
% * if the listed reuse is "no reuse", then obviously, nothing needs to
% be done.
%
% This process updates the module information by adding the new predicates,
% and recording the pred-proc-id to the reuse pred-proc-id mappings in
% module_info.
%
:- pred create_reuse_procedures(reuse_as_table::in, reuse_as_table::out,
module_info::in, module_info::out) is det.
% Create a copy of the predicate/procedure information specified by the
% given pred_proc_id, and return the pred_proc_id of that copy. The copy
% is not altered w.r.t. structure reuse. It is a plain copy, nothing more
% than that.
%
:- pred create_fresh_pred_proc_info_copy(pred_proc_id::in, no_clobber_args::in,
pred_proc_id::out, module_info::in, module_info::out) is det.
% Create a fake reuse procedure that simply calls the non-reuse procedure.
%
:- pred create_fake_reuse_procedure(pred_proc_id::in, no_clobber_args::in,
module_info::in, module_info::out) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.
:- import_module check_hlds.mode_util.
:- import_module hlds.hlds_goal.
:- import_module hlds.passes_aux.
:- import_module hlds.pred_table.
:- import_module hlds.quantification.
:- import_module hlds.status.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.op_mode.
:- import_module libs.options.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.
:- import_module parse_tree.prog_util.
:- import_module transform_hlds.ctgc.structure_reuse.analysis.
:- import_module bimap.
:- import_module bool.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module require.
:- import_module set.
%-----------------------------------------------------------------------------%
:- type reuse_name == sym_name.
:- func generate_reuse_name(module_info, pred_proc_id, list(int)) = reuse_name.
generate_reuse_name(ModuleInfo, PPId, NoClobbers) = ReuseName :-
PPId = proc(_, ProcId),
module_info_pred_proc_info(ModuleInfo, PPId, PredInfo, _ProcInfo),
PredModule = pred_info_module(PredInfo),
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
PredName = pred_info_name(PredInfo),
proc_id_to_int(ProcId, ProcInt),
make_pred_name(PredModule, "ctgc", yes(PredOrFunc), PredName,
newpred_structure_reuse(ProcInt, NoClobbers), ReuseName).
%-----------------------------------------------------------------------------%
create_reuse_procedures(!ReuseTable, !ModuleInfo) :-
% This process can be split into separate steps:
% - determine all the pred-proc-ids of procedure with conditional reuse;
% - create duplicates of these procedures;
% - traverse all these procedures + the procedures with unconditional reuse
% to correctly update the reuse annotations.
% Get the list of conditional reuse procedures already created.
ExistingReusePPIds = bimap.coordinates(!.ReuseTable ^ reuse_version_map),
ExistingReusePPIdsSet = set.from_list(ExistingReusePPIds),
map.foldl2(divide_reuse_procs(ExistingReusePPIdsSet),
!.ReuseTable ^ reuse_info_map, [], CondOrigPPIds, [], UncondOrigPPIds),
% Create duplicates of the procedures which have conditional reuse. The
% "intermediate" reuse procedures will already have been created during the
% analysis, so this creates just the reuse versions where all possible
% arguments are potentially reusable.
list.map_foldl2(maybe_create_full_reuse_proc_copy,
CondOrigPPIds, ReuseCondPPIds, !ModuleInfo, !ReuseTable),
% Process all the goals to update the reuse annotations. In the reuse
% versions of procedures we can take advantage of potential reuse
% opportunities.
list.foldl(check_cond_process_proc(convert_potential_reuse, !.ReuseTable),
ReuseCondPPIds, !ModuleInfo),
list.foldl(check_cond_process_proc(convert_potential_reuse, !.ReuseTable),
ExistingReusePPIds, !ModuleInfo),
% In the original procedures, only the unconditional reuse opportunities
% can be taken.
list.foldl(process_proc(leave_potential_reuse, !.ReuseTable),
CondOrigPPIds, !ModuleInfo),
list.foldl(process_proc(leave_potential_reuse, !.ReuseTable),
UncondOrigPPIds, !ModuleInfo).
% Separate procedures in the reuse table into those with some conditional
% reuse opportunities, and those with only unconditional reuse.
% Skip any procedure which is already a reuse copy of another procedure.
%
:- pred divide_reuse_procs(set(pred_proc_id)::in,
pred_proc_id::in, reuse_as_and_status::in,
list(pred_proc_id)::in, list(pred_proc_id)::out,
list(pred_proc_id)::in, list(pred_proc_id)::out) is det.
divide_reuse_procs(ExistingReusePPIdsSet, PPId, ReuseAs_Status,
!CondPPIds, !UncondPPIds) :-
ReuseAs_Status = reuse_as_and_status(ReuseAs, _),
( if set.contains(ExistingReusePPIdsSet, PPId) then
true
else if reuse_as_conditional_reuses(ReuseAs) then
!:CondPPIds = [PPId | !.CondPPIds]
else if reuse_as_all_unconditional_reuses(ReuseAs) then
!:UncondPPIds = [PPId | !.UncondPPIds]
else if reuse_as_no_reuses(ReuseAs) then
true
else
unexpected($pred, "conditions failed")
).
:- pred maybe_create_full_reuse_proc_copy(pred_proc_id::in, pred_proc_id::out,
module_info::in, module_info::out, reuse_as_table::in, reuse_as_table::out)
is det.
maybe_create_full_reuse_proc_copy(PPId, NewPPId, !ModuleInfo, !ReuseTable) :-
NoClobbers = [],
( if
reuse_as_table_search_reuse_version_proc(!.ReuseTable,
PPId, NoClobbers, _)
then
unexpected($pred, "procedure already exists")
else
true
),
create_fresh_pred_proc_info_copy(PPId, NoClobbers, NewPPId, !ModuleInfo),
( if reuse_as_table_search(!.ReuseTable, PPId, ReuseAs_Status) then
reuse_as_table_set(NewPPId, ReuseAs_Status, !ReuseTable),
reuse_as_table_insert_reuse_version_proc(PPId, NoClobbers, NewPPId,
!ReuseTable)
else
unexpected($pred, "no reuse information")
).
%-----------------------------------------------------------------------------%
create_fresh_pred_proc_info_copy(PPId, NoClobbers, NewPPId, !ModuleInfo) :-
module_info_pred_proc_info(!.ModuleInfo, PPId, PredInfo0, ProcInfo0),
ReusePredName = generate_reuse_name(!.ModuleInfo, PPId, NoClobbers),
PPId = proc(PredId, _),
create_fresh_pred_proc_info_copy_2(PredId, PredInfo0, ProcInfo0,
ReusePredName, ReusePredInfo, ReuseProcId),
NewPPId = proc(ReusePredId, ReuseProcId),
module_info_get_predicate_table(!.ModuleInfo, PredTable0),
predicate_table_insert(ReusePredInfo, ReusePredId, PredTable0, PredTable),
module_info_set_predicate_table(PredTable, !ModuleInfo),
module_info_get_structure_reuse_preds(!.ModuleInfo, ReusePreds0),
set.insert(ReusePredId, ReusePreds0, ReusePreds),
module_info_set_structure_reuse_preds(ReusePreds, !ModuleInfo).
:- pred create_fresh_pred_proc_info_copy_2(pred_id::in, pred_info::in,
proc_info::in, reuse_name::in, pred_info::out, proc_id::out) is det.
create_fresh_pred_proc_info_copy_2(PredId, PredInfo, ProcInfo, ReusePredName,
ReusePredInfo, ReuseProcId) :-
ModuleName = pred_info_module(PredInfo),
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
pred_info_get_context(PredInfo, ProgContext),
pred_info_get_origin(PredInfo, PredOrigin),
pred_info_get_status(PredInfo, PredStatus0),
% If the predicate was opt_imported then the specialised copy should be
% local, otherwise it will be eliminated by dead proc elimination.
( if PredStatus0 = pred_status(status_opt_imported) then
PredStatus = pred_status(status_local)
else
PredStatus = PredStatus0
),
pred_info_get_markers(PredInfo, PredMarkers),
pred_info_get_arg_types(PredInfo, MerTypes),
pred_info_get_typevarset(PredInfo, TVarset),
pred_info_get_exist_quant_tvars(PredInfo, ExistQTVars),
pred_info_get_class_context(PredInfo, ProgConstraints),
pred_info_get_assertions(PredInfo, AssertIds),
pred_info_get_var_name_remap(PredInfo, VarNameRemap),
NewPredOrigin = origin_transformed(transform_structure_reuse, PredOrigin,
PredId),
pred_info_create(ModuleName, ReusePredName, PredOrFunc, ProgContext,
NewPredOrigin, PredStatus, PredMarkers, MerTypes, TVarset,
ExistQTVars, ProgConstraints, AssertIds, VarNameRemap,
ProcInfo, ReuseProcId, ReusePredInfo).
%-----------------------------------------------------------------------------%
:- type convert_potential_reuse
---> convert_potential_reuse
; leave_potential_reuse.
% When generating target code, we may find a set of reuse conditions on a
% procedure which are *harsher* than the reuse conditions that we found
% during the `--make-analysis-registry' step. This can happen due extra
% analysis information gathered for other modules in the meantime. In that
% case, we may have external callers to the procedure which have verified
% only against the *laxer* reuse conditions.
%
% Hence we need to be careful that we don't generate any code which
% violates the weaker reuse conditions. One (conservative) way to do that
% is to ignore all the potential reuse annotations and only use the
% unconditional reuse annotations.
%
% XXX the same problem occurs with `--intermodule-optimisation'
%
:- pred check_cond_process_proc(convert_potential_reuse::in,
reuse_as_table::in, pred_proc_id::in, module_info::in, module_info::out)
is det.
check_cond_process_proc(ConvertPotentialReuse, ReuseTable, ReusePPId,
!ModuleInfo) :-
module_info_get_globals(!.ModuleInfo, Globals),
globals.lookup_bool_option(Globals, intermodule_analysis,
IntermodAnalysis),
globals.get_op_mode(Globals, OpMode),
( if
IntermodAnalysis = yes,
OpMode \= opm_top_args(opma_augment(opmau_make_analysis_registry))
then
structure_reuse_answer_harsher_than_in_analysis_registry(!.ModuleInfo,
ReuseTable, ReusePPId, IsHarsher)
else
IsHarsher = no
),
(
IsHarsher = yes,
% Ignoring potential reuse is equivalent to having only unconditional
% structure reuse.
process_proc(leave_potential_reuse, ReuseTable, ReusePPId, !ModuleInfo)
;
IsHarsher = no,
process_proc(ConvertPotentialReuse, ReuseTable, ReusePPId, !ModuleInfo)
).
% Process the goal of the procedure with the given pred_proc_id so that
% all potential reuses are replaced by real reuses, and all calls to
% procedures that have a reuse version are replaced by calls to their
% reuse version (if of course, that is in accordance with the reuse
% annotations).
%
:- pred process_proc(convert_potential_reuse::in, reuse_as_table::in,
pred_proc_id::in, module_info::in, module_info::out) is det.
process_proc(ConvertPotentialReuse, ReuseTable, PPId, !ModuleInfo) :-
trace [io(!IO)] (
write_proc_progress_message("(reuse version) ", PPId, !.ModuleInfo,
!IO)
),
some [!ProcInfo] (
module_info_pred_proc_info(!.ModuleInfo, PPId, PredInfo0, !:ProcInfo),
pred_info_get_status(PredInfo0, PredStatus),
( if PredStatus = pred_status(status_imported(_)) then
% The bodies may contain junk, so don't try to process.
true
else
proc_info_get_goal(!.ProcInfo, Goal0),
process_goal(ConvertPotentialReuse, ReuseTable, !.ModuleInfo,
Goal0, Goal),
proc_info_set_goal(Goal, !ProcInfo),
% A dead variable needs to appear in the non-local set of the
% construction unification in which its space is reused, so we
% requantify. Then we recompute instmap deltas with the updated
% non-local sets.
requantify_proc_general(ordinary_nonlocals_no_lambda, !ProcInfo),
recompute_instmap_delta_proc(
do_not_recompute_atomic_instmap_deltas,
!ProcInfo, !ModuleInfo),
module_info_set_pred_proc_info(PPId, PredInfo0, !.ProcInfo,
!ModuleInfo)
)
).
:- pred process_goal(convert_potential_reuse::in, reuse_as_table::in,
module_info::in, hlds_goal::in, hlds_goal::out) is det.
process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo, !Goal) :-
!.Goal = hlds_goal(GoalExpr0, GoalInfo0),
(
GoalExpr0 = conj(ConjType, Goals0),
list.map(process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo),
Goals0, Goals),
GoalExpr = conj(ConjType, Goals),
!:Goal = hlds_goal(GoalExpr, GoalInfo0)
;
GoalExpr0 = plain_call(CalleePredId, CalleeProcId, Args, BI, UC,
CalleePredName),
ReuseDescription0 = goal_info_get_reuse(GoalInfo0),
( if
% If the reuse description already says "reuse", then this is
% a call to a procedure which might have specified conditions, yet
% whose conditions are always met, hence do not imply conditions on
% the procedure in which this call appears. We must therefore
% make sure to call the appropriate version of the called
% procedure.
ReuseDescription0 = reuse(reuse_call(_CondDescr, NoClobbers))
then
determine_reuse_version(ReuseTable, ModuleInfo, CalleePredId,
CalleeProcId, CalleePredName, NoClobbers, ReuseCalleePredId,
ReuseCalleeProcId, ReuseCalleePredName),
GoalExpr = plain_call(ReuseCalleePredId, ReuseCalleeProcId,
Args, BI, UC, ReuseCalleePredName),
!:Goal = hlds_goal(GoalExpr, GoalInfo0)
else if
ReuseDescription0 = potential_reuse(reuse_call(CondDescr,
NoClobbers)),
ConvertPotentialReuse = convert_potential_reuse
then
ConvertPotentialReuse = convert_potential_reuse,
% Replace the call to the reuse version, and change the
% potential reuse annotation to a real annotation.
determine_reuse_version(ReuseTable, ModuleInfo,
CalleePredId, CalleeProcId, CalleePredName, NoClobbers,
ReuseCalleePredId, ReuseCalleeProcId, ReuseCalleePredName),
GoalExpr = plain_call(ReuseCalleePredId, ReuseCalleeProcId,
Args, BI, UC, ReuseCalleePredName),
ReuseDescription = reuse(reuse_call(CondDescr, NoClobbers)),
goal_info_set_reuse(ReuseDescription, GoalInfo0, GoalInfo),
!:Goal = hlds_goal(GoalExpr, GoalInfo)
else
true
)
;
GoalExpr0 = generic_call(_, _, _, _, _)
;
GoalExpr0 = unify(_, _, _, Unification0, _),
ReuseDescription0 = goal_info_get_reuse(GoalInfo0),
(
(
ReuseDescription0 = reuse(Descr)
;
ReuseDescription0 = potential_reuse(Descr),
ConvertPotentialReuse = convert_potential_reuse
),
ReuseDescription = reuse(Descr),
unification_set_reuse(Descr, Unification0, Unification),
GoalExpr = GoalExpr0 ^ unify_kind := Unification,
goal_info_set_reuse(ReuseDescription, GoalInfo0, GoalInfo),
!:Goal = hlds_goal(GoalExpr, GoalInfo)
;
ReuseDescription0 = potential_reuse(_),
ConvertPotentialReuse = leave_potential_reuse
;
ReuseDescription0 = no_reuse_info
;
ReuseDescription0 = no_possible_reuse
;
ReuseDescription0 = missed_reuse(_)
)
;
GoalExpr0 = disj(Goals0),
list.map(process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo),
Goals0, Goals),
GoalExpr = disj(Goals),
!:Goal = hlds_goal(GoalExpr, GoalInfo0)
;
GoalExpr0 = switch(A, B, Cases0),
list.map(process_case(ConvertPotentialReuse, ReuseTable, ModuleInfo),
Cases0, Cases),
GoalExpr = switch(A, B, Cases),
!:Goal = hlds_goal(GoalExpr, GoalInfo0)
;
% XXX To check and compare with the theory.
GoalExpr0 = negation(_Goal)
;
GoalExpr0 = scope(Reason, SubGoal0),
( if Reason = from_ground_term(_, from_ground_term_construct) then
true
else
process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo,
SubGoal0, SubGoal),
GoalExpr = scope(Reason, SubGoal),
!:Goal = hlds_goal(GoalExpr, GoalInfo0)
)
;
GoalExpr0 = if_then_else(Vars, IfGoal0, ThenGoal0, ElseGoal0),
process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo,
IfGoal0, IfGoal),
process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo,
ThenGoal0, ThenGoal),
process_goal(ConvertPotentialReuse, ReuseTable, ModuleInfo,
ElseGoal0, ElseGoal),
GoalExpr = if_then_else(Vars, IfGoal, ThenGoal, ElseGoal),
!:Goal = hlds_goal(GoalExpr, GoalInfo0)
;
GoalExpr0 = call_foreign_proc(_Attrs, _ForeignPredId, _ForeignProcId,
_Args, _ExtraArgs, _MaybeTraceRuntimeCond, _Impl)
;
GoalExpr0 = shorthand(_),
% These should have been expanded out by now.
unexpected($pred, "shorthand")
).
:- pred unification_set_reuse(short_reuse_description::in,
unification::in, unification::out) is det.
unification_set_reuse(ShortReuseDescription, !Unification) :-
( if
HowToConstruct0 = !.Unification ^ construct_how,
ShortReuseDescription = cell_reused(DeadVar, _, PossibleConsIds,
CellsToUpdate)
then
(
HowToConstruct0 = construct_statically
% Leave static terms as-is.
;
( HowToConstruct0 = construct_dynamically
; HowToConstruct0 = construct_in_region(_)
; HowToConstruct0 = reuse_cell(_)
),
CellToReuse = cell_to_reuse(DeadVar, PossibleConsIds,
CellsToUpdate),
HowToConstruct = reuse_cell(CellToReuse),
!Unification ^ construct_how := HowToConstruct
)
else
true
).
:- pred determine_reuse_version(reuse_as_table::in, module_info::in,
pred_id::in, proc_id::in, sym_name::in, list(int)::in,
pred_id::out, proc_id::out, reuse_name::out) is det.
determine_reuse_version(ReuseTable, ModuleInfo, PredId, ProcId, PredName,
NoClobbers, ReusePredId, ReuseProcId, ReusePredName) :-
( if
reuse_as_table_search_reuse_version_proc(ReuseTable,
proc(PredId, ProcId), NoClobbers, Result)
then
Result = proc(ReusePredId, ReuseProcId),
module_info_pred_info(ModuleInfo, ReusePredId, ReusePredInfo),
ModuleName = pred_info_module(ReusePredInfo),
Name = pred_info_name(ReusePredInfo),
ReusePredName = qualified(ModuleName, Name)
else
ReusePredId = PredId,
ReuseProcId = ProcId,
ReusePredName = PredName
).
:- pred process_case(convert_potential_reuse::in, reuse_as_table::in,
module_info::in, case::in, case::out) is det.
process_case(ConvertPotentialReuse, ReuseMap, ModuleInfo, Case0, Case) :-
Case0 = case(MainConsId, OtherConsIds, Goal0),
process_goal(ConvertPotentialReuse, ReuseMap, ModuleInfo, Goal0, Goal),
Case = case(MainConsId, OtherConsIds, Goal).
%-----------------------------------------------------------------------------%
create_fake_reuse_procedure(PPId, NoClobbers, !ModuleInfo) :-
PPId = proc(PredId, ProcId),
module_info_pred_proc_info(!.ModuleInfo, PPId, OldPredInfo, OldProcInfo),
OldPredModule = pred_info_module(OldPredInfo),
OldPredName = pred_info_name(OldPredInfo),
proc_info_interface_determinism(OldProcInfo, Determinism),
create_fresh_pred_proc_info_copy(PPId, NoClobbers, NewPPId, !ModuleInfo),
some [!PredInfo, !ProcInfo] (
module_info_pred_proc_info(!.ModuleInfo, NewPPId, !:PredInfo,
!:ProcInfo),
proc_info_get_goal(!.ProcInfo, Body),
Body = hlds_goal(_, GoalInfo0),
proc_info_get_headvars(!.ProcInfo, HeadVars),
GoalExpr = plain_call(PredId, ProcId, HeadVars, not_builtin, no,
qualified(OldPredModule, OldPredName)),
goal_info_set_determinism(Determinism, GoalInfo0, GoalInfo),
Goal = hlds_goal(GoalExpr, GoalInfo),
proc_info_set_goal(Goal, !ProcInfo),
module_info_set_pred_proc_info(NewPPId, !.PredInfo, !.ProcInfo,
!ModuleInfo)
).
%-----------------------------------------------------------------------------%
:- end_module structure_reuse.versions.
%-----------------------------------------------------------------------------%