mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-22 04:43:53 +00:00
deep_profiler/analysis_utils.m:
deep_profiler/autopar_find_best_par.m:
deep_profiler/autopar_reports.m:
deep_profiler/autopar_search_callgraph.m:
deep_profiler/autopar_search_goals.m:
deep_profiler/callgraph.m:
deep_profiler/canonical.m:
deep_profiler/cliques.m:
deep_profiler/coverage.m:
deep_profiler/dump.m:
deep_profiler/mdprof_cgi.m:
deep_profiler/mdprof_create_feedback.m:
deep_profiler/mdprof_dump.m:
deep_profiler/mdprof_procrep.m:
deep_profiler/mdprof_report_feedback.m:
deep_profiler/mdprof_test.m:
deep_profiler/profile.m:
deep_profiler/read_profile.m:
deep_profiler/recursion_patterns.m:
deep_profiler/startup.m:
deep_profiler/var_use_analysis.m:
Replace implicit streams with explicit streams.
In some places, simplify some code, often using constructs such as
string.format that either did not exist or were too expensive to use
when the original code was written.
Consistenly use the spelling StdErr over Stderr.
In mdbprof_dump.m, put filename and reason-for-failing-to-open-that-file
in the right order in an error message.
deep_profiler/DEEP_FLAGS.in:
Turn on --warn-implicit-stream-calls for the entire deep_profiler
directory.
mdbcomp/program_representation.m:
mdbcomp/trace_counts.m:
Replace implicit streams with explicit streams. These are the two mdbcomp
modules that (a) used to use implicit streams, and (2) are used by the
deep profiler.
mdbcomp/Mercury.options:
Turn on --warn-implicit-stream-calls for these two modules.
slice/mcov.m:
slice/mtc_union.m:
Conform to the changes in mdbcomp.
1890 lines
77 KiB
Mathematica
1890 lines
77 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%---------------------------------------------------------------------------%
|
|
% Copyright (C) 2008, 2010-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.
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Authors: pbone, zs.
|
|
%
|
|
% This file implements the coverage propagation algorithm, which attaches
|
|
% coverage information to the component goals of a procedure body.
|
|
%
|
|
% This module can be compiled with the following trace flag to enable
|
|
% debugging: debug_first_var_use. See Mercury.options in this directory.
|
|
%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- module var_use_analysis.
|
|
|
|
:- interface.
|
|
|
|
:- import_module analysis_utils.
|
|
:- import_module mdbcomp.
|
|
:- import_module mdbcomp.goal_path.
|
|
:- import_module mdbcomp.program_representation.
|
|
:- import_module coverage.
|
|
:- import_module measurements.
|
|
:- import_module profile.
|
|
:- import_module report.
|
|
|
|
:- import_module list.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
:- import_module set.
|
|
|
|
% Gives information about the use of a variable measured in average call
|
|
% sequence counts since either the beginning or the end of the procedure.
|
|
%
|
|
:- type var_use_info
|
|
---> var_use_info(
|
|
vui_cost_until_use :: float,
|
|
vui_proc_cost :: float,
|
|
vui_use_type :: var_use_type
|
|
).
|
|
|
|
:- type var_use_options
|
|
---> var_use_options(
|
|
vuo_deep :: deep,
|
|
vuo_intermodule_var_use :: intermodule_var_use,
|
|
vuo_var_use_type :: var_use_type
|
|
).
|
|
|
|
:- type intermodule_var_use
|
|
---> follow_calls_into_module(string)
|
|
; follow_any_call.
|
|
|
|
:- type var_use_type
|
|
---> var_use_production
|
|
% The variable is produced: free >> ground
|
|
; var_use_consumption
|
|
% The variable is consumed: free >> free
|
|
; var_use_other.
|
|
% The variable is used in some other way.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- func average_var_use(list(var_use_info)) = var_use_info.
|
|
|
|
:- pred var_mode_to_var_use_type(var_mode_rep::in, var_use_type::out) is det.
|
|
|
|
:- pred pessimistic_var_use_info(var_use_type::in, float::in,
|
|
var_use_info::out) is det.
|
|
|
|
:- pred pessimistic_var_use_time(var_use_type::in, float::in, float::out)
|
|
is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% get_call_site_dynamic_var_use_info(Deep, CSDPtr, ArgNum, RT, VarUseType,
|
|
% MaybeVarUseInfo):
|
|
%
|
|
% Lookup when the call site CSDPtr will produce ArgNum. RT is the type of
|
|
% recursion in the call site's parent clique and VarUseType is the type of
|
|
% variable use that is expected, VarUseType is used to produce conservative
|
|
% defaults if a callee cannot be analyzed.
|
|
%
|
|
% The first mode avoids a check to ensure that this recursion type provides
|
|
% enough information.
|
|
%
|
|
:- pred get_call_site_dynamic_var_use_info(call_site_dynamic_ptr, int,
|
|
recursion_type, float, var_use_options, maybe_error(var_use_info)).
|
|
:- mode get_call_site_dynamic_var_use_info(in, in,
|
|
in(recursion_type_known_costs), in, in, out) is det.
|
|
:- mode get_call_site_dynamic_var_use_info(in, in,
|
|
in, in, in, out) is det.
|
|
|
|
% get_call_site_dynamic_var_use_info_rec_level(Deep, CliquePtr, CSDPtr,
|
|
% ArgNum, RT, MaybeCurDepth, Cost, CallStack, VarUseType,
|
|
% MaybeVarUseInfo).
|
|
%
|
|
% As above. This alternative should be used if searching starts at a
|
|
% different recursion level or from within a current variable use analysis.
|
|
%
|
|
% CliquePtr is the current clique, MaybeCurDepth is the current recursion
|
|
% depth within the clique if known, CallStack is the set of proc dynamics
|
|
% that have already been explored.
|
|
%
|
|
% Cost is the cost of the call.
|
|
%
|
|
:- pred get_call_site_dynamic_var_use_info_rec_level(clique_ptr,
|
|
call_site_dynamic_ptr, int, recursion_type, maybe(recursion_depth),
|
|
float, set(proc_dynamic_ptr), var_use_options, maybe_error(var_use_info)).
|
|
:- mode get_call_site_dynamic_var_use_info_rec_level(in, in, in,
|
|
in(recursion_type_known_costs), in(maybe_yes(ground)), in, in, in, out)
|
|
is det.
|
|
:- mode get_call_site_dynamic_var_use_info_rec_level(in, in, in,
|
|
in, in, in, in, in, out) is det.
|
|
|
|
:- pred clique_var_use_info(clique_ptr::in, int::in,
|
|
var_use_options::in, maybe_error(var_use_info)::out) is det.
|
|
|
|
:- pred proc_dynamic_var_use_info(clique_ptr::in,
|
|
proc_dynamic_ptr::in, int::in,
|
|
recursion_type::in(recursion_type_known_costs), recursion_depth::in,
|
|
float::in, set(proc_dynamic_ptr)::in, var_use_options::in,
|
|
maybe_error(var_use_info)::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% Find the first use of a variable in an arbitrary goal.
|
|
%
|
|
:- pred var_first_use(clique_ptr::in,
|
|
map(reverse_goal_path, cost_and_callees)::in,
|
|
map(reverse_goal_path, cs_cost_csq)::in,
|
|
containing_goal_map::in, goal_attr_array(coverage_info)::in,
|
|
recursion_type::in(recursion_type_known_costs), recursion_depth::in,
|
|
goal_rep(goal_id)::in, reverse_goal_path::in, float::in, var_rep::in,
|
|
var_use_options::in, var_use_info::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module measurement_units.
|
|
:- import_module program_representation_utils.
|
|
:- import_module recursion_patterns.
|
|
|
|
:- import_module assoc_list.
|
|
:- import_module float.
|
|
:- import_module int.
|
|
:- import_module io.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
:- import_module string.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
average_var_use(Uses) = var_use_info(CostUntilUse, AvgProcCost, Type) :-
|
|
(
|
|
Uses = [],
|
|
unexpected($pred, "cannot average zero items")
|
|
;
|
|
Uses = [var_use_info(_, _, Type) | _],
|
|
foldl2(sum_use_info_costs, Uses, 0.0, SumCost, 0.0, SumProcCost),
|
|
Num = float(length(Uses)),
|
|
CostUntilUse = SumCost / Num,
|
|
AvgProcCost = SumProcCost / Num,
|
|
TestType =
|
|
( pred(var_use_info(_, _, TypeI)::in) is semidet :-
|
|
Type = TypeI
|
|
),
|
|
( if all_true(TestType, Uses) then
|
|
true
|
|
else
|
|
unexpected($pred, "use types do not match")
|
|
)
|
|
).
|
|
|
|
:- pred sum_use_info_costs(var_use_info::in, float::in, float::out,
|
|
float::in, float::out) is det.
|
|
|
|
sum_use_info_costs(var_use_info(Cost, ProcCost, _), !AccCost, !AccProcCost) :-
|
|
!:AccCost = !.AccCost + Cost,
|
|
!:AccProcCost = !.AccProcCost + ProcCost.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
var_mode_to_var_use_type(var_mode_rep(InitialInst, FinalInst), VarUseType) :-
|
|
( if
|
|
InitialInst = ir_ground_rep,
|
|
FinalInst = ir_ground_rep
|
|
then
|
|
VarUseType = var_use_consumption
|
|
else if
|
|
InitialInst = ir_free_rep,
|
|
FinalInst = ir_ground_rep
|
|
then
|
|
VarUseType = var_use_production
|
|
else
|
|
VarUseType = var_use_other
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
pessimistic_var_use_info(VarUseType, ProcCost, VarUseInfo) :-
|
|
pessimistic_var_use_time(VarUseType, ProcCost, CostUntilUse),
|
|
VarUseInfo = var_use_info(CostUntilUse, ProcCost, VarUseType).
|
|
|
|
pessimistic_var_use_time(VarUseType, ProcCost, CostUntilUse) :-
|
|
(
|
|
VarUseType = var_use_consumption,
|
|
CostUntilUse = 0.0
|
|
;
|
|
( VarUseType = var_use_production
|
|
; VarUseType = var_use_other
|
|
),
|
|
CostUntilUse = ProcCost
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
get_call_site_dynamic_var_use_info(CSDPtr, ArgNum, RecursionType, Cost,
|
|
VarUseOptions, MaybeVarUseInfo) :-
|
|
% XXX If the CSD represents a closure then the argument position will be
|
|
% incorrect. This is not currently important, as we assume that the
|
|
% compiler will not push signals and waits into higher order calls.
|
|
% Therefore this should never be called for a higher order call site.
|
|
Deep = VarUseOptions ^ vuo_deep,
|
|
deep_lookup_call_site_dynamics(Deep, CSDPtr, CSD),
|
|
deep_lookup_clique_index(Deep, CSD ^ csd_caller, ParentCliquePtr),
|
|
recursion_type_get_maybe_avg_max_depth(RecursionType, MaybeCurDepth),
|
|
get_call_site_dynamic_var_use_info_rec_level(ParentCliquePtr, CSDPtr,
|
|
ArgNum, RecursionType, MaybeCurDepth, Cost, set.init, VarUseOptions,
|
|
MaybeVarUseInfo).
|
|
|
|
get_call_site_dynamic_var_use_info_rec_level(ParentCliquePtr, CSDPtr, ArgNum,
|
|
RecursionType, MaybeDepth, Cost, CallStack0, VarUseOptions,
|
|
MaybeVarUseInfo) :-
|
|
Deep = VarUseOptions ^ vuo_deep,
|
|
deep_lookup_clique_maybe_child(Deep, CSDPtr, MaybeCalleeCliquePtr),
|
|
(
|
|
MaybeCalleeCliquePtr = yes(CalleeCliquePtr),
|
|
% This is a non-recursive call site.
|
|
|
|
% We don't check if this call crossed a module boundary here,
|
|
% that is done in clique_var_use_info.
|
|
clique_var_use_info(CalleeCliquePtr, ArgNum, VarUseOptions,
|
|
MaybeVarUseInfo)
|
|
;
|
|
MaybeCalleeCliquePtr = no,
|
|
% This is a recursive call site.
|
|
|
|
deep_lookup_call_site_dynamics(Deep, CSDPtr, CSD),
|
|
CalleePDPtr = CSD ^ csd_callee,
|
|
( if
|
|
not intermodule_var_use_should_follow_csd(VarUseOptions, CSDPtr)
|
|
then
|
|
% Don't follow this call across module boundaries.
|
|
pessimistic_var_use_info(VarUseOptions ^ vuo_var_use_type, Cost,
|
|
VarUseInfo),
|
|
MaybeVarUseInfo = ok(VarUseInfo)
|
|
else if set.member(CalleePDPtr, CallStack0) then
|
|
% Return a first use time of 1.0. This is used by the formula
|
|
% below.
|
|
MaybeVarUseInfo = ok(var_use_info(1.0, Cost,
|
|
VarUseOptions ^ vuo_var_use_type))
|
|
else
|
|
(
|
|
MaybeDepth = yes(Depth0),
|
|
recursion_depth_descend(Depth0, Depth),
|
|
(
|
|
( RecursionType = rt_not_recursive
|
|
; RecursionType = rt_single(_, _, _, _, _)
|
|
),
|
|
% Don't follow this recursive call as normal, doing so
|
|
% would make this analysis take too long. We can compute
|
|
% the cost of variable use time by the following formula:
|
|
set.insert(CalleePDPtr, CallStack0, CallStack),
|
|
proc_dynamic_recursive_var_use_info(ParentCliquePtr,
|
|
CalleePDPtr, ArgNum, RecursionType, Depth, Cost,
|
|
CallStack, VarUseOptions, MaybeVarUseInfo0),
|
|
(
|
|
MaybeVarUseInfo0 = ok(var_use_info(UseTime0, ProcTime0,
|
|
UseType)),
|
|
UseTime = UseTime0 + 1.0,
|
|
ProcTime = ProcTime0 + 1.0,
|
|
% Add a call sequence count for the cost of this call.
|
|
MaybeVarUseInfo = ok(var_use_info(UseTime, ProcTime,
|
|
UseType))
|
|
;
|
|
MaybeVarUseInfo0 = error(_),
|
|
MaybeVarUseInfo = MaybeVarUseInfo0
|
|
)
|
|
;
|
|
( RecursionType = rt_divide_and_conquer(_, _)
|
|
; RecursionType = rt_mutual_recursion(_)
|
|
; RecursionType = rt_other(_)
|
|
; RecursionType = rt_errors(_)
|
|
),
|
|
MaybeVarUseInfo = error(
|
|
"Cannot compute var use on a recursive call site "
|
|
++ "for an unknown recursion type")
|
|
)
|
|
;
|
|
MaybeDepth = no,
|
|
MaybeVarUseInfo = error(
|
|
"Cannot compute var use on a recursive call site "
|
|
++ "for an unknown recursion depth")
|
|
)
|
|
)
|
|
).
|
|
|
|
clique_var_use_info(CliquePtr, ArgNum, VarUseOptions, MaybeVarUseInfo) :-
|
|
Deep = VarUseOptions ^ vuo_deep,
|
|
deep_lookup_clique_parents(Deep, CliquePtr, CSDPtr),
|
|
deep_lookup_csd_desc(Deep, CSDPtr, CSDDesc),
|
|
Cost = float(inherit_callseqs(CSDDesc)),
|
|
find_clique_first_and_other_procs(Deep, CliquePtr, MaybeFirstProc,
|
|
_OtherProcs),
|
|
(
|
|
MaybeFirstProc = yes(FirstPDPtr),
|
|
VarUseType = VarUseOptions ^ vuo_var_use_type,
|
|
( if intermodule_var_use_should_follow_csd(VarUseOptions, CSDPtr) then
|
|
create_clique_recursion_costs_report(Deep, CliquePtr,
|
|
MaybeRecursionReport),
|
|
(
|
|
MaybeRecursionReport = ok(RecursionReport),
|
|
RecursionType = RecursionReport ^ crr_recursion_type
|
|
;
|
|
MaybeRecursionReport = error(Error),
|
|
RecursionType = rt_errors([Error])
|
|
),
|
|
(
|
|
(
|
|
RecursionType = rt_not_recursive,
|
|
recursion_type_get_maybe_avg_max_depth(RecursionType,
|
|
yes(Depth)),
|
|
% XXX This shouldn't need the recursion information
|
|
% anymore.
|
|
proc_dynamic_var_use_info(CliquePtr, FirstPDPtr,
|
|
ArgNum, RecursionType, Depth, Cost, set.init,
|
|
VarUseOptions, MaybeVarUseInfo0)
|
|
;
|
|
RecursionType = rt_single(_, _, _, _, _),
|
|
recursion_type_get_maybe_avg_max_depth(RecursionType,
|
|
yes(Depth)),
|
|
% Add this PD to the proc dynamic pointer, we don't use a
|
|
% second recursion in this case.
|
|
proc_dynamic_recursive_var_use_info(CliquePtr, FirstPDPtr,
|
|
ArgNum, RecursionType, Depth, Cost,
|
|
set.make_singleton_set(FirstPDPtr),
|
|
VarUseOptions, MaybeVarUseInfo0)
|
|
),
|
|
(
|
|
MaybeVarUseInfo0 = ok(var_use_info(UseTime0, ProcTime0,
|
|
UseType)),
|
|
UseTime = UseTime0 + 1.0,
|
|
ProcTime = ProcTime0 + 1.0,
|
|
% Add a call sequence count for the cost of this call.
|
|
MaybeVarUseInfo = ok(var_use_info(UseTime, ProcTime,
|
|
UseType))
|
|
;
|
|
MaybeVarUseInfo0 = error(_),
|
|
MaybeVarUseInfo = MaybeVarUseInfo0
|
|
)
|
|
;
|
|
( RecursionType = rt_divide_and_conquer(_, _)
|
|
; RecursionType = rt_mutual_recursion(_)
|
|
; RecursionType = rt_other(_)
|
|
; RecursionType = rt_errors(_)
|
|
),
|
|
pessimistic_var_use_info(VarUseType, Cost, VarUseInfo),
|
|
MaybeVarUseInfo = ok(VarUseInfo)
|
|
)
|
|
else
|
|
pessimistic_var_use_info(VarUseType, Cost, VarUseInfo),
|
|
MaybeVarUseInfo = ok(VarUseInfo)
|
|
)
|
|
;
|
|
MaybeFirstProc = no,
|
|
unexpected($pred, "clique has no first procedure")
|
|
).
|
|
|
|
proc_dynamic_var_use_info(CliquePtr, PDPtr, ArgNum, RecursionType,
|
|
Depth, ProcCost, CallStack, VarUseOptions, MaybeVarUseInfo) :-
|
|
prepare_for_proc_var_first_use(CliquePtr, PDPtr, ArgNum, RecursionType,
|
|
Depth, VarUseOptions, Info),
|
|
(
|
|
Info = proc_var_first_use_prepared_info(Goal, _LastGoalId,
|
|
ContainingGoalMap, CoverageArray, CallSiteCostMap,
|
|
RecursiveCallSiteCostMap, Var),
|
|
goal_var_first_use_wrapper(CliquePtr, CallStack,
|
|
ContainingGoalMap, CoverageArray, CallSiteCostMap,
|
|
RecursiveCallSiteCostMap, RecursionType, Depth, Goal, ProcCost,
|
|
Var, VarUseOptions, VarUseInfo),
|
|
MaybeVarUseInfo = ok(VarUseInfo)
|
|
;
|
|
Info = error(Error),
|
|
MaybeVarUseInfo = error(Error)
|
|
).
|
|
|
|
% Like proc_dynamic_recursive_var_use_info except that it handles recursive
|
|
% code by induction.
|
|
%
|
|
% UseTime = BaseUseTime + (RecUseTime + 1.0) * Depth
|
|
%
|
|
% Where BaseUseTime is the use time in the base case execution and
|
|
% RecUseTime is the use time before the recursive call. This works for any
|
|
% self-recursion pattern.
|
|
%
|
|
:- pred proc_dynamic_recursive_var_use_info(clique_ptr::in,
|
|
proc_dynamic_ptr::in, int::in,
|
|
recursion_type::in(recursion_type_known_costs), recursion_depth::in,
|
|
float::in, set(proc_dynamic_ptr)::in, var_use_options::in,
|
|
maybe_error(var_use_info)::out) is det.
|
|
|
|
proc_dynamic_recursive_var_use_info(CliquePtr, PDPtr, ArgNum,
|
|
RecursionType, Depth, _Cost, CallStack, VarUseOptions,
|
|
MaybeVarUseInfo) :-
|
|
prepare_for_proc_var_first_use(CliquePtr, PDPtr, ArgNum, RecursionType,
|
|
Depth, VarUseOptions, Info),
|
|
(
|
|
Info = proc_var_first_use_prepared_info(Goal, LastGoalId,
|
|
ContainingGoalMap, CoverageArray, CallSiteCostMap,
|
|
RecursiveCallSiteCostMap, Var),
|
|
VarUseInfo = var_first_use_static_info(CliquePtr,
|
|
CallSiteCostMap, RecursiveCallSiteCostMap, ContainingGoalMap,
|
|
CoverageArray, Var, VarUseOptions, CallStack, RecursionType, Depth,
|
|
no_recursion_info),
|
|
|
|
RecProbsArray0 = create_goal_id_array(LastGoalId, impossible),
|
|
build_recursive_call_sites_list(RecursiveCallSiteCostMap,
|
|
RecursiveCalls),
|
|
goal_rec_prob(Goal, RecursiveCalls, VarUseInfo, RecProb,
|
|
RecProbsArray0, RecProbsArray),
|
|
|
|
VarFirstUseInfoRecCase = VarUseInfo ^ fui_rec_info :=
|
|
first_use_rec_info(RecProbsArray, recursive_case),
|
|
rec_goal_var_first_use(Goal, RecursiveCalls, VarFirstUseInfoRecCase,
|
|
RecFoundFirstUse, 0.0, RecTotalTime),
|
|
VarFirstUseInfoBaseCase = VarUseInfo ^ fui_rec_info :=
|
|
first_use_rec_info(RecProbsArray, base_case),
|
|
rec_goal_var_first_use(Goal, RecursiveCalls, VarFirstUseInfoBaseCase,
|
|
BaseFoundFirstUse, 0.0, BaseTotalTime),
|
|
|
|
VarUseType = VarUseOptions ^ vuo_var_use_type,
|
|
(
|
|
RecFoundFirstUse = found_first_use(VarUseTimeRec)
|
|
;
|
|
RecFoundFirstUse = have_not_found_first_use,
|
|
(
|
|
VarUseType = var_use_consumption,
|
|
VarUseTimeRec = 0.0
|
|
;
|
|
( VarUseType = var_use_production
|
|
; VarUseType = var_use_other
|
|
),
|
|
VarUseTimeRec = RecTotalTime
|
|
)
|
|
),
|
|
(
|
|
BaseFoundFirstUse = found_first_use(VarUseTimeBase)
|
|
;
|
|
BaseFoundFirstUse = have_not_found_first_use,
|
|
(
|
|
VarUseType = var_use_consumption,
|
|
VarUseTimeBase = 0.0
|
|
;
|
|
( VarUseType = var_use_production
|
|
; VarUseType = var_use_other
|
|
),
|
|
VarUseTimeBase = BaseTotalTime
|
|
)
|
|
),
|
|
|
|
DepthF = recursion_depth_to_float(Depth),
|
|
VarUseTime = VarUseTimeBase +
|
|
DepthF * VarUseTimeRec,
|
|
Cost = RecTotalTime * probability_to_float(RecProb) +
|
|
BaseTotalTime * probability_to_float(not_probability(RecProb)),
|
|
MaybeVarUseInfo = ok(var_use_info(VarUseTime, Cost,
|
|
VarUseOptions ^ vuo_var_use_type)),
|
|
trace [compile_time(flag("debug_first_var_use")), io(!IO)] (
|
|
io.output_stream(OutputStream, !IO),
|
|
io.write_string(OutputStream,
|
|
"\nUseTime = BaseUseTime + RecUseTime * Depth\n", !IO),
|
|
io.format(OutputStream, "%f = %f + %f * %f\n",
|
|
[f(VarUseTime), f(VarUseTimeBase),
|
|
f(VarUseTimeRec), f(DepthF)], !IO)
|
|
)
|
|
;
|
|
Info = error(Error),
|
|
MaybeVarUseInfo = error(Error)
|
|
).
|
|
|
|
:- type proc_var_first_use_prepared_info
|
|
---> error(string)
|
|
; proc_var_first_use_prepared_info(
|
|
goal_rep(goal_id),
|
|
goal_id,
|
|
containing_goal_map,
|
|
goal_attr_array(coverage_info),
|
|
map(reverse_goal_path, cost_and_callees),
|
|
map(reverse_goal_path, cs_cost_csq),
|
|
var_rep
|
|
).
|
|
|
|
:- pred prepare_for_proc_var_first_use(clique_ptr::in, proc_dynamic_ptr::in,
|
|
int::in, recursion_type::in(recursion_type_known_costs),
|
|
recursion_depth::in, var_use_options::in,
|
|
proc_var_first_use_prepared_info::out) is det.
|
|
|
|
prepare_for_proc_var_first_use(CliquePtr, PDPtr, ArgNum, RecursionType, Depth,
|
|
VarUseOptions, Info) :-
|
|
Deep = VarUseOptions ^ vuo_deep,
|
|
deep_lookup_proc_dynamics(Deep, PDPtr, PD),
|
|
PSPtr = PD ^ pd_proc_static,
|
|
deep_get_maybe_procrep(Deep, PSPtr, MaybeProcrep),
|
|
(
|
|
MaybeProcrep = ok(ProcRep),
|
|
ProcDefn = ProcRep ^ pr_defn,
|
|
HeadVars = ProcDefn ^ pdr_head_vars,
|
|
( if index0(HeadVars, ArgNum, head_var_rep(Var, Mode)) then
|
|
var_mode_to_var_use_type(Mode, ComputedUse),
|
|
VarUseType = VarUseOptions ^ vuo_var_use_type,
|
|
( if VarUseType = ComputedUse then
|
|
true
|
|
else
|
|
PDPtr = proc_dynamic_ptr(PDNum),
|
|
string.format(
|
|
"Var uses do not match, passed: %s calculated from "
|
|
++ "procrep: %s, Arg %d in proc dynamic %d",
|
|
[s(string(VarUseType)), s(string(ComputedUse)),
|
|
i(ArgNum), i(PDNum)], Msg),
|
|
unexpected($pred, Msg)
|
|
),
|
|
|
|
% Prepare callsite information.
|
|
proc_dynamic_paired_call_site_slots(Deep, PDPtr, Slots),
|
|
list.foldl(build_dynamic_call_site_cost_and_callee_map(Deep),
|
|
Slots, map.init, CallSiteCostMap),
|
|
|
|
build_recursive_call_site_cost_map(Deep, CliquePtr, PDPtr,
|
|
RecursionType, yes(Depth), MaybeRecursiveCallSiteCostMap),
|
|
MaybeRecursiveCallSiteCostMap = ok(RecursiveCallSiteCostMap),
|
|
|
|
% Build procrep
|
|
Goal0 = ProcDefn ^ pdr_goal,
|
|
label_goals(LastGoalId, ContainingGoalMap, Goal0, Goal),
|
|
|
|
% Build coverage annotation.
|
|
deep_lookup_proc_statics(Deep, PSPtr, PS),
|
|
ProcLabel = PS ^ ps_id,
|
|
coverage_point_arrays_to_list(PS ^ ps_coverage_point_infos,
|
|
CoveragePointsArray, CoveragePoints),
|
|
MaybeCoveragePointsArray = PD ^ pd_maybe_coverage_points,
|
|
(
|
|
MaybeCoveragePointsArray = yes(CoveragePointsArray)
|
|
;
|
|
MaybeCoveragePointsArray = no,
|
|
unexpected($pred, "Couldn't get coverage info")
|
|
),
|
|
list.foldl2(add_coverage_point_to_map, CoveragePoints,
|
|
map.init, SolnsCoveragePointMap,
|
|
map.init, BranchCoveragePointMap),
|
|
deep_lookup_pd_own(Deep, PDPtr, Own),
|
|
goal_annotate_with_coverage(ProcLabel, Goal, Own, CallSiteCostMap,
|
|
SolnsCoveragePointMap, BranchCoveragePointMap,
|
|
ContainingGoalMap, LastGoalId, CoverageArray),
|
|
|
|
Info = proc_var_first_use_prepared_info(Goal, LastGoalId,
|
|
ContainingGoalMap, CoverageArray, CallSiteCostMap,
|
|
RecursiveCallSiteCostMap, Var)
|
|
else
|
|
PDPtr = proc_dynamic_ptr(PDNum),
|
|
string.format(
|
|
"proc_dynamic_var_use_info: ArgNum %d out of range for PD %d",
|
|
[i(ArgNum), i(PDNum)], Msg),
|
|
Info = error(Msg)
|
|
)
|
|
;
|
|
MaybeProcrep = error(Error),
|
|
Info = error(Error)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% The actual first use analysis code.
|
|
%
|
|
% Any changes here should be reflected in the recursion and base case specific
|
|
% versions of this code below.
|
|
%
|
|
% TODO: This code is currently out of date with the code below, corrections
|
|
% need to be made to this code, see XXX markers below.
|
|
%
|
|
|
|
% This type represents whether the first use of a variable has been found
|
|
% or not. If it has then the call sequence counts since it was found is
|
|
% stored in this type also.
|
|
%
|
|
:- type found_first_use
|
|
---> have_not_found_first_use
|
|
; found_first_use(
|
|
cost_before_use :: float
|
|
).
|
|
|
|
:- inst found_first_use_found for found_first_use/0
|
|
---> found_first_use(ground).
|
|
|
|
:- type var_first_use_static_info
|
|
---> var_first_use_static_info(
|
|
fui_clique :: clique_ptr,
|
|
fui_call_site_map :: map(reverse_goal_path,
|
|
cost_and_callees),
|
|
fui_rec_call_site_map :: map(reverse_goal_path,
|
|
cs_cost_csq),
|
|
fui_containing_goal_map :: containing_goal_map,
|
|
fui_coverage_array :: goal_attr_array(coverage_info),
|
|
fui_var :: var_rep,
|
|
fui_var_use_opts :: var_use_options,
|
|
|
|
% A set of call sites whose analysis has started but not yet
|
|
% completed. We keep this set to prevent infinite recursion
|
|
% in the analysis itself.
|
|
fui_call_stack :: set(proc_dynamic_ptr),
|
|
|
|
fui_recursion_type :: recursion_type,
|
|
fui_cur_depth :: recursion_depth,
|
|
fui_rec_info :: first_use_rec_info
|
|
).
|
|
|
|
:- inst var_first_use_static_info for var_first_use_static_info/0
|
|
---> var_first_use_static_info(
|
|
ground, ground, ground, ground, ground, ground, ground, ground,
|
|
recursion_type_known_costs,
|
|
ground, ground
|
|
).
|
|
|
|
:- inst var_first_use_static_info_rec for var_first_use_static_info/0
|
|
---> var_first_use_static_info(
|
|
ground, ground, ground, ground, ground, ground, ground, ground,
|
|
recursion_type_known_costs,
|
|
ground,
|
|
first_use_rec_info
|
|
).
|
|
|
|
:- type first_use_rec_info
|
|
---> first_use_rec_info(
|
|
furi_rec_prob_array :: goal_attr_array(probability),
|
|
furi_rec_case :: recursive_case
|
|
)
|
|
; no_recursion_info.
|
|
|
|
:- inst first_use_rec_info for first_use_rec_info/0
|
|
---> first_use_rec_info(ground, ground).
|
|
|
|
:- inst no_recursion_info for first_use_rec_info/0
|
|
---> no_recursion_info.
|
|
|
|
:- type recursive_case
|
|
---> recursive_case
|
|
; base_case.
|
|
|
|
% Find the first use of a variable in a goal.
|
|
% Procedure calls can be resolved via the call site which we'll need to
|
|
% lookup anyway to find cost information, This will callback to the deep
|
|
% profiler as it crosses procedure boundaries.
|
|
%
|
|
% This does not follow higher order or method calls. It may be possible to
|
|
% follow call the calls seen during profiling and aggregate their variable
|
|
% use information based on how often they are called from that call site.
|
|
%
|
|
:- pred goal_var_first_use(reverse_goal_path::in, goal_rep(goal_id)::in,
|
|
var_first_use_static_info::in(var_first_use_static_info), float::in,
|
|
float::out, found_first_use::out) is det.
|
|
|
|
goal_var_first_use(RevGoalPath, Goal, StaticInfo, !CostSoFar, FoundFirstUse) :-
|
|
Goal = goal_rep(GoalExpr, Detism, _),
|
|
CoverageArray = StaticInfo ^ fui_coverage_array,
|
|
Coverage = get_goal_attribute_det(CoverageArray, Goal ^ goal_annotation),
|
|
( if
|
|
% Do not bother exploring this goal if it is never entered. Or never
|
|
% finishes and we're looking for a production.
|
|
(
|
|
get_coverage_before(Coverage, 0)
|
|
;
|
|
VarUseType = StaticInfo ^ fui_var_use_opts ^ vuo_var_use_type,
|
|
VarUseType = var_use_production,
|
|
(
|
|
Detism = erroneous_rep
|
|
;
|
|
Detism = failure_rep
|
|
;
|
|
get_coverage_after(Coverage, 0)
|
|
)
|
|
)
|
|
then
|
|
FoundFirstUse = have_not_found_first_use
|
|
else
|
|
(
|
|
GoalExpr = conj_rep(Conjuncts),
|
|
conj_var_first_use(RevGoalPath, 1, Conjuncts, StaticInfo,
|
|
!CostSoFar, FoundFirstUse)
|
|
;
|
|
GoalExpr = disj_rep(Disjuncts),
|
|
disj_var_first_use(RevGoalPath, Disjuncts, Detism, StaticInfo,
|
|
!CostSoFar, FoundFirstUse)
|
|
;
|
|
GoalExpr = switch_rep(SwitchedOnVar, _CanFail, Cases),
|
|
switch_var_first_use(RevGoalPath, SwitchedOnVar, Cases,
|
|
StaticInfo, !CostSoFar, FoundFirstUse)
|
|
;
|
|
GoalExpr = ite_rep(Cond, Then, Else),
|
|
ite_var_first_use(RevGoalPath, Cond, Then, Else, StaticInfo,
|
|
!CostSoFar, FoundFirstUse)
|
|
;
|
|
(
|
|
GoalExpr = negation_rep(SubGoal),
|
|
GoalPathStep = step_neg
|
|
;
|
|
GoalExpr = scope_rep(SubGoal, ScopeIsCut),
|
|
GoalPathStep = step_scope(ScopeIsCut)
|
|
),
|
|
RevSubGoalPath = rgp_cons(RevGoalPath, GoalPathStep),
|
|
goal_var_first_use(RevSubGoalPath, SubGoal, StaticInfo,
|
|
!CostSoFar, FoundFirstUse)
|
|
;
|
|
GoalExpr = atomic_goal_rep(_, _, BoundVars, AtomicGoal),
|
|
(
|
|
( AtomicGoal = plain_call_rep(_, _, _)
|
|
; AtomicGoal = higher_order_call_rep(_, _)
|
|
; AtomicGoal = method_call_rep(_, _, _)
|
|
),
|
|
call_var_first_use(AtomicGoal, BoundVars, RevGoalPath,
|
|
StaticInfo, FoundFirstUse, !CostSoFar)
|
|
;
|
|
( AtomicGoal = unify_construct_rep(_, _, _)
|
|
; AtomicGoal = unify_deconstruct_rep(_, _, _)
|
|
; AtomicGoal = partial_construct_rep(_, _, _)
|
|
; AtomicGoal = partial_deconstruct_rep(_, _, _)
|
|
; AtomicGoal = unify_assign_rep(_, _)
|
|
; AtomicGoal = cast_rep(_, _)
|
|
; AtomicGoal = unify_simple_test_rep(_, _)
|
|
; AtomicGoal = pragma_foreign_code_rep(_)
|
|
; AtomicGoal = event_call_rep(_, _)
|
|
; AtomicGoal = builtin_call_rep(_, _, _)
|
|
),
|
|
% trivial goals have a zero cost, so !CostSoFar is not updated.
|
|
atomic_trivial_var_first_use(AtomicGoal, BoundVars,
|
|
!.CostSoFar, StaticInfo, FoundFirstUse)
|
|
)
|
|
)
|
|
),
|
|
trace [compile_time(flag("debug_first_var_use")), io(!IO)] (
|
|
io.output_stream(OutputStream, !IO),
|
|
io.format(OutputStream, "Trace: goal_var_first_use: %s\n",
|
|
[s(rev_goal_path_to_string(RevGoalPath))], !IO)
|
|
).
|
|
|
|
:- inst atomic_goal_rep_call
|
|
for mdbcomp.program_representation.atomic_goal_rep/0
|
|
---> plain_call_rep(ground, ground, ground)
|
|
; higher_order_call_rep(ground, ground)
|
|
; method_call_rep(ground, ground, ground).
|
|
|
|
:- pred call_var_first_use(atomic_goal_rep::in(atomic_goal_rep_call),
|
|
list(var_rep)::in, reverse_goal_path::in,
|
|
var_first_use_static_info::in(var_first_use_static_info),
|
|
found_first_use::out, float::in, float::out) is det.
|
|
|
|
call_var_first_use(AtomicGoal, BoundVars, RevGoalPath, StaticInfo,
|
|
FoundFirstUse, !CostSoFar) :-
|
|
CostBefore = !.CostSoFar,
|
|
StaticInfo = var_first_use_static_info(CliquePtr, CostMap,
|
|
RecCostMap, _ContainingGoalMap, _CoverageArray, Var, VarUseOptions,
|
|
_CallStack, _RecursionType, _MaybeCurDepth, _RecInfo),
|
|
VarUseType = VarUseOptions ^ vuo_var_use_type,
|
|
map.lookup(CostMap, RevGoalPath, CostAndCallees),
|
|
|
|
% Get the cost of the call.
|
|
( if cost_and_callees_is_recursive(CliquePtr, CostAndCallees) then
|
|
map.lookup(RecCostMap, RevGoalPath, Cost0)
|
|
else
|
|
Cost0 = CostAndCallees ^ cac_cost
|
|
),
|
|
( if cs_cost_get_calls(Cost0) = 0.0 then
|
|
Cost = 0.0
|
|
else
|
|
Cost = cs_cost_get_percall(Cost0)
|
|
),
|
|
!:CostSoFar = !.CostSoFar + Cost,
|
|
|
|
% Determine if the variable we are searching for uses of is involved with
|
|
% this call.
|
|
(
|
|
AtomicGoal = plain_call_rep(_, _, Args),
|
|
Vars = Args
|
|
;
|
|
( AtomicGoal = higher_order_call_rep(HOVar, Args)
|
|
; AtomicGoal = method_call_rep(HOVar, _, Args)
|
|
),
|
|
Vars = [HOVar | Args]
|
|
),
|
|
( if list.member(Var, Vars) then
|
|
(
|
|
( AtomicGoal = higher_order_call_rep(_, _)
|
|
; AtomicGoal = method_call_rep(_, _, _)
|
|
),
|
|
% Conservative assumption, but we *could* do better, since we
|
|
% can find out what procedures are actually called at this call
|
|
% site.
|
|
FirstTime = 0.0
|
|
;
|
|
AtomicGoal = plain_call_rep(_, _, _),
|
|
call_args_first_use(Args, Cost, StaticInfo, CostAndCallees, Times),
|
|
(
|
|
Times = [],
|
|
unexpected($pred, "no solutions for variable first use time")
|
|
;
|
|
Times = [FirstTime | LaterTimes],
|
|
( if
|
|
VarUseType = var_use_production,
|
|
LaterTimes = [_ | _]
|
|
then
|
|
unexpected($pred,
|
|
"multiple solutions for variable production time")
|
|
else
|
|
true
|
|
)
|
|
)
|
|
),
|
|
|
|
FoundFirstUse = found_first_use(CostBefore + FirstTime),
|
|
|
|
% Assertions.
|
|
( if
|
|
VarUseType = var_use_production,
|
|
not list.member(Var, BoundVars)
|
|
then
|
|
unexpected($pred,
|
|
"a bound var must be produced by a call if it is an argument.")
|
|
else
|
|
true
|
|
),
|
|
( if
|
|
VarUseType = var_use_consumption,
|
|
list.member(Var, BoundVars)
|
|
then
|
|
unexpected($pred,
|
|
"a consumed var must not be mentioned in BoundVars")
|
|
else
|
|
true
|
|
),
|
|
( if
|
|
VarUseType = var_use_production,
|
|
( AtomicGoal = higher_order_call_rep(Var, _)
|
|
; AtomicGoal = method_call_rep(Var, _, _)
|
|
)
|
|
then
|
|
unexpected($pred, "a HO call site cannot produce its own HO value")
|
|
else
|
|
true
|
|
)
|
|
else
|
|
FoundFirstUse = have_not_found_first_use
|
|
).
|
|
|
|
:- pred consume_ho_arg(atomic_goal_rep::in(atomic_goal_rep_call),
|
|
var_rep::in, float::out) is semidet.
|
|
|
|
consume_ho_arg(higher_order_call_rep(Var, _), Var, 0.0).
|
|
consume_ho_arg(method_call_rep(Var, _, _), Var, 0.0).
|
|
|
|
:- pred call_args_first_use(list(var_rep)::in, float::in,
|
|
var_first_use_static_info::in(var_first_use_static_info),
|
|
cost_and_callees::in, list(float)::out) is det.
|
|
|
|
call_args_first_use(Args, Cost, StaticInfo, CostAndCallees, Times) :-
|
|
StaticInfo = var_first_use_static_info(CliquePtr, _CostMap,
|
|
_RecCostMap, _ContainingGoalMap, _CoverageArray, Var, VarUseOptions,
|
|
CallStack, RecursionType, CurDepth, _RecInfo),
|
|
VarUseType = VarUseOptions ^ vuo_var_use_type,
|
|
HigherOrder = CostAndCallees ^ cac_call_site_is_ho,
|
|
(
|
|
HigherOrder = first_order_call,
|
|
Callees = CostAndCallees ^ cac_callees,
|
|
list.member_indexes0(Var, Args, ArgNums),
|
|
( if set.is_empty(Callees) then
|
|
% There are no callees; this code is never called.
|
|
pessimistic_var_use_time(VarUseType, Cost, Time),
|
|
Times = [Time]
|
|
else if set.is_singleton(Callees, SingletonCallee) then
|
|
CSDPtr = SingletonCallee ^ c_csd,
|
|
list.map(
|
|
get_call_site_dynamic_var_use_time(CliquePtr, CSDPtr,
|
|
RecursionType, CurDepth, Cost, CallStack, VarUseOptions),
|
|
ArgNums, Times0),
|
|
list.sort_and_remove_dups(Times0, Times)
|
|
else
|
|
unexpected($pred, "wrong number of callees for normal call site")
|
|
)
|
|
;
|
|
HigherOrder = higher_order_call,
|
|
% The compiler cannot push signals or waits into higher order calls.
|
|
% Therefore we assume a pessimistic default here.
|
|
pessimistic_var_use_time(VarUseType, Cost, Time),
|
|
Times = [Time]
|
|
).
|
|
|
|
:- pred get_call_site_dynamic_var_use_time(clique_ptr::in,
|
|
call_site_dynamic_ptr::in, recursion_type::in,
|
|
recursion_depth::in, float::in, set(proc_dynamic_ptr)::in,
|
|
var_use_options::in, int::in, float::out) is det.
|
|
|
|
get_call_site_dynamic_var_use_time(CliquePtr, CSDPtr,
|
|
RecursionType, CurDepth, Cost, CallStack,
|
|
VarUseOptions, ArgNum, Time) :-
|
|
get_call_site_dynamic_var_use_info_rec_level(CliquePtr, CSDPtr,
|
|
ArgNum, RecursionType, yes(CurDepth), Cost, CallStack,
|
|
VarUseOptions, MaybeVarUseInfo),
|
|
(
|
|
MaybeVarUseInfo = ok(VarUseInfo),
|
|
VarUseInfo = var_use_info(Time, _, _)
|
|
;
|
|
MaybeVarUseInfo = error(_),
|
|
VarUseType = VarUseOptions ^ vuo_var_use_type,
|
|
pessimistic_var_use_time(VarUseType, Cost, Time)
|
|
).
|
|
|
|
:- inst atomic_trivial_goal_rep
|
|
for mdbcomp.program_representation.atomic_goal_rep/0
|
|
---> unify_construct_rep(ground, ground, ground)
|
|
; unify_deconstruct_rep(ground, ground, ground)
|
|
; partial_construct_rep(ground, ground, ground)
|
|
; partial_deconstruct_rep(ground, ground, ground)
|
|
; unify_assign_rep(ground, ground)
|
|
; cast_rep(ground, ground)
|
|
; unify_simple_test_rep(ground, ground)
|
|
; pragma_foreign_code_rep(ground)
|
|
; event_call_rep(ground, ground)
|
|
; builtin_call_rep(ground, ground, ground).
|
|
|
|
:- pred atomic_trivial_var_first_use(
|
|
atomic_goal_rep::in(atomic_trivial_goal_rep), list(var_rep)::in, float::in,
|
|
var_first_use_static_info::in, found_first_use::out) is det.
|
|
|
|
atomic_trivial_var_first_use(AtomicGoal, BoundVars, CostSoFar, StaticInfo,
|
|
FoundFirstUse) :-
|
|
Var = StaticInfo ^ fui_var,
|
|
VarUseType = StaticInfo ^ fui_var_use_opts ^ vuo_var_use_type,
|
|
atomic_goal_get_vars(AtomicGoal, Vars),
|
|
( if
|
|
set.member(Var, Vars),
|
|
(
|
|
VarUseType = var_use_consumption
|
|
;
|
|
VarUseType = var_use_production,
|
|
list.member(Var, BoundVars)
|
|
;
|
|
VarUseType = var_use_other
|
|
)
|
|
then
|
|
FoundFirstUse = found_first_use(CostSoFar)
|
|
else
|
|
FoundFirstUse = have_not_found_first_use
|
|
).
|
|
|
|
% Find the first use of a variable within a conjunction. Note that when
|
|
% looking for a production of the variable we search backward and add the
|
|
% time from the end of the goal. Similarly with other goal types that have
|
|
% an execution order, namely disjunctions and if-then-elses.
|
|
%
|
|
:- pred conj_var_first_use(reverse_goal_path::in, int::in,
|
|
list(goal_rep(goal_id))::in,
|
|
var_first_use_static_info::in(var_first_use_static_info),
|
|
float::in, float::out, found_first_use::out) is det.
|
|
|
|
conj_var_first_use(_, _, [], _, !Cost, have_not_found_first_use).
|
|
conj_var_first_use(RevGoalPath, ConjNum, [Conj | Conjs], StaticInfo,
|
|
!CostSoFar, FoundFirstUse) :-
|
|
goal_var_first_use(rgp_cons(RevGoalPath, step_conj(ConjNum)), Conj,
|
|
StaticInfo, !CostSoFar, HeadFoundFirstUse),
|
|
(
|
|
% If a variable is bound more than once, then we want to use
|
|
% the last time it is bound, since all of the earlier bindings
|
|
% must have only partially instantiated it. Instmaps can be used
|
|
% to track this. This is relevant when searching for the producer
|
|
% of a variable.
|
|
% XXX For now, we assume that no variable is bound more than once.
|
|
HeadFoundFirstUse = found_first_use(_),
|
|
FoundFirstUse = HeadFoundFirstUse
|
|
;
|
|
HeadFoundFirstUse = have_not_found_first_use,
|
|
conj_var_first_use(RevGoalPath, ConjNum + 1, Conjs, StaticInfo,
|
|
!CostSoFar, TailFoundFirstUse),
|
|
FoundFirstUse = TailFoundFirstUse
|
|
).
|
|
|
|
:- pred disj_var_first_use(reverse_goal_path::in,
|
|
list(goal_rep(goal_id))::in, detism_rep::in,
|
|
var_first_use_static_info::in(var_first_use_static_info),
|
|
float::in, float::out, found_first_use::out) is det.
|
|
|
|
disj_var_first_use(RevGoalPath, Disjuncts, Detism, StaticInfo,
|
|
!CostSoFar, FoundFirstUse) :-
|
|
% We cannot handle nondet/multi disjunctions. So we use pessimistic
|
|
% defaults for FoundFirstUse if this disjunction is nondet or multi.
|
|
% For calculating the cost of the disjunction, assume that it is a semidet
|
|
% disjunction. Doing this will find the incorrect cost for the
|
|
% disjunction. However disjunctions occur rarely, so this is not likely
|
|
% to dramatically affect anything.
|
|
CostBeforeDisjunction = !.CostSoFar,
|
|
disj_var_first_use_2(RevGoalPath, 1, Disjuncts, StaticInfo,
|
|
!CostSoFar, FoundFirstUse0),
|
|
CostAfterDisjunction = !.CostSoFar,
|
|
( if
|
|
detism_get_solutions(Detism) = at_most_many_rep,
|
|
FoundFirstUse0 = found_first_use(_)
|
|
then
|
|
VarUseType = StaticInfo ^ fui_var_use_opts ^ vuo_var_use_type,
|
|
(
|
|
VarUseType = var_use_consumption,
|
|
FoundFirstUse = found_first_use(CostBeforeDisjunction)
|
|
;
|
|
( VarUseType = var_use_production
|
|
; VarUseType = var_use_other
|
|
),
|
|
FoundFirstUse = found_first_use(CostAfterDisjunction)
|
|
)
|
|
else
|
|
FoundFirstUse = FoundFirstUse0
|
|
).
|
|
|
|
:- pred disj_var_first_use_2(reverse_goal_path::in, int::in,
|
|
list(goal_rep(goal_id))::in,
|
|
var_first_use_static_info::in(var_first_use_static_info),
|
|
float::in, float::out, found_first_use::out) is det.
|
|
|
|
disj_var_first_use_2(_, _, [], _, !CostSoFar, have_not_found_first_use).
|
|
disj_var_first_use_2(RevGoalPath, DisjNum, [Disj | Disjs], StaticInfo,
|
|
!CostSoFar, FoundFirstUse) :-
|
|
VarUseType = StaticInfo ^ fui_var_use_opts ^ vuo_var_use_type,
|
|
CoverageArray = StaticInfo ^ fui_coverage_array,
|
|
goal_var_first_use(rgp_cons(RevGoalPath, step_disj(DisjNum)), Disj,
|
|
StaticInfo, !CostSoFar, HeadFoundFirstUse),
|
|
disj_var_first_use_2(RevGoalPath, DisjNum + 1, Disjs, StaticInfo,
|
|
!CostSoFar, TailFoundFirstUse),
|
|
(
|
|
HeadFoundFirstUse = have_not_found_first_use,
|
|
TailFoundFirstUse = have_not_found_first_use,
|
|
FoundFirstUse = HeadFoundFirstUse
|
|
;
|
|
HeadFoundFirstUse = have_not_found_first_use,
|
|
TailFoundFirstUse = found_first_use(_),
|
|
FoundFirstUse = TailFoundFirstUse
|
|
;
|
|
HeadFoundFirstUse = found_first_use(_),
|
|
TailFoundFirstUse = have_not_found_first_use,
|
|
FoundFirstUse = HeadFoundFirstUse
|
|
;
|
|
HeadFoundFirstUse = found_first_use(HeadCost),
|
|
TailFoundFirstUse = found_first_use(TailCost),
|
|
(
|
|
VarUseType = var_use_consumption,
|
|
% The variable is probably consumed in the first disjunct
|
|
% even if it fails. This is also the pessimistic default.
|
|
Cost = HeadCost
|
|
;
|
|
( VarUseType = var_use_production
|
|
; VarUseType = var_use_other
|
|
),
|
|
% Use a weighted average to reflect the likely success
|
|
% of the first disjunct.
|
|
DisjCoverage =
|
|
get_goal_attribute_det(CoverageArray, Disj ^ goal_annotation),
|
|
( if get_coverage_before(DisjCoverage, HeadCount) then
|
|
HeadWeight = float(HeadCount)
|
|
else
|
|
unexpected($pred, "unknown coverage before disjunct")
|
|
),
|
|
(
|
|
Disjs = [],
|
|
TailWeight = 0.0
|
|
;
|
|
Disjs = [FirstTailDisj | _],
|
|
FirstTailCoverage = get_goal_attribute_det(CoverageArray,
|
|
FirstTailDisj ^ goal_annotation),
|
|
( if get_coverage_before(FirstTailCoverage, TailCount) then
|
|
TailWeight = float(TailCount)
|
|
else
|
|
unexpected($pred, "unknown coverage before disjunct")
|
|
)
|
|
),
|
|
weighted_average([HeadWeight, TailWeight], [HeadCost, TailCost],
|
|
Cost)
|
|
),
|
|
FoundFirstUse = found_first_use(Cost)
|
|
).
|
|
|
|
:- pred switch_var_first_use(reverse_goal_path::in, var_rep::in,
|
|
list(case_rep(goal_id))::in,
|
|
var_first_use_static_info::in(var_first_use_static_info),
|
|
float::in, float::out, found_first_use::out) is det.
|
|
|
|
switch_var_first_use(RevGoalPath, SwitchedOnVar, Cases, StaticInfo,
|
|
CostBeforeSwitch, CostAfterSwitch, FoundFirstUse) :-
|
|
switch_var_first_use_2(RevGoalPath, 1, StaticInfo, Cases, CaseWeights,
|
|
CostBeforeSwitch, CostCases, FoundFirstUseCases),
|
|
weighted_average(CaseWeights, CostCases, CostAfterSwitch),
|
|
Var = StaticInfo ^ fui_var,
|
|
( if Var = SwitchedOnVar then
|
|
% This can only possibly be a consumption of this variable.
|
|
FoundFirstUse = found_first_use(CostBeforeSwitch)
|
|
else
|
|
( if
|
|
list.all_true(unify(have_not_found_first_use), FoundFirstUseCases)
|
|
then
|
|
% No case contained a first-use of this variable.
|
|
FoundFirstUse = have_not_found_first_use
|
|
else
|
|
VarUseType = StaticInfo ^ fui_var_use_opts ^ vuo_var_use_type,
|
|
(
|
|
VarUseType = var_use_consumption,
|
|
DefaultCost = CostAfterSwitch
|
|
;
|
|
( VarUseType = var_use_production
|
|
; VarUseType = var_use_other
|
|
),
|
|
DefaultCost = CostBeforeSwitch
|
|
),
|
|
list.map(ffu_to_float(DefaultCost), FoundFirstUseCases,
|
|
FirstUseTimes),
|
|
weighted_average(CaseWeights, FirstUseTimes, AvgFirstUseTime),
|
|
FoundFirstUse = found_first_use(AvgFirstUseTime)
|
|
)
|
|
).
|
|
|
|
:- pred switch_var_first_use_2(reverse_goal_path::in, int::in,
|
|
var_first_use_static_info::in(var_first_use_static_info),
|
|
list(case_rep(goal_id))::in, list(float)::out, float::in,
|
|
list(float)::out, list(found_first_use)::out) is det.
|
|
|
|
switch_var_first_use_2(_, _, _, [], [], _, [], []).
|
|
switch_var_first_use_2(RevGoalPath, CaseNum, StaticInfo, [Case | Cases],
|
|
[Weight | Weights], Cost0, [Cost | Costs],
|
|
[FoundFirstUse | FoundFirstUses]) :-
|
|
switch_var_first_use_2(RevGoalPath, CaseNum + 1, StaticInfo,
|
|
Cases, Weights, Cost0, Costs, FoundFirstUses),
|
|
Case = case_rep(_, _, Goal),
|
|
RevArmPath = rgp_cons(RevGoalPath,
|
|
step_switch(CaseNum, unknown_num_functors_in_type)),
|
|
goal_var_first_use(RevArmPath, Goal, StaticInfo, Cost0, Cost,
|
|
FoundFirstUse),
|
|
Coverage = get_goal_attribute_det(StaticInfo ^ fui_coverage_array,
|
|
Goal ^ goal_annotation),
|
|
get_coverage_before_det(Coverage, BeforeCount),
|
|
Weight = float(BeforeCount).
|
|
|
|
:- pred ite_var_first_use(reverse_goal_path::in,
|
|
goal_rep(goal_id)::in, goal_rep(goal_id)::in, goal_rep(goal_id)::in,
|
|
var_first_use_static_info::in(var_first_use_static_info),
|
|
float::in, float::out, found_first_use::out)
|
|
is det.
|
|
|
|
ite_var_first_use(RevGoalPath, Cond, Then, Else, StaticInfo,
|
|
!CostSoFar, FoundFirstUse) :-
|
|
CoverageArray = StaticInfo ^ fui_coverage_array,
|
|
ThenCoverage =
|
|
get_goal_attribute_det(CoverageArray, Then ^ goal_annotation),
|
|
get_coverage_before_det(ThenCoverage, CountBeforeThen),
|
|
ElseCoverage =
|
|
get_goal_attribute_det(CoverageArray, Else ^ goal_annotation),
|
|
get_coverage_before_det(ElseCoverage, CountBeforeElse),
|
|
Weights = [float(CountBeforeThen), float(CountBeforeElse)],
|
|
RevCondGoalPath = rgp_cons(RevGoalPath, step_ite_cond),
|
|
RevThenGoalPath = rgp_cons(RevGoalPath, step_ite_then),
|
|
RevElseGoalPath = rgp_cons(RevGoalPath, step_ite_else),
|
|
VarUseType = StaticInfo ^ fui_var_use_opts ^ vuo_var_use_type,
|
|
CostBeforeITE = !.CostSoFar,
|
|
goal_var_first_use(RevCondGoalPath, Cond, StaticInfo,
|
|
CostBeforeITE, CostAfterCond, CondFoundFirstUse),
|
|
goal_var_first_use(RevThenGoalPath, Then, StaticInfo,
|
|
CostAfterCond, CostAfterThen, ThenFoundFirstUse),
|
|
goal_var_first_use(RevElseGoalPath, Else, StaticInfo,
|
|
CostAfterCond, CostAfterElse, ElseFoundFirstUse),
|
|
weighted_average(Weights, [CostAfterThen, CostAfterElse], CostAfterITE),
|
|
!:CostSoFar = CostAfterITE,
|
|
(
|
|
CondFoundFirstUse = found_first_use(_),
|
|
FoundFirstUse = CondFoundFirstUse
|
|
;
|
|
CondFoundFirstUse = have_not_found_first_use,
|
|
( if
|
|
ThenFoundFirstUse = have_not_found_first_use,
|
|
ElseFoundFirstUse = have_not_found_first_use
|
|
then
|
|
FoundFirstUse = have_not_found_first_use
|
|
else
|
|
(
|
|
VarUseType = var_use_consumption,
|
|
DefaultCost = CostAfterCond
|
|
;
|
|
( VarUseType = var_use_production
|
|
; VarUseType = var_use_other
|
|
),
|
|
DefaultCost = CostAfterITE
|
|
),
|
|
ffu_to_float(DefaultCost, ThenFoundFirstUse, ThenVarUseTime),
|
|
ffu_to_float(DefaultCost, ElseFoundFirstUse, ElseVarUseTime),
|
|
weighted_average(Weights, [ThenVarUseTime, ElseVarUseTime],
|
|
VarUseTime),
|
|
FoundFirstUse = found_first_use(VarUseTime),
|
|
trace [compile_time(flag("debug_first_var_use")), io(!IO)] (
|
|
io.output_stream(OutputStream, !IO),
|
|
io.format(OutputStream,
|
|
"Trace: ITE: Weights: %s, Then: %f, Else: %f, " ++
|
|
"VarUseTime: %f\n",
|
|
[s(string(Weights)), f(ThenVarUseTime), f(ElseVarUseTime),
|
|
f(VarUseTime)],
|
|
!IO)
|
|
)
|
|
)
|
|
).
|
|
|
|
:- pred ffu_to_float(float::in, found_first_use::in, float::out) is det.
|
|
|
|
ffu_to_float(Default, have_not_found_first_use, Default).
|
|
ffu_to_float(_, found_first_use(UseTime), UseTime).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% Goal var first use analysis for the base and recursive cases of a recursive
|
|
% procedure.
|
|
%
|
|
|
|
:- type recursive_calls_list ==
|
|
assoc_list(forward_goal_path, float).
|
|
|
|
:- pred build_recursive_call_sites_list(
|
|
map(reverse_goal_path, cs_cost_csq)::in,
|
|
recursive_calls_list::out) is det.
|
|
|
|
build_recursive_call_sites_list(Map, List) :-
|
|
List0 = to_assoc_list(Map),
|
|
list.map(
|
|
( pred((RevGoalPath - Cost)::in, (GoalPath - Calls)::out) is det :-
|
|
Calls = cs_cost_get_calls(Cost),
|
|
rgp_to_fgp(RevGoalPath, GoalPath)
|
|
), List0, List).
|
|
|
|
:- pred filter_recursive_call_sites(goal_path_step::in,
|
|
recursive_calls_list::in, recursive_calls_list::out) is det.
|
|
|
|
filter_recursive_call_sites(GoalPathStep, !RecCallSites) :-
|
|
filter_map((pred((GP0 - Coverage)::in, (GP - Coverage)::out) is semidet :-
|
|
GP0 = fgp_cons(GoalPathStep, GP)
|
|
), !RecCallSites).
|
|
|
|
% rec_goal_var_first_use(Goal, RecCalls, Info, FoundFirstUse, !CostSoFar).
|
|
%
|
|
% Find the first use in both the recursive and base cases.
|
|
%
|
|
% This works under the following assumptions.
|
|
% + All sub goals succeed at most once.
|
|
% + The first use is not conjoined with a switch where some switch
|
|
% branches are recursive and some are not. (or ITE)
|
|
% + The cost of disjunctions is not computed correctly.
|
|
%
|
|
:- pred rec_goal_var_first_use(goal_rep(goal_id)::in,
|
|
recursive_calls_list::in,
|
|
var_first_use_static_info::in(var_first_use_static_info_rec),
|
|
found_first_use::out, float::in, float::out) is det.
|
|
|
|
rec_goal_var_first_use(Goal, RecCalls, Info, FoundFirstUse,
|
|
!CostSoFar) :-
|
|
Goal = goal_rep(GoalExpr, Detism, GoalId),
|
|
Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, GoalId),
|
|
get_coverage_before_and_after_det(Coverage, Before, After),
|
|
( if
|
|
% Do not bother exploring this goal if it is never entered.
|
|
% Or never finishes and we are looking for a production.
|
|
(
|
|
Before = 0
|
|
;
|
|
VarUseType = Info ^ fui_var_use_opts ^ vuo_var_use_type,
|
|
VarUseType = var_use_production,
|
|
( Detism = erroneous_rep
|
|
; Detism = failure_rep
|
|
; After = 0
|
|
)
|
|
)
|
|
then
|
|
FoundFirstUse = have_not_found_first_use
|
|
else
|
|
(
|
|
GoalExpr = conj_rep(Conjs),
|
|
rec_conj_var_first_use(Conjs, 1, RecCalls, Info,
|
|
FoundFirstUse, !CostSoFar)
|
|
;
|
|
GoalExpr = disj_rep(Disjs),
|
|
rec_disj_var_first_use(Disjs, RecCalls, Info, FoundFirstUse,
|
|
!CostSoFar)
|
|
;
|
|
GoalExpr = switch_rep(SwitchedOnVar, _CanFail, Cases),
|
|
rec_switch_var_first_use(Cases, SwitchedOnVar, RecCalls,
|
|
Info, FoundFirstUse, !CostSoFar)
|
|
;
|
|
GoalExpr = ite_rep(Cond, Then, Else),
|
|
rec_ite_var_first_use(Cond, Then, Else,
|
|
RecCalls, Info, FoundFirstUse, !CostSoFar)
|
|
;
|
|
(
|
|
GoalExpr = negation_rep(SubGoal),
|
|
GoalPathStep = step_neg
|
|
;
|
|
GoalExpr = scope_rep(SubGoal, ScopeIsCut),
|
|
GoalPathStep = step_scope(ScopeIsCut)
|
|
),
|
|
filter_recursive_call_sites(GoalPathStep, RecCalls,
|
|
RecCallsSubGoal),
|
|
rec_goal_var_first_use(SubGoal, RecCallsSubGoal,
|
|
Info, FoundFirstUse, !CostSoFar)
|
|
;
|
|
GoalExpr = atomic_goal_rep(_, _, BoundVars, AtomicGoal),
|
|
(
|
|
( AtomicGoal = plain_call_rep(_, _, _)
|
|
; AtomicGoal = higher_order_call_rep(_, _)
|
|
; AtomicGoal = method_call_rep(_, _, _)
|
|
),
|
|
ContainingGoalMap = Info ^ fui_containing_goal_map,
|
|
RevGoalPath =
|
|
goal_id_to_reverse_path(ContainingGoalMap, GoalId),
|
|
call_var_first_use(AtomicGoal, BoundVars, RevGoalPath,
|
|
Info, FoundFirstUse, !CostSoFar)
|
|
;
|
|
( AtomicGoal = unify_construct_rep(_, _, _)
|
|
; AtomicGoal = unify_deconstruct_rep(_, _, _)
|
|
; AtomicGoal = partial_construct_rep(_, _, _)
|
|
; AtomicGoal = partial_deconstruct_rep(_, _, _)
|
|
; AtomicGoal = unify_assign_rep(_, _)
|
|
; AtomicGoal = cast_rep(_, _)
|
|
; AtomicGoal = unify_simple_test_rep(_, _)
|
|
; AtomicGoal = pragma_foreign_code_rep(_)
|
|
; AtomicGoal = event_call_rep(_, _)
|
|
; AtomicGoal = builtin_call_rep(_, _, _)
|
|
),
|
|
% trivial goals have a zero cost, so !CostSoFar is not updated.
|
|
atomic_trivial_var_first_use(AtomicGoal, BoundVars,
|
|
!.CostSoFar, Info, FoundFirstUse)
|
|
)
|
|
)
|
|
),
|
|
trace [compile_time(flag("debug_first_var_use")), io(!IO)] (
|
|
some [ContainingGoalMap, RevGoalPath] (
|
|
ContainingGoalMap = Info ^ fui_containing_goal_map,
|
|
RevGoalPath =
|
|
goal_id_to_reverse_path(ContainingGoalMap, GoalId),
|
|
io.output_stream(OutputStream, !IO),
|
|
io.format(OutputStream, "Trace: goal_var_first_use: %s\n",
|
|
[s(rev_goal_path_to_string(RevGoalPath))], !IO)
|
|
)
|
|
).
|
|
|
|
:- pred rec_conj_var_first_use(list(goal_rep(goal_id))::in, int::in,
|
|
recursive_calls_list::in,
|
|
var_first_use_static_info::in(var_first_use_static_info_rec),
|
|
found_first_use::out, float::in, float::out) is det.
|
|
|
|
rec_conj_var_first_use([], _, _, _, have_not_found_first_use, !CostSoFar).
|
|
rec_conj_var_first_use([Conj | Conjs], ConjNum, RecCalls, StaticInfo,
|
|
FoundFirstUse, !CostSoFar) :-
|
|
filter_recursive_call_sites(step_conj(ConjNum), RecCalls, ConjRecCalls),
|
|
rec_goal_var_first_use(Conj, ConjRecCalls, StaticInfo,
|
|
ConjFoundFirstUse, !CostSoFar),
|
|
rec_conj_var_first_use(Conjs, ConjNum + 1, RecCalls, StaticInfo,
|
|
ConjsFoundFirstUse, !CostSoFar),
|
|
(
|
|
% XXX if a variable is bound more than once, because it's used
|
|
% with partial instantiation then we want to use the last time it
|
|
% is bound. Instmaps can be used to track this. This is relevant
|
|
% when searching for the producer of a variable.
|
|
ConjFoundFirstUse = found_first_use(UseTime),
|
|
FoundFirstUse = found_first_use(UseTime)
|
|
;
|
|
ConjFoundFirstUse = have_not_found_first_use,
|
|
% XXX Use time should be adjusted for the probability of entering
|
|
% Conjs (the success of Conj) But doing so means a weighted average
|
|
% between the success and failure paths, which only makes sense if
|
|
% the consumption (because this is semidet) might be done in the
|
|
% failure case. This also has to be done if the probability of
|
|
% recursion in one of the two cases is different. For now we assume
|
|
% that Conjs will always be entered.
|
|
FoundFirstUse = ConjsFoundFirstUse
|
|
).
|
|
|
|
:- pred rec_disj_var_first_use(list(goal_rep(goal_id))::in,
|
|
recursive_calls_list::in,
|
|
var_first_use_static_info::in(var_first_use_static_info_rec),
|
|
found_first_use::out, float::in, float::out) is det.
|
|
|
|
rec_disj_var_first_use(Disjs, RecCalls, Info, FoundFirstUse, !CostSoFar) :-
|
|
% We do not handle disjunctions, just use a pessimistic default.
|
|
% For calculating the cost of the disjunction, assume that it is a semidet
|
|
% disjunction. Doing this will find the incorrect cost for the
|
|
% disjunction, however disjunctions occur rarely, so this is not likely
|
|
% to dramatically affect anything.
|
|
CostBeforeDisjunction = !.CostSoFar,
|
|
rec_disj_var_first_use_2(Disjs, 1, RecCalls, Info, FoundFirstUse0,
|
|
!CostSoFar),
|
|
CostAfterDisjunction = !.CostSoFar,
|
|
(
|
|
FoundFirstUse0 = found_first_use(_),
|
|
VarUseType = Info ^ fui_var_use_opts ^ vuo_var_use_type,
|
|
(
|
|
VarUseType = var_use_consumption,
|
|
FoundFirstUse = found_first_use(CostBeforeDisjunction)
|
|
;
|
|
( VarUseType = var_use_production
|
|
; VarUseType = var_use_other
|
|
),
|
|
FoundFirstUse = found_first_use(CostAfterDisjunction)
|
|
)
|
|
;
|
|
FoundFirstUse0 = have_not_found_first_use,
|
|
FoundFirstUse = FoundFirstUse0
|
|
).
|
|
|
|
:- pred rec_disj_var_first_use_2(list(goal_rep(goal_id))::in, int::in,
|
|
recursive_calls_list::in,
|
|
var_first_use_static_info::in(var_first_use_static_info_rec),
|
|
found_first_use::out, float::in, float::out) is det.
|
|
|
|
rec_disj_var_first_use_2([], _, _, _, have_not_found_first_use, !CostSoFar).
|
|
rec_disj_var_first_use_2([Disj | Disjs], DisjNum, RecCalls, Info,
|
|
FoundFirstUse, !CostSoFar) :-
|
|
filter_recursive_call_sites(step_disj(DisjNum), RecCalls, DisjRecCalls),
|
|
rec_goal_var_first_use(Disj, DisjRecCalls, Info, DisjFoundFirstUse,
|
|
!CostSoFar),
|
|
|
|
CoverageArray = Info ^ fui_coverage_array,
|
|
Coverage = get_goal_attribute_det(CoverageArray, Disj ^ goal_annotation),
|
|
get_coverage_before_and_after_det(Coverage, Before, After),
|
|
( if Before = 0 then
|
|
% Avoid a divide by zero.
|
|
CostDisjs = 0.0,
|
|
FoundFirstUse = DisjFoundFirstUse
|
|
else
|
|
rec_disj_var_first_use_2(Disjs, DisjNum + 1, RecCalls, Info,
|
|
DisjsFoundFirstUse, 0.0, CostDisjs0),
|
|
FailureProb = probable(float(Before - After) / float(Before)),
|
|
CostDisjs = CostDisjs0 * probability_to_float(FailureProb),
|
|
(
|
|
DisjFoundFirstUse = have_not_found_first_use,
|
|
FoundFirstUse = DisjsFoundFirstUse
|
|
;
|
|
DisjFoundFirstUse = found_first_use(UseTime),
|
|
FoundFirstUse = found_first_use(UseTime)
|
|
)
|
|
),
|
|
!:CostSoFar = !.CostSoFar + CostDisjs.
|
|
|
|
:- pred rec_switch_var_first_use(list(case_rep(goal_id))::in,
|
|
var_rep::in, recursive_calls_list::in,
|
|
var_first_use_static_info::in(var_first_use_static_info_rec),
|
|
found_first_use::out, float::in, float::out) is det.
|
|
|
|
rec_switch_var_first_use(Cases, SwitchedOnVar, RecCalls, Info,
|
|
FoundFirstUse, CostBeforeSwitch, CostAfterSwitch) :-
|
|
rec_switch_var_first_use_2(Cases, 1, RecCalls, Info, CostBeforeSwitch,
|
|
CaseWeights0, FoundFirstUseCases, CostAfterCases, RecProbs),
|
|
RecCase = Info ^ fui_rec_info ^ furi_rec_case,
|
|
map_corresponding(adjust_weight_for_recursion(RecCase),
|
|
RecProbs, CaseWeights0, CaseWeights),
|
|
weighted_average(CaseWeights, CostAfterCases, CostAfterSwitch),
|
|
|
|
Var = Info ^ fui_var,
|
|
( if Var = SwitchedOnVar then
|
|
FoundFirstUse = found_first_use(CostBeforeSwitch)
|
|
else
|
|
( if
|
|
list.all_true(unify(have_not_found_first_use), FoundFirstUseCases)
|
|
then
|
|
% No case contained a first-use of this variable.
|
|
FoundFirstUse = have_not_found_first_use
|
|
else
|
|
VarUseType = Info ^ fui_var_use_opts ^ vuo_var_use_type,
|
|
% XXX this is also flawed, the default costs should not be the
|
|
% average costs, they should be the cost for the specific case
|
|
% where that default would be used.
|
|
% XXX Secondly, this needs to also support the 'don't insert waits
|
|
% on all branches' optimisation.
|
|
(
|
|
VarUseType = var_use_consumption,
|
|
DefaultCost = CostAfterSwitch
|
|
;
|
|
( VarUseType = var_use_production
|
|
; VarUseType = var_use_other
|
|
),
|
|
DefaultCost = CostBeforeSwitch
|
|
),
|
|
list.map(ffu_to_float(DefaultCost),
|
|
FoundFirstUseCases, FirstUseTimes),
|
|
weighted_average(CaseWeights, FirstUseTimes, UseTime),
|
|
FoundFirstUse = found_first_use(UseTime)
|
|
)
|
|
).
|
|
|
|
:- pred rec_switch_var_first_use_2(list(case_rep(goal_id))::in, int::in,
|
|
recursive_calls_list::in,
|
|
var_first_use_static_info::in(var_first_use_static_info_rec),
|
|
float::in, list(float)::out, list(found_first_use)::out,
|
|
list(float)::out, list(probability)::out) is det.
|
|
|
|
rec_switch_var_first_use_2([], _, _, _, _, [], [], [], []).
|
|
rec_switch_var_first_use_2([Case | Cases], CaseNum, RecCalls, Info,
|
|
CostBeforeSwitch, Weights, FoundFirstUses, CostsAfter, RecProbs) :-
|
|
rec_switch_var_first_use_2(Cases, CaseNum + 1, RecCalls, Info,
|
|
CostBeforeSwitch, CasesWeights, CasesFoundFirstUses, CasesCostsAfter,
|
|
CasesRecProbs),
|
|
Goal = Case ^ cr_case_goal,
|
|
GoalId = Goal ^ goal_annotation,
|
|
filter_recursive_call_sites(
|
|
step_switch(CaseNum, unknown_num_functors_in_type),
|
|
RecCalls, CaseRecCalls),
|
|
rec_goal_var_first_use(Goal, CaseRecCalls, Info, FoundFirstUse,
|
|
CostBeforeSwitch, CaseCostAfter),
|
|
Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, GoalId),
|
|
get_coverage_before_det(Coverage, Before),
|
|
RecProb = get_goal_attribute_det(Info ^ fui_rec_info ^ furi_rec_prob_array,
|
|
GoalId),
|
|
|
|
Weight = float(Before),
|
|
Weights = [Weight | CasesWeights],
|
|
FoundFirstUses = [FoundFirstUse | CasesFoundFirstUses],
|
|
CostsAfter = [CaseCostAfter | CasesCostsAfter],
|
|
RecProbs = [RecProb | CasesRecProbs].
|
|
|
|
:- pred rec_ite_var_first_use(goal_rep(goal_id)::in, goal_rep(goal_id)::in,
|
|
goal_rep(goal_id)::in, recursive_calls_list::in,
|
|
var_first_use_static_info::in(var_first_use_static_info_rec),
|
|
found_first_use::out, float::in, float::out) is det.
|
|
|
|
rec_ite_var_first_use(Cond, Then, Else, RecCalls, Info, FoundFirstUse,
|
|
!CostSoFar) :-
|
|
filter_recursive_call_sites(step_ite_cond, RecCalls, CondRecCalls),
|
|
filter_recursive_call_sites(step_ite_then, RecCalls, ThenRecCalls),
|
|
filter_recursive_call_sites(step_ite_else, RecCalls, ElseRecCalls),
|
|
rec_goal_var_first_use(Cond, CondRecCalls, Info, CondFoundFirstUse,
|
|
!CostSoFar),
|
|
CostAfterCond = !.CostSoFar,
|
|
rec_goal_var_first_use(Then, ThenRecCalls, Info, ThenFoundFirstUse,
|
|
!.CostSoFar, CostAfterThen),
|
|
rec_goal_var_first_use(Else, ElseRecCalls, Info, ElseFoundFirstUse,
|
|
!.CostSoFar, CostAfterElse),
|
|
|
|
% Determine goal weights.
|
|
CoverageArray = Info ^ fui_coverage_array,
|
|
Then = goal_rep(_, _, ThenId),
|
|
Else = goal_rep(_, _, ElseId),
|
|
ThenCoverage = get_goal_attribute_det(CoverageArray, ThenId),
|
|
ElseCoverage = get_goal_attribute_det(CoverageArray, ElseId),
|
|
get_coverage_before_det(ThenCoverage, BeforeThen),
|
|
get_coverage_before_det(ElseCoverage, BeforeElse),
|
|
RecProbArray = Info ^ fui_rec_info ^ furi_rec_prob_array,
|
|
ThenRecProb = get_goal_attribute_det(RecProbArray, ThenId),
|
|
ElseRecProb = get_goal_attribute_det(RecProbArray, ElseId),
|
|
Weights0 = map(float, [BeforeThen, BeforeElse]),
|
|
map_corresponding(
|
|
adjust_weight_for_recursion(Info ^ fui_rec_info ^ furi_rec_case),
|
|
[ThenRecProb, ElseRecProb], Weights0, Weights),
|
|
|
|
% Get the weighted average of the costs for !:CostSoFar,
|
|
weighted_average(Weights, [CostAfterThen, CostAfterElse], !:CostSoFar),
|
|
|
|
% Determine FoundFirstUse
|
|
(
|
|
CondFoundFirstUse = found_first_use(_),
|
|
FoundFirstUse = CondFoundFirstUse
|
|
;
|
|
CondFoundFirstUse = have_not_found_first_use,
|
|
|
|
( if
|
|
ThenFoundFirstUse = have_not_found_first_use,
|
|
ElseFoundFirstUse = have_not_found_first_use
|
|
then
|
|
FoundFirstUse = have_not_found_first_use
|
|
else
|
|
VarUseType = Info ^ fui_var_use_opts ^ vuo_var_use_type,
|
|
(
|
|
VarUseType = var_use_consumption,
|
|
Default = !.CostSoFar
|
|
;
|
|
( VarUseType = var_use_production
|
|
; VarUseType = var_use_other
|
|
),
|
|
Default = CostAfterCond
|
|
),
|
|
list.map(ffu_to_float(Default),
|
|
[ThenFoundFirstUse, ElseFoundFirstUse], UseTimes),
|
|
weighted_average(Weights, UseTimes, UseTime),
|
|
FoundFirstUse = found_first_use(UseTime)
|
|
)
|
|
).
|
|
|
|
:- pred adjust_weight_for_recursion(recursive_case::in,
|
|
probability::in, float::in, float::out) is det.
|
|
|
|
adjust_weight_for_recursion(RecCase, RecProb, !Weight) :-
|
|
(
|
|
RecCase = recursive_case,
|
|
Prob = RecProb
|
|
;
|
|
RecCase = base_case,
|
|
Prob = not_probability(RecProb)
|
|
),
|
|
!:Weight = !.Weight * probability_to_float(Prob).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% Give the probability that this goal leads to a recursion.
|
|
%
|
|
% Note that this does not compute whether this goal is on a recursive path,
|
|
% so it is not sufficient on its own. See rec_goal_var_first_use.
|
|
%
|
|
:- pred goal_rec_prob(goal_rep(goal_id)::in, recursive_calls_list::in,
|
|
var_first_use_static_info::in, probability::out,
|
|
goal_attr_array(probability)::gaa_di,
|
|
goal_attr_array(probability)::gaa_uo) is det.
|
|
|
|
goal_rec_prob(Goal, RecCalls, Info, Prob, !ProbArray) :-
|
|
Goal = goal_rep(GoalExpr, _, GoalId),
|
|
Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, GoalId),
|
|
get_coverage_before_det(Coverage, Before),
|
|
( if Before = 0 then
|
|
% Avoid a divide by zero and provide a short-cut.
|
|
Prob = impossible
|
|
% There is no need to update the array. The default value is already
|
|
% impossible.
|
|
else
|
|
(
|
|
GoalExpr = conj_rep(Conjs),
|
|
conj_rec_prob(Conjs, 1, RecCalls, Info, Prob, !ProbArray)
|
|
;
|
|
GoalExpr = disj_rep(Disjs),
|
|
disj_rec_prob(Disjs, 1, RecCalls, Info, Prob, !ProbArray)
|
|
;
|
|
GoalExpr = switch_rep(_, _, Cases),
|
|
switch_rec_prob(Cases, Before, RecCalls, Info, Prob, !ProbArray)
|
|
;
|
|
GoalExpr = ite_rep(Cond, Then, Else),
|
|
ite_rec_prob(Cond, Then, Else, RecCalls, Info, Prob, !ProbArray)
|
|
;
|
|
(
|
|
GoalExpr = negation_rep(SubGoal),
|
|
Step = step_neg
|
|
;
|
|
GoalExpr = scope_rep(SubGoal, MaybeCut),
|
|
Step = step_scope(MaybeCut)
|
|
),
|
|
filter_recursive_call_sites(Step, RecCalls, SubGoalRecCalls),
|
|
goal_rec_prob(SubGoal, SubGoalRecCalls, Info, Prob, !ProbArray)
|
|
;
|
|
GoalExpr = atomic_goal_rep(_, _, _, _),
|
|
(
|
|
RecCalls = [],
|
|
Prob = impossible
|
|
;
|
|
RecCalls = [_ | _],
|
|
Prob = certain
|
|
)
|
|
),
|
|
update_goal_attribute(GoalId, Prob, !ProbArray)
|
|
).
|
|
|
|
:- pred conj_rec_prob(list(goal_rep(goal_id))::in, int::in,
|
|
recursive_calls_list::in, var_first_use_static_info::in, probability::out,
|
|
goal_attr_array(probability)::gaa_di,
|
|
goal_attr_array(probability)::gaa_uo) is det.
|
|
|
|
conj_rec_prob([], _, _, _, impossible, !ProbArray).
|
|
conj_rec_prob([Conj | Conjs], ConjNum, RecCalls, Info, Prob, !ProbArray) :-
|
|
ConjId = Conj ^ goal_annotation,
|
|
Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, ConjId),
|
|
get_coverage_before_and_after_det(Coverage, Before, After),
|
|
( if Before = 0 then
|
|
% This code is dead. Return the result without a division
|
|
% to prevent a divide by zero.
|
|
Prob = impossible
|
|
else
|
|
conj_rec_prob(Conjs, ConjNum + 1, RecCalls, Info, ConjsProb0,
|
|
!ProbArray),
|
|
SuccessProb = probable(float(After) / float(Before)),
|
|
ConjsProb = and(SuccessProb, ConjsProb0),
|
|
|
|
filter_recursive_call_sites(step_conj(ConjNum),
|
|
RecCalls, ConjRecCalls),
|
|
goal_rec_prob(Conj, ConjRecCalls, Info, ConjProb, !ProbArray),
|
|
Prob = or(ConjProb, ConjsProb)
|
|
).
|
|
|
|
:- pred disj_rec_prob(list(goal_rep(goal_id))::in, int::in,
|
|
recursive_calls_list::in, var_first_use_static_info::in, probability::out,
|
|
goal_attr_array(probability)::gaa_di,
|
|
goal_attr_array(probability)::gaa_uo) is det.
|
|
|
|
disj_rec_prob([], _, _, _, impossible, !ProbArray).
|
|
disj_rec_prob([Disj | Disjs], DisjNum, RecCalls, Info, Prob, !ProbArray) :-
|
|
DisjId = Disj ^ goal_annotation,
|
|
Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, DisjId),
|
|
get_coverage_before_and_after_det(Coverage, Before, After),
|
|
( if Before = 0 then
|
|
% As above, this code is dead.
|
|
Prob = impossible
|
|
else
|
|
disj_rec_prob(Disjs, DisjNum + 1, RecCalls, Info, DisjsProb0,
|
|
!ProbArray),
|
|
% Assume that this disjunction is in a single solution context.
|
|
FailureProb = probable(float(Before - After) / float(Before)),
|
|
DisjsProb = and(FailureProb, DisjsProb0),
|
|
|
|
filter_recursive_call_sites(step_disj(DisjNum),
|
|
RecCalls, DisjRecCalls),
|
|
goal_rec_prob(Disj, DisjRecCalls, Info, DisjProb, !ProbArray),
|
|
Prob = or(DisjProb, DisjsProb)
|
|
).
|
|
|
|
:- pred switch_rec_prob(list(case_rep(goal_id))::in, int::in,
|
|
recursive_calls_list::in, var_first_use_static_info::in, probability::out,
|
|
goal_attr_array(probability)::gaa_di,
|
|
goal_attr_array(probability)::gaa_uo) is det.
|
|
|
|
switch_rec_prob(Cases, TotalCalls, RecCalls, Info, Prob, !ProbArray) :-
|
|
switch_rec_prob_2(Cases, 1, TotalCalls, RecCalls, Info, Probs, Weights,
|
|
!ProbArray),
|
|
weighted_average(Weights, map(probability_to_float, Probs), ProbFloat),
|
|
Prob = probable(ProbFloat).
|
|
|
|
:- pred switch_rec_prob_2(list(case_rep(goal_id))::in, int::in, int::in,
|
|
recursive_calls_list::in, var_first_use_static_info::in,
|
|
list(probability)::out, list(float)::out,
|
|
goal_attr_array(probability)::gaa_di,
|
|
goal_attr_array(probability)::gaa_uo) is det.
|
|
|
|
switch_rec_prob_2([], _, _, _, _, [], [], !ProbArray).
|
|
switch_rec_prob_2([Case | Cases], CaseNum, TotalCalls, RecCalls, Info, Probs,
|
|
Weights, !ProbArray) :-
|
|
switch_rec_prob_2(Cases, CaseNum + 1, TotalCalls, RecCalls, Info,
|
|
Probs0, Weights0, !ProbArray),
|
|
|
|
Case = case_rep(_, _, Goal),
|
|
filter_recursive_call_sites(
|
|
step_switch(CaseNum, unknown_num_functors_in_type),
|
|
RecCalls, CaseRecCalls),
|
|
goal_rec_prob(Goal, CaseRecCalls, Info, Prob, !ProbArray),
|
|
Goal = goal_rep(_, _, GoalId),
|
|
Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, GoalId),
|
|
get_coverage_before_det(Coverage, Before),
|
|
Weight = float(Before) / float(TotalCalls),
|
|
|
|
Probs = [Prob | Probs0],
|
|
Weights = [Weight | Weights0].
|
|
|
|
:- pred ite_rec_prob(goal_rep(goal_id)::in, goal_rep(goal_id)::in,
|
|
goal_rep(goal_id)::in, recursive_calls_list::in,
|
|
var_first_use_static_info::in, probability::out,
|
|
goal_attr_array(probability)::gaa_di,
|
|
goal_attr_array(probability)::gaa_uo) is det.
|
|
|
|
ite_rec_prob(Cond, Then, Else, RecCalls, Info, Prob, !ProbArray) :-
|
|
filter_recursive_call_sites(step_ite_cond, RecCalls, CondRecCalls),
|
|
filter_recursive_call_sites(step_ite_then, RecCalls, ThenRecCalls),
|
|
filter_recursive_call_sites(step_ite_else, RecCalls, ElseRecCalls),
|
|
goal_rec_prob(Cond, CondRecCalls, Info, CondProb, !ProbArray),
|
|
goal_rec_prob(Then, ThenRecCalls, Info, ThenProb0, !ProbArray),
|
|
goal_rec_prob(Else, ElseRecCalls, Info, ElseProb0, !ProbArray),
|
|
CondId = Cond ^ goal_annotation,
|
|
Coverage = get_goal_attribute_det(Info ^ fui_coverage_array, CondId),
|
|
get_coverage_before_and_after_det(Coverage, Before, After),
|
|
ThenCallProb = probable(float(After) / float(Before)),
|
|
ElseCallProb = probable(float(Before - After) / float(Before)),
|
|
ThenProb = and(ThenProb0, ThenCallProb),
|
|
ElseProb = and(ElseProb0, ElseCallProb),
|
|
ThenElseProb = or(ThenProb, ElseProb),
|
|
Prob = or(CondProb, ThenElseProb).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred goal_var_first_use_wrapper(clique_ptr::in, set(proc_dynamic_ptr)::in,
|
|
containing_goal_map::in, goal_attr_array(coverage_info)::in,
|
|
map(reverse_goal_path, cost_and_callees)::in,
|
|
map(reverse_goal_path, cs_cost_csq)::in,
|
|
recursion_type::in(recursion_type_known_costs), recursion_depth::in,
|
|
goal_rep(goal_id)::in, float::in, var_rep::in,
|
|
var_use_options::in, var_use_info::out) is det.
|
|
|
|
goal_var_first_use_wrapper(CliquePtr, CallStack, ContainingGoalMap,
|
|
CoverageArray, CallSiteMap, RecursiveCallSiteMap, RT, CurDepth, Goal,
|
|
ProcCost, Var, VarUseOptions, VarUseInfo) :-
|
|
goal_var_first_use(rgp_nil, Goal,
|
|
var_first_use_static_info(CliquePtr, CallSiteMap, RecursiveCallSiteMap,
|
|
ContainingGoalMap, CoverageArray, Var, VarUseOptions, CallStack,
|
|
RT, CurDepth, no_recursion_info),
|
|
0.0, _Cost, FoundFirstUse),
|
|
VarUseType = VarUseOptions ^ vuo_var_use_type,
|
|
found_first_use_to_use_info(FoundFirstUse, ProcCost, VarUseType,
|
|
VarUseInfo).
|
|
|
|
var_first_use(CliquePtr, CallSiteMap, RecursiveCallSiteMap, ContainingGoalMap,
|
|
CoverageArray, RT, CurDepth, Goal, RevGoalPath, Cost, Var,
|
|
VarUseOptions, VarUseInfo) :-
|
|
goal_var_first_use(RevGoalPath, Goal,
|
|
var_first_use_static_info(CliquePtr, CallSiteMap, RecursiveCallSiteMap,
|
|
ContainingGoalMap, CoverageArray, Var, VarUseOptions, set.init, RT,
|
|
CurDepth, no_recursion_info),
|
|
0.0, _, FoundFirstUse),
|
|
VarUseType = VarUseOptions ^ vuo_var_use_type,
|
|
found_first_use_to_use_info(FoundFirstUse, Cost, VarUseType, VarUseInfo).
|
|
|
|
:- pred found_first_use_to_use_info(found_first_use::in, float::in,
|
|
var_use_type::in, var_use_info::out) is det.
|
|
|
|
found_first_use_to_use_info(FoundFirstUse, Cost, VarUseType, VarUseInfo) :-
|
|
(
|
|
FoundFirstUse = found_first_use(CostUntilUse),
|
|
VarUseInfo = var_use_info(CostUntilUse, Cost, VarUseType)
|
|
;
|
|
FoundFirstUse = have_not_found_first_use,
|
|
% If the first use has not been found then:
|
|
% a) for productions: they must have been produced, this is an error.
|
|
% b) For consumptions: the compiler will insert a wait so any calls
|
|
% following this one can assume that a wait has already been
|
|
% performed.
|
|
(
|
|
VarUseType = var_use_production,
|
|
unexpected($pred,
|
|
"goal did not produce a variable that it should have")
|
|
;
|
|
VarUseType = var_use_consumption,
|
|
VarUseInfo = var_use_info(Cost, Cost, VarUseType)
|
|
;
|
|
VarUseType = var_use_other,
|
|
pessimistic_var_use_info(VarUseType, Cost, VarUseInfo)
|
|
)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred intermodule_var_use_should_follow_csd(var_use_options::in,
|
|
call_site_dynamic_ptr::in) is semidet.
|
|
|
|
intermodule_var_use_should_follow_csd(VarUseOptions, CSDPtr) :-
|
|
FollowCall = VarUseOptions ^ vuo_intermodule_var_use,
|
|
(
|
|
FollowCall = follow_calls_into_module(Module),
|
|
Deep = VarUseOptions ^ vuo_deep,
|
|
deep_lookup_call_site_dynamics(Deep, CSDPtr, CSD),
|
|
PDPtr = CSD ^ csd_callee,
|
|
intermodule_var_use_should_follow_pd_2(Deep, Module, PDPtr)
|
|
;
|
|
FollowCall = follow_any_call
|
|
).
|
|
|
|
:- pred intermodule_var_use_should_follow_pd(var_use_options::in,
|
|
proc_dynamic_ptr::in) is semidet.
|
|
|
|
intermodule_var_use_should_follow_pd(VarUseOptions, PDPtr) :-
|
|
FollowCall = VarUseOptions ^ vuo_intermodule_var_use,
|
|
(
|
|
FollowCall = follow_calls_into_module(Module),
|
|
Deep = VarUseOptions ^ vuo_deep,
|
|
intermodule_var_use_should_follow_pd_2(Deep, Module, PDPtr)
|
|
;
|
|
FollowCall = follow_any_call
|
|
).
|
|
|
|
:- pred intermodule_var_use_should_follow_pd_2(deep::in, string::in,
|
|
proc_dynamic_ptr::in) is semidet.
|
|
|
|
intermodule_var_use_should_follow_pd_2(Deep, Module, PDPtr) :-
|
|
deep_lookup_proc_dynamics(Deep, PDPtr, PD),
|
|
PSPtr = PD ^ pd_proc_static,
|
|
deep_lookup_proc_statics(Deep, PSPtr, PS),
|
|
Label = PS ^ ps_id,
|
|
( Label = str_ordinary_proc_label(_, _, Module, _, _, _)
|
|
; Label = str_special_proc_label(_, _, Module, _, _, _)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|