Skip to content

Commit 24ca0fb

Browse files
Yuval Peressfabiobaltieri
authored andcommitted
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 <[email protected]>
1 parent db576ba commit 24ca0fb

File tree

6 files changed

+188
-0
lines changed

6 files changed

+188
-0
lines changed

lib/cpp/abi/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
zephyr_sources_ifdef(CONFIG_STATIC_INIT_GNU
44
cpp_dtors.c
55
)
6+
zephyr_sources_ifdef(CONFIG_ATOMIC_OPERATIONS_C
7+
cpp_atomics.c
8+
)

lib/cpp/abi/cpp_atomics.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2025 Google LLC
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/**
8+
* @file
9+
* @brief C-based implementation of GCC __atomic built-ins.
10+
*
11+
* This file provides a fallback implementation for the C++ atomic functions
12+
* required by the compiler on architectures that do not have native atomic
13+
* instructions and are using the generic C implementation of atomics.
14+
* All operations are made atomic by using a global interrupt lock.
15+
*/
16+
17+
#include <zephyr/kernel.h>
18+
#include <stdint.h>
19+
20+
/*
21+
* Note on memory ordering:
22+
* The `memorder` parameters are ignored because the irq_lock() provides
23+
* a full memory barrier, which is equivalent to the strongest memory order,
24+
* __ATOMIC_SEQ_CST. This is always safe.
25+
*/
26+
27+
/* === compare_exchange ======================================================= */
28+
29+
#define DEFINE_ATOMIC_COMPARE_EXCHANGE(n, type) \
30+
bool __atomic_compare_exchange_##n(volatile void *ptr, void *expected, type desired, \
31+
bool weak, int success, int failure) \
32+
{ \
33+
bool ret = false; \
34+
unsigned int key = irq_lock(); \
35+
volatile type *p = ptr; \
36+
type *e = expected; \
37+
\
38+
if (*p == *e) { \
39+
*p = desired; \
40+
ret = true; \
41+
} else { \
42+
*e = *p; \
43+
ret = false; \
44+
} \
45+
irq_unlock(key); \
46+
return ret; \
47+
}
48+
49+
DEFINE_ATOMIC_COMPARE_EXCHANGE(1, uint8_t)
50+
DEFINE_ATOMIC_COMPARE_EXCHANGE(2, uint16_t)
51+
DEFINE_ATOMIC_COMPARE_EXCHANGE(4, uint32_t)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright (c) 2025 Google LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
cmake_minimum_required(VERSION 3.20.0)
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(atomics_c)
7+
8+
target_sources(app PRIVATE main.cpp)

tests/lib/cpp/atomics_c/main.cpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright (c) 2025 Google LLC
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <atomic>
8+
#include <cstdint>
9+
#include <zephyr/ztest.h>
10+
11+
namespace
12+
{
13+
std::atomic<uint8_t> atomic_u8;
14+
std::atomic<uint16_t> atomic_u16;
15+
std::atomic<uint32_t> atomic_u32;
16+
} // namespace
17+
18+
/**
19+
* @brief This `before` function is run before each test in the suite.
20+
*
21+
* It ensures that all atomic variables are reset to a known state (0)
22+
* so that the tests are independent and repeatable.
23+
*/
24+
static void cxx_atomic_before(void *fixture)
25+
{
26+
atomic_u8.store(0);
27+
atomic_u16.store(0);
28+
atomic_u32.store(0);
29+
}
30+
31+
/**
32+
* @brief Tests the 1-byte (uint8_t) atomic implementation.
33+
*/
34+
ZTEST(cxx_atomic, test_u8_compare_exchange_weak)
35+
{
36+
/* === Test 1: Successful exchange === */
37+
/* We expect the value to be 0, so this exchange should succeed. */
38+
uint8_t expected = 0;
39+
const uint8_t desired = 42;
40+
41+
/* Loop until the weak exchange succeeds. */
42+
while (!atomic_u8.compare_exchange_weak(expected, desired)) {
43+
}
44+
zassert_equal(atomic_u8.load(), desired, "Value should have been updated to 42");
45+
46+
/* === Test 2: Failed exchange === */
47+
/* Now, the atomic value is 42. We will set `expected` to 0, so the exchange must fail. */
48+
expected = 0;
49+
const uint8_t new_desired = 99;
50+
bool success = atomic_u8.compare_exchange_weak(expected, new_desired);
51+
52+
zassert_false(success, "Exchange should have failed");
53+
zassert_equal(atomic_u8.load(), desired, "Value should remain 42");
54+
/* Crucially, `expected` should be updated with the value that caused the failure. */
55+
zassert_equal(expected, desired, "Expected should be updated to 42");
56+
}
57+
58+
/**
59+
* @brief Tests the 2-byte (uint16_t) atomic implementation.
60+
*/
61+
ZTEST(cxx_atomic, test_u16_compare_exchange_weak)
62+
{
63+
/* === Test 1: Successful exchange === */
64+
uint16_t expected = 0;
65+
const uint16_t desired = 1337;
66+
while (!atomic_u16.compare_exchange_weak(expected, desired)) {
67+
}
68+
zassert_equal(atomic_u16.load(), desired, "Value should have been updated to 1337");
69+
70+
/* === Test 2: Failed exchange === */
71+
expected = 0;
72+
const uint16_t new_desired = 9999;
73+
bool success = atomic_u16.compare_exchange_weak(expected, new_desired);
74+
75+
zassert_false(success, "Exchange should have failed");
76+
zassert_equal(atomic_u16.load(), desired, "Value should remain 1337");
77+
zassert_equal(expected, desired, "Expected should be updated to 1337");
78+
}
79+
80+
/**
81+
* @brief Tests the 4-byte (uint32_t) atomic implementation.
82+
*/
83+
ZTEST(cxx_atomic, test_u32_compare_exchange_weak)
84+
{
85+
/* === Test 1: Successful exchange === */
86+
uint32_t expected = 0;
87+
const uint32_t desired = 0xDEADBEEF;
88+
while (!atomic_u32.compare_exchange_weak(expected, desired)) {
89+
}
90+
zassert_equal(atomic_u32.load(), desired, "Value should have been updated to 0xDEADBEEF");
91+
92+
/* === Test 2: Failed exchange === */
93+
expected = 0;
94+
const uint32_t new_desired = 0x12345678;
95+
bool success = atomic_u32.compare_exchange_weak(expected, new_desired);
96+
97+
zassert_false(success, "Exchange should have failed");
98+
zassert_equal(atomic_u32.load(), desired, "Value should remain 0xDEADBEEF");
99+
zassert_equal(expected, desired, "Expected should be updated to 0xDEADBEEF");
100+
}
101+
102+
ZTEST_SUITE(cxx_atomic, nullptr, nullptr, cxx_atomic_before, nullptr, nullptr);

tests/lib/cpp/atomics_c/prj.conf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright (c) 2025 Google LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# C++ support
5+
# ===========
6+
CONFIG_CPP=y
7+
CONFIG_STD_CPP20=y
8+
CONFIG_REQUIRES_FULL_LIBCPP=y
9+
10+
# Test requirements
11+
# =================
12+
CONFIG_ZTEST=y
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright (c) 2025 Google LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
tests:
5+
cpp.atomics_c:
6+
tags: cpp kernel
7+
arch_exclude: posix
8+
build_only: true
9+
filter: CONFIG_ATOMIC_OPERATIONS_C
10+
integration_platforms:
11+
- rpi_pico
12+
- robokit1

0 commit comments

Comments
 (0)