Skip to content

Commit 7ad2ea5

Browse files
committed
code-coverage: report coverage for effect-free lines
fixes #28192
1 parent 756891d commit 7ad2ea5

15 files changed

+111
-60
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1222,7 +1222,8 @@ function typeinf_local(frame::InferenceState)
12221222
if isa(fname, Slot)
12231223
changes = StateUpdate(fname, VarState(Any, false), changes)
12241224
end
1225-
elseif hd === :inbounds || hd === :meta || hd === :loopinfo
1225+
elseif hd === :inbounds || hd === :meta || hd === :loopinfo || hd == :code_coverage_effect
1226+
# these do not generate code
12261227
else
12271228
t = abstract_eval(stmt, changes, frame)
12281229
t === Bottom && break

base/compiler/optimize.jl

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ function optimize(opt::OptimizationState, @nospecialize(result))
198198
opt.src.pure = true
199199
end
200200

201-
if proven_pure && !coverage_enabled()
201+
if proven_pure
202202
# use constant calling convention
203203
# Do not emit `jl_fptr_const_return` if coverage is enabled
204204
# so that we don't need to add coverage support
@@ -408,14 +408,19 @@ function renumber_ir_elements!(body::Vector{Any}, ssachangemap::Vector{Int}, lab
408408
ssachangemap[i] += ssachangemap[i - 1]
409409
end
410410
end
411-
(labelchangemap[end] != 0 && ssachangemap[end] != 0) || return
411+
if labelchangemap[end] == 0 && ssachangemap[end] == 0
412+
return
413+
end
412414
for i = 1:length(body)
413415
el = body[i]
414416
if isa(el, GotoNode)
415417
body[i] = GotoNode(el.label + labelchangemap[el.label])
416418
elseif isa(el, SSAValue)
417419
body[i] = SSAValue(el.id + ssachangemap[el.id])
418420
elseif isa(el, Expr)
421+
if el.head === :(=) && el.args[2] isa Expr
422+
el = el.args[2]::Expr
423+
end
419424
if el.head === :gotoifnot
420425
cond = el.args[1]
421426
if isa(cond, SSAValue)
@@ -427,9 +432,6 @@ function renumber_ir_elements!(body::Vector{Any}, ssachangemap::Vector{Int}, lab
427432
tgt = el.args[1]::Int
428433
el.args[1] = tgt + labelchangemap[tgt]
429434
elseif !is_meta_expr_head(el.head)
430-
if el.head === :(=) && el.args[2] isa Expr && !is_meta_expr_head(el.args[2].head)
431-
el = el.args[2]::Expr
432-
end
433435
args = el.args
434436
for i = 1:length(args)
435437
el = args[i]

base/compiler/ssair/driver.jl

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,28 +49,45 @@ function normalize(@nospecialize(stmt), meta::Vector{Any})
4949
return stmt
5050
end
5151

52-
function just_construct_ssa(ci::CodeInfo, code::Vector{Any}, nargs::Int, sv::OptimizationState)
52+
function convert_to_ircode(ci::CodeInfo, code::Vector{Any}, coverage::Bool, nargs::Int, sv::OptimizationState)
5353
# Go through and add an unreachable node after every
5454
# Union{} call. Then reindex labels.
5555
idx = 1
5656
oldidx = 1
5757
changemap = fill(0, length(code))
58+
labelmap = coverage ? fill(0, length(code)) : changemap
59+
prevloc = zero(eltype(ci.codelocs))
5860
while idx <= length(code)
61+
codeloc = ci.codelocs[idx]
62+
if coverage && codeloc != prevloc && codeloc != 0
63+
# insert a side-effect instruction before the current instruction in the same basic block
64+
insert!(code, idx, Expr(:code_coverage_marker))
65+
insert!(ci.codelocs, idx, codeloc)
66+
insert!(ci.ssavaluetypes, idx, Nothing)
67+
changemap[oldidx] += 1
68+
if oldidx < length(labelmap)
69+
labelmap[oldidx + 1] += 1
70+
end
71+
idx += 1
72+
prevloc = codeloc
73+
end
5974
if code[idx] isa Expr && ci.ssavaluetypes[idx] === Union{}
60-
if !(idx < length(code) && isexpr(code[idx+1], :unreachable))
75+
if !(idx < length(code) && isexpr(code[idx + 1], :unreachable))
76+
# insert unreachable in the same basic block after the current instruction (splitting it)
6177
insert!(code, idx + 1, ReturnNode())
6278
insert!(ci.codelocs, idx + 1, ci.codelocs[idx])
6379
insert!(ci.ssavaluetypes, idx + 1, Union{})
6480
if oldidx < length(changemap)
65-
changemap[oldidx + 1] = 1
81+
changemap[oldidx + 1] += 1
82+
coverage && (labelmap[oldidx + 1] += 1)
6683
end
6784
idx += 1
6885
end
6986
end
7087
idx += 1
7188
oldidx += 1
7289
end
73-
renumber_ir_elements!(code, changemap)
90+
renumber_ir_elements!(code, changemap, labelmap)
7491

7592
inbounds_depth = 0 # Number of stacked inbounds
7693
meta = Any[]
@@ -99,17 +116,22 @@ function just_construct_ssa(ci::CodeInfo, code::Vector{Any}, nargs::Int, sv::Opt
99116
end
100117
strip_trailing_junk!(ci, code, flags)
101118
cfg = compute_basic_blocks(code)
102-
defuse_insts = scan_slot_def_use(nargs, ci, code)
103-
@timeit "domtree 1" domtree = construct_domtree(cfg)
104-
ir = let code = Any[nothing for _ = 1:length(code)]
105-
IRCode(code, Any[], ci.codelocs, flags, cfg, collect(LineInfoNode, ci.linetable), sv.slottypes, meta, sv.sptypes)
106-
end
107-
@timeit "construct_ssa" ir = construct_ssa!(ci, code, ir, domtree, defuse_insts, nargs, sv.sptypes, sv.slottypes)
119+
ir = IRCode(code, Any[], ci.codelocs, flags, cfg, collect(LineInfoNode, ci.linetable), sv.slottypes, meta, sv.sptypes)
120+
return ir
121+
end
122+
123+
function slot2reg(ir::IRCode, ci::CodeInfo, nargs::Int, sv::OptimizationState)
124+
# need `ci` for the slot metadata, IR for the code
125+
@timeit "domtree 1" domtree = construct_domtree(ir.cfg)
126+
defuse_insts = scan_slot_def_use(nargs, ci, ir.stmts)
127+
@timeit "construct_ssa" ir = construct_ssa!(ci, ir, domtree, defuse_insts, nargs, sv.sptypes, sv.slottypes) # consumes `ir`
108128
return ir
109129
end
110130

111131
function run_passes(ci::CodeInfo, nargs::Int, sv::OptimizationState)
112-
ir = just_construct_ssa(ci, copy_exprargs(ci.code), nargs, sv)
132+
preserve_coverage = coverage_enabled(sv.mod)
133+
ir = convert_to_ircode(ci, copy_exprargs(ci.code), preserve_coverage, nargs, sv)
134+
ir = slot2reg(ir, ci, nargs, sv)
113135
#@Base.show ("after_construct", ir)
114136
# TODO: Domsorting can produce an updated domtree - no need to recompute here
115137
@timeit "compact 1" ir = compact!(ir)

base/compiler/ssair/slot2ssa.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,9 @@ function recompute_type(node::Union{PhiNode, PhiCNode}, ci::CodeInfo, ir::IRCode
569569
return new_typ
570570
end
571571

572-
function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::DomTree, defuse, nargs::Int, sptypes::Vector{Any},
572+
function construct_ssa!(ci::CodeInfo, ir::IRCode, domtree::DomTree, defuse, nargs::Int, sptypes::Vector{Any},
573573
slottypes::Vector{Any})
574+
code = ir.stmts
574575
cfg = ir.cfg
575576
left = Int[]
576577
catch_entry_blocks = Tuple{Int, Int}[]

base/compiler/utilities.jl

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,22 @@ end
227227
# options #
228228
###########
229229

230+
is_root_module(m::Module) = false
231+
230232
inlining_enabled() = (JLOptions().can_inline == 1)
231-
coverage_enabled() = (JLOptions().code_coverage != 0)
233+
function coverage_enabled(m::Module)
234+
ccall(:jl_generating_output, Cint, ()) == 0 || return false # don't alter caches
235+
cov = JLOptions().code_coverage
236+
if cov == 1
237+
m = moduleroot(m)
238+
m === Core && return false
239+
isdefined(Main, :Base) && m === Main.Base && return false
240+
return true
241+
elseif cov == 2
242+
return true
243+
end
244+
return false
245+
end
232246
function inbounds_option()
233247
opt_check_bounds = JLOptions().check_bounds
234248
opt_check_bounds == 0 && return :default

base/compiler/validation.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const VALID_EXPR_HEADS = IdDict{Any,Any}(
2626
:foreigncall => 5:typemax(Int), # name, RT, AT, nreq, cconv, args..., roots...
2727
:cfunction => 5:5,
2828
:isdefined => 1:1,
29+
:code_coverage_effect => 0:0,
2930
:loopinfo => 0:typemax(Int),
3031
:gc_preserve_begin => 0:typemax(Int),
3132
:gc_preserve_end => 0:typemax(Int),
@@ -144,7 +145,7 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_
144145
head === :const || head === :enter || head === :leave || head === :pop_exception ||
145146
head === :method || head === :global || head === :static_parameter ||
146147
head === :new || head === :splatnew || head === :thunk || head === :loopinfo ||
147-
head === :throw_undef_if_not || head === :unreachable
148+
head === :throw_undef_if_not || head === :unreachable || head === :code_coverage_effect
148149
validate_val!(x)
149150
else
150151
# TODO: nothing is actually in statement position anymore

base/reflection.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function moduleroot(m::Module)
4242
while true
4343
is_root_module(m) && return m
4444
p = parentmodule(m)
45-
p == m && return m
45+
p === m && return m
4646
m = p
4747
end
4848
end

src/ast.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jl_sym_t *nospecialize_sym; jl_sym_t *macrocall_sym;
6060
jl_sym_t *colon_sym; jl_sym_t *hygienicscope_sym;
6161
jl_sym_t *throw_undef_if_not_sym; jl_sym_t *getfield_undefref_sym;
6262
jl_sym_t *gc_preserve_begin_sym; jl_sym_t *gc_preserve_end_sym;
63-
jl_sym_t *escape_sym;
63+
jl_sym_t *coverageeffect_sym; jl_sym_t *escape_sym;
6464
jl_sym_t *aliasscope_sym; jl_sym_t *popaliasscope_sym;
6565

6666
static uint8_t flisp_system_image[] = {
@@ -365,6 +365,7 @@ void jl_init_frontend(void)
365365
throw_undef_if_not_sym = jl_symbol("throw_undef_if_not");
366366
getfield_undefref_sym = jl_symbol("##getfield##");
367367
do_sym = jl_symbol("do");
368+
coverageeffect_sym = jl_symbol("code_coverage_marker");
368369
aliasscope_sym = jl_symbol("aliasscope");
369370
popaliasscope_sym = jl_symbol("popaliasscope");
370371
}

src/codegen.cpp

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,7 +1750,7 @@ const int logdata_blocksize = 32; // target getting nearby lines in the same gen
17501750
typedef uint64_t logdata_block[logdata_blocksize];
17511751
typedef StringMap< std::vector<logdata_block*> > logdata_t;
17521752

1753-
static void visitLine(jl_codectx_t &ctx, std::vector<logdata_block*> &vec, int line, Value *addend, const char* name)
1753+
static uint64_t *allocLine(std::vector<logdata_block*> &vec, int line)
17541754
{
17551755
unsigned block = line / logdata_blocksize;
17561756
line = line % logdata_blocksize;
@@ -1762,8 +1762,14 @@ static void visitLine(jl_codectx_t &ctx, std::vector<logdata_block*> &vec, int l
17621762
logdata_block &data = *vec[block];
17631763
if (data[line] == 0)
17641764
data[line] = 1;
1765+
return &data[line];
1766+
}
1767+
1768+
static void visitLine(jl_codectx_t &ctx, std::vector<logdata_block*> &vec, int line, Value *addend, const char* name)
1769+
{
1770+
uint64_t *ptr = allocLine(vec, line);
17651771
Value *pv = ConstantExpr::getIntToPtr(
1766-
ConstantInt::get(T_size, (uintptr_t)&data[line]),
1772+
ConstantInt::get(T_size, (uintptr_t)ptr),
17671773
T_pint64);
17681774
Value *v = ctx.builder.CreateLoad(pv, true, name);
17691775
v = ctx.builder.CreateAdd(v, addend);
@@ -1783,6 +1789,14 @@ static void coverageVisitLine(jl_codectx_t &ctx, StringRef filename, int line)
17831789
visitLine(ctx, coverageData[filename], line, ConstantInt::get(T_int64, 1), "lcnt");
17841790
}
17851791

1792+
static void coverageAllocLine(StringRef filename, int line)
1793+
{
1794+
assert(!imaging_mode);
1795+
if (filename == "" || filename == "none" || filename == "no file" || filename == "<missing>" || line < 0)
1796+
return;
1797+
allocLine(coverageData[filename], line);
1798+
}
1799+
17861800
// Memory allocation log (malloc_log)
17871801

17881802
static logdata_t mallocData;
@@ -4012,7 +4026,8 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result)
40124026
jl_expr_t *ex = (jl_expr_t*)expr;
40134027
jl_value_t **args = (jl_value_t**)jl_array_data(ex->args);
40144028
jl_sym_t *head = ex->head;
4015-
if (head == meta_sym || head == inbounds_sym || head == aliasscope_sym || head == popaliasscope_sym) {
4029+
if (head == meta_sym || head == inbounds_sym || head == coverageeffect_sym
4030+
|| head == aliasscope_sym || head == popaliasscope_sym) {
40164031
// some expression types are metadata and can be ignored
40174032
// in statement position
40184033
return;
@@ -4293,26 +4308,10 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval)
42934308
I->setMetadata("julia.loopinfo", MD);
42944309
return jl_cgval_t();
42954310
}
4296-
else if (head == goto_ifnot_sym) {
4297-
jl_error("Expr(:goto_ifnot) in value position");
4298-
}
4299-
else if (head == leave_sym) {
4300-
jl_error("Expr(:leave) in value position");
4301-
}
4302-
else if (head == pop_exception_sym) {
4303-
jl_error("Expr(:pop_exception) in value position");
4304-
}
4305-
else if (head == enter_sym) {
4306-
jl_error("Expr(:enter) in value position");
4307-
}
4308-
else if (head == inbounds_sym) {
4309-
jl_error("Expr(:inbounds) in value position");
4310-
}
4311-
else if (head == aliasscope_sym) {
4312-
jl_error("Expr(:aliasscope) in value position");
4313-
}
4314-
else if (head == popaliasscope_sym) {
4315-
jl_error("Expr(:popaliasscope) in value position");
4311+
else if (head == goto_ifnot_sym || head == leave_sym || head == coverageeffect_sym
4312+
|| head == pop_exception_sym || head == enter_sym || head == inbounds_sym
4313+
|| head == aliasscope_sym || head == popaliasscope_sym) {
4314+
jl_errorf("Expr(:%s) in value position", jl_symbol_name(head));
43164315
}
43174316
else if (head == boundscheck_sym) {
43184317
return mark_julia_const(bounds_check_enabled(ctx, jl_true) ? jl_true : jl_false);
@@ -6416,6 +6415,12 @@ static std::unique_ptr<Module> emit_function(
64166415
dbg = linetable.at(dbg).inlined_at;
64176416
mallocVisitLine(ctx, ctx.file, linetable.at(dbg).line);
64186417
};
6418+
if (coverage_mode != JL_LOG_NONE) {
6419+
// record all lines that could be covered
6420+
for (const auto &info : linetable)
6421+
if (do_coverage(info.is_user_code))
6422+
coverageAllocLine(info.file, info.line);
6423+
}
64196424

64206425
come_from_bb[0] = ctx.builder.GetInsertBlock();
64216426

src/interpreter.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s)
543543
else if (head == boundscheck_sym) {
544544
return jl_true;
545545
}
546-
else if (head == meta_sym || head == inbounds_sym || head == loopinfo_sym) {
546+
else if (head == meta_sym || head == coverageeffect_sym || head == inbounds_sym || head == loopinfo_sym) {
547547
return jl_nothing;
548548
}
549549
else if (head == gc_preserve_begin_sym || head == gc_preserve_end_sym) {

0 commit comments

Comments
 (0)