Skip to content

Commit 735d755

Browse files
vtjnashJeffBezanson
authored andcommitted
coverage: support output in LCOV tracefile format (#30381)
Also ensure that `julia_cmd` is forwarding all desirable options, and provide a general framework for specifying formatted filenames for similar such options.
1 parent 072ad7d commit 735d755

File tree

14 files changed

+381
-77
lines changed

14 files changed

+381
-77
lines changed

base/options.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ struct JLOptions
3939
outputjitbc::Ptr{UInt8}
4040
outputo::Ptr{UInt8}
4141
outputji::Ptr{UInt8}
42+
output_code_coverage::Ptr{UInt8}
4243
incremental::Int8
4344
end
4445

base/util.jl

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -399,42 +399,79 @@ printstyled(io::IO, msg...; bold::Bool=false, color::Union{Int,Symbol}=:normal)
399399
with_output_color(print, color, io, msg...; bold=bold)
400400
printstyled(msg...; bold::Bool=false, color::Union{Int,Symbol}=:normal) =
401401
printstyled(stdout, msg...; bold=bold, color=color)
402+
402403
"""
403404
Base.julia_cmd(juliapath=joinpath(Sys.BINDIR::String, julia_exename()))
404405
405406
Return a julia command similar to the one of the running process.
406-
Propagates the `--cpu-target`, `--sysimage`, --compile `, `--depwarn`
407-
and `--inline` command line arguments.
407+
Propagates any of the `--cpu-target`, `--sysimage`, `--compile`, `--sysimage-native-code`,
408+
`--compiled-modules`, `--inline`, `--check-bounds`, `--optimize`, `-g`,
409+
`--code-coverage`, and `--depwarn`
410+
command line arguments that are not at their default values.
411+
412+
Among others, `--math-mode`, `--warn-overwrite`, and `--trace-compile` are notably not propagated currently.
408413
409414
!!! compat "Julia 1.1"
410-
The `--inline` flag is only propagated in Julia 1.1 and later.
415+
Only the `--cpu-target`, `--sysimage`, `--depwarn`, `--compile` and `--check-bounds` flags were propagated before Julia 1.1.
411416
"""
412417
function julia_cmd(julia=joinpath(Sys.BINDIR::String, julia_exename()))
413418
opts = JLOptions()
414419
cpu_target = unsafe_string(opts.cpu_target)
415420
image_file = unsafe_string(opts.image_file)
416-
compile = if opts.compile_enabled == 0
417-
"no"
418-
elseif opts.compile_enabled == 2
419-
"all"
420-
elseif opts.compile_enabled == 3
421-
"min"
422-
else
423-
"yes"
424-
end
425-
depwarn = if opts.depwarn == 0
426-
"no"
427-
elseif opts.depwarn == 2
428-
"error"
429-
else
430-
"yes"
431-
end
432-
inline = if opts.can_inline == 0
433-
"no"
434-
else
435-
"yes"
436-
end
437-
`$julia -C$cpu_target -J$image_file --compile=$compile --depwarn=$depwarn --inline=$inline`
421+
addflags = String[]
422+
let compile = if opts.compile_enabled == 0
423+
"no"
424+
elseif opts.compile_enabled == 2
425+
"all"
426+
elseif opts.compile_enabled == 3
427+
"min"
428+
else
429+
"" # default = "yes"
430+
end
431+
isempty(compile) || push!(addflags, "--compile=$compile")
432+
end
433+
let depwarn = if opts.depwarn == 0
434+
"no"
435+
elseif opts.depwarn == 2
436+
"error"
437+
else
438+
"" # default = "yes"
439+
end
440+
isempty(depwarn) || push!(addflags, "--depwarn=$depwarn")
441+
end
442+
let check_bounds = if opts.check_bounds == 1
443+
"yes" # on
444+
elseif opts.check_bounds == 2
445+
"no" # off
446+
else
447+
"" # "default"
448+
end
449+
isempty(check_bounds) || push!(addflags, "--check-bounds=$check_bounds")
450+
end
451+
opts.can_inline == 0 && push!(addflags, "--inline=no")
452+
opts.use_compiled_modules == 0 && push!(addflags, "--compiled-modules=no")
453+
opts.opt_level == 2 || push!(addflags, "-O$(opts.opt_level)")
454+
push!(addflags, "-g$(opts.debug_level)")
455+
if opts.code_coverage != 0
456+
# Forward the code-coverage flag only if applicable (if the filename is pid-dependent)
457+
coverage_file = (opts.output_code_coverage != C_NULL) ? unsafe_string(opts.output_code_coverage) : ""
458+
if isempty(coverage_file) || occursin("%p", coverage_file)
459+
if opts.code_coverage == 1
460+
push!(addflags, "--code-coverage=user")
461+
elseif opts.code_coverage == 2
462+
push!(addflags, "--code-coverage=all")
463+
end
464+
isempty(coverage_file) || push!(addflags, "--code-coverage=$coverage_file")
465+
end
466+
end
467+
if opts.malloc_log != 0
468+
if opts.malloc_log == 1
469+
push!(addflags, "--track-allocation=user")
470+
elseif opts.malloc_log == 2
471+
push!(addflags, "--track-allocation=all")
472+
end
473+
end
474+
return `$julia -C$cpu_target -J$image_file $addflags`
438475
end
439476

440477
function julia_exename()

src/codegen.cpp

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,7 +1696,7 @@ static void write_log_data(logdata_t &logData, const char *extension)
16961696
std::ifstream inf(filename.c_str());
16971697
if (inf.is_open()) {
16981698
std::string outfile = filename + extension;
1699-
std::ofstream outf(outfile.c_str(), std::ofstream::trunc | std::ofstream::out);
1699+
std::ofstream outf(outfile.c_str(), std::ofstream::trunc | std::ofstream::out | std::ofstream::binary);
17001700
char line[1024];
17011701
int l = 1;
17021702
unsigned block = 0;
@@ -1722,7 +1722,7 @@ static void write_log_data(logdata_t &logData, const char *extension)
17221722
else
17231723
outf << (value - 1);
17241724
outf.width(0);
1725-
outf << " " << line << std::endl;
1725+
outf << " " << line << '\n';
17261726
}
17271727
outf.close();
17281728
inf.close();
@@ -1732,20 +1732,68 @@ static void write_log_data(logdata_t &logData, const char *extension)
17321732
}
17331733

