Closed Bug 209499 Opened 21 years ago Closed 20 years ago

convert PL_str* impls to use ANSI C equivalents

Categories

(NSPR :: NSPR, defect)

defect
Not set
normal

Tracking

(Not tracked)

RESOLVED FIXED

People

(Reporter: aaronl, Assigned: dwitte)

Details

(Keywords: perf)

Attachments

(3 files, 6 obsolete files)

NSPR includes many string functions that are redundant: PL_strlen, PL_strcat,
...{cpy,ncpy,ncat,strpbrk}, etc. are all implementations of functions provided
by the C standard. There are many advantages to using the standard functions.
Not only do C libraries often provide assembly-optimized versions, but some
compilers are able to inline optimised standard string functions when they
decide that it is appropriate.

I am replacing uses of the functions provided by the C standard with the
standard versions. If this is an unwanted change, please mark this bug INVALID.
Functions where NSPR is necessary for portability, such as the PL_strdup and
PL_str[n]case{cmp,str} family, are preserved. I am also changing calls to
PL_strfree() into normal free() calls, since that's all PL_strfree() does. This
is because I don't expect PL_str* code to change its memory allocation scheme,
seeing that it already seems somewhat deprecated. Anyway, it's easy for me to
undo the removals of PL_strfree calls if that's what the reviewer wants.

The nsCRT::str* functions that take const char* arguments are also a target, but
I haven't gotten to those yet.
Attached patch First stab (obsolete) — Splinter Review
This does the conversion in the following subdirs: netwerk layout caps content
directory dom editor ipc js xpinstall

More to come if people like this.
>I am also changing calls to
>PL_strfree() into normal free() calls, since that's all PL_strfree() does. This
>is because I don't expect PL_str* code to change its memory allocation scheme,

I don't think that you should make assumptions about NSPR's memory allocator.
Comment on attachment 125712 [details] [diff] [review]
First stab

indeed PL_strfree is *not* equivalent to free. it's explictly for PL_strdup, if
you're replacing the PL_strdup then you can (must) change the PL_strfree at the
same time. otherwise please leave it alone.
Attachment #125712 - Flags: review-
Attached patch v2 (obsolete) — Splinter Review
Fair enough.
Attachment #125712 - Attachment is obsolete: true
Index: netwerk/protocol/http/src/nsHttpTransaction.cpp
===================================================================
-            char *p = PL_strchr(PL_strchr(buf.get(), ' ')+1, ' ');
+            char *p = strchr(PL_strchr(buf.get(), ' ')+1, ' ');

looks like you missed one there.
This won't work in all cases - you can't blindly replace PL_str* functions with
ANSI C equivalents, because the PL_str* family is nullsafe. ANSI C string
functions are not. We probably rely on this behavior in many cases.

The PL_str* implementations are definitely slower than the ANSI C ones, so it
would be nice to do something here; perhaps we could redefine the PL_str*
functions to be nullsafe wrappers around ANSI counterparts? Then, consumers that
don't need nullsafety could be converted later, but that's a laborious task
since each occurrence needs to be checked by hand.
what dwitte said.  instead of trying to manually replace all PL_strlen with
strlen, let's try to change the implementation of PL_strlen instead ;)

i'm not sure that NSPR will want to change the definitions of these functions
since they are designed to work with really ancient operating systems that often
don't follow ANSI C in very subtle ways.  cc'ing wtc for his input.  it seems
like it should be safe to make PL_strlen a macro on platforms that have a good
strlen.  e.g., strlen is a single CPU instruction on ix86.

same applies to other string routines (like PL_strchr in particular).
The PL_str functions must remain functions for binary
compatibility.  We can change their implementations to
call the corresponding str functions.  Then the only
optimization you lose is the inlining of the str functions.
Do we know of any platforms where the ANSI functions are broken or inconsistent?
Or should we just forge ahead and do this for all platforms?

Which functions must remain with their NSPR implementation (Aaron mentioned
PL_strdup and PL_str[n]case{cmp,str}, where did this list come from)?

Once these are answered, I guess we just need someone to do the typing. ;)
It is safe to do this for all platforms.

The functions that must remain with their NSPR implementation
are those that are not in the Standard C Library.
You probably confused strdup w/ strndup. strndup, strcasecmp, and strcasestr aren't part of the standard.
The manpage for strdup I have here says:

CONFORMING TO
       SVID 3, BSD 4.3.  strndup(), strdupa(), and strndupa() are  GNU  exten-
       sions.

Notice no ISO conformance. But it's dated 1993...
We should assume that only the str* functions listed
in The C Programming Language, 2nd Ed., are
universally available at present.  strdup is not one
of them.
according to an ANSI C reference i found, the following functions are provided:

strlen
strcpy
strncpy
strcat
strncat
strcmp
strncmp
strchr
strrchr
memcpy
memmove
memcmp
memchr
memset

