Commit 24ca0fbb authored by Yuval Peress's avatar Yuval Peress Committed by Fabio Baltieri
Browse files

cpp: Support C mocked __atomic_compare_exchange_*



The builtin functions __atomic_compare_exchange_* are missing when
CONFIG_ATOMIC_OPERATIONS_C is set. Add them and verify they build/work
as expected.

Signed-off-by: default avatarYuval Peress <peress@google.com>
parent db576bad
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -3,3 +3,6 @@
zephyr_sources_ifdef(CONFIG_STATIC_INIT_GNU
  cpp_dtors.c
)
zephyr_sources_ifdef(CONFIG_ATOMIC_OPERATIONS_C
  cpp_atomics.c
)
+51 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2025 Google LLC
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief C-based implementation of GCC __atomic built-ins.
 *
 * This file provides a fallback implementation for the C++ atomic functions
 * required by the compiler on architectures that do not have native atomic
 * instructions and are using the generic C implementation of atomics.
 * All operations are made atomic by using a global interrupt lock.
 */

#include <zephyr/kernel.h>
#include <stdint.h>

/*
 * Note on memory ordering:
 * The `memorder` parameters are ignored because the irq_lock() provides
 * a full memory barrier, which is equivalent to the strongest memory order,
 * __ATOMIC_SEQ_CST. This is always safe.
 */

/* === compare_exchange ======================================================= */

#define DEFINE_ATOMIC_COMPARE_EXCHANGE(n, type)                                                    \
	bool __atomic_compare_exchange_##n(volatile void *ptr, void *expected, type desired,       \
					   bool weak, int success, int failure)                    \
	{                                                                                          \
		bool ret = false;                                                                  \
		unsigned int key = irq_lock();                                                     \
		volatile type *p = ptr;                                                            \
		type *e = expected;                                                                \
                                                                                                   \
		if (*p == *e) {                                                                    \
			*p = desired;                                                              \
			ret = true;                                                                \
		} else {                                                                           \
			*e = *p;                                                                   \
			ret = false;                                                               \
		}                                                                                  \
		irq_unlock(key);                                                                   \
		return ret;                                                                        \
	}

DEFINE_ATOMIC_COMPARE_EXCHANGE(1, uint8_t)
DEFINE_ATOMIC_COMPARE_EXCHANGE(2, uint16_t)
DEFINE_ATOMIC_COMPARE_EXCHANGE(4, uint32_t)
+8 −0
Original line number Diff line number Diff line
# Copyright (c) 2025 Google LLC
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(atomics_c)

target_sources(app PRIVATE main.cpp)
+102 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2025 Google LLC
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <atomic>
#include <cstdint>
#include <zephyr/ztest.h>

namespace
{
std::atomic<uint8_t> atomic_u8;
std::atomic<uint16_t> atomic_u16;
std::atomic<uint32_t> atomic_u32;
} // namespace

/**
 * @brief This `before` function is run before each test in the suite.
 *
 * It ensures that all atomic variables are reset to a known state (0)
 * so that the tests are independent and repeatable.
 */
static void cxx_atomic_before(void *fixture)
{
	atomic_u8.store(0);
	atomic_u16.store(0);
	atomic_u32.store(0);
}

/**
 * @brief Tests the 1-byte (uint8_t) atomic implementation.
 */
ZTEST(cxx_atomic, test_u8_compare_exchange_weak)
{
	/* === Test 1: Successful exchange === */
	/* We expect the value to be 0, so this exchange should succeed. */
	uint8_t expected = 0;
	const uint8_t desired = 42;

	/* Loop until the weak exchange succeeds. */
	while (!atomic_u8.compare_exchange_weak(expected, desired)) {
	}
	zassert_equal(atomic_u8.load(), desired, "Value should have been updated to 42");

	/* === Test 2: Failed exchange === */
	/* Now, the atomic value is 42. We will set `expected` to 0, so the exchange must fail. */
	expected = 0;
	const uint8_t new_desired = 99;
	bool success = atomic_u8.compare_exchange_weak(expected, new_desired);

	zassert_false(success, "Exchange should have failed");
	zassert_equal(atomic_u8.load(), desired, "Value should remain 42");
	/* Crucially, `expected` should be updated with the value that caused the failure. */
	zassert_equal(expected, desired, "Expected should be updated to 42");
}

/**
 * @brief Tests the 2-byte (uint16_t) atomic implementation.
 */
ZTEST(cxx_atomic, test_u16_compare_exchange_weak)
{
	/* === Test 1: Successful exchange === */
	uint16_t expected = 0;
	const uint16_t desired = 1337;
	while (!atomic_u16.compare_exchange_weak(expected, desired)) {
	}
	zassert_equal(atomic_u16.load(), desired, "Value should have been updated to 1337");

	/* === Test 2: Failed exchange === */
	expected = 0;
	const uint16_t new_desired = 9999;
	bool success = atomic_u16.compare_exchange_weak(expected, new_desired);

	zassert_false(success, "Exchange should have failed");
	zassert_equal(atomic_u16.load(), desired, "Value should remain 1337");
	zassert_equal(expected, desired, "Expected should be updated to 1337");
}

/**
 * @brief Tests the 4-byte (uint32_t) atomic implementation.
 */
ZTEST(cxx_atomic, test_u32_compare_exchange_weak)
{
	/* === Test 1: Successful exchange === */
	uint32_t expected = 0;
	const uint32_t desired = 0xDEADBEEF;
	while (!atomic_u32.compare_exchange_weak(expected, desired)) {
	}
	zassert_equal(atomic_u32.load(), desired, "Value should have been updated to 0xDEADBEEF");

	/* === Test 2: Failed exchange === */
	expected = 0;
	const uint32_t new_desired = 0x12345678;
	bool success = atomic_u32.compare_exchange_weak(expected, new_desired);

	zassert_false(success, "Exchange should have failed");
	zassert_equal(atomic_u32.load(), desired, "Value should remain 0xDEADBEEF");
	zassert_equal(expected, desired, "Expected should be updated to 0xDEADBEEF");
}

ZTEST_SUITE(cxx_atomic, nullptr, nullptr, cxx_atomic_before, nullptr, nullptr);
+12 −0
Original line number Diff line number Diff line
# Copyright (c) 2025 Google LLC
# SPDX-License-Identifier: Apache-2.0

# C++ support
# ===========
CONFIG_CPP=y
CONFIG_STD_CPP20=y
CONFIG_REQUIRES_FULL_LIBCPP=y

# Test requirements
# =================
CONFIG_ZTEST=y
Loading