Skip to content

Commit a5bc6c8

Browse files
committed
New runtime library: libmemory
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.
1 parent fbf114d commit a5bc6c8

File tree

2 files changed

+184
-1
lines changed

2 files changed

+184
-1
lines changed

Dockerfile

+5-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ RUN sha256sum -c $PREFIX/src/SHA256SUMS \
5959
&& tar xJf nasm-$NASM_VERSION.tar.xz \
6060
&& tar xjf vim-$VIM_VERSION.tar.bz2 \
6161
&& tar xzf cppcheck-$CPPCHECK_VERSION.tar.gz
62-
COPY src/w64devkit.c src/w64devkit.ico \
62+
COPY src/w64devkit.c src/w64devkit.ico src/libmemory.c \
6363
src/alias.c src/debugbreak.c src/pkg-config.c src/vc++filt.c \
6464
$PREFIX/src/
6565

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

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

126+
RUN mkdir -p $PREFIX/$ARCH/lib \
127+
&& CC=$ARCH-gcc DESTDIR=$PREFIX/$ARCH/lib/ sh $PREFIX/src/libmemory.c \
128+
&& ln $PREFIX/$ARCH/lib/libmemory.a /bootstrap/$ARCH/lib/
129+
126130
WORKDIR /x-mingw-crt
127131
RUN /mingw-w64-v$MINGW_VERSION/mingw-w64-crt/configure \
128132
--prefix=/bootstrap/$ARCH \

