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

1120 lines
45 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury 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.analysis.m.
% Main authors: nancy, wangp.
%
% Implementation of the structure reuse analysis (compile-time garbage
% collection system): each procedure is analysed to see whether some
% of the terms it manipulates become garbage thus making it possible
% to reuse that garbage straight away for creating new terms.
%
% Structure reuse is broken up into three phases:
% * the direct reuse analysis (structure_reuse.direct.m)
% * the indirect analysis (structure_reuse.indirect.m)
% * and the generation of the optimised procedures.
%
% The following example shows instances of direct and indirect reuse:
%
% list.append(H1, H2, H3) :-
% (
% H1 => [],
% H3 := H2
% ;
% % Cell H1 dies provided some condition about the
% % structure sharing of H1 is true. A deconstruction
% % generating a dead cell, followed by a
% % construction reusing that cell, is called a direct
% % reuse.
% H1 => [X | Xs],
%
% % If the condition about the structure sharing of H1
% % is true then we can call the version of list.append
% % which does reuse. Calling the optimised version here leads
% % to a new condition to be met by the headvars of any
% % call to the resulting optimised version of append.
% % This is an indirect reuse.
% list.append(Xs, H2, Zs),
%
% % Reuse the dead cell H1. This is a direct reuse.
% H3 <= [X | Zs]
% ).
%
%-----------------------------------------------------------------------------%
:- module transform_hlds.ctgc.structure_reuse.analysis.
:- interface.
:- import_module analysis.
:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_pred.
:- import_module transform_hlds.ctgc.structure_reuse.domain.
:- import_module bool.
:- import_module io.
%-----------------------------------------------------------------------------%
% Perform structure reuse analysis on the procedures defined in the
% current module.
%
:- pred perform_structure_reuse_analysis(module_info::in, module_info::out,
io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
:- type structure_reuse_call.
:- type structure_reuse_answer.
:- type structure_reuse_func_info.
:- instance analysis(structure_reuse_func_info, structure_reuse_call,
structure_reuse_answer).
:- instance call_pattern(structure_reuse_func_info, structure_reuse_call).
:- instance partial_order(structure_reuse_func_info, structure_reuse_call).
:- instance to_term(structure_reuse_call).
:- instance answer_pattern(structure_reuse_func_info, structure_reuse_answer).
:- instance partial_order(structure_reuse_func_info, structure_reuse_answer).
:- instance to_term(structure_reuse_answer).
:- pred structure_reuse_answer_harsher_than_in_analysis_registry(
module_info::in, reuse_as_table::in, pred_proc_id::in, bool::out) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module hlds.goal_path.
:- import_module hlds.hlds_out.
:- import_module hlds.hlds_out.hlds_out_util.
:- import_module hlds.passes_aux.
:- import_module hlds.pred_table.
:- import_module hlds.status.
:- import_module hlds.vartypes.
:- import_module libs.
:- import_module libs.file_util.
:- 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_ctgc.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_data_pragma.
:- import_module parse_tree.prog_type.
:- import_module parse_tree.set_of_var.
:- import_module transform_hlds.ctgc.selector.
:- import_module transform_hlds.ctgc.structure_reuse.direct.
:- import_module transform_hlds.ctgc.structure_reuse.indirect.
:- import_module transform_hlds.ctgc.structure_reuse.lbu.
:- import_module transform_hlds.ctgc.structure_reuse.lfu.
:- import_module transform_hlds.ctgc.structure_reuse.versions.
:- import_module transform_hlds.ctgc.structure_sharing.
:- import_module transform_hlds.ctgc.structure_sharing.domain.
:- import_module transform_hlds.intermod.
:- import_module transform_hlds.mmc_analysis.
:- import_module bimap.
:- import_module int.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module require.
:- import_module set.
:- import_module string.
:- import_module term.
:- import_module term_conversion.
%-----------------------------------------------------------------------------%
perform_structure_reuse_analysis(!ModuleInfo, !IO):-
module_info_get_globals(!.ModuleInfo, Globals),
globals.lookup_bool_option(Globals, very_verbose, VeryVerbose),
% Load all available structure sharing information into a sharing table.
SharingTable = load_structure_sharing_table(!.ModuleInfo),
% Process all imported reuse information.
globals.lookup_bool_option(Globals, intermodule_analysis,
IntermodAnalysis),
(
IntermodAnalysis = yes,
% Load structure reuse answers from the analysis registry into a reuse
% table. Add procedures to the module as necessary. Look up the
% requests made for procedures in this module by other modules.
process_intermod_analysis_reuse(!ModuleInfo, ReuseTable0,
ExternalRequests, MustHaveReuseVersions)
;
IntermodAnalysis = no,
% Convert imported structure reuse information into structure reuse
% information, then load the available reuse information into a reuse
% table.
%
% There is no way to request specific reuse versions of procedures
% across module boundaries using the old intermodule optimisation
% system.
process_imported_reuse(!ModuleInfo),
ReuseTable0 = load_structure_reuse_table(!.ModuleInfo),
ExternalRequests = [],
MustHaveReuseVersions = []
),
some [!ReuseTable] (
!:ReuseTable = ReuseTable0,
% Pre-annotate each of the goals with "Local Forward Use" and
% "Local Backward Use" information, and fill in all the goal_id slots
% as well.
trace [io(!TIO)] (
maybe_write_string(VeryVerbose,
"% Annotating in use information...", !TIO)
),
process_all_nonimported_procs(update_proc(annotate_in_use_information),
!ModuleInfo),
trace [io(!TIO)] (
maybe_write_string(VeryVerbose, "done.\n", !TIO),
maybe_write_string(VeryVerbose,
"% Reuse table before intermediate reuse:\n", !TIO),
reuse_as_table_maybe_dump(VeryVerbose, !.ModuleInfo, !.ReuseTable,
!TIO)
),
% Create copies of externally requested procedures. This must be done
% after the in-use annotations have been added to the procedures being
% copied.
list.map_foldl2(make_intermediate_reuse_proc, ExternalRequests,
_NewPPIds, !ReuseTable, !ModuleInfo),
% Determine information about possible direct reuses.
trace [io(!TIO)] (
maybe_write_string(VeryVerbose,
"% Reuse table after intermediate reuse:\n", !TIO),
reuse_as_table_maybe_dump(VeryVerbose, !.ModuleInfo, !.ReuseTable,
!TIO),
maybe_write_string(VeryVerbose, "% Direct reuse...\n", !TIO)
),
direct_reuse_pass(SharingTable, !ModuleInfo, !ReuseTable),
trace [io(!TIO)] (
maybe_write_string(VeryVerbose, "% Direct reuse: done.\n", !TIO),
reuse_as_table_maybe_dump(VeryVerbose, !.ModuleInfo, !.ReuseTable,
!TIO)
),
% Determine information about possible indirect reuses.
trace [io(!TIO)] (
maybe_write_string(VeryVerbose, "% Indirect reuse...\n", !TIO)
),
indirect_reuse_pass(SharingTable, !ModuleInfo, !ReuseTable, DepProcs0,
InternalRequests, IntermodRequests0),
trace [io(!TIO)] (
maybe_write_string(VeryVerbose, "% Indirect reuse: done.\n", !TIO),
reuse_as_table_maybe_dump(VeryVerbose, !.ModuleInfo, !.ReuseTable,
!TIO)
),
% Handle requests for "intermediate" reuse versions of procedures
% and repeat the analyses.
globals.lookup_int_option(Globals, structure_reuse_repeat, Repeats),
handle_structure_reuse_requests(Repeats, SharingTable,
InternalRequests, !ReuseTable, !ModuleInfo, DepProcs0, DepProcs,
IntermodRequests0, IntermodRequests),
% Create reuse versions of procedures. Update goals to reuse cells
% and call reuse versions of procedures.
create_reuse_procedures(!ReuseTable, !ModuleInfo),
ReuseTable = !.ReuseTable
),
(
IntermodAnalysis = no,
% Create forwarding procedures for procedures which we thought had
% conditional reuse when making the `.opt' file, but with further
% information (say, from `.trans_opt' files) we decide has no reuse
% opportunities. Otherwise other modules may contain references to
% reuse versions of procedures which we never produce.
maybe_create_forwarding_procedures_intermod_opt(ReuseTable0,
ReuseTable, !ModuleInfo)
;
IntermodAnalysis = yes,
% We may need to create forwarding procedures for procedures which had
% conditional reuse in the `.analysis' file, but which have no reuse
% or unconditional reuse now. We should only need to do this for
% procedures with NoClobbers = [].
list.foldl(
maybe_create_forwarding_procedures_intermod_analysis(ReuseTable),
MustHaveReuseVersions, !ModuleInfo)
),
ReuseTable = reuse_as_table(ReuseInfoMap, ReuseVersionMap),
% Record the results of the reuse table into the HLDS.
% This is mainly to show the reuse information in HLDS dumps as no later
% passes need the information.
map.foldl(save_reuse_in_module_info, ReuseInfoMap, !ModuleInfo),
module_info_get_proc_analysis_kinds(!.ModuleInfo, ProcAnalysisKinds0),
set.insert(pak_structure_reuse, ProcAnalysisKinds0, ProcAnalysisKinds),
module_info_set_proc_analysis_kinds(ProcAnalysisKinds, !ModuleInfo),
% If making a `.analysis' file, record structure reuse results, analysis
% dependencies, assumed answers and requests in the analysis framework.
globals.get_op_mode(Globals, OpMode),
( if OpMode = opm_top_args(opma_augment(opmau_make_analysis_registry)) then
some [!AnalysisInfo] (
module_info_get_analysis_info(!.ModuleInfo, !:AnalysisInfo),
map.foldl(
record_structure_reuse_results(!.ModuleInfo, ReuseVersionMap),
ReuseInfoMap, !AnalysisInfo),
set.fold(handle_structure_reuse_dependency(!.ModuleInfo),
DepProcs, !AnalysisInfo),
set.fold(record_intermod_requests(!.ModuleInfo),
IntermodRequests, !AnalysisInfo),
module_info_set_analysis_info(!.AnalysisInfo, !ModuleInfo)
)
else
true
),
% Delete the reuse versions of procedures which turn out to have no reuse.
% Nothing should be calling them but dead procedure elimination won't
% remove them if they were created from exported procedures (so would be
% exported themselves).
module_info_get_predicate_table(!.ModuleInfo, PredTable0),
bimap.foldl(
remove_useless_reuse_proc(!.ModuleInfo, VeryVerbose, ReuseInfoMap),
ReuseVersionMap, PredTable0, PredTable),
module_info_set_predicate_table(PredTable, !ModuleInfo),
selector.reset_tables(!IO).
%-----------------------------------------------------------------------------%
% Create intermediate reuse versions of procedures according to the
% requests from indirect reuse analysis. We perform direct reuse
% analyses on the newly created procedures, then repeat indirect reuse
% analysis on all procedures in the module so that calls to the new
% procedures can be made. This may create new requests.
%
% XXX this is temporary only; we shouldn't be redoing so much work.
%
:- pred handle_structure_reuse_requests(int::in, sharing_as_table::in,
set(sr_request)::in, reuse_as_table::in, reuse_as_table::out,
module_info::in, module_info::out,
set(ppid_no_clobbers)::in, set(ppid_no_clobbers)::out,
set(sr_request)::in, set(sr_request)::out) is det.
handle_structure_reuse_requests(Repeats, SharingTable, Requests,
!ReuseTable, !ModuleInfo, !DepProcs, !IntermodRequests) :-
( if Repeats > 0 then
handle_structure_reuse_requests_2(Repeats, SharingTable, Requests,
!ReuseTable, !ModuleInfo, !DepProcs, !IntermodRequests)
else
true
).
:- pred handle_structure_reuse_requests_2(int::in, sharing_as_table::in,
set(sr_request)::in, reuse_as_table::in, reuse_as_table::out,
module_info::in, module_info::out,
set(ppid_no_clobbers)::in, set(ppid_no_clobbers)::out,
set(sr_request)::in, set(sr_request)::out) is det.
handle_structure_reuse_requests_2(Repeats, SharingTable, Requests,
!ReuseTable, !ModuleInfo, !DepProcs, !IntermodRequests) :-
module_info_get_globals(!.ModuleInfo, Globals),
globals.lookup_bool_option(Globals, very_verbose, VeryVerbose),
% Create copies of the requested procedures.
RequestList = set.to_sorted_list(Requests),
list.map_foldl2(make_intermediate_reuse_proc, RequestList, NewPPIds,
!ReuseTable, !ModuleInfo),
% Perform direct reuse analysis on the new procedures.
trace [io(!IO)] (
maybe_write_string(VeryVerbose, "% Repeating direct reuse...\n", !IO)
),
direct_reuse_process_specific_procs(SharingTable, NewPPIds,
!ModuleInfo, !ReuseTable),
trace [io(!IO)] (
maybe_write_string(VeryVerbose, "% done.\n", !IO)
),
% Rerun indirect reuse analysis on all procedures.
%
% XXX goals which already have reuse annotations don't need to be
% reanalysed. For old procedures (not the ones just created) we actually
% only need to check that calls which previously had no reuse opportunity
% might be able to call the new procedures.
trace [io(!IO)] (
maybe_write_string(VeryVerbose, "% Repeating indirect reuse...\n", !IO)
),
indirect_reuse_rerun(SharingTable, !ModuleInfo, !ReuseTable,
NewDepProcs, NewRequests, !IntermodRequests),
!:DepProcs = set.union(NewDepProcs, !.DepProcs),
trace [io(!IO)] (
maybe_write_string(VeryVerbose, "% done.\n", !IO)
),
( if set.is_empty(NewRequests) then
trace [io(!IO)] (
maybe_write_string(VeryVerbose,
"% No more structure reuse requests.\n", !IO)
)
else
trace [io(!IO)] (
maybe_write_string(VeryVerbose,
"% Outstanding structure reuse requests exist.\n", !IO)
),
handle_structure_reuse_requests(Repeats - 1, SharingTable, NewRequests,
!ReuseTable, !ModuleInfo, !DepProcs, !IntermodRequests)
).
% Create a new copy of a procedure to satisfy an intermediate reuse
% request, i.e. some of its arguments are prevented from being reused.
%
% The goal of the original procedure must already be annotated with in-use
% sets. For the new procedure, we simply add the head variables at the
% no-clobber argument positions to the forward-use set of each goal.
% We also remove any existing reuse annotations on the goals.
%
:- pred make_intermediate_reuse_proc(sr_request::in, pred_proc_id::out,
reuse_as_table::in, reuse_as_table::out, module_info::in, module_info::out)
is det.
make_intermediate_reuse_proc(sr_request(PPId, NoClobbers), NewPPId,
!ReuseTable, !ModuleInfo) :-
create_fresh_pred_proc_info_copy(PPId, NoClobbers, NewPPId, !ModuleInfo),
module_info_pred_proc_info(!.ModuleInfo, NewPPId, PredInfo, ProcInfo0),
proc_info_get_headvars(ProcInfo0, HeadVars),
get_numbered_args(1, NoClobbers, HeadVars, NoClobberVars),
add_vars_to_lfu(set_of_var.list_to_set(NoClobberVars),
ProcInfo0, ProcInfo),
module_info_set_pred_proc_info(NewPPId, PredInfo, ProcInfo, !ModuleInfo),
reuse_as_table_insert_reuse_version_proc(PPId, NoClobbers, NewPPId,
!ReuseTable).
:- pred get_numbered_args(int::in, list(int)::in,
list(prog_var)::in, list(prog_var)::out) is det.
get_numbered_args(_, [], _, []).
get_numbered_args(_, [_ | _], [], _) :-
unexpected($pred, "argument list too short").
get_numbered_args(I, [N | Ns], [Var | Vars], Selected) :-
( if I = N then
get_numbered_args(I + 1, Ns, Vars, Selected0),
Selected = [Var | Selected0]
else
get_numbered_args(I + 1, [N | Ns], Vars, Selected)
).
%-----------------------------------------------------------------------------%
:- pred maybe_create_forwarding_procedures_intermod_opt(reuse_as_table::in,
reuse_as_table::in, module_info::in, module_info::out) is det.
maybe_create_forwarding_procedures_intermod_opt(
InitialReuseTable, FinalReuseTable, !ModuleInfo) :-
map.foldl(
maybe_create_forwarding_procedures_intermod_opt_2(FinalReuseTable),
InitialReuseTable ^ reuse_info_map, !ModuleInfo).
:- pred maybe_create_forwarding_procedures_intermod_opt_2(reuse_as_table::in,
pred_proc_id::in, reuse_as_and_status::in,
module_info::in, module_info::out) is det.
maybe_create_forwarding_procedures_intermod_opt_2(FinalReuseTable, PPId,
reuse_as_and_status(InitialReuseAs, _), !ModuleInfo) :-
PPId = proc(PredId, _),
module_info_pred_info(!.ModuleInfo, PredId, PredInfo),
pred_info_get_status(PredInfo, PredStatus),
( if
reuse_as_conditional_reuses(InitialReuseAs),
pred_status_defined_in_this_module(PredStatus) = yes,
reuse_as_table_search(FinalReuseTable, PPId, FinalReuseAs_Status),
FinalReuseAs_Status = reuse_as_and_status(FinalReuseAs, _),
reuse_as_no_reuses(FinalReuseAs)
then
NoClobbers = [],
create_fake_reuse_procedure(PPId, NoClobbers, !ModuleInfo)
else
true
).
:- pred maybe_create_forwarding_procedures_intermod_analysis(
reuse_as_table::in, pred_proc_id::in, module_info::in, module_info::out)
is det.
maybe_create_forwarding_procedures_intermod_analysis(ReuseTable, PredProcId,
!ModuleInfo) :-
% The procedure PredProcId would have been listed as having conditional
% reuse for call pattern NoClobbers = [] in the analysis registry. If our
% analysis of the procedure didn't create a conditional reuse version,
% then we need to produce a forwarding procedure to avoid linking
% problems.
( if
reuse_as_table_search(ReuseTable, PredProcId, ReuseAs_Status),
ReuseAs_Status = reuse_as_and_status(ReuseAs, _),
reuse_as_conditional_reuses(ReuseAs)
then
true
else
NoClobbers = [],
create_fake_reuse_procedure(PredProcId, NoClobbers, !ModuleInfo)
).
%-----------------------------------------------------------------------------%
% Process the imported reuse annotations from .opt files.
%
:- pred process_imported_reuse(module_info::in, module_info::out) is det.
process_imported_reuse(!ModuleInfo):-
module_info_get_valid_pred_ids(!.ModuleInfo, PredIds),
list.foldl(process_imported_reuse_in_pred, PredIds, !ModuleInfo).
:- pred process_imported_reuse_in_pred(pred_id::in, module_info::in,
module_info::out) is det.
process_imported_reuse_in_pred(PredId, !ModuleInfo) :-
some [!PredTable] (
module_info_get_preds(!.ModuleInfo, !:PredTable),
map.lookup(!.PredTable, PredId, PredInfo0),
process_imported_reuse_in_procs(PredInfo0, PredInfo),
map.det_update(PredId, PredInfo, !PredTable),
module_info_set_preds(!.PredTable, !ModuleInfo)
).
:- pred process_imported_reuse_in_procs(pred_info::in,
pred_info::out) is det.
process_imported_reuse_in_procs(!PredInfo) :-
some [!ProcTable] (
pred_info_get_proc_table(!.PredInfo, !:ProcTable),
ProcIds = pred_info_procids(!.PredInfo),
list.foldl(process_imported_reuse_in_proc(!.PredInfo),
ProcIds, !ProcTable),
pred_info_set_proc_table(!.ProcTable, !PredInfo)
).
:- pred process_imported_reuse_in_proc(pred_info::in, proc_id::in,
proc_table::in, proc_table::out) is det.
process_imported_reuse_in_proc(PredInfo, ProcId, !ProcTable) :-
some [!ProcInfo] (
!:ProcInfo = !.ProcTable ^ det_elem(ProcId),
( if
proc_info_get_imported_structure_reuse(!.ProcInfo,
ImpHeadVars, ImpTypes, ImpReuse)
then
proc_info_get_headvars(!.ProcInfo, HeadVars),
pred_info_get_arg_types(PredInfo, HeadVarTypes),
map.from_corresponding_lists(ImpHeadVars, HeadVars, VarRenaming),
some [!TypeSubst] (
!:TypeSubst = map.init,
( if
type_unify_list(ImpTypes, HeadVarTypes, [], !.TypeSubst,
TypeSubstNew)
then
!:TypeSubst = TypeSubstNew
else
true
),
rename_structure_reuse_domain(VarRenaming, !.TypeSubst,
ImpReuse, Reuse)
),
% Optimality does not apply to `--intermodule-optimisation'
% system, only `--intermodule-analysis'.
proc_info_set_structure_reuse(
structure_reuse_domain_and_status(Reuse, optimal), !ProcInfo),
proc_info_reset_imported_structure_reuse(!ProcInfo),
map.det_update(ProcId, !.ProcInfo, !ProcTable)
else
true
)
).
%-----------------------------------------------------------------------------%
% Process the intermodule imported reuse information from the analysis
% framework.
%
:- pred process_intermod_analysis_reuse(module_info::in, module_info::out,
reuse_as_table::out, list(sr_request)::out, list(pred_proc_id)::out)
is det.
process_intermod_analysis_reuse(!ModuleInfo, ReuseTable, ExternalRequests,
MustHaveReuseVersions) :-
module_info_get_valid_pred_ids(!.ModuleInfo, PredIds),
list.foldl4(process_intermod_analysis_reuse_pred, PredIds,
!ModuleInfo, reuse_as_table_init, ReuseTable, [], ExternalRequests0,
[], MustHaveReuseVersions),
list.sort_and_remove_dups(ExternalRequests0, ExternalRequests).
:- pred process_intermod_analysis_reuse_pred(pred_id::in,
module_info::in, module_info::out, reuse_as_table::in, reuse_as_table::out,
list(sr_request)::in, list(sr_request)::out,
list(pred_proc_id)::in, list(pred_proc_id)::out) is det.
process_intermod_analysis_reuse_pred(PredId, !ModuleInfo, !ReuseTable,
!ExternalRequests, !MustHaveReuseVersions) :-
module_info_pred_info(!.ModuleInfo, PredId, PredInfo),
pred_info_get_status(PredInfo, PredStatus),
ProcIds = pred_info_procids(PredInfo),
( if
PredStatus = pred_status(status_imported(_))
then
% Read in answers for imported procedures.
list.foldl2(process_intermod_analysis_reuse_proc(PredId, PredInfo),
ProcIds, !ModuleInfo, !ReuseTable)
else if
pred_status_defined_in_this_module(PredStatus) = yes
then
% For procedures defined in this module we need to read in the answers
% from previous passes to know which versions of procedures other
% modules will be expecting. We also need to read in new requests.
list.foldl2(
process_intermod_analysis_defined_proc(!.ModuleInfo, PredId),
ProcIds, !ExternalRequests, !MustHaveReuseVersions)
else
true
).
:- pred process_intermod_analysis_reuse_proc(pred_id::in,
pred_info::in, proc_id::in, module_info::in, module_info::out,
reuse_as_table::in, reuse_as_table::out) is det.
process_intermod_analysis_reuse_proc(PredId, PredInfo, ProcId,
!ModuleInfo, !ReuseTable) :-
PPId = proc(PredId, ProcId),
module_info_get_analysis_info(!.ModuleInfo, AnalysisInfo),
module_name_func_id(!.ModuleInfo, PPId, ModuleName, FuncId),
pred_info_proc_info(PredInfo, ProcId, ProcInfo),
lookup_results(AnalysisInfo, ModuleName, FuncId, ImportedResults),
list.foldl2(
process_intermod_analysis_imported_reuse_answer(PPId, PredInfo,
ProcInfo),
ImportedResults, !ModuleInfo, !ReuseTable).
:- pred process_intermod_analysis_imported_reuse_answer(pred_proc_id::in,
pred_info::in, proc_info::in,
analysis_result(structure_reuse_call, structure_reuse_answer)::in,
module_info::in, module_info::out, reuse_as_table::in, reuse_as_table::out)
is det.
process_intermod_analysis_imported_reuse_answer(PPId, PredInfo, ProcInfo,
ImportedResult, !ModuleInfo, !ReuseTable) :-
ImportedResult = analysis_result(Call, Answer, ResultStatus),
Call = structure_reuse_call(NoClobbers),
pred_info_get_arg_types(PredInfo, HeadVarTypes),
structure_reuse_answer_to_domain(HeadVarTypes, ProcInfo, Answer, Domain),
ReuseAs = from_structure_reuse_domain(Domain),
ReuseAs_Status = reuse_as_and_status(ReuseAs, ResultStatus),
(
NoClobbers = [],
% When the no-clobber list is empty we store the information with the
% original pred_proc_id.
reuse_as_table_set(PPId, ReuseAs_Status, !ReuseTable)
;
NoClobbers = [_ | _],
% When the no-clobber list is non-empty we need to create a new
% procedure stub and add a mapping to from the original pred_proc_id to
% the stub.
create_fresh_pred_proc_info_copy(PPId, NoClobbers, NewPPId,
!ModuleInfo),
reuse_as_table_set(NewPPId, ReuseAs_Status, !ReuseTable),
reuse_as_table_insert_reuse_version_proc(PPId, NoClobbers, NewPPId,
!ReuseTable)
).
:- pred structure_reuse_answer_to_domain(list(mer_type)::in,
proc_info::in, structure_reuse_answer::in, structure_reuse_domain::out)
is det.
structure_reuse_answer_to_domain(HeadVarTypes, ProcInfo, Answer, Reuse) :-
(
Answer = structure_reuse_answer_no_reuse,
Reuse = has_no_reuse
;
Answer = structure_reuse_answer_unconditional,
Reuse = has_only_unconditional_reuse
;
Answer = structure_reuse_answer_conditional(ImpHeadVars, ImpTypes,
ImpReuseConditions),
proc_info_get_headvars(ProcInfo, HeadVars),
map.from_corresponding_lists(ImpHeadVars, HeadVars, VarRenaming),
( if
type_unify_list(ImpTypes, HeadVarTypes, [], map.init, TypeSubst)
then
rename_structure_reuse_domain(VarRenaming, TypeSubst,
has_conditional_reuse(ImpReuseConditions), Reuse)
else
unexpected($pred, "type_unify_list failed")
)
).
:- pred process_intermod_analysis_defined_proc(module_info::in, pred_id::in,
proc_id::in, list(sr_request)::in, list(sr_request)::out,
list(pred_proc_id)::in, list(pred_proc_id)::out) is det.
process_intermod_analysis_defined_proc(ModuleInfo, PredId, ProcId,
!ExternalRequests, !MustHaveReuseVersions) :-
PPId = proc(PredId, ProcId),
module_info_get_analysis_info(ModuleInfo, AnalysisInfo),
module_name_func_id(ModuleInfo, PPId, ModuleName, FuncId),
% Only add requests for procedures that *really* belong to this module.
module_info_get_name(ModuleInfo, ThisModule),
( if ThisModule = ModuleName then
% Add requests corresponding to the call patterns of existing answers.
lookup_existing_call_patterns(AnalysisInfo, analysis_name, ModuleName,
FuncId, OldCalls),
list.foldl(add_reuse_request(PPId), OldCalls, !ExternalRequests),
% Add new requests from other modules.
lookup_requests(AnalysisInfo, analysis_name, ModuleName, FuncId,
NewCalls),
list.foldl(add_reuse_request(PPId), NewCalls, !ExternalRequests),
% A procedure listed as having conditional reuse *must* have a reuse
% version procedure, even if in this analysis we don't find
% conditional reuse.
module_info_proc_info(ModuleInfo, PPId, ProcInfo),
FuncInfo = structure_reuse_func_info(ModuleInfo, ProcInfo),
Call = structure_reuse_call([]),
lookup_best_result(AnalysisInfo, ModuleName, FuncId, FuncInfo, Call,
MaybeBestResult),
(
MaybeBestResult = yes(analysis_result(_, Answer, _)),
(
Answer = structure_reuse_answer_no_reuse
;
Answer = structure_reuse_answer_unconditional
;
Answer = structure_reuse_answer_conditional(_, _, _),
!:MustHaveReuseVersions = [PPId | !.MustHaveReuseVersions]
)
;
MaybeBestResult = no
)
else
true
).
:- pred add_reuse_request(pred_proc_id::in, structure_reuse_call::in,
list(sr_request)::in, list(sr_request)::out) is det.
add_reuse_request(PPId, structure_reuse_call(NoClobbers), !Requests) :-
(
NoClobbers = []
% We don't need to add these as explicit requests, and in fact it's
% better if we don't. The analysis is already designed to analyse for
% this case by default and create the reuse procedures if necessary.
;
NoClobbers = [_ | _],
!:Requests = [sr_request(PPId, NoClobbers) | !.Requests]
).
%-----------------------------------------------------------------------------%
:- pred save_reuse_in_module_info(pred_proc_id::in, reuse_as_and_status::in,
module_info::in, module_info::out) is det.
save_reuse_in_module_info(PPId, ReuseAs_Status, !ModuleInfo) :-
ReuseAs_Status = reuse_as_and_status(ReuseAs, Status),
ReuseDomain = to_structure_reuse_domain(ReuseAs),
Domain_Status = structure_reuse_domain_and_status(ReuseDomain, Status),
module_info_pred_proc_info(!.ModuleInfo, PPId, PredInfo, ProcInfo0),
proc_info_set_structure_reuse(Domain_Status, ProcInfo0, ProcInfo),
module_info_set_pred_proc_info(PPId, PredInfo, ProcInfo, !ModuleInfo).
:- pred annotate_in_use_information(module_info::in,
proc_info::in, proc_info::out) is det.
annotate_in_use_information(ModuleInfo, !ProcInfo) :-
forward_use_information(!ProcInfo),
backward_use_information(ModuleInfo, !ProcInfo),
fill_goal_path_slots_in_proc(ModuleInfo, !ProcInfo).
%-----------------------------------------------------------------------------%
%
% Types and instances for the intermodule analysis framework.
%
:- type structure_reuse_call
---> structure_reuse_call(no_clobber_args).
:- type structure_reuse_answer
---> structure_reuse_answer_no_reuse
; structure_reuse_answer_unconditional
; structure_reuse_answer_conditional(
srac_vars :: prog_vars,
srac_types :: list(mer_type),
srac_conds :: structure_reuse_conditions
% We cannot keep this as a reuse_as. When the analysis answers
% are loaded, we don't have enough information to rename the
% variables in the .analysis answer to the correct variables
% for the proc_info that the reuse_as will be used with.
).
:- type structure_reuse_func_info
---> structure_reuse_func_info(
srfi_module :: module_info,
srfi_proc :: proc_info
).
:- func analysis_name = string.
analysis_name = "structure_reuse".
:- instance analysis(structure_reuse_func_info, structure_reuse_call,
structure_reuse_answer) where
[
analysis_name(_, _) = analysis_name,
analysis_version_number(_, _) = 3,
preferred_fixpoint_type(_, _) = greatest_fixpoint,
bottom(_, _) = structure_reuse_answer_no_reuse,
( top(_, _) = _ :-
% We have no representation for "all possible conditions".
unexpected($pred, "top/2 called")
),
( get_func_info(ModuleInfo, ModuleName, FuncId, _, _, FuncInfo) :-
func_id_to_ppid(ModuleInfo, ModuleName, FuncId, PPId),
module_info_proc_info(ModuleInfo, PPId, ProcInfo),
FuncInfo = structure_reuse_func_info(ModuleInfo, ProcInfo)
)
].
:- instance call_pattern(structure_reuse_func_info, structure_reuse_call)
where [].
:- instance partial_order(structure_reuse_func_info, structure_reuse_call)
where [
(more_precise_than(_, Call1, Call2) :-
Call1 = structure_reuse_call(Args1),
Call2 = structure_reuse_call(Args2),
set.subset(sorted_list_to_set(Args2), sorted_list_to_set(Args1))
),
equivalent(_, Call, Call)
].
:- instance to_term(structure_reuse_call) where [
( to_term(Call) = Term :-
Call = structure_reuse_call(NoClobbers),
type_to_term(NoClobbers, Term)
),
( from_term(Term, Call) :-
term_to_type(Term, NoClobbers),
Call = structure_reuse_call(NoClobbers)
)
].
:- instance answer_pattern(structure_reuse_func_info, structure_reuse_answer)
where [].
:- instance partial_order(structure_reuse_func_info, structure_reuse_answer)
where [
% We deliberately have `conditional' reuse incomparable with
% `unconditional' reuse. If they were comparable, a caller using an
% `conditional' answer would would only be marked `suboptimal' if that
% answer changes to `unconditional'. Since we don't honour the old
% `conditional' answer by generating that version of the procedure, there
% would be a linking error if the caller is not updated to call the
% unconditional version.
(more_precise_than(FuncInfo, Answer1, Answer2) :-
(
Answer1 = structure_reuse_answer_conditional(_, _, _),
Answer2 = structure_reuse_answer_no_reuse
;
Answer1 = structure_reuse_answer_unconditional,
Answer2 = structure_reuse_answer_no_reuse
;
Answer1 = structure_reuse_answer_conditional(_, _, _),
Answer2 = structure_reuse_answer_conditional(_, _, _),
FuncInfo = structure_reuse_func_info(ModuleInfo, ProcInfo),
proc_info_get_headvars(ProcInfo, HeadVars),
proc_info_get_vartypes(ProcInfo, VarTypes),
lookup_var_types(VarTypes, HeadVars, HeadVarTypes),
structure_reuse_answer_to_domain(HeadVarTypes, ProcInfo, Answer1,
Reuse1),
structure_reuse_answer_to_domain(HeadVarTypes, ProcInfo, Answer2,
Reuse2),
ReuseAs1 = from_structure_reuse_domain(Reuse1),
ReuseAs2 = from_structure_reuse_domain(Reuse2),
reuse_as_subsumed_by(ModuleInfo, ProcInfo, ReuseAs1, ReuseAs2),
not reuse_as_subsumed_by(ModuleInfo, ProcInfo, ReuseAs2, ReuseAs1)
)
),
(equivalent(FuncInfo, Answer1, Answer2) :-
(
Answer1 = Answer2
;
Answer1 = structure_reuse_answer_conditional(_, _, _),
Answer2 = structure_reuse_answer_conditional(_, _, _),
FuncInfo = structure_reuse_func_info(ModuleInfo, ProcInfo),
proc_info_get_headvars(ProcInfo, HeadVars),
proc_info_get_vartypes(ProcInfo, VarTypes),
lookup_var_types(VarTypes, HeadVars, HeadVarTypes),
structure_reuse_answer_to_domain(HeadVarTypes, ProcInfo, Answer1,
Reuse1),
structure_reuse_answer_to_domain(HeadVarTypes, ProcInfo, Answer2,
Reuse2),
ReuseAs1 = from_structure_reuse_domain(Reuse1),
ReuseAs2 = from_structure_reuse_domain(Reuse2),
reuse_as_subsumed_by(ModuleInfo, ProcInfo, ReuseAs2, ReuseAs1),
reuse_as_subsumed_by(ModuleInfo, ProcInfo, ReuseAs1, ReuseAs2)
)
)
].
:- instance to_term(structure_reuse_answer) where [
func(to_term/1) is reuse_answer_to_term,
pred(from_term/2) is reuse_answer_from_term
].
:- func reuse_answer_to_term(structure_reuse_answer) = term.
reuse_answer_to_term(Answer) = Term :-
(
Answer = structure_reuse_answer_no_reuse,
Term = term.functor(atom("no_reuse"), [], term.context_init)
;
Answer = structure_reuse_answer_unconditional,
Term = term.functor(atom("uncond"), [], term.context_init)
;
Answer = structure_reuse_answer_conditional(HeadVars, Types,
Conditions),
type_to_term(HeadVars, HeadVarsTerm),
type_to_term(Types, TypesTerm),
type_to_term(Conditions, ConditionsTerm),
Term = term.functor(atom("cond"),
[HeadVarsTerm, TypesTerm, ConditionsTerm], term.context_init)
).
:- pred reuse_answer_from_term(term::in, structure_reuse_answer::out)
is semidet.
reuse_answer_from_term(Term, Answer) :-
(
Term = functor(atom("no_reuse"), [], _),
Answer = structure_reuse_answer_no_reuse
;
Term = functor(atom("uncond"), [], _),
Answer = structure_reuse_answer_unconditional
;
Term = functor(atom("cond"),
[HeadVarsTerm, TypesTerm, ConditionsTerm], _),
term_to_type(HeadVarsTerm, HeadVars),
term_to_type(TypesTerm, Types),
term_to_type(ConditionsTerm, Conditions),
Answer = structure_reuse_answer_conditional(HeadVars, Types,
Conditions)
).
%-----------------------------------------------------------------------------%
%
% Additional predicates used for intermodule analysis.
%
:- pred record_structure_reuse_results(module_info::in,
bimap(ppid_no_clobbers, pred_proc_id)::in, pred_proc_id::in,
reuse_as_and_status::in, analysis_info::in, analysis_info::out) is det.
record_structure_reuse_results(ModuleInfo, CondReuseMap, PPId, ReuseAs_Status,
!AnalysisInfo) :-
( if bimap.reverse_search(CondReuseMap, Key, PPId) then
% PPId is a conditional reuse procedure created from another procedure.
% We need to record the result using the name of the original
% procedure.
Key = ppid_no_clobbers(RecordPPId, NoClobbers)
else
RecordPPId = PPId,
NoClobbers = []
),
record_structure_reuse_results_2(ModuleInfo, RecordPPId, NoClobbers,
ReuseAs_Status, !AnalysisInfo).
:- pred record_structure_reuse_results_2(module_info::in, pred_proc_id::in,
no_clobber_args::in, reuse_as_and_status::in,
analysis_info::in, analysis_info::out) is det.
record_structure_reuse_results_2(ModuleInfo, PPId, NoClobbers, ReuseAs_Status,
!AnalysisInfo) :-
PPId = proc(PredId, ProcId),
ReuseAs_Status = reuse_as_and_status(ReuseAs, Status),
module_info_pred_info(ModuleInfo, PredId, PredInfo),
should_write_reuse_info(ModuleInfo, PredId, ProcId, PredInfo,
for_analysis_framework, ShouldWrite),
(
ShouldWrite = should_write,
reuse_as_to_structure_reuse_answer(ModuleInfo, PPId, ReuseAs, Answer),
module_name_func_id(ModuleInfo, PPId, ModuleName, FuncId),
record_result(ModuleName, FuncId, structure_reuse_call(NoClobbers),
Answer, Status, !AnalysisInfo)
;
ShouldWrite = should_not_write
).
:- pred reuse_as_to_structure_reuse_answer(module_info::in, pred_proc_id::in,
reuse_as::in, structure_reuse_answer::out) is det.
reuse_as_to_structure_reuse_answer(ModuleInfo, PPId, ReuseAs, Answer) :-
Reuse = to_structure_reuse_domain(ReuseAs),
(
Reuse = has_no_reuse,
Answer = structure_reuse_answer_no_reuse
;
Reuse = has_only_unconditional_reuse,
Answer = structure_reuse_answer_unconditional
;
Reuse = has_conditional_reuse(Conditions),
module_info_proc_info(ModuleInfo, PPId, ProcInfo),
proc_info_get_headvars(ProcInfo, HeadVars),
proc_info_get_vartypes(ProcInfo, VarTypes),
lookup_var_types(VarTypes, HeadVars, HeadVarTypes),
Answer = structure_reuse_answer_conditional(HeadVars, HeadVarTypes,
Conditions)
).
:- pred handle_structure_reuse_dependency(module_info::in,
ppid_no_clobbers::in, analysis_info::in, analysis_info::out) is det.
handle_structure_reuse_dependency(ModuleInfo,
ppid_no_clobbers(DepPPId, NoClobbers), !AnalysisInfo) :-
% Record that we depend on the result for the called procedure.
module_name_func_id(ModuleInfo, DepPPId, DepModuleName, DepFuncId),
Call = structure_reuse_call(NoClobbers),
Answer = _ : structure_reuse_answer,
get_func_info(ModuleInfo, DepModuleName, DepFuncId, Call, Answer,
FuncInfo),
record_dependency(DepModuleName, DepFuncId, FuncInfo, Call, Answer,
!AnalysisInfo).
:- pred record_intermod_requests(module_info::in, sr_request::in,
analysis_info::in, analysis_info::out) is det.
record_intermod_requests(ModuleInfo, sr_request(PPId, NoClobbers),
!AnalysisInfo) :-
module_name_func_id(ModuleInfo, PPId, ModuleName, FuncId),
record_request(analysis_name, ModuleName, FuncId,
structure_reuse_call(NoClobbers), !AnalysisInfo).
%-----------------------------------------------------------------------------%
%
% for structure_reuse.versions
%
structure_reuse_answer_harsher_than_in_analysis_registry(ModuleInfo,
ReuseTable, ReusePPId, Harsher) :-
module_info_get_analysis_info(ModuleInfo, AnalysisInfo),
% Find the original pred_proc_id and no-clobber list that this reuse
% procedure was made for.
reuse_as_table_reverse_search_reuse_version_proc(ReuseTable, ReusePPId,
OrigPPId, NoClobbers),
% Look up the old result.
module_name_func_id(ModuleInfo, OrigPPId, ModuleName, FuncId),
module_info_proc_info(ModuleInfo, OrigPPId, ProcInfo),
FuncInfo = structure_reuse_func_info(ModuleInfo, ProcInfo),
Call = structure_reuse_call(NoClobbers),
analysis.lookup_best_result(AnalysisInfo, ModuleName, FuncId, FuncInfo,
Call, MaybeOldResult),
( if
MaybeOldResult = yes(analysis_result(OldCall, OldAnswer, _)),
equivalent(FuncInfo, Call, OldCall)
then
% Compare with the new result.
lookup_new_structure_reuse_answer(ModuleInfo, ReuseTable, ReusePPId,
NewAnswer),
( if more_precise_than(FuncInfo, NewAnswer, OldAnswer) then
Harsher = yes,
trace [
compile_time(flag("harsher_answer_check")),
runtime(env("HARSHER_ANSWER_CHECK")),
io(!IO)
] (
io.write_string("Structure reuse answer for ", !IO),
write_pred_proc_id(ModuleInfo, ReusePPId, !IO),
io.write_string(" has harsher conditions than listed " ++
"in analysis file.\n", !IO),
io.write_string("was: ", !IO),
io.write(OldAnswer, !IO),
io.nl(!IO),
io.write_string("now: ", !IO),
io.write(NewAnswer, !IO),
io.nl(!IO)
)
else
Harsher = no
)
else
Harsher = no
).
:- pred lookup_new_structure_reuse_answer(module_info::in, reuse_as_table::in,
pred_proc_id::in, structure_reuse_answer::out) is det.
lookup_new_structure_reuse_answer(ModuleInfo, ReuseTable, ReusePPId,
NewAnswer) :-
( if reuse_as_table_search(ReuseTable, ReusePPId, ReuseAs_Status) then
ReuseAs_Status = reuse_as_and_status(NewReuseAs, _)
else
unexpected($pred, "search failed")
),
reuse_as_to_structure_reuse_answer(ModuleInfo, ReusePPId, NewReuseAs,
NewAnswer).
%-----------------------------------------------------------------------------%
:- pred remove_useless_reuse_proc(module_info::in, bool::in,
map(pred_proc_id, reuse_as_and_status)::in,
ppid_no_clobbers::in, pred_proc_id::in,
predicate_table::in, predicate_table::out) is det.
remove_useless_reuse_proc(ModuleInfo, VeryVerbose, ReuseAsMap, _, PPId,
!PredTable) :-
map.lookup(ReuseAsMap, PPId, ReuseAs_Status),
ReuseAs_Status = reuse_as_and_status(ReuseAs, _),
% XXX perhaps we can also remove reuse procedures with only unconditional
% reuse? Such a procedure should be the same as the "non-reuse" procedure
% (which also implements any unconditional reuse).
( if reuse_as_no_reuses(ReuseAs) then
(
VeryVerbose = yes,
trace [io(!IO)] (
io.write_string("% Removing useless reuse ", !IO),
write_pred_proc_id(ModuleInfo, PPId, !IO),
io.nl(!IO)
)
;
VeryVerbose = no
),
PPId = proc(PredId, _),
% We can remove the whole predicate because we never generate
% multi-moded reuse versions of predicates.
predicate_table_remove_predicate(PredId, !PredTable)
else
true
).
%-----------------------------------------------------------------------------%
:- end_module transform_hlds.ctgc.structure_reuse.analysis.
%-----------------------------------------------------------------------------%