17341734
extern "C" int jl_getpid();
1735-
extern "C" void jl_write_coverage_data(void)
1735+
1736+
static void write_lcov_data(logdata_t &logData, const std::string &outfile)
17361737
{
1737-
std::ostringstream stm;
1738-
stm << jl_getpid();
1739-
std::string outf = "." + stm.str() + ".cov";
1740-
write_log_data(coverageData, outf.c_str());
1738+
std::ofstream outf(outfile.c_str(), std::ofstream::ate | std::ofstream::out | std::ofstream::binary);
1739+
//std::string base = std::string(jl_options.julia_bindir);
1740+
//base = base + "/../share/julia/base/";
1741+
logdata_t::iterator it = logData.begin();
1742+
for (; it != logData.end(); it++) {
1743+
const std::string &filename = it->first();
1744+
const std::vector<logdata_block*> &values = it->second;
1745+
if (!values.empty()) {
1746+
//if (!isabspath(filename.c_str()))
1747+
// filename = base + filename;
1748+
outf << "SF:" << filename << '\n';
1749+
size_t n_covered = 0;
1750+
size_t n_instrumented = 0;
1751+
size_t lno = 0;
1752+
for (auto &itv : values) {
1753+
if (itv) {
1754+
logdata_block &data = *itv;
1755+
for (int i = 0; i < logdata_blocksize; i++) {
1756+
auto cov = data[i];
1757+
if (cov > 0) {
1758+
n_instrumented++;
1759+
if (cov > 1)
1760+
n_covered++;
1761+
outf << "DA:" << lno << ',' << (cov - 1) << '\n';
1762+
}
1763+
lno++;
1764+
}
1765+
}
1766+
else {
1767+
lno += logdata_blocksize;
1768+
}
1769+
}
1770+
outf << "LH:" << n_covered << '\n';
1771+
outf << "LF:" << n_instrumented << '\n';
1772+
outf << "end_of_record\n";
1773+
}
1774+
}
1775+
outf.close();
1776+
}
1777+
1778+
extern "C" void jl_write_coverage_data(const char *output)
1779+
{
1780+
if (output) {
1781+
StringRef output_pattern(output);
1782+
if (output_pattern.endswith(".info"))
1783+
write_lcov_data(coverageData, jl_format_filename(output_pattern));
1784+
}
1785+
else {
1786+
std::ostringstream stm;
1787+
stm << "." << jl_getpid() << ".cov";
1788+
write_log_data(coverageData, stm.str().c_str());
1789+
}
17411790
}
17421791

17431792
extern "C" void jl_write_malloc_log(void)
17441793
{
17451794
std::ostringstream stm;
1746-
stm << jl_getpid();
1747-
std::string outf = "." + stm.str() + ".mem";
1748-
write_log_data(mallocData, outf.c_str());
1795+
stm << "." << jl_getpid() << ".mem";
1796+
write_log_data(mallocData, stm.str().c_str());
17491797
}
17501798

