Skip to content

Commit

Permalink
Add optimized x86-64 implementation of strnlen.
Browse files Browse the repository at this point in the history
While at it, beef up the test suite for strnlen and add performance
tests for it, too.
  • Loading branch information
Ulrich Drepper committed Jul 26, 2010
1 parent 8e96b93 commit 24fb0f8
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 21 deletions.
9 changes: 9 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
2010-07-26 Ulrich Drepper <drepper@redhat.com>

* string/test-strnlen.c: New file.
* string/Makefile (strop-tests): Add strnlen.
* string/tester.c (test_strnlen): Add a few more test cases.
* string/tst-strlen.c: Better error reporting.

* sysdeps/x86_64/strnlen.S: New file.

2010-07-24 Ulrich Drepper <drepper@redhat.com>

* sysdeps/x86_64/multiarch/strstr.c (__m128i_strloadu_tolower): Use
Expand Down
2 changes: 1 addition & 1 deletion string/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ o-objects.ob := memcpy.o memset.o memchr.o
strop-tests := memchr memcmp memcpy memmove mempcpy memset memccpy \
stpcpy stpncpy strcat strchr strcmp strcpy strcspn \
strlen strncmp strncpy strpbrk strrchr strspn memmem \
strstr strcasestr
strstr strcasestr strnlen
tests := tester inl-tester noinl-tester testcopy test-ffs \
tst-strlen stratcliff tst-svc tst-inlcall \
bug-strncat1 bug-strspn1 bug-strpbrk1 tst-bswap \
Expand Down
197 changes: 197 additions & 0 deletions string/test-strnlen.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/* Test and measure strlen functions.
Copyright (C) 1999, 2002, 2003, 2005, 2010 Free Software Foundation, Inc.
This file is part of the GNU C Library.
Written by Jakub Jelinek <jakub@redhat.com>, 1999.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA. */

#define TEST_MAIN
#include "test-string.h"

typedef size_t (*proto_t) (const char *, size_t);
size_t simple_strnlen (const char *, size_t);

IMPL (simple_strnlen, 0)
IMPL (strnlen, 1)

size_t
simple_strnlen (const char *s, size_t maxlen)
{
size_t i;

for (i = 0; i < maxlen && s[i]; ++i);
return i;
}

static void
do_one_test (impl_t *impl, const char *s, size_t maxlen, size_t exp_len)
{
size_t len = CALL (impl, s, maxlen);
if (len != exp_len)
{
error (0, 0, "Wrong result in function %s %zd %zd", impl->name,
len, exp_len);
ret = 1;
return;
}

if (HP_TIMING_AVAIL)
{
hp_timing_t start __attribute ((unused));
hp_timing_t stop __attribute ((unused));
hp_timing_t best_time = ~ (hp_timing_t) 0;
size_t i;

for (i = 0; i < 32; ++i)
{
HP_TIMING_NOW (start);
CALL (impl, s, maxlen);
HP_TIMING_NOW (stop);
HP_TIMING_BEST (best_time, start, stop);
}

printf ("\t%zd", (size_t) best_time);
}
}

static void
do_test (size_t align, size_t len, size_t maxlen, int max_char)
{
size_t i;

align &= 7;
if (align + len >= page_size)
return;

for (i = 0; i < len; ++i)
buf1[align + i] = 1 + 7 * i % max_char;
buf1[align + len] = 0;

if (HP_TIMING_AVAIL)
printf ("Length %4zd, alignment %2zd:", len, align);

FOR_EACH_IMPL (impl, 0)
do_one_test (impl, (char *) (buf1 + align), maxlen, MIN (len, maxlen));

if (HP_TIMING_AVAIL)
putchar ('\n');
}

static void
do_random_tests (void)
{
size_t i, j, n, align, len;
unsigned char *p = buf1 + page_size - 512;

for (n = 0; n < ITERATIONS; n++)
{
align = random () & 15;
len = random () & 511;
if (len + align > 510)
len = 511 - align - (random () & 7);
j = len + align + 64;
if (j > 512)
j = 512;

for (i = 0; i < j; i++)
{
if (i == len + align)
p[i] = 0;
else
{
p[i] = random () & 255;
if (i >= align && i < len + align && !p[i])
p[i] = (random () & 127) + 1;
}
}

FOR_EACH_IMPL (impl, 1)
{
if (len > 0
&& CALL (impl, (char *) (p + align), len - 1) != len - 1)
{
error (0, 0, "Iteration %zd (limited) - wrong result in function %s (%zd) %zd != %zd, p %p",
n, impl->name, align,
CALL (impl, (char *) (p + align), len - 1), len - 1, p);
ret = 1;
}
if (CALL (impl, (char *) (p + align), len) != len)
{
error (0, 0, "Iteration %zd (exact) - wrong result in function %s (%zd) %zd != %zd, p %p",
n, impl->name, align,
CALL (impl, (char *) (p + align), len), len, p);
ret = 1;
}
if (CALL (impl, (char *) (p + align), len + 1) != len)
{
error (0, 0, "Iteration %zd (long) - wrong result in function %s (%zd) %zd != %zd, p %p",
n, impl->name, align,
CALL (impl, (char *) (p + align), len + 1), len, p);
ret = 1;
}
}
}
}

