From d0558aecfb7dda4c013166105eabbe6e041b2764 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Mon, 20 May 2024 23:00:43 +0200 Subject: [PATCH 1/2] Add atomic builtins implementation --- include/atomic | 4 + include/bits/atomic_builtins.h | 333 +++++++++++++++++++++++++++++++++ src/atomic_builtins.cc | 149 +++++++++++++++ 3 files changed, 486 insertions(+) create mode 100644 include/bits/atomic_builtins.h create mode 100644 src/atomic_builtins.cc diff --git a/include/atomic b/include/atomic index a455286..da81b0a 100644 --- a/include/atomic +++ b/include/atomic @@ -34,6 +34,10 @@ #pragma GCC system_header +#if __has_include() +# include +#endif + #if __cplusplus < 201103L # include #else diff --git a/include/bits/atomic_builtins.h b/include/bits/atomic_builtins.h new file mode 100644 index 0000000..5896daf --- /dev/null +++ b/include/bits/atomic_builtins.h @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once +#include + +/* We are implementing the libary interface described here: + * See https://gcc.gnu.org/wiki/Atomic/GCCMM/LIbrary + * + * This file is an adaptation of this template in modm: + * https://github.com/modm-io/modm/blob/develop/ext/gcc/modm_atomic.hpp.in + * + * AVRs have no memory barrier instruction, but we still need a compiler fence! + */ + +class __modm_atomic_lock +{ +public: + [[gnu::always_inline]] inline __modm_atomic_lock() : sreg(SREG) + { + __atomic_signal_fence(__ATOMIC_SEQ_CST); + cli(); + } + + [[gnu::always_inline]] inline ~__modm_atomic_lock() + { + SREG = sreg; + __atomic_signal_fence(__ATOMIC_SEQ_CST); + } + +private: + uint8_t sreg; +}; + +// ============================= generic integers ============================= +template +[[gnu::always_inline]] inline T +__modm_atomic_load_t(const volatile void *ptr) +{ + __modm_atomic_lock l; + return *reinterpret_cast(ptr); +} + +template +[[gnu::always_inline]] inline void +__modm_atomic_store_t(volatile void *ptr, T value) +{ + __modm_atomic_lock l; + *reinterpret_cast(ptr) = value; +} + +template +[[gnu::always_inline]] inline T +__modm_atomic_exchange_t(volatile void *ptr, T desired) +{ + __modm_atomic_lock l; + T previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = desired; + return previous; +} + +template +[[gnu::always_inline]] inline bool +__modm_atomic_compare_exchange_t(volatile void *ptr, void *expected, T desired) +{ + __modm_atomic_lock l; + const T current = *reinterpret_cast(ptr); + if (current == *reinterpret_cast(expected)) [[likely]] + { + *reinterpret_cast(ptr) = desired; + return true; + } + else *reinterpret_cast(expected) = current; + return false; +} + +extern "C" +{ + +// ================================ lock free ================================= +[[gnu::always_inline]] inline bool +__atomic_is_lock_free (unsigned int object_size, const volatile void */*ptr*/) +{ + // only lock free if size ≤ bus width and then also properly aligned + return (object_size <= 1); +} + + +// ========================= atomics for 8 bit integers ========================= +[[gnu::always_inline]] inline uint8_t +__atomic_exchange_1(volatile void *ptr, uint8_t desired, int /*memorder*/) +{ + return __modm_atomic_exchange_t(ptr, desired); +} + +[[gnu::always_inline]] inline bool +__atomic_compare_exchange_1(volatile void *ptr, void *expected, uint8_t desired, + bool /*weak*/, int /*success_memorder*/, int /*failure_memorder*/) +{ + return __modm_atomic_compare_exchange_t(ptr, expected, desired); +} + +[[gnu::always_inline]] inline uint8_t +__atomic_fetch_and_1(volatile void *ptr, uint8_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint8_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous & value); + return previous; +} + +[[gnu::always_inline]] inline uint8_t +__atomic_fetch_or_1(volatile void *ptr, uint8_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint8_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous | value); + return previous; +} + +[[gnu::always_inline]] inline uint8_t +__atomic_fetch_xor_1(volatile void *ptr, uint8_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint8_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous ^ value); + return previous; +} + +[[gnu::always_inline]] inline uint8_t +__atomic_fetch_nand_1(volatile void *ptr, uint8_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint8_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = ~(previous & value); + return previous; +} + +// ========================= atomics for 16 bit integers ========================= +[[gnu::always_inline]] inline uint16_t +__atomic_load_2(const volatile void *ptr, int /*memorder*/) +{ + return __modm_atomic_load_t(ptr); +} + +[[gnu::always_inline]] inline void +__atomic_store_2(volatile void *ptr, uint16_t value, int /*memorder*/) +{ + __modm_atomic_store_t(ptr, value); +} + +[[gnu::always_inline]] inline uint16_t +__atomic_exchange_2(volatile void *ptr, uint16_t desired, int /*memorder*/) +{ + return __modm_atomic_exchange_t(ptr, desired); +} + +[[gnu::always_inline]] inline bool +__atomic_compare_exchange_2(volatile void *ptr, void *expected, uint16_t desired, + bool /*weak*/, int /*success_memorder*/, int /*failure_memorder*/) +{ + return __modm_atomic_compare_exchange_t(ptr, expected, desired); +} + +[[gnu::always_inline]] inline uint16_t +__atomic_fetch_and_2(volatile void *ptr, uint16_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint16_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous & value); + return previous; +} + +[[gnu::always_inline]] inline uint16_t +__atomic_fetch_or_2(volatile void *ptr, uint16_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint16_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous | value); + return previous; +} + +[[gnu::always_inline]] inline uint16_t +__atomic_fetch_xor_2(volatile void *ptr, uint16_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint16_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous ^ value); + return previous; +} + +[[gnu::always_inline]] inline uint16_t +__atomic_fetch_nand_2(volatile void *ptr, uint16_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint16_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = ~(previous & value); + return previous; +} + +// ========================= atomics for 32 bit integers ========================= +[[gnu::always_inline]] inline uint32_t +__atomic_load_4(const volatile void *ptr, int /*memorder*/) +{ + return __modm_atomic_load_t(ptr); +} + +[[gnu::always_inline]] inline void +__atomic_store_4(volatile void *ptr, uint32_t value, int /*memorder*/) +{ + __modm_atomic_store_t(ptr, value); +} + +[[gnu::always_inline]] inline uint32_t +__atomic_exchange_4(volatile void *ptr, uint32_t desired, int /*memorder*/) +{ + return __modm_atomic_exchange_t(ptr, desired); +} + +[[gnu::always_inline]] inline bool +__atomic_compare_exchange_4(volatile void *ptr, void *expected, uint32_t desired, + bool /*weak*/, int /*success_memorder*/, int /*failure_memorder*/) +{ + return __modm_atomic_compare_exchange_t(ptr, expected, desired); +} + +[[gnu::always_inline]] inline uint32_t +__atomic_fetch_and_4(volatile void *ptr, uint32_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint32_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous & value); + return previous; +} + +[[gnu::always_inline]] inline uint32_t +__atomic_fetch_or_4(volatile void *ptr, uint32_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint32_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous | value); + return previous; +} + +[[gnu::always_inline]] inline uint32_t +__atomic_fetch_xor_4(volatile void *ptr, uint32_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint32_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous ^ value); + return previous; +} + +[[gnu::always_inline]] inline uint32_t +__atomic_fetch_nand_4(volatile void *ptr, uint32_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint32_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = ~(previous & value); + return previous; +} + +// ========================= atomics for 64 bit integers ========================= +[[gnu::always_inline]] inline uint64_t +__atomic_load_8(const volatile void *ptr, int /*memorder*/) +{ + return __modm_atomic_load_t(ptr); +} + +[[gnu::always_inline]] inline void +__atomic_store_8(volatile void *ptr, uint64_t value, int /*memorder*/) +{ + __modm_atomic_store_t(ptr, value); +} + +[[gnu::always_inline]] inline uint64_t +__atomic_exchange_8(volatile void *ptr, uint64_t desired, int /*memorder*/) +{ + return __modm_atomic_exchange_t(ptr, desired); +} + +[[gnu::always_inline]] inline bool +__atomic_compare_exchange_8(volatile void *ptr, void *expected, uint64_t desired, + bool /*weak*/, int /*success_memorder*/, int /*failure_memorder*/) +{ + return __modm_atomic_compare_exchange_t(ptr, expected, desired); +} + +[[gnu::always_inline]] inline uint64_t +__atomic_fetch_and_8(volatile void *ptr, uint64_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint64_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous & value); + return previous; +} + +[[gnu::always_inline]] inline uint64_t +__atomic_fetch_or_8(volatile void *ptr, uint64_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint64_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous | value); + return previous; +} + +[[gnu::always_inline]] inline uint64_t +__atomic_fetch_xor_8(volatile void *ptr, uint64_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint64_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous ^ value); + return previous; +} + +[[gnu::always_inline]] inline uint64_t +__atomic_fetch_nand_8(volatile void *ptr, uint64_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint64_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = ~(previous & value); + return previous; +} + +} // extern "C" diff --git a/src/atomic_builtins.cc b/src/atomic_builtins.cc new file mode 100644 index 0000000..c92705d --- /dev/null +++ b/src/atomic_builtins.cc @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2020, 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include + +/* We are implementing the libary interface described here: + * See https://gcc.gnu.org/wiki/Atomic/GCCMM/LIbrary + * + * This file is an adaptation of this template in modm: + * https://github.com/modm-io/modm/blob/develop/ext/gcc/atomic.cpp.in + * + * No need for compiler fences, since the function call already is one. + */ + +extern "C" +{ + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" + +// ============================ atomics for arrays ============================ +// These functions cannot be inlined, since the compiler builtins are named the +// same. Terrible design really. +void +__atomic_load(unsigned int size, const volatile void *src, void *dest, int /*memorder*/) +{ + __modm_atomic_lock l; + __builtin_memcpy(dest, (const void*)src, size); +} + +void +__atomic_store(unsigned int size, volatile void *dest, void *src, int /*memorder*/) +{ + __modm_atomic_lock l; + __builtin_memcpy((void*)dest, src, size); +} + +void +__atomic_exchange(unsigned int size, volatile void *ptr, void *val, void *ret, int /*memorder*/) +{ + __modm_atomic_lock l; + __builtin_memcpy(ret, (void*)ptr, size); + __builtin_memcpy((void*)ptr, val, size); +} + +bool +__atomic_compare_exchange(unsigned int len, volatile void *ptr, void *expected, void *desired, + int /*success_memorder*/, int /*failure_memorder*/) +{ + __modm_atomic_lock l; + if (__builtin_memcmp((void*)ptr, expected, len) == 0) [[likely]] + { + __builtin_memcpy((void*)ptr, desired, len); + return true; + } + else __builtin_memcpy(expected, (void*)ptr, len); + return false; +} + +#pragma GCC diagnostic pop + +// These functions cannot be inlined since the compiler refuses to find these +// functions even if they are declared right at the call site. Unclear why. + +// ========================= atomics for 8 bit integers ========================= +uint8_t +__atomic_fetch_add_1(volatile void *ptr, uint8_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint8_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous + value); + return previous; +} + +uint8_t +__atomic_fetch_sub_1(volatile void *ptr, uint8_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint8_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous - value); + return previous; +} + +// ========================= atomics for 16 bit integers ========================= +uint16_t +__atomic_fetch_add_2(volatile void *ptr, uint16_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint16_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous + value); + return previous; +} + +uint16_t +__atomic_fetch_sub_2(volatile void *ptr, uint16_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint16_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous - value); + return previous; +} + +// ========================= atomics for 32 bit integers ========================= +uint32_t +__atomic_fetch_add_4(volatile void *ptr, uint32_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint32_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous + value); + return previous; +} + +uint32_t +__atomic_fetch_sub_4(volatile void *ptr, uint32_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint32_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous - value); + return previous; +} + +// ========================= atomics for 64 bit integers ========================= +uint64_t +__atomic_fetch_add_8(volatile void *ptr, uint64_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint64_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous + value); + return previous; +} + +uint64_t +__atomic_fetch_sub_8(volatile void *ptr, uint64_t value, int /*memorder*/) +{ + __modm_atomic_lock l; + uint64_t previous = *reinterpret_cast(ptr); + *reinterpret_cast(ptr) = (previous - value); + return previous; +} + +} // extern "C" From 80f3d390e47358b7035aa74c9b99f887593b6dca Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Mon, 20 May 2024 23:22:22 +0200 Subject: [PATCH 2/2] Add atomics example --- examples/atomic/Makefile | 54 +++++++++++++++++++++++++ examples/atomic/atomic.cpp | 81 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 examples/atomic/Makefile create mode 100644 examples/atomic/atomic.cpp diff --git a/examples/atomic/Makefile b/examples/atomic/Makefile new file mode 100644 index 0000000..4613107 --- /dev/null +++ b/examples/atomic/Makefile @@ -0,0 +1,54 @@ +NAME=vector-test +MCU=atmega328p +F_CPU=16000000ul + +PROGRAMMER=arduino +AVRDUDE_FLAGS= -P/dev/ttyUSB0 -b57600 + +ifeq ($(STD),) +STD=c++20 +endif + +BUILD_DIR=./build +LIB_DIR=../.. +COMMON_DIR=../common + +INCLUDES=-I$(COMMON_DIR) -I$(LIB_DIR)/include + +SOURCES=$(wildcard *.cpp $(LIB_DIR)/src/*.cc) +VPATH=.:$(LIB_DIR)/src:$(COMMON_DIR) +OBJECTS=$(addprefix $(BUILD_DIR)/,$(notdir $(SOURCES:%=%.o))) + +CXXFLAGS=-std=$(STD) -Os -Wno-volatile --param=min-pagesize=0 -Wall -Wextra -pedantic -fno-exceptions -fno-rtti -fno-unwind-tables -fno-threadsafe-statics -Wshadow -Wcast-qual -Wpointer-arith -Wundef -DF_CPU=$(F_CPU) +LDFLAGS= + +TARGET=$(BUILD_DIR)/$(NAME) + +all: hex size + +hex: $(TARGET).hex + +$(TARGET).hex: $(TARGET).elf + avr-objcopy -O ihex -j .data -j .text $(TARGET).elf $(TARGET).hex + +$(TARGET).elf: $(OBJECTS) + avr-g++ $(LDFLAGS) -mmcu=$(MCU) $(OBJECTS) -o $(TARGET).elf + +$(BUILD_DIR)/%.cpp.o: %.cpp + @mkdir -p $(BUILD_DIR) + avr-g++ -c $(CXXFLAGS) -mmcu=$(MCU) $(INCLUDES) $< -o $@ + +$(BUILD_DIR)/%.cc.o: %.cc + @mkdir -p $(BUILD_DIR) + avr-g++ -c $(CXXFLAGS) -mmcu=$(MCU) $(INCLUDES) $< -o $@ + +size: $(TARGET).elf + avr-objdump -Pmem-usage $(TARGET).elf + +program: $(TARGET).hex + avrdude -p$(MCU) $(AVRDUDE_FLAGS) -c$(PROGRAMMER) -Uflash:w:$(TARGET).hex:a + +clean: + rm -rf $(BUILD_DIR)/*.o + rm -rf $(BUILD_DIR)/*.elf + rm -rf $(BUILD_DIR)/*.hex diff --git a/examples/atomic/atomic.cpp b/examples/atomic/atomic.cpp new file mode 100644 index 0000000..ee75f5e --- /dev/null +++ b/examples/atomic/atomic.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2022, Christopher Kormanyos + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include + +#include +#include +#include + +static void app_hw_init(); + +std::atomic sequence; + +int main() +{ + // Initialize the application hardware. This includes WDT, PORTB.5 and TIMER0. + app_hw_init(); + + for(;;) + { + // Toggle the LED on portb.5. + PINB = (1U << PORTB5); + + sequence++; + } +} + +static void app_hw_init() +{ + // Initialize the application including WDT, PORTB.5 and TIMER0 + + // We will now disable the watchdog. + // Service the watchdog just to be sure to avoid pending timeout. + wdt_reset(); + + // Clear WDRF in MCUSR. + MCUSR &= ~(1U << WDRF); + + // Write logical one to WDCE and WDE. + // Keep the old prescaler setting to prevent unintentional time-out. + WDTCSR |= (1U << WDCE) | (1U << WDE); + + // Turn off the WDT. + WDTCSR = 0x00; + + // We will now initialize PORTB.5 to be used as an LED driver port. + // Set PORTB.5 value to low. + PORTB &= ~(1U << PORTB5); + + // Set PORTB.5 direction to output. + DDRB |= (1U << DDB5); + + // We will now initialize the TIMER0 clock and interrupt. + // Clear the TIMER0 overflow flag. + TIFR0 = static_cast(1U << TOV0); + + // Enable the TIMER0 overflow interrupt. + TIMSK0 = static_cast(1U << TOIE0); + + // Set the TIMER0 clock source to f_osc/8 = 2MHz and begin counting. + TCCR0B = static_cast(1U << CS01); + + // Enable all interrupts. + sei(); +} + + + +ISR(TIMER0_OVF_vect) +{ + sequence++; +}