pw_assert_tokenized: Highly compressed asserts
This change introduces a highly compressed tokenized assert backend that
drops some of the rich PW_CHECK() support in favor of maximum
compactness.
Change-Id: Ifc34a352d071223f712c34d450e15c7c6dc818a3
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/67261
Pigweed-Auto-Submit: Armando Montanez <amontanez@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index a1391b1..8c71a71 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -69,6 +69,7 @@
"$dir_pw_assert:docs",
"$dir_pw_assert_basic:docs",
"$dir_pw_assert_log:docs",
+ "$dir_pw_assert_tokenized:docs",
"$dir_pw_assert_zephyr:docs",
"$dir_pw_base64:docs",
"$dir_pw_bloat:docs",
diff --git a/modules.gni b/modules.gni
index b78953e..8b9dc58 100644
--- a/modules.gni
+++ b/modules.gni
@@ -24,6 +24,7 @@
dir_pw_assert = get_path_info("pw_assert", "abspath")
dir_pw_assert_basic = get_path_info("pw_assert_basic", "abspath")
dir_pw_assert_log = get_path_info("pw_assert_log", "abspath")
+ dir_pw_assert_tokenized = get_path_info("pw_assert_tokenized", "abspath")
dir_pw_assert_zephyr = get_path_info("pw_assert_zephyr", "abspath")
dir_pw_base64 = get_path_info("pw_base64", "abspath")
dir_pw_bloat = get_path_info("pw_bloat", "abspath")
diff --git a/pw_assert_tokenized/BUILD.bazel b/pw_assert_tokenized/BUILD.bazel
new file mode 100644
index 0000000..bd2fe86
--- /dev/null
+++ b/pw_assert_tokenized/BUILD.bazel
@@ -0,0 +1,47 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+load(
+ "//pw_build:pigweed.bzl",
+ "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+pw_cc_library(
+ name = "pw_assert_tokenized",
+ srcs = [
+ "log_handler.cc",
+ ],
+ hdrs = [
+ "assert_public_overrides/pw_assert_backend/assert_lite_backend.h",
+ "check_public_overrides/pw_assert_backend/assert_backend.h",
+ "public/pw_assert_tokenized/assert_tokenized.h",
+ "public/pw_assert_tokenized/check_tokenized.h",
+ "public/pw_assert_tokenized/handler.h",
+ ],
+ includes = [
+ "assert_public_overrides",
+ "check_public_overrides",
+ "public",
+ ],
+ deps = [
+ "//pw_assert",
+ "//pw_log_tokenized,",
+ "//pw_preprocessor",
+ "//pw_tokenizer",
+ ],
+)
diff --git a/pw_assert_tokenized/BUILD.gn b/pw_assert_tokenized/BUILD.gn
new file mode 100644
index 0000000..42561a4
--- /dev/null
+++ b/pw_assert_tokenized/BUILD.gn
@@ -0,0 +1,110 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+
+declare_args() {
+ pw_assert_tokenized_HANDLER_BACKEND = "$dir_pw_assert_tokenized:log_handler"
+}
+
+config("public_include_path") {
+ include_dirs = [ "public" ]
+ visibility = [ ":*" ]
+}
+
+config("assert_backend_config") {
+ include_dirs = [ "assert_public_overrides" ]
+ visibility = [ ":*" ]
+}
+
+config("check_backend_config") {
+ include_dirs = [ "check_public_overrides" ]
+ visibility = [ ":*" ]
+}
+
+pw_source_set("handler") {
+ public_configs = [ ":public_include_path" ]
+ public_deps = [ "$dir_pw_preprocessor" ]
+ public = [ "public/pw_assert_tokenized/handler.h" ]
+}
+
+pw_source_set("assert_backend") {
+ public_configs = [
+ ":public_include_path",
+ ":assert_backend_config",
+ ]
+ public_deps = [
+ ":handler",
+ "$dir_pw_tokenizer",
+ ]
+ public = [
+ "assert_public_overrides/pw_assert_backend/assert_lite_backend.h",
+ "public/pw_assert_tokenized/assert_tokenized.h",
+ ]
+}
+
+pw_source_set("assert_backend.impl") {
+ public_deps = [ pw_assert_tokenized_HANDLER_BACKEND ]
+}
+
+pw_source_set("check_backend") {
+ public_configs = [
+ ":public_include_path",
+ ":check_backend_config",
+ ]
+ public_deps = [
+ ":handler",
+ "$dir_pw_tokenizer",
+ ]
+ public = [
+ "check_public_overrides/pw_assert_backend/assert_backend.h",
+ "public/pw_assert_tokenized/check_tokenized.h",
+ ]
+}
+
+pw_source_set("check_backend.impl") {
+ public_deps = [ pw_assert_tokenized_HANDLER_BACKEND ]
+}
+
+pw_source_set("pw_assert_tokenized") {
+ public_deps = [
+ ":assert_backend",
+ ":check_backend",
+ ]
+}
+
+pw_source_set("pw_assert_tokenized.impl") {
+ deps = [
+ ":assert_backend.impl",
+ ":check_backend.impl",
+ ]
+}
+
+pw_source_set("log_handler") {
+ deps = [
+ ":handler",
+ "$dir_pw_assert:config",
+ "$dir_pw_base64",
+ "$dir_pw_log",
+ "$dir_pw_log_tokenized",
+ ]
+ sources = [ "log_handler.cc" ]
+}
+
+pw_doc_group("docs") {
+ sources = [ "docs.rst" ]
+}
diff --git a/pw_assert_tokenized/assert_public_overrides/pw_assert_backend/assert_lite_backend.h b/pw_assert_tokenized/assert_public_overrides/pw_assert_backend/assert_lite_backend.h
new file mode 100644
index 0000000..561f521
--- /dev/null
+++ b/pw_assert_tokenized/assert_public_overrides/pw_assert_backend/assert_lite_backend.h
@@ -0,0 +1,16 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "pw_assert_tokenized/assert_tokenized.h"
diff --git a/pw_assert_tokenized/check_public_overrides/pw_assert_backend/assert_backend.h b/pw_assert_tokenized/check_public_overrides/pw_assert_backend/assert_backend.h
new file mode 100644
index 0000000..4a1e3ae
--- /dev/null
+++ b/pw_assert_tokenized/check_public_overrides/pw_assert_backend/assert_backend.h
@@ -0,0 +1,16 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "pw_assert_tokenized/check_tokenized.h"
diff --git a/pw_assert_tokenized/docs.rst b/pw_assert_tokenized/docs.rst
new file mode 100644
index 0000000..bc645a7
--- /dev/null
+++ b/pw_assert_tokenized/docs.rst
@@ -0,0 +1,95 @@
+.. _module-pw_assert_tokenized:
+
+===================
+pw_assert_tokenized
+===================
+
+--------
+Overview
+--------
+The ``pw_assert_tokenized`` module provides ``PW_ASSERT()`` and ``PW_CHECK_*()``
+backends for the ``pw_assert`` module. These backends are much more space
+efficient than using ``pw_assert_log`` with ``pw_log_tokenized`` The tradeoff,
+however, is that ``PW_CHECK_*()`` macros are much more limited as all argument
+values are discarded. This means only constant string information is captured in
+the reported tokens.
+
+* **PW_ASSERT()**: The ``PW_ASSERT()`` macro will capture the file name and line
+ number of the assert statement. By default, it is passed to the logging system
+ to produce a string like this:
+
+ PW_ASSERT() or PW_DASSERT() failure at
+ pw_result/public/pw_result/result.h:63
+
+* **PW_CHECK_\*()**: The ``PW_CHECK_*()`` macros work in contexts where
+ tokenization is fully supported, so they are able to capture the CHECK
+ statement expression and any provided string literal in addition to the file
+ name:
+
+ Check failure in pw_metric/size_report/base.cc: \*unoptimizable >= 0,
+ Ensure this CHECK logic stays.
+
+ Evaluated values of ``PW_CHECK_*()`` statements are not captured, and any
+ string formatting arguments are also not captured. This minimizes call-site
+ cost as only two arguments are ever passed to the handler (the calculated
+ token, and the line number of the statement).
+
+ Note that the line number is passed to the tokenized logging system as
+ metadata, but is not part of the tokenized string. This is to ensure the
+ CHECK callsite maximizes efficiency by only passing two arguments to the
+ handler.
+
+In both cases, the assert handler is only called with two arguments: a 32-bit
+token to represent a string, and the integer line number of the callsite.
+
+-----
+Setup
+-----
+
+#. Set ``pw_assert_BACKEND = "$dir_pw_assert_tokenized:check_backend"`` and
+ ``pw_assert_LITE_BACKEND = "$dir_pw_assert_tokenized:assert_backend"`` in
+ your target configuration.
+#. Ensure your target provides ``pw_tokenizer_GLOBAL_HANDLER_BACKEND``. By
+ default, pw_assert_tokenized will forward assert failures to the tokenizer
+ handler as logs. The tokenizer handler should check for ``LOG_LEVEL_FATAL``
+ and properly divert to a crash handler.
+#. Add file name tokens to your token database. pw_assert_tokenized can't create
+ file name tokens that can be parsed out of the final compiled binary. The
+ ``pw_relative_source_file_names``
+ `GN template <module-pw_build-relative-source-file-names>`_ can be used to
+ collect the names of all source files used in your final executable into a
+ JSON file, which can then be included in the creation of a tokenizer
+ database.
+
+Example file name token database setup
+--------------------------------------
+
+.. code-block::
+
+ pw_executable("main") {
+ deps = [
+ # ...
+ ]
+ sources = [ "main.cc" ]
+ }
+
+ pw_tokenizer_database("log_tokens") {
+ database = "tools/tokenized_logs.csv"
+ deps = [
+ ":source_file_names",
+ ":main",
+ ]
+ optional_paths = [ "$root_build_dir/**/*.elf" ]
+ input_databases = [ "$target_gen_dir/source_file_names.json" ]
+ }
+
+ # Extracts all source/header file names from "main" and its transitive
+ # dependencies for tokenization.
+ pw_relative_source_file_names("source_file_names") {
+ deps = [ ":main" ]
+ outputs = [ "$target_gen_dir/source_file_names.json" ]
+ }
+
+
+.. warning::
+ This module is experimental and does not provide a stable API.
diff --git a/pw_assert_tokenized/log_handler.cc b/pw_assert_tokenized/log_handler.cc
new file mode 100644
index 0000000..ea559ea
--- /dev/null
+++ b/pw_assert_tokenized/log_handler.cc
@@ -0,0 +1,66 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <span>
+
+#include "pw_assert/config.h"
+#include "pw_assert_tokenized/handler.h"
+#include "pw_base64/base64.h"
+#include "pw_log/log.h"
+#include "pw_log_tokenized/log_tokenized.h"
+
+extern "C" void pw_assert_tokenized_HandleAssertFailure(
+ uint32_t tokenized_file_name, int line_number) {
+ // Buffer size for binary->base64 conversion with a null terminator.
+ constexpr size_t kBufferSize =
+ pw::base64::EncodedSize(sizeof(tokenized_file_name)) + 1;
+ std::byte* hash_buffer = reinterpret_cast<std::byte*>(&tokenized_file_name);
+ char base64_buffer[kBufferSize];
+
+ size_t len =
+ pw::base64::Encode(std::span(hash_buffer, sizeof(tokenized_file_name)),
+ std::span(base64_buffer));
+ base64_buffer[len] = '\0';
+#if PW_ASSERT_ENABLE_DEBUG
+ PW_LOG(PW_LOG_LEVEL_FATAL,
+ PW_LOG_FLAGS,
+ "PW_ASSERT() or PW_DASSERT() failure at $%s:%d",
+ base64_buffer,
+ line_number);
+#else
+ PW_LOG(PW_LOG_LEVEL_FATAL,
+ PW_LOG_FLAGS,
+ "PW_ASSERT() failure. Note: PW_DASSERT disabled $%s:%d",
+ base64_buffer,
+ line_number);
+#endif // PW_ASSERT_ENABLE_DEBUG
+ PW_UNREACHABLE;
+}
+
+extern "C" void pw_assert_tokenized_HandleCheckFailure(
+ uint32_t tokenized_message, int line_number) {
+ // TODO(amontanez): There should be a less-hacky way to assemble this.
+ const uint32_t payload = _PW_LOG_TOKENIZED_LEVEL(PW_LOG_LEVEL_FATAL) |
+ _PW_LOG_TOKENIZED_FLAGS(PW_LOG_FLAGS) |
+ _PW_LOG_TOKENIZED_LINE(line_number);
+ uint8_t token_buffer[sizeof(tokenized_message)];
+ memcpy(token_buffer, &tokenized_message, sizeof(tokenized_message));
+
+ pw_tokenizer_HandleEncodedMessageWithPayload(
+ payload, token_buffer, sizeof(token_buffer));
+ PW_UNREACHABLE;
+}
diff --git a/pw_assert_tokenized/public/pw_assert_tokenized/assert_tokenized.h b/pw_assert_tokenized/public/pw_assert_tokenized/assert_tokenized.h
new file mode 100644
index 0000000..7d3811d
--- /dev/null
+++ b/pw_assert_tokenized/public/pw_assert_tokenized/assert_tokenized.h
@@ -0,0 +1,24 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "pw_assert_tokenized/handler.h"
+#include "pw_tokenizer/tokenize.h"
+
+// The tokens generated by this expression are not stored in the ELF in a way
+// that the pw_tokenizer tooling can extract. Tokenized file names must be
+// generated offline separately.
+#define PW_ASSERT_HANDLE_FAILURE(expression) \
+ pw_assert_tokenized_HandleAssertFailure(PW_TOKENIZER_STRING_TOKEN(__FILE__), \
+ __LINE__);
diff --git a/pw_assert_tokenized/public/pw_assert_tokenized/check_tokenized.h b/pw_assert_tokenized/public/pw_assert_tokenized/check_tokenized.h
new file mode 100644
index 0000000..591a194
--- /dev/null
+++ b/pw_assert_tokenized/public/pw_assert_tokenized/check_tokenized.h
@@ -0,0 +1,40 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "pw_assert_tokenized/handler.h"
+#include "pw_tokenizer/tokenize.h"
+
+#define _PW_ASSERT_TOKENIZED_TO_HANDLER(str) \
+ do { \
+ constexpr uint32_t token = \
+ PW_TOKENIZE_STRING("Check failure in " __FILE__ ": " str); \
+ pw_assert_tokenized_HandleCheckFailure(token, __LINE__); \
+ } while (0)
+
+#define PW_HANDLE_CRASH(...) _PW_ASSERT_TOKENIZED_TO_HANDLER(#__VA_ARGS__)
+
+#define PW_HANDLE_ASSERT_FAILURE(condition_string, message, ...) \
+ _PW_ASSERT_TOKENIZED_TO_HANDLER(condition_string ", " message)
+
+#define PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE(arg_a_str, \
+ arg_a_val, \
+ comparison_op_str, \
+ arg_b_str, \
+ arg_b_val, \
+ type_fmt, \
+ message, \
+ ...) \
+ _PW_ASSERT_TOKENIZED_TO_HANDLER( \
+ arg_a_str " " comparison_op_str " " arg_b_str ", " message #__VA_ARGS__)
diff --git a/pw_assert_tokenized/public/pw_assert_tokenized/handler.h b/pw_assert_tokenized/public/pw_assert_tokenized/handler.h
new file mode 100644
index 0000000..dcdba50
--- /dev/null
+++ b/pw_assert_tokenized/public/pw_assert_tokenized/handler.h
@@ -0,0 +1,29 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include <stdint.h>
+
+#include "pw_preprocessor/compiler.h"
+#include "pw_preprocessor/util.h"
+
+PW_EXTERN_C_START
+
+PW_NO_RETURN void pw_assert_tokenized_HandleAssertFailure(
+ uint32_t tokenized_file_name, int line_number);
+
+PW_NO_RETURN void pw_assert_tokenized_HandleCheckFailure(
+ uint32_t tokenized_message, int line_number);
+
+PW_EXTERN_C_END
diff --git a/pw_build/docs.rst b/pw_build/docs.rst
index d0c6628..322f796 100644
--- a/pw_build/docs.rst
+++ b/pw_build/docs.rst
@@ -470,6 +470,8 @@
├── file1.txt
└── renamed.txt
+.. _module-pw_build-relative-source-file-names:
+
pw_relative_source_file_names
-----------------------------
This template recursively walks the listed dependencies and collects the names
diff --git a/pw_log_tokenized/public/pw_log_tokenized/log_tokenized.h b/pw_log_tokenized/public/pw_log_tokenized/log_tokenized.h
index 42cc614..110d542 100644
--- a/pw_log_tokenized/public/pw_log_tokenized/log_tokenized.h
+++ b/pw_log_tokenized/public/pw_log_tokenized/log_tokenized.h
@@ -58,7 +58,7 @@
PW_LOG_TOKENIZED_ENCODE_MESSAGE( \
(_PW_LOG_TOKENIZED_LEVEL(_pw_log_tokenized_level) | \
_PW_LOG_TOKENIZED_MODULE(_pw_log_tokenized_module_token) | \
- _PW_LOG_TOKENIZED_FLAGS(flags) | _PW_LOG_TOKENIZED_LINE()), \
+ _PW_LOG_TOKENIZED_FLAGS(flags) | _PW_LOG_TOKENIZED_LINE(__LINE__)), \
PW_LOG_TOKENIZED_FORMAT_STRING(message), \
__VA_ARGS__); \
} while (0)
@@ -76,10 +76,10 @@
// If the line number field is present, shift it to its position. Set it to zero
// if the line number is too large for PW_LOG_TOKENIZED_LINE_BITS.
#if PW_LOG_TOKENIZED_LINE_BITS == 0
-#define _PW_LOG_TOKENIZED_LINE() ((uintptr_t)0)
+#define _PW_LOG_TOKENIZED_LINE(line) ((uintptr_t)0)
#else
-#define _PW_LOG_TOKENIZED_LINE() \
- ((uintptr_t)(__LINE__ < (1 << PW_LOG_TOKENIZED_LINE_BITS) ? __LINE__ : 0) \
+#define _PW_LOG_TOKENIZED_LINE(line) \
+ ((uintptr_t)(line < (1 << PW_LOG_TOKENIZED_LINE_BITS) ? line : 0) \
<< PW_LOG_TOKENIZED_LEVEL_BITS)
#endif // PW_LOG_TOKENIZED_LINE_BITS