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 <[email protected]>
This commit is contained in:
@@ -0,0 +1,53 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user