Skip to content
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

Merged
merged 1 commit into from
Apr 3, 2021

Conversation

richkadel
Copy link
Contributor

@richkadel richkadel commented Apr 2, 2021

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

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 2, 2021
@richkadel
Copy link
Contributor Author

I'll rebase this, and it will only change 10 files. I need to wrap up #83755 first though.

These are the changed files:

  • compiler/rustc_codegen_ssa/src/coverageinfo/ffi.rs
  • compiler/rustc_codegen_ssa/src/coverageinfo/map.rs
  • compiler/rustc_codegen_ssa/src/mir/coverageinfo.rs
  • compiler/rustc_middle/src/mir/coverage.rs
  • src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.async.txt
  • src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.doctest.txt
  • src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.inline.txt
  • src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.loops_branches.txt
  • src/test/run-make-fulldeps/coverage/async.rs
  • src/test/run-make-fulldeps/coverage/loops_branches.rs

@richkadel richkadel force-pushed the zero-based-counters branch 4 times, most recently from e947b8b to 0cade41 Compare April 2, 2021 18:52
@richkadel
Copy link
Contributor Author

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 add_unused_function_coverage(). I no longer need to do that.

@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)
Copy link
Member

@nagisa nagisa Apr 2, 2021

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

@richkadel
Copy link
Contributor Author

@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.

@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 optimized_mir() (where the missing coverage has already been dropped).

I want to get a version of the MIR prior to optimizations, but calls like mir_promoted and promoted_mir only return results that have already been "stolen" (I think that means I can't access them anymore).

So let's land this PR as-is and look at what to do to get the dropped coverage at a later time.

/// 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);
Copy link
Member

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

Copy link
Contributor Author

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)

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!
@tmandry
Copy link
Member

tmandry commented Apr 3, 2021

@bors r+

@bors
Copy link
Contributor

bors commented Apr 3, 2021

📌 Commit 7ceff68 has been approved by tmandry

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 3, 2021
Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this pull request Apr 3, 2021
…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`
@bors
Copy link
Contributor

bors commented Apr 3, 2021

⌛ Testing commit 7ceff68 with merge 836c317...

@bors
Copy link
Contributor

bors commented Apr 3, 2021

☀️ Test successful - checks-actions
Approved by: tmandry
Pushing 836c317 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Apr 3, 2021
@bors bors merged commit 836c317 into rust-lang:master Apr 3, 2021
@rustbot rustbot added this to the 1.53.0 milestone Apr 3, 2021
pirama-arumuga-nainar added a commit to llvm/llvm-project that referenced this pull request May 19, 2021
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
arichardson pushed a commit to arichardson/llvm-project that referenced this pull request Sep 12, 2021
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants