[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) {}