pw_assert: Add new "light" PW_ASSERT macros

This introduces two new macros: PW_ASSERT() and PW_DASSERT(). These
exist to offer a lightweight assert that is safe to use in headers and
is safe for constexpr. Most of the time, these macros should not be
used, and instead the PW_CHECK() and PW_DCHECK() variants used instead.

Other changes:

- Renames PW_ASSERT_ENABLE_DCHECK to PW_ASSERT_ENABLE_DEBUG, to reflect
  that the setting applies to both PW_CHECK and PW_ASSERT.
- Updates Roadamp & Status documentation sections

Testing: Since currently it is not possible to test the PW_ASSERT macros
due to needing to swap the backend, this is manually tested by flipping
the #if in pw_assert/light_test.cc to 1, and verifying that it crashes
as expected.

Change-Id: I6d2c68f772da4280e61a52576bfb6ab60e280bdf
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/17462
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Keir Mierle <keir@google.com>
diff --git a/pw_assert/BUILD b/pw_assert/BUILD
index 16e8cec..6b799f1 100644
--- a/pw_assert/BUILD
+++ b/pw_assert/BUILD
@@ -29,6 +29,8 @@
     name = "facade",
     hdrs = [
         "public/pw_assert/assert.h",
+        "public/pw_assert/light.h",
+        "public/pw_assert/options.h",
         "public/pw_assert/internal/assert_impl.h",
     ],
     includes = ["public"],
@@ -58,6 +60,7 @@
     srcs = [
         "assert_facade_test.cc",
         "fake_backend.cc",
+        "light_test.cc",
         "public/pw_assert/internal/assert_impl.h",
         "pw_assert_test/fake_backend.h",
     ],
diff --git a/pw_assert/BUILD.gn b/pw_assert/BUILD.gn
index d8d1ad1..bf65d47 100644
--- a/pw_assert/BUILD.gn
+++ b/pw_assert/BUILD.gn
@@ -34,18 +34,50 @@
     "public/pw_assert/assert.h",
     "public/pw_assert/internal/assert_impl.h",
   ]
-  public_deps = [ dir_pw_preprocessor ]
+  public_deps = [
+    dir_pw_preprocessor,
+
+    # Also expose light.h to all users of pw_assert.
+    ":light",
+  ]
+}
+
+# Provide a way include "pw_assert/light.h" without depending on the full
+# assert facade. This enables relying on light asserts from low-level headers
+# like polyfill or span that might trigger circular includes due to the
+# backend.
+#
+# See the docs for more discussion around where to use which assert system.
+pw_source_set("light") {
+  public_configs = [ ":default_config" ]
+  public = [
+    "public/pw_assert/light.h",
+
+    # Needed for PW_ASSERT_ENABLE_DEBUG. Note that depending on :pw_assert to
+    # get options.h won't work here since it will trigger the circular include
+    # problem that light asserts are designed to solve.
+    "public/pw_assert/options.h",
+  ]
+}
+
+# Note: While this is technically a test, doesn't verify any of the output and
+# is more of a compile test. The results can be visually verified if desired.
+pw_test("light_test") {
+  configs = [ ":default_config" ]
+  sources = [ "light_test.cc" ]
+  deps = [ ":pw_assert" ]
 }
 
 pw_test_group("tests") {
   tests = [
     ":assert_backend_compile_test",
     ":assert_facade_test",
+    ":light_test",
   ]
 }
 
 # The assert facade test doesn't require a backend since a fake one is
-# provided.  However, since this doesn't depend on the backend it re-includes
+# provided. However, since this doesn't depend on the backend it re-includes
 # the facade headers.
 pw_test("assert_facade_test") {
   configs = [ ":default_config" ]  # For internal/assert_impl.h
@@ -55,7 +87,10 @@
     "public/pw_assert/internal/assert_impl.h",
     "pw_assert_test/fake_backend.h",
   ]
-  deps = [ dir_pw_status ]
+  deps = [
+    ":light",
+    dir_pw_status,
+  ]
 
   # TODO(frolv): Fix this test on the QEMU target.
   enable_if = pw_build_EXECUTABLE_TARGET_TYPE != "lm3s6965evb_executable"
