-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
much faster code-coverage for packages #57988
Open
vtjnash
wants to merge
1
commit into
master
Choose a base branch
from
jn/fast-coverage
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+85
−64
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,8 @@ | |
|
||
module StaticData | ||
|
||
using Core: CodeInstance, MethodInstance | ||
using Base: get_world_counter | ||
using .Core: CodeInstance, MethodInstance | ||
using .Base: JLOptions, Compiler, get_world_counter, _methods_by_ftype, get_methodtable | ||
|
||
const WORLD_AGE_REVALIDATION_SENTINEL::UInt = 1 | ||
const _jl_debug_method_invalidation = Ref{Union{Nothing,Vector{Any}}}(nothing) | ||
|
@@ -73,6 +73,51 @@ end | |
|
||
get_require_world() = unsafe_load(cglobal(:jl_require_world, UInt)) | ||
|
||
function gen_staged_sig(def::Method, mi::MethodInstance) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file feels like a slightly strange place to define this, but obviously not really a problem |
||
isdefined(def, :generator) || return nothing | ||
isdispatchtuple(mi.specTypes) || return nothing | ||
gen = Core.Typeof(def.generator) | ||
return Tuple{gen, UInt, Method, Vararg} | ||
## more precise method lookup, but more costly and likely not actually better? | ||
#tts = (mi.specTypes::DataType).parameters | ||
#sps = Any[Core.Typeof(mi.sparam_vals[i]) for i in 1:length(mi.sparam_vals)] | ||
#if def.isva | ||
# return Tuple{gen, UInt, Method, sps..., tts[1:def.nargs - 1]..., Tuple{tts[def.nargs - 1:end]...}} | ||
#else | ||
# return Tuple{gen, UInt, Method, sps..., tts...} | ||
#end | ||
end | ||
|
||
function needs_instrumentation(codeinst::CodeInstance, mi::MethodInstance, def::Method) | ||
if JLOptions().code_coverage != 0 || JLOptions().malloc_log != 0 | ||
# test if the code needs to run with instrumentation, in which case we cannot use existing generated code | ||
if isdefined(def, :debuginfo) ? # generated_only functions do not have debuginfo, so fall back to considering their codeinst debuginfo though this may be slower (and less accurate?) | ||
Compiler.should_instrument(def.module, def.debuginfo) : | ||
Compiler.should_instrument(def.module, codeinst.debuginfo) | ||
return true | ||
end | ||
gensig = gen_staged_sig(def, mi) | ||
if gensig !== nothing | ||
# if this is defined by a generator, try to consider forcing re-running the generators too, to add coverage for them | ||
minworld = Ref{UInt}(1) | ||
maxworld = Ref{UInt}(typemax(UInt)) | ||
has_ambig = Ref{Int32}(0) | ||
result = _methods_by_ftype(gensig, nothing, -1, validation_world, #=ambig=#false, minworld, maxworld, has_ambig) | ||
if result !== nothing | ||
for k = 1:length(result) | ||
match = result[k]::Core.MethodMatch | ||
genmethod = match.method | ||
# no, I refuse to refuse to recurse into your cursed generated function generators and will only test one level deep here | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😆 |
||
if isdefined(genmethod, :debuginfo) && Compiler.should_instrument(genmethod.module, genmethod.debuginfo) | ||
return true | ||
end | ||
end | ||
end | ||
end | ||
end | ||
return false | ||
end | ||
|
||
# Test all edges relevant to a method: | ||
# - Visit the entire call graph, starting from edges[idx] to determine if that method is valid | ||
# - Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable | ||
|
@@ -84,6 +129,12 @@ function verify_method(codeinst::CodeInstance, stack::Vector{CodeInstance}, visi | |
return 0, world, max_valid2 | ||
end | ||
end | ||
mi = get_ci_mi(codeinst) | ||
def = mi.def::Method | ||
if needs_instrumentation(codeinst, mi, def) | ||
return 0, world, UInt(0) | ||
end | ||
|
||
# Implicitly referenced bindings in the current module do not get explicit edges. | ||
# If they were invalidated, they'll be in `mwis`. If they weren't, they imply a minworld | ||
# of `get_require_world`. In principle, this is only required for methods that do reference | ||
|
@@ -92,8 +143,6 @@ function verify_method(codeinst::CodeInstance, stack::Vector{CodeInstance}, visi | |
# but no implicit edges) is rare and there would be little benefit to lower the minworld for it | ||
# in any case, so we just always use `get_require_world` here. | ||
local minworld::UInt, maxworld::UInt = get_require_world(), validation_world | ||
def = get_ci_mi(codeinst).def | ||
@assert def isa Method | ||
if haskey(visiting, codeinst) | ||
return visiting[codeinst], minworld, maxworld | ||
end | ||
|
@@ -226,7 +275,7 @@ function verify_method(codeinst::CodeInstance, stack::Vector{CodeInstance}, visi | |
end | ||
@atomic :monotonic child.max_world = maxworld | ||
if maxworld == validation_world && validation_world == get_world_counter() | ||
Base.Compiler.store_backedges(child, child.edges) | ||
Compiler.store_backedges(child, child.edges) | ||
end | ||
@assert visiting[child] == length(stack) + 1 | ||
delete!(visiting, child) | ||
|
@@ -244,7 +293,7 @@ function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n | |
minworld = Ref{UInt}(1) | ||
maxworld = Ref{UInt}(typemax(UInt)) | ||
has_ambig = Ref{Int32}(0) | ||
result = Base._methods_by_ftype(sig, nothing, lim, world, #=ambig=#false, minworld, maxworld, has_ambig) | ||
result = _methods_by_ftype(sig, nothing, lim, world, #=ambig=#false, minworld, maxworld, has_ambig) | ||
if result === nothing | ||
maxworld[] = 0 | ||
else | ||
|
@@ -306,11 +355,11 @@ function verify_invokesig(@nospecialize(invokesig), expected::Method, world::UIn | |
else | ||
minworld = 1 | ||
maxworld = typemax(UInt) | ||
mt = Base.get_methodtable(expected) | ||
mt = get_methodtable(expected) | ||
if mt === nothing | ||
maxworld = 0 | ||
else | ||
matched, valid_worlds = Base.Compiler._findsup(invokesig, mt, world) | ||
matched, valid_worlds = Compiler._findsup(invokesig, mt, world) | ||
minworld, maxworld = valid_worlds.min_world, valid_worlds.max_world | ||
if matched === nothing | ||
maxworld = 0 | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
The name here sounds like it means "check whether instrumentation is enabled for
m
" but really it seems to determine whetherm
needs to have instrumentation. Mayberequires_instrumentation
?