From dc123cb38eeaabb5d40b3a710deb0069ca37e794 Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 13 Mar 2026 16:33:37 +0800 Subject: [PATCH 1/4] testing/libc/arch_libc: Add tests for all string/memory functions. The arch_libc test only covered strcpy, so the architecture optimized implementations of the remaining string and memory routines were never exercised by the test suite. Extend the test to also cover memcpy, memmove, memset, memcmp, memchr, strlen, strcmp, strchr, strncmp, strnlen, strncpy, stpcpy, strcat and strrchr: * Every function gets a correctness test that sweeps the buffer alignment and the transfer size and compares the result against the expected value. * Every function gets a speed test that reports the average cycle count measured with perf_gettime(). * Every individual test is selected by its own CONFIG_TESTING_ARCH_LIBC_ option (default y), so a target can drop the ones it does not need. Impact: test only. Nothing is built unless CONFIG_TESTING_ARCH_LIBC (default n) is selected, so no existing board configuration changes. Testing: built and ran sim:nsh on Linux x86_64 (Ubuntu 24.04, gcc 13.3.0) with CONFIG_TESTING_ARCH_LIBC=y. All 15 enabled functions report PASSED and "arch_libc_test Passed". Signed-off-by: Xiang Xiao --- testing/libc/arch_libc/Kconfig | 56 + testing/libc/arch_libc/arch_libc_test_main.c | 1046 ++++++++++++++++-- 2 files changed, 1007 insertions(+), 95 deletions(-) diff --git a/testing/libc/arch_libc/Kconfig b/testing/libc/arch_libc/Kconfig index 65927909cbb..b0aba69a5ba 100644 --- a/testing/libc/arch_libc/Kconfig +++ b/testing/libc/arch_libc/Kconfig @@ -12,10 +12,66 @@ config TESTING_ARCH_LIBC if TESTING_ARCH_LIBC +config TESTING_ARCH_LIBC_MEMCHR + bool "test memchr" + default y + +config TESTING_ARCH_LIBC_MEMCMP + bool "test memcmp" + default y + +config TESTING_ARCH_LIBC_MEMCPY + bool "test memcpy" + default y + +config TESTING_ARCH_LIBC_MEMMOVE + bool "test memmove" + default y + +config TESTING_ARCH_LIBC_MEMSET + bool "test memset" + default y + +config TESTING_ARCH_LIBC_STRCHR + bool "test strchr" + default y + +config TESTING_ARCH_LIBC_STRCMP + bool "test strcmp" + default y + config TESTING_ARCH_LIBC_STRCPY bool "test strcpy" default y +config TESTING_ARCH_LIBC_STRLEN + bool "test strlen" + default y + +config TESTING_ARCH_LIBC_STRNCMP + bool "test strncmp" + default y + +config TESTING_ARCH_LIBC_STRNLEN + bool "test strnlen" + default y + +config TESTING_ARCH_LIBC_STRNCPY + bool "test strncpy" + default y + +config TESTING_ARCH_LIBC_STPCPY + bool "test stpcpy" + default y + +config TESTING_ARCH_LIBC_STRCAT + bool "test strcat" + default y + +config TESTING_ARCH_LIBC_STRRCHR + bool "test strrchr" + default y + config TESTING_ARCH_LIBC_PROGNAME string "Program name" default "arch_libctest" diff --git a/testing/libc/arch_libc/arch_libc_test_main.c b/testing/libc/arch_libc/arch_libc_test_main.c index b69c1dfc453..67f8e6cb323 100644 --- a/testing/libc/arch_libc/arch_libc_test_main.c +++ b/testing/libc/arch_libc/arch_libc_test_main.c @@ -39,197 +39,994 @@ * Pre-processor Definitions ****************************************************************************/ -/* Configuration ************************************************************/ - -#ifdef CONFIG_TESTING_ARCH_LIBC_STRCPY -# define TEST_MAX_STRING_LEN 128 -# define TEST_SHORT_SRC_LEN 50 -# define TEST_LONG_SRC_LEN 128 -#endif +#define TEST_BUF_SIZE 256 +#define TEST_REPEAT 100 +#define MAX_ALIGN 16 /**************************************************************************** * Private Data ****************************************************************************/ -#ifdef CONFIG_TESTING_ARCH_LIBC_STRCPY -static char g_test_src_str[TEST_MAX_STRING_LEN]; -static char g_test_dst_str[TEST_MAX_STRING_LEN]; -#endif +static char g_buf1[TEST_BUF_SIZE + MAX_ALIGN]; +static char g_buf2[TEST_BUF_SIZE + MAX_ALIGN]; +static volatile uintptr_t g_sink; /**************************************************************************** * Private Functions ****************************************************************************/ -#ifdef CONFIG_TESTING_ARCH_LIBC_STRCPY +static void fill_pattern(FAR char *buf, int len) +{ + int i; + for (i = 0; i < len; i++) + { + buf[i] = 'A' + (i % 26); + } +} /**************************************************************************** - * Name: arch_libc_test_strcpy + * Name: test_memcpy ****************************************************************************/ -void init_short_test_str(int dst_offset, int src_offset) +#ifdef CONFIG_TESTING_ARCH_LIBC_MEMCPY +static int test_memcpy(void) { - int off; + int align; + int size; + int fail = 0; + + printf("Testing memcpy...\n"); + for (align = 0; align < 8; align++) + { + for (size = 1; size <= 128; size++) + { + fill_pattern(g_buf1 + align, size); + memset(g_buf2, 0, sizeof(g_buf2)); + memcpy(g_buf2 + align, g_buf1 + align, size); + if (memcmp(g_buf2 + align, g_buf1 + align, size) != 0) + { + printf(" FAIL: align=%d size=%d\n", align, size); + fail++; + } + } + } - memset(g_test_src_str, '\0', sizeof(g_test_src_str)); - memset(g_test_dst_str, '\0', sizeof(g_test_dst_str)); + printf("memcpy: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} - for (off = 0; off < TEST_SHORT_SRC_LEN; off++) +static void speed_memcpy(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) { - g_test_src_str[off + src_offset] = '0' + off % 10; + g_sink = (uintptr_t)memcpy(g_buf2, g_buf1, 128); } + + end = perf_gettime(); + printf("memcpy(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); } +#endif /**************************************************************************** - * Name: arch_libc_test_strcpy_offset + * Name: test_memmove ****************************************************************************/ -int arch_libc_test_strcpy_offset(int dst_offset, int src_offset) +#ifdef CONFIG_TESTING_ARCH_LIBC_MEMMOVE +static int test_memmove(void) { - int off; - FAR char *dest; - FAR char *src; - FAR char *result_str; - bool pass = true; + int size; + int fail = 0; - init_short_test_str(dst_offset, src_offset); - dest = g_test_dst_str + dst_offset; - src = g_test_src_str + src_offset; - result_str = strcpy(dest, src); + printf("Testing memmove...\n"); - /* check strcpy data */ + /* Test overlapping forward */ - for (off = 0; off < TEST_SHORT_SRC_LEN; off++) + for (size = 1; size <= 64; size++) { - if (result_str[off] != '0' + off % 10) + fill_pattern(g_buf1, size + 16); + memcpy(g_buf2, g_buf1, size + 16); + memmove(g_buf1 + 8, g_buf1, size); + if (memcmp(g_buf1 + 8, g_buf2, size) != 0) { - pass = false; - printf("dest copied data error, index %d\n", off); + printf(" FAIL forward: size=%d\n", size); + fail++; } + } + + /* Test overlapping backward */ - if (src[off] != '0' + off % 10) + for (size = 1; size <= 64; size++) + { + fill_pattern(g_buf1 + 8, size); + memcpy(g_buf2, g_buf1 + 8, size); + memmove(g_buf1, g_buf1 + 8, size); + if (memcmp(g_buf1, g_buf2, size) != 0) { - pass = false; - printf("src data error, index %d\n", off); + printf(" FAIL backward: size=%d\n", size); + fail++; } } - for (off = TEST_SHORT_SRC_LEN; off < TEST_MAX_STRING_LEN - dst_offset; - off++) + printf("memmove: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +static void speed_memmove(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = (uintptr_t)memmove(g_buf2, g_buf1, 128); + } + + end = perf_gettime(); + printf("memmove(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + +/**************************************************************************** + * Name: test_memset + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_MEMSET +static int test_memset(void) +{ + int align; + int size; + int i; + int fail = 0; + + printf("Testing memset...\n"); + for (align = 0; align < 8; align++) { - if (result_str[off] != '\0') + for (size = 1; size <= 128; size++) { - pass = false; - printf("dest tailing zero error, index %d\n", off); + memset(g_buf1, 0, sizeof(g_buf1)); + memset(g_buf1 + align, 0xaa, size); + for (i = 0; i < size; i++) + { + if ((unsigned char)g_buf1[align + i] != 0xaa) + { + printf(" FAIL: align=%d size=%d idx=%d\n", + align, size, i); + fail++; + break; + } + } } } - for (off = TEST_SHORT_SRC_LEN; off < TEST_MAX_STRING_LEN - src_offset; - off++) + printf("memset: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +static void speed_memset(void) +{ + clock_t start; + clock_t end; + int i; + + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = (uintptr_t)memset(g_buf1, 0x55, 128); + } + + end = perf_gettime(); + printf("memset(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + +/**************************************************************************** + * Name: test_memcmp + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_MEMCMP +static int test_memcmp(void) +{ + int size; + int fail = 0; + + printf("Testing memcmp...\n"); + for (size = 1; size <= 128; size++) { - if (src[off] != '\0') + fill_pattern(g_buf1, size); + fill_pattern(g_buf2, size); + if (memcmp(g_buf1, g_buf2, size) != 0) + { + printf(" FAIL equal: size=%d\n", size); + fail++; + } + + g_buf2[size - 1] = '~'; + if (memcmp(g_buf1, g_buf2, size) >= 0) { - pass = false; - printf("src tailing zero error, index %d\n", off); + printf(" FAIL less: size=%d\n", size); + fail++; + } + + g_buf2[size - 1] = 0; + if (memcmp(g_buf1, g_buf2, size) <= 0) + { + printf(" FAIL greater: size=%d\n", size); + fail++; } } - /* strcpy shouldn't change arch_libc_test_strcpy's local variable */ + printf("memcmp: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} - if (dest != (g_test_dst_str + dst_offset) || - src != (g_test_src_str + src_offset)) +static void speed_memcmp(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + fill_pattern(g_buf2, 128); + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) { - pass = false; - printf("local var changed after calling strcpy\n"); + g_sink = (uintptr_t)memcmp(g_buf1, g_buf2, 128); } - if (!pass) + end = perf_gettime(); + printf("memcmp(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + +/**************************************************************************** + * Name: test_memchr + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_MEMCHR +static int test_memchr(void) +{ + int size; + int fail = 0; + FAR void *p; + + printf("Testing memchr...\n"); + for (size = 1; size <= 128; size++) { - printf("Test Failed at dst/src offset [%d, %d]\n", dst_offset, - src_offset); - return -1; + fill_pattern(g_buf1, size); + + /* Find last char */ + + p = memchr(g_buf1, g_buf1[size - 1], size); + if (p == NULL) + { + printf(" FAIL found: size=%d\n", size); + fail++; + } + + /* Not found */ + + p = memchr(g_buf1, 0xff, size); + if (p != NULL) + { + printf(" FAIL notfound: size=%d\n", size); + fail++; + } } - return 0; + printf("memchr: %s\n", fail ? "FAILED" : "PASSED"); + return fail; } +static void speed_memchr(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = (uintptr_t)memchr(g_buf1, g_buf1[127], 128); + } + + end = perf_gettime(); + printf("memchr(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + +/**************************************************************************** + * Name: test_strlen + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_STRLEN +static int test_strlen(void) +{ + int size; + int fail = 0; + + printf("Testing strlen...\n"); + for (size = 0; size <= 128; size++) + { + fill_pattern(g_buf1, size); + g_buf1[size] = '\0'; + if ((int)strlen(g_buf1) != size) + { + printf(" FAIL: size=%d got=%d\n", size, (int)strlen(g_buf1)); + fail++; + } + } + + printf("strlen: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +static void speed_strlen(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + g_buf1[128] = '\0'; + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = strlen(g_buf1); + } + + end = perf_gettime(); + printf("strlen(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + +/**************************************************************************** + * Name: test_strcmp + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_STRCMP +static int test_strcmp(void) +{ + int size; + int fail = 0; + + printf("Testing strcmp...\n"); + for (size = 1; size <= 128; size++) + { + fill_pattern(g_buf1, size); + g_buf1[size] = '\0'; + fill_pattern(g_buf2, size); + g_buf2[size] = '\0'; + if (strcmp(g_buf1, g_buf2) != 0) + { + printf(" FAIL equal: size=%d\n", size); + fail++; + } + + g_buf2[size - 1] = '~'; + g_buf2[size] = '\0'; + if (strcmp(g_buf1, g_buf2) >= 0) + { + printf(" FAIL less: size=%d\n", size); + fail++; + } + } + + printf("strcmp: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +static void speed_strcmp(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + g_buf1[128] = '\0'; + fill_pattern(g_buf2, 128); + g_buf2[128] = '\0'; + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = strcmp(g_buf1, g_buf2); + } + + end = perf_gettime(); + printf("strcmp(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + /**************************************************************************** - * Name: arch_libc_test_strcpy + * Name: test_strcpy ****************************************************************************/ -int arch_libc_test_strcpy(void) +#ifdef CONFIG_TESTING_ARCH_LIBC_STRCPY +static int test_strcpy(void) { - int dest_off; - int src_off; - int ret = 0; + int align; + int size; + int fail = 0; - for (dest_off = 0; dest_off <= 4; dest_off++) + printf("Testing strcpy...\n"); + for (align = 0; align < 8; align++) { - for (src_off = 0; src_off <= 4; src_off++) + for (size = 1; size <= 64; size++) { - if (arch_libc_test_strcpy_offset(dest_off, src_off) != 0) + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; + memset(g_buf2, 0, sizeof(g_buf2)); + strcpy(g_buf2 + align, g_buf1 + align); + if (strcmp(g_buf2 + align, g_buf1 + align) != 0) { - ret = -1; + printf(" FAIL: align=%d size=%d\n", align, size); + fail++; } } } - if (ret != 0) + printf("strcpy: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +static void speed_strcpy(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + g_buf1[128] = '\0'; + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = (uintptr_t)strcpy(g_buf2, g_buf1); + } + + end = perf_gettime(); + printf("strcpy(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + +/**************************************************************************** + * Name: test_strchr + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_STRCHR +static int test_strchr(void) +{ + int size; + int fail = 0; + FAR char *p; + + printf("Testing strchr...\n"); + for (size = 1; size <= 128; size++) + { + fill_pattern(g_buf1, size); + g_buf1[size] = '\0'; + + /* Find first char */ + + p = strchr(g_buf1, g_buf1[0]); + if (p != g_buf1) + { + printf(" FAIL first: size=%d\n", size); + fail++; + } + + /* Find NUL */ + + p = strchr(g_buf1, '\0'); + if (p != g_buf1 + size) + { + printf(" FAIL nul: size=%d\n", size); + fail++; + } + + /* Not found */ + + p = strchr(g_buf1, 0x01); + if (p != NULL) + { + printf(" FAIL notfound: size=%d\n", size); + fail++; + } + } + + printf("strchr: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +static void speed_strchr(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + g_buf1[128] = '\0'; + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = (uintptr_t)strchr(g_buf1, g_buf1[127]); + } + + end = perf_gettime(); + printf("strchr(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + +/**************************************************************************** + * Name: test_strncmp + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_STRNCMP +static int test_strncmp(void) +{ + int size; + int fail = 0; + + printf("Testing strncmp...\n"); + for (size = 1; size <= 128; size++) + { + fill_pattern(g_buf1, size); + g_buf1[size] = '\0'; + fill_pattern(g_buf2, size); + g_buf2[size] = '\0'; + + /* Equal within n */ + + if (strncmp(g_buf1, g_buf2, size) != 0) + { + printf(" FAIL equal: size=%d\n", size); + fail++; + } + + /* Differ at last position */ + + g_buf2[size - 1] = '~'; + if (strncmp(g_buf1, g_buf2, size) >= 0) + { + printf(" FAIL less: size=%d\n", size); + fail++; + } + + /* Equal when n is smaller */ + + fill_pattern(g_buf2, size); + g_buf2[size] = '\0'; + g_buf2[size - 1] = '~'; + if (size > 1 && strncmp(g_buf1, g_buf2, size - 1) != 0) + { + printf(" FAIL partial: size=%d\n", size); + fail++; + } + + /* Zero count */ + + if (strncmp(g_buf1, g_buf2, 0) != 0) + { + printf(" FAIL zero: size=%d\n", size); + fail++; + } + } + + printf("strncmp: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +static void speed_strncmp(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + g_buf1[128] = '\0'; + fill_pattern(g_buf2, 128); + g_buf2[128] = '\0'; + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) { - printf("arch_libc_test_strcpy Test Failed\n"); + g_sink = strncmp(g_buf1, g_buf2, 128); } - else + + end = perf_gettime(); + printf("strncmp(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + +/**************************************************************************** + * Name: test_strnlen + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_STRNLEN +static int test_strnlen(void) +{ + int size; + int fail = 0; + + printf("Testing strnlen...\n"); + for (size = 0; size <= 128; size++) { - printf("arch_libc_test_strcpy Test Passed\n"); + fill_pattern(g_buf1, size); + g_buf1[size] = '\0'; + + /* maxlen >= actual length */ + + if ((int)strnlen(g_buf1, size + 10) != size) + { + printf(" FAIL full: size=%d\n", size); + fail++; + } + + /* maxlen < actual length */ + + if (size > 0 && (int)strnlen(g_buf1, size - 1) != size - 1) + { + printf(" FAIL trunc: size=%d\n", size); + fail++; + } + + /* maxlen == 0 */ + + if (strnlen(g_buf1, 0) != 0) + { + printf(" FAIL zero: size=%d\n", size); + fail++; + } + } + + printf("strnlen: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +static void speed_strnlen(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + g_buf1[128] = '\0'; + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = strnlen(g_buf1, 256); } - return ret; + end = perf_gettime(); + printf("strnlen(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); } +#endif /**************************************************************************** - * Name: arch_libc_strcpy_speed_offset + * Name: test_strncpy ****************************************************************************/ -clock_t arch_libc_strcpy_speed_offset(int dst_offset, int src_offset) +#ifdef CONFIG_TESTING_ARCH_LIBC_STRNCPY +static int test_strncpy(void) +{ + int align; + int size; + int i; + int fail = 0; + + printf("Testing strncpy...\n"); + for (align = 0; align < 8; align++) + { + for (size = 1; size <= 64; size++) + { + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; + + /* n > strlen: should copy and zero-fill */ + + memset(g_buf2, 0xaa, sizeof(g_buf2)); + strncpy(g_buf2 + align, g_buf1 + align, size + 4); + if (strcmp(g_buf2 + align, g_buf1 + align) != 0) + { + printf(" FAIL copy: align=%d size=%d\n", align, size); + fail++; + } + + /* Check zero-fill */ + + for (i = 0; i < 4; i++) + { + if (g_buf2[align + size + i] != '\0') + { + printf(" FAIL zfill: align=%d size=%d\n", align, size); + fail++; + break; + } + } + + /* n < strlen: should truncate without NUL */ + + if (size > 1) + { + memset(g_buf2, 0xaa, sizeof(g_buf2)); + strncpy(g_buf2 + align, g_buf1 + align, size - 1); + if (memcmp(g_buf2 + align, g_buf1 + align, size - 1) != 0) + { + printf(" FAIL trunc: align=%d size=%d\n", align, size); + fail++; + } + } + } + } + + printf("strncpy: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +static void speed_strncpy(void) { - FAR char *dest; - FAR char *src; clock_t start; clock_t end; + int i; - init_short_test_str(dst_offset, src_offset); - dest = g_test_dst_str + dst_offset; - src = g_test_src_str + src_offset; + fill_pattern(g_buf1, 128); + g_buf1[128] = '\0'; start = perf_gettime(); - strcpy(dest, src); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = (uintptr_t)strncpy(g_buf2, g_buf1, 128); + } + end = perf_gettime(); + printf("strncpy(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + +/**************************************************************************** + * Name: test_stpcpy + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_STPCPY +static int test_stpcpy(void) +{ + int align; + int size; + int fail = 0; + FAR char *p; - return end - start; + printf("Testing stpcpy...\n"); + for (align = 0; align < 8; align++) + { + for (size = 1; size <= 64; size++) + { + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; + memset(g_buf2, 0, sizeof(g_buf2)); + p = stpcpy(g_buf2 + align, g_buf1 + align); + + /* Check content */ + + if (strcmp(g_buf2 + align, g_buf1 + align) != 0) + { + printf(" FAIL copy: align=%d size=%d\n", align, size); + fail++; + } + + /* Check return value points to NUL */ + + if (p != g_buf2 + align + size) + { + printf(" FAIL retval: align=%d size=%d\n", align, size); + fail++; + } + + if (*p != '\0') + { + printf(" FAIL nul: align=%d size=%d\n", align, size); + fail++; + } + } + } + + printf("stpcpy: %s\n", fail ? "FAILED" : "PASSED"); + return fail; } +static void speed_stpcpy(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + g_buf1[128] = '\0'; + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = (uintptr_t)stpcpy(g_buf2, g_buf1); + } + + end = perf_gettime(); + printf("stpcpy(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + /**************************************************************************** - * Name: arch_libc_strcpy_speed + * Name: test_strcat ****************************************************************************/ -int arch_libc_strcpy_speed(void) +#ifdef CONFIG_TESTING_ARCH_LIBC_STRCAT +static int test_strcat(void) { - int dest_off; - int src_off; - clock_t cycles = 0; + int size; + int fail = 0; - for (dest_off = 0; dest_off <= 4; dest_off++) + printf("Testing strcat...\n"); + for (size = 1; size <= 64; size++) { - for (src_off = 0; src_off <= 4; src_off++) + /* Build expected: "ABCD..." + "ABCD..." */ + + fill_pattern(g_buf1, size); + g_buf1[size] = '\0'; + + memset(g_buf2, 0, sizeof(g_buf2)); + fill_pattern(g_buf2, size); + g_buf2[size] = '\0'; + + strcat(g_buf2, g_buf1); + + /* Check total length */ + + if ((int)strlen(g_buf2) != size * 2) + { + printf(" FAIL len: size=%d got=%d\n", size, + (int)strlen(g_buf2)); + fail++; + } + + /* Check second half matches */ + + if (memcmp(g_buf2 + size, g_buf1, size) != 0) + { + printf(" FAIL content: size=%d\n", size); + fail++; + } + + /* Test cat to empty string */ + + g_buf2[0] = '\0'; + strcat(g_buf2, g_buf1); + if (strcmp(g_buf2, g_buf1) != 0) { - cycles += arch_libc_strcpy_speed_offset(dest_off, src_off); + printf(" FAIL empty: size=%d\n", size); + fail++; } } - printf("strcpy total(run 25 times) cpu cycles %"PRIu64"\n", cycles); - printf("strcpy average cpu cycles %"PRIu64"\n", cycles / 25); + printf("strcat: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +static void speed_strcat(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 64); + g_buf1[64] = '\0'; + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_buf2[0] = '\0'; + g_sink = (uintptr_t)strcat(g_buf2, g_buf1); + } + + end = perf_gettime(); + printf("strcat(64) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + +/**************************************************************************** + * Name: test_strrchr + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_STRRCHR +static int test_strrchr(void) +{ + int size; + int fail = 0; + FAR char *p; + + printf("Testing strrchr...\n"); + for (size = 1; size <= 128; size++) + { + fill_pattern(g_buf1, size); + g_buf1[size] = '\0'; + + /* Find last occurrence of first char (repeats every 26) */ + + p = strrchr(g_buf1, 'A'); + if (p == NULL) + { + printf(" FAIL found: size=%d\n", size); + fail++; + } + else + { + /* 'A' appears at 0, 26, 52, ... — last one <= size-1 */ + + int expected = ((size - 1) / 26) * 26; + if (p != g_buf1 + expected) + { + printf(" FAIL pos: size=%d expected=%d got=%d\n", + size, expected, (int)(p - g_buf1)); + fail++; + } + } + + /* Find NUL */ + + p = strrchr(g_buf1, '\0'); + if (p != g_buf1 + size) + { + printf(" FAIL nul: size=%d\n", size); + fail++; + } + + /* Not found */ + + p = strrchr(g_buf1, 0x01); + if (p != NULL) + { + printf(" FAIL notfound: size=%d\n", size); + fail++; + } + } - return 0; + printf("strrchr: %s\n", fail ? "FAILED" : "PASSED"); + return fail; } +static void speed_strrchr(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + g_buf1[128] = '\0'; + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = (uintptr_t)strrchr(g_buf1, g_buf1[127]); + } + + end = perf_gettime(); + printf("strrchr(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} #endif /**************************************************************************** @@ -242,11 +1039,70 @@ int arch_libc_strcpy_speed(void) int main(int argc, FAR char *argv[]) { + int fail = 0; + +#ifdef CONFIG_TESTING_ARCH_LIBC_MEMCPY + fail += test_memcpy(); + speed_memcpy(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_MEMMOVE + fail += test_memmove(); + speed_memmove(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_MEMSET + fail += test_memset(); + speed_memset(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_MEMCMP + fail += test_memcmp(); + speed_memcmp(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_MEMCHR + fail += test_memchr(); + speed_memchr(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_STRLEN + fail += test_strlen(); + speed_strlen(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_STRCMP + fail += test_strcmp(); + speed_strcmp(); +#endif #ifdef CONFIG_TESTING_ARCH_LIBC_STRCPY - arch_libc_test_strcpy(); - arch_libc_strcpy_speed(); + fail += test_strcpy(); + speed_strcpy(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_STRCHR + fail += test_strchr(); + speed_strchr(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_STRNCMP + fail += test_strncmp(); + speed_strncmp(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_STRNLEN + fail += test_strnlen(); + speed_strnlen(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_STRNCPY + fail += test_strncpy(); + speed_strncpy(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_STPCPY + fail += test_stpcpy(); + speed_stpcpy(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_STRCAT + fail += test_strcat(); + speed_strcat(); +#endif +#ifdef CONFIG_TESTING_ARCH_LIBC_STRRCHR + fail += test_strrchr(); + speed_strrchr(); #endif - return 0; + printf("arch_libc_test %s\n", fail ? "Failed" : "Passed"); + return fail ? EXIT_FAILURE : EXIT_SUCCESS; } From e15b652ccf8f109436f13c0cc67486c04b4ac673 Mon Sep 17 00:00:00 2001 From: anjiahao Date: Wed, 1 Jul 2026 16:02:13 +0800 Subject: [PATCH 2/4] testing/libc/arch_libc: Add strchrnul test and sweep size boundaries. Add test_strchrnul() and speed_strchrnul(), selected by the new CONFIG_TESTING_ARCH_LIBC_STRCHRNUL option, covering the hit, miss and NUL cases. Sweep alignment 0..7 and the boundary sizes {0, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255, 256, 257} in the scan function tests (memcmp, memchr, strlen, strcmp, strchr, strncmp, strnlen, strrchr) and in memmove. Those sizes sit on the 8 and 16 byte chunk edges and on the sub-word tails, so vectorized (NEON/MVE) and word-at-a-time implementations are stressed exactly at their alignment and size boundaries instead of only at "nice" lengths. memmove is additionally exercised across four overlap layouts: forward, backward, contained and adjacent. Impact: test only, selected by CONFIG_TESTING_ARCH_LIBC (default n). Testing: built and ran qemu-armv7a:nsh (Cortex-A7, generic C implementation) and sim:nsh on Linux x86_64 (Ubuntu 24.04, gcc 13.3.0) with CONFIG_TESTING_ARCH_LIBC=y. All 16 enabled functions report PASSED and "arch_libc_test Passed". These tests pass against the generic C routines, which establishes the correctness baseline before architecture optimized assembly is introduced. Signed-off-by: anjiahao --- testing/libc/arch_libc/Kconfig | 4 + testing/libc/arch_libc/arch_libc_test_main.c | 685 ++++++++++++++----- 2 files changed, 507 insertions(+), 182 deletions(-) diff --git a/testing/libc/arch_libc/Kconfig b/testing/libc/arch_libc/Kconfig index b0aba69a5ba..97885ea40f0 100644 --- a/testing/libc/arch_libc/Kconfig +++ b/testing/libc/arch_libc/Kconfig @@ -72,6 +72,10 @@ config TESTING_ARCH_LIBC_STRRCHR bool "test strrchr" default y +config TESTING_ARCH_LIBC_STRCHRNUL + bool "test strchrnul" + default y + config TESTING_ARCH_LIBC_PROGNAME string "Program name" default "arch_libctest" diff --git a/testing/libc/arch_libc/arch_libc_test_main.c b/testing/libc/arch_libc/arch_libc_test_main.c index 67f8e6cb323..b840ab815bd 100644 --- a/testing/libc/arch_libc/arch_libc_test_main.c +++ b/testing/libc/arch_libc/arch_libc_test_main.c @@ -34,12 +34,13 @@ #include #include #include +#include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ -#define TEST_BUF_SIZE 256 +#define TEST_BUF_SIZE 512 #define TEST_REPEAT 100 #define MAX_ALIGN 16 @@ -51,6 +52,18 @@ static char g_buf1[TEST_BUF_SIZE + MAX_ALIGN]; static char g_buf2[TEST_BUF_SIZE + MAX_ALIGN]; static volatile uintptr_t g_sink; +/* Boundary sizes that exercise 8-byte and 16-byte chunk edges and sub-word + * tails, so that vectorized (NEON/MVE) and word-at-a-time paths are stressed + * at their alignment and size boundaries. Unused if every test that sweeps + * these sizes is disabled. + */ + +static const int unused_data g_boundary_sizes[] = +{ + 0, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, + 63, 64, 65, 127, 128, 129, 255, 256, 257 +}; + /**************************************************************************** * Private Functions ****************************************************************************/ @@ -121,36 +134,77 @@ static void speed_memcpy(void) #ifdef CONFIG_TESTING_ARCH_LIBC_MEMMOVE static int test_memmove(void) { + int align; + int si; int size; int fail = 0; printf("Testing memmove...\n"); - /* Test overlapping forward */ + /* Sweep alignment 0..7 and boundary sizes, across four overlap layouts: + * forward : dst = src + 8 (dst > src, backward copy internally) + * backward : dst = src - 8 (dst < src, forward copy internally) + * contained: dst = src + size/2 (full overlap region) + * adjacent : dst = src + size (no overlap, tail-to-tail) + */ - for (size = 1; size <= 64; size++) + for (align = 0; align < 8; align++) { - fill_pattern(g_buf1, size + 16); - memcpy(g_buf2, g_buf1, size + 16); - memmove(g_buf1 + 8, g_buf1, size); - if (memcmp(g_buf1 + 8, g_buf2, size) != 0) + for (si = 0; si < nitems(g_boundary_sizes); si++) { - printf(" FAIL forward: size=%d\n", size); - fail++; - } - } + size = g_boundary_sizes[si]; + if (size < 1) + { + continue; + } - /* Test overlapping backward */ + /* Forward overlap: dst = src + 8 */ - for (size = 1; size <= 64; size++) - { - fill_pattern(g_buf1 + 8, size); - memcpy(g_buf2, g_buf1 + 8, size); - memmove(g_buf1, g_buf1 + 8, size); - if (memcmp(g_buf1, g_buf2, size) != 0) - { - printf(" FAIL backward: size=%d\n", size); - fail++; + fill_pattern(g_buf1 + align + 8, size); + memcpy(g_buf2 + align + 8, g_buf1 + align + 8, size); + memmove(g_buf1 + align + 16, g_buf1 + align + 8, size); + if (memcmp(g_buf1 + align + 16, g_buf2 + align + 8, size) != 0) + { + printf(" FAIL forward: align=%d size=%d\n", align, size); + fail++; + } + + /* Backward overlap: dst = src - 8 */ + + fill_pattern(g_buf1 + align + 16, size); + memcpy(g_buf2 + align + 16, g_buf1 + align + 16, size); + memmove(g_buf1 + align + 8, g_buf1 + align + 16, size); + if (memcmp(g_buf1 + align + 8, g_buf2 + align + 16, size) != 0) + { + printf(" FAIL backward: align=%d size=%d\n", align, size); + fail++; + } + + /* Contained overlap: dst = src + size/2 */ + + fill_pattern(g_buf1 + align + 32, size); + memcpy(g_buf2 + align + 32, g_buf1 + align + 32, size); + memmove(g_buf1 + align + 32 + size / 2, + g_buf1 + align + 32, size); + if (memcmp(g_buf1 + align + 32 + size / 2, + g_buf2 + align + 32, size) != 0) + { + printf(" FAIL contained: align=%d size=%d\n", align, size); + fail++; + } + + /* Adjacent (no overlap): dst = src + size */ + + fill_pattern(g_buf1 + align + 64, size); + memcpy(g_buf2 + align + 64, g_buf1 + align + 64, size); + memmove(g_buf1 + align + 64 + size, + g_buf1 + align + 64, size); + if (memcmp(g_buf1 + align + 64 + size, + g_buf2 + align + 64, size) != 0) + { + printf(" FAIL adjacent: align=%d size=%d\n", align, size); + fail++; + } } } @@ -238,32 +292,59 @@ static void speed_memset(void) #ifdef CONFIG_TESTING_ARCH_LIBC_MEMCMP static int test_memcmp(void) { + int align; + int si; int size; int fail = 0; printf("Testing memcmp...\n"); - for (size = 1; size <= 128; size++) + for (align = 0; align < 8; align++) { - fill_pattern(g_buf1, size); - fill_pattern(g_buf2, size); - if (memcmp(g_buf1, g_buf2, size) != 0) + for (si = 0; si < nitems(g_boundary_sizes); si++) { - printf(" FAIL equal: size=%d\n", size); - fail++; - } + size = g_boundary_sizes[si]; + if (size < 1) + { + continue; + } - g_buf2[size - 1] = '~'; - if (memcmp(g_buf1, g_buf2, size) >= 0) - { - printf(" FAIL less: size=%d\n", size); - fail++; - } + fill_pattern(g_buf1 + align, size); + fill_pattern(g_buf2 + align, size); - g_buf2[size - 1] = 0; - if (memcmp(g_buf1, g_buf2, size) <= 0) - { - printf(" FAIL greater: size=%d\n", size); - fail++; + /* Equal */ + + if (memcmp(g_buf1 + align, g_buf2 + align, size) != 0) + { + printf(" FAIL equal: align=%d size=%d\n", align, size); + fail++; + } + + /* Last byte less */ + + g_buf2[align + size - 1] = '~'; + if (memcmp(g_buf1 + align, g_buf2 + align, size) >= 0) + { + printf(" FAIL less: align=%d size=%d\n", align, size); + fail++; + } + + /* Last byte greater */ + + g_buf2[align + size - 1] = 0; + if (memcmp(g_buf1 + align, g_buf2 + align, size) <= 0) + { + printf(" FAIL greater: align=%d size=%d\n", align, size); + fail++; + } + + /* First byte differs */ + + g_buf2[align] = g_buf1[align] + 1; + if (memcmp(g_buf1 + align, g_buf2 + align, size) >= 0) + { + printf(" FAIL first: align=%d size=%d\n", align, size); + fail++; + } } } @@ -298,31 +379,68 @@ static void speed_memcmp(void) #ifdef CONFIG_TESTING_ARCH_LIBC_MEMCHR static int test_memchr(void) { + int align; + int si; int size; int fail = 0; FAR void *p; printf("Testing memchr...\n"); - for (size = 1; size <= 128; size++) + for (align = 0; align < 8; align++) { - fill_pattern(g_buf1, size); + for (si = 0; si < nitems(g_boundary_sizes); si++) + { + size = g_boundary_sizes[si]; + if (size < 1) + { + continue; + } - /* Find last char */ + fill_pattern(g_buf1 + align, size); - p = memchr(g_buf1, g_buf1[size - 1], size); - if (p == NULL) - { - printf(" FAIL found: size=%d\n", size); - fail++; - } + /* Find first char (pattern starts with 'A' at align) */ - /* Not found */ + p = memchr(g_buf1 + align, g_buf1[align], size); + if (p != g_buf1 + align) + { + printf(" FAIL first: align=%d size=%d\n", align, size); + fail++; + } - p = memchr(g_buf1, 0xff, size); - if (p != NULL) - { - printf(" FAIL notfound: size=%d\n", size); - fail++; + /* Find a unique marker placed at the last position */ + + fill_pattern(g_buf1 + align, size); + g_buf1[align + size - 1] = 0x7e; + p = memchr(g_buf1 + align, 0x7e, size); + if (p != g_buf1 + align + size - 1) + { + printf(" FAIL last: align=%d size=%d\n", align, size); + fail++; + } + + /* Find a unique marker placed in the middle */ + + if (size > 2) + { + fill_pattern(g_buf1 + align, size); + g_buf1[align + size / 2] = 0x7e; + p = memchr(g_buf1 + align, 0x7e, size); + if (p != g_buf1 + align + size / 2) + { + printf(" FAIL mid: align=%d size=%d\n", align, size); + fail++; + } + } + + /* Not found (full scan) */ + + fill_pattern(g_buf1 + align, size); + p = memchr(g_buf1 + align, 0xff, size); + if (p != NULL) + { + printf(" FAIL notfound: align=%d size=%d\n", align, size); + fail++; + } } } @@ -356,18 +474,25 @@ static void speed_memchr(void) #ifdef CONFIG_TESTING_ARCH_LIBC_STRLEN static int test_strlen(void) { + int align; + int si; int size; int fail = 0; printf("Testing strlen...\n"); - for (size = 0; size <= 128; size++) + for (align = 0; align < 8; align++) { - fill_pattern(g_buf1, size); - g_buf1[size] = '\0'; - if ((int)strlen(g_buf1) != size) + for (si = 0; si < nitems(g_boundary_sizes); si++) { - printf(" FAIL: size=%d got=%d\n", size, (int)strlen(g_buf1)); - fail++; + size = g_boundary_sizes[si]; + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; + if ((int)strlen(g_buf1 + align) != size) + { + printf(" FAIL: align=%d size=%d got=%d\n", align, size, + (int)strlen(g_buf1 + align)); + fail++; + } } } @@ -402,28 +527,54 @@ static void speed_strlen(void) #ifdef CONFIG_TESTING_ARCH_LIBC_STRCMP static int test_strcmp(void) { + int align; + int si; int size; int fail = 0; printf("Testing strcmp...\n"); - for (size = 1; size <= 128; size++) + for (align = 0; align < 8; align++) { - fill_pattern(g_buf1, size); - g_buf1[size] = '\0'; - fill_pattern(g_buf2, size); - g_buf2[size] = '\0'; - if (strcmp(g_buf1, g_buf2) != 0) + for (si = 0; si < nitems(g_boundary_sizes); si++) { - printf(" FAIL equal: size=%d\n", size); - fail++; - } + size = g_boundary_sizes[si]; + if (size < 1) + { + continue; + } - g_buf2[size - 1] = '~'; - g_buf2[size] = '\0'; - if (strcmp(g_buf1, g_buf2) >= 0) - { - printf(" FAIL less: size=%d\n", size); - fail++; + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; + fill_pattern(g_buf2 + align, size); + g_buf2[align + size] = '\0'; + + /* Equal */ + + if (strcmp(g_buf1 + align, g_buf2 + align) != 0) + { + printf(" FAIL equal: align=%d size=%d\n", align, size); + fail++; + } + + /* Last byte less */ + + g_buf2[align + size - 1] = '~'; + if (strcmp(g_buf1 + align, g_buf2 + align) >= 0) + { + printf(" FAIL less: align=%d size=%d\n", align, size); + fail++; + } + + /* First byte differs */ + + fill_pattern(g_buf2 + align, size); + g_buf2[align + size] = '\0'; + g_buf2[align] = g_buf1[align] + 1; + if (strcmp(g_buf1 + align, g_buf2 + align) >= 0) + { + printf(" FAIL first: align=%d size=%d\n", align, size); + fail++; + } } } @@ -512,41 +663,66 @@ static void speed_strcpy(void) #ifdef CONFIG_TESTING_ARCH_LIBC_STRCHR static int test_strchr(void) { + int align; + int si; int size; int fail = 0; FAR char *p; printf("Testing strchr...\n"); - for (size = 1; size <= 128; size++) + for (align = 0; align < 8; align++) { - fill_pattern(g_buf1, size); - g_buf1[size] = '\0'; + for (si = 0; si < nitems(g_boundary_sizes); si++) + { + size = g_boundary_sizes[si]; + if (size < 1) + { + continue; + } - /* Find first char */ + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; - p = strchr(g_buf1, g_buf1[0]); - if (p != g_buf1) - { - printf(" FAIL first: size=%d\n", size); - fail++; - } + /* Find first char (pattern starts with 'A' at align) */ - /* Find NUL */ + p = strchr(g_buf1 + align, g_buf1[align]); + if (p != g_buf1 + align) + { + printf(" FAIL first: align=%d size=%d\n", align, size); + fail++; + } - p = strchr(g_buf1, '\0'); - if (p != g_buf1 + size) - { - printf(" FAIL nul: size=%d\n", size); - fail++; - } + /* Find a unique marker placed at the last position */ - /* Not found */ + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; + g_buf1[align + size - 1] = 0x7e; + p = strchr(g_buf1 + align, 0x7e); + if (p != g_buf1 + align + size - 1) + { + printf(" FAIL last: align=%d size=%d\n", align, size); + fail++; + } - p = strchr(g_buf1, 0x01); - if (p != NULL) - { - printf(" FAIL notfound: size=%d\n", size); - fail++; + /* Find NUL terminator */ + + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; + p = strchr(g_buf1 + align, '\0'); + if (p != g_buf1 + align + size) + { + printf(" FAIL nul: align=%d size=%d\n", align, size); + fail++; + } + + /* Not found (full scan) */ + + p = strchr(g_buf1 + align, 0x01); + if (p != NULL) + { + printf(" FAIL notfound: align=%d size=%d\n", align, size); + fail++; + } } } @@ -581,51 +757,72 @@ static void speed_strchr(void) #ifdef CONFIG_TESTING_ARCH_LIBC_STRNCMP static int test_strncmp(void) { + int align; + int si; int size; int fail = 0; printf("Testing strncmp...\n"); - for (size = 1; size <= 128; size++) + for (align = 0; align < 8; align++) { - fill_pattern(g_buf1, size); - g_buf1[size] = '\0'; - fill_pattern(g_buf2, size); - g_buf2[size] = '\0'; + for (si = 0; si < nitems(g_boundary_sizes); si++) + { + size = g_boundary_sizes[si]; + if (size < 1) + { + continue; + } - /* Equal within n */ + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; + fill_pattern(g_buf2 + align, size); + g_buf2[align + size] = '\0'; - if (strncmp(g_buf1, g_buf2, size) != 0) - { - printf(" FAIL equal: size=%d\n", size); - fail++; - } + /* Equal within n */ - /* Differ at last position */ + if (strncmp(g_buf1 + align, g_buf2 + align, size) != 0) + { + printf(" FAIL equal: align=%d size=%d\n", align, size); + fail++; + } - g_buf2[size - 1] = '~'; - if (strncmp(g_buf1, g_buf2, size) >= 0) - { - printf(" FAIL less: size=%d\n", size); - fail++; - } + /* Differ at last position */ - /* Equal when n is smaller */ + g_buf2[align + size - 1] = '~'; + if (strncmp(g_buf1 + align, g_buf2 + align, size) >= 0) + { + printf(" FAIL less: align=%d size=%d\n", align, size); + fail++; + } - fill_pattern(g_buf2, size); - g_buf2[size] = '\0'; - g_buf2[size - 1] = '~'; - if (size > 1 && strncmp(g_buf1, g_buf2, size - 1) != 0) - { - printf(" FAIL partial: size=%d\n", size); - fail++; - } + /* Equal when n is smaller than the difference */ + + fill_pattern(g_buf2 + align, size); + g_buf2[align + size] = '\0'; + g_buf2[align + size - 1] = '~'; + if (size > 1 && + strncmp(g_buf1 + align, g_buf2 + align, size - 1) != 0) + { + printf(" FAIL partial: align=%d size=%d\n", align, size); + fail++; + } - /* Zero count */ + /* n boundary: 0 and 1 (reset buf2 to match buf1 first) */ - if (strncmp(g_buf1, g_buf2, 0) != 0) - { - printf(" FAIL zero: size=%d\n", size); - fail++; + fill_pattern(g_buf2 + align, size); + g_buf2[align + size] = '\0'; + + if (strncmp(g_buf1 + align, g_buf2 + align, 0) != 0) + { + printf(" FAIL zero: align=%d size=%d\n", align, size); + fail++; + } + + if (strncmp(g_buf1 + align, g_buf2 + align, 1) != 0) + { + printf(" FAIL one: align=%d size=%d\n", align, size); + fail++; + } } } @@ -662,37 +859,52 @@ static void speed_strncmp(void) #ifdef CONFIG_TESTING_ARCH_LIBC_STRNLEN static int test_strnlen(void) { + int align; + int si; int size; int fail = 0; printf("Testing strnlen...\n"); - for (size = 0; size <= 128; size++) + for (align = 0; align < 8; align++) { - fill_pattern(g_buf1, size); - g_buf1[size] = '\0'; + for (si = 0; si < nitems(g_boundary_sizes); si++) + { + size = g_boundary_sizes[si]; + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; - /* maxlen >= actual length */ + /* maxlen >= actual length */ - if ((int)strnlen(g_buf1, size + 10) != size) - { - printf(" FAIL full: size=%d\n", size); - fail++; - } + if ((int)strnlen(g_buf1 + align, size + 10) != size) + { + printf(" FAIL full: align=%d size=%d\n", align, size); + fail++; + } - /* maxlen < actual length */ + /* maxlen == actual length */ - if (size > 0 && (int)strnlen(g_buf1, size - 1) != size - 1) - { - printf(" FAIL trunc: size=%d\n", size); - fail++; - } + if ((int)strnlen(g_buf1 + align, size) != size) + { + printf(" FAIL exact: align=%d size=%d\n", align, size); + fail++; + } - /* maxlen == 0 */ + /* maxlen < actual length */ - if (strnlen(g_buf1, 0) != 0) - { - printf(" FAIL zero: size=%d\n", size); - fail++; + if (size > 0 && + (int)strnlen(g_buf1 + align, size - 1) != size - 1) + { + printf(" FAIL trunc: align=%d size=%d\n", align, size); + fail++; + } + + /* maxlen == 0 */ + + if (strnlen(g_buf1 + align, 0) != 0) + { + printf(" FAIL zero: align=%d size=%d\n", align, size); + fail++; + } } } @@ -955,53 +1167,65 @@ static void speed_strcat(void) #ifdef CONFIG_TESTING_ARCH_LIBC_STRRCHR static int test_strrchr(void) { + int align; + int si; int size; int fail = 0; FAR char *p; printf("Testing strrchr...\n"); - for (size = 1; size <= 128; size++) + for (align = 0; align < 8; align++) { - fill_pattern(g_buf1, size); - g_buf1[size] = '\0'; + for (si = 0; si < nitems(g_boundary_sizes); si++) + { + size = g_boundary_sizes[si]; + if (size < 1) + { + continue; + } - /* Find last occurrence of first char (repeats every 26) */ + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; - p = strrchr(g_buf1, 'A'); - if (p == NULL) - { - printf(" FAIL found: size=%d\n", size); - fail++; - } - else - { - /* 'A' appears at 0, 26, 52, ... — last one <= size-1 */ + /* Find last occurrence of first char (repeats every 26) */ - int expected = ((size - 1) / 26) * 26; - if (p != g_buf1 + expected) + p = strrchr(g_buf1 + align, 'A'); + if (p == NULL) { - printf(" FAIL pos: size=%d expected=%d got=%d\n", - size, expected, (int)(p - g_buf1)); + printf(" FAIL found: align=%d size=%d\n", align, size); fail++; } - } + else + { + /* 'A' appears at 0, 26, 52, ... — last one <= size-1 */ - /* Find NUL */ + int expected = ((size - 1) / 26) * 26; + if (p != g_buf1 + align + expected) + { + printf(" FAIL pos: align=%d size=%d expected=%d" + " got=%d\n", align, size, expected, + (int)(p - (g_buf1 + align))); + fail++; + } + } - p = strrchr(g_buf1, '\0'); - if (p != g_buf1 + size) - { - printf(" FAIL nul: size=%d\n", size); - fail++; - } + /* Find NUL */ + + p = strrchr(g_buf1 + align, '\0'); + if (p != g_buf1 + align + size) + { + printf(" FAIL nul: align=%d size=%d\n", align, size); + fail++; + } - /* Not found */ + /* Not found (full scan) */ - p = strrchr(g_buf1, 0x01); - if (p != NULL) - { - printf(" FAIL notfound: size=%d\n", size); - fail++; + p = strrchr(g_buf1 + align, 0x01); + if (p != NULL) + { + printf(" FAIL notfound: align=%d size=%d\n", align, size); + fail++; + } } } @@ -1029,6 +1253,100 @@ static void speed_strrchr(void) } #endif +/**************************************************************************** + * Name: test_strchrnul + ****************************************************************************/ + +#ifdef CONFIG_TESTING_ARCH_LIBC_STRCHRNUL +static int test_strchrnul(void) +{ + int align; + int si; + int size; + int fail = 0; + FAR char *p; + + printf("Testing strchrnul...\n"); + for (align = 0; align < 8; align++) + { + for (si = 0; si < nitems(g_boundary_sizes); si++) + { + size = g_boundary_sizes[si]; + if (size < 1) + { + continue; + } + + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; + + /* Find first char (pattern starts with 'A' at align) */ + + p = strchrnul(g_buf1 + align, g_buf1[align]); + if (p != g_buf1 + align) + { + printf(" FAIL first: align=%d size=%d\n", align, size); + fail++; + } + + /* Find a unique marker placed at the last position */ + + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; + g_buf1[align + size - 1] = 0x7e; + p = strchrnul(g_buf1 + align, 0x7e); + if (p != g_buf1 + align + size - 1) + { + printf(" FAIL last: align=%d size=%d\n", align, size); + fail++; + } + + /* Find NUL terminator (always succeeds) */ + + fill_pattern(g_buf1 + align, size); + g_buf1[align + size] = '\0'; + p = strchrnul(g_buf1 + align, '\0'); + if (p != g_buf1 + align + size) + { + printf(" FAIL nul: align=%d size=%d\n", align, size); + fail++; + } + + /* Not found: must return pointer to the NUL terminator */ + + p = strchrnul(g_buf1 + align, 0x01); + if (p != g_buf1 + align + size) + { + printf(" FAIL notfound: align=%d size=%d\n", align, size); + fail++; + } + } + } + + printf("strchrnul: %s\n", fail ? "FAILED" : "PASSED"); + return fail; +} + +static void speed_strchrnul(void) +{ + clock_t start; + clock_t end; + int i; + + fill_pattern(g_buf1, 128); + g_buf1[128] = '\0'; + start = perf_gettime(); + for (i = 0; i < TEST_REPEAT; i++) + { + g_sink = (uintptr_t)strchrnul(g_buf1, g_buf1[127]); + } + + end = perf_gettime(); + printf("strchrnul(128) avg cycles: %ju\n", + (uintmax_t)(end - start) / TEST_REPEAT); +} +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -1101,8 +1419,11 @@ int main(int argc, FAR char *argv[]) fail += test_strrchr(); speed_strrchr(); #endif +#ifdef CONFIG_TESTING_ARCH_LIBC_STRCHRNUL + fail += test_strchrnul(); + speed_strchrnul(); +#endif printf("arch_libc_test %s\n", fail ? "Failed" : "Passed"); return fail ? EXIT_FAILURE : EXIT_SUCCESS; } - From d7648776d7fa4b1f0b548e08fba766b6b6cc46e9 Mon Sep 17 00:00:00 2001 From: dengwenqi Date: Mon, 6 Jul 2026 21:59:03 +0800 Subject: [PATCH 3/4] testing/libc/arch_libc: Fix out-of-bounds write in memmove test. The adjacent overlap case in test_memmove() placed the source at a fixed g_buf1 + align + 64 and the destination one size further, so the destination tail reached align + 64 + 2 * size. g_buf1 is only TEST_BUF_SIZE + MAX_ALIGN (528) bytes, so the larger swept sizes ran off the end: align=0 with size=255 writes up to offset 573, that is 46 bytes past the object. AddressSanitizer aborted arch_libctest with a global-buffer-overflow. Start the adjacent layout at g_buf1 + align instead. The tail then reaches align + 2 * size, which is at most 7 + 2 * 257 = 521 and stays inside g_buf1 for every alignment and boundary size that is swept, while still keeping source and destination exactly adjacent. Impact: test only, selected by CONFIG_TESTING_ARCH_LIBC (default n). Testing: built and ran sim:nsh on Linux x86_64 (Ubuntu 24.04, gcc 13.3.0) with CONFIG_TESTING_ARCH_LIBC=y. memmove reports PASSED with no sanitizer report, and "arch_libc_test Passed". Assisted-by: Claude:claude-opus-5 Signed-off-by: dengwenqi --- testing/libc/arch_libc/arch_libc_test_main.c | 23 +++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/testing/libc/arch_libc/arch_libc_test_main.c b/testing/libc/arch_libc/arch_libc_test_main.c index b840ab815bd..f1ff3dc05f3 100644 --- a/testing/libc/arch_libc/arch_libc_test_main.c +++ b/testing/libc/arch_libc/arch_libc_test_main.c @@ -193,14 +193,21 @@ static int test_memmove(void) fail++; } - /* Adjacent (no overlap): dst = src + size */ - - fill_pattern(g_buf1 + align + 64, size); - memcpy(g_buf2 + align + 64, g_buf1 + align + 64, size); - memmove(g_buf1 + align + 64 + size, - g_buf1 + align + 64, size); - if (memcmp(g_buf1 + align + 64 + size, - g_buf2 + align + 64, size) != 0) + /* Adjacent (no overlap): dst = src + size. + * The destination tail reaches align + 2*size, so the base + * offset must satisfy align + 2*size <= sizeof(g_buf1); a + * fixed +64 base overflows g_buf1 for the larger boundary + * sizes (e.g. size=255, align=0 writes 45 bytes past the + * end), which AddressSanitizer flags as a global-buffer- + * overflow. Start from g_buf1 + align instead. + */ + + fill_pattern(g_buf1 + align, size); + memcpy(g_buf2 + align, g_buf1 + align, size); + memmove(g_buf1 + align + size, + g_buf1 + align, size); + if (memcmp(g_buf1 + align + size, + g_buf2 + align, size) != 0) { printf(" FAIL adjacent: align=%d size=%d\n", align, size); fail++; From 3526d7b05fbbd497901856b003b03ba7aee94a5a Mon Sep 17 00:00:00 2001 From: zhangyuan29 Date: Wed, 8 Jul 2026 15:52:54 +0800 Subject: [PATCH 4/4] testing/libc/arch_libc: Cover unaligned src/dst copy paths. test_strcpy(), test_strncpy() and test_stpcpy() applied the same offset to the source and to the destination, so both pointers always shared the same word congruence. Architecture optimized copy routines take a different code path when the two offsets differ: a byte prologue to align the destination, then either a byte fallback or a shift-merge loop that recombines two source words per store. None of that was reached by the test. Vary the source and destination offsets independently over 0..7 in those three tests, so both the equal congruence (aligned word copy) and the unequal congruence (shift-merge) paths are covered, and report both offsets on failure so a regression points at the offending combination. Also drop the ARCH_TOOLCHAIN_GNU dependency from TESTING_ARCH_LIBC. The test only uses standard C string functions and perf_gettime(), with no GNU specific construct, so it builds with non GNU toolchains such as TASKING as well. Impact: test only, selected by CONFIG_TESTING_ARCH_LIBC (default n). Dropping the ARCH_TOOLCHAIN_GNU dependency only widens the set of toolchains that may select the test, no existing configuration changes. Testing: built and ran sim:nsh on Linux x86_64 (Ubuntu 24.04, gcc 13.3.0) with CONFIG_TESTING_ARCH_LIBC=y. strcpy, strncpy and stpcpy report PASSED for all 64 offset combinations, and "arch_libc_test Passed". Assisted-by: Claude:claude-opus-5 Signed-off-by: zhangyuan29 --- testing/libc/arch_libc/Kconfig | 1 - testing/libc/arch_libc/arch_libc_test_main.c | 86 +++++++++++++------- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/testing/libc/arch_libc/Kconfig b/testing/libc/arch_libc/Kconfig index 97885ea40f0..05ccacdd88b 100644 --- a/testing/libc/arch_libc/Kconfig +++ b/testing/libc/arch_libc/Kconfig @@ -6,7 +6,6 @@ config TESTING_ARCH_LIBC tristate "arch-specific libc function test" default n - depends on ARCH_TOOLCHAIN_GNU ---help--- Enable the arch libc test diff --git a/testing/libc/arch_libc/arch_libc_test_main.c b/testing/libc/arch_libc/arch_libc_test_main.c index f1ff3dc05f3..1a5e8bbf1db 100644 --- a/testing/libc/arch_libc/arch_libc_test_main.c +++ b/testing/libc/arch_libc/arch_libc_test_main.c @@ -618,22 +618,32 @@ static void speed_strcmp(void) #ifdef CONFIG_TESTING_ARCH_LIBC_STRCPY static int test_strcpy(void) { - int align; int size; int fail = 0; + int ai; printf("Testing strcpy...\n"); - for (align = 0; align < 8; align++) + + /* ai encodes da*8+sa: sa==da keeps src/dst in the same 4-byte congruence + * (aligned word-copy path), sa!=da forces different congruence and + * exercises the shift-merge path; any nonzero da also hits the dst byte + * prologue. + */ + + for (ai = 0; ai < 64; ai++) { + int da = ai / 8; + int sa = ai % 8; + for (size = 1; size <= 64; size++) { - fill_pattern(g_buf1 + align, size); - g_buf1[align + size] = '\0'; + fill_pattern(g_buf1 + sa, size); + g_buf1[sa + size] = '\0'; memset(g_buf2, 0, sizeof(g_buf2)); - strcpy(g_buf2 + align, g_buf1 + align); - if (strcmp(g_buf2 + align, g_buf1 + align) != 0) + strcpy(g_buf2 + da, g_buf1 + sa); + if (strcmp(g_buf2 + da, g_buf1 + sa) != 0) { - printf(" FAIL: align=%d size=%d\n", align, size); + printf(" FAIL: sa=%d da=%d size=%d\n", sa, da, size); fail++; } } @@ -946,26 +956,34 @@ static void speed_strnlen(void) #ifdef CONFIG_TESTING_ARCH_LIBC_STRNCPY static int test_strncpy(void) { - int align; int size; int i; int fail = 0; + int ai; printf("Testing strncpy...\n"); - for (align = 0; align < 8; align++) + + /* ai encodes da*8+sa so src/dst offsets vary independently: sa!=da forces + * different congruence and exercises the shift-merge path. + */ + + for (ai = 0; ai < 64; ai++) { + int da = ai / 8; + int sa = ai % 8; + for (size = 1; size <= 64; size++) { - fill_pattern(g_buf1 + align, size); - g_buf1[align + size] = '\0'; + fill_pattern(g_buf1 + sa, size); + g_buf1[sa + size] = '\0'; /* n > strlen: should copy and zero-fill */ memset(g_buf2, 0xaa, sizeof(g_buf2)); - strncpy(g_buf2 + align, g_buf1 + align, size + 4); - if (strcmp(g_buf2 + align, g_buf1 + align) != 0) + strncpy(g_buf2 + da, g_buf1 + sa, size + 4); + if (strcmp(g_buf2 + da, g_buf1 + sa) != 0) { - printf(" FAIL copy: align=%d size=%d\n", align, size); + printf(" FAIL copy: sa=%d da=%d size=%d\n", sa, da, size); fail++; } @@ -973,9 +991,10 @@ static int test_strncpy(void) for (i = 0; i < 4; i++) { - if (g_buf2[align + size + i] != '\0') + if (g_buf2[da + size + i] != '\0') { - printf(" FAIL zfill: align=%d size=%d\n", align, size); + printf(" FAIL zfill: sa=%d da=%d size=%d\n", + sa, da, size); fail++; break; } @@ -986,10 +1005,11 @@ static int test_strncpy(void) if (size > 1) { memset(g_buf2, 0xaa, sizeof(g_buf2)); - strncpy(g_buf2 + align, g_buf1 + align, size - 1); - if (memcmp(g_buf2 + align, g_buf1 + align, size - 1) != 0) + strncpy(g_buf2 + da, g_buf1 + sa, size - 1); + if (memcmp(g_buf2 + da, g_buf1 + sa, size - 1) != 0) { - printf(" FAIL trunc: align=%d size=%d\n", align, size); + printf(" FAIL trunc: sa=%d da=%d size=%d\n", + sa, da, size); fail++; } } @@ -1027,40 +1047,48 @@ static void speed_strncpy(void) #ifdef CONFIG_TESTING_ARCH_LIBC_STPCPY static int test_stpcpy(void) { - int align; int size; int fail = 0; + int ai; FAR char *p; printf("Testing stpcpy...\n"); - for (align = 0; align < 8; align++) + + /* ai encodes da*8+sa so src/dst offsets vary independently: sa!=da forces + * different congruence and exercises the shift-merge path. + */ + + for (ai = 0; ai < 64; ai++) { + int da = ai / 8; + int sa = ai % 8; + for (size = 1; size <= 64; size++) { - fill_pattern(g_buf1 + align, size); - g_buf1[align + size] = '\0'; + fill_pattern(g_buf1 + sa, size); + g_buf1[sa + size] = '\0'; memset(g_buf2, 0, sizeof(g_buf2)); - p = stpcpy(g_buf2 + align, g_buf1 + align); + p = stpcpy(g_buf2 + da, g_buf1 + sa); /* Check content */ - if (strcmp(g_buf2 + align, g_buf1 + align) != 0) + if (strcmp(g_buf2 + da, g_buf1 + sa) != 0) { - printf(" FAIL copy: align=%d size=%d\n", align, size); + printf(" FAIL copy: sa=%d da=%d size=%d\n", sa, da, size); fail++; } /* Check return value points to NUL */ - if (p != g_buf2 + align + size) + if (p != g_buf2 + da + size) { - printf(" FAIL retval: align=%d size=%d\n", align, size); + printf(" FAIL retval: sa=%d da=%d size=%d\n", sa, da, size); fail++; } if (*p != '\0') { - printf(" FAIL nul: align=%d size=%d\n", align, size); + printf(" FAIL nul: sa=%d da=%d size=%d\n", sa, da, size); fail++; } }