mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-24 13:53:54 +00:00
If a module has two or more import_module or use_module declarations
for the same module, (typically, but not always, one being in its interface
and one in its implementation), generate an informational message about
each redundant declaration if --warn-unused-imports is enabled.
compiler/hlds_module.m:
We used to record the set of imported/used modules, and the set of
modules imported/used in the interface of the current module. However,
these sets
- did not record the distinction between imports and uses;
- did not allow distinction between single and multiple imports/uses;
- did not record the locations of the imports/uses.
The first distinction was needed only by module_qual.m, which *did*
pay attention to it; the other two were not needed at all.
To generate messages for imports/uses shadowing other imports/uses,
we need all three, so change the data structure storing such information
for *direct* imports to one that records all three of the above kinds
of information. (For imports made by read-in interface and optimization
files, the old set of modules approach is fine, and this diff leaves
the set of thus *indirectly* imported module names alone.)
compiler/unused_imports.m:
Use the extra information now available to generate a
severity_informational message about any import or use that is made
redundant by an earlier, more general import or use.
Fix two bugs in the code that generated warnings for just plain unused
modules.
(1) It did not consider that a use of the builtin type char justified
an import of char.m, but without that import, the type is not visible.
(2) It scanned cons_ids in goals in procedure bodies, but did not scan
cons_ids that have been put into the const_struct_db. (I did not update
the code here when I added the const_struct_db.)
Also, add a (hopefully temporary) workaround for a bug in
make_hlds_passes.m, which is noted below.
However, there are at least three problems that prevent us from enabling
--warn-unused-imports by default.
(1) In some places, the import of a module is used only by clauses for
a predicate that also has foreign procs. When compiled in a grade that
selects one of those foreign_procs as the implementation of the predicate,
the clauses are discarded *without* being added to the HLDS at all.
This leads unused_imports.m to generate an uncalled-for warning in such
cases. To fix this, we would need to preserve the Mercury clauses for
*all* predicates, even those with foreign procs, and do all the semantic
checks on them before throwing them away. (I tried to do this once, and
failed, but the task should be easier after the item list change.)
(2) We have two pieces of code to generate import warnings. The one in
unused_imports.m operates on the HLDS after type and mode checking,
while module_qual.m operates on the parse tree before the creation of
the HLDS. The former is more powerful, since it knows e.g. what types and
modes are used in the bodies of predicates, and hence can generate warnings
about an import being unused *anywhere* in a module, as opposed to just
unused in its interface.
If --warn-unused-imports is enabled, we will get two separate set of
reports about an interface import being unused in the interface,
*unless* we get a type or mode error, in which case unused_imports.m
won't be invoked. But in case we do get such errors, we don't want to
throw away the warnings from module_qual.m. We could store them and
throw them away only after we know we won't need them, or just get
the two modules to generate identical error_specs for each warning,
so that the sort_and_remove_dups of the error specs will do the
throwing away for us for free, if we get that far.
(3) The valid/bug100.m test case was added as a regression test for a bug
that was fixed in module_qual.m. However the bug is still present in
unused_imports.m.
compiler/make_hlds_passes.m:
Give hlds_module.m the extra information it now needs for each item_avail.
Add an XXX for a bug that cannot be fixed right now: the setting of
the status of abstract instances to abstract_imported. (The "abstract"
part is correct; the "imported" part may not be.)
compiler/intermod.m:
compiler/try_expand.m:
compiler/xml_documentation.m:
Conform to the change in hlds_module.m.
compiler/module_qual.m:
Update the documentation of the relationship of this module
with unused_imports.m.
compiler/hlds_data.m:
Document a problem with the status of instance definitions.
compiler/hlds_out_module.m:
Update the code that prints out the module_info to conform to the change
to hlds_module.m.
Print status information about instances, which was needed to diagnose
one of the bugs in unused_imports.m. Format the output for instances
nicer.
compiler/prog_item.m:
Add a convenience predicate.
compiler/prog_data.m:
Remove a type synonym that makes things harder to understand, not easier.
compiler/modules.m:
Delete an XXX that asks for the feature this diff implements.
Add another XXX about how that feature could be improved.
compiler/Mercury.options.m:
Add some more modules to the list of modules on which the compiler
should be invoked with --no-warn-unused-imports.
compiler/*.m:
library/*.m:
mdbcomp/*.m:
browser/*.m:
deep_profiler/*.m:
mfilterjavac/*.m:
Delete unneeded imports. Many of these shadow other imports, and some
are just plain unneeded, as shown by --warn-unused-imports. In a few
modules, there were a *lot* of unneeded imports, but most had just
one or two.
In a few cases, removing an import from a module, because it *itself*
does not need it, required adding that same import to those of its
submodules which *do* need it.
In a few cases, conform to other changes above.
tests/invalid/Mercury.options:
Test the generation of messages about import shadowing on the existing
import_in_parent.m test case (although it was also tested very thoroughly
when giving me the information needed for the deletion of all the
unneeded imports above).
tests/*/*.{m,*exp}:
Delete unneeded imports, and update any expected error messages
to expect the now-smaller line numbers.
1401 lines
54 KiB
Mathematica
1401 lines
54 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2001-2012 The University of Melbourne.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% File: coverage_profiling.m.
|
|
% Main author: pbone.
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module ll_backend.coverage_profiling.
|
|
|
|
:- interface.
|
|
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module hlds.hlds_module.
|
|
:- import_module hlds.hlds_pred.
|
|
:- import_module hlds.vartypes.
|
|
:- import_module mdbcomp.goal_path.
|
|
:- import_module mdbcomp.program_representation.
|
|
|
|
:- import_module list.
|
|
:- import_module maybe.
|
|
|
|
:- type proc_coverage_info.
|
|
|
|
% Initialize the coverage_profiling_options structure.
|
|
%
|
|
:- pred coverage_profiling_options(module_info::in,
|
|
coverage_profiling_options::out) is det.
|
|
|
|
% 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.globals.
|
|
:- import_module libs.options.
|
|
:- import_module ll_backend.deep_profiling.
|
|
:- import_module parse_tree.builtin_lib_types.
|
|
:- import_module parse_tree.prog_data.
|
|
:- 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
|
|
).
|
|
|
|
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) :-
|
|
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),
|
|
|
|
(
|
|
IsMDProfInst = goal_is_not_mdprof_inst,
|
|
CoverageBeforeKnown = coverage_before_unknown
|
|
->
|
|
GoalId = goal_id(GoalNum),
|
|
UnknownMsg = string.format(
|
|
"Coverage information is unknown for goal_id %d\n", [i(GoalNum)]),
|
|
unexpected($module, $pred, UnknownMsg)
|
|
;
|
|
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 = out_of_line_builtin
|
|
; 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 =
|
|
ScopeGoal0 ^ hlds_goal_info ^ goal_info_get_determinism,
|
|
( ScopedGoalDetism = Detism ->
|
|
NextCoverageBeforeKnown0 = CoverageAfterScopedGoalKnown
|
|
;
|
|
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.
|
|
(
|
|
(
|
|
% 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
|
|
)
|
|
->
|
|
MaybeAddCP = no,
|
|
NextCoverageBeforeKnown = NextCoverageBeforeKnown0
|
|
;
|
|
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.
|
|
(
|
|
HeadGoal = hlds_goal(conj(plain_conj, HeadConjGoals), _),
|
|
ConjType = plain_conj
|
|
->
|
|
Goals = HeadConjGoals ++ TailGoals
|
|
;
|
|
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.
|
|
(
|
|
DPInfo = dp_goal_info(goal_is_mdprof_inst, _),
|
|
Disjuncts0 = [FirstDisjunct0, SecondDisjunct],
|
|
goal_info_get_determinism(SecondDisjunct ^ hlds_goal_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.
|
|
->
|
|
coverage_prof_second_pass_goal(FirstDisjunct0, FirstDisjunct,
|
|
CoverageBeforeKnown, NextCoverageBeforeKnown, !Info,
|
|
AddedImpurity),
|
|
Disjuncts = [FirstDisjunct, SecondDisjunct]
|
|
;
|
|
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, _),
|
|
(
|
|
CPOBranchDisj = yes,
|
|
CoverageBeforeKnown0 = coverage_before_unknown,
|
|
IsMDProfInst = goal_is_not_mdprof_inst
|
|
->
|
|
InsertCP = yes,
|
|
CoverageBeforeKnown = coverage_before_known
|
|
;
|
|
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 ^ hlds_goal_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, _),
|
|
(
|
|
CPOBranchSwitch = yes,
|
|
CoverageBeforeCaseKnown0 = coverage_before_unknown,
|
|
IsMDProfInst = goal_is_not_mdprof_inst
|
|
->
|
|
InsertCP = yes,
|
|
CoverageBeforeCaseKnown = coverage_before_known
|
|
;
|
|
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 ^ hlds_goal_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, _),
|
|
(
|
|
CPOBranchIf = yes,
|
|
IsMDProfInst = goal_is_not_mdprof_inst
|
|
->
|
|
ContainingGoalMap = !.Info ^ ci_containing_goal_map,
|
|
(
|
|
CoverageKnownBeforeThen0 = coverage_before_unknown,
|
|
ThenId = goal_info_get_goal_id(Then0 ^ hlds_goal_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 ^ hlds_goal_info),
|
|
ElsePath = goal_id_to_reverse_path(ContainingGoalMap, ElseId),
|
|
CondDetism = goal_info_get_determinism(Cond ^ hlds_goal_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.
|
|
(
|
|
CoverageBeforeITEKnown = coverage_before_known,
|
|
CoverageKnownBeforeThen = coverage_before_known
|
|
->
|
|
InsertCPElse = no,
|
|
CoverageKnownBeforeElse = coverage_before_known
|
|
;
|
|
InsertCPElse = yes(coverage_point_info(ElsePath,
|
|
cp_type_branch_arm)),
|
|
CoverageKnownBeforeElse = coverage_before_known
|
|
)
|
|
)
|
|
;
|
|
% 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) :-
|
|
(
|
|
A = goal_is_trivial,
|
|
B = goal_is_trivial
|
|
->
|
|
Trivial = goal_is_trivial
|
|
;
|
|
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) :-
|
|
(
|
|
A = port_counts_give_coverage_after,
|
|
B = port_counts_give_coverage_after
|
|
->
|
|
PortCountsCoverageAfter = port_counts_give_coverage_after
|
|
;
|
|
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 ^ hlds_goal_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) :-
|
|
(
|
|
( Detism = detism_det
|
|
; Detism = detism_cc_multi
|
|
)
|
|
->
|
|
PortCountsCoverageAfter = PortCountsCoverageAfter0
|
|
;
|
|
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're 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 = out_of_line_builtin
|
|
; 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),
|
|
( !.Goal = hlds_goal(conj(plain_conj, InnerGoals), _) ->
|
|
Goals = CPGoals ++ InnerGoals
|
|
;
|
|
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),
|
|
DataType = CPOptions ^ cpo_dynamic_coverage,
|
|
(
|
|
DataType = dynamic_coverage_data,
|
|
PredName = "increment_dynamic_coverage_point_count",
|
|
ArgVars = [CPIndexVar],
|
|
make_foreign_args(ArgVars,
|
|
[(yes("CPIndex" - (Ground -> Ground)) - 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,
|
|
[(yes("ProcLayout" - (Ground -> Ground)) - native_if_possible),
|
|
(yes("CPIndex" - (Ground -> Ground)) - 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,
|
|
(
|
|
MaybeRecInfo = yes(RecInfo),
|
|
RecInfo ^ dri_role = deep_prof_inner_proc(OuterPredProcId)
|
|
->
|
|
OuterPredProcId = proc(PredId, ProcId)
|
|
;
|
|
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")
|
|
).
|
|
|
|
:- func goal_info_get_maybe_dp_coverage_info(hlds_goal_info) =
|
|
maybe(dp_coverage_goal_info).
|
|
|
|
goal_info_get_maybe_dp_coverage_info(GoalInfo) = MaybeCoverageInfo :-
|
|
MaybeDPInfo = goal_info_get_maybe_dp_info(GoalInfo),
|
|
(
|
|
MaybeDPInfo = yes(DPInfo),
|
|
DPInfo = dp_goal_info(_, MaybeCoverageInfo)
|
|
;
|
|
MaybeDPInfo = no,
|
|
MaybeCoverageInfo = no
|
|
).
|
|
|
|
:- func goal_get_maybe_dp_port_counts_coverage(hlds_goal) =
|
|
port_counts_give_coverage_after.
|
|
|
|
goal_get_maybe_dp_port_counts_coverage(Goal) = PortCountsGiveCoverageAfter :-
|
|
Goal = hlds_goal(_, GoalInfo),
|
|
MaybeCoverageInfo = goal_info_get_maybe_dp_coverage_info(GoalInfo),
|
|
(
|
|
MaybeCoverageInfo =
|
|
yes(dp_coverage_goal_info(_, PortCountsGiveCoverageAfter))
|
|
;
|
|
MaybeCoverageInfo = no,
|
|
PortCountsGiveCoverageAfter = no_port_counts_give_coverage_after
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module ll_backend.coverage_profiling.
|
|
%-----------------------------------------------------------------------------%
|