diff --git a/pw_assert/assert_facade_test.cc b/pw_assert/assert_facade_test.cc
index 8621163..34faecb 100644
--- a/pw_assert/assert_facade_test.cc
+++ b/pw_assert/assert_facade_test.cc
@@ -384,7 +384,7 @@
 
 // Verify side effects of debug checks work as expected.
 // Only check a couple of cases, since the logic is all the same.
-#if PW_ASSERT_ENABLE_DCHECK
+#if PW_ASSERT_ENABLE_DEBUG
 // When DCHECKs are enabled, they behave the same as normal checks.
 TEST(AssertPass, DCheckEnabledSingleSideEffectingCall) {
   global_state_for_multi_evaluate_test = 0;
@@ -417,7 +417,7 @@
   EXPECT_EQ(global_state_for_multi_evaluate_test, 2);
 }
 
-#else  // PW_ASSERT_ENABLE_DCHECK
+#else  // PW_ASSERT_ENABLE_DEBUG
 
 // When DCHECKs are disabled, they should not trip, and their arguments
 // shouldn't be evaluated.
@@ -451,7 +451,7 @@
   PW_DCHECK_INT_EQ(IncrementsGlobal() + 10, IncrementsGlobal());
   EXPECT_EQ(global_state_for_multi_evaluate_test, 0);
 }
