diff options
author | nkozlovskiy <nmk@ydb.tech> | 2023-10-11 19:11:46 +0300 |
---|---|---|
committer | nkozlovskiy <nmk@ydb.tech> | 2023-10-11 19:33:28 +0300 |
commit | 61b3971447e473726d6cdb23fc298e457b4d973c (patch) | |
tree | e2a2a864bb7717f7ae6138f6a3194a254dd2c7bb /contrib/libs/clang14-rt/lib/profile/GCDAProfiling.c | |
parent | a674dc57d88d43c2e8e90a6084d5d2c988e0402c (diff) | |
download | ydb-61b3971447e473726d6cdb23fc298e457b4d973c.tar.gz |
add sanitizers dependencies
Diffstat (limited to 'contrib/libs/clang14-rt/lib/profile/GCDAProfiling.c')
-rw-r--r-- | contrib/libs/clang14-rt/lib/profile/GCDAProfiling.c | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/contrib/libs/clang14-rt/lib/profile/GCDAProfiling.c b/contrib/libs/clang14-rt/lib/profile/GCDAProfiling.c new file mode 100644 index 0000000000..8e51f57b09 --- /dev/null +++ b/contrib/libs/clang14-rt/lib/profile/GCDAProfiling.c @@ -0,0 +1,642 @@ +/*===- GCDAProfiling.c - Support library for GCDA file emission -----------===*\ +|* +|* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +|* See https://llvm.org/LICENSE.txt for license information. +|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +|* +|*===----------------------------------------------------------------------===*| +|* +|* This file implements the call back routines for the gcov profiling +|* instrumentation pass. Link against this library when running code through +|* the -insert-gcov-profiling LLVM pass. +|* +|* We emit files in a corrupt version of GCOV's "gcda" file format. These files +|* are only close enough that LCOV will happily parse them. Anything that lcov +|* ignores is missing. +|* +|* TODO: gcov is multi-process safe by having each exit open the existing file +|* and append to it. We'd like to achieve that and be thread-safe too. +|* +\*===----------------------------------------------------------------------===*/ + +#if !defined(__Fuchsia__) + +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include "WindowsMMap.h" +#else +#include <sys/file.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> +#endif + +#include "InstrProfiling.h" +#include "InstrProfilingUtil.h" + +/* #define DEBUG_GCDAPROFILING */ + +enum { + GCOV_DATA_MAGIC = 0x67636461, // "gcda" + + GCOV_TAG_FUNCTION = 0x01000000, + GCOV_TAG_COUNTER_ARCS = 0x01a10000, + // GCOV_TAG_OBJECT_SUMMARY superseded GCOV_TAG_PROGRAM_SUMMARY in GCC 9. + GCOV_TAG_OBJECT_SUMMARY = 0xa1000000, + GCOV_TAG_PROGRAM_SUMMARY = 0xa3000000, +}; + +/* + * --- GCOV file format I/O primitives --- + */ + +/* + * The current file name we're outputting. Used primarily for error logging. + */ +static char *filename = NULL; + +/* + * The current file we're outputting. + */ +static FILE *output_file = NULL; + +/* + * Buffer that we write things into. + */ +#define WRITE_BUFFER_SIZE (128 * 1024) +static unsigned char *write_buffer = NULL; +static uint64_t cur_buffer_size = 0; +static uint64_t cur_pos = 0; +static uint64_t file_size = 0; +static int new_file = 0; +static int gcov_version; +#if defined(_WIN32) +static HANDLE mmap_handle = NULL; +#endif +static int fd = -1; + +typedef void (*fn_ptr)(); + +typedef void* dynamic_object_id; +// The address of this variable identifies a given dynamic object. +static dynamic_object_id current_id; +#define CURRENT_ID (¤t_id) + +struct fn_node { + dynamic_object_id id; + fn_ptr fn; + struct fn_node* next; +}; + +struct fn_list { + struct fn_node *head, *tail; +}; + +/* + * A list of functions to write out the data, shared between all dynamic objects. + */ +struct fn_list writeout_fn_list; + +/* + * A list of reset functions, shared between all dynamic objects. + */ +struct fn_list reset_fn_list; + +static void fn_list_insert(struct fn_list* list, fn_ptr fn) { + struct fn_node* new_node = malloc(sizeof(struct fn_node)); + new_node->fn = fn; + new_node->next = NULL; + new_node->id = CURRENT_ID; + + if (!list->head) { + list->head = list->tail = new_node; + } else { + list->tail->next = new_node; + list->tail = new_node; + } +} + +static void fn_list_remove(struct fn_list* list) { + struct fn_node* curr = list->head; + struct fn_node* prev = NULL; + struct fn_node* next = NULL; + + while (curr) { + next = curr->next; + + if (curr->id == CURRENT_ID) { + if (curr == list->head) { + list->head = next; + } + + if (curr == list->tail) { + list->tail = prev; + } + + if (prev) { + prev->next = next; + } + + free(curr); + } else { + prev = curr; + } + + curr = next; + } +} + +static void resize_write_buffer(uint64_t size) { + if (!new_file) return; + size += cur_pos; + if (size <= cur_buffer_size) return; + size = (size - 1) / WRITE_BUFFER_SIZE + 1; + size *= WRITE_BUFFER_SIZE; + write_buffer = realloc(write_buffer, size); + cur_buffer_size = size; +} + +static void write_bytes(const char *s, size_t len) { + resize_write_buffer(len); + memcpy(&write_buffer[cur_pos], s, len); + cur_pos += len; +} + +static void write_32bit_value(uint32_t i) { + write_bytes((char*)&i, 4); +} + +static void write_64bit_value(uint64_t i) { + // GCOV uses a lo-/hi-word format even on big-endian systems. + // See also GCOVBuffer::readInt64 in LLVM. + uint32_t lo = (uint32_t) i; + uint32_t hi = (uint32_t) (i >> 32); + write_32bit_value(lo); + write_32bit_value(hi); +} + +static uint32_t read_32bit_value() { + uint32_t val; + + if (new_file) + return (uint32_t)-1; + + val = *(uint32_t*)&write_buffer[cur_pos]; + cur_pos += 4; + return val; +} + +static uint64_t read_64bit_value() { + // GCOV uses a lo-/hi-word format even on big-endian systems. + // See also GCOVBuffer::readInt64 in LLVM. + uint32_t lo = read_32bit_value(); + uint32_t hi = read_32bit_value(); + return ((uint64_t)hi << 32) | ((uint64_t)lo); +} + +static char *mangle_filename(const char *orig_filename) { + char *new_filename; + size_t prefix_len; + int prefix_strip; + const char *prefix = lprofGetPathPrefix(&prefix_strip, &prefix_len); + + if (prefix == NULL) + return strdup(orig_filename); + + new_filename = malloc(prefix_len + 1 + strlen(orig_filename) + 1); + lprofApplyPathPrefix(new_filename, orig_filename, prefix, prefix_len, + prefix_strip); + + return new_filename; +} + +static int map_file() { + fseek(output_file, 0L, SEEK_END); + file_size = ftell(output_file); + + /* A size of 0 means the file has been created just now (possibly by another + * process in lock-after-open race condition). No need to mmap. */ + if (file_size == 0) + return -1; + +#if defined(_WIN32) + HANDLE mmap_fd; + if (fd == -1) + mmap_fd = INVALID_HANDLE_VALUE; + else + mmap_fd = (HANDLE)_get_osfhandle(fd); + + mmap_handle = CreateFileMapping(mmap_fd, NULL, PAGE_READWRITE, DWORD_HI(file_size), DWORD_LO(file_size), NULL); + if (mmap_handle == NULL) { + fprintf(stderr, "profiling: %s: cannot create file mapping: %lu\n", + filename, GetLastError()); + return -1; + } + + write_buffer = MapViewOfFile(mmap_handle, FILE_MAP_WRITE, 0, 0, file_size); + if (write_buffer == NULL) { + fprintf(stderr, "profiling: %s: cannot map: %lu\n", filename, + GetLastError()); + CloseHandle(mmap_handle); + return -1; + } +#else + write_buffer = mmap(0, file_size, PROT_READ | PROT_WRITE, + MAP_FILE | MAP_SHARED, fd, 0); + if (write_buffer == (void *)-1) { + int errnum = errno; + fprintf(stderr, "profiling: %s: cannot map: %s\n", filename, + strerror(errnum)); + return -1; + } +#endif + + return 0; +} + +static void unmap_file() { +#if defined(_WIN32) + if (!FlushViewOfFile(write_buffer, file_size)) { + fprintf(stderr, "profiling: %s: cannot flush mapped view: %lu\n", filename, + GetLastError()); + } + + if (!UnmapViewOfFile(write_buffer)) { + fprintf(stderr, "profiling: %s: cannot unmap mapped view: %lu\n", filename, + GetLastError()); + } + + if (!CloseHandle(mmap_handle)) { + fprintf(stderr, "profiling: %s: cannot close file mapping handle: %lu\n", + filename, GetLastError()); + } + + mmap_handle = NULL; +#else + if (munmap(write_buffer, file_size) == -1) { + int errnum = errno; + fprintf(stderr, "profiling: %s: cannot munmap: %s\n", filename, + strerror(errnum)); + } +#endif + + write_buffer = NULL; + file_size = 0; +} + +/* + * --- LLVM line counter API --- + */ + +/* A file in this case is a translation unit. Each .o file built with line + * profiling enabled will emit to a different file. Only one file may be + * started at a time. + */ +COMPILER_RT_VISIBILITY +void llvm_gcda_start_file(const char *orig_filename, uint32_t version, + uint32_t checksum) { + const char *mode = "r+b"; + filename = mangle_filename(orig_filename); + + /* Try just opening the file. */ + fd = open(filename, O_RDWR | O_BINARY); + + if (fd == -1) { + /* Try creating the file. */ + fd = open(filename, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0644); + if (fd != -1) { + mode = "w+b"; + } else { + /* Try creating the directories first then opening the file. */ + __llvm_profile_recursive_mkdir(filename); + fd = open(filename, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0644); + if (fd != -1) { + mode = "w+b"; + } else { + /* Another process may have created the file just now. + * Try opening it without O_CREAT and O_EXCL. */ + fd = open(filename, O_RDWR | O_BINARY); + if (fd == -1) { + /* Bah! It's hopeless. */ + int errnum = errno; + fprintf(stderr, "profiling: %s: cannot open: %s\n", filename, + strerror(errnum)); + return; + } + } + } + } + + /* Try to flock the file to serialize concurrent processes writing out to the + * same GCDA. This can fail if the filesystem doesn't support it, but in that + * case we'll just carry on with the old racy behaviour and hope for the best. + */ + lprofLockFd(fd); + output_file = fdopen(fd, mode); + + /* Initialize the write buffer. */ + new_file = 0; + write_buffer = NULL; + cur_buffer_size = 0; + cur_pos = 0; + + if (map_file() == -1) { + /* The file has been created just now (file_size == 0) or mmap failed + * unexpectedly. In the latter case, try to recover by clobbering. */ + new_file = 1; + write_buffer = NULL; + resize_write_buffer(WRITE_BUFFER_SIZE); + memset(write_buffer, 0, WRITE_BUFFER_SIZE); + } + + /* gcda file, version, stamp checksum. */ + { + uint8_t c3 = version >> 24; + uint8_t c2 = (version >> 16) & 255; + uint8_t c1 = (version >> 8) & 255; + gcov_version = c3 >= 'A' ? (c3 - 'A') * 100 + (c2 - '0') * 10 + c1 - '0' + : (c3 - '0') * 10 + c1 - '0'; + } + write_32bit_value(GCOV_DATA_MAGIC); + write_32bit_value(version); + write_32bit_value(checksum); + +#ifdef DEBUG_GCDAPROFILING + fprintf(stderr, "llvmgcda: [%s]\n", orig_filename); +#endif +} + +COMPILER_RT_VISIBILITY +void llvm_gcda_emit_function(uint32_t ident, uint32_t func_checksum, + uint32_t cfg_checksum) { + uint32_t len = 2; + int use_extra_checksum = gcov_version >= 47; + + if (use_extra_checksum) + len++; +#ifdef DEBUG_GCDAPROFILING + fprintf(stderr, "llvmgcda: function id=0x%08x\n", ident); +#endif + if (!output_file) return; + + /* function tag */ + write_32bit_value(GCOV_TAG_FUNCTION); + write_32bit_value(len); + write_32bit_value(ident); + write_32bit_value(func_checksum); + if (use_extra_checksum) + write_32bit_value(cfg_checksum); +} + +COMPILER_RT_VISIBILITY +void llvm_gcda_emit_arcs(uint32_t num_counters, uint64_t *counters) { + uint32_t i; + uint64_t *old_ctrs = NULL; + uint32_t val = 0; + uint64_t save_cur_pos = cur_pos; + + if (!output_file) return; + + val = read_32bit_value(); + + if (val != (uint32_t)-1) { + /* There are counters present in the file. Merge them. */ + if (val != GCOV_TAG_COUNTER_ARCS) { + fprintf(stderr, "profiling: %s: cannot merge previous GCDA file: " + "corrupt arc tag (0x%08x)\n", + filename, val); + return; + } + + val = read_32bit_value(); + if (val == (uint32_t)-1 || val / 2 != num_counters) { + fprintf(stderr, "profiling: %s: cannot merge previous GCDA file: " + "mismatched number of counters (%d)\n", + filename, val); + return; + } + + old_ctrs = malloc(sizeof(uint64_t) * num_counters); + for (i = 0; i < num_counters; ++i) + old_ctrs[i] = read_64bit_value(); + } + + cur_pos = save_cur_pos; + + /* Counter #1 (arcs) tag */ + write_32bit_value(GCOV_TAG_COUNTER_ARCS); + write_32bit_value(num_counters * 2); + for (i = 0; i < num_counters; ++i) { + counters[i] += (old_ctrs ? old_ctrs[i] : 0); + write_64bit_value(counters[i]); + } + + free(old_ctrs); + +#ifdef DEBUG_GCDAPROFILING + fprintf(stderr, "llvmgcda: %u arcs\n", num_counters); + for (i = 0; i < num_counters; ++i) + fprintf(stderr, "llvmgcda: %llu\n", (unsigned long long)counters[i]); +#endif +} + +COMPILER_RT_VISIBILITY +void llvm_gcda_summary_info() { + uint32_t runs = 1; + static uint32_t run_counted = 0; // We only want to increase the run count once. + uint32_t val = 0; + uint64_t save_cur_pos = cur_pos; + + if (!output_file) return; + + val = read_32bit_value(); + + if (val != (uint32_t)-1) { + /* There are counters present in the file. Merge them. */ + uint32_t gcov_tag = + gcov_version >= 90 ? GCOV_TAG_OBJECT_SUMMARY : GCOV_TAG_PROGRAM_SUMMARY; + if (val != gcov_tag) { + fprintf(stderr, + "profiling: %s: cannot merge previous run count: " + "corrupt object tag (0x%08x)\n", + filename, val); + return; + } + + val = read_32bit_value(); /* length */ + uint32_t prev_runs; + if (gcov_version < 90) { + read_32bit_value(); + read_32bit_value(); + prev_runs = read_32bit_value(); + } else { + prev_runs = read_32bit_value(); + read_32bit_value(); + } + for (uint32_t i = gcov_version < 90 ? 3 : 2; i < val; ++i) + read_32bit_value(); + /* Add previous run count to new counter, if not already counted before. */ + runs = run_counted ? prev_runs : prev_runs + 1; + } + + cur_pos = save_cur_pos; + + if (gcov_version >= 90) { + write_32bit_value(GCOV_TAG_OBJECT_SUMMARY); + write_32bit_value(2); + write_32bit_value(runs); + write_32bit_value(0); // sum_max + } else { + // Before gcov 4.8 (r190952), GCOV_TAG_SUMMARY_LENGTH was 9. r190952 set + // GCOV_TAG_SUMMARY_LENGTH to 22. We simply use the smallest length which + // can make gcov read "Runs:". + write_32bit_value(GCOV_TAG_PROGRAM_SUMMARY); + write_32bit_value(3); + write_32bit_value(0); + write_32bit_value(0); + write_32bit_value(runs); + } + + run_counted = 1; + +#ifdef DEBUG_GCDAPROFILING + fprintf(stderr, "llvmgcda: %u runs\n", runs); +#endif +} + +COMPILER_RT_VISIBILITY +void llvm_gcda_end_file() { + /* Write out EOF record. */ + if (output_file) { + write_bytes("\0\0\0\0\0\0\0\0", 8); + + if (new_file) { + fwrite(write_buffer, cur_pos, 1, output_file); + free(write_buffer); + } else { + unmap_file(); + } + + fflush(output_file); + lprofUnlockFd(fd); + fclose(output_file); + output_file = NULL; + write_buffer = NULL; + } + free(filename); + +#ifdef DEBUG_GCDAPROFILING + fprintf(stderr, "llvmgcda: -----\n"); +#endif +} + +COMPILER_RT_VISIBILITY +void llvm_register_writeout_function(fn_ptr fn) { + fn_list_insert(&writeout_fn_list, fn); +} + +COMPILER_RT_VISIBILITY +void llvm_writeout_files(void) { + struct fn_node *curr = writeout_fn_list.head; + + while (curr) { + if (curr->id == CURRENT_ID) { + curr->fn(); + } + curr = curr->next; + } +} + +#ifndef _WIN32 +// __attribute__((destructor)) and destructors whose priorities are greater than +// 100 run before this function and can thus be tracked. The priority is +// compatible with GCC 7 onwards. +#if __GNUC__ >= 9 +#pragma GCC diagnostic ignored "-Wprio-ctor-dtor" +#endif +__attribute__((destructor(100))) +#endif +static void llvm_writeout_and_clear(void) { + llvm_writeout_files(); + fn_list_remove(&writeout_fn_list); +} + +COMPILER_RT_VISIBILITY +void llvm_register_reset_function(fn_ptr fn) { + fn_list_insert(&reset_fn_list, fn); +} + +COMPILER_RT_VISIBILITY +void llvm_delete_reset_function_list(void) { fn_list_remove(&reset_fn_list); } + +COMPILER_RT_VISIBILITY +void llvm_reset_counters(void) { + struct fn_node *curr = reset_fn_list.head; + + while (curr) { + if (curr->id == CURRENT_ID) { + curr->fn(); + } + curr = curr->next; + } +} + +#if !defined(_WIN32) +COMPILER_RT_VISIBILITY +pid_t __gcov_fork() { + pid_t parent_pid = getpid(); + pid_t pid = fork(); + + if (pid == 0) { + pid_t child_pid = getpid(); + if (child_pid != parent_pid) { + // The pid changed so we've a fork (one could have its own fork function) + // Just reset the counters for this child process + // threads. + llvm_reset_counters(); + } + } + return pid; +} +#endif + +COMPILER_RT_VISIBILITY +void llvm_gcov_init(fn_ptr wfn, fn_ptr rfn) { + static int atexit_ran = 0; + + if (wfn) + llvm_register_writeout_function(wfn); + + if (rfn) + llvm_register_reset_function(rfn); + + if (atexit_ran == 0) { + atexit_ran = 1; + + /* Make sure we write out the data and delete the data structures. */ + atexit(llvm_delete_reset_function_list); +#ifdef _WIN32 + atexit(llvm_writeout_and_clear); +#endif + } +} + +void __gcov_dump(void) { + for (struct fn_node *f = writeout_fn_list.head; f; f = f->next) + f->fn(); +} + +void __gcov_reset(void) { + for (struct fn_node *f = reset_fn_list.head; f; f = f->next) + f->fn(); +} + +#endif |