#if 0
# From:
# https://github.com/skeeto/w64devkit/blob/master/src/libmemory.c
#
# because Clang generates calls to these, even with -fno-builtin, it is
# necessary to provide our implementation when not linking to C library.
#
# memset, memcpy, memmove, and memcmp via x86 string instructions
# Execute this source with a shell to build libmemory.a.
# This is free and unencumbered software released into the public domain.
set -e
CFLAGS="-Os -fno-builtin -fno-asynchronous-unwind-tables -fno-ident"
objects=""
for func in memset memcpy memmove memcmp strlen; do
    FUNC="$(echo $func | tr '[:lower:]' '[:upper:]')"
    objects="$objects $func.o"
    (set -x; ${CC:-cc} -c -D$FUNC -Wa,--no-pad-sections $CFLAGS -o $func.o $0)
done
rm -f "${DESTDIR}libmemory.a"
${AR:-ar} r "${DESTDIR}libmemory.a" $objects
rm $objects
exit 0
#endif

typedef __SIZE_TYPE__    size_t;
typedef __UINTPTR_TYPE__ uintptr_t;

#ifdef MEMSET
void *memset(void *dst, int c, size_t len)
{
    void *r = dst;
    asm volatile (
        "rep stosb"
        : "+D"(dst), "+c"(len)
        : "a"(c)
        : "memory"
    );
    return r;
}
#endif

#ifdef MEMCPY
void *memcpy(void *restrict dst, void *restrict src, size_t len)
{
    void *r = dst;
    asm volatile (
        "rep movsb"
        : "+D"(dst), "+S"(src), "+c"(len)
        :
        : "memory"
    );
    return r;
}
#endif

#ifdef MEMMOVE
void *memmove(void *dst, void *src, size_t len)
{
    // Use uintptr_t to bypass pointer semantics:
    // (1) comparing unrelated pointers
    // (2) pointer arithmetic on null (i.e. gracefully handle null dst/src)
    // (3) pointer overflow ("one-before-the-beginning" in reversed copy)
    uintptr_t d = (uintptr_t)dst;
    uintptr_t s = (uintptr_t)src;
    if (d > s) {
        d += len - 1;
        s += len - 1;
        asm ("std");
    }
    asm volatile (
        "rep movsb; cld"
        : "+D"(d), "+S"(s), "+c"(len)
        :
        : "memory"
    );
    return dst;
}
#endif

#ifdef MEMCMP
int memcmp(void *s1, void *s2, size_t len)
{
    // CCa "after"  == CF=0 && ZF=0
    // CCb "before" == CF=1
    int a, b;
    asm volatile (
        "xor %%eax, %%eax\n"  // CF=0, ZF=1 (i.e. CCa = CCb = 0)
        "repz cmpsb\n"
        : "+D"(s1), "+S"(s2), "+c"(len), "=@cca"(a), "=@ccb"(b)
        :
        : "ax", "memory"
    );
    return b - a;
}
#endif

#ifdef STRLEN
size_t strlen(char *s)
{
    size_t n = -1;
    asm volatile (
        "repne scasb"
        : "+D"(s), "+c"(n)
        : "a"(0)
        : "memory"
    );
    return -n - 2;
}
#endif

#ifdef TEST
// $ sh libmemory.c
// $ cc -nostdlib -fno-builtin -DTEST -g3 -O -o test libmemory.c libmemory.a
// $ gdb -ex r -ex q ./test

#define assert(c) while (!(c)) __builtin_trap()
void   *memset(void *, int, size_t);
int     memcmp(void *, void *, size_t);
void   *memcpy(void *restrict, void *restrict, size_t);
void   *memmove(void *, void *, size_t);
size_t  strlen(char *);

#if defined(__linux) && defined(__amd64)
asm ("        .global _start\n"
     "_start: call mainCRTStartup\n"
     "        mov  %eax, %edi\n"
     "        mov  $60, %eax\n"
     "        syscall\n");
#elif defined(__linux) && defined(__i386)
asm ("        .global _start\n"
     "_start: call mainCRTStartup\n"
     "        mov  %eax, %ebx\n"
     "        mov  $1, %eax\n"
     "        int  $0x80\n");
#endif

int mainCRTStartup(void)
{
    {
        char buf[12] = "............";
        memset(buf+4, 'x', 4);
        assert(!memcmp(buf, "....xxxx....", 12));
        memset(buf, 0, 12);
        assert(!memcmp(buf, (char[12]){0}, 12));
        memset(buf+8, 1, 0);
        assert(!memcmp(buf, (char[12]){0}, 12));
    }

    {
        char buf[7] = "abcdefg";
        memcpy(buf+0, buf+3, 3);
        assert(!memcmp(buf, "defdefg", 7));
        memcpy(buf+5, buf+1, 2);
        assert(!memcmp(buf, "defdeef", 7));
        memcpy(buf+1, buf+4, 0);
        assert(!memcmp(buf, "defdeef", 7));
    }

    {
        char buf[] = "abcdefgh";
        memmove(buf+0, buf+1, 7);
        assert(!memcmp(buf, "bcdefghh", 8));
        buf[7] = 0;
        memmove(buf+1, buf+0, 7);
        assert(!memcmp(buf, "bbcdefgh", 8));
        memmove(buf+2, buf+1, 0);
        assert(!memcmp(buf, "bbcdefgh", 8));
    }

    assert(memcmp("\xff", "1", 1) > 0);
    assert(memcmp("", "", 0) == 0);  // test empty after > result
    assert(memcmp("1", "\xff", 1) < 0);
    assert(memcmp("", "", 0) == 0);  // test empty after < result
    assert(memcmp("ab", "aa", 2) > 0);
    assert(memcmp("aa", "ab", 2) < 0);
    assert(memcmp("x", "y", 0) == 0);

    assert(0 == strlen(""));
    assert(1 == strlen(" "));
    assert(1 == strlen("\xff"));
    assert(5 == strlen("hello"));

    return 0;
}
#endif