-#endif  // PW_ASSERT_ENABLE_DCHECK
+#endif  // PW_ASSERT_ENABLE_DEBUG
 
 // Note: This requires enabling PW_ASSERT_USE_SHORT_NAMES 1 above.
 TEST(Check, ShortNamesWork) {
@@ -512,7 +512,7 @@
 TEST_F(AssertFail, Dynamic) { PW_CHECK_OK(MakeStatus(pw::Status::UNKNOWN)); }
 TEST_F(AssertFail, Enum) { PW_CHECK_OK(PW_STATUS_UNKNOWN); }
 
-#if PW_ASSERT_ENABLE_DCHECK
+#if PW_ASSERT_ENABLE_DEBUG
 
 // In debug mode, the asserts should check their arguments.
 TEST_F(AssertPass, DCheckConstant) { PW_DCHECK_OK(pw::Status::OK); }
@@ -521,7 +521,7 @@
 TEST_F(AssertFail, DCheckDynamic) {
   PW_DCHECK_OK(MakeStatus(pw::Status::UNKNOWN));
 }
-#else  // PW_ASSERT_ENABLE_DCHECK
+#else  // PW_ASSERT_ENABLE_DEBUG
 
 // In release mode, all the asserts should pass.
 TEST_F(AssertPass, DCheckConstant) { PW_DCHECK_OK(pw::Status::OK); }
@@ -530,7 +530,7 @@
 TEST_F(AssertPass, DCheckDynamic) {
   PW_DCHECK_OK(MakeStatus(pw::Status::UNKNOWN));
 }
-#endif  // PW_ASSERT_ENABLE_DCHECK
+#endif  // PW_ASSERT_ENABLE_DEBUG
 
 // TODO: Figure out how to run some of these tests is C.
 
diff --git a/pw_assert/docs.rst b/pw_assert/docs.rst
index ca13883..4c2a63e 100644
--- a/pw_assert/docs.rst
+++ b/pw_assert/docs.rst
@@ -29,6 +29,7 @@
   a message.
 - **PW_CHECK_<type>_<cmp>(a, b[, fmt, ...])** - Assert that the expression ``a
   <cmp> b`` is true, optionally with a message.
+- **PW_ASSERT(condition)** - Header- and constexpr- assert.
 
 .. tip::
 
@@ -73,6 +74,11 @@
     // The functions ItemCount() and GetStateStr() are never called.
     PW_DCHECK_INT_LE(ItemCount(), 100, "System state: %s", GetStateStr());
 
+.. tip::
+
+  Use ``PW_ASSERT`` from ``pw_assert/light.h`` for asserts in headers or
+  asserting in ``constexpr`` contexts.
+
 Structure of assert modules
 ---------------------------
 The module is split into two components:
@@ -97,12 +103,12 @@
 
 See the Backend API section below for more details.
 
---------------------
-Facade API reference
---------------------
+----------
+Facade API
+----------
 
 The below functions describe the assert API functions that applications should
-invoke to assert.
+invoke to assert. These macros found in the ``pw_assert/assert.h`` header.
 
 .. cpp:function:: PW_CRASH(format, ...)
 
@@ -391,9 +397,90 @@
     code; for example ``status == RESOURCE_EXHAUSTED`` instead of ``status ==
     5``.
 
----------------------
-Backend API reference
----------------------
+---------
+Light API
+---------
+The normal ``PW_CHECK_*`` and ``PW_DCHECK_*`` family of macros are intended to
+provide rich debug information, like the file, line number, value of operands
+in boolean comparisons, and more. However, this comes at a cost: these macros
+depend directly on the backend headers, and may perform complicated call-site
+transformations like tokenization.
+
+There are several issues with the normal ``PW_CHECK_*`` suite of macros:
+
+1. ``PW_CHECK_*`` in headers can cause ODR violations in the case of tokenized
+   asserts, due to differing module choices.
+2. ``PW_CHECK_*`` is not constexpr-safe.
+3. ``PW_CHECK_*`` can cause code bloat with some backends; this is the tradeoff
+   to get rich assert information.
+4. ``PW_CHECK_*`` can trigger circular dependencies when asserts are used from
+   low-level contexts, like in ``<span>``.
+
+**Light asserts** solve all of the above three problems: No risk of ODR
+violations, are constexpr safe, and have a tiny call site footprint; and there
+is no header dependency on the backend preventing circular include issues.
+However, there are **no format messages, no captured line number, no captured
+file, no captured expression, or anything other than a binary indication of
+failure**.
+
+Example
+-------
+
+.. code-block:: cpp
+
+  // This example demonstrates asserting in a header.
+
+  #include "pw_assert/light.h"
+
+  class InlinedSubsystem {
+   public:
+    void DoSomething() {
+      // GOOD: No problem; PW_ASSERT is fine to inline and place in a header.
+      PW_ASSERT(IsEnabled());
+    }
+    void DoSomethingElse() {
+      // BAD: Generally avoid using PW_DCHECK() or PW_CHECK in headers. If you
+      // want rich asserts or logs, move the function into the .cc file, and
+      // then use PW_CHECK there.
+      PW_DCHECK(IsEnabled());  // DON'T DO THIS
+    }
+  };
+
+Light API reference
+-------------------
+.. cpp:function:: PW_ASSERT(condition)
+
+  A header- and constexpr-safe version of ``PW_CHECK()``.
+
+  If the given condition is false, crash the system. Otherwise, do nothing.
+  The condition is guaranteed to be evaluated. This assert implementation is
+  guaranteed to be constexpr-safe.
+
+.. cpp:function:: PW_DASSERT(condition)
+
+  A header- and constexpr-safe version of ``PW_DCHECK()``.
+
+  Same as ``PW_ASSERT()``, except that if ``PW_ASSERT_ENABLE_DEBUG == 1``, the
+  assert is disabled and condition is not evaluated.
+
+.. attention::
+
+  Unlike the ``PW_CHECK_*()`` suite of macros, ``PW_ASSERT()`` and
+  ``PW_DASSERT()`` capture no rich information like line numbers, the file,
+  expression arguments, or the stringified expression. Use these macros **only
+  when absolutely necessary**--in headers, constexr contexts, or in rare cases
+  where the call site overhead of a full PW_CHECK must be avoided.
+
+  Use ``PW_CHECK_*()`` whenever possible.
+
+Light API backend
+-----------------
+The light API ultimately calls the C function ``pw_assert_HandleFailure()``,
+which must be provided by the assert backend.
+
+-----------
+Backend API
+-----------
 
 The backend controls what to do in the case of an assertion failure. In the
 most basic cases, the backend could display the assertion failure on something
@@ -465,6 +552,17 @@
     See :ref:`chapter-pw-assert-basic` for one way to combine these arguments
     into a meaningful error message.
 
+Additionally, the backend must provide a link-time function for the light
+assert handler. This does not need to appear in the backend header, but instead
+is in a ``.cc`` file.
+
+.. cpp:function:: pw_assert_HandleFailure()
+
+  Handle a low-level crash. This crash entry happens through
+  ``pw_assert/light.h``. In this crash handler, there is no access to line,
+  file, expression, or other rich assert information. Backends should do
+  something reasonable in this case; typically, capturing the stack is useful.
+
 --------------------------
 Frequently asked questions
 --------------------------
@@ -582,17 +680,16 @@
 ----------------
 Roadmap & Status
 ----------------
-
 The Pigweed assert subsystem consiststs of several modules that work in
-coordination. In particular, there is the facade (this module), then a number
-of backends to handle assert failures. In some cases, the backends will have
-backends (like log_tokenized). Not all of those modules are ready today. Below
-is a brief summary of what modules are ready now, and which need to be written
-or need more work:
+coordination. This module is the facade (API), then a number of backends are
+available to handle assert failures. Products can also define their own
+backends. In some cases, the backends will have backends (like
+``pw_log_tokenized``).
 
-Currently implemented modules
------------------------------
+Below is a brief summary of what modules are ready for use:
 
+Available assert backends
+-------------------------
 - ``pw_assert`` - **Stable** - The assert facade (this module). This module is
   stable, and in production use. The documentation is comprehensive and covers
   the functionality. There are (a) tests for the facade macro processing logic,
@@ -603,46 +700,20 @@
   arguments. Output is directed to ``pw_sys_io``. This module is a great
   ready-to-roll module when bringing up a system, but is likely not the best
   choice for production.
+- ``pw_assert_log`` - **Stable** - This assert backend redirects to logging,
+  but with a logging flag set that indicates an assert failure. This is our
+  advised approach to get **tokenized asserts**--by using tokenized logging,
+  then using the ``pw_assert_log`` backend.
 
-Not yet written modules
------------------------
-
-- ``pw_assert_tokenized`` - **Not started** - Just like there is a
-  ``pw_log_tokenized`` module that leverages the tokenizer to strip log string
-  literals from binaries, there will be an implementation that tokenizes the
-  assert contents (such as the captured source expression string). We may
-  combine ``pw_log_tokenized`` and ``pw_assert_tokenized`` into
-  ``pw_log_assert_tokenized`` since much of the details would be shared between
-  them.
-- ``pw_assert_log`` - **Not started** - This would use ``pw_log`` on assert
-  failure to communicate the failure information. Instead of being a separate
-  module, this might be an optional flag added to ``pw_assert_basic``.
+Note: If one desires a null assert module (where asserts are removed), use
+``pw_assert_log`` in combination with ``pw_log_null``. This will direct asserts
+to logs, then the logs are removed due to the null backend.
 
 Missing functionality
 ---------------------
-
 - **Stack traces** - Pigweed doesn't have a reliable stack walker, which makes
   displaying a stack trace on crash harder. We plan to add this eventually.
 - **Snapshot integration** - Pigweed doesn't yet have a rich system state
   capture system that can capture state like number of tasks, available memory,
   and so on. Snapshot facilities are the obvious ones to run inside an assert
   handler. It'll happen someday.
-- **Light asserts to break circular dependencies** - The Pigweed assert API is
-  flexible thanks to the facade structure; however, this can trigger circular
-  header dependencies due to facade directly including the backend. This can
-  happen when adding asserts to low-level functionality in headers, like for
-  example inside ``<span>``. Our polyfill span is used extensively in low level
-  components (like malloc and vector), including in some assert backends, which
-  leads to circular dependencies when span itself tries to assert.
-
-  There is no solution to this problem that doesn't require a compromise. The
-  easiest solution to enabling asserts everywhere is to shift to a C ABI with
-  link time dependency resolution; this breaks the header cycles. However, such
-  a structure would prevent custom compile-time filtering and tokenization.
-
-  Our current plan is to offer a simple C-function based assert API, with the
-  backend provided at link time rather than through header redirection. For
-  example, the API could be as simple as ``void pw_assert(bool condition)`` or
-  ``pw_assert_crash()`` if the condition check was done in a macro. This assert
-  API is intended for contexts where including the backend header would trigger
-  these problems, for use in common low level headers like ``<span>``.
diff --git a/pw_assert/light_test.cc b/pw_assert/light_test.cc
new file mode 100644
index 0000000..1710e4f
--- /dev/null
+++ b/pw_assert/light_test.cc
@@ -0,0 +1,53 @@
+// Copyright 2020 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 "pw_assert/light.h"
+
+#include "gtest/gtest.h"
+#include "pw_assert/assert.h"
+
+// PW_ASSERT() should always be enabled, and always evaluate the expression.
+TEST(Light, AssertTrue) {
+  int evaluated = 1;
+  PW_ASSERT(++evaluated);
+  EXPECT_EQ(evaluated, 2);
+}
+
+// PW_DASSERT() might be disabled sometimes.
+TEST(Light, DebugAssertTrue) {
+  int evaluated = 1;
+  PW_DASSERT(++evaluated);
+  if (PW_ASSERT_ENABLE_DEBUG == 1) {
+    EXPECT_EQ(evaluated, 2);
+  } else {
+    EXPECT_EQ(evaluated, 1);
+  }
+}
+
+// Unfortunately, we don't have the infrastructure to test failure handling
+// automatically, since the harness crashes in the process of running this
+// test. The unsatisfying alternative is to test the functionality manually,
+// then disable the test.
+
+TEST(Light, AssertFalse) {
+  if (0) {
+    PW_ASSERT(false);
+  }
+}
+
+TEST(Light, DebugAssertFalse) {
+  if (0) {
+    PW_DASSERT(false);
+  }
+}
diff --git a/pw_assert/public/pw_assert/internal/assert_impl.h b/pw_assert/public/pw_assert/internal/assert_impl.h
index 95862f0..345608d 100644
--- a/pw_assert/public/pw_assert/internal/assert_impl.h
+++ b/pw_assert/public/pw_assert/internal/assert_impl.h
@@ -19,19 +19,9 @@
 
 // Note: This file depends on the backend header already being included.
 
+#include "pw_assert/options.h"
 #include "pw_preprocessor/arguments.h"
 
-// Define PW_ASSERT_ENABLE_DCHECK, which controls whether DCHECKs are enabled.
-#if !defined(PW_ASSERT_ENABLE_DCHECK)
-#if defined(NDEBUG)
-// Release mode; remove all DCHECK*() asserts.
-#define PW_ASSERT_ENABLE_DCHECK 0
-#else
-// Debug mode; keep all DCHECK*() asserts.
-#define PW_ASSERT_ENABLE_DCHECK 1
-#endif  // defined (NDEBUG)
-#endif  // !defined(PW_ASSERT_ENABLE_DCHECK)
-
 // PW_CRASH - Crash the system, with a message.
 #define PW_CRASH PW_HANDLE_CRASH
 
@@ -44,8 +34,8 @@
     }                                                         \
   } while (0)
 
