[libcxx-commits] [libcxx] [libc++] Implement P0528R3 `std::atomic` CAS for types with paddings (PR #76180)

via libcxx-commits libcxx-commits at lists.llvm.org
Sun Jun 21 06:23:34 PDT 2026


================
@@ -0,0 +1,243 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// atomic_init is deprecated
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_DISABLE_DEPRECATION_WARNINGS
+
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <cstring>
+#include <type_traits>
+
+struct Foo {
+  int i;
+  char c;
+};
+
+static_assert(!std::has_unique_object_representations_v<Foo>);
+static_assert(sizeof(Foo) > sizeof(int) + sizeof(char));
+
+Foo make_foo(int i, char c, unsigned char pad_byte) {
+  Foo f;
+  std::memset(&f, pad_byte, sizeof(Foo));
+  f.i = i;
+  f.c = c;
+  return f;
+}
+
+void assert_foo_padding(const Foo& f, unsigned char pad_byte) {
+  alignas(Foo) unsigned char buf[sizeof(Foo)];
+  std::memset(buf, pad_byte, sizeof(Foo));
+  Foo& reference = *reinterpret_cast<Foo*>(buf);
+  reference.i    = f.i;
+  reference.c    = f.c;
+  assert(std::memcmp(&f, &reference, sizeof(Foo)) == 0);
+}
+
+#if __has_builtin(__builtin_clear_padding)
+
+void test_default_constructor() {
+  std::atomic<Foo> a;
+  Foo loaded = a.load();
+  assert(loaded.i == 0);
+  assert(loaded.c == '\0');
+  assert_foo_padding(loaded, 0);
+}
+
+void test_value_constructor() {
+  Foo init = make_foo(10, 'a', 0xBB);
+  assert_foo_padding(init, 0xBB);
+  std::atomic<Foo> a(init);
+  Foo loaded = a.load();
+  assert(loaded.i == 10);
+  assert(loaded.c == 'a');
+  assert_foo_padding(loaded, 0);
+}
+
+void test_store() {
+  std::atomic<Foo> a;
+  Foo value = make_foo(5, 'x', 0xAB);
+  assert_foo_padding(value, 0xAB);
+  a.store(value);
+  Foo loaded = a.load();
+  assert(loaded.i == 5);
+  assert(loaded.c == 'x');
+  assert_foo_padding(loaded, 0);
+}
+
+void test_exchange() {
+  Foo initial = make_foo(1, 'a', 0x00);
+  assert_foo_padding(initial, 0x00);
+  std::atomic<Foo> a(initial);
+  Foo new_val = make_foo(2, 'b', 0xCD);
+  assert_foo_padding(new_val, 0xCD);
+  Foo old = a.exchange(new_val);
+  assert(old.i == 1);
+  assert(old.c == 'a');
+  assert_foo_padding(old, 0);
+  Foo loaded = a.load();
+  assert(loaded.i == 2);
+  assert(loaded.c == 'b');
+  assert_foo_padding(loaded, 0);
+}
+
+void test_atomic_init() {
+  std::atomic<Foo> a;
+  Foo init = make_foo(7, 'z', 0xEF);
+  assert_foo_padding(init, 0xEF);
+  std::atomic_init(&a, init);
+  Foo loaded = a.load();
+  assert(loaded.i == 7);
+  assert(loaded.c == 'z');
+  assert_foo_padding(loaded, 0);
+}
+
+void test_compare_exchange_strong_success_padding_only() {
+  // CAS should succeed when only padding differs in expected; expected is unchanged.
+  std::atomic<Foo> a;
+
+  Foo init = make_foo(10, 'a', 0xBB);
+  assert_foo_padding(init, 0xBB);
+  a.store(init);
+
+  Foo expected = make_foo(10, 'a', 0xAA);
+  assert_foo_padding(expected, 0xAA);
+
+  alignas(Foo) char original_expected[sizeof(Foo)];
+  std::memcpy(original_expected, &expected, sizeof(Foo));
+
+  Foo new_value = make_foo(42, 'b', 0xCC);
+  assert_foo_padding(new_value, 0xCC);
+
+  bool r = a.compare_exchange_strong(expected, new_value);
+
+  assert(r);
+  assert(std::memcmp(&expected, original_expected, sizeof(Foo)) == 0);
+  Foo loaded = a.load();
+  assert(loaded.i == 42);
+  assert(loaded.c == 'b');
+  assert_foo_padding(loaded, 0);
+}
+
+void test_compare_exchange_strong_failure() {
+  std::atomic<Foo> a;
+  Foo stored = make_foo(10, 'a', 0xBB);
+  assert_foo_padding(stored, 0xBB);
+  a.store(stored);
+
+  Foo expected = make_foo(99, 'a', 0xAA);
+  assert_foo_padding(expected, 0xAA);
+  Foo new_value = make_foo(42, 'b', 0xCC);
+  assert_foo_padding(new_value, 0xCC);
+
+  bool r = a.compare_exchange_strong(expected, new_value);
+
+  assert(!r);
+  assert(expected.i == 10);
+  assert(expected.c == 'a');
+  assert_foo_padding(expected, 0);
+  Foo loaded = a.load();
+  assert(loaded.i == 10);
+  assert(loaded.c == 'a');
+  assert_foo_padding(loaded, 0);
+}
+
+void test_compare_exchange_weak_success_padding_only() {
+  std::atomic<Foo> a;
+  Foo stored = make_foo(10, 'a', 0xBB);
+  assert_foo_padding(stored, 0xBB);
+  a.store(stored);
+
+  Foo new_value = make_foo(42, 'b', 0xCC);
+  assert_foo_padding(new_value, 0xCC);
+
+  Foo original_expected = make_foo(10, 'a', 0xAA);
+  assert_foo_padding(original_expected, 0xAA);
+
+  bool r              = false;
+  const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
+  while (!r) {
+    assert(std::chrono::steady_clock::now() < deadline && "compare_exchange_weak did not succeed within 3 seconds");
+    Foo expected = make_foo(10, 'a', 0xAA);
+    assert_foo_padding(expected, 0xAA);
+    r = a.compare_exchange_weak(expected, new_value);
+    if (r) {
+      assert(std::memcmp(&expected, &original_expected, sizeof(Foo)) == 0);
+    } else {
+      // Spurious failure: expected is updated to the current atomic value.
+      assert(expected.i == 10);
+      assert(expected.c == 'a');
+      assert_foo_padding(expected, 0);
+    }
+  }
+
+  Foo loaded = a.load();
+  assert(loaded.i == 42);
+  assert(loaded.c == 'b');
+  assert_foo_padding(loaded, 0);
+}
+
+void test_compare_exchange_weak_failure() {
+  std::atomic<Foo> a;
+  Foo stored = make_foo(10, 'a', 0xBB);
+  assert_foo_padding(stored, 0xBB);
+  a.store(stored);
+
+  Foo expected = make_foo(99, 'a', 0xAA);
+  assert_foo_padding(expected, 0xAA);
+  Foo new_value = make_foo(42, 'b', 0xCC);
+  assert_foo_padding(new_value, 0xCC);
+
+  bool r = a.compare_exchange_weak(expected, new_value);
+
+  assert(!r);
+  assert(expected.i == 10);
+  assert(expected.c == 'a');
+  assert_foo_padding(expected, 0);
+  Foo loaded = a.load();
+  assert(loaded.i == 10);
+  assert(loaded.c == 'a');
+  assert_foo_padding(loaded, 0);
+}
+
+void test_no_padding_type() {
+  // Types with unique object representations skip the padding-clearing path.
+  std::atomic<int> a(1);
+  int expected = 1;
+  assert(a.compare_exchange_strong(expected, 2));
+  assert(expected == 1);
+  assert(a.load() == 2);
+
+  expected = 3;
+  assert(!a.compare_exchange_strong(expected, 4));
+  assert(expected == 2);
+  assert(a.load() == 2);
+}
+
+int main(int, char**) {
+  test_default_constructor();
+  test_value_constructor();
+  test_store();
+  test_exchange();
+  test_atomic_init();
+  test_compare_exchange_strong_success_padding_only();
+  test_compare_exchange_strong_failure();
+  test_compare_exchange_weak_success_padding_only();
+  test_compare_exchange_weak_failure();
+  test_no_padding_type();
+
+  return 0;
+}
+
+#else // !__has_builtin(__builtin_clear_padding)
----------------
huixie90 wrote:

hmm since the builtin was just merged in clang, I don't find a reliable way to XFAIL. if we XFAIL clang-23, the in-tree clang will not fail and fail the test. any suggestions?

https://github.com/llvm/llvm-project/pull/76180


More information about the libcxx-commits mailing list