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

Dart VM code coverage reports may include false negatives #42061

Open
rmacnak-google opened this issue May 26, 2020 · 11 comments
Open

Dart VM code coverage reports may include false negatives #42061

rmacnak-google opened this issue May 26, 2020 · 11 comments
Labels
area-vm Use area-vm for VM related issues, including code coverage, FFI, and the AOT and JIT backends. vm-regression

Comments

@rmacnak-google
Copy link
Contributor

Code coverage reports from the VM are based on invocation counters on functions (Function) and call sites (ICData). If the invocation counter is non-zero, the position is a hit, otherwise a miss. When code coverage was originally added, the optimizer was much simpler. A call site would not be inlined unless it was executed, so every call site that ever executed would increment the ICData usage counter before being optimized away (probably modulo a small set or recognized static functions). Since then, the optimizer has gotten smarter and may inline calls with a zero usage count (during AOT compilation all usage counts are zero). The language has also changed to enforced static types, giving the optimizer an opportunity to inline away calls before any type feedback is collected. If the VM inlines away a call site before its first invocation, code coverage will report it as a miss. This means currently the only way to get accurate coverage data is to run with the VM with the optimizer disabled, --optimization_counter_threshold=-1.

This comes a heavy performance cost. We should add a flag that specifically disables only the optimizations that interfere with code coverage: inlining or otherwise replacing call sites with a zero usage count.

@bkonyi @MichaelRFairhurst

@rmacnak-google rmacnak-google added area-vm Use area-vm for VM related issues, including code coverage, FFI, and the AOT and JIT backends. vm-regression labels May 26, 2020
@mraleph
Copy link
Member

mraleph commented May 27, 2020

I think we should consider emitting coverage collecting instructions into the graph (similar to DebugBreak) and decouple it from an already existing machinery - I think this might simplify both coverage and compiler itself.

Optimiser then could contain a very simple canonicalisation rule which removes coverage instructions which already have triggered.

@rmacnak-google
Copy link
Contributor Author

@liamappelbe

@rmacnak-google
Copy link
Contributor Author

The situation is probably somewhat improved after, i.e., 8896894 and 2eb4ea5, though I'm not sure if this handles everything that might be optimized away.

@stereotype441
Copy link
Member

@jensjoha
Copy link
Contributor

jensjoha commented Jun 6, 2024

8896894 doesn't really work either because (I think) RecordCoverageInstr goes away with optimization --- see https://dart-review.googlesource.com/c/sdk/+/369920

/cc @mraleph

@jensjoha
Copy link
Contributor

jensjoha commented Jun 7, 2024

https://dart-review.googlesource.com/c/sdk/+/370220 could make it not go away with optimization.

copybara-service bot pushed a commit that referenced this issue Jun 10, 2024
Dead Code Elimination can remove `RecordCoverageInstr`, but shouldn't.
This CL makes it stay by returning true in `HasUnknownSideEffects`
making `MayHaveVisibleEffect` return true, making Dead Code Elimination
keep it.
Note that `RecordCoverageInstr::Canonicalize` will still let it go away
if the position is already covered which is what we want.

Bug: #42061

TEST=pkg/vm_service/test/coverage_closure_call_after_optimization_test.dart,vm/cc/IL_RecordCoverageSurvivesOptimizations

Change-Id: Ifd72f9071a51924fd71f3dae91687acb1467047d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/370220
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
@liamappelbe
Copy link
Contributor

We already insert some of these instructions behind the --branch-coverage flag. So I guess what we're proposing here is to add the instruction in way more places, to totally replace the ICData based coverage logic.

We'll still want to gate this behind a flag, because the extra instructions will significantly bloat the binary. So either way, users will have to pass a VM flag to collect coverage. The only difference between a --coverage flag and the existing --optimization_counter_threshold=-1 flag is that --coverage will have a much smaller performance impact.

@jensjoha
Copy link
Contributor

Also worth noting is that --optimization_counter_threshold=-1 for whatever reason does not produce the right output.

@mraleph
Copy link
Member

mraleph commented Jun 11, 2024

We'll still want to gate this behind a flag, because the extra instructions will significantly bloat the binary.

It's unclear if that really matters that much. I think the only binary we could potentially care about is app-jit snapshot size - so we could add a flag which we would disable during app-jit training, but keep enabled by default otherwise. JIT snapshots for tools are on their way out anyway, so it's a really niche thing.

The size of unoptimized code might be of some consideration (we should probably measure the impact), but I am not sure if it is worth worrying about it too much (and it is already rather bloated).

@liamappelbe
Copy link
Contributor

It's unclear if that really matters that much.

I think I've missed something. Are these coverage instructions being removed in AOT builds somehow?

@mraleph
Copy link
Member

mraleph commented Jun 12, 2024

It's unclear if that really matters that much.

I think I've missed something. Are these coverage instructions being removed in AOT builds somehow?

Right now they are never emitted in AOT in the first place. See SupportsCoverage:

static bool SupportsCoverage() {
#if defined(PRODUCT)
return false;
#else
return !CompilerState::Current().is_aot();
#endif
}

FWIW AOT builds don't use ICs for method dispatching and all the code is specialized and optimized - so you can't collect good coverage data with the current approach anyway. With RecordCoverage instructions we can actually support collecting coverage from AOT builds if we wanted. (and if that's indeed something that we wanted we could also do few local optimizations to significantly limit code size blow out as well)

copybara-service bot pushed a commit that referenced this issue Jun 17, 2024
Before this CL, becase of optimizations, coverage of some of these goes
away, making coverage unreliable.

I believe this fixes the issues for "regular" runs (at least it seems to
be stable on the CFE coverage tests).

If setting `--optimization-counter-threshold=-1` there'll still be
trouble though and we would have to also insert these calls in the start
of FunctionBody and the start of FieldInitializer for it to produce the
same results.

TEST=pkg/vm_service/test/coverage_instance_call_after_optimization_test.dart,pkg/vm_service/test/coverage_static_call_after_optimization_test.dart

Bug: #42061
Bug: #55959
Bug: #56018

Change-Id: I34947f0d4b123e52ce67b71a195782d31e4bda16
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/370501
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-vm Use area-vm for VM related issues, including code coverage, FFI, and the AOT and JIT backends. vm-regression
Projects
None yet
Development

No branches or pull requests

5 participants