-#define PW_DCHECK(...)         \
-  if (PW_ASSERT_ENABLE_DCHECK) \
+#define PW_DCHECK(...)        \
+  if (PW_ASSERT_ENABLE_DEBUG) \
   PW_CHECK(__VA_ARGS__)
 
 // PW_D?CHECK_<type>_<comparison> macros - Binary comparison asserts.
@@ -66,12 +56,12 @@
 #define PW_CHECK_INT_NE(arga, argb, ...) _PW_CHECK_BINARY_CMP_IMPL(arga, !=, argb, int, "%d", __VA_ARGS__)
 
 // Debug checks for int: LE, LT, GE, GT, EQ.
-#define PW_DCHECK_INT_LE(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_INT_LE(__VA_ARGS__)
-#define PW_DCHECK_INT_LT(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_INT_LT(__VA_ARGS__)
-#define PW_DCHECK_INT_GE(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_INT_GE(__VA_ARGS__)
-#define PW_DCHECK_INT_GT(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_INT_GT(__VA_ARGS__)
-#define PW_DCHECK_INT_EQ(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_INT_EQ(__VA_ARGS__)
-#define PW_DCHECK_INT_NE(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_INT_NE(__VA_ARGS__)
+#define PW_DCHECK_INT_LE(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_INT_LE(__VA_ARGS__)
+#define PW_DCHECK_INT_LT(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_INT_LT(__VA_ARGS__)
+#define PW_DCHECK_INT_GE(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_INT_GE(__VA_ARGS__)
+#define PW_DCHECK_INT_GT(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_INT_GT(__VA_ARGS__)
+#define PW_DCHECK_INT_EQ(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_INT_EQ(__VA_ARGS__)
+#define PW_DCHECK_INT_NE(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_INT_NE(__VA_ARGS__)
 
 // Checks for unsigned int: LE, LT, GE, GT, EQ.
 #define PW_CHECK_UINT_LE(arga, argb, ...) _PW_CHECK_BINARY_CMP_IMPL(arga, <=, argb, unsigned int, "%u", __VA_ARGS__)
