Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/compiler/codegen/expressions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,30 @@ function record_coverage!(ctx::CGCtx)
line > 0 || return nothing
file = string(loc.file)
isempty(file) && return nothing
ccall(:jl_coverage_visit_line, Cvoid, (Cstring, Csize_t, Cint),
file, ncodeunits(file), line)
ctx.recorded_coverage = true
return nothing
end

# The per-statement `:code_coverage_effect`s only cover body lines. Julia's codegen
# visits the function-definition (signature) line separately at the prologue
# (`toplineno`); mirror that here, or the signature is reported as missed while the
# body is hit. Only for tracked kernels (body carried coverage effects), to avoid
# reporting signatures of untracked code.
function record_definition_coverage!(ctx::CGCtx)
Base.JLOptions().code_coverage == 0 && return nothing
ctx.recorded_coverage || return nothing
inst = first_located_instruction(ctx.sci, ctx.sci.entry)
inst === nothing && return nothing
stack = source_location(ctx.sci, inst)
isempty(stack) && return nothing
outer = stack[1] # outermost frame = the kernel itself
m = outer.method
m isa MethodInstance && (m = m.def)
file = string(m isa Method ? m.file : outer.file)
line = Int(m isa Method ? m.line : outer.line)
(isempty(file) || line <= 0) && return nothing
ccall(:jl_coverage_visit_line, Cvoid, (Cstring, Csize_t, Cint),
file, ncodeunits(file), line)
return nothing
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/codegen/kernel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ function emit_kernel!(writer::BytecodeWriter, func_buf::Vector{UInt8},
# Emit the structured IR (uses original Julia SSA indices everywhere)
emit_block!(ctx, ctx.sci.entry)

# Cover the function-definition line (Julia's codegen does this at the
# prologue; the per-statement coverage effects only cover body lines).
record_definition_coverage!(ctx)

finalize_function!(func_buf, cb, writer.debug_info)
end

Expand Down
6 changes: 6 additions & 0 deletions src/compiler/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,11 @@ mutable struct CGCtx
# `tuple_element_source` and other parent-walking queries can start
# from the right scope. `nothing` when no block has been entered yet.
current_block::Any

# Set by `record_coverage!` when at least one `:code_coverage_effect` was
# marked for this kernel. Gates the function-definition-line coverage visit
# in `emit_kernel!` so untracked kernels aren't reported.
recorded_coverage::Bool
end

function CGCtx(; cb::CodeBuilder, tt::TypeTable, sci::StructuredIRCode,
Expand Down Expand Up @@ -354,6 +359,7 @@ function CGCtx(; cb::CodeBuilder, tt::TypeTable, sci::StructuredIRCode,
nothing, # bounds_info — set by run_passes!
Dict{Value, Value}(), # assume_wrapped
nothing, # current_block — set by emit_block!
false, # recorded_coverage — set by record_coverage!
)
end

Expand Down
25 changes: 25 additions & 0 deletions test/codegen/coverage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@
return false
end

# Execution count recorded for an exact line, or `nothing` if not instrumented.
function lcov_line_count(tracefile, file, line)
in_block = false
for l in eachline(tracefile)
if startswith(l, "SF:")
in_block = (l == "SF:" * file)
elseif l == "end_of_record"
in_block = false
elseif in_block && startswith(l, "DA:")
ln, cnt = parse.(Int, split(l[4:end], ","))
ln == line && return cnt
end
end
return nothing
end

if Base.JLOptions().code_coverage == 0
@test_skip "requires --code-coverage"
else
Expand All @@ -50,6 +66,15 @@
m = only(methods(f))
@test lcov_any_covered(tracefile, string(m.file), m.line, m.line + 4)
end

# The kernel entry's definition (signature) line specifically must be
# covered — the per-statement coverage effects only mark body lines, so
# without the prologue visit the header shows as missed (red). Use the
# compiled entry `cov_kernel`, not the `@inline`d `cov_only_scale` whose
# signature line is intentionally not marked (Julia doesn't mark it either).
m = only(methods(cov_kernel))
@test lcov_line_count(tracefile, string(m.file), m.line) !== nothing
@test something(lcov_line_count(tracefile, string(m.file), m.line), 0) >= 1
end
end
end