[sw] Implement test_coverage library
This change:
- Introduces the `test_coverage` library that provides LLVM profiling
runtime support and a function for sending the LLVM profile buffer
over UART, and
- Introduces a new meson option named `coverage` to define the relevant
dependencies conditionally.
Signed-off-by: Alphan Ulusoy <alphan@google.com>
diff --git a/meson.build b/meson.build
index b329faa..3ecfccc 100644
--- a/meson.build
+++ b/meson.build
@@ -36,6 +36,11 @@
meson.add_postconf_script(meson.source_root() + '/util/meson-purge-includes.sh')
endif
+coverage = get_option('coverage')
+# Coverage requires clang.
+if coverage and meson.get_compiler('c').get_id() != 'clang'
+ error('Coverage requires clang.')
+endif
# C Arguments to optimize for size, used on cross builds only.
optimize_size_args = [
diff --git a/meson_init.sh b/meson_init.sh
index a4fba83..81ebd4d 100755
--- a/meson_init.sh
+++ b/meson_init.sh
@@ -49,6 +49,7 @@
-K: Keep include search paths as generated by Meson.
-t FILE: Configure Meson with toolchain configuration FILE
-T PATH: Build tock from PATH rather than the remote repository.
+ -c: Enable coverage (requires clang).
USAGE
}
@@ -61,9 +62,10 @@
FLAGS_reconfigure=""
FLAGS_keep_includes=false
FLAGS_specified_toolchain_file=false
+FLAGS_coverage=false
ARG_toolchain_file="${TOOLCHAIN_PATH}/meson-riscv32-unknown-elf-clang.txt"
ARG_tock_dir=""
-while getopts 'r?:f?:K?:A?:T:t:' flag; do
+while getopts 'r?:f?:K?:A?:T:t:c?' flag; do
case "${flag}" in
f) FLAGS_force=true;;
r) FLAGS_reconfigure="--reconfigure";;
@@ -72,6 +74,7 @@
t) FLAGS_specified_toolchain_file=true
ARG_toolchain_file="${OPTARG}";;
T) ARG_tock_dir="${OPTARG}";;
+ c) FLAGS_coverage=true;;
?) usage && exit 1;;
:) usage
error "${OPTARG} requires a path argument"
@@ -161,5 +164,6 @@
-Dhost_bin_dir="$HOST_BIN_DIR" \
-Dtock_local="$TOCK_LOCAL" \
-Dkeep_includes="$FLAGS_keep_includes" \
+ -Dcoverage="$FLAGS_coverage" \
--cross-file="$ARG_toolchain_file" \
"$OBJ_DIR"
diff --git a/meson_options.txt b/meson_options.txt
index e8bfeda..c4feec5 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -31,3 +31,9 @@
type: 'boolean',
value: true,
)
+
+option(
+ 'coverage',
+ type: 'boolean',
+ value: false,
+)
diff --git a/sw/device/lib/testing/meson.build b/sw/device/lib/testing/meson.build
index d3df6b8..3cf9ea7 100644
--- a/sw/device/lib/testing/meson.build
+++ b/sw/device/lib/testing/meson.build
@@ -2,7 +2,7 @@
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
-# Test status libary.
+# Test status library.
sw_lib_testing_test_status = declare_dependency(
link_with: static_library(
'test_status_ot',
@@ -15,6 +15,40 @@
)
)
+# NOP coverage dependencies when coverage is not enabled.
+sw_lib_testing_test_coverage = declare_dependency(
+ link_with: static_library(
+ 'test_coverage_none',
+ sources: [files('test_coverage_none.c')],
+ ),
+)
+collect_coverage = declare_dependency()
+
+if coverage
+ # Test coverage library that provides runtime functions for LLVM profiling.
+ sw_lib_testing_test_coverage = declare_dependency(
+ link_with: static_library(
+ 'test_coverage_llvm',
+ include_directories: sw_vendor_llvm_clang_rt_inc_dir,
+ sources: [
+ sw_vendor_llvm_clang_rt_sources,
+ files('test_coverage_llvm.c'),
+ ],
+ dependencies: [
+ sw_lib_mem,
+ sw_lib_uart,
+ sw_lib_base_log,
+ ],
+ ),
+ )
+
+ # Dependency for enabling coverage
+ collect_coverage = declare_dependency(
+ compile_args: ['-fprofile-instr-generate', '-fcoverage-mapping'],
+ dependencies: sw_lib_testing_test_coverage,
+ )
+endif
+
sw_lib_testing_test_main = declare_dependency(
link_with: static_library(
'test_main_ot',
diff --git a/sw/device/lib/testing/test_coverage.h b/sw/device/lib/testing/test_coverage.h
new file mode 100644
index 0000000..4a0da29
--- /dev/null
+++ b/sw/device/lib/testing/test_coverage.h
@@ -0,0 +1,12 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Sends the LLVM profile buffer along with its length and CRC32.
+ *
+ * This function must be called at the end of a test. Note that this profile
+ * data is raw and must be indexed before it can be used to generate coverage
+ * reports.
+ */
+void test_coverage_send_buffer(void);
diff --git a/sw/device/lib/testing/test_coverage_llvm.c b/sw/device/lib/testing/test_coverage_llvm.c
new file mode 100644
index 0000000..6328ca6
--- /dev/null
+++ b/sw/device/lib/testing/test_coverage_llvm.c
@@ -0,0 +1,76 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/testing/test_coverage.h"
+#include "sw/device/lib/base/log.h"
+#include "sw/device/lib/uart.h"
+#include "sw/vendor/llvm_clang_rt_profile/compiler-rt/lib/profile/InstrProfiling.h"
+#include <stdint.h>
+
+/**
+ * When the linker finds a definition of this symbol, it knows to skip loading
+ * the object which contains the profiling runtime’s static initializer. See
+ * https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#using-the-profiling-runtime-without-static-initializers
+ * for more information.
+ */
+int __llvm_profile_runtime;
+
+/**
+ * Computes the CRC32 of a buffer as expected by Python's `zlib.crc32()`. The
+ * implementation below is basically a simplified, i.e. byte-by-byte and without
+ * a lookup table, version of zlib's crc32. See
+ * https://github.com/madler/zlib/blob/2fa463bacfff79181df1a5270fb67cc679a53e71/crc32.c,
+ * lines 111-112 and 276-279.
+ */
+static uint32_t crc32(uint8_t *buf, size_t len) {
+ // CRC32 polynomial.
+ static const uint32_t kPoly = 0xEDB88320;
+ // Since we use a contiguous buffer, we don't need to call this function
+ // multiple times. That's why `crc` is not a parameter and is initialized to
+ // `UINT32_MAX`, i.e. `~0`.
+ uint32_t crc = UINT32_MAX;
+ for (size_t i = 0; i < len; ++i) {
+ crc ^= buf[i];
+ for (uint8_t j = 0; j < 8; ++j) {
+ bool lsb = crc & 1;
+ crc >>= 1;
+ if (lsb) {
+ crc ^= kPoly;
+ }
+ }
+ }
+ return ~crc;
+}
+
+/**
+ * Sends the given buffer as a hex string over UART.
+ */
+static void send_buffer(uint8_t *buf, size_t len) {
+ for (uint32_t i = 0; i < len; ++i) {
+ base_printf("%2X", buf[i]);
+ }
+}
+
+void test_coverage_send_buffer(void) {
+ // It looks like we don't have a way to read the profile buffer incrementally.
+ // Thus, we define the following buffer.
+ uint8_t buf[8192] = {0};
+ // Write the profile buffer to the buffer defined above.
+ uint32_t buf_size = (uint32_t)__llvm_profile_get_size_for_buffer();
+ if (buf_size > sizeof(buf)) {
+ LOG_ERROR("ERROR: LLVM profile buffer is too large: %u bytes.", buf_size);
+ } else {
+ __llvm_profile_write_buffer((char *)buf);
+ // Send the buffer along with its length and CRC32.
+ base_printf("\r\nLLVM profile data (length: %u, CRC32: ", buf_size);
+ uint32_t checksum = crc32(buf, buf_size);
+ send_buffer((uint8_t *)&checksum, sizeof(checksum));
+ base_printf("):\r\n");
+ send_buffer(buf, buf_size);
+ base_printf("\r\n");
+ }
+ // Send `EOT` so that `cat` can exit. Note that this requires enabling
+ // `icanon` using `stty`.
+ uart_send_char(4);
+}
diff --git a/sw/device/lib/testing/test_coverage_none.c b/sw/device/lib/testing/test_coverage_none.c
new file mode 100644
index 0000000..67af8f0
--- /dev/null
+++ b/sw/device/lib/testing/test_coverage_none.c
@@ -0,0 +1,9 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/testing/test_coverage.h"
+
+// This NOP function gets linked in when coverage is disabled. See
+// `test_coverage_llvm.c` for its actual definition when coverage is enabled.
+void test_coverage_send_buffer(void) {}