@@ -82,12 +72,12 @@
 #define PW_CHECK_UINT_NE(arga, argb, ...) _PW_CHECK_BINARY_CMP_IMPL(arga, !=, argb, unsigned int, "%u", __VA_ARGS__)
 
 // Debug checks for unsigned int: LE, LT, GE, GT, EQ.
-#define PW_DCHECK_UINT_LE(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_UINT_LE(__VA_ARGS__)
-#define PW_DCHECK_UINT_LT(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_UINT_LT(__VA_ARGS__)
-#define PW_DCHECK_UINT_GE(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_UINT_GE(__VA_ARGS__)
-#define PW_DCHECK_UINT_GT(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_UINT_GT(__VA_ARGS__)
-#define PW_DCHECK_UINT_EQ(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_UINT_EQ(__VA_ARGS__)
-#define PW_DCHECK_UINT_NE(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_UINT_NE(__VA_ARGS__)
+#define PW_DCHECK_UINT_LE(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_UINT_LE(__VA_ARGS__)
+#define PW_DCHECK_UINT_LT(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_UINT_LT(__VA_ARGS__)
+#define PW_DCHECK_UINT_GE(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_UINT_GE(__VA_ARGS__)
+#define PW_DCHECK_UINT_GT(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_UINT_GT(__VA_ARGS__)
+#define PW_DCHECK_UINT_EQ(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_UINT_EQ(__VA_ARGS__)
+#define PW_DCHECK_UINT_NE(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_UINT_NE(__VA_ARGS__)
 
 // Checks for pointer: LE, LT, GE, GT, EQ, NE.
 #define PW_CHECK_PTR_LE(arga, argb, ...) _PW_CHECK_BINARY_CMP_IMPL(arga, <=, argb, void*, "%p", __VA_ARGS__)
