From 8434845050e84986a3b47381db58781bd6d64632 Mon Sep 17 00:00:00 2001 From: "Michael (XIAO Xufeng)" Date: Thu, 26 Dec 2019 18:20:54 +0800 Subject: [PATCH 1/2] atomic: support fetch_and, fetch_and and fetch_xor --- components/xtensa/linker.lf | 1 + components/xtensa/stdatomic.c | 77 +++++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/components/xtensa/linker.lf b/components/xtensa/linker.lf index c39c4ff527..a721ac41ba 100644 --- a/components/xtensa/linker.lf +++ b/components/xtensa/linker.lf @@ -2,6 +2,7 @@ archive: libxtensa.a entries: eri (noflash_text) + stdatomic (noflash) [mapping:hal] archive: libhal.a diff --git a/components/xtensa/stdatomic.c b/components/xtensa/stdatomic.c index bc5b7ba605..b052c2ba2e 100644 --- a/components/xtensa/stdatomic.c +++ b/components/xtensa/stdatomic.c @@ -15,42 +15,90 @@ //replacement for gcc built-in functions #include "sdkconfig.h" -#include "freertos/FreeRTOS.h" +#include #include "xtensa/config/core-isa.h" +#include "xtensa/xtruntime.h" +//reserved to measure atomic operation time +#define atomic_benchmark_intr_disable() +#define atomic_benchmark_intr_restore(STATE) + +// This allows nested interrupts disabling and restoring via local registers or stack. +// They can be called from interrupts too. +// WARNING: Only applies to current CPU. +#define _ATOMIC_ENTER_CRITICAL(void) ({ \ + unsigned state = XTOS_SET_INTLEVEL(XCHAL_EXCM_LEVEL); \ + atomic_benchmark_intr_disable(); \ + state; \ +}) + +#define _ATOMIC_EXIT_CRITICAL(state) do { \ + atomic_benchmark_intr_restore(state); \ + XTOS_RESTORE_JUST_INTLEVEL(state); \ + } while (0) #define CMP_EXCHANGE(n, type) bool __atomic_compare_exchange_ ## n (type* mem, type* expect, type desired, int success, int failure) \ { \ bool ret = false; \ - unsigned state = portENTER_CRITICAL_NESTED(); \ + unsigned state = _ATOMIC_ENTER_CRITICAL(); \ if (*mem == *expect) { \ ret = true; \ *mem = desired; \ } else { \ *expect = *mem; \ } \ - portEXIT_CRITICAL_NESTED(state); \ + _ATOMIC_EXIT_CRITICAL(state); \ return ret; \ } #define FETCH_ADD(n, type) type __atomic_fetch_add_ ## n (type* ptr, type value, int memorder) \ { \ - unsigned state = portENTER_CRITICAL_NESTED(); \ + unsigned state = _ATOMIC_ENTER_CRITICAL(); \ type ret = *ptr; \ *ptr = *ptr + value; \ - portEXIT_CRITICAL_NESTED(state); \ + _ATOMIC_EXIT_CRITICAL(state); \ return ret; \ } #define FETCH_SUB(n, type) type __atomic_fetch_sub_ ## n (type* ptr, type value, int memorder) \ { \ - unsigned state = portENTER_CRITICAL_NESTED(); \ + unsigned state = _ATOMIC_ENTER_CRITICAL(); \ type ret = *ptr; \ *ptr = *ptr - value; \ - portEXIT_CRITICAL_NESTED(state); \ + _ATOMIC_EXIT_CRITICAL(state); \ return ret; \ } +#define FETCH_AND(n, type) type __atomic_fetch_and_ ## n (type* ptr, type value, int memorder) \ +{ \ + unsigned state = _ATOMIC_ENTER_CRITICAL(); \ + type ret = *ptr; \ + *ptr = *ptr & value; \ + _ATOMIC_EXIT_CRITICAL(state); \ + return ret; \ +} + +#define FETCH_OR(n, type) type __atomic_fetch_or_ ## n (type* ptr, type value, int memorder) \ +{ \ + unsigned state = _ATOMIC_ENTER_CRITICAL(); \ + type ret = *ptr; \ + *ptr = *ptr | value; \ + _ATOMIC_EXIT_CRITICAL(state); \ + return ret; \ +} + +#define FETCH_XOR(n, type) type __atomic_fetch_xor_ ## n (type* ptr, type value, int memorder) \ +{ \ + unsigned state = _ATOMIC_ENTER_CRITICAL(); \ + type ret = *ptr; \ + *ptr = *ptr ^ value; \ + _ATOMIC_EXIT_CRITICAL(state); \ + return ret; \ +} + +#ifndef XCHAL_HAVE_S32C1I +#error "XCHAL_HAVE_S32C1I not defined, include correct header!" +#endif //this piece of code should only be compiled if the cpu doesn't support atomic compare and swap (s32c1i) #if XCHAL_HAVE_S32C1I == 0 @@ -72,4 +120,19 @@ FETCH_SUB(2, uint16_t) FETCH_SUB(4, uint32_t) FETCH_SUB(8, uint64_t) +FETCH_AND(1, uint8_t) +FETCH_AND(2, uint16_t) +FETCH_AND(4, uint32_t) +FETCH_AND(8, uint64_t) + +FETCH_OR(1, uint8_t) +FETCH_OR(2, uint16_t) +FETCH_OR(4, uint32_t) +FETCH_OR(8, uint64_t) + +FETCH_XOR(1, uint8_t) +FETCH_XOR(2, uint16_t) +FETCH_XOR(4, uint32_t) +FETCH_XOR(8, uint64_t) + #endif \ No newline at end of file From 9e12586ab70513499292a338057ed696158ab9c0 Mon Sep 17 00:00:00 2001 From: "Michael (XIAO Xufeng)" Date: Wed, 4 Mar 2020 01:13:10 +0800 Subject: [PATCH 2/2] esp_common: new unit test benchmarking stdatomic operations --- components/esp_common/test/CMakeLists.txt | 3 + components/esp_common/test/component.mk | 5 + components/esp_common/test/test_atomic.c | 135 ++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 components/esp_common/test/CMakeLists.txt create mode 100644 components/esp_common/test/component.mk create mode 100644 components/esp_common/test/test_atomic.c diff --git a/components/esp_common/test/CMakeLists.txt b/components/esp_common/test/CMakeLists.txt new file mode 100644 index 0000000000..30def4ebd5 --- /dev/null +++ b/components/esp_common/test/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRC_DIRS . + REQUIRES unity spi_flash + ) \ No newline at end of file diff --git a/components/esp_common/test/component.mk b/components/esp_common/test/component.mk new file mode 100644 index 0000000000..5dd172bdb7 --- /dev/null +++ b/components/esp_common/test/component.mk @@ -0,0 +1,5 @@ +# +#Component Makefile +# + +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/esp_common/test/test_atomic.c b/components/esp_common/test/test_atomic.c new file mode 100644 index 0000000000..890c42dee0 --- /dev/null +++ b/components/esp_common/test/test_atomic.c @@ -0,0 +1,135 @@ +#include "unity.h" +#include +#include "esp_log.h" +#include "esp_attr.h" +#include "freertos/xtensa_api.h" + +#include "../cache_utils.h" + +#define RECORD_TIME_PREPARE() uint32_t __t1, __t2 +#define RECORD_TIME_START() do {__t1 = xthal_get_ccount();}while(0) +#define RECORD_TIME_END(p_time) do{__t2 = xthal_get_ccount(); *p_time = (__t2-__t1);}while(0) + + +#define TEST_TIMES 11 + +//Test twice, and only get the result of second time, to avoid influence of cache miss +#define TEST_WRAP_START() \ + RECORD_TIME_PREPARE(); \ + spi_flash_disable_interrupts_caches_and_other_cpu(); \ + for (int i = 0; i < 2; i++) { \ + RECORD_TIME_START(); + +#define TEST_WRAP_END(output) \ + RECORD_TIME_END(output); \ + } \ + spi_flash_enable_interrupts_caches_and_other_cpu(); + +typedef void (*test_f)(uint32_t* t_op); + +static const char TAG[] = "test_atomic"; +static uint32_t s_t_ref; + +static void sorted_array_insert(uint32_t* array, int* size, uint32_t item) +{ + int pos; + for (pos = *size; pos>0; pos--) { + if (array[pos-1] < item) break; + array[pos] = array[pos-1]; + } + array[pos]=item; + (*size)++; +} + +static void test_flow(const char* name, test_f func) +{ + int t_flight_num = 0; + uint32_t t_flight_sorted[TEST_TIMES]; + + for (int i = 0; i < TEST_TIMES; i++) { + uint32_t t_op; + func(&t_op); + sorted_array_insert(t_flight_sorted, &t_flight_num, t_op); + } + for (int i = 0; i < TEST_TIMES; i++) { + ESP_LOGI(TAG, "%s: %d ops", name, t_flight_sorted[i]-s_t_ref); + } +} + +static IRAM_ATTR void test_ref(uint32_t* t_op) +{ + TEST_WRAP_START() + TEST_WRAP_END(t_op) + s_t_ref = *t_op; +} + +static IRAM_ATTR void test_atomic_load(uint32_t* t_op) +{ + atomic_uint_fast32_t var; + uint32_t target = rand(); + TEST_WRAP_START() + target = atomic_load(&var); + TEST_WRAP_END(t_op) + (void) target; +} + +static IRAM_ATTR void test_atomic_store(uint32_t* t_op) +{ + atomic_uint_fast32_t var; + uint32_t src = rand(); + TEST_WRAP_START() + atomic_store(&var, src); + TEST_WRAP_END(t_op) +} + +static IRAM_ATTR void test_atomic_store_load(uint32_t* t_op) +{ + atomic_uint_fast32_t var; + uint32_t src = rand(); + TEST_WRAP_START() + atomic_store(&var, src); + src = atomic_load(&var); + TEST_WRAP_END(t_op) +} + +static IRAM_ATTR void test_atomic_fetch_and(uint32_t* t_op) +{ + atomic_uint_fast32_t var; + uint32_t src = rand(); + TEST_WRAP_START() + src = atomic_fetch_and(&var, src); + TEST_WRAP_END(t_op) +} + +static IRAM_ATTR void test_atomic_fetch_or(uint32_t* t_op) +{ + atomic_uint_fast32_t var; + uint32_t src = rand(); + TEST_WRAP_START() + src = atomic_fetch_or(&var, src); + TEST_WRAP_END(t_op) +} + +static IRAM_ATTR void test_atomic_compare_exchange(uint32_t* t_op) +{ + atomic_uint_fast32_t var; + uint32_t src = rand(); + uint32_t cmp = rand(); + bool res; + TEST_WRAP_START() + res = atomic_compare_exchange_weak(&var, &src, cmp); + TEST_WRAP_END(t_op) + (void) res; +} + +TEST_CASE("test atomic","[atomic]") +{ + test_flow("ref", test_ref); + + test_flow("atomic_load", test_atomic_load); + test_flow("atomic_store", test_atomic_store); + test_flow("atomic_compare_exchange", test_atomic_compare_exchange); + test_flow("atomic_store + atomic_load", test_atomic_store_load); + test_flow("atomic_and", test_atomic_fetch_and); + test_flow("atomic_or", test_atomic_fetch_or); +}