17511799
// --- constant determination ---
@@ -7368,7 +7416,7 @@ extern "C" void *jl_init_llvm(void)
73687416
cl::ParseEnvironmentOptions("Julia", "JULIA_LLVM_ARGS");
73697417

73707418
jl_page_size = jl_getpagesize();
7371-
imaging_mode = jl_generating_output();
7419+
imaging_mode = jl_generating_output() && !jl_options.incremental;
73727420
jl_init_debuginfo();
73737421

73747422
#ifdef USE_POLLY

src/init.c

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ static void jl_uv_exitcleanup_walk(uv_handle_t *handle, void *arg)
163163
jl_uv_exitcleanup_add(handle, (struct uv_shutdown_queue*)arg);
164164
}
165165

166-
void jl_write_coverage_data(void);
166+
void jl_write_coverage_data(const char*);
167167
void jl_write_malloc_log(void);
168168
void jl_write_compiler_output(void);
169169

@@ -222,7 +222,7 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode)
222222
jl_write_compiler_output();
223223
jl_print_gc_stats(JL_STDERR);
224224
if (jl_options.code_coverage)
225-
jl_write_coverage_data();
225+
jl_write_coverage_data(jl_options.output_code_coverage);
226226
if (jl_options.malloc_log)
227227
jl_write_malloc_log();
228228
if (jl_base_module) {
@@ -462,7 +462,7 @@ int isabspath(const char *in)
462462
}
463463

464464
static char *abspath(const char *in, int nprefix)
465-
{ // compute an absolute path location, so that chdir doesn't change the file reference
465+
{ // compute an absolute realpath location, so that chdir doesn't change the file reference
466466
// ignores (copies directly over) nprefix characters at the start of abspath
467467
#ifndef _OS_WINDOWS_
468468
char *out = realpath(in + nprefix, NULL);
@@ -495,6 +495,8 @@ static char *abspath(const char *in, int nprefix)
495495
jl_error("fatal error: unexpected error while retrieving current working directory");
496496
}
497497
out = (char*)malloc(path_size + 1 + sz + nprefix);
498+
if (!out)
499+
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
498500
memcpy(out, in, nprefix);
499501
memcpy(out + nprefix, path, path_size);
500502
out[nprefix + path_size] = PATHSEPSTRING[0];
@@ -508,6 +510,8 @@ static char *abspath(const char *in, int nprefix)
508510
jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed");
509511
}
510512
char *out = (char*)malloc(n + nprefix);
513+
if (!out)
514+
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
511515
DWORD m = GetFullPathName(in + nprefix, n, out + nprefix, NULL);
512516
if (n != m + 1) {
513517
jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed");
@@ -517,6 +521,38 @@ static char *abspath(const char *in, int nprefix)
517521
return out;
518522
}
519523