@@ -107,14 +97,14 @@
 #endif  // __cplusplus
 
 // Debug checks for pointer: LE, LT, GE, GT, EQ, NE, and NOTNULL.
-#define PW_DCHECK_PTR_LE(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_PTR_LE(__VA_ARGS__)
-#define PW_DCHECK_PTR_LT(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_PTR_LT(__VA_ARGS__)
-#define PW_DCHECK_PTR_GE(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_PTR_GE(__VA_ARGS__)
-#define PW_DCHECK_PTR_GT(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_PTR_GT(__VA_ARGS__)
-#define PW_DCHECK_PTR_EQ(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_PTR_EQ(__VA_ARGS__)
-#define PW_DCHECK_PTR_NE(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_PTR_NE(__VA_ARGS__)
+#define PW_DCHECK_PTR_LE(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_PTR_LE(__VA_ARGS__)
+#define PW_DCHECK_PTR_LT(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_PTR_LT(__VA_ARGS__)
+#define PW_DCHECK_PTR_GE(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_PTR_GE(__VA_ARGS__)
+#define PW_DCHECK_PTR_GT(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_PTR_GT(__VA_ARGS__)
+#define PW_DCHECK_PTR_EQ(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_PTR_EQ(__VA_ARGS__)
+#define PW_DCHECK_PTR_NE(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_PTR_NE(__VA_ARGS__)
 #define PW_DCHECK_NOTNULL(...) \
-  if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_NOTNULL(__VA_ARGS__)
+  if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_NOTNULL(__VA_ARGS__)
 
 // Checks for float: EXACT_LE, EXACT_LT, EXACT_GE, EXACT_GT, EXACT_EQ, EXACT_NE,
 // NEAR.
@@ -130,13 +120,13 @@
 // Debug checks for float: NEAR, EXACT_LE, EXACT_LT, EXACT_GE, EXACT_GT,
 // EXACT_EQ.
 #define PW_DCHECK_FLOAT_NEAR(...) \
