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: Yuval Peress <peress@google.com>
This commit is contained in:
committed by
Fabio Baltieri
parent
db576bad6c
commit
24ca0fbb76
@@ -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
lib/cpp/abi/cpp_atomics.c
Normal file
51
lib/cpp/abi/cpp_atomics.c
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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
tests/lib/cpp/atomics_c/CMakeLists.txt
Normal file
8
tests/lib/cpp/atomics_c/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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
tests/lib/cpp/atomics_c/main.cpp
Normal file
102
tests/lib/cpp/atomics_c/main.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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
tests/lib/cpp/atomics_c/prj.conf
Normal file
12
tests/lib/cpp/atomics_c/prj.conf
Normal file
@@ -0,0 +1,12 @@
|
||||
# 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
|
||||
12
tests/lib/cpp/atomics_c/testcase.yaml
Normal file
12
tests/lib/cpp/atomics_c/testcase.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2025 Google LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
tests:
|
||||
cpp.atomics_c:
|
||||
tags: cpp kernel
|
||||
arch_exclude: posix
|
||||
build_only: true
|
||||
filter: CONFIG_ATOMIC_OPERATIONS_C
|
||||
integration_platforms:
|
||||
- rpi_pico
|
||||
- robokit1
|
||||
Reference in New Issue
Block a user