524+
// create an absolute-path copy of the input path format string
525+
// formed as `joinpath(replace(pwd(), "%" => "%%"), in)`
526+
// unless `in` starts with `%`
527+
static const char *absformat(const char *in)
528+
{
529+
if (in[0] == '%' || isabspath(in))
530+
return in;
531+
// get an escaped copy of cwd
532+
size_t path_size = PATH_MAX;
533+
char path[PATH_MAX];
534+
if (uv_cwd(path, &path_size)) {
535+
jl_error("fatal error: unexpected error while retrieving current working directory");
536+
}
537+
size_t sz = strlen(in) + 1;
538+
size_t i, fmt_size = 0;
539+
for (i = 0; i < path_size; i++)
540+
fmt_size += (path[i] == '%' ? 2 : 1);
541+
char *out = (char*)malloc(fmt_size + 1 + sz);
542+
if (!out)
543+
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
544+
fmt_size = 0;
545+
for (i = 0; i < path_size; i++) { // copy-replace pwd portion
546+
char c = path[i];
547+
out[fmt_size++] = c;
548+
if (c == '%')
549+
out[fmt_size++] = '%';
550+
}
551+
out[fmt_size++] = PATHSEPSTRING[0]; // path sep
552+
memcpy(out + fmt_size, in, sz); // copy over format, including nul
553+
return out;
554+
}
555+
520556
static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel)
521557
{ // this function resolves the paths in jl_options to absolute file locations as needed
522558
// and it replaces the pointers to `julia_bindir`, `julia_bin`, `image_file`, and output file paths
@@ -527,13 +563,17 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel)
527563
// calling `julia_init()`
528564
char *free_path = (char*)malloc(PATH_MAX);
529565
size_t path_size = PATH_MAX;
566+
if (!free_path)
567+
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
530568
if (uv_exepath(free_path, &path_size)) {
531569
jl_error("fatal error: unexpected error while retrieving exepath");
532570
}
533571
if (path_size >= PATH_MAX) {
534572
jl_error("fatal error: jl_options.julia_bin path too long");
535573
}
536574
jl_options.julia_bin = (char*)malloc(path_size+1);
575+
if (!jl_options.julia_bin)
576+
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
537577
memcpy((char*)jl_options.julia_bin, free_path, path_size);
538578
((char*)jl_options.julia_bin)[path_size] = '\0';
539579
if (!jl_options.julia_bindir) {
@@ -550,6 +590,8 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel)
550590
if (rel == JL_IMAGE_JULIA_HOME && !isabspath(jl_options.image_file)) {
551591
// build time path, relative to JULIA_BINDIR
552592
free_path = (char*)malloc(PATH_MAX);
593+
if (!free_path)
594+
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
553595
int n = snprintf(free_path, PATH_MAX, "%s" PATHSEPSTRING "%s",
554596
jl_options.julia_bindir, jl_options.image_file);
555597
if (n >= PATH_MAX || n < 0) {
@@ -572,8 +614,13 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel)
572614
jl_options.outputbc = abspath(jl_options.outputbc, 0);
573615
if (jl_options.machine_file)
574616
jl_options.machine_file = abspath(jl_options.machine_file, 0);
575-
if (jl_options.project && strncmp(jl_options.project, "@.", strlen(jl_options.project)) != 0)
617+
if (jl_options.project
618+
&& strcmp(jl_options.project, "@.") != 0
619+
&& strcmp(jl_options.project, "@") != 0
620+
&& strcmp(jl_options.project, "") != 0)
576621
jl_options.project = abspath(jl_options.project, 0);
622+
if (jl_options.output_code_coverage)
623+
jl_options.output_code_coverage = absformat(jl_options.output_code_coverage);
577624

578625
const char **cmdp = jl_options.cmds;
579626
if (cmdp) {
@@ -600,6 +647,7 @@ void _julia_init(JL_IMAGE_SEARCH rel)
600647
jl_get_ptls_states_getter();
601648
#endif
602649
jl_ptls_t ptls = jl_get_ptls_states();
650+
(void)ptls; assert(ptls); // make sure early that we have initialized ptls
603651
jl_safepoint_init();
604652
libsupport_init();
605653
htable_new(&jl_current_modules, 0);

src/jloptions.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ jl_options_t jl_options = { 0, // quiet
6868
NULL, // output-jit-bc
6969
NULL, // output-o
7070
NULL, // output-ji
71+
NULL, // output-code_coverage
7172
0, // incremental
7273
0 // image_file_specified
7374
};
@@ -129,6 +130,9 @@ static const char opts[] =
129130
// instrumentation options
130131
" --code-coverage={none|user|all}, --code-coverage\n"
131132
" Count executions of source lines (omitting setting is equivalent to \"user\")\n"
133+
" --code-coverage=tracefile.info\n"
134+
" Append coverage information to the LCOV tracefile (filename supports format tokens).\n"
135+
// TODO: These TOKENS are defined in `runtime_ccall.cpp`. A more verbose `--help` should include that list here.
132136
" --track-allocation={none|user|all}, --track-allocation\n"
133137
" Count bytes allocated by each source line\n\n"
134138

@@ -437,12 +441,18 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
437441
break;
438442
case opt_code_coverage:
439443
if (optarg != NULL) {
440-
if (!strcmp(optarg,"user"))
444+
size_t endof = strlen(optarg);
445+
if (!strcmp(optarg, "user"))
441446
codecov = JL_LOG_USER;
442-
else if (!strcmp(optarg,"all"))
447+
else if (!strcmp(optarg, "all"))
443448
codecov = JL_LOG_ALL;
444-
else if (!strcmp(optarg,"none"))
449+
else if (!strcmp(optarg, "none"))
445450
codecov = JL_LOG_NONE;
451+
else if (endof > 5 && !strcmp(optarg + endof - 5, ".info")) {
452+
if (codecov == JL_LOG_NONE)
453+
codecov = JL_LOG_ALL;
454+
jl_options.output_code_coverage = optarg;
455+
}
446456
else
447457
jl_errorf("julia: invalid argument to --code-coverage (%s)", optarg);
448458
break;

0 commit comments

Comments
 (0)