-  if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_FLOAT_NEAR(__VA_ARGS__)
-#define PW_DCHECK_FLOAT_EXACT_LE(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_FLOAT_EXACT_LE(__VA_ARGS__)
-#define PW_DCHECK_FLOAT_EXACT_LT(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_FLOAT_EXACT_LT(__VA_ARGS__)
-#define PW_DCHECK_FLOAT_EXACT_GE(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_FLOAT_EXACT_GE(__VA_ARGS__)
-#define PW_DCHECK_FLOAT_EXACT_GT(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_FLOAT_EXACT_GT(__VA_ARGS__)
-#define PW_DCHECK_FLOAT_EXACT_EQ(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_FLOAT_EXACT_EQ(__VA_ARGS__)
-#define PW_DCHECK_FLOAT_EXACT_NE(...) if (PW_ASSERT_ENABLE_DCHECK) PW_CHECK_FLOAT_EXACT_NE(__VA_ARGS__)
+  if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_FLOAT_NEAR(__VA_ARGS__)
+#define PW_DCHECK_FLOAT_EXACT_LE(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_FLOAT_EXACT_LE(__VA_ARGS__)
+#define PW_DCHECK_FLOAT_EXACT_LT(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_FLOAT_EXACT_LT(__VA_ARGS__)
+#define PW_DCHECK_FLOAT_EXACT_GE(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_FLOAT_EXACT_GE(__VA_ARGS__)
+#define PW_DCHECK_FLOAT_EXACT_GT(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_FLOAT_EXACT_GT(__VA_ARGS__)
+#define PW_DCHECK_FLOAT_EXACT_EQ(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_FLOAT_EXACT_EQ(__VA_ARGS__)
+#define PW_DCHECK_FLOAT_EXACT_NE(...) if (PW_ASSERT_ENABLE_DEBUG) PW_CHECK_FLOAT_EXACT_NE(__VA_ARGS__)
 
 // clang-format on
 
@@ -151,8 +141,8 @@
     }                                                     \
   } while (0)
 
-#define PW_DCHECK_OK(...)      \
-  if (PW_ASSERT_ENABLE_DCHECK) \
+#define PW_DCHECK_OK(...)     \
+  if (PW_ASSERT_ENABLE_DEBUG) \
   PW_CHECK_OK(__VA_ARGS__)
 
 // =========================================================================
diff --git a/pw_assert/public/pw_assert/light.h b/pw_assert/public/pw_assert/light.h
new file mode 100644
index 0000000..f653338
--- /dev/null
+++ b/pw_assert/public/pw_assert/light.h
@@ -0,0 +1,53 @@
+// Copyright 2020 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/options.h"  // For PW_ASSERT_ENABLE_DEBUG
+
+extern "C" void pw_assert_HandleFailure();
+
+// A header- and constexpr-safe version of PW_CHECK().
+//
+// If the given condition is false, crash the system. Otherwise, do nothing.
+// The condition is guaranteed to be evaluated. This assert implementation is
+// guaranteed to be constexpr-safe.
+//
+// IMPORTANT: Unlike the PW_CHECK_*() suite of macros, this API captures no
+// rich information like line numbers, the file, expression arguments, or the
+// stringified expression. Use these macros only when absolutely necessary --
+// in headers, constexr contexts, or in rare cases where the call site overhead
+// of a full PW_CHECK must be avoided. Use PW_CHECK_*() whenever possible.
+#define PW_ASSERT(condition)     \
+  do {                           \
+    if (!(condition)) {          \
+      pw_assert_HandleFailure(); \
+    }                            \
+  } while (0)
+
+// A header- and constexpr-safe version of PW_DCHECK().
+//
+// Same as PW_ASSERT(), except that if PW_ASSERT_ENABLE_DEBUG == 1, the assert
+// is disabled and condition is not evaluated.
+//
+// IMPORTANT: Unlike the PW_CHECK_*() suite of macros, this API captures no
+// rich information like line numbers, the file, expression arguments, or the
+// stringified expression. Use these macros only when absolutely necessary --
+// in headers, constexr contexts, or in rare cases where the call site overhead
+// of a full PW_CHECK must be avoided. Use PW_DCHECK_*() whenever possible.
+#define PW_DASSERT(condition)                            \
+  do {                                                   \
+    if ((PW_ASSERT_ENABLE_DEBUG == 1) && !(condition)) { \
+      pw_assert_HandleFailure();                         \
+    }                                                    \
+  } while (0)
diff --git a/pw_assert/public/pw_assert/options.h b/pw_assert/public/pw_assert/options.h
new file mode 100644
index 0000000..e7ea02f
--- /dev/null
+++ b/pw_assert/public/pw_assert/options.h
@@ -0,0 +1,28 @@
+// Copyright 2020 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
+
+// PW_ASSERT_ENABLE_DEBUG controls whether DCHECKs and DASSERTs are enabled.
+//
+// This block defines PW_ASSERT_ENABLE_DEBUG if it is not already, taking into
+// account traditional NDEBUG macro.
+#if !defined(PW_ASSERT_ENABLE_DEBUG)
+#if defined(NDEBUG)
+// Release mode; remove all DCHECK*() and DASSERT() asserts.
+#define PW_ASSERT_ENABLE_DEBUG 0
+#else
+// Debug mode; keep all DCHECK*() and DASSERT() asserts.
+#define PW_ASSERT_ENABLE_DEBUG 1
+#endif  // defined (NDEBUG)
+#endif  // !defined(PW_ASSERT_ENABLE_DEBUG)
diff --git a/pw_assert_basic/assert_basic.cc b/pw_assert_basic/assert_basic.cc
index 837ce52..6db08ac 100644
--- a/pw_assert_basic/assert_basic.cc
+++ b/pw_assert_basic/assert_basic.cc
@@ -155,3 +155,11 @@
     WriteLine("");
   }
 }
