]>
Commit | Line | Data |
---|---|---|
add651ee | 1 | use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind}; |
f035d41b | 2 | |
781aab86 FG |
3 | use rustc_data_structures::fx::FxIndexSet; |
4 | use rustc_index::IndexVec; | |
5 | use rustc_middle::mir::coverage::{CodeRegion, CounterId, ExpressionId, Op, Operand}; | |
3dfed10e XL |
6 | use rustc_middle::ty::Instance; |
7 | use rustc_middle::ty::TyCtxt; | |
f035d41b | 8 | |
6a06907d | 9 | #[derive(Clone, Debug, PartialEq)] |
29967ef6 | 10 | pub struct Expression { |
add651ee | 11 | lhs: Operand, |
3dfed10e | 12 | op: Op, |
add651ee | 13 | rhs: Operand, |
29967ef6 | 14 | region: Option<CodeRegion>, |
f035d41b XL |
15 | } |
16 | ||
17 | /// Collects all of the coverage regions associated with (a) injected counters, (b) counter | |
18 | /// expressions (additions or subtraction), and (c) unreachable regions (always counted as zero), | |
add651ee | 19 | /// for a given Function. This struct also stores the `function_source_hash`, |
3dfed10e | 20 | /// computed during instrumentation, and forwarded with counters. |
f035d41b | 21 | /// |
3dfed10e XL |
22 | /// Note, it may be important to understand LLVM's definitions of `unreachable` regions versus "gap |
23 | /// regions" (or "gap areas"). A gap region is a code region within a counted region (either counter | |
24 | /// or expression), but the line or lines in the gap region are not executable (such as lines with | |
25 | /// only whitespace or comments). According to LLVM Code Coverage Mapping documentation, "A count | |
26 | /// for a gap area is only used as the line execution count if there are no other regions on a | |
27 | /// line." | |
17df50a5 | 28 | #[derive(Debug)] |
29967ef6 XL |
29 | pub struct FunctionCoverage<'tcx> { |
30 | instance: Instance<'tcx>, | |
3dfed10e | 31 | source_hash: u64, |
cdc7bbd5 | 32 | is_used: bool, |
add651ee FG |
33 | counters: IndexVec<CounterId, Option<CodeRegion>>, |
34 | expressions: IndexVec<ExpressionId, Option<Expression>>, | |
3dfed10e | 35 | unreachable_regions: Vec<CodeRegion>, |
f035d41b XL |
36 | } |
37 | ||
29967ef6 | 38 | impl<'tcx> FunctionCoverage<'tcx> { |
cdc7bbd5 | 39 | /// Creates a new set of coverage data for a used (called) function. |
29967ef6 | 40 | pub fn new(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Self { |
cdc7bbd5 XL |
41 | Self::create(tcx, instance, true) |
42 | } | |
43 | ||
44 | /// Creates a new set of coverage data for an unused (never called) function. | |
45 | pub fn unused(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Self { | |
46 | Self::create(tcx, instance, false) | |
47 | } | |
48 | ||
49 | fn create(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, is_used: bool) -> Self { | |
17df50a5 | 50 | let coverageinfo = tcx.coverageinfo(instance.def); |
29967ef6 | 51 | debug!( |
17df50a5 | 52 | "FunctionCoverage::create(instance={:?}) has coverageinfo={:?}. is_used={}", |
cdc7bbd5 | 53 | instance, coverageinfo, is_used |
29967ef6 | 54 | ); |
3dfed10e | 55 | Self { |
29967ef6 | 56 | instance, |
3dfed10e | 57 | source_hash: 0, // will be set with the first `add_counter()` |
cdc7bbd5 | 58 | is_used, |
3dfed10e XL |
59 | counters: IndexVec::from_elem_n(None, coverageinfo.num_counters as usize), |
60 | expressions: IndexVec::from_elem_n(None, coverageinfo.num_expressions as usize), | |
61 | unreachable_regions: Vec::new(), | |
62 | } | |
63 | } | |
64 | ||
cdc7bbd5 XL |
65 | /// Returns true for a used (called) function, and false for an unused function. |
66 | pub fn is_used(&self) -> bool { | |
67 | self.is_used | |
68 | } | |
69 | ||
29967ef6 XL |
70 | /// Sets the function source hash value. If called multiple times for the same function, all |
71 | /// calls should have the same hash value. | |
72 | pub fn set_function_source_hash(&mut self, source_hash: u64) { | |
3dfed10e XL |
73 | if self.source_hash == 0 { |
74 | self.source_hash = source_hash; | |
75 | } else { | |
76 | debug_assert_eq!(source_hash, self.source_hash); | |
77 | } | |
29967ef6 XL |
78 | } |
79 | ||
80 | /// Adds a code region to be counted by an injected counter intrinsic. | |
add651ee | 81 | pub fn add_counter(&mut self, id: CounterId, region: CodeRegion) { |
6a06907d XL |
82 | if let Some(previous_region) = self.counters[id].replace(region.clone()) { |
83 | assert_eq!(previous_region, region, "add_counter: code region for id changed"); | |
84 | } | |
f035d41b XL |
85 | } |
86 | ||
3dfed10e | 87 | /// Both counters and "counter expressions" (or simply, "expressions") can be operands in other |
add651ee FG |
88 | /// expressions. These are tracked as separate variants of `Operand`, so there is no ambiguity |
89 | /// between operands that are counter IDs and operands that are expression IDs. | |
f035d41b XL |
90 | pub fn add_counter_expression( |
91 | &mut self, | |
add651ee FG |
92 | expression_id: ExpressionId, |
93 | lhs: Operand, | |
3dfed10e | 94 | op: Op, |
add651ee | 95 | rhs: Operand, |
29967ef6 | 96 | region: Option<CodeRegion>, |
f035d41b | 97 | ) { |
29967ef6 XL |
98 | debug!( |
99 | "add_counter_expression({:?}, lhs={:?}, op={:?}, rhs={:?} at {:?}", | |
100 | expression_id, lhs, op, rhs, region | |
101 | ); | |
17df50a5 | 102 | debug_assert!( |
add651ee FG |
103 | expression_id.as_usize() < self.expressions.len(), |
104 | "expression_id {} is out of range for expressions.len() = {} | |
17df50a5 | 105 | for {:?}", |
add651ee | 106 | expression_id.as_usize(), |
17df50a5 XL |
107 | self.expressions.len(), |
108 | self, | |
109 | ); | |
add651ee | 110 | if let Some(previous_expression) = self.expressions[expression_id].replace(Expression { |
6a06907d XL |
111 | lhs, |
112 | op, | |
113 | rhs, | |
114 | region: region.clone(), | |
115 | }) { | |
116 | assert_eq!( | |
117 | previous_expression, | |
118 | Expression { lhs, op, rhs, region }, | |
119 | "add_counter_expression: expression for id changed" | |
120 | ); | |
121 | } | |
3dfed10e XL |
122 | } |
123 | ||
124 | /// Add a region that will be marked as "unreachable", with a constant "zero counter". | |
125 | pub fn add_unreachable_region(&mut self, region: CodeRegion) { | |
126 | self.unreachable_regions.push(region) | |
127 | } | |
128 | ||
781aab86 FG |
129 | /// Perform some simplifications to make the final coverage mappings |
130 | /// slightly smaller. | |
131 | /// | |
132 | /// This method mainly exists to preserve the simplifications that were | |
133 | /// already being performed by the Rust-side expression renumbering, so that | |
134 | /// the resulting coverage mappings don't get worse. | |
135 | pub(crate) fn simplify_expressions(&mut self) { | |
136 | // The set of expressions that either were optimized out entirely, or | |
137 | // have zero as both of their operands, and will therefore always have | |
138 | // a value of zero. Other expressions that refer to these as operands | |
139 | // can have those operands replaced with `Operand::Zero`. | |
140 | let mut zero_expressions = FxIndexSet::default(); | |
141 | ||
142 | // For each expression, perform simplifications based on lower-numbered | |
143 | // expressions, and then update the set of always-zero expressions if | |
144 | // necessary. | |
145 | // (By construction, expressions can only refer to other expressions | |
146 | // that have lower IDs, so one simplification pass is sufficient.) | |
147 | for (id, maybe_expression) in self.expressions.iter_enumerated_mut() { | |
148 | let Some(expression) = maybe_expression else { | |
149 | // If an expression is missing, it must have been optimized away, | |
150 | // so any operand that refers to it can be replaced with zero. | |
151 | zero_expressions.insert(id); | |
152 | continue; | |
153 | }; | |
154 | ||
155 | // If an operand refers to an expression that is always zero, then | |
156 | // that operand can be replaced with `Operand::Zero`. | |
157 | let maybe_set_operand_to_zero = |operand: &mut Operand| match &*operand { | |
158 | Operand::Expression(id) if zero_expressions.contains(id) => { | |
159 | *operand = Operand::Zero; | |
160 | } | |
161 | _ => (), | |
162 | }; | |
163 | maybe_set_operand_to_zero(&mut expression.lhs); | |
164 | maybe_set_operand_to_zero(&mut expression.rhs); | |
165 | ||
166 | // Coverage counter values cannot be negative, so if an expression | |
167 | // involves subtraction from zero, assume that its RHS must also be zero. | |
168 | // (Do this after simplifications that could set the LHS to zero.) | |
169 | if let Expression { lhs: Operand::Zero, op: Op::Subtract, .. } = expression { | |
170 | expression.rhs = Operand::Zero; | |
171 | } | |
172 | ||
173 | // After the above simplifications, if both operands are zero, then | |
174 | // we know that this expression is always zero too. | |
175 | if let Expression { lhs: Operand::Zero, rhs: Operand::Zero, .. } = expression { | |
176 | zero_expressions.insert(id); | |
177 | } | |
178 | } | |
179 | } | |
180 | ||
3dfed10e XL |
181 | /// Return the source hash, generated from the HIR node structure, and used to indicate whether |
182 | /// or not the source code structure changed between different compilations. | |
183 | pub fn source_hash(&self) -> u64 { | |
184 | self.source_hash | |
f035d41b XL |
185 | } |
186 | ||
3dfed10e XL |
187 | /// Generate an array of CounterExpressions, and an iterator over all `Counter`s and their |
188 | /// associated `Regions` (from which the LLVM-specific `CoverageMapGenerator` will create | |
189 | /// `CounterMappingRegion`s. | |
a2a8927a XL |
190 | pub fn get_expressions_and_counter_regions( |
191 | &self, | |
192 | ) -> (Vec<CounterExpression>, impl Iterator<Item = (Counter, &CodeRegion)>) { | |
29967ef6 | 193 | assert!( |
cdc7bbd5 XL |
194 | self.source_hash != 0 || !self.is_used, |
195 | "No counters provided the source_hash for used function: {:?}", | |
29967ef6 XL |
196 | self.instance |
197 | ); | |
3dfed10e | 198 | |
781aab86 FG |
199 | let counter_expressions = self.counter_expressions(); |
200 | // Expression IDs are indices into `self.expressions`, and on the LLVM | |
201 | // side they will be treated as indices into `counter_expressions`, so | |
202 | // the two vectors should correspond 1:1. | |
203 | assert_eq!(self.expressions.len(), counter_expressions.len()); | |
204 | ||
3dfed10e | 205 | let counter_regions = self.counter_regions(); |
781aab86 | 206 | let expression_regions = self.expression_regions(); |
3dfed10e XL |
207 | let unreachable_regions = self.unreachable_regions(); |
208 | ||
209 | let counter_regions = | |
210 | counter_regions.chain(expression_regions.into_iter().chain(unreachable_regions)); | |
211 | (counter_expressions, counter_regions) | |
212 | } | |
213 | ||
a2a8927a | 214 | fn counter_regions(&self) -> impl Iterator<Item = (Counter, &CodeRegion)> { |
3dfed10e XL |
215 | self.counters.iter_enumerated().filter_map(|(index, entry)| { |
216 | // Option::map() will return None to filter out missing counters. This may happen | |
217 | // if, for example, a MIR-instrumented counter is removed during an optimization. | |
cdc7bbd5 | 218 | entry.as_ref().map(|region| (Counter::counter_value_reference(index), region)) |
3dfed10e XL |
219 | }) |
220 | } | |
221 | ||
781aab86 FG |
222 | /// Convert this function's coverage expression data into a form that can be |
223 | /// passed through FFI to LLVM. | |
224 | fn counter_expressions(&self) -> Vec<CounterExpression> { | |
225 | // We know that LLVM will optimize out any unused expressions before | |
226 | // producing the final coverage map, so there's no need to do the same | |
227 | // thing on the Rust side unless we're confident we can do much better. | |
228 | // (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.) | |
3dfed10e | 229 | |
781aab86 FG |
230 | self.expressions |
231 | .iter() | |
232 | .map(|expression| match expression { | |
233 | None => { | |
234 | // This expression ID was allocated, but we never saw the | |
235 | // actual expression, so it must have been optimized out. | |
236 | // Replace it with a dummy expression, and let LLVM take | |
237 | // care of omitting it from the expression list. | |
238 | CounterExpression::DUMMY | |
cdc7bbd5 | 239 | } |
781aab86 FG |
240 | &Some(Expression { lhs, op, rhs, .. }) => { |
241 | // Convert the operands and operator as normal. | |
242 | CounterExpression::new( | |
243 | Counter::from_operand(lhs), | |
244 | match op { | |
245 | Op::Add => ExprKind::Add, | |
246 | Op::Subtract => ExprKind::Subtract, | |
247 | }, | |
248 | Counter::from_operand(rhs), | |
249 | ) | |
29967ef6 | 250 | } |
781aab86 FG |
251 | }) |
252 | .collect::<Vec<_>>() | |
253 | } | |
254 | ||
255 | fn expression_regions(&self) -> Vec<(Counter, &CodeRegion)> { | |
256 | // Find all of the expression IDs that weren't optimized out AND have | |
257 | // an attached code region, and return the corresponding mapping as a | |
258 | // counter/region pair. | |
259 | self.expressions | |
260 | .iter_enumerated() | |
261 | .filter_map(|(id, expression)| { | |
262 | let code_region = expression.as_ref()?.region.as_ref()?; | |
263 | Some((Counter::expression(id), code_region)) | |
264 | }) | |
265 | .collect::<Vec<_>>() | |
f035d41b XL |
266 | } |
267 | ||
a2a8927a | 268 | fn unreachable_regions(&self) -> impl Iterator<Item = (Counter, &CodeRegion)> { |
781aab86 | 269 | self.unreachable_regions.iter().map(|region| (Counter::ZERO, region)) |
f035d41b | 270 | } |
f035d41b | 271 | } |