|
1 | 1 | use rustc_data_structures::captures::Captures; |
2 | | -use rustc_data_structures::fx::FxIndexSet; |
3 | | -use rustc_index::bit_set::BitSet; |
4 | | -use rustc_middle::mir::CoverageIdsInfo; |
5 | 2 | use rustc_middle::mir::coverage::{ |
6 | | - CounterId, CovTerm, Expression, ExpressionId, FunctionCoverageInfo, Mapping, MappingKind, Op, |
| 3 | + CovTerm, CoverageIdsInfo, Expression, FunctionCoverageInfo, Mapping, MappingKind, Op, |
7 | 4 | SourceRegion, |
8 | 5 | }; |
9 | | -use rustc_middle::ty::Instance; |
10 | | -use tracing::debug; |
11 | 6 |
|
12 | 7 | use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind}; |
13 | 8 |
|
14 | | -/// Holds all of the coverage mapping data associated with a function instance, |
15 | | -/// collected during traversal of `Coverage` statements in the function's MIR. |
16 | | -#[derive(Debug)] |
17 | | -pub(crate) struct FunctionCoverageCollector<'tcx> { |
18 | | - /// Coverage info that was attached to this function by the instrumentor. |
19 | | - function_coverage_info: &'tcx FunctionCoverageInfo, |
20 | | - ids_info: &'tcx CoverageIdsInfo, |
21 | | - is_used: bool, |
| 9 | +pub(crate) struct FunctionCoverage<'tcx> { |
| 10 | + pub(crate) function_coverage_info: &'tcx FunctionCoverageInfo, |
| 11 | + /// If `None`, the corresponding function is unused. |
| 12 | + ids_info: Option<&'tcx CoverageIdsInfo>, |
22 | 13 | } |
23 | 14 |
|
24 | | -impl<'tcx> FunctionCoverageCollector<'tcx> { |
25 | | - /// Creates a new set of coverage data for a used (called) function. |
26 | | - pub(crate) fn new( |
27 | | - instance: Instance<'tcx>, |
28 | | - function_coverage_info: &'tcx FunctionCoverageInfo, |
29 | | - ids_info: &'tcx CoverageIdsInfo, |
30 | | - ) -> Self { |
31 | | - Self::create(instance, function_coverage_info, ids_info, true) |
32 | | - } |
33 | | - |
34 | | - /// Creates a new set of coverage data for an unused (never called) function. |
35 | | - pub(crate) fn unused( |
36 | | - instance: Instance<'tcx>, |
37 | | - function_coverage_info: &'tcx FunctionCoverageInfo, |
38 | | - ids_info: &'tcx CoverageIdsInfo, |
39 | | - ) -> Self { |
40 | | - Self::create(instance, function_coverage_info, ids_info, false) |
41 | | - } |
42 | | - |
43 | | - fn create( |
44 | | - instance: Instance<'tcx>, |
| 15 | +impl<'tcx> FunctionCoverage<'tcx> { |
| 16 | + pub(crate) fn new_used( |
45 | 17 | function_coverage_info: &'tcx FunctionCoverageInfo, |
46 | 18 | ids_info: &'tcx CoverageIdsInfo, |
47 | | - is_used: bool, |
48 | 19 | ) -> Self { |
49 | | - let num_counters = function_coverage_info.num_counters; |
50 | | - let num_expressions = function_coverage_info.expressions.len(); |
51 | | - debug!( |
52 | | - "FunctionCoverage::create(instance={instance:?}) has \ |
53 | | - num_counters={num_counters}, num_expressions={num_expressions}, is_used={is_used}" |
54 | | - ); |
55 | | - |
56 | | - Self { function_coverage_info, ids_info, is_used } |
57 | | - } |
58 | | - |
59 | | - /// Identify expressions that will always have a value of zero, and note |
60 | | - /// their IDs in [`ZeroExpressions`]. Mappings that refer to a zero expression |
61 | | - /// can instead become mappings to a constant zero value. |
62 | | - /// |
63 | | - /// This method mainly exists to preserve the simplifications that were |
64 | | - /// already being performed by the Rust-side expression renumbering, so that |
65 | | - /// the resulting coverage mappings don't get worse. |
66 | | - fn identify_zero_expressions(&self) -> ZeroExpressions { |
67 | | - // The set of expressions that either were optimized out entirely, or |
68 | | - // have zero as both of their operands, and will therefore always have |
69 | | - // a value of zero. Other expressions that refer to these as operands |
70 | | - // can have those operands replaced with `CovTerm::Zero`. |
71 | | - let mut zero_expressions = ZeroExpressions::default(); |
72 | | - |
73 | | - // Simplify a copy of each expression based on lower-numbered expressions, |
74 | | - // and then update the set of always-zero expressions if necessary. |
75 | | - // (By construction, expressions can only refer to other expressions |
76 | | - // that have lower IDs, so one pass is sufficient.) |
77 | | - for (id, expression) in self.function_coverage_info.expressions.iter_enumerated() { |
78 | | - if !self.is_used || !self.ids_info.expressions_seen.contains(id) { |
79 | | - // If an expression was not seen, it must have been optimized away, |
80 | | - // so any operand that refers to it can be replaced with zero. |
81 | | - zero_expressions.insert(id); |
82 | | - continue; |
83 | | - } |
84 | | - |
85 | | - // We don't need to simplify the actual expression data in the |
86 | | - // expressions list; we can just simplify a temporary copy and then |
87 | | - // use that to update the set of always-zero expressions. |
88 | | - let Expression { mut lhs, op, mut rhs } = *expression; |
89 | | - |
90 | | - // If an expression has an operand that is also an expression, the |
91 | | - // operand's ID must be strictly lower. This is what lets us find |
92 | | - // all zero expressions in one pass. |
93 | | - let assert_operand_expression_is_lower = |operand_id: ExpressionId| { |
94 | | - assert!( |
95 | | - operand_id < id, |
96 | | - "Operand {operand_id:?} should be less than {id:?} in {expression:?}", |
97 | | - ) |
98 | | - }; |
99 | | - |
100 | | - // If an operand refers to a counter or expression that is always |
101 | | - // zero, then that operand can be replaced with `CovTerm::Zero`. |
102 | | - let maybe_set_operand_to_zero = |operand: &mut CovTerm| { |
103 | | - if let CovTerm::Expression(id) = *operand { |
104 | | - assert_operand_expression_is_lower(id); |
105 | | - } |
106 | | - |
107 | | - if is_zero_term(&self.ids_info.counters_seen, &zero_expressions, *operand) { |
108 | | - *operand = CovTerm::Zero; |
109 | | - } |
110 | | - }; |
111 | | - maybe_set_operand_to_zero(&mut lhs); |
112 | | - maybe_set_operand_to_zero(&mut rhs); |
113 | | - |
114 | | - // Coverage counter values cannot be negative, so if an expression |
115 | | - // involves subtraction from zero, assume that its RHS must also be zero. |
116 | | - // (Do this after simplifications that could set the LHS to zero.) |
117 | | - if lhs == CovTerm::Zero && op == Op::Subtract { |
118 | | - rhs = CovTerm::Zero; |
119 | | - } |
120 | | - |
121 | | - // After the above simplifications, if both operands are zero, then |
122 | | - // we know that this expression is always zero too. |
123 | | - if lhs == CovTerm::Zero && rhs == CovTerm::Zero { |
124 | | - zero_expressions.insert(id); |
125 | | - } |
126 | | - } |
127 | | - |
128 | | - zero_expressions |
| 20 | + Self { function_coverage_info, ids_info: Some(ids_info) } |
129 | 21 | } |
130 | 22 |
|
131 | | - pub(crate) fn into_finished(self) -> FunctionCoverage<'tcx> { |
132 | | - let zero_expressions = self.identify_zero_expressions(); |
133 | | - let FunctionCoverageCollector { function_coverage_info, ids_info, is_used, .. } = self; |
134 | | - |
135 | | - FunctionCoverage { function_coverage_info, ids_info, is_used, zero_expressions } |
| 23 | + pub(crate) fn new_unused(function_coverage_info: &'tcx FunctionCoverageInfo) -> Self { |
| 24 | + Self { function_coverage_info, ids_info: None } |
136 | 25 | } |
137 | | -} |
138 | | - |
139 | | -pub(crate) struct FunctionCoverage<'tcx> { |
140 | | - pub(crate) function_coverage_info: &'tcx FunctionCoverageInfo, |
141 | | - ids_info: &'tcx CoverageIdsInfo, |
142 | | - is_used: bool, |
143 | | - |
144 | | - zero_expressions: ZeroExpressions, |
145 | | -} |
146 | 26 |
|
147 | | -impl<'tcx> FunctionCoverage<'tcx> { |
148 | 27 | /// Returns true for a used (called) function, and false for an unused function. |
149 | 28 | pub(crate) fn is_used(&self) -> bool { |
150 | | - self.is_used |
| 29 | + self.ids_info.is_some() |
151 | 30 | } |
152 | 31 |
|
153 | 32 | /// Return the source hash, generated from the HIR node structure, and used to indicate whether |
154 | 33 | /// or not the source code structure changed between different compilations. |
155 | 34 | pub(crate) fn source_hash(&self) -> u64 { |
156 | | - if self.is_used { self.function_coverage_info.function_source_hash } else { 0 } |
| 35 | + if self.is_used() { self.function_coverage_info.function_source_hash } else { 0 } |
157 | 36 | } |
158 | 37 |
|
159 | 38 | /// Convert this function's coverage expression data into a form that can be |
@@ -196,37 +75,10 @@ impl<'tcx> FunctionCoverage<'tcx> { |
196 | 75 | } |
197 | 76 |
|
198 | 77 | fn is_zero_term(&self, term: CovTerm) -> bool { |
199 | | - !self.is_used || is_zero_term(&self.ids_info.counters_seen, &self.zero_expressions, term) |
200 | | - } |
201 | | -} |
202 | | - |
203 | | -/// Set of expression IDs that are known to always evaluate to zero. |
204 | | -/// Any mapping or expression operand that refers to these expressions can have |
205 | | -/// that reference replaced with a constant zero value. |
206 | | -#[derive(Default)] |
207 | | -struct ZeroExpressions(FxIndexSet<ExpressionId>); |
208 | | - |
209 | | -impl ZeroExpressions { |
210 | | - fn insert(&mut self, id: ExpressionId) { |
211 | | - self.0.insert(id); |
212 | | - } |
213 | | - |
214 | | - fn contains(&self, id: ExpressionId) -> bool { |
215 | | - self.0.contains(&id) |
216 | | - } |
217 | | -} |
218 | | - |
219 | | -/// Returns `true` if the given term is known to have a value of zero, taking |
220 | | -/// into account knowledge of which counters are unused and which expressions |
221 | | -/// are always zero. |
222 | | -fn is_zero_term( |
223 | | - counters_seen: &BitSet<CounterId>, |
224 | | - zero_expressions: &ZeroExpressions, |
225 | | - term: CovTerm, |
226 | | -) -> bool { |
227 | | - match term { |
228 | | - CovTerm::Zero => true, |
229 | | - CovTerm::Counter(id) => !counters_seen.contains(id), |
230 | | - CovTerm::Expression(id) => zero_expressions.contains(id), |
| 78 | + match self.ids_info { |
| 79 | + Some(ids_info) => ids_info.is_zero_term(term), |
| 80 | + // This function is unused, so all coverage counters/expressions are zero. |
| 81 | + None => true, |
| 82 | + } |
231 | 83 | } |
232 | 84 | } |
0 commit comments