Replace the offsetof cache-line check with a runtime check (#1877) (#2264)
State is not a standard-layout type, so offsetof(State, skipped_) is only
conditionally-supported. Compilers diagnose it, and the static_assert was
kept quiet by a stack of vendor pragmas for GCC, clang, ICC, NVCC and NVHPC.
That stack does not cover every compiler: #1877 is clang-18/19 on Windows
reporting it anyway.
Measure the offset on the object being constructed instead. That is well
defined, needs no pragmas, and expresses the same invariant.
The check uses a new BM_CHECK_ALWAYS, which is BM_CHECK without the NDEBUG
opt-out, so it still holds in a release build. A State is constructed once
per benchmark instance and thread, never inside the iteration loop.
Verified: with the bound lowered to 8 bytes, a release build fails at run
time with
src/benchmark.cc:231: State: Check `... <= 8 - ...' failed.
the commonly accessed members of State must fit in the first cache line
and src/benchmark.cc compiles clean under -Winvalid-offsetof -Wall -Wextra
-Werror with both gcc 15 and clang 20.
Co-authored-by: dominic <510002+dmah42@users.noreply.github.com>
diff --git a/src/benchmark.cc b/src/benchmark.cc
index 22a395d..9388e34 100644
--- a/src/benchmark.cc
+++ b/src/benchmark.cc
@@ -42,6 +42,7 @@
#include <algorithm>
#include <atomic>
#include <condition_variable>
+#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
@@ -222,42 +223,17 @@
}
}
- // Note: The use of offsetof below is technically undefined until C++17
- // because State is not a standard layout type. However, all compilers
- // currently provide well-defined behavior as an extension (which is
- // demonstrated since constexpr evaluation must diagnose all undefined
- // behavior). However, GCC and Clang also warn about this use of offsetof,
- // which must be suppressed.
-#if defined(__INTEL_COMPILER)
-#pragma warning push
-#pragma warning(disable : 1875)
-#elif defined(__GNUC__) || defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Winvalid-offsetof"
-#endif
-#if defined(__NVCC__)
-#pragma nv_diagnostic push
-#pragma nv_diag_suppress 1427
-#endif
-#if defined(__NVCOMPILER)
-#pragma diagnostic push
-#pragma diag_suppress offset_in_non_POD_nonstandard
-#endif
- // Offset tests to ensure commonly accessed data is on the first cache line.
- const int cache_line_size = 64;
- static_assert(
- offsetof(State, skipped_) <= (cache_line_size - sizeof(skipped_)), "");
-#if defined(__INTEL_COMPILER)
-#pragma warning pop
-#elif defined(__GNUC__) || defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
-#if defined(__NVCC__)
-#pragma nv_diagnostic pop
-#endif
-#if defined(__NVCOMPILER)
-#pragma diagnostic pop
-#endif
+ // Ensure commonly accessed data is on the first cache line. State is not a
+ // standard-layout type, so the offset is measured on the live object.
+ //
+ // The check is enabled under NDEBUG as well: a State is constructed once per
+ // benchmark instance and thread, never inside the iteration loop.
+ BM_CHECK_ALWAYS(reinterpret_cast<const char*>(&skipped_) -
+ reinterpret_cast<const char*>(this) <=
+ /*cache_line_size=*/64 -
+ static_cast<std::ptrdiff_t>(sizeof(skipped_)))
+ << "the commonly accessed members of State must fit in the first cache "
+ "line";
}
void State::PauseTiming() {
diff --git a/src/check.h b/src/check.h
index aa8c78c..b6fc22f 100644
--- a/src/check.h
+++ b/src/check.h
@@ -79,15 +79,20 @@
} // end namespace internal
} // end namespace benchmark
-// The BM_CHECK macro returns a std::ostream object that can have extra
-// information written to it.
-#ifndef NDEBUG
-#define BM_CHECK(b) \
+// Like BM_CHECK, but enabled in every build configuration. Reserved for
+// invariants that are cheap enough to verify unconditionally and that must not
+// be violated silently in a release build.
+#define BM_CHECK_ALWAYS(b) \
(b ? ::benchmark::internal::GetNullLogInstance() \
: ::benchmark::internal::CheckHandler( \
std::string_view(#b), std::string_view(__FILE__), \
std::string_view(__func__), __LINE__) \
.GetLog())
+
+// The BM_CHECK macro returns a std::ostream object that can have extra
+// information written to it.
+#ifndef NDEBUG
+#define BM_CHECK(b) BM_CHECK_ALWAYS(b)
#else
#define BM_CHECK(b) ::benchmark::internal::GetNullLogInstance()
#endif