+
+extern "C" void pw_assert_HandleFailure() {
+#if PW_ASSERT_DEBUG_ENABLED
+  pw_Crash("", 0, "", "Crash: PW_ASSERT() or PW_DASSERT() failure");
+#else
+  pw_Crash("", 0, "", "Crash: PW_ASSERT() failure. Note: PW_DASSERT disabled");
+#endif  // PW_ASSERT_DEBUG_ENABLED
+}
diff --git a/pw_assert_log/BUILD b/pw_assert_log/BUILD
index 4f9c95a..25768d1 100644
--- a/pw_assert_log/BUILD
+++ b/pw_assert_log/BUILD
@@ -31,6 +31,9 @@
         "public",
         "public_overrides",
     ],
+    srcs = [
+        "assert_log.cc",
+    ],
     deps = [
         "//pw_preprocessor",
     ],
diff --git a/pw_assert_log/BUILD.gn b/pw_assert_log/BUILD.gn
index 5c4139c..41a207c 100644
--- a/pw_assert_log/BUILD.gn
+++ b/pw_assert_log/BUILD.gn
@@ -40,6 +40,7 @@
   public_deps = [ "$dir_pw_log" ]
   deps = [ "$dir_pw_preprocessor" ]
   public = [ "public/pw_assert_log/assert_log.h" ]
+  sources = [ "assert_log.cc" ]
 }
 
 pw_doc_group("docs") {
diff --git a/pw_assert_log/assert_log.cc b/pw_assert_log/assert_log.cc
new file mode 100644
index 0000000..779af1b
--- /dev/null
+++ b/pw_assert_log/assert_log.cc
@@ -0,0 +1,28 @@
+// Copyright 2020 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 "pw_assert_log/assert_log.h"
+
+extern "C" void pw_assert_HandleFailure() {
+#if PW_ASSERT_DEBUG_ENABLED
+  PW_LOG(PW_LOG_LEVEL_CRITICAL,
+         PW_LOG_ASSERT_FAILED_FLAG,
+         "Crash: PW_ASSERT() or PW_DASSERT() failure");
+#else
+  PW_LOG(PW_LOG_LEVEL_CRITICAL,
+         PW_LOG_ASSERT_FAILED_FLAG,
+         "Crash: PW_ASSERT() failure. Note: PW_DASSERT disabled");
+#endif  // PW_ASSERT_DEBUG_ENABLED
+  PW_UNREACHABLE;
+}
diff --git a/pw_build/facade.gni b/pw_build/facade.gni
index e9604fc..a5a5159 100644
--- a/pw_build/facade.gni
+++ b/pw_build/facade.gni
@@ -18,9 +18,10 @@
 import("$dir_pw_build/target_types.gni")
 
 # Declare a facade.
-# A Pigweed facade is an API layer that has a single implementation it must link
-# against. Typically this will be done by pointing `dir_pw_[module]_backend` at
-# a backend implementation for that module.
+#
+# A Pigweed facade is an API layer that has a single implementation it must
+# link against. Typically this will be done by pointing a build arg like
+# `pw_[module]_BACKEND` at a backend implementation for that module.
 #
 # Example facade:
 #