From 42682b8f3d02c4489545cfadab230d291cfef271 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Thu, 28 Oct 2021 12:23:53 -0400 Subject: [PATCH] fix #42659, move `jl_coverage_visit_line` to runtime library (#42810) --- src/Makefile | 2 +- src/codegen-stubs.c | 3 - src/codegen.cpp | 188 ++------------------------------- src/coverage.cpp | 214 ++++++++++++++++++++++++++++++++++++++ src/jl_exported_funcs.inc | 3 - src/julia.expmap | 3 + 6 files changed, 225 insertions(+), 188 deletions(-) create mode 100644 src/coverage.cpp diff --git a/src/Makefile b/src/Makefile index 427d3b5c351c7e..17e70fe6d01f55 100644 --- a/src/Makefile +++ b/src/Makefile @@ -47,7 +47,7 @@ RUNTIME_SRCS := \ simplevector runtime_intrinsics precompile \ threading partr stackwalk gc gc-debug gc-pages gc-stacks method \ jlapi signal-handling safepoint timing subtype \ - crc32c APInt-C processor ircode opaque_closure codegen-stubs + crc32c APInt-C processor ircode opaque_closure codegen-stubs coverage SRCS := jloptions runtime_ccall rtutils ifeq ($(OS),WINNT) SRCS += win32_ucontext diff --git a/src/codegen-stubs.c b/src/codegen-stubs.c index c98d55f77c53f8..e95e2f4f46d320 100644 --- a/src/codegen-stubs.c +++ b/src/codegen-stubs.c @@ -14,10 +14,7 @@ JL_DLLEXPORT void jl_dump_native_fallback(void *native_code, const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *asm_fname, const char *sysimg_data, size_t sysimg_len) UNAVAILABLE JL_DLLEXPORT int32_t jl_get_llvm_gv_fallback(void *native_code, jl_value_t *p) UNAVAILABLE -JL_DLLEXPORT void jl_write_malloc_log_fallback(void) UNAVAILABLE -JL_DLLEXPORT void jl_write_coverage_data_fallback(const char *output) UNAVAILABLE -JL_DLLEXPORT void jl_clear_malloc_data_fallback(void) UNAVAILABLE JL_DLLEXPORT int jl_extern_c_fallback(jl_function_t *f, jl_value_t *rt, jl_value_t *argt, char *name) UNAVAILABLE JL_DLLEXPORT jl_value_t *jl_dump_method_asm_fallback(jl_method_instance_t *linfo, size_t world, char raw_mc, char getwrapper, const char* asm_variant, const char *debuginfo, char binary) UNAVAILABLE diff --git a/src/codegen.cpp b/src/codegen.cpp index b09eb8db04e074..d1011f9139f566 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1803,28 +1803,12 @@ static std::pair uses_specsig(jl_method_instance_t *lam, jl_value_t // Logging for code coverage and memory allocation -const int logdata_blocksize = 32; // target getting nearby lines in the same general cache area and reducing calls to malloc by chunking -typedef uint64_t logdata_block[logdata_blocksize]; -typedef StringMap< std::vector > logdata_t; +JL_DLLEXPORT void jl_coverage_alloc_line(StringRef filename, int line); +JL_DLLEXPORT uint64_t *jl_coverage_data_pointer(StringRef filename, int line); +JL_DLLEXPORT uint64_t *jl_malloc_data_pointer(StringRef filename, int line); -static uint64_t *allocLine(std::vector &vec, int line) +static void visitLine(jl_codectx_t &ctx, uint64_t *ptr, Value *addend, const char *name) { - unsigned block = line / logdata_blocksize; - line = line % logdata_blocksize; - if (vec.size() <= block) - vec.resize(block + 1); - if (vec[block] == NULL) { - vec[block] = (logdata_block*)calloc(1, sizeof(logdata_block)); - } - logdata_block &data = *vec[block]; - if (data[line] == 0) - data[line] = 1; - return &data[line]; -} - -static void visitLine(jl_codectx_t &ctx, std::vector &vec, int line, Value *addend, const char* name) -{ - uint64_t *ptr = allocLine(vec, line); Value *pv = ConstantExpr::getIntToPtr( ConstantInt::get(T_size, (uintptr_t)ptr), T_pint64); @@ -1836,38 +1820,16 @@ static void visitLine(jl_codectx_t &ctx, std::vector &vec, int l // Code coverage -static logdata_t coverageData; - static void coverageVisitLine(jl_codectx_t &ctx, StringRef filename, int line) { assert(!imaging_mode); if (filename == "" || filename == "none" || filename == "no file" || filename == "" || line < 0) return; - visitLine(ctx, coverageData[filename], line, ConstantInt::get(T_int64, 1), "lcnt"); -} - -static void coverageAllocLine(StringRef filename, int line) -{ - assert(!imaging_mode); - if (filename == "" || filename == "none" || filename == "no file" || filename == "" || line < 0) - return; - allocLine(coverageData[filename], line); -} - -extern "C" JL_DLLEXPORT void jl_coverage_visit_line(const char* filename_, size_t len_filename, int line) -{ - StringRef filename = StringRef(filename_, len_filename); - if (imaging_mode || filename == "" || filename == "none" || filename == "no file" || filename == "" || line < 0) - return; - std::vector &vec = coverageData[filename]; - uint64_t *ptr = allocLine(vec, line); - (*ptr)++; + visitLine(ctx, jl_coverage_data_pointer(filename, line), ConstantInt::get(T_int64, 1), "lcnt"); } // Memory allocation log (malloc_log) -static logdata_t mallocData; - static void mallocVisitLine(jl_codectx_t &ctx, StringRef filename, int line, Value *sync) { assert(!imaging_mode); @@ -1876,143 +1838,7 @@ static void mallocVisitLine(jl_codectx_t &ctx, StringRef filename, int line, Val Value *addend = sync ? ctx.builder.CreateCall(prepare_call(sync_gc_total_bytes_func), {sync}) : ctx.builder.CreateCall(prepare_call(diff_gc_total_bytes_func), {}); - visitLine(ctx, mallocData[filename], line, addend, "bytecnt"); -} - -// Resets the malloc counts. -extern "C" JL_DLLEXPORT void jl_clear_malloc_data_impl(void) -{ - logdata_t::iterator it = mallocData.begin(); - for (; it != mallocData.end(); it++) { - std::vector &bytes = (*it).second; - std::vector::iterator itb; - for (itb = bytes.begin(); itb != bytes.end(); itb++) { - if (*itb) { - logdata_block &data = **itb; - for (int i = 0; i < logdata_blocksize; i++) { - if (data[i] > 0) - data[i] = 1; - } - } - } - } - jl_gc_sync_total_bytes(0); -} - -static void write_log_data(logdata_t &logData, const char *extension) -{ - std::string base = std::string(jl_options.julia_bindir); - base = base + "/../share/julia/base/"; - logdata_t::iterator it = logData.begin(); - for (; it != logData.end(); it++) { - std::string filename(it->first()); - std::vector &values = it->second; - if (!values.empty()) { - if (!jl_isabspath(filename.c_str())) - filename = base + filename; - std::ifstream inf(filename.c_str()); - if (!inf.is_open()) - continue; - std::string outfile = filename + extension; - std::ofstream outf(outfile.c_str(), std::ofstream::trunc | std::ofstream::out | std::ofstream::binary); - if (outf.is_open()) { - inf.exceptions(std::ifstream::badbit); - outf.exceptions(std::ifstream::failbit | std::ifstream::badbit); - char line[1024]; - int l = 1; - unsigned block = 0; - while (!inf.eof()) { - inf.getline(line, sizeof(line)); - if (inf.fail()) { - if (inf.eof()) - break; // no content on trailing line - // Read through lines longer than sizeof(line) - inf.clear(); - inf.ignore(std::numeric_limits::max(), '\n'); - } - logdata_block *data = NULL; - if (block < values.size()) { - data = values[block]; - } - uint64_t value = data ? (*data)[l] : 0; - if (++l >= logdata_blocksize) { - l = 0; - block++; - } - outf.width(9); - if (value == 0) - outf << '-'; - else - outf << (value - 1); - outf.width(0); - outf << " " << line << '\n'; - } - outf.close(); - } - inf.close(); - } - } -} - -static void write_lcov_data(logdata_t &logData, const std::string &outfile) -{ - std::ofstream outf(outfile.c_str(), std::ofstream::ate | std::ofstream::out | std::ofstream::binary); - //std::string base = std::string(jl_options.julia_bindir); - //base = base + "/../share/julia/base/"; - logdata_t::iterator it = logData.begin(); - for (; it != logData.end(); it++) { - StringRef filename = it->first(); - const std::vector &values = it->second; - if (!values.empty()) { - outf << "SF:" << filename.str() << '\n'; - size_t n_covered = 0; - size_t n_instrumented = 0; - size_t lno = 0; - for (auto &itv : values) { - if (itv) { - logdata_block &data = *itv; - for (int i = 0; i < logdata_blocksize; i++) { - auto cov = data[i]; - if (cov > 0) { - n_instrumented++; - if (cov > 1) - n_covered++; - outf << "DA:" << lno << ',' << (cov - 1) << '\n'; - } - lno++; - } - } - else { - lno += logdata_blocksize; - } - } - outf << "LH:" << n_covered << '\n'; - outf << "LF:" << n_instrumented << '\n'; - outf << "end_of_record\n"; - } - } - outf.close(); -} - -extern "C" JL_DLLEXPORT void jl_write_coverage_data_impl(const char *output) -{ - if (output) { - StringRef output_pattern(output); - if (output_pattern.endswith(".info")) - write_lcov_data(coverageData, jl_format_filename(output_pattern.str().c_str())); - } - else { - std::string stm; - raw_string_ostream(stm) << "." << jl_getpid() << ".cov"; - write_log_data(coverageData, stm.c_str()); - } -} - -extern "C" JL_DLLEXPORT void jl_write_malloc_log_impl(void) -{ - std::string stm; - raw_string_ostream(stm) << "." << jl_getpid() << ".mem"; - write_log_data(mallocData, stm.c_str()); + visitLine(ctx, jl_malloc_data_pointer(filename, line), addend, "bytecnt"); } // --- constant determination --- @@ -7073,7 +6899,7 @@ static std::pair, jl_llvm_functions_t> // record all lines that could be covered for (const auto &info : linetable) if (do_coverage(info.is_user_code)) - coverageAllocLine(info.file, info.line); + jl_coverage_alloc_line(info.file, info.line); } come_from_bb[0] = ctx.builder.GetInsertBlock(); diff --git a/src/coverage.cpp b/src/coverage.cpp new file mode 100644 index 00000000000000..4ce33c105691ce --- /dev/null +++ b/src/coverage.cpp @@ -0,0 +1,214 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#include +#include +#include +#include + +#include "llvm-version.h" +#include +#include +#include + +#include "julia.h" +#include "julia_internal.h" + +using namespace llvm; + +static int codegen_imaging_mode(void) +{ + return jl_options.image_codegen || (jl_generating_output() && !jl_options.incremental); +} + +// Logging for code coverage and memory allocation + +const int logdata_blocksize = 32; // target getting nearby lines in the same general cache area and reducing calls to malloc by chunking +typedef uint64_t logdata_block[logdata_blocksize]; +typedef StringMap< std::vector > logdata_t; + +static uint64_t *allocLine(std::vector &vec, int line) +{ + unsigned block = line / logdata_blocksize; + line = line % logdata_blocksize; + if (vec.size() <= block) + vec.resize(block + 1); + if (vec[block] == NULL) { + vec[block] = (logdata_block*)calloc(1, sizeof(logdata_block)); + } + logdata_block &data = *vec[block]; + if (data[line] == 0) + data[line] = 1; + return &data[line]; +} + +// Code coverage + +static logdata_t coverageData; + +JL_DLLEXPORT void jl_coverage_alloc_line(StringRef filename, int line) +{ + assert(!codegen_imaging_mode()); + if (filename == "" || filename == "none" || filename == "no file" || filename == "" || line < 0) + return; + allocLine(coverageData[filename], line); +} + +JL_DLLEXPORT uint64_t *jl_coverage_data_pointer(StringRef filename, int line) +{ + return allocLine(coverageData[filename], line); +} + +extern "C" JL_DLLEXPORT void jl_coverage_visit_line(const char *filename_, size_t len_filename, int line) +{ + StringRef filename = StringRef(filename_, len_filename); + if (codegen_imaging_mode() || filename == "" || filename == "none" || filename == "no file" || filename == "" || line < 0) + return; + std::vector &vec = coverageData[filename]; + uint64_t *ptr = allocLine(vec, line); + (*ptr)++; +} + +// Memory allocation log (malloc_log) + +static logdata_t mallocData; + +JL_DLLEXPORT uint64_t *jl_malloc_data_pointer(StringRef filename, int line) +{ + return allocLine(mallocData[filename], line); +} + +// Resets the malloc counts. +extern "C" JL_DLLEXPORT void jl_clear_malloc_data(void) +{ + logdata_t::iterator it = mallocData.begin(); + for (; it != mallocData.end(); it++) { + std::vector &bytes = (*it).second; + std::vector::iterator itb; + for (itb = bytes.begin(); itb != bytes.end(); itb++) { + if (*itb) { + logdata_block &data = **itb; + for (int i = 0; i < logdata_blocksize; i++) { + if (data[i] > 0) + data[i] = 1; + } + } + } + } + jl_gc_sync_total_bytes(0); +} + +static void write_log_data(logdata_t &logData, const char *extension) +{ + std::string base = std::string(jl_options.julia_bindir); + base = base + "/../share/julia/base/"; + logdata_t::iterator it = logData.begin(); + for (; it != logData.end(); it++) { + std::string filename(it->first()); + std::vector &values = it->second; + if (!values.empty()) { + if (!jl_isabspath(filename.c_str())) + filename = base + filename; + std::ifstream inf(filename.c_str()); + if (!inf.is_open()) + continue; + std::string outfile = filename + extension; + std::ofstream outf(outfile.c_str(), std::ofstream::trunc | std::ofstream::out | std::ofstream::binary); + if (outf.is_open()) { + inf.exceptions(std::ifstream::badbit); + outf.exceptions(std::ifstream::failbit | std::ifstream::badbit); + char line[1024]; + int l = 1; + unsigned block = 0; + while (!inf.eof()) { + inf.getline(line, sizeof(line)); + if (inf.fail()) { + if (inf.eof()) + break; // no content on trailing line + // Read through lines longer than sizeof(line) + inf.clear(); + inf.ignore(std::numeric_limits::max(), '\n'); + } + logdata_block *data = NULL; + if (block < values.size()) { + data = values[block]; + } + uint64_t value = data ? (*data)[l] : 0; + if (++l >= logdata_blocksize) { + l = 0; + block++; + } + outf.width(9); + if (value == 0) + outf << '-'; + else + outf << (value - 1); + outf.width(0); + outf << " " << line << '\n'; + } + outf.close(); + } + inf.close(); + } + } +} + +static void write_lcov_data(logdata_t &logData, const std::string &outfile) +{ + std::ofstream outf(outfile.c_str(), std::ofstream::ate | std::ofstream::out | std::ofstream::binary); + //std::string base = std::string(jl_options.julia_bindir); + //base = base + "/../share/julia/base/"; + logdata_t::iterator it = logData.begin(); + for (; it != logData.end(); it++) { + StringRef filename = it->first(); + const std::vector &values = it->second; + if (!values.empty()) { + outf << "SF:" << filename.str() << '\n'; + size_t n_covered = 0; + size_t n_instrumented = 0; + size_t lno = 0; + for (auto &itv : values) { + if (itv) { + logdata_block &data = *itv; + for (int i = 0; i < logdata_blocksize; i++) { + auto cov = data[i]; + if (cov > 0) { + n_instrumented++; + if (cov > 1) + n_covered++; + outf << "DA:" << lno << ',' << (cov - 1) << '\n'; + } + lno++; + } + } + else { + lno += logdata_blocksize; + } + } + outf << "LH:" << n_covered << '\n'; + outf << "LF:" << n_instrumented << '\n'; + outf << "end_of_record\n"; + } + } + outf.close(); +} + +extern "C" JL_DLLEXPORT void jl_write_coverage_data(const char *output) +{ + if (output) { + StringRef output_pattern(output); + if (output_pattern.endswith(".info")) + write_lcov_data(coverageData, jl_format_filename(output_pattern.str().c_str())); + } + else { + std::string stm; + raw_string_ostream(stm) << "." << jl_getpid() << ".cov"; + write_log_data(coverageData, stm.c_str()); + } +} + +extern "C" JL_DLLEXPORT void jl_write_malloc_log(void) +{ + std::string stm; + raw_string_ostream(stm) << "." << jl_getpid() << ".mem"; + write_log_data(mallocData, stm.c_str()); +} diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index c2405d6e1a3891..50baad851da3a3 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -519,7 +519,6 @@ // use YY instead of XX to avoid jl -> ijl renaming in libjulia-codegen #define JL_CODEGEN_EXPORTED_FUNCS(YY) \ - YY(jl_clear_malloc_data) \ YY(jl_dump_function_ir) \ YY(jl_dump_method_asm) \ YY(jl_extern_c) \ @@ -530,8 +529,6 @@ YY(jl_get_LLVM_VERSION) \ YY(jl_dump_native) \ YY(jl_get_llvm_gv) \ - YY(jl_write_malloc_log) \ - YY(jl_write_coverage_data) \ YY(jl_dump_function_asm) \ YY(jl_LLVMCreateDisasm) \ YY(jl_LLVMDisasmInstruction) \ diff --git a/src/julia.expmap b/src/julia.expmap index 9c21fff9963f76..425fd9c9c1ee09 100644 --- a/src/julia.expmap +++ b/src/julia.expmap @@ -37,6 +37,9 @@ jlbacktracet; _IO_stdin_used; __ZN4llvm23createLowerSimdLoopPassEv; + _Z24jl_coverage_data_pointerN4llvm9StringRefEi; + _Z22jl_coverage_alloc_lineN4llvm9StringRefEi; + _Z22jl_malloc_data_pointerN4llvm9StringRefEi; LLVMExtra*; /* freebsd */