From 4943b1cbf065a68a486dfebc008e264eae9b46fd Mon Sep 17 00:00:00 2001 From: Jakob Hasse Date: Tue, 7 Apr 2020 12:55:17 +0800 Subject: [PATCH] CXX: make __attribute__((init_priority(n))) work * Added corresponding test case * Moved all C++ init tests to separate file Closes https://github.com/espressif/esp-idf/issues/5038 --- components/cxx/test/test_cxx.cpp | 152 --------------- components/cxx/test/test_initialization.cpp | 194 ++++++++++++++++++++ components/esp32/ld/esp32.project.ld.in | 2 +- components/esp32s2/ld/esp32s2.project.ld.in | 2 +- 4 files changed, 196 insertions(+), 154 deletions(-) create mode 100644 components/cxx/test/test_initialization.cpp diff --git a/components/cxx/test/test_cxx.cpp b/components/cxx/test/test_cxx.cpp index 027fb6730..b3bf91792 100644 --- a/components/cxx/test/test_cxx.cpp +++ b/components/cxx/test/test_cxx.cpp @@ -9,8 +9,6 @@ #include "freertos/semphr.h" #include "soc/soc.h" -static const char* TAG = "cxx"; - TEST_CASE("can use new and delete", "[cxx]") { int* int_p = new int(10); @@ -39,27 +37,6 @@ TEST_CASE("can call virtual functions", "[cxx]") b.foo(); } -class NonPOD -{ -public: - NonPOD(int a_) : a(a_) { } - int a; -}; - -static int non_pod_test_helper(int new_val) -{ - static NonPOD non_pod(42); - int ret = non_pod.a; - non_pod.a = new_val; - return ret; -} - -TEST_CASE("can use static initializers for non-POD types", "[cxx]") -{ - TEST_ASSERT_EQUAL(42, non_pod_test_helper(1)); - TEST_ASSERT_EQUAL(1, non_pod_test_helper(0)); -} - TEST_CASE("can use std::vector", "[cxx]") { std::vector v(10, 1); @@ -67,135 +44,6 @@ TEST_CASE("can use std::vector", "[cxx]") TEST_ASSERT_EQUAL(51, std::accumulate(std::begin(v), std::end(v), 0)); } -/* - * This test exercises static initialization guards for two objects. - * For each object, 4 tasks are created which attempt to perform static initialization. - * We check that constructor runs only once for each object. - */ - -static SemaphoreHandle_t s_slow_init_sem = NULL; - -template -class SlowInit -{ -public: - SlowInit(int arg) { - ESP_LOGD(TAG, "init obj=%d start, arg=%d\n", obj, arg); - vTaskDelay(300/portTICK_PERIOD_MS); - TEST_ASSERT_EQUAL(-1, mInitBy); - TEST_ASSERT_EQUAL(0, mInitCount); - mInitBy = arg; - ++mInitCount; - ESP_LOGD(TAG, "init obj=%d done\n", obj); - } - - static void task(void* arg) { - int taskId = reinterpret_cast(arg); - ESP_LOGD(TAG, "obj=%d before static init, task=%d\n", obj, taskId); - static SlowInit slowinit(taskId); - ESP_LOGD(TAG, "obj=%d after static init, task=%d\n", obj, taskId); - xSemaphoreGive(s_slow_init_sem); - vTaskDelete(NULL); - } -private: - static int mInitBy; - static int mInitCount; -}; - -template<> int SlowInit<1>::mInitBy = -1; -template<> int SlowInit<1>::mInitCount = 0; -template<> int SlowInit<2>::mInitBy = -1; -template<> int SlowInit<2>::mInitCount = 0; - -template -static int start_slow_init_task(int id, int affinity) -{ - return xTaskCreatePinnedToCore(&SlowInit::task, "slow_init", 2048, - reinterpret_cast(id), 3, NULL, affinity) ? 1 : 0; -} - -TEST_CASE("static initialization guards work as expected", "[cxx]") -{ - s_slow_init_sem = xSemaphoreCreateCounting(10, 0); - TEST_ASSERT_NOT_NULL(s_slow_init_sem); - int task_count = 0; - // four tasks competing for static initialization of one object - task_count += start_slow_init_task<1>(0, PRO_CPU_NUM); -#if portNUM_PROCESSORS == 2 - task_count += start_slow_init_task<1>(1, APP_CPU_NUM); -#endif - task_count += start_slow_init_task<1>(2, PRO_CPU_NUM); - task_count += start_slow_init_task<1>(3, tskNO_AFFINITY); - - // four tasks competing for static initialization of another object - task_count += start_slow_init_task<2>(0, PRO_CPU_NUM); -#if portNUM_PROCESSORS == 2 - task_count += start_slow_init_task<2>(1, APP_CPU_NUM); -#endif - task_count += start_slow_init_task<2>(2, PRO_CPU_NUM); - task_count += start_slow_init_task<2>(3, tskNO_AFFINITY); - - // All tasks should - for (int i = 0; i < task_count; ++i) { - TEST_ASSERT_TRUE(xSemaphoreTake(s_slow_init_sem, 500/portTICK_PERIOD_MS)); - } - vSemaphoreDelete(s_slow_init_sem); - - vTaskDelay(10); // Allow tasks to clean up, avoids race with leak detector -} - -struct GlobalInitTest -{ - GlobalInitTest() : index(order++) { - } - int index; - static int order; -}; - -int GlobalInitTest::order = 0; - -GlobalInitTest g_init_test1; -GlobalInitTest g_init_test2; -GlobalInitTest g_init_test3; - -TEST_CASE("global initializers run in the correct order", "[cxx]") -{ - TEST_ASSERT_EQUAL(0, g_init_test1.index); - TEST_ASSERT_EQUAL(1, g_init_test2.index); - TEST_ASSERT_EQUAL(2, g_init_test3.index); -} - -struct StaticInitTestBeforeScheduler -{ - StaticInitTestBeforeScheduler() - { - static int first_init_order = getOrder(); - index = first_init_order; - } - - int getOrder() - { - return order++; - } - - int index; - static int order; -}; - -int StaticInitTestBeforeScheduler::order = 1; - -StaticInitTestBeforeScheduler g_static_init_test1; -StaticInitTestBeforeScheduler g_static_init_test2; -StaticInitTestBeforeScheduler g_static_init_test3; - -TEST_CASE("before scheduler has started, static initializers work correctly", "[cxx]") -{ - TEST_ASSERT_EQUAL(1, g_static_init_test1.index); - TEST_ASSERT_EQUAL(1, g_static_init_test2.index); - TEST_ASSERT_EQUAL(1, g_static_init_test3.index); - TEST_ASSERT_EQUAL(2, StaticInitTestBeforeScheduler::order); -} - /* Note: When first exception (in system) is thrown this test produces memory leaks report (~500 bytes): - 392 bytes (can vary) as libunwind allocates memory to keep stack frames info to handle exceptions. This info is kept until global destructors are called by __do_global_dtors_aux() diff --git a/components/cxx/test/test_initialization.cpp b/components/cxx/test/test_initialization.cpp new file mode 100644 index 000000000..76a7bf731 --- /dev/null +++ b/components/cxx/test/test_initialization.cpp @@ -0,0 +1,194 @@ +#include +#include +#include +#include +#include "unity.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "soc/soc.h" + +static const char* TAG = "cxx"; + +class NonPOD +{ +public: + NonPOD(int a_) : a(a_) { } + int a; +}; + +static int non_pod_test_helper(int new_val) +{ + static NonPOD non_pod(42); + int ret = non_pod.a; + non_pod.a = new_val; + return ret; +} + +TEST_CASE("can use static initializers for non-POD types", "[cxx]") +{ + TEST_ASSERT_EQUAL(42, non_pod_test_helper(1)); + TEST_ASSERT_EQUAL(1, non_pod_test_helper(0)); +} + +/* + * This test exercises static initialization guards for two objects. + * For each object, 4 tasks are created which attempt to perform static initialization. + * We check that constructor runs only once for each object. + */ + +static SemaphoreHandle_t s_slow_init_sem = NULL; + +template +class SlowInit +{ +public: + SlowInit(int arg) { + ESP_LOGD(TAG, "init obj=%d start, arg=%d\n", obj, arg); + vTaskDelay(300/portTICK_PERIOD_MS); + TEST_ASSERT_EQUAL(-1, mInitBy); + TEST_ASSERT_EQUAL(0, mInitCount); + mInitBy = arg; + ++mInitCount; + ESP_LOGD(TAG, "init obj=%d done\n", obj); + } + + static void task(void* arg) { + int taskId = reinterpret_cast(arg); + ESP_LOGD(TAG, "obj=%d before static init, task=%d\n", obj, taskId); + static SlowInit slowinit(taskId); + ESP_LOGD(TAG, "obj=%d after static init, task=%d\n", obj, taskId); + xSemaphoreGive(s_slow_init_sem); + vTaskDelete(NULL); + } +private: + static int mInitBy; + static int mInitCount; +}; + +template<> int SlowInit<1>::mInitBy = -1; +template<> int SlowInit<1>::mInitCount = 0; +template<> int SlowInit<2>::mInitBy = -1; +template<> int SlowInit<2>::mInitCount = 0; + +template +static int start_slow_init_task(int id, int affinity) +{ + return xTaskCreatePinnedToCore(&SlowInit::task, "slow_init", 2048, + reinterpret_cast(id), 3, NULL, affinity) ? 1 : 0; +} + +TEST_CASE("static initialization guards work as expected", "[cxx]") +{ + s_slow_init_sem = xSemaphoreCreateCounting(10, 0); + TEST_ASSERT_NOT_NULL(s_slow_init_sem); + int task_count = 0; + // four tasks competing for static initialization of one object + task_count += start_slow_init_task<1>(0, PRO_CPU_NUM); +#if portNUM_PROCESSORS == 2 + task_count += start_slow_init_task<1>(1, APP_CPU_NUM); +#endif + task_count += start_slow_init_task<1>(2, PRO_CPU_NUM); + task_count += start_slow_init_task<1>(3, tskNO_AFFINITY); + + // four tasks competing for static initialization of another object + task_count += start_slow_init_task<2>(0, PRO_CPU_NUM); +#if portNUM_PROCESSORS == 2 + task_count += start_slow_init_task<2>(1, APP_CPU_NUM); +#endif + task_count += start_slow_init_task<2>(2, PRO_CPU_NUM); + task_count += start_slow_init_task<2>(3, tskNO_AFFINITY); + + // All tasks should + for (int i = 0; i < task_count; ++i) { + TEST_ASSERT_TRUE(xSemaphoreTake(s_slow_init_sem, 500/portTICK_PERIOD_MS)); + } + vSemaphoreDelete(s_slow_init_sem); + + vTaskDelay(10); // Allow tasks to clean up, avoids race with leak detector +} + +struct GlobalInitTest +{ + GlobalInitTest() : index(order++) { + } + int index; + static int order; +}; + +int GlobalInitTest::order = 0; + +GlobalInitTest g_init_test1; +GlobalInitTest g_init_test2; +GlobalInitTest g_init_test3; + +TEST_CASE("global initializers run in the correct order", "[cxx]") +{ + TEST_ASSERT_EQUAL(0, g_init_test1.index); + TEST_ASSERT_EQUAL(1, g_init_test2.index); + TEST_ASSERT_EQUAL(2, g_init_test3.index); +} + +struct StaticInitTestBeforeScheduler +{ + StaticInitTestBeforeScheduler() + { + static int first_init_order = getOrder(); + index = first_init_order; + } + + int getOrder() + { + return order++; + } + + int index; + static int order; +}; + +int StaticInitTestBeforeScheduler::order = 1; + +StaticInitTestBeforeScheduler g_static_init_test1; +StaticInitTestBeforeScheduler g_static_init_test2; +StaticInitTestBeforeScheduler g_static_init_test3; + +TEST_CASE("before scheduler has started, static initializers work correctly", "[cxx]") +{ + TEST_ASSERT_EQUAL(1, g_static_init_test1.index); + TEST_ASSERT_EQUAL(1, g_static_init_test2.index); + TEST_ASSERT_EQUAL(1, g_static_init_test3.index); + TEST_ASSERT_EQUAL(2, StaticInitTestBeforeScheduler::order); +} + +struct PriorityInitTest +{ + PriorityInitTest() + { + index = getOrder(); + } + + int getOrder() + { + return order++; + } + + int index; + static int order; +}; + +int PriorityInitTest::order = 0; + +// init_priority objects are initialized from the lowest to the heighest priority number +// Default init_priority is always the lowest +PriorityInitTest g_static_init_priority_test3; +PriorityInitTest g_static_init_priority_test2 __attribute__((init_priority(1000))); +PriorityInitTest g_static_init_priority_test1 __attribute__((init_priority(999))); + +TEST_CASE("init_priority extension works", "[cxx]") +{ + TEST_ASSERT_EQUAL(0, g_static_init_priority_test1.index); + TEST_ASSERT_EQUAL(1, g_static_init_priority_test2.index); + TEST_ASSERT_EQUAL(2, g_static_init_priority_test3.index); +} + diff --git a/components/esp32/ld/esp32.project.ld.in b/components/esp32/ld/esp32.project.ld.in index f43bf7db7..e2c95c876 100644 --- a/components/esp32/ld/esp32.project.ld.in +++ b/components/esp32/ld/esp32.project.ld.in @@ -276,7 +276,7 @@ SECTIONS Make a point of not including anything from crtbegin.o or crtend.o, as IDF doesn't use toolchain crt */ __init_array_start = ABSOLUTE(.); - KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors .ctors.*)) + KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors SORT(.ctors.*))) __init_array_end = ABSOLUTE(.); KEEP (*crtbegin.*(.dtors)) KEEP (*(EXCLUDE_FILE (*crtend.*) .dtors)) diff --git a/components/esp32s2/ld/esp32s2.project.ld.in b/components/esp32s2/ld/esp32s2.project.ld.in index ecb804cd1..51eeec40f 100644 --- a/components/esp32s2/ld/esp32s2.project.ld.in +++ b/components/esp32s2/ld/esp32s2.project.ld.in @@ -272,7 +272,7 @@ SECTIONS Make a point of not including anything from crtbegin.o or crtend.o, as IDF doesn't use toolchain crt */ __init_array_start = ABSOLUTE(.); - KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors .ctors.*)) + KEEP (*(EXCLUDE_FILE (*crtend.* *crtbegin.*) .ctors SORT(.ctors.*))) __init_array_end = ABSOLUTE(.); KEEP (*crtbegin.*(.dtors)) KEEP (*(EXCLUDE_FILE (*crtend.*) .dtors))