-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Translate counters from Rust 1-based to LLVM 0-based counter ids #83774
Conversation
I'll rebase this, and it will only change 10 files. I need to wrap up #83755 first though. These are the changed files:
|
e947b8b
to
0cade41
Compare
I went ahead and rebased on upstream/master. All of the spanview changed files (8, I think), will go away once #83755 lands. Really cool that this fix resolved another mystery in compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs. Now I know why I had to add both a Counter and an Unreachable for the same region, in @tmandry - I'm looking into making one more change relevant to this PR and will probably add it as a second commit, if it's not too complex. |
if !(lhs_counter.is_zero() && op.is_subtract()) { | ||
debug_assert!( | ||
lhs_counter.is_zero() | ||
|| ((lhs_counter.zero_based_id() as usize) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pedantic nit: This can wrap for usize = u16
, but unclear if the compiler built for a platform where this holds true is going be at all useful.
I would perhaps write this as:
usize::max(self.counters.len(), self.expressions.len()))
.try_into::<u32>()
.map(|v| lhs_counter.zero_based_id() <= v)
.unwrap_or(true)
Though, I guess, if self.counters.len() >= u32::MAX
things are probably already going very wrong everywhere anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I get it, but this is a lot harder to understand what's going on, and why. I think if usize = u16
and the zero_based_id() > usize::MAX
then a lot of things are probably breaking/broken.
Plus this is in a debug_assert!()
, so unless we're going to write a test case that creates thousands of branches to catch this, we're unlikely to even execute this check (in production code, particularly when targeting a small machine like this).
I'm tempted to leave this as-is to save others the mental exercise, but I can add a comment.
@nagisa - You said it's a "nit", but do let me know if you strongly disagree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, I just added a comment above each as usize
so at least the concern is captured there.
0cade41
to
1da70a0
Compare
@tmandry I'm not going to add anything else to this PR. I thought I had an easy solution to adding coverage for branches that are optimized out (e..g., MIR const eval-based), but I forgot that, currently, I can only get the coverage from the I want to get a version of the MIR prior to optimizations, but calls like So let's land this PR as-is and look at what to do to get the dropped coverage at a later time. |
1da70a0
to
df55114
Compare
/// during codegen. LLVM expects zero-based indexes. | ||
pub fn zero_based_index(&self) -> u32 { | ||
let one_based_index = self.as_u32(); | ||
debug_assert!(one_based_index > 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fyi: not necessary since underflow already panics in debug mode
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ack... I think I'll leave it though, just for the readability of the message (looks less like an unexpected bug and more like an assertion)
src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.loops_branches.txt
Outdated
Show resolved
Hide resolved
df55114
to
6ef77e0
Compare
A colleague contacted me and asked why Rust's counters start at 1, when Clangs appear to start at 0. There is a reason why Rust's internal counters start at 1 (see the docs), and I tried to keep them consistent when codegenned to LLVM's coverage mapping format. LLVM should be tolerant of missing counters, but as my colleague pointed out, `llvm-cov` will silently fail to generate a coverage report for a function based on LLVM's assumption that the counters are 0-based. See: https://github.com/llvm/llvm-project/blob/main/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp#L170 Apparently, if, for example, a function has no branches, it would have exactly 1 counter. `CounterValues.size()` would be 1, and (with the 1-based index), the counter ID would be 1. This would fail the check and abort reporting coverage for the function. It turns out that by correcting for this during coverage map generation, by subtracting 1 from the Rust Counter ID (both when generating the counter increment intrinsic call, and when adding counters to the map), some uncovered functions (including in tests) now appear covered! This corrects the coverage for a few tests!
6ef77e0
to
7ceff68
Compare
@bors r+ |
📌 Commit 7ceff68 has been approved by |
…andry Translate counters from Rust 1-based to LLVM 0-based counter ids A colleague contacted me and asked why Rust's counters start at 1, when Clangs appear to start at 0. There is a reason why Rust's internal counters start at 1 (see the docs), and I tried to keep them consistent when codegenned to LLVM's coverage mapping format. LLVM should be tolerant of missing counters, but as my colleague pointed out, `llvm-cov` will silently fail to generate a coverage report for a function based on LLVM's assumption that the counters are 0-based. See: https://github.com/llvm/llvm-project/blob/main/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp#L170 Apparently, if, for example, a function has no branches, it would have exactly 1 counter. `CounterValues.size()` would be 1, and (with the 1-based index), the counter ID would be 1. This would fail the check and abort reporting coverage for the function. It turns out that by correcting for this during coverage map generation, by subtracting 1 from the Rust Counter ID (both when generating the counter increment intrinsic call, and when adding counters to the map), some uncovered functions (including in tests) now appear covered! This corrects the coverage for a few tests! r? `@tmandry` FYI: `@wesleywiser`
☀️ Test successful - checks-actions |
For source-based coverage, the frontend sets the counter IDs and the constraints of counter IDs is not defined. For e.g., the Rust frontend until recently had a reserved counter #0 (rust-lang/rust#83774). Rust coverage instrumentation also creates counters on edges in addition to basic blocks. Some functions may have more counters than regions. This breaks an assumption in CoverageMapping.cpp where the number of counters in a function is assumed to be bounded by the number of regions: Counts.assign(Record.MappingRegions.size(), 0); This assumption causes CounterMappingContext::evaluate() to fail since there are not enough counter values created in the above call to `Counts.assign`. Consequently, some uncovered functions are not reported in coverage reports. This change walks a Function's CoverageMappingRecord to find the maximum counter ID, and uses it to initialize the counter array when instrprof records are missing for a function in sparse profiles. Differential Revision: https://reviews.llvm.org/D101780
For source-based coverage, the frontend sets the counter IDs and the constraints of counter IDs is not defined. For e.g., the Rust frontend until recently had a reserved counter #0 (rust-lang/rust#83774). Rust coverage instrumentation also creates counters on edges in addition to basic blocks. Some functions may have more counters than regions. This breaks an assumption in CoverageMapping.cpp where the number of counters in a function is assumed to be bounded by the number of regions: Counts.assign(Record.MappingRegions.size(), 0); This assumption causes CounterMappingContext::evaluate() to fail since there are not enough counter values created in the above call to `Counts.assign`. Consequently, some uncovered functions are not reported in coverage reports. This change walks a Function's CoverageMappingRecord to find the maximum counter ID, and uses it to initialize the counter array when instrprof records are missing for a function in sparse profiles. Differential Revision: https://reviews.llvm.org/D101780
A colleague contacted me and asked why Rust's counters start at 1, when
Clangs appear to start at 0. There is a reason why Rust's internal
counters start at 1 (see the docs), and I tried to keep them consistent
when codegenned to LLVM's coverage mapping format. LLVM should be
tolerant of missing counters, but as my colleague pointed out,
llvm-cov
will silently fail to generate a coverage report for afunction based on LLVM's assumption that the counters are 0-based.
See:
https://github.com/llvm/llvm-project/blob/main/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp#L170
Apparently, if, for example, a function has no branches, it would have
exactly 1 counter.
CounterValues.size()
would be 1, and (with the1-based index), the counter ID would be 1. This would fail the check
and abort reporting coverage for the function.
It turns out that by correcting for this during coverage map generation,
by subtracting 1 from the Rust Counter ID (both when generating the
counter increment intrinsic call, and when adding counters to the map),
some uncovered functions (including in tests) now appear covered! This
corrects the coverage for a few tests!
r? @tmandry
FYI: @wesleywiser