int
test_main (void)
{
size_t i;

test_init ();

printf ("%20s", "");
FOR_EACH_IMPL (impl, 0)
printf ("\t%s", impl->name);
putchar ('\n');

for (i = 1; i < 8; ++i)
{
do_test (0, i, i - 1, 127);
do_test (0, i, i, 127);
do_test (0, i, i + 1, 127);
}

for (i = 1; i < 8; ++i)
{
do_test (i, i, i - 1, 127);
do_test (i, i, i, 127);
do_test (i, i, i + 1, 127);
}

for (i = 2; i <= 10; ++i)
{
do_test (0, 1 << i, 5000, 127);
do_test (1, 1 << i, 5000, 127);
}

for (i = 1; i < 8; ++i)
do_test (0, i, 5000, 255);

for (i = 1; i < 8; ++i)
do_test (i, i, 5000, 255);

for (i = 2; i <= 10; ++i)
{
do_test (0, 1 << i, 5000, 255);
do_test (1, 1 << i, 5000, 255);
}

do_random_tests ();
return ret;
}

#include "../test-skeleton.c"
33 changes: 17 additions & 16 deletions string/tester.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Tester for string functions.
Copyright (C) 1995-2001, 2003, 2005, 2008 Free Software Foundation, Inc.
Copyright (C) 1995-2001, 2003, 2005, 2008, 2010 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
Expand Down Expand Up @@ -441,20 +441,21 @@ test_strnlen (void)
check (strnlen ("", 10) == 0, 1); /* Empty. */
check (strnlen ("a", 10) == 1, 2); /* Single char. */
check (strnlen ("abcd", 10) == 4, 3); /* Multiple chars. */
check (strnlen ("foo", (size_t)-1) == 3, 4); /* limits of n. */

{
char buf[4096];
int i;
char *p;
for (i=0; i < 0x100; i++)
{
p = (char *) ((unsigned long int)(buf + 0xff) & ~0xff) + i;
strcpy (p, "OK");
strcpy (p+3, "BAD/WRONG");
check (strnlen (p, 100) == 2, 5+i);
}
}
check (strnlen ("foo", (size_t) -1) == 3, 4); /* limits of n. */
check (strnlen ("abcd", 0) == 0, 5); /* Restricted. */
check (strnlen ("abcd", 1) == 1, 6); /* Restricted. */
check (strnlen ("abcd", 2) == 2, 7); /* Restricted. */
check (strnlen ("abcd", 3) == 3, 8); /* Restricted. */
check (strnlen ("abcd", 4) == 4, 9); /* Restricted. */

char buf[4096];
for (int i = 0; i < 0x100; ++i)
{
char *p = (char *) ((unsigned long int)(buf + 0xff) & ~0xff) + i;
strcpy (p, "OK");
strcpy (p + 3, "BAD/WRONG");
check (strnlen (p, 100) == 2, 10 + i);
}
}

static void
Expand Down Expand Up @@ -988,7 +989,7 @@ test_memcmp (void)
int cnt = 1;
char one[21];
char two[21];

it = "memcmp";
check(memcmp("a", "a", 1) == 0, cnt++); /* Identity. */
check(memcmp("abc", "abc", 3) == 0, cnt++); /* Multicharacter. */
Expand Down
18 changes: 14 additions & 4 deletions string/tst-strlen.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,21 @@ main(int argc, char *argv[])
buf[words * 4 + 3] = (last & 8) != 0 ? 'e' : '\0';
buf[words * 4 + 4] = '\0';

if (strlen (buf) != words * 4 + lens[last]
|| strnlen (buf, -1) != words * 4 + lens[last])
if (strlen (buf) != words * 4 + lens[last])
{
printf ("failed for base=%Zu, words=%Zu, and last=%Zu\n",
base, words, last);
printf ("\
strlen failed for base=%Zu, words=%Zu, and last=%Zu (is %zd, expected %zd)\n",
base, words, last,
strlen (buf), words * 4 + lens[last]);
return 1;
}

if (strnlen (buf, -1) != words * 4 + lens[last])
{
printf ("\
strnlen failed for base=%Zu, words=%Zu, and last=%Zu (is %zd, expected %zd)\n",
base, words, last,
strnlen (buf, -1), words * 4 + lens[last]);
return 1;
}
}
Expand Down
64 changes: 64 additions & 0 deletions sysdeps/x86_64/strnlen.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* strnlen(str,maxlen) -- determine the length of the string STR up to MAXLEN.
Copyright (C) 2010 Free Software Foundation, Inc.
Contributed by Ulrich Drepper <drepper@redhat.com>.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA. */

#include <sysdep.h>


.text
ENTRY(__strnlen)
movq %rsi, %rax
testq %rsi, %rsi
jz 3f
pxor %xmm2, %xmm2
movq %rdi, %rcx
movq %rdi, %r8
movq $16, %r9
andq $~15, %rdi
movdqa %xmm2, %xmm1
pcmpeqb (%rdi), %xmm2
orl $0xffffffff, %r10d
subq %rdi, %rcx
shll %cl, %r10d
subq %rcx, %r9
pmovmskb %xmm2, %edx
andl %r10d, %edx
jnz 1f
subq %r9, %rsi
jbe 3f

2: movdqa 16(%rdi), %xmm0
leaq 16(%rdi), %rdi
pcmpeqb %xmm1, %xmm0
pmovmskb %xmm0, %edx
testl %edx, %edx
jnz 1f
subq $16, %rsi
jnbe 2b
3: ret

1: subq %r8, %rdi
bsfl %edx, %edx
addq %rdi, %rdx
cmpq %rdx, %rax
cmovnbq %rdx, %rax
ret
END(__strnlen)
weak_alias (__strnlen, strnlen)
libc_hidden_def (strnlen)

0 comments on commit 24fb0f8

Please sign in to comment.