Files
mercury/compiler/structure_reuse.direct.choose_reuse.m
Zoltan Somogyi 6d1bc24d0b Make vartypes an abstract data type, in preparation for exploring
Estimated hours taken: 4
Branches: main

compiler/prog_data.m:
	Make vartypes an abstract data type, in preparation for exploring
	better representations for it.

compiler/mode_util.m:
	Provide two different versions of a predicate. The generic version
	continues to use map lookups. The other version knows it works on
	prog_vars, so it can use the abstract operations on them provided
	by prog_data.m.

compiler/accumulator.m:
compiler/add_class.m:
compiler/add_heap_ops.m:
compiler/add_pragma.m:
compiler/add_pred.m:
compiler/add_trail_ops.m:
compiler/arg_info.m:
compiler/builtin_lib_types.m:
compiler/bytecode_gen.m:
compiler/call_gen.m:
compiler/clause_to_proc.m:
compiler/closure_analysis.m:
compiler/code_info.m:
compiler/common.m:
compiler/complexity.m:
compiler/const_prop.m:
compiler/constraint.m:
compiler/continuation_info.m:
compiler/cse_detection.m:
compiler/ctgc.datastruct.m:
compiler/ctgc.util.m:
compiler/deep_profiling.m:
compiler/deforest.m:
compiler/dep_par_conj.m:
compiler/det_analysis.m:
compiler/det_report.m:
compiler/det_util.m:
compiler/disj_gen.m:
compiler/equiv_type_hlds.m:
compiler/erl_call_gen.m:
compiler/erl_code_gen.m:
compiler/erl_code_util.m:
compiler/exception_analysis.m:
compiler/float_regs.m:
compiler/follow_vars.m:
compiler/format_call.m:
compiler/goal_path.m:
compiler/goal_util.m:
compiler/hhf.m:
compiler/higher_order.m:
compiler/hlds_clauses.m:
compiler/hlds_goal.m:
compiler/hlds_out_goal.m:
compiler/hlds_out_pred.m:
compiler/hlds_pred.m:
compiler/hlds_rtti.m:
compiler/inlining.m:
compiler/instmap.m:
compiler/intermod.m:
compiler/interval.m:
compiler/lambda.m:
compiler/lco.m:
compiler/live_vars.m:
compiler/liveness.m:
compiler/lookup_switch.m:
compiler/mercury_to_mercury.m:
compiler/ml_accurate_gc.m:
compiler/ml_closure_gen.m:
compiler/ml_code_gen.m:
compiler/ml_code_util.m:
compiler/ml_disj_gen.m:
compiler/ml_lookup_switch.m:
compiler/ml_proc_gen.m:
compiler/ml_unify_gen.m:
compiler/mode_info.m:
compiler/modecheck_call.m:
compiler/modecheck_conj.m:
compiler/modecheck_goal.m:
compiler/modecheck_unify.m:
compiler/modecheck_util.m:
compiler/modes.m:
compiler/par_loop_control.m:
compiler/pd_info.m:
compiler/pd_util.m:
compiler/polymorphism.m:
compiler/post_typecheck.m:
compiler/prog_type_subst.m:
compiler/prop_mode_constraints.m:
compiler/purity.m:
compiler/qual_info.m:
compiler/rbmm.points_to_info.m:
compiler/rbmm.region_liveness_info.m:
compiler/rbmm.region_transformation.m:
compiler/saved_vars.m:
compiler/simplify.m:
compiler/size_prof.m:
compiler/ssdebug.m:
compiler/stack_alloc.m:
compiler/stack_opt.m:
compiler/store_alloc.m:
compiler/structure_reuse.analysis.m:
compiler/structure_reuse.direct.choose_reuse.m:
compiler/structure_reuse.direct.detect_garbage.m:
compiler/structure_reuse.indirect.m:
compiler/structure_sharing.analysis.m:
compiler/structure_sharing.domain.m:
compiler/switch_detection.m:
compiler/table_gen.m:
compiler/term_constr_build.m:
compiler/term_constr_util.m:
compiler/term_traversal.m:
compiler/term_util.m:
compiler/trace_gen.m:
compiler/trailing_analysis.m:
compiler/try_expand.m:
compiler/tupling.m:
compiler/type_constraints.m:
compiler/type_util.m:
compiler/typecheck.m:
compiler/typecheck_errors.m:
compiler/typecheck_info.m:
compiler/unify_gen.m:
compiler/unify_proc.m:
compiler/unique_modes.m:
compiler/untupling.m:
compiler/unused_args.m:
compiler/var_locn.m:
	Conform to the above.

compiler/prog_type.m:
compiler/rbmm.points_to_graph.m:
	Conform to the above.

	Move some comments where they belong.

compiler/stm_expand.m:
	Conform to the above.

	Do not export a predicate that is not used outside this module.

	Disable some debugging output unless it is asked for.

	Remove unnecessary prefixes on variable names.

library/version_array.m:
	Instead writing code for field access lookalike functions and defining
	lookup, set etc in terms of them, write code for lookup, set etc,
	and define the field access lookalike functions in terms of them.

	Change argument orders of some internal predicates to be
	more state variable friendly.

	Fix typos in comments.

tests/hard_coded/version_array_test.exp:
	Conform to the change to version_array.m.
2012-07-02 01:16:39 +00:00

