Skip to content

Commit

Permalink
New runtime library: libmemory
Browse files Browse the repository at this point in the history
This static library provides memset, memcpy, memmove, memcmp, and
strlen. The implementations are minimal wrappers around x86 string
instructions, making them both tiny (~26 bytes apiece) and reasonably
performant, though not nearly as fast as CRT implementations. I selected
these particular functions because GCC fabricates calls to each out of
thin air at some optimization levels. The first four are also genuinely
useful as built-ins (__builtin_memset, etc.), keeping in mind arbitrary
limitations with null.

Mingw-w64 does not implement them but instead offers imports from system
DLLs: -lmsvcrt, -lmsvcr120, -lucrt, -lntdllcrt. It creates a dependency
on a system DLL: practical, though unoptimal, and no licensing woes.
With -nostartfiles instead of -nostdlib, this happens implicitly.

MSVC provides static definitions in libvcuntime.lib, useful when cl or
clang-cl fabricates calls. They weigh several KB each, and require the
application or installer to present a EULA ("external end users to agree
to terms"). Not a great trade-off just for some basic memory functions.

In w64devkit, libmemory fills this role. It's a public domain library so
there are no license terms. Just add -lmemory to the build command. It
also allows liberal use of the associated built-ins, especially in debug
builds, which can benefit from fast memory operations despite -O0. MSVC
toolchains can also freely use libmemory.a, though it won't know how to
find it without help of course.

I do not plan to add more functions except for cases of GCC fabricating
calls (e.g. strlen). Certainly no null-terminated string functions.
  • Loading branch information
skeeto committed Jan 6, 2024
1 parent fbf114d commit a5bc6c8
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 1 deletion.
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ RUN sha256sum -c $PREFIX/src/SHA256SUMS \
&& tar xJf nasm-$NASM_VERSION.tar.xz \
&& tar xjf vim-$VIM_VERSION.tar.bz2 \
&& tar xzf cppcheck-$CPPCHECK_VERSION.tar.gz
COPY src/w64devkit.c src/w64devkit.ico \
COPY src/w64devkit.c src/w64devkit.ico src/libmemory.c \
src/alias.c src/debugbreak.c src/pkg-config.c src/vc++filt.c \
$PREFIX/src/

Expand Down Expand Up @@ -123,6 +123,10 @@ RUN cat $PREFIX/src/gcc-*.patch | patch -d/gcc-$GCC_VERSION -p1 \

ENV PATH="/bootstrap/bin:${PATH}"

RUN mkdir -p $PREFIX/$ARCH/lib \
&& CC=$ARCH-gcc DESTDIR=$PREFIX/$ARCH/lib/ sh $PREFIX/src/libmemory.c \
&& ln $PREFIX/$ARCH/lib/libmemory.a /bootstrap/$ARCH/lib/

WORKDIR /x-mingw-crt
RUN /mingw-w64-v$MINGW_VERSION/mingw-w64-crt/configure \
--prefix=/bootstrap/$ARCH \
Expand Down
179 changes: 179 additions & 0 deletions src/libmemory.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#if 0
# 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 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

0 comments on commit a5bc6c8

Please sign in to comment.