From d23deaa32afe9937a69ece5f25d7538d5783ec88 Mon Sep 17 00:00:00 2001 From: huntedbytheirs Date: Mon, 31 Aug 2026 21:14:49 -0400 Subject: [PATCH] feat(string): implement core string/memory functions (level 1) Word-at-a-time memcpy/memmove/memset/strlen/strcmp implementing the level-1 string slice. memcpy carries an optimize(no-tree-loop- distribute-patterns) attribute: GCC's loop-distribution pass rewrites its word-copy loop into a self-recursive memcpy@plt call at -O2/-O3 otherwise. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/string/memcpy.c | 53 +++++++++++++++++++++++++++++ src/string/memmove.c | 71 +++++++++++++++++++++++++++++++++++++++ src/string/memset.c | 43 ++++++++++++++++++++++++ src/string/strcmp.c | 80 ++++++++++++++++++++++++++++++++++++++++++++ src/string/strlen.c | 62 ++++++++++++++++++++++++++++++++++ 5 files changed, 309 insertions(+) create mode 100644 src/string/memcpy.c create mode 100644 src/string/memmove.c create mode 100644 src/string/memset.c create mode 100644 src/string/strcmp.c create mode 100644 src/string/strlen.c diff --git a/src/string/memcpy.c b/src/string/memcpy.c new file mode 100644 index 0000000..5e8fe5c --- /dev/null +++ b/src/string/memcpy.c @@ -0,0 +1,53 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +/* + * Copy n bytes from src to dst. The regions must not overlap (restrict). + * Word-at-a-time: copy whole unsigned long words once dst is aligned, with + * a byte head and tail. Word loads and stores go through __builtin_memcpy + * so unaligned src is handled without undefined behavior. + * + * The word-copy loop below is the canonical memcpy idiom; GCC's + * -ftree-loop-distribute-patterns (on by default at -O2 and -O3) rewrites + * it into a call to memcpy() — this very function — causing infinite + * self-recursion and stack overflow. Disable that one transformation for + * this function only. + */ +__attribute__((optimize("no-tree-loop-distribute-patterns"))) +void * +memcpy(void *restrict dst, const void *restrict src, size_t n) +{ + const unsigned long word = sizeof(unsigned long); + unsigned char *d = dst; + const unsigned char *s = src; + + /* Copy the unaligned head byte-wise. */ + for (; (unsigned long)d % word != 0 && n != 0; n--) + { + *d++ = *s++; + } + + /* Copy whole words. */ + while (n >= word) + { + unsigned long w; + + __builtin_memcpy(&w, s, sizeof w); + __builtin_memcpy(d, &w, sizeof w); + d += word; + s += word; + n -= word; + } + + /* Copy the remaining tail byte-wise. */ + while (n != 0) + { + *d++ = *s++; + n--; + } + + return dst; +} diff --git a/src/string/memmove.c b/src/string/memmove.c new file mode 100644 index 0000000..86645e2 --- /dev/null +++ b/src/string/memmove.c @@ -0,0 +1,71 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +/* + * Copy n bytes from src to dst. The regions may overlap, so the copy + * direction is chosen by the relative positions: forward when dst starts + * at or before src, or at or after the end of src; backward when dst + * starts inside src. Word-at-a-time in both directions, with byte heads + * and tails; word loads and stores go through __builtin_memcpy. + */ +void * +memmove(void *dst, const void *src, size_t n) +{ + const unsigned long word = sizeof(unsigned long); + unsigned char *d = dst; + const unsigned char *s = src; + + if (d <= s || d >= s + n) + { + /* Forward copy. */ + for (; (unsigned long)d % word != 0 && n != 0; n--) + { + *d++ = *s++; + } + while (n >= word) + { + unsigned long w; + + __builtin_memcpy(&w, s, sizeof w); + __builtin_memcpy(d, &w, sizeof w); + d += word; + s += word; + n -= word; + } + while (n != 0) + { + *d++ = *s++; + n--; + } + } + else + { + /* Backward copy, starting from the last byte. */ + d += n; + s += n; + for (; (unsigned long)d % word != 0 && n != 0; n--) + { + *--d = *--s; + } + while (n >= word) + { + unsigned long w; + + d -= word; + s -= word; + __builtin_memcpy(&w, s, sizeof w); + __builtin_memcpy(d, &w, sizeof w); + n -= word; + } + while (n != 0) + { + *--d = *--s; + n--; + } + } + + return dst; +} diff --git a/src/string/memset.c b/src/string/memset.c new file mode 100644 index 0000000..f6ad6a2 --- /dev/null +++ b/src/string/memset.c @@ -0,0 +1,43 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +/* + * Fill n bytes at dst with c (converted to unsigned char). Word-at-a-time: + * broadcast the byte into every byte of an unsigned long, align dst, fill + * whole words, then finish with a byte tail. The broadcast multiply cannot + * carry between bytes because each byte is at most 0xff. + */ +void * +memset(void *dst, int c, size_t n) +{ + const unsigned long word = sizeof(unsigned long); + const unsigned char byte = (unsigned char)c; + const unsigned long fill = (unsigned long)byte * ((unsigned long)-1 / 0xff); + unsigned char *d = dst; + + /* Fill the unaligned head byte-wise. */ + for (; (unsigned long)d % word != 0 && n != 0; n--) + { + *d++ = byte; + } + + /* Fill whole words. */ + while (n >= word) + { + __builtin_memcpy(d, &fill, sizeof fill); + d += word; + n -= word; + } + + /* Fill the remaining tail byte-wise. */ + while (n != 0) + { + *d++ = byte; + n--; + } + + return dst; +} diff --git a/src/string/strcmp.c b/src/string/strcmp.c new file mode 100644 index 0000000..6badf91 --- /dev/null +++ b/src/string/strcmp.c @@ -0,0 +1,80 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +/* + * True if w contains a zero byte. Subtracting ONES from w propagates a + * borrow into the high bit of a byte exactly when that byte is zero; + * ANDing with ~w keeps only bytes that were zero and high-bit-clear in w. + * ONES and its shifted form are derived from the word width, so this works + * for both 32- and 64-bit unsigned long. + */ +static unsigned long +haszero(unsigned long w) +{ + const unsigned long ones = (unsigned long)-1 / 0xff; + + return (w - ones) & ~w & (ones << 7); +} + +/* + * Compare the strings lhs and rhs; return negative, zero, or positive when + * lhs is less than, equal to, or greater than rhs. Bytes are compared as + * unsigned char, so values at or above 0x80 sort above 0x7f. + */ +int +strcmp(const char *lhs, const char *rhs) +{ + const unsigned long word = sizeof(unsigned long); + const unsigned char *l = (const unsigned char *)lhs; + const unsigned char *r = (const unsigned char *)rhs; + + /* Compare the unaligned head, aligning l to a word boundary. */ + for (; (unsigned long)l % word != 0; l++, r++) + { + if (*l != *r) + { + return *l < *r ? -1 : 1; + } + if (*l == '\0') + { + return 0; + } + } + + if ((unsigned long)r % word == 0) + { + /* Both pointers are word-aligned: scan whole words. */ + for (;;) + { + unsigned long wl; + unsigned long wr; + + __builtin_memcpy(&wl, l, sizeof wl); + __builtin_memcpy(&wr, r, sizeof wr); + if (wl != wr || haszero(wl)) + { + break; + } + l += word; + r += word; + } + } + + /* Resolve the differing or terminating word byte-wise. */ + for (;;) + { + if (*l != *r) + { + return *l < *r ? -1 : 1; + } + if (*l == '\0') + { + return 0; + } + l++; + r++; + } +} diff --git a/src/string/strlen.c b/src/string/strlen.c new file mode 100644 index 0000000..176316e --- /dev/null +++ b/src/string/strlen.c @@ -0,0 +1,62 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +/* + * True if w contains a zero byte. Subtracting ONES from w propagates a + * borrow into the high bit of a byte exactly when that byte is zero; + * ANDing with ~w keeps only bytes that were zero and high-bit-clear in w. + * ONES and its shifted form are derived from the word width, so this works + * for both 32- and 64-bit unsigned long. + */ +static unsigned long +haszero(unsigned long w) +{ + const unsigned long ones = (unsigned long)-1 / 0xff; + + return (w - ones) & ~w & (ones << 7); +} + +/* + * Return the length of s, excluding the terminating NUL. + */ +size_t +strlen(const char *s) +{ + const unsigned long word = sizeof(unsigned long); + const char *p = s; + + /* Check the head byte-wise until p is word-aligned. */ + for (; (unsigned long)p % word != 0; p++) + { + if (*p == '\0') + { + return (size_t)(p - s); + } + } + + /* Scan whole words for a zero byte. */ + for (;;) + { + unsigned long w; + + __builtin_memcpy(&w, p, sizeof w); + if (haszero(w)) + { + break; + } + p += word; + } + + /* Resolve the terminating word byte-wise. */ + for (;;) + { + if (*p == '\0') + { + return (size_t)(p - s); + } + p++; + } +}