src/libmemory.c

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#if 0
2+
# memset, memcpy, memmove, and memcmp via x86 string instructions
3+
# Execute this source with a shell to build libmemory.a.
4+
# This is free and unencumbered software released into the public domain.
5+
set -e
6+
CFLAGS="-Os -fno-builtin -fno-asynchronous-unwind-tables -fno-ident"
7+
objects=""
8+
for func in memset memcpy memmove memcmp strlen; do
9+
FUNC="$(echo $func | tr '[:lower:]' '[:upper:]')"
10+
objects="$objects $func.o"
11+
(set -x; ${CC:-cc} -c -D$FUNC -Wa,--no-pad-sections $CFLAGS -o $func.o $0)
12+
done
13+
rm -f "${DESTDIR}libmemory.a"
14+
ar r "${DESTDIR}libmemory.a" $objects
15+
rm $objects
16+
exit 0
17+
#endif
18+
19+
typedef __SIZE_TYPE__ size_t;
20+
typedef __UINTPTR_TYPE__ uintptr_t;
21+
22+
#ifdef MEMSET
23+
void *memset(void *dst, int c, size_t len)
24+
{
25+
void *r = dst;
26+
asm volatile (
27+
"rep stosb"
28+
: "+D"(dst), "+c"(len)
29+
: "a"(c)
30+
: "memory"
31+
);
32+
return r;
33+
}
34+
#endif
35+
36+
#ifdef MEMCPY
37+
void *memcpy(void *restrict dst, void *restrict src, size_t len)
38+
{
39+
void *r = dst;
40+
asm volatile (
41+
"rep movsb"
42+
: "+D"(dst), "+S"(src), "+c"(len)
43+
:
44+
: "memory"
45+
);
46+
return r;
47+
}
48+
#endif
49+
50+
#ifdef MEMMOVE
51+
void *memmove(void *dst, void *src, size_t len)
52+
{
53+
// Use uintptr_t to bypass pointer semantics:
54+
// (1) comparing unrelated pointers
55+
// (2) pointer arithmetic on null (i.e. gracefully handle null dst/src)
56+
// (3) pointer overflow ("one-before-the-beginning" in reversed copy)
57+
uintptr_t d = (uintptr_t)dst;
58+
uintptr_t s = (uintptr_t)src;
59+
if (d > s) {
60+
d += len - 1;
61+
s += len - 1;
62+
asm ("std");
63+
}
64+
asm volatile (
65+
"rep movsb; cld"
66+
: "+D"(d), "+S"(s), "+c"(len)
67+
:
68+
: "memory"
69+
);
70+
return dst;
71+
}
72+
#endif
73+
74+
#ifdef MEMCMP
75+
int memcmp(void *s1, void *s2, size_t len)
76+
{
77+
// CCa "after" == CF=0 && ZF=0
78+
// CCb "before" == CF=1
79+
int a, b;
80+
asm volatile (
81+
"xor %%eax, %%eax\n" // CF=0, ZF=1 (i.e. CCa = CCb = 0)
82+
"repz cmpsb\n"
83+
: "+D"(s1), "+S"(s2), "+c"(len), "=@cca"(a), "=@ccb"(b)
84+
:
85+
: "ax", "memory"
86+
);
87+
return b - a;
88+
}
89+
#endif
90+
91+
#ifdef STRLEN
92+
size_t strlen(char *s)
93+
{
94+
size_t n = -1;
95+
asm volatile (
96+
"repne scasb"
97+
: "+D"(s), "+c"(n)
98+
: "a"(0)
99+
: "memory"
100+
);
101+
return -n - 2;
102+
}
103+
#endif
104+
105+
#ifdef TEST
106+
// $ sh libmemory.c
107+
// $ cc -nostdlib -fno-builtin -DTEST -g3 -O -o test libmemory.c libmemory.a
108+
// $ gdb -ex r -ex q ./test
109+
110+
#define assert(c) while (!(c)) __builtin_trap()
111+
void *memset(void *, int, size_t);
112+
int memcmp(void *, void *, size_t);
113+
void *memcpy(void *restrict, void *restrict, size_t);
114+
void *memmove(void *, void *, size_t);
115+
size_t strlen(char *);
116+
117+
#if defined(__linux) && defined(__amd64)
118+
asm (" .global _start\n"
119+
"_start: call mainCRTStartup\n"
120+
" mov %eax, %edi\n"
121+
" mov $60, %eax\n"
122+
" syscall\n");
123+
#elif defined(__linux) && defined(__i386)
124+
asm (" .global _start\n"
125+
"_start: call mainCRTStartup\n"
126+
" mov %eax, %ebx\n"
127+
" mov $1, %eax\n"
128+
" int $0x80\n");
129+
#endif
130+
131+
int mainCRTStartup(void)
132+
{
133+
{
134+
char buf[12] = "............";
135+
memset(buf+4, 'x', 4);
136+
assert(!memcmp(buf, "....xxxx....", 12));
137+
memset(buf, 0, 12);
138+
assert(!memcmp(buf, (char[12]){0}, 12));
139+
memset(buf+8, 1, 0);
140+
assert(!memcmp(buf, (char[12]){0}, 12));
141+
}
142+
143+
{
144+
char buf[7] = "abcdefg";
145+
memcpy(buf+0, buf+3, 3);
146+
assert(!memcmp(buf, "defdefg", 7));
147+
memcpy(buf+5, buf+1, 2);
148+
assert(!memcmp(buf, "defdeef", 7));
149+
memcpy(buf+1, buf+4, 0);
150+
assert(!memcmp(buf, "defdeef", 7));
151+
}
152+
153+
{
154+
char buf[] = "abcdefgh";
155+
memmove(buf+0, buf+1, 7);
156+
assert(!memcmp(buf, "bcdefghh", 8));
157+
buf[7] = 0;
158+
memmove(buf+1, buf+0, 7);
159+
assert(!memcmp(buf, "bbcdefgh", 8));
160+
memmove(buf+2, buf+1, 0);
161+
assert(!memcmp(buf, "bbcdefgh", 8));
162+
}
163+
164+
assert(memcmp("\xff", "1", 1) > 0);
165+
assert(memcmp("", "", 0) == 0); // test empty after > result
166+
assert(memcmp("1", "\xff", 1) < 0);
167+
assert(memcmp("", "", 0) == 0); // test empty after < result
168+
assert(memcmp("ab", "aa", 2) > 0);
169+
assert(memcmp("aa", "ab", 2) < 0);
170+
assert(memcmp("x", "y", 0) == 0);
171+
172+
assert(0 == strlen(""));
173+
assert(1 == strlen(" "));
174+
assert(1 == strlen("\xff"));
175+
assert(5 == strlen("hello"));
176+
177+
return 0;
178+
}
179+
#endif

0 commit comments

Comments
 (0)