if this list is incorrect for our purposes, please correct me. ;)
add the following to the above list:

strspn
strcspn
strpbrk
strstr
strtok
dwitte: avoid ANSI C strtok... nsCRT::strtok is better ;)
i definitely will be, since ANSI C strtok isn't threadsafe at all :)

so i've got a first-cut patch for nspr to convert pretty much everything i can
over to ANSI C... coming right up!
Assignee: aaronl → dwitte
Severity: trivial → normal
Component: Browser-General → NSPR
Product: Browser → NSPR
Summary: Remove uses of PL_str* functions that are provided by ANSI C → convert PL_str* impls to use ANSI C equivalents
Version: Trunk → other
aren't nspr PR_str* functions null-safe, contrary to ANSI C ones?
Attached patch first-cut nspr patch (obsolete) — Splinter Review
so this changes most of the nspr string functions to use libc... pretty much
first-cut at this point, to see what wtc and other folk think.

it keeps binary and API compatibility untouched. i've measured a 3.1%
improvement in Txul with this patch (linux gtk2, gcc 3.3.2). i'm unable to
measure Tp at this point.
Attachment #125777 - Attachment is obsolete: true
Comment on attachment 135490 [details] [diff] [review]
first-cut nspr patch

wtc, what do you think? :)
Attachment #135490 - Flags: review?(wchang0222)
oh, one note, about the comment

/* error checking in case we have a 64-bit platform -- make sure we dont
 * have ultra long strings that overflow a int32
 */

in strlen.c. the reason i removed the assertion here is that it doesn't seem to
be implemented consistently in any of the other PL_str* functions that deal with
int32's in an unsafe fashion - so, i chose to be consistent and drop it
altogether, rather than making a mess of the string functions. comments on this
approach are welcome.
Attachment #135490 - Flags: review?(wchang0222)
Attached patch second-cut nspr patch (obsolete) — Splinter Review
err, ignore that... nspr's handling of int32 overflows was perfectly correct.

