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 <zhangyuan29@xiaomi.com>
This commit is contained in:
zhangyuan29 2026-07-08 15:52:54 +08:00 committed by Alan C. Assis
parent 7043f96fef
commit 413d2ddbc3
2 changed files with 57 additions and 30 deletions

View file

@ -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

View file

@ -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++;
}
}