Files
mercury/compiler/structure_reuse.domain.m
Zoltan Somogyi d00ea69529 Switch from using set(prog_var), which is represented using set_ordlist,
Estimated hours taken: 12
Branches: main

Switch from using set(prog_var), which is represented using set_ordlist,
to set_of_progvar, which is represented using tree_bitset, for most sets
of variables in the compiler, including the nonlocals sets in goal_infos.

This diff yields about a 5% speedup when compiling the training_cars_full.m
stress test, but also about a 1% slowdown on tools/speedtest. Both of these
are with the current default state in which tree_bitset is compiled with
a whole bunch of sanity checks. If these are disabled, we get roughly a 1%
speedup on tools/speedtest. I intend to disable those sanity checks after
a shakedown period of a week or two in which the updated version of the
compiler is installed on our platforms.

compiler/hlds_goal.m:
	Replace almost all occurrences of set(prog_var) with set_of_progvar.
	The main exceptions are the types supporting rbmm.

compiler/set_of_var.m:
	Add some more predicates and functions that previous existed on sets
	but not yet on set_of_vars.

compiler/*.m:
	Conform to the change in hlds_goal.m, and make similar changes
	in set representations.

library/bag.m:
	Add a predicate and function for creating a bag from a sorted list.
	We already had them for creating a bag from a set, but a set_of_progvar
	shouldn't have to be converted to a set.

library/robdd.m:
	Fix deviations from our programming style.
2011-08-16 03:26:40 +00:00

1016 lines
38 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 2006-2007, 2010-2011 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.domain.m.
% Main authors: nancy.
%
% Definition of the abstract domain for keeping track of opportunities for
% structure reuse.
%
%-----------------------------------------------------------------------------%
:- module transform_hlds.ctgc.structure_reuse.domain.
:- interface.
:- import_module analysis.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_pred.
:- import_module parse_tree.prog_data.
:- import_module transform_hlds.ctgc.livedata.
:- import_module transform_hlds.ctgc.structure_sharing.domain.
:- import_module bimap.
:- import_module bool.
:- import_module io.
:- import_module map.
:- import_module list.
%-----------------------------------------------------------------------------%
% A reuse condition stores all the necessary information to check if
% a procedure call is safe w.r.t. a structure reuse opportunity within
% the body of the called procedure.
%
:- type reuse_condition.
:- type reuse_conditions == list(reuse_condition).
% Abstract representation for a set of reuse conditions.
%
:- type reuse_as.
%-----------------------------------------------------------------------------%
%
% reuse_condition
%
% reuse_condition_init(ModuleInfo, ProcInfo, DeadVar, LocalForwardUse,
% LocalBackwardUse, SharingAs) = NewReuseCondition.
%
% Create a reuse condition for DeadVar, knowing the set of variables in
% local forward and backward use, as well as the local structure sharing.
%
:- func reuse_condition_init(module_info, proc_info, dead_var,
set_of_live_var, set_of_live_var, sharing_as) = reuse_condition.
:- pred reuse_condition_is_conditional(reuse_condition::in) is semidet.
:- pred reuse_condition_reusable_nodes(reuse_condition::in,
dead_datastructs::out) is semidet.
% Renaming operation.
% This operation renames all occurrences of program variables and
% type variables according to a program and type variable mapping.
%
:- pred reuse_condition_rename(prog_var_renaming::in, tsubst::in,
reuse_condition::in, reuse_condition::out) is det.
% Succeeds if the first condition is subsumed by the second one, i.e.,
% if a procedure call verifies the second condition, then it also
% verifies the first condition.
%
:- pred reuse_condition_subsumed_by(module_info::in, proc_info::in,
reuse_condition::in, reuse_condition::in) is semidet.
%-----------------------------------------------------------------------------%
% reuse_as
%
% XXX The implementation of this type has changed wrt. its counterpart in the
% reuse branch (called memo_reuse). While memo_reuse's didn't always keep a
% minimal representation, reuse_as does.
%
% Create an initial set of reuse descriptions.
%
:- func reuse_as_init = reuse_as.
:- func reuse_as_init_with_one_condition(reuse_condition) = reuse_as.
% Return a short description of the reuse information.
%
:- func reuse_as_short_description(reuse_as) = string.
% Succeeds if the first reuses description is subsumed by the second
% description, i.e., if a procedure call satisfies all the conditions
% expressed by the second reuses description, then it also satisfies all
% the conditions expressed by the first reuses description.
%
:- pred reuse_as_subsumed_by(module_info::in, proc_info::in, reuse_as::in,
reuse_as::in) is semidet.
:- pred reuse_as_and_status_subsumed_by(module_info::in, proc_info::in,
reuse_as_and_status::in, reuse_as_and_status::in) is semidet.
% Tests to see whether the reuses description describes no reuses at all,
% only unconditional reuses, or conditional reuses resp.
%
:- pred reuse_as_no_reuses(reuse_as::in) is semidet.
:- pred reuse_as_all_unconditional_reuses(reuse_as::in) is semidet.
:- pred reuse_as_conditional_reuses(reuse_as::in) is semidet.
:- func reuse_as_count_conditions(reuse_as) = int.
% reuse_as_rename_using_module_info(ModuleInfo, PPId,
% ActualVars, ActualTypes, CallerTypeVarSet, CallerHeadTypeParams,
% FormalReuse, ActualReuse):
%
% Renaming of the formal description of structure reuse conditions to the
% actual description of these conditions. The information about the formal
% variables needs to be extracted from the module information.
% The actual names are determined by the actual variables names, the
% actual types, and the type-variables occurring in those types.
%
:- pred reuse_as_rename_using_module_info(module_info::in,
pred_proc_id::in, prog_vars::in, list(mer_type)::in, tvarset::in,
head_type_params::in, reuse_as::in, reuse_as::out) is det.
% Given a variable and type variable mapping, rename the reuses
% conditions accordingly.
%
:- pred reuse_as_rename(prog_var_renaming::in, tsubst::in, reuse_as::in,
reuse_as::out) is det.
% Add a reuse condition to the reuses description. The information of
% module_info and proc_info are needed to verify subsumption before adding
% the new condition.
%
:- pred reuse_as_add_condition(module_info::in, proc_info::in,
reuse_condition::in, reuse_as::in, reuse_as::out) is det.
% A shortcut version of the above procedure when the additional condition
% is "unconditional".
%
:- pred reuse_as_add_unconditional(reuse_as::in, reuse_as::out) is det.
% Compute the least upper bound of two reuses descriptions. Module_info
% and proc_info are needed for verifying subsumption.
%
:- pred reuse_as_least_upper_bound(module_info::in, proc_info::in,
reuse_as::in, reuse_as::in, reuse_as::out) is det.
:- func reuse_as_least_upper_bound(module_info, proc_info, reuse_as,
reuse_as) = reuse_as.
% reuse_as_from_called_procedure_to_local_reuse_as(ModuleInfo,
% ProcInfo, HeadVars, InUseData, SharingAs, CalledReuseAs) =
% LocalReuseAs.
%
% Translate the reuse description of a called procedure to the
% environment of the caller. This means taking into account the local
% sets of in use variables, as well as the local sharing.
%
% Pre-condition: the reuse description of the called procedure is already
% correctly renamed to the caller's environment.
% Pre-condition: the reuse_as from the called procedure contains at
% least one conditional reuse condition.
%
:- func reuse_as_from_called_procedure_to_local_reuse_as(module_info,
proc_info, prog_vars, live_datastructs, sharing_as, reuse_as) = reuse_as.
% Taking into account the live data and static variables, check if the
% reuse conditions expressed by reuse_as are all satisfied, hence making
% the associated memory reuses safe for that particular calling
% environment. If the conditions are not satisfied, return the
% variables which caused one of the conditions to be violated.
%
:- pred reuse_as_satisfied(module_info::in, proc_info::in, livedata::in,
sharing_as::in, prog_vars::in, reuse_as::in, reuse_satisfied_result::out)
is det.
:- type reuse_satisfied_result
---> reuse_possible
; reuse_not_possible(reuse_not_possible_reason).
:- type reuse_not_possible_reason
---> no_reuse
% No reuse version of the procedure.
; unknown_livedata
% We had to assume everything was live.
; reuse_condition_violated(list(prog_var))
% At least these variables couldn't be allowed to be clobbered.
; reuse_nodes_have_sharing(list(prog_var)).
% The reuse conditions are individually satisfied, but the
% arguments for reuse have sharing between them which would lead
% to undefined behaviour in the reuse version of the procedure.
% Conversion procedures between the public (structure_reuse_domain)
% and private (reuse_as) representation for structure reuse conditions.
%
:- func from_structure_reuse_domain(structure_reuse_domain) = reuse_as.
:- func to_structure_reuse_domain(reuse_as) = structure_reuse_domain.
%-----------------------------------------------------------------------------%
%
% reuse_as_table
%
% Intermediate storage of the reuse results for individual procedures.
%
:- type reuse_as_table
---> reuse_as_table(
reuse_info_map :: map(pred_proc_id, reuse_as_and_status),
% Maps pred_proc_ids to their reuse information and status.
reuse_version_map :: bimap(ppid_no_clobbers, pred_proc_id)
% Maps original procedures and associated no-clobber argument
% lists to the reuse version procedures already created.
).
:- type reuse_as_and_status
---> reuse_as_and_status(
reuse_as,
analysis_status
).
:- type ppid_no_clobbers
---> ppid_no_clobbers(
pred_proc_id,
no_clobber_args
).
% The arguments at these positions must not be clobbered.
%
:- type no_clobber_args == list(int).
:- func reuse_as_table_init = reuse_as_table.
:- pred reuse_as_table_search(reuse_as_table::in, pred_proc_id::in,
reuse_as_and_status::out) is semidet.
:- pred reuse_as_table_search_reuse_version_proc(reuse_as_table::in,
pred_proc_id::in, list(int)::in, pred_proc_id::out) is semidet.
:- pred reuse_as_table_reverse_search_reuse_version_proc(reuse_as_table::in,
pred_proc_id::in, pred_proc_id::out, list(int)::out) is det.
:- pred reuse_as_table_set(pred_proc_id::in, reuse_as_and_status::in,
reuse_as_table::in, reuse_as_table::out) is det.
:- pred reuse_as_table_insert_reuse_version_proc(pred_proc_id::in,
no_clobber_args::in, pred_proc_id::in,
reuse_as_table::in, reuse_as_table::out) is det.
:- pred reuse_as_table_maybe_dump(bool::in, module_info::in,
reuse_as_table::in, io::di, io::uo) is det.
% Load all the structure reuse information present in the HLDS into
% a reuse table. This is only for the old intermodule optimisation system
% where imported structure reuse information lives with the proc_infos.
%
:- func load_structure_reuse_table(module_info) = reuse_as_table.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module hlds.hlds_out.
:- import_module hlds.hlds_out.hlds_out_util.
:- import_module parse_tree.prog_ctgc.
:- import_module parse_tree.set_of_var.
:- import_module transform_hlds.ctgc.datastruct.
:- import_module transform_hlds.ctgc.util.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module set.
:- import_module solutions.
:- import_module string.
%-----------------------------------------------------------------------------%
:- type reuse_condition
---> always
% The reuse is always safe and does not actually have a condition.
; condition(
% Description of the datastructures pointing to the memory
% that can be reused within a procedure.
reuseable_nodes :: dead_datastructs,
% Set of (headvar-related) datastructures that are
% inherently live at the place where the reuse is decided.
local_use_headvars :: live_datastructs,
% Description of the (headvar-related) structure sharing
% that exists at the place where the reuse is decided.
local_sharing_headvars :: sharing_as
).
:- type reuse_as
---> no_reuse
% = fictive bottom element representing the fact that no
% reuse has been detected so far.
; unconditional
% no_reuse < unconditional.
% = element representing the fact that all reuses detected
% so far are unconditional.
% Semantically equivalent to "conditional(Cs)" where every C in
% Cs is "always".
; conditional(reuse_conditions).
% no_reuse < unconditional < conditional(List)
% = element representing the collection of reuse conditions
% collected for the reuses detected so far.
%-----------------------------------------------------------------------------%
%
% Reuse_condition.
%
reuse_condition_init(ModuleInfo, ProcInfo, DeadVar, LFU, LBU, Sharing)
= Condition :-
proc_info_get_headvars(ProcInfo, HeadVars),
% First determine the nodes to which the reuse is related.
% There are two cases:
%
% 1 Var is a headvar, then it is sufficient to keep the top cell of that
% Var as only node. HeadVar-datastructures shared with that node will
% still be retraceable at the moment of verifying the condition.
%
% 2 Var is a local var, then we must compute all the headvar-
% datastructures sharing the same memory representation as the top cell
% of this var (note that the datastructures that share with some
% substructure of this var are not relevant for the nodes). All the
% obtained datastructures are kept as the nodes for our condition.
TopCell = ctgc.datastruct.datastruct_init(DeadVar),
( list.member(DeadVar, HeadVars) ->
Nodes = [TopCell]
;
SharedDatastructs = extend_datastruct(ModuleInfo, ProcInfo,
Sharing, TopCell),
Nodes = datastructs_project(HeadVars, SharedDatastructs)
),
% It is possible that the obtained set of nodes is empty. In that case
% the condition is always satisfied, independent of any calling
% environment.
(
Nodes = [],
Condition = always
;
Nodes = [_ | _],
set_of_var.union(LFU, LBU, LU),
% XXX the old implementation did not bother about extending at this
% place, which was contrary to the theory. Check the effect of this
% change!
LuData = list.map(datastruct_init, set_of_var.to_sorted_list(LU)),
ExtendedLuData = list.map(
extend_datastruct(ModuleInfo, ProcInfo, Sharing), LuData),
SharedLU = list.condense(ExtendedLuData),
HeadVarSharedLU = datastructs_project(HeadVars, SharedLU),
structure_sharing.domain.sharing_as_project(HeadVars, Sharing,
HeadVarSharing),
Condition = condition(set.list_to_set(Nodes), HeadVarSharedLU,
HeadVarSharing)
).
reuse_condition_is_conditional(condition(_, _, _)).
reuse_condition_reusable_nodes(condition(Nodes, _, _), Nodes).
reuse_condition_subsumed_by(ModuleInfo, ProcInfo, Cond1, Cond2) :-
(
Cond1 = always
;
Cond1 = condition(Nodes1, LocalUse1, LocalSharing1),
Cond2 = condition(Nodes2, LocalUse2, LocalSharing2),
% XXX this was Nancy's implementation, but bad_indirect_reuse.m is
% broken when using this definition. --pw
%
% datastructs_subsumed_by_list(ModuleInfo, ProcInfo, Nodes1, Nodes2),
%
% That seems to match the theory, but doesn't make sense to me: if you
% satisfy a condition that allows you to clobber the top-cell
% `selected_cel(V, [])', it doesn't mean you're free to clobber a cell
% beneath that, say `selected_cel(V, [termsel(f, 1)])'.
%
set.subset(Nodes1, Nodes2),
datastructs_subsumed_by_list(ModuleInfo, ProcInfo,
LocalUse1, LocalUse2),
sharing_as_is_subsumed_by(ModuleInfo, ProcInfo,
LocalSharing1, LocalSharing2)
).
:- pred reuse_condition_subsumed_by_list(module_info::in, proc_info::in,
reuse_condition::in, reuse_conditions::in) is semidet.
reuse_condition_subsumed_by_list(ModuleInfo, ProcInfo, Cond, [Cond1|Rest]) :-
(
reuse_condition_subsumed_by(ModuleInfo, ProcInfo, Cond, Cond1)
;
reuse_condition_subsumed_by_list(ModuleInfo, ProcInfo, Cond, Rest)
).
:- pred reuse_conditions_subsume_reuse_condition(module_info::in,
proc_info::in, reuse_conditions::in, reuse_condition::in) is semidet.
reuse_conditions_subsume_reuse_condition(ModuleInfo, ProcInfo, Conds, Cond):-
reuse_condition_subsumed_by_list(ModuleInfo, ProcInfo, Cond, Conds).
reuse_condition_rename(MapVar, TypeSubst, Condition, RenamedCondition):-
(
Condition = always,
RenamedCondition = always
;
Condition = condition(DeadNodes, InUseNodes, LocalSharing),
RenamedDeadNodes = set.map(rename_datastruct(MapVar, TypeSubst),
DeadNodes),
RenamedInUseNodes = list.map(rename_datastruct(MapVar, TypeSubst),
InUseNodes),
sharing_as_rename(MapVar, TypeSubst, LocalSharing,
RenamedLocalSharing),
RenamedCondition = condition(RenamedDeadNodes, RenamedInUseNodes,
RenamedLocalSharing)
).
%-----------------------------------------------------------------------------%
%
% reuse_as
%
reuse_as_init = no_reuse.
reuse_as_init_with_one_condition(ReuseCondition) = ReuseAs :-
( reuse_condition_is_conditional(ReuseCondition) ->
ReuseAs = conditional([ReuseCondition])
;
ReuseAs = unconditional
).
reuse_as_short_description(no_reuse) = "no_reuse".
reuse_as_short_description(unconditional) = "uncond".
reuse_as_short_description(conditional(Conds)) = "cond(" ++ Size ++ ")" :-
Size = string.int_to_string(list.length(Conds)).
reuse_as_subsumed_by(ModuleInfo, ProcInfo, FirstReuseAs, SecondReuseAs) :-
(
FirstReuseAs = no_reuse
;
FirstReuseAs = unconditional,
SecondReuseAs = conditional(_)
% Every calling environment satisfies the reuse conditions as all
% reuse is unconditional, hence also the calling environments that
% satisfy the conditions expressed by SecondReuseAs.
;
FirstReuseAs = unconditional,
SecondReuseAs = unconditional
;
FirstReuseAs = conditional(ReuseConditionsFirst),
SecondReuseAs = conditional(ReuseConditionsSecond),
list.takewhile(reuse_conditions_subsume_reuse_condition(ModuleInfo,
ProcInfo, ReuseConditionsSecond), ReuseConditionsFirst, _,
NotSubsumed),
NotSubsumed = []
).
reuse_as_and_status_subsumed_by(ModuleInfo, ProcInfo,
ReuseAs_Status1, ReuseAs_Status2) :-
ReuseAs_Status1 = reuse_as_and_status(Reuse1, _Status1),
ReuseAs_Status2 = reuse_as_and_status(Reuse2, _Status2),
reuse_as_subsumed_by(ModuleInfo, ProcInfo, Reuse1, Reuse2).
% XXX do we need to compare Status1 and Status2?
reuse_as_no_reuses(no_reuse).
reuse_as_all_unconditional_reuses(unconditional).
reuse_as_conditional_reuses(conditional(_)).
reuse_as_count_conditions(no_reuse) = 0.
reuse_as_count_conditions(unconditional) = 0.
reuse_as_count_conditions(conditional(Conds)) = list.length(Conds).
reuse_as_rename_using_module_info(ModuleInfo, PPId, ActualArgs, ActualTypes,
CallerTypeVarSet, CallerHeadTypeParams, FormalReuse, ActualReuse) :-
VarRenaming = get_variable_renaming(ModuleInfo, PPId, ActualArgs),
TypeSubst = get_type_substitution(ModuleInfo, PPId, ActualTypes,
CallerTypeVarSet, CallerHeadTypeParams),
reuse_as_rename(VarRenaming, TypeSubst, FormalReuse, ActualReuse).
reuse_as_rename(MapVar, TypeSubst, ReuseAs, RenamedReuseAs) :-
(
ReuseAs = no_reuse,
RenamedReuseAs = no_reuse
;
ReuseAs = unconditional,
RenamedReuseAs = unconditional
;
ReuseAs = conditional(ReuseConditions),
list.map(reuse_condition_rename(MapVar, TypeSubst),
ReuseConditions, RenamedReuseConditions),
RenamedReuseAs = conditional(RenamedReuseConditions)
).
reuse_as_add_condition(ModuleInfo, ProcInfo, Condition, !ReuseAs) :-
(
Condition = always,
( !.ReuseAs = no_reuse ->
!:ReuseAs = unconditional
;
true
)
;
Condition = condition(_, _, _),
(
!.ReuseAs = no_reuse,
!:ReuseAs = conditional([Condition])
;
!.ReuseAs = unconditional,
!:ReuseAs = conditional([Condition])
;
!.ReuseAs = conditional(Conditions),
reuse_conditions_add_condition(ModuleInfo, ProcInfo,
Condition, Conditions, NewConditions),
!:ReuseAs = conditional(NewConditions)
)
).
reuse_as_add_unconditional(!ReuseAs) :-
(
!.ReuseAs = no_reuse,
!:ReuseAs = unconditional
;
!.ReuseAs = unconditional
;
!.ReuseAs = conditional(_)
).
:- pred reuse_conditions_add_condition(module_info::in, proc_info::in,
reuse_condition::in, reuse_conditions::in, reuse_conditions::out) is det.
reuse_conditions_add_condition(ModuleInfo, ProcInfo, Condition, !Conds):-
(
reuse_condition_subsumed_by_list(ModuleInfo, ProcInfo,
Condition, !.Conds)
->
true
;
!:Conds = [Condition | !.Conds]
).
:- pred reuse_conditions_add_conditions(module_info::in, proc_info::in,
reuse_conditions::in, reuse_conditions::in, reuse_conditions::out) is det.
reuse_conditions_add_conditions(ModuleInfo, ProcInfo, NewConds, !Conds):-
(
NewConds = [Cond | RemainingConds],
reuse_conditions_add_condition(ModuleInfo, ProcInfo, Cond, !Conds),
reuse_conditions_add_conditions(ModuleInfo, ProcInfo,
RemainingConds, !Conds)
;
NewConds = []
).
reuse_as_least_upper_bound(ModuleInfo, ProcInfo, NewReuseAs, !ReuseAs) :-
(
NewReuseAs = no_reuse
;
NewReuseAs = unconditional,
( !.ReuseAs = no_reuse ->
!:ReuseAs = unconditional
;
true
)
;
NewReuseAs = conditional(NewConditions),
(
!.ReuseAs = no_reuse,
!:ReuseAs = NewReuseAs
;
!.ReuseAs = unconditional,
!:ReuseAs = NewReuseAs
;
!.ReuseAs = conditional(Conditions),
reuse_conditions_add_conditions(ModuleInfo, ProcInfo,
NewConditions, Conditions, AllConditions),
!:ReuseAs = conditional(AllConditions)
)
).
reuse_as_least_upper_bound(ModuleInfo, ProcInfo, Reuse1, Reuse2) = Reuse :-
reuse_as_least_upper_bound(ModuleInfo, ProcInfo, Reuse1, Reuse2, Reuse).
reuse_as_from_called_procedure_to_local_reuse_as(ModuleInfo, ProcInfo,
HeadVars, LuData, SharingAs, CalledReuseAs) = LocalReuseAs :-
(
CalledReuseAs = no_reuse,
unexpected($module, $pred, "reuse_as does not specify any reuses")
;
CalledReuseAs = unconditional,
unexpected($module, $pred, "reuse_as is unconditional")
;
CalledReuseAs = conditional(ConditionsCaller),
ConditionsCallee =
list.map(reuse_condition_from_called_proc_to_local_condition(
ModuleInfo, ProcInfo, HeadVars, LuData, SharingAs),
ConditionsCaller),
list.foldl(reuse_as_add_condition(ModuleInfo, ProcInfo),
ConditionsCallee, reuse_as_init, LocalReuseAs)
).
:- func reuse_condition_from_called_proc_to_local_condition(module_info,
proc_info, prog_vars, live_datastructs, sharing_as, reuse_condition) =
reuse_condition.
reuse_condition_from_called_proc_to_local_condition(ModuleInfo, ProcInfo,
HeadVars, LuData, SharingAs, CalledCondition) = LocalCondition :-
(
CalledCondition = always,
unexpected($module, $pred, "explicit condition expected")
;
CalledCondition = condition(CalledDeadNodes,
CalledInUseNodes, CalledSharingAs),
% Translate the dead nodes:
AllDeadNodes = extend_datastructs(ModuleInfo, ProcInfo,
SharingAs, set.to_sorted_list(CalledDeadNodes)),
AllDeadHeadVarNodes = datastructs_project(HeadVars, AllDeadNodes),
(
AllDeadHeadVarNodes = [],
LocalCondition = always
;
AllDeadHeadVarNodes = [_ | _],
% Translate the in use nodes:
AllInUseNodes = extend_datastructs(ModuleInfo, ProcInfo,
SharingAs, list.append(LuData, CalledInUseNodes)),
AllInUseHeadVarNodes = datastructs_project(HeadVars,
AllInUseNodes),
% Translate the sharing information:
AllLocalSharing = sharing_as_comb(ModuleInfo, ProcInfo,
CalledSharingAs, SharingAs),
AllHeadVarLocalSharing = sharing_as_project(HeadVars,
AllLocalSharing),
LocalCondition = condition(set.from_list(AllDeadHeadVarNodes),
AllInUseHeadVarNodes, AllHeadVarLocalSharing)
)
).
reuse_as_satisfied(ModuleInfo, ProcInfo, LiveData, SharingAs, StaticVars,
ReuseAs, Result) :-
(
ReuseAs = no_reuse,
Result = reuse_not_possible(no_reuse)
;
ReuseAs = unconditional,
Result = reuse_possible
;
ReuseAs = conditional(Conditions),
reuse_as_satisfied_2(ModuleInfo, ProcInfo, LiveData, SharingAs,
StaticVars, Conditions, Result0),
% Next to verifying each condition separately, one has to verify
% whether the nodes which are reused in each of the conditions are
% not aliased within the current context. If this would be the
% case, then reuse is not allowed. If this would be allowed, then
% the callee want to reuse the different parts of the input while
% these may point to exactly the same structure, resulting in
% undefined behaviour.
(
Result0 = reuse_possible,
aliases_between_reuse_nodes(ModuleInfo, ProcInfo, SharingAs,
Conditions, AliasedVars),
(
AliasedVars = [],
Result = reuse_possible
;
AliasedVars = [_ | _],
Result = reuse_not_possible(reuse_nodes_have_sharing(
AliasedVars))
)
;
Result0 = reuse_not_possible(_),
Result = Result0
)
).
:- pred reuse_as_satisfied_2(module_info::in, proc_info::in, livedata::in,
sharing_as::in, prog_vars::in, reuse_conditions::in,
reuse_satisfied_result::out) is det.
reuse_as_satisfied_2(_, _, _, _, _, [], reuse_possible).
reuse_as_satisfied_2(ModuleInfo, ProcInfo, LiveData, SharingAs, StaticVars,
[Cond | Conds], Result) :-
reuse_condition_satisfied(ModuleInfo, ProcInfo,
LiveData, SharingAs, StaticVars, Cond, Result0),
(
Result0 = reuse_possible,
reuse_as_satisfied_2(ModuleInfo, ProcInfo, LiveData, SharingAs,
StaticVars, Conds, Result)
;
Result0 = reuse_not_possible(reuse_condition_violated(Vars0)),
% We try to collect all the variables which violate conditions.
reuse_as_satisfied_2(ModuleInfo, ProcInfo, LiveData, SharingAs,
StaticVars, Conds, Result1),
(
Result1 = reuse_not_possible(reuse_condition_violated(Vars1)),
Vars = list.sort_and_remove_dups(Vars0 ++ Vars1),
Result = reuse_not_possible(reuse_condition_violated(Vars))
;
( Result1 = reuse_possible
; Result1 = reuse_not_possible(no_reuse)
),
Result = Result0
;
( Result1 = reuse_not_possible(unknown_livedata)
; Result1 = reuse_not_possible(reuse_nodes_have_sharing(_))
),
unexpected($module, $pred, "unexpected result")
)
;
Result0 = reuse_not_possible(no_reuse),
Result = Result0
;
Result0 = reuse_not_possible(unknown_livedata),
Result = Result0
;
Result0 = reuse_not_possible(reuse_nodes_have_sharing(_)),
unexpected($module, $pred, "reuse_nodes_have_sharing")
).
:- pred aliases_between_reuse_nodes(module_info::in, proc_info::in,
sharing_as::in, list(reuse_condition)::in, prog_vars::out) is det.
aliases_between_reuse_nodes(ModuleInfo, ProcInfo, SharingAs, Conditions,
AliasedVars) :-
list.filter_map(reuse_condition_reusable_nodes, Conditions, ListNodes),
AllNodes0 = set.union_list(ListNodes),
AllNodes = set.to_sorted_list(AllNodes0),
(
AllNodes = [Node | Rest],
aggregate(aliases_between_reuse_nodes_2(ModuleInfo, ProcInfo,
SharingAs, Node, Rest), collect_aliased_vars, set.init,
AliasedVarsSet),
AliasedVars = set.to_sorted_list(AliasedVarsSet)
;
AllNodes = [],
unexpected($module, $pred, "no nodes")
).
:- pred aliases_between_reuse_nodes_2(module_info::in, proc_info::in,
sharing_as::in, datastruct::in, list(datastruct)::in,
pair(datastruct)::out) is nondet.
aliases_between_reuse_nodes_2(ModuleInfo, ProcInfo, SharingAs, Node,
OtherNodes, AliasedNodes) :-
SharingNodes0 = extend_datastruct(ModuleInfo, ProcInfo, SharingAs, Node),
list.delete(SharingNodes0, Node, SharingNodes),
% Check whether none of the structures to which the current Node is
% aliased is subsumed by or subsumes one of the other nodes, including the
% current node itself.
(
list.member(SharingNode, SharingNodes),
there_is_a_subsumption_relation(ModuleInfo, ProcInfo,
[Node | OtherNodes], SharingNode, OtherAliasedNode),
AliasedNodes = SharingNode - OtherAliasedNode
;
OtherNodes = [NextNode | NextOtherNodes],
aliases_between_reuse_nodes_2(ModuleInfo, ProcInfo, SharingAs,
NextNode, NextOtherNodes, AliasedNodes)
).
% Succeed if Data is subsumed or subsumes some of the datastructures in
% Datastructs.
%
:- pred there_is_a_subsumption_relation(module_info::in, proc_info::in,
list(datastruct)::in, datastruct::in, datastruct::out) is nondet.
there_is_a_subsumption_relation(ModuleInfo, ProcInfo, [DataB0 | DataBs],
DataA, DataB) :-
(
datastruct_subsumed_by(ModuleInfo, ProcInfo, DataA, DataB),
DataB = DataB0
;
datastruct_subsumed_by(ModuleInfo, ProcInfo, DataB, DataA),
DataB = DataB0
;
there_is_a_subsumption_relation(ModuleInfo, ProcInfo, DataBs,
DataA, DataB)
).
:- pred collect_aliased_vars(pair(datastruct)::in,
set(prog_var)::in, set(prog_var)::out) is det.
collect_aliased_vars(DataA - DataB, !Vars) :-
set.insert(DataA ^ sc_var, !Vars),
set.insert(DataB ^ sc_var, !Vars).
%-----------------------------------------------------------------------------%
:- pred reuse_condition_satisfied(module_info::in, proc_info::in,
livedata::in, sharing_as::in, prog_vars::in, reuse_condition::in,
reuse_satisfied_result::out) is det.
reuse_condition_satisfied(ModuleInfo, ProcInfo, LiveData, SharingAs,
StaticVars, Condition, Result) :-
(
Condition = always,
Result = reuse_possible
;
Condition = condition(DeadNodes0, InUseNodes, SharingNodes),
DeadNodes = set.to_sorted_list(DeadNodes0),
% Reuse of static vars is not allowed:
StaticDeadNodes = datastructs_project(StaticVars, DeadNodes),
(
StaticDeadNodes = [],
% Using the InUseNodes, and the sharing recorded by the condition,
% compute a new set of livedata that (safely) approximates the
% set of livedata that would have been obtained when looking at
% the program point from where the reuse condition actually comes
% from.
NewSharing = sharing_as_comb(ModuleInfo, ProcInfo, SharingNodes,
SharingAs),
UpdatedLiveData = livedata_add_liveness(ModuleInfo, ProcInfo,
InUseNodes, NewSharing, LiveData),
nodes_are_not_live(ModuleInfo, ProcInfo, DeadNodes,
UpdatedLiveData, NotLiveResult),
(
NotLiveResult = nodes_all_live,
Result = reuse_not_possible(unknown_livedata)
;
NotLiveResult = nodes_are_live(StillLive),
(
StillLive = [],
Result = reuse_possible
;
StillLive = [_ | _],
Vars = datastructs_vars(StillLive),
Result = reuse_not_possible(reuse_condition_violated(Vars))
)
)
;
StaticDeadNodes = [_ | _],
Vars = datastructs_vars(StaticDeadNodes),
Result = reuse_not_possible(reuse_condition_violated(Vars))
)
).
%-----------------------------------------------------------------------------%
from_structure_reuse_domain(ReuseDomain) = ReuseAs :-
(
ReuseDomain = has_no_reuse,
ReuseAs = no_reuse
;
ReuseDomain = has_only_unconditional_reuse,
ReuseAs = unconditional
;
ReuseDomain = has_conditional_reuse(PublicReuseConditions),
ReuseAs = conditional(
from_public_reuse_conditions(PublicReuseConditions))
).
:- func from_public_reuse_conditions(structure_reuse_conditions) =
reuse_conditions.
from_public_reuse_conditions(PublicReuseConditions) =
list.map(from_public_reuse_condition, PublicReuseConditions).
:- func from_public_reuse_condition(structure_reuse_condition) =
reuse_condition.
from_public_reuse_condition(PublicReuseCondition) = ReuseCondition :-
PublicReuseCondition = structure_reuse_condition(DeadNodes, LiveNodes,
PublicSharing),
ReuseCondition = condition(DeadNodes, LiveNodes,
from_structure_sharing_domain(PublicSharing)).
to_structure_reuse_domain(ReuseAs) = ReuseDomain :-
(
ReuseAs = no_reuse,
ReuseDomain = has_no_reuse
;
ReuseAs = unconditional,
ReuseDomain = has_only_unconditional_reuse
;
ReuseAs = conditional(ReuseConditions),
ReuseDomain = has_conditional_reuse(
to_structure_reuse_conditions(ReuseConditions))
).
:- func to_structure_reuse_conditions(reuse_conditions) =
structure_reuse_conditions.
to_structure_reuse_conditions(ReuseConditions) =
list.filter_map(to_structure_reuse_condition, ReuseConditions).
:- func to_structure_reuse_condition(reuse_condition) =
structure_reuse_condition is semidet.
to_structure_reuse_condition(Condition) = StructureReuseCondition :-
Condition = condition(DeadNodes, LiveNodes, SharingAs),
StructureReuseCondition = structure_reuse_condition(DeadNodes, LiveNodes,
to_structure_sharing_domain(SharingAs)).
%-----------------------------------------------------------------------------%
%
% reuse_as_table
%
reuse_as_table_init = reuse_as_table(map.init, bimap.init).
reuse_as_table_search(Table, PPId, ReuseAs_Status) :-
map.search(Table ^ reuse_info_map, PPId, ReuseAs_Status).
reuse_as_table_search_reuse_version_proc(Table, PPId, NoClobbers, NewPPId) :-
bimap.search(Table ^ reuse_version_map, ppid_no_clobbers(PPId, NoClobbers),
NewPPId).
reuse_as_table_reverse_search_reuse_version_proc(Table, NewPPId,
OrigPPId, NoClobbers) :-
( bimap.reverse_search(Table ^ reuse_version_map, Key, NewPPId) ->
Key = ppid_no_clobbers(OrigPPId, NoClobbers)
;
unexpected($module, $pred, "reverse search failed")
).
reuse_as_table_set(PPId, ReuseAs_Status, !Table) :-
T0 = !.Table ^ reuse_info_map,
map.set(PPId, ReuseAs_Status, T0, T),
!Table ^ reuse_info_map := T.
reuse_as_table_insert_reuse_version_proc(PPId, NoClobbers, NewPPId, !Table) :-
T0 = !.Table ^ reuse_version_map,
bimap.det_insert(ppid_no_clobbers(PPId, NoClobbers), NewPPId, T0, T),
!Table ^ reuse_version_map := T.
reuse_as_table_maybe_dump(DoDump, ModuleInfo, Table, !IO) :-
(
DoDump = no
;
DoDump = yes,
reuse_as_table_dump(ModuleInfo, Table, !IO)
).
:- pred reuse_as_table_dump(module_info::in, reuse_as_table::in,
io::di, io::uo) is det.
reuse_as_table_dump(ModuleInfo, Table, !IO) :-
ReuseInfoMap = Table ^ reuse_info_map,
( map.is_empty(ReuseInfoMap) ->
io.write_string("% ReuseTable: Empty\n", !IO)
;
io.write_string("% ReuseTable: PPId --> Reuse\n", !IO),
map.foldl(dump_entries(ModuleInfo), ReuseInfoMap, !IO)
).
:- pred dump_entries(module_info::in, pred_proc_id::in,
reuse_as_and_status::in, io::di, io::uo) is det.
dump_entries(ModuleInfo, PPId, reuse_as_and_status(ReuseAs, _Status), !IO) :-
io.write_string("% ", !IO),
write_pred_proc_id(ModuleInfo, PPId, !IO),
io.write_string("\t--> ", !IO),
io.write_string(reuse_as_short_description(ReuseAs), !IO),
io.nl(!IO).
load_structure_reuse_table(ModuleInfo) = ReuseTable :-
module_info_get_valid_predids(PredIds, ModuleInfo, _ModuleInfo),
list.foldl(load_structure_reuse_table_2(ModuleInfo), PredIds,
reuse_as_table_init, ReuseTable).
:- pred load_structure_reuse_table_2(module_info::in, pred_id::in,
reuse_as_table::in, reuse_as_table::out) is det.
load_structure_reuse_table_2(ModuleInfo, PredId, !ReuseTable) :-
module_info_pred_info(ModuleInfo, PredId, PredInfo),
ProcIds = pred_info_procids(PredInfo),
list.foldl(load_structure_reuse_table_3(ModuleInfo, PredId),
ProcIds, !ReuseTable).
:- pred load_structure_reuse_table_3(module_info::in, pred_id::in,
proc_id::in, reuse_as_table::in, reuse_as_table::out) is det.
load_structure_reuse_table_3(ModuleInfo, PredId, ProcId, !ReuseTable) :-
module_info_proc_info(ModuleInfo, PredId, ProcId, ProcInfo),
proc_info_get_structure_reuse(ProcInfo, MaybePublicReuse),
(
MaybePublicReuse = yes(structure_reuse_domain_and_status(PublicReuse,
Status)),
PPId = proc(PredId, ProcId),
PrivateReuse = from_structure_reuse_domain(PublicReuse),
reuse_as_table_set(PPId, reuse_as_and_status(PrivateReuse, Status),
!ReuseTable)
;
MaybePublicReuse = no
).
%-----------------------------------------------------------------------------%
:- end_module transform_hlds.ctgc.structure_reuse.domain.
%-----------------------------------------------------------------------------%