this patch fixes up that part for 64-bit platforms, by using PRSize rather than
PRUint32 when dealing with ANSI C functions.
Attachment #135490 - Attachment is obsolete: true
Attachment #135491 - Flags: review?(wchang0222)
Nit: if you're going to keep nspr's wacky if ((char *)0 == tests, you should do
so for your ?: tests too. Also, some || return consistency would be nice.
Comment on attachment 135491 [details] [diff] [review]
second-cut nspr patch

some review comments (from me hoping to help this patch along)...

>Index: nsprpub/lib/libc/src/strcat.c

> PL_strncat(char *dest, const char *src, PRUint32 max)
...
>+    char *end = strchr(dest, '\0');
>+    (void)strncpy(end, src, (PRSize)max);

nit: s/PRSize/size_t/ ??

i know this shouldn't matter, but isn't size_t likewise part of ANSI-C?
this nit applies throughout the patch...


> PL_strcatn(char *dest, PRUint32 max, const char *src)
> {
>+    if( ((char *)0 == dest) || ((const char *)0 == src) )
>+        return dest;
> 
>+    PRSize dl = strlen(dest);
>+    PRSize n = (PRSize)max - (dl + 1);
>+    if( n > 0 )
>+    {
>+        char *end = dest + dl;
>+        (void)strncat(end, src, n);
>+    }

this looks fishy to me.  suppose:

  dest -> "xyz"
  max  -> 5
  src  -> "abc"

the expected result is: "xyza" (null terminated)

with this code:

  dl  -> 3
  n   -> 5 - 3 - 1 -> 1
  end -> dest + 3 -> "" (pointing at the null terminator)

strncat would basically copy one byte from src to end and that's it.
i believe the result would not be null terminated because strncat
does not guarantee null termination.  in fact, it only null terminates
if the selected range of src data ends with a null terminator.

i recommend keeping the existing code that calls PL_strncpyz.


>Index: nsprpub/lib/libc/src/strchr.c

> PR_IMPLEMENT(char *)
> PL_strnchr(const char *s, char c, PRUint32 n)
> {
>+    if( ((const char *)0 == s) ) return (char *)0;
> 
>+    const char *end = (const char *)memchr(s, '\0', (PRSize)n);
> 
>+    return (char *)memchr(s, c, end ? end - s + 1 : (PRSize)n);
> }

this might be clearer:

    /* do not search beyond a null terminator if one exists */
    const char *end = (const char *) memchr(s, '\0', (size_t)n);
    if (end)
	n = end - s + 1;
    return (char *)memchr(s, c, (size_t)n);


>Index: nsprpub/lib/libc/src/strcpy.c

> PL_strncpy(char *dest, const char *src, PRUint32 max)
...
>-#ifdef JLRU
>-    while( --max )
>-        *++dest = '\0';
>-#endif /* JLRU */

what's up with this ifdef?  do we need to preserve it?	wtc?


> PL_strncpyz(char *dest, const char *src, PRUint32 max)
...
>+    if( ((char *)0 == dest) || ((const char *)0 == src) ) return (char *)0;

nit: elsewhere you have put the return statement on a new line -- be consistent
;)
i think the return on the same line is the prevailing style fwiw.


>Index: nsprpub/lib/libc/src/strdup.c

> PL_strdup(const char *s)
> {
>     char *rv;
> 
>     if( (const char *)0 == s )
>+    {
>+        rv = (char *)malloc(1);
>+
>+        if( (char *)0 != rv )
>+            *rv = '\0';
>+    }
>     else
>+    {
>+        rv = (char *)malloc(strlen(s) + 1);
>+
>+        if( (char *)0 != rv )
>+            (void)strcpy(rv, s);

it would be more efficient to not compute strlen twice.  like this:

   size_t n = strlen(s) + 1;

   rv = (char *)malloc(n);

   if( (char *)0 != rv )
       (void)memcpy(rv, s, n);


> PL_strndup(const char *s, PRUint32 max)

i think the old code for this function might have been better.	it
is certainly more compact.  i'm not sure what benefit is to be had
by reimplementing this function.
thanks for the comments darin! re the stylistic mess... yeah, i should tidy it
up a bit more. partly i couldn't be bothered until wtc says what style he'd
prefer (that goes for size_t as well) :)

>with this code:
>
>  dl  -> 3
>  n   -> 5 - 3 - 1 -> 1
>  end -> dest + 3 -> "" (pointing at the null terminator)
>
>strncat would basically copy one byte from src to end and that's it.
>i believe the result would not be null terminated because strncat
>does not guarantee null termination.  in fact, it only null terminates
>if the selected range of src data ends with a null terminator.

hmm, are you sure? my reference for strncat says that it copies up to n chars
(where n is the 3rd arg), and _then_ nullterminates. maybe you're confusing it
with strncpy, which behaves how you describe?

so it would copy one byte from src to end, and then nullterminate as expected.
http://www.mkssoftware.com/docs/man3/strncat.3.asp

>it would be more efficient to not compute strlen twice.

yup, just a late-night goof. Neil pointed that out and i've made the change
locally :)

>i think the old code for this function might have been better.

sure... i just felt like inlining as much as possible, since codesize isn't
important for these tiny functions. i can change it back.

>this might be clearer:

agreed :)
here's the MAN page entry:

       The  strcat()  function appends the src string to the dest string over-
       writing the ‘\0’ character at the end of dest, and then adds  a  termi-
       nating  ‘\0’  character.   The  strings  may  not overlap, and the dest
       string must have enough space for the result.
 
       The strncat() function is similar, except that only the first n charac-
       ters of src are appended to dest.

so, yeah... hmm... it isn't exactly clear.  does "similar" mean similar in that
it also null terminates?  maybe there is a better reference for strncat somewhere?
Comment on attachment 135491 [details] [diff] [review]
second-cut nspr patch

I reviewed this patch last weekend.  I didn't finish it.

This patch must be reviewed with extreme care.	It is very
easy to introduce off-by-one errors when working on character
strings.