1537 lines
57 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.direct.choose_reuse.m.
% Main authors: nancy.
%
% Given a dead cell table listing the deconstructions that may leave garbage
% (dead cells), we compute the concrete assignments of which constructions can
% profit from these dead cells. Obviously, we want to find those assignments
% which result in the 'best' form of memory reuse possible for the given goals.
%
% Hence, the assignment problem is translated into a mapping problem (inspired
% from Debray's paper: "On copy avoidance in single assignment languages", and
% restricted to reuse of dead cells by at most one new cell).
%
% When assigning constructions to dead deconstructions, a table is first
% computed. For each dead cell, a value is computed that reflects the gain
% a reuse might bring, and the list of constructions involved with reusing it.
% The cell with highest value is selected first, the according constructions
% are annotated, and the table is recomputed. This process is repeated until
% no reusable dead deconstructions are left.
%
% The value of a dead cell (a specific deconstruction) is computed taking
% into account the call graph which can be simplified to take only into account
% construction-unifications, conjunctions, and disjunctions.
% The source of the graph is the deconstruction, the leaves are
% either constructions, or empty. The branches are either conjunctions
% or disjunctions.
% The value of the dead cell is then computed as follows:
% - value of a conjunction = maximum of the values of each of the
% conjunct branches.
% Intuitively: if a dead deconstruction is followed by
% two constructions which might reuse the dead cell: pick
% the one which allows the most potential gain.
% - value of a disjunction = average of the value of each of the
% disjunct branches.
% Intuitively: if a dead deconstruction is followed by
% a disjunction with 2 disjuncts. If reuse is only possible
% in one of the branches, allowing this reuse means that
% a priori reuse will occur in only 50% of the cases.
% The value of the disjunct should take this into account.
% Without precise notion of which branches are executed
% more often, taking the simple average of the values is
% a good approximation.
% - value of a construction = a value that takes into account
% the cost of constructing a new cell and compares it
% to the cost of updating a dead cell. If the arities
% between the dead and new cell differ, a penalty cost
% is added (approximated as the gain one would have had if
% the unusable words would have been reused too).
% Weights are used to estimate all of these costs and are
% hard-coded. I don't think there is any need in making
% these values an option.
%
% Once the table is computed, the cell with highest value is selected.
% To cut the decision between different dead cells with the same
% value, we select the dead cell that has the least number of
% opportunities to be reused.
%
% e.g.
% X can be reused by 5 different constructions,
% but reaches its highest value for a construction C1
% (value 10).
% Y can be reused by only one construction, also C1 (value 10).
%
% First selecting X (and reusing it with construction C1) would
% jeopardize the reuse of Y and leaves us with only one cell reused.
% If, on the contrary, one would select Y first, chances are that
% after recomputing the table, X can still be reused by other
% constructions, hence possibly 2 cells reused.
% Even if Y would be of smaller value, selecting Y first would still
% be more interesting. Hence, instead of selecting the cell
% with highest value, we select the cell with highest
% value/degree ratio, degree being the number of constructions at which
% the cell could potentially be reused.
%
% Note that cells being deconstructed in the different branches of a
% disjunction can now also be reused after the the disjunction.
%
% e.g.:
% (
% ..., X => f(... ), ... % X dies
% ;
% ..., X => g(... ), ... % X dies
% ),
% Y <= f(... ), ... % Y can reuse X
%
% In this example, it is allowed to reuse X for Y. And it will also be
% discovered by the analysis.
%
%-----------------------------------------------------------------------------%
:- module transform_hlds.ctgc.structure_reuse.direct.choose_reuse.
:- interface.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_pred.
:- import_module hlds.hlds_module.
:- import_module transform_hlds.ctgc.structure_reuse.domain.
%-----------------------------------------------------------------------------%
:- pred determine_reuse(module_info::in, proc_info::in, dead_cell_table::in,
hlds_goal::in, hlds_goal::out, reuse_as::out) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.type_util.
:- import_module hlds.hlds_data.
:- import_module parse_tree.prog_data.
:- import_module float.
:- import_module int.
:- import_module maybe.
:- import_module multi_map.
:- import_module require.
:- import_module set.
:- import_module string.
:- import_module term.
%-----------------------------------------------------------------------------%
determine_reuse(ModuleInfo, ProcInfo, DeadCellTable, !Goal, ReuseAs) :-
% Check for local reuse:
module_info_get_globals(ModuleInfo, Globals),
globals.get_reuse_strategy(Globals, Strategy),
BackGroundInfo = background_info_init(Strategy, ModuleInfo, ProcInfo),
choose_reuse_in_goal(BackGroundInfo, DeadCellTable, RemainingDeadCellTable,
!Goal, reuse_as_init, ReuseAs),
globals.lookup_bool_option(Globals, structure_reuse_free_cells, FreeCells),
(
FreeCells = yes,
globals.lookup_bool_option(Globals, very_verbose, VeryVerbose),
check_for_cell_caching(VeryVerbose, RemainingDeadCellTable, !Goal)
;
FreeCells = no
).
%-----------------------------------------------------------------------------%
% A type to collect all the background information needed to process
% each individual goal.
%
:- type background_info
---> background(
back_strategy :: reuse_strategy,
back_module_info :: module_info,
back_proc_info :: proc_info,
back_vartypes :: vartypes
).
:- func background_info_init(reuse_strategy, module_info, proc_info) =
background_info.
background_info_init(Strategy, ModuleInfo, ProcInfo) = Background :-
proc_info_get_vartypes(ProcInfo, VarTypes),
Background = background(Strategy, ModuleInfo, ProcInfo, VarTypes).
%-----------------------------------------------------------------------------%
% Some types and predicates for the administration of the deconstructions,
% constructions and the 'matches' we want to derive from them.
%
% XXX With the --use-atomic-cells option, the compiler generates code
% that uses GC_MALLOC_ATOMIC to allocate memory for heap cells that contain
% no pointers to GCable memory. If we later reuse such a cell and put a pointer
% to GCable memory into it, the Boehm collector will not see that pointer,
% which may lead to the heap cell being pointed to being reclaimed prematurely,
% a bug that will probably be very hard to find.
%
% To avoid this situation, we should
%
% (1) extend deconstruction_spec with a field of type may_use_atomic_alloc,
% indicating whether the potentially reused cell may be atomic or not, and
% (2) ensure that we reuse atomically-created cells only for connstructions
% in which all arguments can be put into atomic cells.
%
% These will require applying type_may_use_atomic_alloc to the arguments of
% both the reused deconstruction unifications and the reusing construction
% unifications.
%
% However, a fix to this problem can wait until structure reuse starts to be
% used in earnest. Until then, Nancy can simply avoid turning on
% --use-atomic-cells.
% Details of a deconstruction yielding garbage.
%
:- type deconstruction_spec
---> decon(
decon_var :: dead_var,
decon_pp :: program_point,
decon_cons_id :: cons_id,
decon_args :: prog_vars,
decon_conds :: reuse_as
).
% Details of a construction possibly reusing some specific garbage cells
% generated at a deconstruction.
%
:- type construction_spec
---> con(
con_pp :: program_point,
con_reuse :: reuse_type
).
% The reuse-type is a basic identification of whether the cons-ids involved
% in the reuse are the same, what the arities of the old and new cells are,
% and which arguments do not need to be updated.
%
:- type reuse_type
---> reuse_type(
same_cons :: bool,
% States whether the corresponding argument in the list of
% arguments of the reused cons needs to be updated when
% reused or not.
% Note that list.length(reuse_fields) is the arity of the
% reused term.
reuse_fields :: list(needs_update),
% A metric measuring the value of the reuse. A high value
% should represent a 'good' reuse (yielding possibly good
% results on the general memory behaviour of the procedure)
% compared to a reuse with a lower value.
reuse_value :: float
).
% A match is a description of a list of deconstructions and a list of
% constructions. The deconstructions and constructions can all be coded
% into reuses, as they are such that at run-time at most one
% deconstruction yielding the dead cell will occur on the same
% execution path as a construction that
% can reuse that cell.
% This means that all the deconstructions can be coded as
% deconstructions yielding dead cell, and all the constructions can be
% coded as constructions reusing the cell that becomes available
% through one of the deconstructions.
%
:- type match
---> match(
decon_specs :: list(deconstruction_spec),
con_specs :: list(construction_spec),
match_value :: float,
match_degree :: int
).
:- type match_table == multi_map(dead_var, match).
% Initialise a deconstruction_spec.
%
:- func deconstruction_spec_init(dead_var, program_point, cons_id,
prog_vars, reuse_as) = deconstruction_spec.
deconstruction_spec_init(Var, PP, ConsId, Args, Cond)
= decon(Var, PP, ConsId, Args, Cond).
% Pre-condition: the set of variables to which the list of deconstructions
% relate (the dead vars) should be a singleton set. In other words,
% all of the deconstructions in a match relate to one and the same
% dying variable.
%
:- func match_init(list(deconstruction_spec)) = match.
match_init(DS) = match(DS, [], 0.0, 0).
% Verify that a match is still 'empty', i.e. has no constructions that can
% reuse the dead cell available from the deconstructions listed in the
% match.
%
:- pred match_has_no_construction_candidates(match::in) is semidet.
match_has_no_construction_candidates(match(_, [], _, _)).
% Determine the variable whose term is involved in the reuse if the
% match would be implemented.
%
:- func match_get_dead_var(match) = dead_var.
match_get_dead_var(Match) = Var :-
GetVar = (func(D) = D ^ decon_var),
DeadVars0 = list.map(GetVar, Match ^ decon_specs),
DeadVars = list.remove_dups(DeadVars0),
(
DeadVars = [Var | Rest],
(
Rest = []
;
Rest = [_ | _],
unexpected($module, $pred, "too many dead vars")
)
;
DeadVars = [],
unexpected($module, $pred, "empty list of vars")
).
% Get the list of cons_ids that the dead variable may have when it
% will be reused.
%
:- func match_get_dead_cons_ids(match) = list(cons_id).
match_get_dead_cons_ids(Match) = ConsIds :-
GetConsId = (func(D) = D ^ decon_cons_id),
ConsIds = list.map(GetConsId, Match ^ decon_specs).
% Determine the reuse condition of the match.
%
:- func match_get_condition(background_info, match) = reuse_as.
match_get_condition(Background, Match) = Condition :-
GetCond = (func(D) = D ^ decon_conds),
Conditions = list.map(GetCond, Match ^ decon_specs),
(
Conditions = [First | Rest],
list.foldl(
reuse_as_least_upper_bound(Background ^ back_module_info,
Background ^ back_proc_info),
Rest, First, Condition)
;
Conditions = [],
unexpected($module, $pred, "no reuse conditions")
).
% Add a construction as a potential place for reusing the garbage
% produced by any of the deconstructions listed in the match.
% This changes the value of the match.
%
:- pred match_add_construction(construction_spec::in,
match::in, match::out) is det.
match_add_construction(ConSpec, Match0, Match) :-
Match0 = match(DeconSpecs0, ConSpecs0, Value0, Degree0),
ConSpecs = [ConSpec | ConSpecs0],
Degree = Degree0 + 1,
FDegree0 = float(Degree0),
FDegree = float(Degree),
Value = (Value0 * FDegree0 + ConSpec ^ con_reuse ^ reuse_value) / FDegree,
Match = match(DeconSpecs0, ConSpecs, Value, Degree).
%-----------------------------------------------------------------------------%
%
% Manipulating the values of matches...
%
:- func highest_match_degree_ratio(match_table) = match.
highest_match_degree_ratio(MatchTable) = Match :-
multi_map.values(MatchTable, Matches),
list.sort(reverse_compare_matches_value_degree, Matches, Sorted),
(
Sorted = [Match | _]
;
Sorted = [],
unexpected($module, $pred, "empty multi_map")
).
:- pred compare_matches_value_degree(match::in, match::in,
comparison_result::out) is det.
compare_matches_value_degree(MatchA, MatchB, Result) :-
VA = match_value_degree(MatchA),
VB = match_value_degree(MatchB),
compare(Result, VA, VB).
:- pred reverse_compare_matches_value_degree(match::in, match::in,
comparison_result::out) is det.
reverse_compare_matches_value_degree(MatchA, MatchB, Result) :-
compare_matches_value_degree(MatchB, MatchA, Result).
:- func match_value_degree(match) = float.
match_value_degree(Match) =
( Match ^ match_value \= 0.0 ->
Match ^ match_value / float(Match ^ match_degree)
;
0.0
).
:- pred compare_matches_value(match::in, match::in,
comparison_result::out) is det.
compare_matches_value(Match1, Match2, Result) :-
V1 = Match1 ^ match_value,
V2 = Match2 ^ match_value,
compare(Result, V1, V2).
:- pred reverse_compare_matches_value(match::in, match::in,
comparison_result::out) is det.
reverse_compare_matches_value(Match1, Match2, Result) :-
compare_matches_value(Match2, Match1, Result).
:- pred match_allows_reuse(match::in) is semidet.
match_allows_reuse(Match) :-
Constructions = Match ^ con_specs,
Value = Match ^ match_value,
Constructions = [_|_],
Value > 0.0.
:- pred highest_match_in_list(list(match)::in, match::in, match::out) is det.
highest_match_in_list(Matches, Match0, Match) :-
list.sort(reverse_compare_matches_value, [Match0 | Matches], Sorted),
(
Sorted = [Match | _]
;
Sorted = [],
unexpected($module, $pred, "empty list of matches")
).
% Given a list of matches concerning the same (list of) deconstruction,
% compute the average reuse value of that deconstruction. This means
% merging all the constructions together into one list, and using the
% average value of the reuses of each of the matches. The final degree
% of the match is set to the sum of all degrees.
%
:- pred average_match(list(match)::in, match::out) is det.
average_match(List, AverageMatch) :-
(
List = [First | Rest],
list.length(List, Length),
P = (pred(M::in, !.Acc::in, !:Acc::out) is det :-
DeconSpecs = !.Acc ^ decon_specs,
ConSpecs = append(!.Acc ^ con_specs, M ^ con_specs),
Val = !.Acc ^ match_value + M ^ match_value,
Deg = !.Acc ^ match_degree + M ^ match_degree,
!:Acc = match(DeconSpecs, ConSpecs, Val, Deg)
),
list.foldl(P, Rest, First, Match0),
AverageMatch = (Match0 ^ match_value :=
(Match0 ^ match_value / float(Length)))
;
List = [],
unexpected($module, $pred, "empty list")
).
%-----------------------------------------------------------------------------%
%
% Process a single goal:
%
% * determine a match table
% * find the best match
% * annotate the goal with the reuse described by that match
% * and reprocess the goal until no matches are found.
:- pred choose_reuse_in_goal(background_info::in, dead_cell_table::in,
dead_cell_table::out, hlds_goal::in, hlds_goal::out, reuse_as::in,
reuse_as::out) is det.
choose_reuse_in_goal(Background, !DeadCellTable, !Goal, !ReuseAs) :-
ModuleInfo = Background ^ back_module_info,
module_info_get_globals(ModuleInfo, Globals),
globals.lookup_bool_option(Globals, very_verbose, VeryVerbose),
% Compute a match table.
compute_match_table(Background, !.DeadCellTable, !.Goal, MatchTable),
% As long as the match table is not empty, pick out the match with the
% highest value, annotate the goal accordingly, and repeat the procedure.
% If the match table is empty, the work is finished.
( multi_map.is_empty(MatchTable) ->
true
;
% Select the deconstructions-constructions with highest value.
Match = highest_match_degree_ratio(MatchTable),
(
Match ^ con_specs = []
;
Match ^ con_specs = [_ | _],
% Maybe dump all the matches recorded in the table, highlight the
% match with the highest value.
trace [io(!IO)] (
maybe_write_string(VeryVerbose, "% Reuse results: \n", !IO),
maybe_dump_match_table(VeryVerbose, MatchTable, Match, !IO)
),
OldGoal = !.Goal,
OldReuseAs = !.ReuseAs,
% Realise the reuses by explicitly annotating the procedure goal.
annotate_reuses_in_goal(Background, Match, !Goal),
% Remove the deconstructions from the available map of dead cells.
remove_deconstructions_from_dead_cell_table(Match, !DeadCellTable),
% Add the conditions involved in the reuses to the existing
% conditions.
ProcInfo = Background ^ back_proc_info,
reuse_as_least_upper_bound(ModuleInfo, ProcInfo,
match_get_condition(Background, Match), !ReuseAs),
% If there would be too many reuse conditions on this procedure
% by taking the reuse opportunity, just drop it.
globals.lookup_int_option(Globals, structure_reuse_max_conditions,
MaxConditions),
( reuse_as_count_conditions(!.ReuseAs) > MaxConditions ->
!:Goal = OldGoal,
!:ReuseAs = OldReuseAs
;
true
),
% Process the goal for further reuse-matches.
choose_reuse_in_goal(Background, !DeadCellTable, !Goal, !ReuseAs)
)
).
:- pred remove_deconstructions_from_dead_cell_table(match::in,
dead_cell_table::in, dead_cell_table::out) is det.
remove_deconstructions_from_dead_cell_table(Match, !DeadCellTable) :-
DeconSpecs = Match ^ decon_specs,
list.foldl(remove_deconstruction_from_dead_cell_table, DeconSpecs,
!DeadCellTable).
:- pred remove_deconstruction_from_dead_cell_table(deconstruction_spec::in,
dead_cell_table::in, dead_cell_table::out) is det.
remove_deconstruction_from_dead_cell_table(DeconSpec, !DeadCellTable) :-
dead_cell_table_remove(DeconSpec ^ decon_pp, !DeadCellTable).
%-----------------------------------------------------------------------------%
%
% Compute the match table for a given goal
%
% The table is computed by traversing the whole goal. For each
% deconstruction encountered that is also listed in the dead_cell_table,
% compute a match.
%
:- pred compute_match_table(background_info::in, dead_cell_table::in,
hlds_goal::in, match_table::out) is det.
compute_match_table(Background, DeadCellTable, Goal, MatchTable) :-
ContinuationGoals = [],
compute_match_table_with_continuation(Background, DeadCellTable,
Goal, ContinuationGoals, multi_map.init, MatchTable).
:- pred compute_match_table_goal_list(background_info::in, dead_cell_table::in,
hlds_goals::in, match_table::in, match_table::out) is det.
compute_match_table_goal_list(Background, DeadCellTable, Goals, !Table) :-
(
Goals = []
;
Goals = [CurrentGoal | Cont],
compute_match_table_with_continuation(Background, DeadCellTable,
CurrentGoal, Cont, !Table)
).
:- pred compute_match_table_with_continuation(background_info::in,
dead_cell_table::in, hlds_goal::in, hlds_goals::in,
match_table::in, match_table::out) is det.
compute_match_table_with_continuation(Background, DeadCellTable,
CurrentGoal, Cont, !Table) :-
CurrentGoal = hlds_goal(GoalExpr, GoalInfo),
(
GoalExpr = unify(_, _, _, Unification, _),
( Unification = deconstruct(Var, ConsId, Args, _, _, _) ->
ProgramPoint = program_point_init(GoalInfo),
(
Condition = dead_cell_table_search(ProgramPoint, DeadCellTable)
->
ReuseAs = reuse_as_init_with_one_condition(Condition),
DeconstructionSpec = deconstruction_spec_init(Var,
ProgramPoint, ConsId, Args, ReuseAs),
Match0 = match_init([DeconstructionSpec]),
find_best_match_in_conjunction(Background, Cont,
Match0, Match),
multi_map.set(Var, Match, !Table)
;
true
)
;
true
),
compute_match_table_goal_list(Background, DeadCellTable, Cont, !Table)
;
GoalExpr = plain_call(_, _, _, _, _, _),
compute_match_table_goal_list(Background, DeadCellTable,
Cont, !Table)
;
GoalExpr = generic_call( _, _, _, _, _),
compute_match_table_goal_list(Background, DeadCellTable,
Cont, !Table)
;
GoalExpr = call_foreign_proc(_, _, _, _, _, _, _),
compute_match_table_goal_list(Background, DeadCellTable,
Cont, !Table)
;
GoalExpr = conj(_, Goals),
list.append(Goals, Cont, NewCont),
compute_match_table_goal_list(Background, DeadCellTable,
NewCont, !Table)
;
GoalExpr = disj(Goals),
compute_match_table_in_disjunction(Background, DeadCellTable, Goals,
Cont, !Table),
compute_match_table_goal_list(Background, DeadCellTable, Cont, !Table)
;
GoalExpr = switch(_, _, Cases),
Goals = list.map((func(C) = C ^ case_goal), Cases),
compute_match_table_in_disjunction(Background, DeadCellTable,
Goals, Cont, !Table),
compute_match_table_goal_list(Background, DeadCellTable, Cont, !Table)
;
GoalExpr = negation(Goal),
% If Goal contains deconstructions, they should not be reused within
% Cont.
compute_match_table_with_continuation(Background, DeadCellTable,
Goal, [], !Table),
compute_match_table_goal_list(Background, DeadCellTable, Cont,
!Table)
;
GoalExpr = scope(_, Goal),
% XXX We should special-case the handling of from_ground_term_construct
% scopes. Since they construct ground terms statically, there is no
% uniqueness we can exploit,
compute_match_table_with_continuation(Background, DeadCellTable,
Goal, Cont, !Table)
;
GoalExpr = if_then_else(_, CondGoal, ThenGoal, ElseGoal),
multi_map.init(Table0),
compute_match_table_with_continuation(Background, DeadCellTable,
CondGoal, [ThenGoal], Table0, TableThen),
compute_match_table_with_continuation(Background, DeadCellTable,
ElseGoal, [], Table0, TableElse),
multi_map.merge(TableThen, !Table),
multi_map.merge(TableElse, !Table),
process_possible_common_dead_vars(Background, Cont,
[TableThen, TableElse], CommonDeadVarsTables),
list.foldl(multi_map.merge, CommonDeadVarsTables, !Table),
compute_match_table_goal_list(Background, DeadCellTable, Cont,
!Table)
;
GoalExpr = shorthand(_),
% These should have been expanded out by now.
unexpected($module, $pred, "shorthand")
).
:- pred compute_match_table_in_disjs(background_info::in, dead_cell_table::in,
hlds_goals::in, list(match_table)::out) is det.
compute_match_table_in_disjs(Background, DeadCellTable, Branches, Tables) :-
list.map(compute_match_table(Background, DeadCellTable),
Branches, Tables).
:- pred compute_match_table_in_disjunction(background_info::in,
dead_cell_table::in, hlds_goals::in, hlds_goals::in,
match_table::in, match_table::out) is det.
compute_match_table_in_disjunction(Background, DeadCellTable, DisjGoals, Cont,
!Table) :-
% Compute a match table for each of the branches of the disjunction.
% Each of these tables will contain information about local reuses
% w.r.t. the disjunction, i.e. a data structure is reused within the
% same branch in which it dies.
compute_match_table_in_disjs(Background, DeadCellTable, DisjGoals,
DisjTables),
list.foldl(multi_map.merge, DisjTables, !Table),
% It is possible that each of the branches of the disjunctions
% deconstructs the same (non local) dead variable. In such a case, we
% need to check if that dead variable can be reused outside of the
% disjunction.
process_possible_common_dead_vars(Background, Cont, DisjTables,
CommonDeadVarsDisjTables),
list.foldl(multi_map.merge, CommonDeadVarsDisjTables, !Table).
:- pred process_possible_common_dead_vars(background_info::in, hlds_goals::in,
list(match_table)::in, list(match_table)::out) is det.
process_possible_common_dead_vars(Background, Cont, DisjTables,
ExtraTables) :-
CommonDeadVars = common_vars(DisjTables),
(
CommonDeadVars = [_ | _],
list.filter_map(process_common_var(Background, Cont, DisjTables),
CommonDeadVars, ExtraTables)
;
CommonDeadVars = [],
ExtraTables = []
).
:- func common_vars(list(match_table)) = dead_vars.
common_vars(Tables) = CommonVars :-
(
Tables = [First | RestTables],
CommonVars = list.foldl(common_var_with_list, RestTables,
map.keys(First))
;
Tables = [],
CommonVars = []
).
:- func common_var_with_list(match_table, prog_vars) = dead_vars.
common_var_with_list(Table, List0) = List :-
map.keys(Table, Keys),
Set = set.intersect(list_to_set(List0), list_to_set(Keys)),
List = set.to_sorted_list(Set).
:- pred process_common_var(background_info::in, hlds_goals::in,
list(match_table)::in, dead_var::in, match_table::out) is semidet.
process_common_var(Background, Cont, DisjTables, CommonDeadVar, Table) :-
Match0 = match_init(deconstruction_specs(CommonDeadVar, DisjTables)),
find_best_match_in_conjunction(Background, Cont, Match0, Match),
match_allows_reuse(Match), % can fail
multi_map.init(Table0),
multi_map.det_insert(CommonDeadVar, Match, Table0, Table).
:- func deconstruction_specs(prog_var, list(match_table)) =
list(deconstruction_spec).
deconstruction_specs(DeadVar, Tables) = DeconstructionSpecs :-
list.foldl(deconstruction_specs_2(DeadVar), Tables, [],
DeconstructionSpecs).
:- pred deconstruction_specs_2(prog_var::in, match_table::in,
list(deconstruction_spec)::in, list(deconstruction_spec)::out) is det.
deconstruction_specs_2(DeadVar, Table, !DeconstructionSpecs) :-
multi_map.lookup(Table, DeadVar, Matches),
NewSpecs = list.condense(list.map(match_get_decon_specs, Matches)),
append(NewSpecs, !DeconstructionSpecs).
:- func match_get_decon_specs(match) = list(deconstruction_spec).
match_get_decon_specs(Match) = Match ^ decon_specs.
%-----------------------------------------------------------------------------%
%
% Find construction unifications for dead cells, compute the values of the
% matches.
%
% Compute the value of a dead cell with respect to its possible reuses in
% a conjunction of goals. If reuse is possible, add the specification of
% the construction where it can be reused to the list of constructions
% recorded in the match.
%
% In a conjunction, a dead cell can only be reused in at most one of its
% direct children. This means that for each child a new value is computed.
% At the end of a conjunction, we immediately choose the reuse with the
% highest value.
%
% XXX This may not be such a good idea, as the notion of "degree" is used
% to decide between reuses with the same value later on, once the full
% match_table is computed.
%
% XXX What is the thing with the degrees here?
%
:- pred find_best_match_in_conjunction(background_info::in, hlds_goals::in,
match::in, match::out) is det.
find_best_match_in_conjunction(Background, Goals, !Match) :-
Match0 = !.Match,
list.map(find_match_in_goal(Background, Match0), Goals, ExclusiveMatches),
Degree = count_candidates(ExclusiveMatches),
highest_match_in_list(ExclusiveMatches, !Match),
!Match ^ match_degree := Degree.
% Compute the matches for a dead cell in the context of a disjunction. For
% each branch, a different match may be found. At the end, these matches
% are merged together into one single match, taking the average of match
% values to be the value of the final match. Each construction involved in
% the reuses is counted as a possibility for reuse, hence is reflected in
% the degree of the final match description.
%
:- pred find_match_in_disjunction(background_info::in, hlds_goals::in,
match::in, match::out) is det.
find_match_in_disjunction(Background, Branches, !Match) :-
(
Branches = []
;
Branches = [_ | _],
list.map(find_match_in_goal(Background, !.Match), Branches,
BranchMatches),
average_match(BranchMatches, !:Match)
).
:- pred find_match_in_goal(background_info::in, match::in, hlds_goal::in,
match::out) is det.
find_match_in_goal(Background, Match0, Goal, Match) :-
find_match_in_goal_2(Background, Goal, Match0, Match).
:- pred find_match_in_goal_2(background_info::in, hlds_goal::in,
match::in, match::out) is det.
find_match_in_goal_2(Background, Goal, !Match) :-
Goal = hlds_goal(GoalExpr, GoalInfo),
(
GoalExpr = unify(_, _, _, Unification, _),
(
Unification = construct(Var, Cons, Args, _, _, _, _),
(
lookup_var_type(Background ^ back_vartypes, Var, VarType),
top_cell_may_be_reusable(Background ^ back_module_info,
VarType),
% Is the construction still looking for reuse-possibilities...
empty_reuse_description(goal_info_get_reuse(GoalInfo))
->
% Is it possible for the construction to reuse the dead cell
% specified by the match?
verify_match(Background, Var, Cons, Args,
program_point_init(GoalInfo), !Match)
;
true
)
;
( Unification = deconstruct(_, _, _, _, _, _)
; Unification = assign(_, _)
; Unification = simple_test(_, _)
)
;
Unification = complicated_unify(_, _, _),
unexpected($module, $pred, "complicated unify")
)
;
GoalExpr = plain_call(_, _, _, _, _, _)
;
GoalExpr = generic_call( _, _, _, _, _)
;
GoalExpr = call_foreign_proc(_, _, _, _, _, _, _)
;
GoalExpr = conj(_, Goals),
find_best_match_in_conjunction(Background, Goals, !Match)
;
GoalExpr = disj(Goals),
find_match_in_disjunction(Background, Goals, !Match)
;
GoalExpr = switch(_, _, Cases),
Goals = list.map((func(C) = C ^ case_goal), Cases),
find_match_in_disjunction(Background, Goals, !Match)
;
GoalExpr = if_then_else(_, CondGoal, ThenGoal, ElseGoal),
Match0 = !.Match,
find_best_match_in_conjunction(Background, [CondGoal, ThenGoal],
Match0, MatchThen),
find_match_in_goal_2(Background, ElseGoal, Match0, MatchElse),
average_match([MatchThen, MatchElse], !:Match)
;
GoalExpr = negation(_)
;
GoalExpr = scope(_, ScopeGoal),
% XXX We should special-case the handling of from_ground_term_construct
% scopes. Since they construct ground terms statically, there is no
% uniqueness we can exploit,
find_match_in_goal_2(Background, ScopeGoal, !Match)
;
GoalExpr = shorthand(_),
unexpected($module, $pred, "shorthand")
).
:- func count_candidates(list(match)) = int.
count_candidates(Matches) = list.foldl(add_degree, Matches, 0).
:- func add_degree(match, int) = int.
add_degree(Match, Degree0) = Degree0 + Match ^ match_degree.
:- pred empty_reuse_description(reuse_description::in) is semidet.
empty_reuse_description(no_reuse_info).
%-----------------------------------------------------------------------------%
%
% Verify the value of a match for a given construction
%
% The value is computed using the following rule:
%
% Gain = (Alfa + Gamma) * ArityNewCell + Beta
% - Gamma * (ArityNewCell - UptoDateFields)
% - ( SameCons? Beta; 0)
% - Alfa * (ArityOldCell - ArityNewCell)
%
% where
% * Alfa: cost of allocating one single memory cell on the heap;
% * Gamma: cost of setting the value of one single memory cell on the heap;
% * Beta: cost of setting the value of the cons_id field;
:- func alfa_value = int is det.
alfa_value = 5.
:- func gamma_value = int is det.
gamma_value = 1.
:- func beta_value = int is det.
beta_value = 1.
:- pred verify_match(background_info::in, prog_var::in, cons_id::in,
prog_vars::in, program_point::in, match::in, match::out) is det.
verify_match(Background, NewVar, NewCons, NewArgs, PP, !Match) :-
DeconSpecs = !.Match ^ decon_specs,
(
% The construction must be compatible with *all* deconstruction specs.
% Otherwise we may try to reuse a cell which is only compatible through
% one code path but not another.
list.map(compute_reuse_type(Background, NewVar, NewCons, NewArgs),
DeconSpecs, ReuseTypes),
ReuseType = glb_reuse_types(ReuseTypes) % Can Fail.
->
ConSpec = con(PP, ReuseType),
match_add_construction(ConSpec, !Match)
;
true
).
% compute_reuse_type(Background, NewVar, NewCons, NewArgs,
% DeconstructionSpecification) = Cost (represented as a reuse_type).
%
% Compute a description (including its cost) of reusing the
% specified deconstruction for the construction of the new var (NewVar),
% with cons_id NewCons, and arguments NewArgs.
%
% The predicate fails if the construction is incompatible with the
% deconstructed dead data structure.
%
:- pred compute_reuse_type(background_info::in, prog_var::in, cons_id::in,
prog_vars::in, deconstruction_spec::in, reuse_type::out) is semidet.
compute_reuse_type(Background, NewVar, NewCons, NewCellArgs, DeconSpec,
ReuseType) :-
DeconSpec = decon(DeadVar, _, DeadCons, DeadCellArgs, _),
ModuleInfo = Background ^ back_module_info,
VarTypes = Background ^ back_vartypes,
( NewCons = DeadCons ->
SameCons = yes
;
SameCons = no,
% XXX All the reuse code was written before packed and double word
% arguments were introduced. For now only allow reuse of cells with
% packed fields when the dead variable and the new variable have the
% same constructor.
cons_has_normal_fields(ModuleInfo, NewCons),
cons_has_normal_fields(ModuleInfo, DeadCons)
),
NewNumArgs = list.length(NewCellArgs),
DeadNumArgs = list.length(DeadCellArgs),
% Cells with arity zero can not reuse heap cells.
NewNumArgs \= 0,
% Include the space needed for secondary tags.
has_secondary_tag(ModuleInfo, VarTypes, NewVar, NewCons, SecTag),
has_secondary_tag(ModuleInfo, VarTypes, DeadVar, DeadCons, DeadSecTag),
NewArity = NewNumArgs + (SecTag = yes -> 1 ; 0),
DeadArity = DeadNumArgs + (DeadSecTag = yes -> 1 ; 0),
% The new cell must not be bigger than the dead cell.
NewArity =< DeadArity,
% Verify whether the cons_ids and arities match the reuse constraint
% specified by the user.
Constraint = Background ^ back_strategy,
DiffArity = DeadArity - NewArity,
(
Constraint = within_n_cells_difference(N),
DiffArity =< N
;
Constraint = same_cons_id,
SameCons = yes
),
% Upon success of all the previous checks, determine the number of
% fields that do not require an update if the construction unification
% would reuse the deconstructed cell.
%
ReuseFields = already_correct_fields(SecTag, NewCellArgs,
DeadSecTag, DeadCellArgs),
UpToDateFields = list.length(
list.delete_all(ReuseFields, needs_update)),
%
% Finally, compute the value of this reuse-configuration.
%
( SameCons = yes -> SameConsV = 0; SameConsV = 1),
Weight = ( (alfa_value + gamma_value) * NewArity + beta_value
- gamma_value * (NewArity - UpToDateFields)
- beta_value * SameConsV
- alfa_value * DiffArity ),
Weight > 0,
ReuseType = reuse_type(SameCons, ReuseFields, float(Weight)).
:- pred cons_has_normal_fields(module_info::in, cons_id::in) is semidet.
cons_has_normal_fields(ModuleInfo, Cons) :-
(
Cons = cons(_, _, TypeCtor),
get_cons_defn_det(ModuleInfo, TypeCtor, Cons, ConsDefn),
ConsArgs = ConsDefn ^ cons_args,
all [Arg] (
list.member(Arg, ConsArgs)
=>
Arg = ctor_arg(_, _, full_word, _)
)
;
Cons = tuple_cons(_)
;
( Cons = closure_cons(_, _)
; Cons = int_const(_)
; Cons = float_const(_)
; Cons = char_const(_)
; Cons = string_const(_)
; Cons = impl_defined_const(_)
; Cons = type_ctor_info_const(_, _, _)
; Cons = base_typeclass_info_const(_, _, _, _)
; Cons = type_info_cell_constructor(_)
; Cons = typeclass_info_cell_constructor
; Cons = tabling_info_const(_)
; Cons = table_io_decl(_)
; Cons = deep_profiling_proc_layout(_)
),
unexpected($module, $pred, "unusual cons_id")
).
:- func glb_reuse_types(list(reuse_type)) = reuse_type is semidet.
glb_reuse_types([First|Rest]) =
list.foldl(glb_reuse_types_2, Rest, First).
:- func glb_reuse_types_2(reuse_type, reuse_type) = reuse_type.
glb_reuse_types_2(R1, R2) = R :-
R1 = reuse_type(SameCons1, Fields1, V1),
R2 = reuse_type(SameCons2, Fields2, V2),
R = reuse_type(SameCons1 `and` SameCons2, Fields1 `ands` Fields2,
(V1 + V2) / 2.00 ).
:- func ands(list(needs_update), list(needs_update)) = list(needs_update).
ands(L1, L2) = L :-
(
length(L1) =< length(L2)
->
L1b = L1,
L2b = take_upto(length(L1), L2)
;
L1b = take_upto(length(L2), L1),
L2b = L2
),
L = list.map_corresponding(needs_update_and, L1b, L2b).
:- func needs_update_and(needs_update, needs_update) = needs_update.
needs_update_and(needs_update, needs_update) = needs_update.
needs_update_and(needs_update, does_not_need_update) = needs_update.
needs_update_and(does_not_need_update, needs_update) = needs_update.
needs_update_and(does_not_need_update, does_not_need_update) =
does_not_need_update.
%-----------------------------------------------------------------------------%
% has_secondary_tag(Var, ConsId, ExplicitSecTag) returns `yes' iff the
% variable, Var, with cons_id, ConsId, requires a remote
% secondary tag to distinguish between its various functors.
%
:- pred has_secondary_tag(module_info::in, vartypes::in,
prog_var::in, cons_id::in, bool::out) is det.
has_secondary_tag(ModuleInfo, VarTypes, Var, ConsId, SecondaryTag) :-
(
lookup_var_type(VarTypes, Var, Type),
type_to_type_defn_body(ModuleInfo, Type, TypeBody),
TypeBody = hlds_du_type(_, ConsTagValues, _, _, _, _, _, _, _),
map.search(ConsTagValues, ConsId, ConsTag),
MaybeSecondaryTag = get_secondary_tag(ConsTag),
MaybeSecondaryTag = yes(_)
->
SecondaryTag = yes
;
SecondaryTag = no
).
% already_correct_fields(ExplicitSecTagC, VarsC, ExplicitSecTagR, VarsR)
% takes a list of variables, VarsC, which are the arguments for the cell to
% be constructed and the list of variables, VarsR, which are the arguments
% for the cell to be reused and returns a list of 'needs_update' values.
% Each occurrence of 'does_not_need_update' indicates that the argument at
% the corresponding position in the list of arguments already has the
% correct value stored in it. To do this correctly we
% need to know whether each cell has a secondary tag field.
%
:- func already_correct_fields(bool, prog_vars, bool, prog_vars) =
list(needs_update).
already_correct_fields(ExplicitSecTagC, CurrentCellVars, ExplicitSecTagR,
ReuseCellVars) = ReuseFields :-
NeedsNoUpdate = already_correct_fields_2(ExplicitSecTagC, CurrentCellVars,
ExplicitSecTagR, ReuseCellVars),
LengthC = list.length(CurrentCellVars),
LengthB = list.length(NeedsNoUpdate),
NeedsUpdate = list.duplicate(LengthC - LengthB, needs_update),
ReuseFields = NeedsNoUpdate ++ NeedsUpdate.
:- func already_correct_fields_2(bool, prog_vars, bool, prog_vars)
= list(needs_update).
already_correct_fields_2(yes, CurrentCellVars, yes, ReuseCellVars)
= equals(CurrentCellVars, ReuseCellVars).
already_correct_fields_2(yes, CurrentCellVars, no, ReuseCellVars)
= [needs_update | equals(CurrentCellVars, drop_one(ReuseCellVars))].
already_correct_fields_2(no, CurrentCellVars, yes, ReuseCellVars)
= [needs_update | equals(drop_one(CurrentCellVars), ReuseCellVars)].
already_correct_fields_2(no, CurrentCellVars, no, ReuseCellVars)
= equals(CurrentCellVars, ReuseCellVars).
% equals(ListA, ListB) produces a list of 'needs_update' that indicates
% whether the corresponding elements from ListA and ListB are equal. If
% ListA and ListB are of different lengths, the resulting list is the
% length of the shorter of the two.
%
:- func equals(list(T), list(T)) = list(needs_update).
equals([], []) = [].
equals([], [_|_]) = [].
equals([_|_], []) = [].
equals([X | Xs], [Y | Ys]) = [NeedsUpdate | equals(Xs, Ys)] :-
( X = Y ->
NeedsUpdate = does_not_need_update
;
NeedsUpdate = needs_update
).
:- func drop_one(list(T)) = list(T).
drop_one([]) = [].
drop_one([_ | Xs]) = Xs.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
%
% Once a match is selected (hence a set of deconstructions and matching
% constructions), annotate all the involved unifications in the goal.
%
:- pred annotate_reuses_in_goal(background_info::in, match::in, hlds_goal::in,
hlds_goal::out) is det.
annotate_reuses_in_goal(Background, Match, !Goal) :-
!.Goal = hlds_goal(GoalExpr0, GoalInfo0),
(
GoalExpr0 = unify(_, _, _, Unification, _),
GoalExpr = GoalExpr0,
annotate_reuse_for_unification(Background, Match, Unification,
GoalInfo0, GoalInfo)
;
GoalExpr0 = plain_call(_, _, _, _, _, _),
GoalExpr = GoalExpr0,
GoalInfo = GoalInfo0
;
GoalExpr0 = generic_call( _, _, _, _, _),
GoalExpr = GoalExpr0,
GoalInfo = GoalInfo0
;
GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _),
GoalExpr = GoalExpr0,
GoalInfo = GoalInfo0
;
GoalExpr0 = conj(A, Goals0),
list.map(annotate_reuses_in_goal(Background, Match), Goals0, Goals),
GoalExpr = conj(A, Goals),
GoalInfo = GoalInfo0
;
GoalExpr0 = disj(Goals0),
list.map(annotate_reuses_in_goal(Background, Match), Goals0, Goals),
GoalExpr = disj(Goals),
GoalInfo = GoalInfo0
;
GoalExpr0 = switch(A, B, Cases0),
list.map(annotate_reuses_in_case(Background, Match), Cases0, Cases),
GoalExpr = switch(A, B, Cases),
GoalInfo = GoalInfo0
;
GoalExpr0 = negation(_),
GoalExpr = GoalExpr0,
GoalInfo = GoalInfo0
;
GoalExpr0 = scope(A, ScopeGoal0),
% XXX We should special-case the handling of from_ground_term_construct
% scopes. Since they construct ground terms statically, there is no
% uniqueness we can exploit,
annotate_reuses_in_goal(Background, Match, ScopeGoal0, ScopeGoal),
GoalExpr = scope(A, ScopeGoal),
GoalInfo = GoalInfo0
;
GoalExpr0 = if_then_else(A, CondGoal0, ThenGoal0, ElseGoal0),
annotate_reuses_in_goal(Background, Match, CondGoal0, CondGoal),
annotate_reuses_in_goal(Background, Match, ThenGoal0, ThenGoal),
annotate_reuses_in_goal(Background, Match, ElseGoal0, ElseGoal),
GoalExpr = if_then_else(A, CondGoal, ThenGoal, ElseGoal),
GoalInfo = GoalInfo0
;
GoalExpr0 = shorthand(_),
% These should have been expanded out by now.
unexpected($module, $pred, "shorthand.")
),
!:Goal = hlds_goal(GoalExpr, GoalInfo).
:- pred annotate_reuses_in_case(background_info::in, match::in,
case::in, case::out) is det.
annotate_reuses_in_case(Background, Match, !Case) :-
!.Case = case(MainConsId, OtherConsIds, Goal0),
annotate_reuses_in_goal(Background, Match, Goal0, Goal),
!:Case = case(MainConsId, OtherConsIds, Goal).
:- pred annotate_reuse_for_unification(background_info::in, match::in,
unification::in, hlds_goal_info::in, hlds_goal_info::out) is det.
annotate_reuse_for_unification(Background, Match, Unification, !GoalInfo) :-
CurrentProgramPoint = program_point_init(!.GoalInfo),
(
Unification = deconstruct(_, _, _, _, _, _),
(
match_find_deconstruction_spec(Match, CurrentProgramPoint,
_DeconSpec)
->
goal_info_set_reuse(potential_reuse(cell_died), !GoalInfo)
;
true
)
;
Unification = construct(_, _, _, _, _, _, _),
(
match_find_construction_spec(Match, CurrentProgramPoint, ConSpec)
->
DeadVar = match_get_dead_var(Match),
DeadConsIds = match_get_dead_cons_ids(Match),
ReuseAs = match_get_condition(Background, Match),
ReuseFields = ConSpec ^ con_reuse ^ reuse_fields,
( reuse_as_conditional_reuses(ReuseAs) ->
Kind = conditional_reuse
; reuse_as_all_unconditional_reuses(ReuseAs) ->
Kind = unconditional_reuse
;
% reuse_as_no_reuses(ReuseAs)
unexpected($module, $pred, "no reuse conditions")
),
CellReused = cell_reused(DeadVar, Kind, DeadConsIds,
ReuseFields),
(
Kind = conditional_reuse,
KindReuse = potential_reuse(CellReused)
;
% When the reuse is unconditional, we can safely annotate
% that the unification is always a reuse unification.
Kind = unconditional_reuse,
KindReuse = reuse(CellReused)
),
goal_info_set_reuse(KindReuse, !GoalInfo)
;
true
)
;
Unification = assign(_, _)
;
Unification = simple_test(_, _)
;
Unification = complicated_unify(_, _, _),
unexpected($module, $pred, "complicated_unify")
).
:- pred match_find_deconstruction_spec(match::in, program_point::in,
deconstruction_spec::out) is semidet.
match_find_deconstruction_spec(Match, ProgramPoint, DeconstructionSpec) :-
list.filter(deconstruction_spec_with_program_point(ProgramPoint),
Match ^ decon_specs, [DeconstructionSpec]).
:- pred match_find_construction_spec(match::in, program_point::in,
construction_spec::out) is semidet.
match_find_construction_spec(Match, ProgramPoint, ConstructionSpec) :-
list.filter(construction_spec_with_program_point(ProgramPoint),
Match ^ con_specs, [ConstructionSpec]).
:- pred deconstruction_spec_with_program_point(program_point::in,
deconstruction_spec::in) is semidet.
deconstruction_spec_with_program_point(DeconstructionSpec ^ decon_pp,
DeconstructionSpec).
:- pred construction_spec_with_program_point(program_point::in,
construction_spec::in) is semidet.
construction_spec_with_program_point(ConstructionSpec ^ con_pp,
ConstructionSpec).
%-----------------------------------------------------------------------------%
%
% Predicates to print intermediate results as stored in a match_table.
%
:- func line_length = int.
line_length = 79.
:- pred dump_line(string::in, io::di, io::uo) is det.
dump_line(Msg, !IO) :-
Prefix = "%---",
Start = string.append(Prefix, Msg),
Remainder = line_length - string.count_codepoints(Start) - 1,
Line = Start ++ string.duplicate_char('-', Remainder),
io.write_string(Line, !IO),
io.write_string("%\n", !IO).
:- pred maybe_dump_match_table(bool::in, match_table::in, match::in,
io::di, io::uo) is det.
maybe_dump_match_table(VeryVerbose, MatchTable, HighestMatch, !IO) :-
(
VeryVerbose = yes,
dump_line("reuse table", !IO),
io.write_string("%\t|\tvar\t|\tvalue\t|\tdegree\n", !IO),
dump_match("%-sel- ", HighestMatch, !IO),
dump_full_table(MatchTable, !IO),
dump_line("", !IO)
;
VeryVerbose = no
).
:- pred dump_match(string::in, match::in, io::di, io::uo) is det.
dump_match(Prefix, Match, !IO) :-
io.write_string(Prefix, !IO),
io.write_string("\t|\t", !IO),
io.write_int(term.var_to_int(match_get_dead_var(Match)), !IO),
io.write_string("\t|\t", !IO),
Val = Match ^ match_value,
(
Val \= 0.0
->
io.format("%.2f", [f(Val)], !IO)
;
io.write_string("-", !IO)
),
Degree = Match ^ match_degree,
io.write_string("\t|\t", !IO),
io.write_int(Degree, !IO),
io.write_string("\t", !IO),
dump_match_details(Match, !IO),
io.nl(!IO).
:- pred dump_match_details(match::in, io::di, io::uo) is det.
dump_match_details(Match, !IO) :-
Conds = list.map((func(DeconSpec) = DeconSpec ^ decon_conds),
Match ^ decon_specs),
( list.takewhile(reuse_as_all_unconditional_reuses, Conds, _, []) ->
CondsString = "A"
;
CondsString = "C"
),
D = list.length(Match ^ decon_specs),
C = list.length(Match ^ con_specs),
string.append_list(["d: ", int_to_string(D), ", c: ",
int_to_string(C),
", Co: ", CondsString], Details),
io.write_string(Details, !IO).
:- pred dump_full_table(match_table::in, io::di, io::uo) is det.
dump_full_table(MatchTable, !IO) :-
( multi_map.is_empty(MatchTable) ->
dump_line("empty match table", !IO)
;
dump_line("full table (start)", !IO),
multi_map.values(MatchTable, Matches),
list.foldl(dump_match("%-----"), Matches, !IO),
dump_line("full table (end)", !IO)
).
:- pred maybe_dump_full_table(bool::in, match_table::in,
io::di, io::uo) is det.
maybe_dump_full_table(no, _M, !IO).
maybe_dump_full_table(yes, M, !IO) :-
dump_full_table(M, !IO).
%-----------------------------------------------------------------------------%
% After determining all local reuses of dead datastructures (a data
% structure becomes dead and is reused in one and the same procedure), we
% determine the 'global reuses': deconstructions that yield dead data
% structures, without imposing any reuse constraints are annotated so that
% these cells can be cached whenever the user specifies that option.
%
% XXX cell caching is not actually implemented, but we use the same
% information to free dead cells that have no reuse opportunity
%
:- pred check_for_cell_caching(bool::in, dead_cell_table::in,
hlds_goal::in, hlds_goal::out) is det.
check_for_cell_caching(VeryVerbose, DeadCellTable0, !Goal) :-
dead_cell_table_remove_conditionals(DeadCellTable0, DeadCellTable),
( dead_cell_table_is_empty(DeadCellTable) ->
trace [io(!IO)] (
maybe_write_string(VeryVerbose,
"% No cells to be cached/freed.\n", !IO)
)
;
trace [io(!IO)] (
maybe_write_string(VeryVerbose,
"% Marking cacheable/freeable cells.\n", !IO)
),
check_for_cell_caching_2(DeadCellTable, !Goal)
).
:- pred check_for_cell_caching_2(dead_cell_table::in,
hlds_goal::in, hlds_goal::out) is det.
check_for_cell_caching_2(DeadCellTable, !Goal) :-
!.Goal = hlds_goal(GoalExpr0, GoalInfo0),
(
GoalExpr0 = unify(A, B, C, Unification0, D),
check_for_cell_caching_in_unification(DeadCellTable,
Unification0, Unification, GoalInfo0, GoalInfo),
GoalExpr = unify(A, B, C, Unification, D)
;
GoalExpr0 = plain_call(_, _, _, _, _, _),
GoalExpr = GoalExpr0,
GoalInfo = GoalInfo0
;
GoalExpr0 = generic_call( _, _, _, _, _),
GoalExpr = GoalExpr0,
GoalInfo = GoalInfo0
;
GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _),
GoalExpr = GoalExpr0,
GoalInfo = GoalInfo0
;
GoalExpr0 = conj(A, Goals0),
list.map(check_for_cell_caching_2(DeadCellTable), Goals0, Goals),
GoalExpr = conj(A, Goals),
GoalInfo = GoalInfo0
;
GoalExpr0 = disj(Goals0),
list.map(check_for_cell_caching_2(DeadCellTable), Goals0, Goals),
GoalExpr = disj(Goals),
GoalInfo = GoalInfo0
;
GoalExpr0 = switch(A, B, Cases0),
list.map(check_for_cell_caching_in_case(DeadCellTable), Cases0, Cases),
GoalExpr = switch(A, B, Cases),
GoalInfo = GoalInfo0
;
GoalExpr0 = negation(_),
GoalExpr = GoalExpr0,
GoalInfo = GoalInfo0
;
GoalExpr0 = scope(A, ScopeGoal0),
% XXX We should special-case the handling of from_ground_term_construct
% scopes. Since they construct ground terms statically, there is no
% uniqueness we can exploit,
check_for_cell_caching_2(DeadCellTable, ScopeGoal0, ScopeGoal),
GoalExpr = scope(A, ScopeGoal),
GoalInfo = GoalInfo0
;
GoalExpr0 = if_then_else(A, CondGoal0, ThenGoal0, ElseGoal0),
check_for_cell_caching_2(DeadCellTable, CondGoal0, CondGoal),
check_for_cell_caching_2(DeadCellTable, ThenGoal0, ThenGoal),
check_for_cell_caching_2(DeadCellTable, ElseGoal0, ElseGoal),
GoalExpr = if_then_else(A, CondGoal, ThenGoal, ElseGoal),
GoalInfo = GoalInfo0
;
GoalExpr0 = shorthand(_),
% These should have been expanded out by now.
unexpected($module, $pred, "shorthand")
),
!:Goal = hlds_goal(GoalExpr, GoalInfo).
:- pred check_for_cell_caching_in_case(dead_cell_table::in,
case::in, case::out) is det.
check_for_cell_caching_in_case(DeadCellTable, !Case) :-
!.Case = case(MainConsId, OtherConsIds, Goal0),
check_for_cell_caching_2(DeadCellTable, Goal0, Goal),
!:Case = case(MainConsId, OtherConsIds, Goal).
:- pred check_for_cell_caching_in_unification(dead_cell_table::in,
unification::in, unification::out,
hlds_goal_info::in, hlds_goal_info::out) is det.
check_for_cell_caching_in_unification(DeadCellTable, !Unification,
!GoalInfo) :-
(
!.Unification = deconstruct(Var, ConsId, Args, ArgModes, CanFail, _),
Condition = dead_cell_table_search(program_point_init(!.GoalInfo),
DeadCellTable),
\+ reuse_condition_is_conditional(Condition)
->
!:Unification = deconstruct(Var, ConsId, Args, ArgModes, CanFail,
can_cgc),
% XXX Why potential_reuse and not simply "reuse" ?
ReuseInfo = potential_reuse(cell_died),
goal_info_set_reuse(ReuseInfo, !GoalInfo)
;
true
).
%-----------------------------------------------------------------------------%
:- end_module transform_hlds.ctgc.structure_reuse.direct.choose_reuse.
%-----------------------------------------------------------------------------%