mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-13 04:44:39 +00:00
compiler/hlds_goal.m:
Change the names of the fields of the hlds_goal structure to avoid
conflicts with the names of the hlds_goal_expr and hlds_goal_info types.
compiler/coverage_profiling.m:
compiler/deep_profiling.m:
compiler/dep_par_conj.m:
compiler/goal_util.m:
compiler/higher_order.m:
compiler/introduce_parallelism.m:
compiler/middle_rec.m:
compiler/mode_ordering.m:
compiler/modecheck_conj.m:
compiler/modecheck_goal.m:
compiler/ordering_mode_constraints.m:
compiler/par_conj_gen.m:
compiler/par_loop_control.m:
compiler/prog_rep.m:
compiler/stm_expand.m:
compiler/term_constr_build.m:
compiler/tupling.m:
compiler/untupling.m:
Conform to the change above.
1379 lines
54 KiB
Mathematica
1379 lines
54 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2001-2012 The University of Melbourne.
|
|
% Copyright (C) 2015 The Mercury team.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% File: coverage_profiling.m.
|
|
% Main author: pbone.
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module ll_backend.coverage_profiling.
|
|
|
|
:- interface.
|
|
|
|
:- import_module hlds.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module hlds.hlds_module.
|
|
:- import_module hlds.hlds_pred.
|
|
:- import_module hlds.vartypes.
|
|
:- import_module mdbcomp.
|
|
:- import_module mdbcomp.goal_path.
|
|
:- import_module mdbcomp.program_representation.
|
|
|
|
:- import_module list.
|
|
:- import_module maybe.
|
|
|
|
% Perform the coverage profiling transformation on the given goal,
|
|
% and return a list of the coverage points created.
|
|
%
|
|
:- pred coverage_prof_transform_proc_body(module_info::in, pred_proc_id::in,
|
|
containing_goal_map::in, maybe(deep_recursion_info)::in,
|
|
hlds_goal::in, hlds_goal::out,
|
|
prog_var_set_types::in, prog_var_set_types::out,
|
|
list(coverage_point_info)::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module hlds.goal_util.
|
|
:- import_module hlds.instmap.
|
|
:- import_module libs.
|
|
:- import_module libs.globals.
|
|
:- import_module libs.options.
|
|
:- import_module ll_backend.deep_profiling.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.builtin_lib_types.
|
|
:- import_module parse_tree.prog_data.
|
|
:- import_module parse_tree.prog_data_foreign.
|
|
:- import_module parse_tree.set_of_var.
|
|
|
|
:- import_module assoc_list.
|
|
:- import_module bool.
|
|
:- import_module counter.
|
|
:- import_module map.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
:- import_module string.
|
|
|
|
% Information used by the coverage profiling transformation.
|
|
%
|
|
:- type proc_coverage_info
|
|
---> proc_coverage_info(
|
|
% A map from coverage point indexes to coverage point
|
|
% information. Updated as coverage points are inserted.
|
|
ci_coverage_points :: map(int, coverage_point_info),
|
|
|
|
% Used to track the next coverage point index to be allocated.
|
|
ci_cp_index_counter :: counter,
|
|
|
|
% Information about variables, this is updated as variables are
|
|
% introduced.
|
|
ci_var_info :: prog_var_set_types,
|
|
|
|
% The following fields are static; they are not modified
|
|
% after initialization.
|
|
ci_module_info :: module_info,
|
|
ci_pred_proc_id :: pred_proc_id,
|
|
ci_maybe_rec_info :: maybe(deep_recursion_info),
|
|
ci_coverage_profiling_opts :: coverage_profiling_options,
|
|
|
|
ci_containing_goal_map :: containing_goal_map
|
|
).
|
|
|
|
:- type coverage_data_type
|
|
---> static_coverage_data
|
|
; dynamic_coverage_data.
|
|
|
|
% Store what coverage profiling options have been selected.
|
|
%
|
|
:- type coverage_profiling_options
|
|
---> coverage_profiling_options(
|
|
% These fields correspond to coverage profiling options that
|
|
% may be specified on the command line.
|
|
|
|
% Use per ProcDynamic coverage rather than per ProcStatic.
|
|
cpo_dynamic_coverage :: coverage_data_type,
|
|
|
|
% Use calls to library code rather than inline foreign code.
|
|
cpo_use_calls :: bool,
|
|
|
|
cpo_coverage_after_goal :: bool,
|
|
cpo_branch_ite :: bool,
|
|
cpo_branch_switch :: bool,
|
|
cpo_branch_disj :: bool,
|
|
cpo_use_portcounts :: bool,
|
|
cpo_use_trivial :: bool,
|
|
|
|
% cpo_run_first_pass is true if some information needs to be
|
|
% collected in an initial pass.
|
|
cpo_run_first_pass :: bool
|
|
).
|
|
|
|
:- pred init_coverage_profiling_options(module_info::in,
|
|
coverage_profiling_options::out) is det.
|
|
|
|
init_coverage_profiling_options(ModuleInfo, CoveragePointOptions) :-
|
|
module_info_get_globals(ModuleInfo, Globals),
|
|
|
|
% Options controlling what instrumentation code we generate.
|
|
globals.lookup_bool_option(Globals, coverage_profiling_static,
|
|
Static),
|
|
(
|
|
Static = yes,
|
|
DataType = static_coverage_data
|
|
;
|
|
Static = no,
|
|
DataType = dynamic_coverage_data
|
|
),
|
|
globals.lookup_bool_option(Globals, coverage_profiling_via_calls,
|
|
UseCalls),
|
|
|
|
% Coverage point types.
|
|
globals.lookup_bool_option(Globals, profile_deep_coverage_after_goal,
|
|
CoverageAfterGoal),
|
|
globals.lookup_bool_option(Globals, profile_deep_coverage_branch_ite,
|
|
BranchIf),
|
|
globals.lookup_bool_option(Globals, profile_deep_coverage_branch_switch,
|
|
BranchSwitch),
|
|
globals.lookup_bool_option(Globals, profile_deep_coverage_branch_disj,
|
|
BranchDisj),
|
|
|
|
% Options for tuning the coverage profiling pass.
|
|
globals.lookup_bool_option(Globals, profile_deep_coverage_use_portcounts,
|
|
UsePortCounts),
|
|
globals.lookup_bool_option(Globals, profile_deep_coverage_use_trivial,
|
|
UseTrivial),
|
|
bool.or(UsePortCounts, UseTrivial, RunFirstPass),
|
|
|
|
CoveragePointOptions = coverage_profiling_options(DataType, UseCalls,
|
|
CoverageAfterGoal, BranchIf, BranchSwitch, BranchDisj,
|
|
UsePortCounts, UseTrivial, RunFirstPass).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
coverage_prof_transform_proc_body(ModuleInfo, PredProcId, ContainingGoalMap,
|
|
MaybeRecInfo, !Goal, !VarInfo, CoveragePoints) :-
|
|
init_coverage_profiling_options(ModuleInfo, CoverageProfilingOptions),
|
|
CoverageInfo0 = init_proc_coverage_info(!.VarInfo, ModuleInfo,
|
|
PredProcId, MaybeRecInfo, CoverageProfilingOptions, ContainingGoalMap),
|
|
RunFirstPass = CoverageProfilingOptions ^ cpo_run_first_pass,
|
|
(
|
|
RunFirstPass = yes,
|
|
coverage_prof_first_pass(CoverageProfilingOptions, !Goal,
|
|
port_counts_give_coverage_after, _)
|
|
;
|
|
RunFirstPass = no
|
|
),
|
|
coverage_prof_second_pass_goal(!Goal, coverage_before_known, _,
|
|
CoverageInfo0, CoverageInfo, _),
|
|
CoverageInfo ^ ci_coverage_points = CoveragePointsMap,
|
|
CoverageInfo ^ ci_var_info = !:VarInfo,
|
|
coverage_points_map_list(CoveragePointsMap, CoveragePoints).
|
|
|
|
% Transform a goal for coverage profiling. This is the second pass of
|
|
% the coverage profiling transformation, and it consists of several steps.
|
|
%
|
|
% Step 1: Apply transformation recursively.
|
|
%
|
|
% Step 2: Decide whether to insert a coverage point after this goal
|
|
% to measure how many times it succeeds.
|
|
%
|
|
% Step 3: Insert the coverage point if we decided to do so.
|
|
%
|
|
:- pred coverage_prof_second_pass_goal(hlds_goal::in, hlds_goal::out,
|
|
coverage_before_known::in, coverage_before_known::out,
|
|
proc_coverage_info::in, proc_coverage_info::out, bool::out) is det.
|
|
|
|
coverage_prof_second_pass_goal(Goal0, Goal,
|
|
CoverageBeforeKnown, NextCoverageBeforeKnown, !Info, AddedImpurity) :-
|
|
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
|
|
Detism = goal_info_get_determinism(GoalInfo0),
|
|
GoalId = goal_info_get_goal_id(GoalInfo0),
|
|
CPOptions = !.Info ^ ci_coverage_profiling_opts,
|
|
|
|
% Currently the first pass is unsupported, we don't make use of the
|
|
% information it provides in IsMDProfInst.
|
|
DPInfo = goal_info_get_dp_info(GoalInfo0),
|
|
DPInfo = dp_goal_info(IsMDProfInst, _MaybeDPCoverageInfo),
|
|
|
|
( if
|
|
IsMDProfInst = goal_is_not_mdprof_inst,
|
|
CoverageBeforeKnown = coverage_before_unknown
|
|
then
|
|
GoalId = goal_id(GoalNum),
|
|
UnknownMsg = string.format(
|
|
"Coverage information is unknown for goal_id %d\n", [i(GoalNum)]),
|
|
unexpected($module, $pred, UnknownMsg)
|
|
else
|
|
true
|
|
),
|
|
|
|
% Step 1.
|
|
%
|
|
% Apply transformation recursively.
|
|
(
|
|
(
|
|
GoalExpr0 = unify(_, _, _, _, _)
|
|
;
|
|
GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _)
|
|
% Even though the deep profiler creates a call site when these may
|
|
% call Mercury, the coverage propagation code cannot make use of
|
|
% the call site's port counts, since they measure how often
|
|
% Mercury is re-entered, not how often this call is made.
|
|
),
|
|
coverage_known_after_goal_with_detism(Detism,
|
|
CoverageBeforeKnown, NextCoverageBeforeKnown0),
|
|
AddedImpurityInner = no,
|
|
GoalExpr1 = GoalExpr0
|
|
;
|
|
(
|
|
GoalExpr0 = plain_call(_, _, _, BuiltinState, _, _),
|
|
(
|
|
BuiltinState = not_builtin,
|
|
GathersCoverageAfter = yes
|
|
;
|
|
BuiltinState = inline_builtin,
|
|
GathersCoverageAfter = no
|
|
)
|
|
;
|
|
GoalExpr0 = generic_call(GenericCall, _, _, _, _),
|
|
(
|
|
( GenericCall = higher_order(_, _, _, _)
|
|
; GenericCall = class_method(_, _, _, _)
|
|
),
|
|
GathersCoverageAfter = yes
|
|
;
|
|
( GenericCall = cast(_)
|
|
; GenericCall = event_call(_)
|
|
),
|
|
GathersCoverageAfter = no
|
|
)
|
|
),
|
|
(
|
|
GathersCoverageAfter = yes,
|
|
NextCoverageBeforeKnown0 = coverage_before_known
|
|
;
|
|
GathersCoverageAfter = no,
|
|
coverage_known_after_goal_with_detism(Detism,
|
|
CoverageBeforeKnown, NextCoverageBeforeKnown0)
|
|
),
|
|
AddedImpurityInner = no,
|
|
GoalExpr1 = GoalExpr0
|
|
;
|
|
GoalExpr0 = conj(ConjType, Goals0),
|
|
coverage_prof_second_pass_conj(ConjType, Goals0, Goals,
|
|
CoverageBeforeKnown, NextCoverageBeforeKnown0, !Info,
|
|
AddedImpurityInner),
|
|
GoalExpr1 = conj(ConjType, Goals)
|
|
;
|
|
GoalExpr0 = disj(Goals0),
|
|
coverage_prof_second_pass_disj(DPInfo,
|
|
CoverageBeforeKnown, NextCoverageBeforeKnown0,
|
|
Goals0, Goals, !Info, AddedImpurityInner),
|
|
GoalExpr1 = disj(Goals)
|
|
;
|
|
GoalExpr0 = switch(Var, SwitchCanFail, Cases0),
|
|
coverage_prof_second_pass_switchcase(DPInfo, SwitchCanFail,
|
|
Cases0, Cases, CoverageBeforeKnown, NextCoverageBeforeKnown0,
|
|
!Info, AddedImpurityInner),
|
|
GoalExpr1 = switch(Var, SwitchCanFail, Cases)
|
|
;
|
|
GoalExpr0 = negation(NegGoal0),
|
|
coverage_prof_second_pass_goal(NegGoal0, NegGoal,
|
|
CoverageBeforeKnown, _, !Info, AddedImpurityInner),
|
|
% The coverage after a negated goal cannot be inferred from its inner
|
|
% goals.
|
|
NextCoverageBeforeKnown0 = coverage_before_unknown,
|
|
GoalExpr1 = negation(NegGoal)
|
|
;
|
|
GoalExpr0 = scope(Reason, ScopeGoal0),
|
|
% We should special-case the handling of from_ground_term_construct
|
|
% scopes, but that would require special-casing the coverage
|
|
% propagation code in the deep profiler as well.
|
|
coverage_prof_second_pass_goal(ScopeGoal0, ScopeGoal,
|
|
CoverageBeforeKnown, CoverageAfterScopedGoalKnown, !Info,
|
|
AddedImpurityInner),
|
|
% A scope may cut away solutions, if it does we don't know the number
|
|
% of solutions of the scoped goal.
|
|
ScopedGoalDetism = goal_info_get_determinism(ScopeGoal0 ^ hg_info),
|
|
( if ScopedGoalDetism = Detism then
|
|
NextCoverageBeforeKnown0 = CoverageAfterScopedGoalKnown
|
|
else
|
|
NextCoverageBeforeKnown0 = coverage_before_unknown
|
|
),
|
|
GoalExpr1 = scope(Reason, ScopeGoal)
|
|
;
|
|
GoalExpr0 = if_then_else(ITEExistVars, Cond, Then, Else),
|
|
coverage_prof_second_pass_ite(DPInfo, ITEExistVars, Cond, Then, Else,
|
|
GoalExpr1, CoverageBeforeKnown, NextCoverageBeforeKnown0, !Info,
|
|
AddedImpurityInner)
|
|
;
|
|
GoalExpr0 = shorthand(_),
|
|
unexpected($module, $pred, "shorthand")
|
|
),
|
|
|
|
% Step 2.
|
|
%
|
|
% Decide whether we need to insert a coverage point after this goal
|
|
% to measure how many times execution reaches there.
|
|
( if
|
|
(
|
|
% Never insert coverage points on goals that are part of the deep
|
|
% profiling instrumentation.
|
|
IsMDProfInst = goal_is_mdprof_inst
|
|
;
|
|
% We already have execution counts for the program point after this
|
|
% goal; adding a counter would be redundant.
|
|
NextCoverageBeforeKnown0 = coverage_before_known
|
|
)
|
|
then
|
|
MaybeAddCP = no,
|
|
NextCoverageBeforeKnown = NextCoverageBeforeKnown0
|
|
else
|
|
CoverageAfterGoals = CPOptions ^ cpo_coverage_after_goal,
|
|
(
|
|
CoverageAfterGoals = yes,
|
|
MaybeAddCP = yes(cp_type_coverage_after),
|
|
NextCoverageBeforeKnown = coverage_before_known
|
|
;
|
|
CoverageAfterGoals = no,
|
|
MaybeAddCP = no,
|
|
NextCoverageBeforeKnown = NextCoverageBeforeKnown0
|
|
)
|
|
),
|
|
|
|
% Step 3.
|
|
%
|
|
% Insert the coverage point if we decided to.
|
|
add_impurity_if_needed(AddedImpurityInner, GoalInfo0, GoalInfo1),
|
|
Goal1 = hlds_goal(GoalExpr1, GoalInfo1),
|
|
(
|
|
MaybeAddCP = yes(CPType),
|
|
ContainingGoalMap = !.Info ^ ci_containing_goal_map,
|
|
RevGoalPath = goal_id_to_reverse_path(ContainingGoalMap, GoalId),
|
|
CPInfo = coverage_point_info(RevGoalPath, CPType),
|
|
|
|
make_coverage_point(CPOptions, CPInfo, CPGoals, !Info),
|
|
create_conj_from_list([Goal1 | CPGoals], plain_conj, Goal),
|
|
|
|
AddedImpurity = yes
|
|
;
|
|
MaybeAddCP = no,
|
|
Goal = Goal1,
|
|
AddedImpurity = AddedImpurityInner
|
|
).
|
|
|
|
:- pred coverage_known_after_goal_with_detism(determinism::in,
|
|
coverage_before_known::in, coverage_before_known::out) is det.
|
|
|
|
coverage_known_after_goal_with_detism(Detism, !CoverageKnown) :-
|
|
(
|
|
( Detism = detism_semi
|
|
; Detism = detism_multi
|
|
; Detism = detism_non
|
|
; Detism = detism_cc_non
|
|
; Detism = detism_erroneous
|
|
; Detism = detism_failure
|
|
),
|
|
!:CoverageKnown = coverage_before_unknown
|
|
;
|
|
( Detism = detism_det
|
|
; Detism = detism_cc_multi
|
|
)
|
|
).
|
|
|
|
% Perform the coverage profiling transformation for conjuncts.
|
|
%
|
|
% The goal list represents the tail of a conjunction. Pos is the position
|
|
% of this list within the entire conjunction, if this is the entire
|
|
% conjunction then Pos should be 1.
|
|
%
|
|
:- pred coverage_prof_second_pass_conj(conj_type::in,
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
coverage_before_known::in, coverage_before_known::out,
|
|
proc_coverage_info::in, proc_coverage_info::out, bool::out) is det.
|
|
|
|
coverage_prof_second_pass_conj(_, [], [], !CoverageBeforeKnown, !Info, no).
|
|
coverage_prof_second_pass_conj(ConjType, [HeadGoal0 | TailGoals0], Goals,
|
|
CoverageBeforeKnown, NextCoverageBeforeKnown, !Info, AddedImpurity) :-
|
|
coverage_prof_second_pass_goal(HeadGoal0, HeadGoal,
|
|
CoverageBeforeKnown, CoverageBeforeTailKnown, !Info,
|
|
AddedImpurityHead),
|
|
coverage_prof_second_pass_conj(ConjType, TailGoals0, TailGoals,
|
|
CoverageBeforeTailKnown, NextCoverageBeforeKnown, !Info,
|
|
AddedImpurityTail),
|
|
% Flatten the conjunction. We need to do this if we replaced the head
|
|
% with a goal that is itself a conjunction.
|
|
( if
|
|
HeadGoal = hlds_goal(conj(plain_conj, HeadConjGoals), _),
|
|
ConjType = plain_conj
|
|
then
|
|
Goals = HeadConjGoals ++ TailGoals
|
|
else
|
|
Goals = [HeadGoal | TailGoals]
|
|
),
|
|
bool.or(AddedImpurityHead, AddedImpurityTail, AddedImpurity).
|
|
|
|
% Perform the coverage profiling transformation over goals within a
|
|
% disjunction.
|
|
%
|
|
:- pred coverage_prof_second_pass_disj(dp_goal_info::in,
|
|
coverage_before_known::in, coverage_before_known::out,
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
proc_coverage_info::in, proc_coverage_info::out, bool::out) is det.
|
|
|
|
coverage_prof_second_pass_disj(DPInfo, CoverageBeforeKnown,
|
|
NextCoverageBeforeKnown, Disjuncts0, Disjuncts, !Info,
|
|
AddedImpurity) :-
|
|
% If the disjunction was introduced by the deep profiling pass, it has two
|
|
% disjuncts and its second disjunct has 'failure' determinism, then
|
|
% perform the coverage profiling pass on the first disjunct as if this
|
|
% is the only goal.
|
|
( if
|
|
DPInfo = dp_goal_info(goal_is_mdprof_inst, _),
|
|
Disjuncts0 = [FirstDisjunct0, SecondDisjunct],
|
|
goal_info_get_determinism(SecondDisjunct ^ hg_info) = detism_failure
|
|
% XXX: zs: Would this be a better test here?
|
|
% goal_has_feature(SecondDisjunct, feature_preserve_backtrack_into)
|
|
% pbone: I don't think so, the deep profiler doesn't seem to add this
|
|
% feature to disjuncts that end in failure, it is probably a good idea
|
|
% to add this annotation to prevent later compiler passes from breaking
|
|
% the deep profiler.
|
|
then
|
|
coverage_prof_second_pass_goal(FirstDisjunct0, FirstDisjunct,
|
|
CoverageBeforeKnown, NextCoverageBeforeKnown, !Info,
|
|
AddedImpurity),
|
|
Disjuncts = [FirstDisjunct, SecondDisjunct]
|
|
else
|
|
coverage_prof_second_pass_disj_2(DPInfo, CoverageBeforeKnown,
|
|
coverage_before_known, NextCoverageBeforeKnown,
|
|
Disjuncts0, Disjuncts, !Info, AddedImpurity)
|
|
).
|
|
|
|
:- pred coverage_prof_second_pass_disj_2(dp_goal_info::in,
|
|
coverage_before_known::in,
|
|
coverage_before_known::in, coverage_before_known::out,
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
proc_coverage_info::in, proc_coverage_info::out, bool::out) is det.
|
|
|
|
coverage_prof_second_pass_disj_2(_, _, !CoverageKnownAfter, [], [], !Info, no).
|
|
coverage_prof_second_pass_disj_2(DPInfo,
|
|
CoverageBeforeKnown0, !CoverageAfterKnown,
|
|
[HeadDisjunct0 | TailDisjuncts0], [HeadDisjunct | TailDisjuncts],
|
|
!Info, AddedImpurity) :-
|
|
% Decide whether we want to insert a branch coverage point at the beginning
|
|
% of the head disjunct.
|
|
CPOptions = !.Info ^ ci_coverage_profiling_opts,
|
|
CPOBranchDisj = CPOptions ^ cpo_branch_disj,
|
|
DPInfo = dp_goal_info(IsMDProfInst, _),
|
|
( if
|
|
CPOBranchDisj = yes,
|
|
CoverageBeforeKnown0 = coverage_before_unknown,
|
|
IsMDProfInst = goal_is_not_mdprof_inst
|
|
then
|
|
InsertCP = yes,
|
|
CoverageBeforeKnown = coverage_before_known
|
|
else
|
|
InsertCP = no,
|
|
CoverageBeforeKnown = CoverageBeforeKnown0
|
|
),
|
|
|
|
coverage_prof_second_pass_goal(HeadDisjunct0, HeadDisjunct1,
|
|
CoverageBeforeKnown, CoverageAfterDisjKnown, !Info, AddedImpurityHead),
|
|
!:CoverageAfterKnown = coverage_before_known_and(!.CoverageAfterKnown,
|
|
CoverageAfterDisjKnown),
|
|
coverage_prof_second_pass_disj_2(DPInfo, coverage_before_unknown,
|
|
!CoverageAfterKnown, TailDisjuncts0, TailDisjuncts, !Info,
|
|
AddedImpurityTail),
|
|
|
|
% Insert the coverage point if we decided to above.
|
|
(
|
|
InsertCP = yes,
|
|
DisjId = goal_info_get_goal_id(HeadDisjunct0 ^ hg_info),
|
|
ContainingGoalMap = !.Info ^ ci_containing_goal_map,
|
|
DisjPath = goal_id_to_reverse_path(ContainingGoalMap, DisjId),
|
|
HeadCoveragePoint = coverage_point_info(DisjPath, cp_type_branch_arm),
|
|
insert_coverage_point_before(CPOptions, HeadCoveragePoint,
|
|
HeadDisjunct1, HeadDisjunct, !Info),
|
|
AddedImpurity = yes
|
|
;
|
|
InsertCP = no,
|
|
HeadDisjunct = HeadDisjunct1,
|
|
AddedImpurity = bool.or(AddedImpurityHead, AddedImpurityTail)
|
|
).
|
|
|
|
% coverage_prof_second_pass_switchcase(DPInfo, SwitchCanFial, !Cases,
|
|
% CoverageBeforeSwitch, !Info, AddedImpurity).
|
|
%
|
|
% Preform coverage profiling transformation on switch cases.
|
|
%
|
|
:- pred coverage_prof_second_pass_switchcase(dp_goal_info::in, can_fail::in,
|
|
list(case)::in, list(case)::out,
|
|
coverage_before_known::in, coverage_before_known::out,
|
|
proc_coverage_info::in, proc_coverage_info::out, bool::out) is det.
|
|
|
|
coverage_prof_second_pass_switchcase(DPInfo, CanFail, Cases0, Cases,
|
|
CoverageBeforeSwitchKnown, CoverageAfterSwitchKnown, !Info,
|
|
AddedImpurity) :-
|
|
% If the switch can fail then the coverage after it will be unknown.
|
|
(
|
|
CanFail = can_fail,
|
|
CoverageAfterSwitchKnown0 = coverage_before_unknown
|
|
;
|
|
CanFail = cannot_fail,
|
|
CoverageAfterSwitchKnown0 = coverage_before_known
|
|
),
|
|
CoverageBeforeEveryCaseKnown = coverage_before_known,
|
|
coverage_prof_second_pass_switchcase_2(DPInfo, CanFail, Cases0, Cases,
|
|
CoverageBeforeSwitchKnown, CoverageBeforeEveryCaseKnown,
|
|
CoverageAfterSwitchKnown0, CoverageAfterSwitchKnown, !Info,
|
|
AddedImpurity).
|
|
|
|
:- pred coverage_prof_second_pass_switchcase_2(dp_goal_info::in, can_fail::in,
|
|
list(case)::in, list(case)::out, coverage_before_known::in,
|
|
coverage_before_known::in,
|
|
coverage_before_known::in, coverage_before_known::out,
|
|
proc_coverage_info::in, proc_coverage_info::out, bool::out) is det.
|
|
|
|
coverage_prof_second_pass_switchcase_2(_, _, [], [], _, _,
|
|
!CoverageAfterSwitchKnown, !Info, no).
|
|
coverage_prof_second_pass_switchcase_2(DPInfo, SwitchCanFail,
|
|
[Case0 | Cases0], [Case | Cases], CoverageBeforeSwitchKnown,
|
|
CoverageBeforeEveryCaseKnown, !CoverageAfterSwitchKnown, !Info,
|
|
AddedImpurity) :-
|
|
Case0 = case(MainConsId, OtherConsIds, Goal0),
|
|
|
|
% If the switch cannot fail and this is the last case, then the coverage
|
|
% at the beginning of this case can be computed from the coverage before
|
|
% the entire switch and coverage information from each of the other
|
|
% branches of the switch.
|
|
(
|
|
Cases0 = [],
|
|
(
|
|
SwitchCanFail = cannot_fail,
|
|
CoverageBeforeCaseKnown0 = coverage_before_known_and(
|
|
CoverageBeforeSwitchKnown, CoverageBeforeEveryCaseKnown)
|
|
;
|
|
SwitchCanFail = can_fail,
|
|
CoverageBeforeCaseKnown0 = coverage_before_unknown
|
|
)
|
|
;
|
|
Cases0 = [_ | _],
|
|
CoverageBeforeCaseKnown0 = coverage_before_unknown
|
|
),
|
|
|
|
% Decide whether to insert a coverage point here.
|
|
CPOptions = !.Info ^ ci_coverage_profiling_opts,
|
|
CPOBranchSwitch = CPOptions ^ cpo_branch_switch,
|
|
DPInfo = dp_goal_info(IsMDProfInst, _),
|
|
( if
|
|
CPOBranchSwitch = yes,
|
|
CoverageBeforeCaseKnown0 = coverage_before_unknown,
|
|
IsMDProfInst = goal_is_not_mdprof_inst
|
|
then
|
|
InsertCP = yes,
|
|
CoverageBeforeCaseKnown = coverage_before_known
|
|
else
|
|
InsertCP = no,
|
|
CoverageBeforeCaseKnown = CoverageBeforeCaseKnown0
|
|
),
|
|
|
|
coverage_prof_second_pass_goal(Goal0, Goal1,
|
|
CoverageBeforeCaseKnown, CoverageAfterCaseKnown, !Info,
|
|
AddedImpurityHead0),
|
|
!:CoverageAfterSwitchKnown = coverage_before_known_and(
|
|
CoverageAfterCaseKnown, !.CoverageAfterSwitchKnown),
|
|
|
|
% Possibly insert coverage point at the start of the case.
|
|
(
|
|
InsertCP = yes,
|
|
CaseId = goal_info_get_goal_id(Goal0 ^ hg_info),
|
|
ContainingGoalMap = !.Info ^ ci_containing_goal_map,
|
|
CasePath = goal_id_to_reverse_path(ContainingGoalMap, CaseId),
|
|
CoveragePoint = coverage_point_info(CasePath, cp_type_branch_arm),
|
|
insert_coverage_point_before(CPOptions, CoveragePoint, Goal1, Goal,
|
|
!Info),
|
|
AddedImpurityHead = yes
|
|
;
|
|
InsertCP = no,
|
|
Goal = Goal1,
|
|
AddedImpurityHead = AddedImpurityHead0
|
|
),
|
|
|
|
% Handle recursive case and prepare output variables.
|
|
% We cannot optimize away the coverage point at the start of the last case
|
|
% if one of the previous cases does not have coverage information at its
|
|
% start.
|
|
NextCoverageBeforeEveryCaseKnown = coverage_before_known_and(
|
|
CoverageBeforeEveryCaseKnown, CoverageBeforeCaseKnown),
|
|
coverage_prof_second_pass_switchcase_2(DPInfo, SwitchCanFail,
|
|
Cases0, Cases,
|
|
CoverageBeforeSwitchKnown, NextCoverageBeforeEveryCaseKnown,
|
|
!CoverageAfterSwitchKnown, !Info, AddedImpurityTail),
|
|
Case = case(MainConsId, OtherConsIds, Goal),
|
|
bool.or(AddedImpurityHead, AddedImpurityTail, AddedImpurity).
|
|
|
|
% Determine if branch coverage points should be inserted in either or
|
|
% both of the then and else branches, insert them and transform the
|
|
% subgoals.
|
|
%
|
|
% This is performed by first transforming the condition, then making
|
|
% decisions about coverage points and inserting them, then transforming
|
|
% the then and else branches and constructing the new ITE goal_expr.
|
|
%
|
|
:- pred coverage_prof_second_pass_ite(dp_goal_info::in, list(prog_var)::in,
|
|
hlds_goal::in, hlds_goal::in, hlds_goal::in, hlds_goal_expr::out,
|
|
coverage_before_known::in, coverage_before_known::out,
|
|
proc_coverage_info::in, proc_coverage_info::out, bool::out) is det.
|
|
|
|
coverage_prof_second_pass_ite(DPInfo, ITEExistVars, Cond0, Then0, Else0,
|
|
GoalExpr, CoverageBeforeITEKnown, NextCoverageBeforeKnown,
|
|
!Info, AddedImpurity) :-
|
|
% Transform the condition.
|
|
coverage_prof_second_pass_goal(Cond0, Cond,
|
|
CoverageBeforeITEKnown, CoverageKnownAfterCond, !Info,
|
|
AddedImpurityCond),
|
|
|
|
CoverageKnownBeforeThen0 = CoverageKnownAfterCond,
|
|
CoverageKnownBeforeElse0 = coverage_before_unknown,
|
|
|
|
% Gather information and decide what coverage points to insert.
|
|
%
|
|
% Notice that it doesn't matter if any of the goals are trivial or not,
|
|
% we want to know what branch is taken regardless of how inexpensive it
|
|
% may be as different variables may be used in different branches.
|
|
%
|
|
% Whatever we do we will ensure that the coverage will be known at the
|
|
% beginning of each branch.
|
|
CPOptions = !.Info ^ ci_coverage_profiling_opts,
|
|
CPOBranchIf = CPOptions ^ cpo_branch_ite,
|
|
DPInfo = dp_goal_info(IsMDProfInst, _),
|
|
( if
|
|
CPOBranchIf = yes,
|
|
IsMDProfInst = goal_is_not_mdprof_inst
|
|
then
|
|
ContainingGoalMap = !.Info ^ ci_containing_goal_map,
|
|
(
|
|
CoverageKnownBeforeThen0 = coverage_before_unknown,
|
|
ThenId = goal_info_get_goal_id(Then0 ^ hg_info),
|
|
ThenPath = goal_id_to_reverse_path(ContainingGoalMap, ThenId),
|
|
InsertCPThen = yes(coverage_point_info(ThenPath,
|
|
cp_type_branch_arm))
|
|
;
|
|
CoverageKnownBeforeThen0 = coverage_before_known,
|
|
InsertCPThen = no
|
|
),
|
|
CoverageKnownBeforeThen = coverage_before_known,
|
|
|
|
ElseId = goal_info_get_goal_id(Else0 ^ hg_info),
|
|
ElsePath = goal_id_to_reverse_path(ContainingGoalMap, ElseId),
|
|
CondDetism = goal_info_get_determinism(Cond ^ hg_info),
|
|
determinism_components(CondDetism, _, CondSolns),
|
|
(
|
|
CondSolns = at_most_many,
|
|
|
|
% Always insert a coverage point for the else branch.
|
|
InsertCPElse = yes(coverage_point_info(ElsePath,
|
|
cp_type_branch_arm)),
|
|
CoverageKnownBeforeElse = coverage_before_known
|
|
;
|
|
( CondSolns = at_most_zero
|
|
; CondSolns = at_most_one
|
|
; CondSolns = at_most_many_cc
|
|
),
|
|
|
|
% Only insert a coverage point if we cannot infer the coverage
|
|
% from before the ITE and before the then branch.
|
|
( if
|
|
CoverageBeforeITEKnown = coverage_before_known,
|
|
CoverageKnownBeforeThen = coverage_before_known
|
|
then
|
|
InsertCPElse = no,
|
|
CoverageKnownBeforeElse = coverage_before_known
|
|
else
|
|
InsertCPElse = yes(coverage_point_info(ElsePath,
|
|
cp_type_branch_arm)),
|
|
CoverageKnownBeforeElse = coverage_before_known
|
|
)
|
|
)
|
|
else
|
|
% Don't insert any coverage points.
|
|
InsertCPThen = no,
|
|
InsertCPElse = no,
|
|
CoverageKnownBeforeThen = CoverageKnownBeforeThen0,
|
|
CoverageKnownBeforeElse = CoverageKnownBeforeElse0
|
|
),
|
|
|
|
% Transform Then and Else branches,
|
|
coverage_prof_second_pass_goal(Then0, Then1,
|
|
CoverageKnownBeforeThen, NextCoverageKnownThen, !Info,
|
|
AddedImpurityThenGoal),
|
|
coverage_prof_second_pass_goal(Else0, Else1,
|
|
CoverageKnownBeforeElse, NextCoverageKnownElse, !Info,
|
|
AddedImpurityElseGoal),
|
|
|
|
% Insert any coverage points.
|
|
(
|
|
InsertCPThen = yes(CPInfoThen),
|
|
insert_coverage_point_before(CPOptions, CPInfoThen, Then1, Then,
|
|
!Info),
|
|
AddedImpurityThen = yes
|
|
;
|
|
InsertCPThen = no,
|
|
Then = Then1,
|
|
AddedImpurityThen = AddedImpurityThenGoal
|
|
),
|
|
(
|
|
InsertCPElse = yes(CPInfoElse),
|
|
insert_coverage_point_before(CPOptions, CPInfoElse, Else1, Else,
|
|
!Info),
|
|
AddedImpurityElse = yes
|
|
;
|
|
InsertCPElse = no,
|
|
Else = Else1,
|
|
AddedImpurityElse = AddedImpurityElseGoal
|
|
),
|
|
|
|
% Build goal expression and tidy up.
|
|
AddedImpurity = bool.or(AddedImpurityCond,
|
|
bool.or(AddedImpurityThen, AddedImpurityElse)),
|
|
GoalExpr = if_then_else(ITEExistVars, Cond, Then, Else),
|
|
NextCoverageBeforeKnown = coverage_before_known_and(
|
|
NextCoverageKnownThen, NextCoverageKnownElse).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Create a coverage info struture, initializing some values to sensible
|
|
% defaults.
|
|
%
|
|
:- func init_proc_coverage_info(prog_var_set_types, module_info, pred_proc_id,
|
|
maybe(deep_recursion_info), coverage_profiling_options,
|
|
containing_goal_map) = proc_coverage_info.
|
|
|
|
init_proc_coverage_info(VarInfo, ModuleInfo, PredProcId, MaybeRecInfo,
|
|
CoverageProfilingOptions, ContainingGoalMap) = CoverageInfo :-
|
|
CoverageInfo = proc_coverage_info(map.init, counter.init(0), VarInfo,
|
|
ModuleInfo, PredProcId, MaybeRecInfo, CoverageProfilingOptions,
|
|
ContainingGoalMap).
|
|
|
|
% Used to describe if coverage information is known at a partiular point
|
|
% within a procedure.
|
|
%
|
|
:- type coverage_before_known
|
|
---> coverage_before_known
|
|
; coverage_before_unknown.
|
|
|
|
% The logical 'and' of coverage_before_known values.
|
|
:- func coverage_before_known_and(coverage_before_known, coverage_before_known)
|
|
= coverage_before_known.
|
|
|
|
coverage_before_known_and(coverage_before_known, coverage_before_known) =
|
|
coverage_before_known.
|
|
coverage_before_known_and(coverage_before_known, coverage_before_unknown) =
|
|
coverage_before_unknown.
|
|
coverage_before_known_and(coverage_before_unknown, _) =
|
|
coverage_before_unknown.
|
|
|
|
% Boolean AND for the goal_trivial data type.
|
|
%
|
|
:- pred goal_trivial_and(goal_trivial::in, goal_trivial::in,
|
|
goal_trivial::out) is det.
|
|
|
|
goal_trivial_and(A, B, Trivial) :-
|
|
( if
|
|
A = goal_is_trivial,
|
|
B = goal_is_trivial
|
|
then
|
|
Trivial = goal_is_trivial
|
|
else
|
|
Trivial = goal_is_nontrivial
|
|
).
|
|
|
|
:- pred port_counts_give_coverage_after_and(
|
|
port_counts_give_coverage_after::in, port_counts_give_coverage_after::in,
|
|
port_counts_give_coverage_after::out) is det.
|
|
|
|
port_counts_give_coverage_after_and(A, B, PortCountsCoverageAfter) :-
|
|
( if
|
|
A = port_counts_give_coverage_after,
|
|
B = port_counts_give_coverage_after
|
|
then
|
|
PortCountsCoverageAfter = port_counts_give_coverage_after
|
|
else
|
|
PortCountsCoverageAfter = no_port_counts_give_coverage_after
|
|
).
|
|
|
|
% Given a goal, whether it has its own port counts and whether port counts
|
|
% are available immediately before it, determine if either set of port
|
|
% counts allows us to determine how often execution reaches the point
|
|
% immediately after the goal.
|
|
%
|
|
:- pred has_port_counts_after(hlds_goal::in,
|
|
port_counts_give_coverage_after::in, port_counts_give_coverage_after::in,
|
|
port_counts_give_coverage_after::out) is det.
|
|
|
|
has_port_counts_after(Goal, PCDirect, PCBefore, PC) :-
|
|
(
|
|
% The trivial case. If port counts are directly available,
|
|
% then they can be used to determine coverage immediately after it.
|
|
|
|
PCDirect = port_counts_give_coverage_after,
|
|
PC = port_counts_give_coverage_after
|
|
;
|
|
PCDirect = no_port_counts_give_coverage_after,
|
|
|
|
% If port counts aren't directly available but are before this goal
|
|
% and this goal behaves deterministically (it cannot fail or redo),
|
|
% then they can be used to determine how often execution reaches the
|
|
% point after this goal.
|
|
|
|
Detism = goal_info_get_determinism(Goal ^ hg_info),
|
|
has_port_counts_if_det(Detism, PCBefore, PC)
|
|
).
|
|
|
|
% Given the current goal's determinism and whether the next earliest goal
|
|
% has port counts does this goal have port counts
|
|
%
|
|
:- pred has_port_counts_if_det(determinism::in,
|
|
port_counts_give_coverage_after::in, port_counts_give_coverage_after::out)
|
|
is det.
|
|
|
|
has_port_counts_if_det(Detism,
|
|
PortCountsCoverageAfter0, PortCountsCoverageAfter) :-
|
|
( if
|
|
( Detism = detism_det
|
|
; Detism = detism_cc_multi
|
|
)
|
|
then
|
|
PortCountsCoverageAfter = PortCountsCoverageAfter0
|
|
else
|
|
PortCountsCoverageAfter = no_port_counts_give_coverage_after
|
|
).
|
|
|
|
% Used to gather some information about goals before the coverage
|
|
% transformation.
|
|
%
|
|
% This pass gathers the information in the dp_coverage_goal_info structure,
|
|
% namely
|
|
%
|
|
% - whether the goal is trivial (a goal is trivial if neither it
|
|
% nor any of its subgoals are calls), and
|
|
% - whether a port count is available from the deep profiler from which
|
|
% the coverage _after_ this goal can be computed.
|
|
%
|
|
% XXX: Currently the first pass is unsupported. The second pass does not
|
|
% use the information it generates.
|
|
%
|
|
:- pred coverage_prof_first_pass(coverage_profiling_options::in, hlds_goal::in,
|
|
hlds_goal::out, port_counts_give_coverage_after::in,
|
|
dp_coverage_goal_info::out) is det.
|
|
|
|
coverage_prof_first_pass(CPOptions, Goal0, Goal, PortCountsCoverageAfterBefore,
|
|
Info) :-
|
|
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
|
|
(
|
|
% XXX: Not all call goals have associated call sites, therefore not all
|
|
% of these will have port counts. For, example inline foreign code does
|
|
% not get instrumented by the deep profiler. See above in the
|
|
% deep_profiling transformation.
|
|
%
|
|
% This doesn't matter for the near future, since we are using a single
|
|
% pass coverage profiling algorithm. This will need to be fixed when
|
|
% the two-pass coverage profiling is enabled. Or if a naive assumption
|
|
% in the second pass is corrected, (See the XXX comment at the
|
|
% beginning of coverage_prof_second_pass_goal regarding the defaults
|
|
% that are assumed if the information from the first pass is not
|
|
% available.).
|
|
%
|
|
GoalExpr0 = plain_call(_, _, _, BuiltinState, _, _),
|
|
(
|
|
BuiltinState = not_builtin,
|
|
Trivial0 = goal_is_nontrivial,
|
|
PortCountsCoverageAfterDirect = port_counts_give_coverage_after
|
|
;
|
|
BuiltinState = inline_builtin,
|
|
Trivial0 = goal_is_trivial,
|
|
PortCountsCoverageAfterDirect = no_port_counts_give_coverage_after
|
|
),
|
|
GoalExpr = GoalExpr0
|
|
;
|
|
GoalExpr0 = generic_call(GenericCall, _, _, _, _),
|
|
(
|
|
( GenericCall = higher_order(_, _, _, _)
|
|
; GenericCall = class_method(_, _, _, _)
|
|
),
|
|
Trivial0 = goal_is_nontrivial,
|
|
PortCountsCoverageAfterDirect = port_counts_give_coverage_after
|
|
;
|
|
( GenericCall = cast(_)
|
|
; GenericCall = event_call(_)
|
|
),
|
|
Trivial0 = goal_is_trivial,
|
|
PortCountsCoverageAfterDirect = no_port_counts_give_coverage_after
|
|
),
|
|
GoalExpr = GoalExpr0
|
|
;
|
|
GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _),
|
|
% Some foreign proc goals may be trivial., but there is no clear
|
|
% criteria by which we can conclude that here.
|
|
Trivial0 = goal_is_nontrivial,
|
|
PortCountsCoverageAfterDirect = no_port_counts_give_coverage_after,
|
|
GoalExpr = GoalExpr0
|
|
;
|
|
GoalExpr0 = unify(_, _, _, _, _),
|
|
Trivial0 = goal_is_trivial,
|
|
PortCountsCoverageAfterDirect = no_port_counts_give_coverage_after,
|
|
GoalExpr = GoalExpr0
|
|
;
|
|
GoalExpr0 = conj(ConjType, Goals0),
|
|
map_foldl2(coverage_prof_first_pass_conj(CPOptions), Goals0, Goals,
|
|
goal_is_trivial, Trivial0,
|
|
PortCountsCoverageAfterBefore, PortCountsCoverageAfterDirect),
|
|
GoalExpr = conj(ConjType, Goals)
|
|
;
|
|
GoalExpr0 = disj(Goals0),
|
|
coverage_prof_first_pass_disj(CPOptions, Goals0, Goals, Trivial0,
|
|
PortCountsCoverageAfterBefore, PortCountsCoverageAfterDirect),
|
|
GoalExpr = disj(Goals)
|
|
;
|
|
GoalExpr0 = switch(Var, CanFail, Cases0),
|
|
coverage_prof_first_pass_switchcase(CPOptions, Cases0, Cases, Trivial0,
|
|
PortCountsCoverageAfterCases),
|
|
GoalExpr = switch(Var, CanFail, Cases),
|
|
(
|
|
CanFail = can_fail,
|
|
PortCountsCoverageAfterDirect = no_port_counts_give_coverage_after
|
|
;
|
|
CanFail = cannot_fail,
|
|
PortCountsCoverageAfterDirect = PortCountsCoverageAfterCases
|
|
)
|
|
;
|
|
GoalExpr0 = negation(InnerGoal0),
|
|
coverage_prof_first_pass(CPOptions, InnerGoal0, InnerGoal,
|
|
PortCountsCoverageAfterBefore,
|
|
dp_coverage_goal_info(Trivial0, PortCountsCoverageAfterDirect)),
|
|
GoalExpr = negation(InnerGoal)
|
|
;
|
|
GoalExpr0 = scope(Reason, InnerGoal0),
|
|
% We should special-case the handling of from_ground_term_construct
|
|
% scopes, but that would require special-casing the coverage
|
|
% propagation code in the deep profiler as well.
|
|
coverage_prof_first_pass(CPOptions, InnerGoal0, InnerGoal,
|
|
PortCountsCoverageAfterBefore,
|
|
dp_coverage_goal_info(Trivial0, PortCountsCoverageAfterDirect)),
|
|
GoalExpr = scope(Reason, InnerGoal)
|
|
;
|
|
GoalExpr0 = if_then_else(Vars, CondGoal0, ThenGoal0, ElseGoal0),
|
|
|
|
% The then and else parts of a ITE goal will be able to use the
|
|
% port counts provided by the cond goal if it has them.
|
|
|
|
coverage_prof_first_pass(CPOptions, CondGoal0, CondGoal,
|
|
PortCountsCoverageAfterBefore,
|
|
dp_coverage_goal_info(TrivialCond, PortCountsCoverageAfterCond)),
|
|
|
|
coverage_prof_first_pass(CPOptions, ThenGoal0, ThenGoal,
|
|
PortCountsCoverageAfterCond,
|
|
dp_coverage_goal_info(TrivialThen, PortCountsCoverageAfterThen)),
|
|
coverage_prof_first_pass(CPOptions, ElseGoal0, ElseGoal,
|
|
PortCountsCoverageAfterCond,
|
|
dp_coverage_goal_info(TrivialElse, PortCountsCoverageAfterElse)),
|
|
|
|
GoalExpr = if_then_else(Vars, CondGoal, ThenGoal, ElseGoal),
|
|
|
|
% An ITE is trivial iff all of its inner goals are trivial,
|
|
|
|
goal_trivial_and(TrivialCond, TrivialThen, TrivialCondThen),
|
|
goal_trivial_and(TrivialCondThen, TrivialElse, Trivial0),
|
|
|
|
% And it has port counts iff it will end in a goal with a port count
|
|
% regardless of which of the then and the else branch is taken.
|
|
|
|
port_counts_give_coverage_after_and(PortCountsCoverageAfterThen,
|
|
PortCountsCoverageAfterElse, PortCountsCoverageAfterDirect)
|
|
;
|
|
GoalExpr0 = shorthand(_),
|
|
unexpected($module, $pred, "shorthand")
|
|
),
|
|
|
|
(
|
|
CPOptions ^ cpo_use_portcounts = yes,
|
|
has_port_counts_after(Goal0, PortCountsCoverageAfterDirect,
|
|
PortCountsCoverageAfterBefore, PortCountsCoverageAfter)
|
|
;
|
|
CPOptions ^ cpo_use_portcounts = no,
|
|
PortCountsCoverageAfter = no_port_counts_give_coverage_after
|
|
),
|
|
|
|
(
|
|
CPOptions ^ cpo_use_trivial = yes,
|
|
Trivial = Trivial0
|
|
;
|
|
CPOptions ^ cpo_use_trivial = no,
|
|
Trivial = goal_is_nontrivial
|
|
),
|
|
|
|
% Annotate the goal with this new information.
|
|
Info = dp_coverage_goal_info(Trivial, PortCountsCoverageAfter),
|
|
goal_info_get_maybe_dp_info(GoalInfo0) = MaybeDPInfo0,
|
|
(
|
|
MaybeDPInfo0 = yes(dp_goal_info(IsProfilingInstrumentation, _)),
|
|
DPInfo = dp_goal_info(IsProfilingInstrumentation, yes(Info))
|
|
;
|
|
MaybeDPInfo0 = no,
|
|
unexpected($module, $pred, "goal_dp_info not present")
|
|
),
|
|
goal_info_set_maybe_dp_info(yes(DPInfo), GoalInfo0, GoalInfo),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo).
|
|
|
|
% Combine information about goals within a conjunction
|
|
%
|
|
:- pred coverage_prof_first_pass_conj(coverage_profiling_options::in,
|
|
hlds_goal::in, hlds_goal::out, goal_trivial::in, goal_trivial::out,
|
|
port_counts_give_coverage_after::in, port_counts_give_coverage_after::out)
|
|
is det.
|
|
|
|
coverage_prof_first_pass_conj(CPOptions, Goal0, Goal, TrivialAcc, Trivial,
|
|
PortCountsCoverageAfterAcc, PortCountsCoverageAfter) :-
|
|
coverage_prof_first_pass(CPOptions, Goal0, Goal,
|
|
PortCountsCoverageAfterAcc,
|
|
dp_coverage_goal_info(TrivialGoal, PortCountsCoverageAfter)),
|
|
goal_trivial_and(TrivialAcc, TrivialGoal, Trivial).
|
|
|
|
% Combine information about goals within a disjunction.
|
|
%
|
|
% A portcount may be available to the goal executed when first entering a
|
|
% disjunction. However it is impractical to determine if any disjuncts
|
|
% other than the first are ever tried. So port counts at the beginning of
|
|
% them are unknown.
|
|
%
|
|
:- pred coverage_prof_first_pass_disj(coverage_profiling_options::in,
|
|
list(hlds_goal)::in, list(hlds_goal)::out, goal_trivial::out,
|
|
port_counts_give_coverage_after::in, port_counts_give_coverage_after::out)
|
|
is det.
|
|
|
|
coverage_prof_first_pass_disj(_, [], [], goal_is_trivial,
|
|
!PortCountsCoverageAfter).
|
|
coverage_prof_first_pass_disj(CPOptions, [Goal0 | Goals0], [Goal | Goals],
|
|
Trivial, PortCountsCoverageBeforeDisjunct, PortCountsCoverageAfter) :-
|
|
coverage_prof_first_pass(CPOptions, Goal0, Goal,
|
|
PortCountsCoverageBeforeDisjunct,
|
|
dp_coverage_goal_info(TrivialGoal, PortCountsCoverageAfterGoal)),
|
|
coverage_prof_first_pass_disj(CPOptions, Goals0, Goals, TrivialDisj,
|
|
no_port_counts_give_coverage_after, PortCountsCoverageAfterDisj),
|
|
goal_trivial_and(TrivialGoal, TrivialDisj, Trivial),
|
|
port_counts_give_coverage_after_and(PortCountsCoverageAfterGoal,
|
|
PortCountsCoverageAfterDisj, PortCountsCoverageAfter).
|
|
|
|
% A switch is a special type of disjunction. The important difference here
|
|
% is that the coverage of the first case cannot be inferred from the
|
|
% coverage before the switch.
|
|
%
|
|
:- pred coverage_prof_first_pass_switchcase(coverage_profiling_options::in,
|
|
list(case)::in, list(case)::out, goal_trivial::out,
|
|
port_counts_give_coverage_after::out) is det.
|
|
|
|
coverage_prof_first_pass_switchcase(_, [], [],
|
|
goal_is_trivial, port_counts_give_coverage_after).
|
|
coverage_prof_first_pass_switchcase(CPOptions,
|
|
[Case0 | Cases0], [Case | Cases], Trivial, PortCountsCoverageAfter) :-
|
|
Case0 = case(FirstFunctor, LaterFunctors, Goal0),
|
|
|
|
coverage_prof_first_pass(CPOptions, Goal0, Goal,
|
|
no_port_counts_give_coverage_after,
|
|
dp_coverage_goal_info(TrivialGoal, PortCountsCoverageAfterGoal)),
|
|
coverage_prof_first_pass_switchcase(CPOptions, Cases0, Cases,
|
|
TrivialSwitchcase, PortCountsCoverageAfterSwitchcase),
|
|
goal_trivial_and(TrivialGoal, TrivialSwitchcase, Trivial),
|
|
port_counts_give_coverage_after_and(PortCountsCoverageAfterGoal,
|
|
PortCountsCoverageAfterSwitchcase, PortCountsCoverageAfter),
|
|
|
|
Case = case(FirstFunctor, LaterFunctors, Goal).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Insert a coverage point before the given goal. This returns a flat
|
|
% conjunction consisting of a coverage point followed by the goal.
|
|
%
|
|
:- pred insert_coverage_point_before(coverage_profiling_options::in,
|
|
coverage_point_info::in, hlds_goal::in, hlds_goal::out,
|
|
proc_coverage_info::in, proc_coverage_info::out) is det.
|
|
|
|
insert_coverage_point_before(CPOptions, CPInfo, !Goal, !Info) :-
|
|
make_coverage_point(CPOptions, CPInfo, CPGoals, !Info),
|
|
( if !.Goal = hlds_goal(conj(plain_conj, InnerGoals), _) then
|
|
Goals = CPGoals ++ InnerGoals
|
|
else
|
|
Goals = CPGoals ++ [!.Goal]
|
|
),
|
|
create_conj_from_list(Goals, plain_conj, !:Goal).
|
|
|
|
% Builds a list of goals (that will form part of a conjunction)
|
|
% for a coverage point.
|
|
%
|
|
:- pred make_coverage_point(coverage_profiling_options::in,
|
|
coverage_point_info::in, list(hlds_goal)::out,
|
|
proc_coverage_info::in, proc_coverage_info::out) is det.
|
|
|
|
make_coverage_point(CPOptions, CoveragePointInfo, Goals, !CoverageInfo) :-
|
|
CoveragePointInfos0 = !.CoverageInfo ^ ci_coverage_points,
|
|
CPIndexCounter0 = !.CoverageInfo ^ ci_cp_index_counter,
|
|
|
|
counter.allocate(CPIndex, CPIndexCounter0, CPIndexCounter),
|
|
map.det_insert(CPIndex, CoveragePointInfo,
|
|
CoveragePointInfos0, CoveragePointInfos),
|
|
!CoverageInfo ^ ci_coverage_points := CoveragePointInfos,
|
|
!CoverageInfo ^ ci_cp_index_counter := CPIndexCounter,
|
|
|
|
% Build unifications for the coverage point index and the proc static.
|
|
|
|
some [!VarInfo] (
|
|
!:VarInfo = !.CoverageInfo ^ ci_var_info,
|
|
|
|
generate_var("CPIndex", int_type, CPIndexVar, !VarInfo),
|
|
generate_deep_const_unify(int_const(CPIndex), CPIndexVar,
|
|
GoalUnifyIndex),
|
|
% When using dynamic coverage profiling we really on this variable
|
|
% being optimised away later.
|
|
generate_var("ProcLayout", c_pointer_type, ProcLayoutVar, !VarInfo),
|
|
proc_static_cons_id(!.CoverageInfo, ProcStaticConsId),
|
|
generate_deep_const_unify(ProcStaticConsId, ProcLayoutVar,
|
|
GoalUnifyProcLayout),
|
|
|
|
!CoverageInfo ^ ci_var_info := !.VarInfo
|
|
),
|
|
|
|
% Build a call to the instrumentation code.
|
|
|
|
UseCalls = CPOptions ^ cpo_use_calls,
|
|
ModuleInfo = !.CoverageInfo ^ ci_module_info,
|
|
Ground = ground(shared, none_or_default_func),
|
|
DataType = CPOptions ^ cpo_dynamic_coverage,
|
|
FromToGround = from_to_mode(Ground, Ground),
|
|
(
|
|
DataType = dynamic_coverage_data,
|
|
PredName = "increment_dynamic_coverage_point_count",
|
|
ArgVars = [CPIndexVar],
|
|
make_foreign_args(ArgVars,
|
|
[foreign_arg_name_mode_box(
|
|
yes(foreign_arg_name_mode("CPIndex", FromToGround)),
|
|
bp_native_if_possible)],
|
|
[int_type], ForeignArgVars),
|
|
PredArity = 1
|
|
;
|
|
DataType = static_coverage_data,
|
|
PredName = "increment_static_coverage_point_count",
|
|
ArgVars = [ProcLayoutVar, CPIndexVar],
|
|
make_foreign_args(ArgVars,
|
|
[foreign_arg_name_mode_box(
|
|
yes(foreign_arg_name_mode("ProcLayout", FromToGround)),
|
|
bp_native_if_possible),
|
|
foreign_arg_name_mode_box(
|
|
yes(foreign_arg_name_mode("CPIndex", FromToGround)),
|
|
bp_native_if_possible)],
|
|
[c_pointer_type, int_type], ForeignArgVars),
|
|
PredArity = 2
|
|
),
|
|
% Note: The body of increment_coverage_point_count includes several
|
|
% assertions. If these are enabled, then bodily including the C code
|
|
% at EVERY coverage point will cause significant code bloat. Generating
|
|
% a call to a predicate with the same code in library/profiling_builtin.m
|
|
% should then yield smaller code, and due to cache effects, it will
|
|
% probably yield faster code as well.
|
|
(
|
|
UseCalls = no,
|
|
get_deep_profile_builtin_ppid(ModuleInfo, PredName, PredArity,
|
|
PredId, ProcId),
|
|
coverage_point_ll_code(DataType, ForeignCallAttrs, ForeignProc),
|
|
CallGoalExpr = call_foreign_proc(ForeignCallAttrs, PredId, ProcId,
|
|
ForeignArgVars, [], no, ForeignProc),
|
|
NonLocals = set_of_var.list_to_set(ArgVars),
|
|
InstMapDelta = instmap_delta_from_assoc_list([]),
|
|
CallGoalInfo = impure_init_goal_info(NonLocals, InstMapDelta,
|
|
detism_det),
|
|
CallGoal = hlds_goal(CallGoalExpr, CallGoalInfo)
|
|
;
|
|
UseCalls = yes,
|
|
generate_deep_call(ModuleInfo, PredName, PredArity, ArgVars,
|
|
yes([]), detism_det, CallGoal)
|
|
),
|
|
|
|
% Construct complete goal list.
|
|
Goals = [GoalUnifyIndex, GoalUnifyProcLayout, CallGoal].
|
|
|
|
% Turn a map of coverage points and their indexes into a list in sorted
|
|
% order.
|
|
%
|
|
:- pred coverage_points_map_list(map(int, coverage_point_info)::in,
|
|
list(coverage_point_info)::out) is det.
|
|
|
|
coverage_points_map_list(Map, List) :-
|
|
map.to_sorted_assoc_list(Map, AssocList),
|
|
assoc_list.values(AssocList, List).
|
|
|
|
% Retrieve the pred and proc ids from either the deep_maybe_rec_info or
|
|
% deep_pred_proc_id fields of a deep_info structure.
|
|
%
|
|
:- pred pred_proc_id(proc_coverage_info::in, pred_id::out, proc_id::out)
|
|
is det.
|
|
|
|
pred_proc_id(CoverageInfo, PredId, ProcId) :-
|
|
MaybeRecInfo = CoverageInfo ^ ci_maybe_rec_info,
|
|
PredProcId = CoverageInfo ^ ci_pred_proc_id,
|
|
( if
|
|
MaybeRecInfo = yes(RecInfo),
|
|
RecInfo ^ dri_role = deep_prof_inner_proc(OuterPredProcId)
|
|
then
|
|
OuterPredProcId = proc(PredId, ProcId)
|
|
else
|
|
PredProcId = proc(PredId, ProcId)
|
|
).
|
|
|
|
% Create a proc static cons_id from the deep recursion info.
|
|
%
|
|
:- pred proc_static_cons_id(proc_coverage_info::in, cons_id::out) is det.
|
|
|
|
proc_static_cons_id(CoverageInfo, ProcStaticConsId) :-
|
|
pred_proc_id(CoverageInfo, PredId, ProcId),
|
|
ShroudedPredProcId = shroud_pred_proc_id(proc(PredId, ProcId)),
|
|
ProcStaticConsId = deep_profiling_proc_layout(ShroudedPredProcId).
|
|
|
|
% Returns a string containing the Low Level C code for a coverage point.
|
|
%
|
|
:- pred coverage_point_ll_code(coverage_data_type::in,
|
|
pragma_foreign_proc_attributes::out, pragma_foreign_proc_impl::out) is det.
|
|
|
|
coverage_point_ll_code(CoverageDataType, ForeignProcAttrs, ForeignProcImpl) :-
|
|
some [!ForeignProcAttrs] (
|
|
% XXX When running this code in a parallel grade, the contention for
|
|
% the foreign code mutex may be very expensive. To improve this, we
|
|
% should add a mechanism that, in par grades, allows us to replace
|
|
% the general foreign code mutex with one that guards only the
|
|
% coverage data structure we are updating, since that would yield
|
|
% a LOT less contention.
|
|
!:ForeignProcAttrs = default_attributes(lang_c),
|
|
set_thread_safe(proc_not_thread_safe, !ForeignProcAttrs),
|
|
set_may_call_mercury(proc_will_not_call_mercury, !ForeignProcAttrs),
|
|
set_purity(purity_impure, !ForeignProcAttrs),
|
|
set_terminates(proc_terminates, !ForeignProcAttrs),
|
|
set_may_throw_exception(proc_will_not_throw_exception,
|
|
!ForeignProcAttrs),
|
|
ForeignProcAttrs = !.ForeignProcAttrs
|
|
),
|
|
ForeignProcImpl = fp_impl_ordinary(Code, no),
|
|
Code = coverage_point_ll_code(CoverageDataType).
|
|
|
|
:- func coverage_point_ll_code(coverage_data_type) = string.
|
|
|
|
coverage_point_ll_code(static_coverage_data) =
|
|
% The code of this predicate is duplicated bodily in profiling_builtin.m
|
|
% in the library directory, so any changes here should also be made there.
|
|
"
|
|
#ifdef MR_DEEP_PROFILING_COVERAGE_STATIC
|
|
const MR_ProcLayout *pl;
|
|
MR_ProcStatic *ps;
|
|
|
|
MR_enter_instrumentation();
|
|
|
|
#ifdef MR_DEEP_PROFILING_LOWLEVEL_DEBUG
|
|
if (MR_calldebug && MR_lld_print_enabled) {
|
|
MR_print_deep_prof_vars(stdout, ""increment_coverage_point_count"");
|
|
printf("", ProcLayout: 0x%x, CPIndex: %d\\n"", ProcLayout, CPIndex);
|
|
}
|
|
#endif
|
|
|
|
pl = (const MR_ProcLayout *) ProcLayout;
|
|
|
|
MR_deep_assert(NULL, NULL, NULL, pl != NULL);
|
|
ps = pl->MR_sle_proc_static;
|
|
MR_deep_assert(NULL, pl, NULL, ps != NULL);
|
|
|
|
MR_deep_assert(NULL, pl, ps, CPIndex < ps->MR_ps_num_coverage_points);
|
|
MR_deep_assert(NULL, pl, ps, ps->MR_ps_coverage_points != NULL);
|
|
|
|
ps->MR_ps_coverage_points[CPIndex]++;
|
|
|
|
MR_leave_instrumentation();
|
|
#else
|
|
MR_fatal_error(
|
|
""increment_static_coverage_point_count: ""
|
|
""static coverage profiling not enabled"");
|
|
#endif /* MR_DEEP_PROFILING_COVERAGE_STATIC */
|
|
".
|
|
|
|
coverage_point_ll_code(dynamic_coverage_data) =
|
|
% The code of this predicate is duplicated bodily in profiling_builtin.m
|
|
% in the library directory, so any changes here should also be made there.
|
|
"
|
|
#ifdef MR_DEEP_PROFILING_COVERAGE_DYNAMIC
|
|
const MR_CallSiteDynamic *csd;
|
|
const MR_ProcDynamic *pd;
|
|
|
|
MR_enter_instrumentation();
|
|
|
|
#ifdef MR_DEEP_PROFILING_LOWLEVEL_DEBUG
|
|
if (MR_calldebug && MR_lld_print_enabled) {
|
|
MR_print_deep_prof_vars(stdout, ""increment_coverage_point_count"");
|
|
printf("", CallSiteDynamic: 0x%x, CPIndex: %d\\n"",
|
|
MR_current_call_site_dynamic, CPIndex);
|
|
}
|
|
#endif
|
|
|
|
csd = MR_current_call_site_dynamic;
|
|
|
|
MR_deep_assert(NULL, NULL, NULL, csd != NULL);
|
|
pd = csd->MR_csd_callee_ptr;
|
|
|
|
MR_deep_assert(csd, NULL, NULL, pd != NULL);
|
|
|
|
#ifdef MR_DEEP_CHECKS
|
|
/*
|
|
** Check that CPIndex is within bounds.
|
|
*/
|
|
{
|
|
const MR_ProcLayout *pl;
|
|
const MR_ProcStatic *ps;
|
|
|
|
pl = pd->MR_pd_proc_layout;
|
|
MR_deep_assert(csd, NULL, NULL, pl != NULL);
|
|
ps = pl->MR_sle_proc_static;
|
|
MR_deep_assert(csd, pl, NULL, ps != NULL);
|
|
MR_deep_assert(csd, pl, ps, CPIndex < ps->MR_ps_num_coverage_points);
|
|
}
|
|
#endif
|
|
|
|
MR_deep_assert(csd, NULL, NULL, pd->MR_pd_coverage_points != NULL);
|
|
|
|
pd->MR_pd_coverage_points[CPIndex]++;
|
|
|
|
MR_leave_instrumentation();
|
|
#else
|
|
MR_fatal_error(
|
|
""increment_dynamic_coverage_point_count: ""
|
|
""dynamic deep profiling not enabled"");
|
|
#endif /* MR_DEEP_PROFILING_COVERAGE_DYNAMIC */
|
|
".
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- func goal_info_get_dp_info(hlds_goal_info) = dp_goal_info.
|
|
|
|
goal_info_get_dp_info(GoalInfo) = DPInfo :-
|
|
MaybeDPInfo = goal_info_get_maybe_dp_info(GoalInfo),
|
|
(
|
|
MaybeDPInfo = yes(DPInfo)
|
|
;
|
|
MaybeDPInfo = no,
|
|
unexpected($module, $pred, "MaybeDPInfo = no")
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module ll_backend.coverage_profiling.
|
|
%-----------------------------------------------------------------------------%
|