Some of the changes are obviously correct (for example, those
that handle null pointer arguments and then simply call the
libc equivalents) and can be checked in first.
Can we check in some of that?
wtc, any chance you can take a look at this patch sometime? (or if you'd rather
some of the changes wait for further review/testing, could you indicate what
you'd be happy with?)

thanks!
Comment on attachment 135491 [details] [diff] [review]
second-cut nspr patch

Dan,

Sorry about the delay.	I reviewed this patch
before but forgot to write my comments here.

One reason for the delay is that some of the
changes in this patch have a high risk of
introducing off-by-one bugs.  The strategy
I have in mind is to check in the other
low-risk changes first.
These are the safest changes in dwitte's "second-cut nspr patch".
I will check these in first.

I only made some minor changes:
1. I changed PRSize to size_t.
2. In PL_strstr, I restored the checks for empty strings because
   if 'little' is an empty string, PL_strstr returns (char *)0
   whereas strstr returns 'big'.
3. The change for PL_strlen can't declare 'l' in the middle of
   the function (this is C code).
Attachment #146924 - Flags: review?(dwitte)
Comment on attachment 146924 [details] [diff] [review]
safest changes (PL_strxxx checks for null pointers and then calls strxxx)

Dan, please at least review my change to your
patch for PL_strstr.
Attachment #146924 - Flags: review?(dwitte)
I removed the patch for PL_strncpy from this set
because I noticed a subtle difference between
PL_strncpy and strncpy in the padding of null bytes
if the 'src' string has less than 'max' chars.
(See the code in PL_strncpy ifdef'd with JLRU.)
Attachment #146924 - Attachment is obsolete: true
Attachment #146929 - Flags: superreview?(darin)
Attachment #146929 - Flags: review?(dwitte)
Attached patch remaining changes (obsolete) — Splinter Review
Attachment #135491 - Attachment is obsolete: true
Attachment #135491 - Flags: review?(wchang0222)
Comment on attachment 146930 [details] [diff] [review]
remaining changes

after bug 94580, while you're touching strdup.c, you could replace

+    {
+	 rv = (char *)malloc(strlen(s) + 1);
+
+	 if( (char *)0 != rv )
+	     (void)strcpy(rv, s);
+    }

by

+    {
+	 size_t l;
+
+	 l = strlen(s) + 1;
+	 rv = (char *)malloc(l);
+	 if( (char *)0 != rv )
+	     (void)memcpy(rv, s, l);
+    }
Comment on attachment 146929 [details] [diff] [review]
safest changes (PL_strxxx checks for null pointers and then calls strxxx)

looks like a good start, thanks wtc!

r=dwitte
Attachment #146929 - Flags: review?(dwitte) → review+
Comment on attachment 146929 [details] [diff] [review]
safest changes (PL_strxxx checks for null pointers and then calls strxxx)

We should ensure that we are compiling with -O2 under GCC (in release builds)
to ensure that the inline assembly code for many of these functions is used.

NOTE: if compiled with -Os, then none of these will be expanded to inline
assembly.  the result might not be so good due to the function call overhead
(extra jumps through PLT on ELF, for example).
back when i wrote this patch, i tested using #define __USE_STRING_INLINES... it
didn't seem to make any difference in perf (Txul), compared to non-inline (see
comment 19). (aside: it's also possible to achieve inlining with -Os, by
#undef'ing __OPTIMIZE_SIZE__, in the .c files of interest.)

the probable reason for this is that, for glibc on linux (my test platform), the
inline versions of the string functions (where they exist) are _not_ the same as
the non-inline versions that get used normally. they are more compact for
codesize reasons, which generally means they're slower, and they're only
provided for one arch iirc... so, the inline versions will always be written in
a compact i386 asm, whereas the noninline ones might be a faster i486/i686 asm.

unfortunate that glibc still worries about consumer codesize even when you
specify to use inlines... it'd be nice if it gave the consumer that choice. ;)
I've reviewed all the remaining changes.  Here
are the changes that I think are okay.	The only
substantial changes are in strdup.c (which I rewrote
to handle a null pointer as an empty string and to
use memcpy to copy strings) and strstr.c.

I will explain next why the other changes are not
accepted.
Attachment #147466 - Flags: superreview?(darin)
Attachment #147466 - Flags: review?(dwitte)
Comments on the remaining changes.

1. The patches for PL_strncat, PL_strncpy, and
PL_strncpyz are wrong.

PL_strncat, PL_strncpy: PL_strncpy and strncpy are
not interchangeable because strncpy always stores
'max' characters in the 'dest' buffer while
PL_strncpy terminates after copying the null
character.

PL_strncpyz: this statement is wrong:
    dest[--max] = '\0';
because the 'dest' buffer is not guaranteed to
be 'max' character long.

2. The patch for PL_strcatn may be okay.  Are
you sure the new code is faster than the old
code?

3. As for the other changes, it is not obvious
that the new code is faster than the old code.

I am not sure that

    r = (const char *)memchr(s, '\0', (PRSize)max);

is faster than

    for( r = s; max && *r; r++, max-- )
	;

or

    r = strchr(s, '\0');

is faster than

    for( r = s; *r; r++ )
	;

My gut feeling is that we won't get perceivable
application performance gain with these changes.
The effort to review these changes and to prove
that the new code is faster than the old code
may not be worthwhile.
Attachment #146930 - Attachment is obsolete: true
Comment on attachment 147481 [details] [diff] [review]
remaining changes

I also want to comment on the patch for
PL_strnchr.  I am not sure if the new code
(two memchr calls) is faster than the old
code (a single for loop).
Attachment #147466 - Flags: review?(dwitte) → review+
I've checked in the patches "safest changes" and
"wtc's second patch" on the NSPR tip (NSPR 4.6)
and NSPRPUB_PRE_4_2_CLIENT_BRANCH (Mozilla 1.8
alpha).

I've obtained Dan Witte's agreement that we do
not check in the remaining changes.

I'd like to thank Dan for contributing a high-quality
patch.  I am impressed that he followed the coding
style of the original author exactly.
Status: NEW → RESOLVED
Closed: 20 years ago
Resolution: --- → FIXED
Target Milestone: --- → 4.6
Attachment #146929 - Flags: superreview?(darin)
Attachment #147466 - Flags: superreview?(darin)
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: