Article View: gnu.gcc.bug
Article #6316patch for faster version of strlen
From: Wolfgang Formann
Date: Mon, 18 Dec 1995 00:00
Date: Mon, 18 Dec 1995 00:00
491 lines
21448 bytes
21448 bytes
Hello!
I want to send you a little patch which makes the inlined function strlen(3)
much faster on i[45]86. Since the the fastest version on i486 and on i586
differ a little I have also added the new options -m[no-]586, -m[no-]pentium
to get optimal code for each processor. Still code generated for one of
Intel's processors runs on each other. I have tried hard, but on an i386
I did not find a faster way than the good old repne scasb.
Here is a little table that shows the speedup. The string I took the length
from was 500 bytes long.
+-------------------+-------------+---------------+---------------+
+ processor\pattern | repne scasb | 486-optimized | 586-optimized |
+ | clock cycles used |
+-------------------+-------------+---------------+---------------+
+ i386-33 | 11311 | 11613 | 12284 |
+ i486DX2/50 (AMD) | 4572 | 2873 | 3105 |
+ i486DX2/50 (Intel)| 4573 | 2874 | 3107 |
+ i586-90 | 2026 | 772 | 658 |
+-------------------+-------------+---------------+---------------+
The difference between Intel's and AMD's i486 depend on the moon ;-)
The differences on shorter strings are similar. So as you can see the
optimized code is not so bad on i386, but it gives a nice speedup on
the others.
Well I have added the new switch -m586 / -mpentium. I implemented theese in
the way that no old code is affected. -mno-pentium is the same as
-mno-486, which is the same as -m386. -mpentium also turns on -m486.
The macro TARGET_386 is true when none of the bits for i[45]86 are set.
The macro TARGET_486 is not modified.
The new macro TARGET_586 is true when the bits for i486 *AND* i586 are set.
( also included is a little typo in md.texi [ if instead of iff ])
I hope I have explained enough ;->
The patch is based on gcc.2.7.2.
Sun Dec 17 11:14:29 1995 Wolfgang Formann <wolfi@exner.tgm.ac.at>
* Added flags -m586, -mpentium
* New inlined versions for strlen(), optimized for i486 and i586
* Fixed typo in md.texi, added new flags to invoke.texi
diff -rcp -x TAGS -x c-parse.y -x c-parse.c -x c-parse.h -x c-gperf.h -x cexp.c -x bi-parser.c -x objc-parse.y -x objc-parse.c -x gcc.?? -x gcc.??s -x gcc.aux -x gcc.info* -x cpp.?? -x cpp.??s -x cpp.aux -x cpp.info* -x */cp/parse.c -x */cp/parse.h -x Makefile gcc-2.7.2/config/i386/i386.c gcc-2.7.2-new/config/i386/i386.c
*** gcc-2.7.2/config/i386/i386.c Sun Oct 22 12:13:21 1995
--- gcc-2.7.2-new/config/i386/i386.c Sun Dec 17 12:42:19 1995
*************** assign_386_stack_local (mode, n)
*** 3243,3245 ****
--- 3243,3433 ----
return i386_stack_locals[(int) mode][n];
}
+
+ /* Output the approprate insns for doing strlen if not just doing repnz; scasb
+
+ operands[0] = result, initialized with the startaddress
+ operands[1] = alignment of the address.
+ operands[2] = scratch register, initialized with the startaddress when
+ not aligned, otherwise undefined
+
+ This is just the body. It needs the initialisations mentioned above and
+ some adress computing at the end. These things are done in i386.md. */
+
+ char *
+ output_strlen_unroll (operands)
+ rtx operands[];
+ {
+ rtx xops[18];
+
+ xops[0] = operands[0]; /* Result */
+ /* operands[1]; * Alignment */
+ xops[1] = operands[2]; /* Scratch */
+ xops[2] = GEN_INT (0);
+ xops[3] = GEN_INT (2);
+ xops[4] = GEN_INT (3);
+ xops[5] = GEN_INT (4);
+ /* xops[6] = gen_label_rtx (); * label when aligned to 3-byte */
+ /* xops[7] = gen_label_rtx (); * label when aligned to 2-byte */
+ xops[8] = gen_label_rtx (); /* label of main loop */
+ if(TARGET_586 && QI_REG_P (xops[1]))
+ xops[9] = gen_label_rtx (); /* pentium optimisation */
+ xops[10] = gen_label_rtx (); /* end label 2 */
+ xops[11] = gen_label_rtx (); /* end label 1 */
+ xops[12] = gen_label_rtx (); /* end label */
+ /* xops[13] * Temporary used */
+ xops[14] = GEN_INT (0xff);
+ xops[15] = GEN_INT (0xff00);
+ xops[16] = GEN_INT (0xff0000);
+ xops[17] = GEN_INT (0xff000000);
+
+ /* maybe you do not want this ?? */
+ fprintf (asm_out_file, "\n%s start of strlen\n", ASM_COMMENT_START);
+
+ /* Loop to check 1..3 bytes for null to get an aligned pointer */
+
+ /* is there a known alignment and is it less then 4 */
+ if (GET_CODE (operands[1]) != CONST_INT || INTVAL (operands[1]) < 4)
+ {
+ /* is there a known alignment and is it not 2 */
+ if (GET_CODE (operands[1]) != CONST_INT || INTVAL (operands[1]) != 2)
+ {
+ xops[6] = gen_label_rtx (); /* label when aligned to 3-byte */
+ xops[7] = gen_label_rtx (); /* label when aligned to 2-byte */
+
+ /* leave just the 3 lower bits */
+ /* if this is a q-register, then the high part is used later */
+ /* therefore user andl rather than andb */
+ output_asm_insn (AS2 (and%L1,%4,%1), xops);
+ /* is aligned to 4-byte adress when zero */
+ output_asm_insn (AS1 (je,%l8), xops);
+ /* side-effect even Parity when %eax == 3 */
+ output_asm_insn (AS1 (jp,%6), xops);
+
+ /* is it aligned to 2 bytes ? */
+ if (QI_REG_P (xops[1]))
+ output_asm_insn (AS2 (cmp%B1,%3,%1), xops);
+ else
+ output_asm_insn (AS2 (cmp%L1,%3,%1), xops);
+ output_asm_insn (AS1 (je,%7), xops);
+ }
+ else
+ {
+ /* since the alignment is 2, we have to check 2 or 0 bytes */
+
+ /* check if is aligned to 4 - byte */
+ output_asm_insn (AS2 (and%L1,%3,%1), xops);
+ /* is aligned to 4-byte adress when zero */
+ output_asm_insn (AS1 (je,%l8), xops);
+ }
+
+ xops[13] = gen_rtx (MEM, QImode, xops[0]);
+ /* now, compare the bytes */
+ /* compare with the high part of a q-reg gives shorter code */
+ if (QI_REG_P (xops[1]))
+ {
+ /* compare the first n unaligned byte on a byte per byte basis */
+ output_asm_insn (AS2 (cmp%B1,%h1,%13), xops);
+ /* when zero we reached the end */
+ output_asm_insn (AS1 (je,%l12), xops);
+ /* increment the address */
+ output_asm_insn (AS1 (inc%L0,%0), xops);
+
+ /* not needed with an alignment of 2 */
+ if (GET_CODE (operands[1]) != CONST_INT || INTVAL (operands[1]) != 2)
+ {
+ ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (xops[7]));
+ output_asm_insn (AS2 (cmp%B1,%h1,%13), xops);
+ output_asm_insn (AS1 (je,%l12), xops);
+ output_asm_insn (AS1 (inc%L0,%0), xops);
+
+ ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (xops[6]));
+ }
+ output_asm_insn (AS2 (cmp%B1,%h1,%13), xops);
+ }
+ else
+ {
+ output_asm_insn (AS2 (cmp%B13,%2,%13), xops);
+ output_asm_insn (AS1 (je,%l12), xops);
+ output_asm_insn (AS1 (inc%L0,%0), xops);
+
+ ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (xops[7]));
+ output_asm_insn (AS2 (cmp%B13,%2,%13), xops);
+ output_asm_insn (AS1 (je,%l12), xops);
+ output_asm_insn (AS1 (inc%L0,%0), xops);
+
+ ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (xops[6]));
+ output_asm_insn (AS2 (cmp%B13,%2,%13), xops);
+ }
+ output_asm_insn (AS1 (je,%l12), xops);
+ output_asm_insn (AS1 (inc%L0,%0), xops);
+ }
+
+ /* Generate loop to check 4 bytes at a time */
+ /* IMHO it is not a good idea to align this loop. It gives only */
+ /* huge programms, but does not help to speed up */
+ /* ASM_OUTPUT_LOOP_ALIGN (asm_out_file); */
+ ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (xops[8]));
+
+ xops[13] = gen_rtx (MEM, SImode, xops[0]);
+ output_asm_insn (AS2 (mov%L1,%13,%1), xops);
+
+ if (QI_REG_P (xops[1]))
+ {
+ /* on i586 it is faster to compare the hi- and lo- part */
+ /* as a kind of lookahead. If xoring both is zero, then one */
+ /* of both *could* be zero, otherwith none of both is zero */
+ /* this saves one instruction, on i486 this is slower */
+ /* testet with P-90, i486DX2-66, AMD486DX2-66 */
+ if(TARGET_586)
+ {
+ output_asm_insn (AS2 (test%B1,%h1,%b1), xops);
+ output_asm_insn (AS1 (jne,%l9), xops);
+ }
+
+ /* check first byte */
+ output_asm_insn (AS2 (test%B1,%b1,%b1), xops);
+ output_asm_insn (AS1 (je,%l12), xops);
+
+ /* check second byte */
+ output_asm_insn (AS2 (test%B1,%h1,%h1), xops);
+ output_asm_insn (AS1 (je,%l11), xops);
+
+ if(TARGET_586)
+ ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (xops[9]));
+ }
+ else
+ {
+ /* check first byte */
+ output_asm_insn (AS2 (test%L1,%14,%1), xops);
+ output_asm_insn (AS1 (je,%l12), xops);
+
+ /* check second byte */
+ output_asm_insn (AS2 (test%L1,%15,%1), xops);
+ output_asm_insn (AS1 (je,%l11), xops);
+ }
+
+ /* check third byte */
+ output_asm_insn (AS2 (test%L1,%16,%1), xops);
+ output_asm_insn (AS1 (je,%l10), xops);
+
+ /* check fourth byte and increment address */
+ output_asm_insn (AS2 (add%L0,%5,%0), xops);
+ output_asm_insn (AS2 (test%L1,%17,%1), xops);
+ output_asm_insn (AS1 (jne,%l8), xops);
+
+ /* now generate fixups when the compare stops within a 4-byte word */
+ output_asm_insn (AS2 (sub%L0,%4,%0), xops);
+
+ ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (xops[10]));
+ output_asm_insn (AS1 (inc%L0,%0), xops);
+
+ ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (xops[11]));
+ output_asm_insn (AS1 (inc%L0,%0), xops);
+
+ ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (xops[12]));
+
+ /* maybe you do not want this ?? */
+ fprintf (asm_out_file, "%s end of strlen\n", ASM_COMMENT_START);
+ RET;
+ }
diff -rcp -x TAGS -x c-parse.y -x c-parse.c -x c-parse.h -x c-gperf.h -x cexp.c -x bi-parser.c -x objc-parse.y -x objc-parse.c -x gcc.?? -x gcc.??s -x gcc.aux -x gcc.info* -x cpp.?? -x cpp.??s -x cpp.aux -x cpp.info* -x */cp/parse.c -x */cp/parse.h -x Makefile gcc-2.7.2/config/i386/i386.h gcc-2.7.2-new/config/i386/i386.h
*** gcc-2.7.2/config/i386/i386.h Fri Sep 22 23:42:57 1995
--- gcc-2.7.2-new/config/i386/i386.h Sun Dec 17 10:23:34 1995
*************** extern int target_flags;
*** 67,73 ****
/* Masks for the -m switches */
#define MASK_80387 000000000001 /* Hardware floating point */
#define MASK_486 000000000002 /* 80486 specific */
! #define MASK_NOTUSED1 000000000004 /* bit not currently used */
#define MASK_RTD 000000000010 /* Use ret that pops args */
#define MASK_ALIGN_DOUBLE 000000000020 /* align doubles to 2 word boundary */
#define MASK_SVR3_SHLIB 000000000040 /* Uninit locals into bss */
--- 67,73 ----
/* Masks for the -m switches */
#define MASK_80387 000000000001 /* Hardware floating point */
#define MASK_486 000000000002 /* 80486 specific */
! #define MASK_586 000000000006 /* pentium specific (implies 80486) */
#define MASK_RTD 000000000010 /* Use ret that pops args */
#define MASK_ALIGN_DOUBLE 000000000020 /* align doubles to 2 word boundary */
#define MASK_SVR3_SHLIB 000000000040 /* Uninit locals into bss */
*************** extern int target_flags;
*** 129,136 ****
#define TARGET_MOVE ((target_flags & MASK_NO_MOVE) == 0) /* Don't generate memory->memory */
/* Specific hardware switches */
#define TARGET_486 (target_flags & MASK_486) /* 80486DX, 80486SX, 80486DX[24] */
! #define TARGET_386 (!TARGET_486) /* 80386 */
#define TARGET_SWITCHES \
{ { "80387", MASK_80387 }, \
--- 129,137 ----
#define TARGET_MOVE ((target_flags & MASK_NO_MOVE) == 0) /* Don't generate memory->memory */
/* Specific hardware switches */
+ #define TARGET_586 ((target_flags & MASK_586) == MASK_586) /* pentium */
#define TARGET_486 (target_flags & MASK_486) /* 80486DX, 80486SX, 80486DX[24] */
! #define TARGET_386 (!(target_flags & MASK_586)) /* 80386 */
#define TARGET_SWITCHES \
{ { "80387", MASK_80387 }, \
*************** extern int target_flags;
*** 142,147 ****
--- 143,152 ----
{ "no-386", MASK_486 }, \
{ "486", MASK_486 }, \
{ "no-486", -MASK_486 }, \
+ { "586", MASK_586 }, \
+ { "no-586", -MASK_586 }, \
+ { "pentium", MASK_586 }, \
+ { "no-pentium", -MASK_586 }, \
{ "rtd", MASK_RTD }, \
{ "no-rtd", -MASK_RTD }, \
{ "align-double", MASK_ALIGN_DOUBLE }, \
*************** extern void init_cumulative_args ();
*** 1868,1873 ****
--- 1873,1879 ----
extern void function_arg_advance ();
extern struct rtx_def *function_arg ();
extern int function_arg_partial_nregs ();
+ extern char *output_strlen_unroll ();
extern void output_op_from_reg ();
extern void output_to_reg ();
extern char *singlemove_string ();
diff -rcp -x TAGS -x c-parse.y -x c-parse.c -x c-parse.h -x c-gperf.h -x cexp.c -x bi-parser.c -x objc-parse.y -x objc-parse.c -x gcc.?? -x gcc.??s -x gcc.aux -x gcc.info* -x cpp.?? -x cpp.??s -x cpp.aux -x cpp.info* -x */cp/parse.c -x */cp/parse.h -x Makefile gcc-2.7.2/config/i386/i386.md gcc-2.7.2-new/config/i386/i386.md
*** gcc-2.7.2/config/i386/i386.md Mon Aug 21 18:27:58 1995
--- gcc-2.7.2-new/config/i386/i386.md Sun Dec 17 17:11:51 1995
***************
*** 5736,5742 ****
(define_expand "strlensi"
[(parallel [(set (match_dup 4)
(unspec:SI [(mem:BLK (match_operand:BLK 1 "general_operand" ""))
! (match_operand:QI 2 "register_operand" "")
(match_operand:SI 3 "immediate_operand" "")] 0))
(clobber (match_dup 1))])
(set (match_dup 5)
--- 5736,5742 ----
(define_expand "strlensi"
[(parallel [(set (match_dup 4)
(unspec:SI [(mem:BLK (match_operand:BLK 1 "general_operand" ""))
! (match_operand:QI 2 "immediate_operand" "")
(match_operand:SI 3 "immediate_operand" "")] 0))
(clobber (match_dup 1))])
(set (match_dup 5)
***************
*** 5747,5752 ****
--- 5747,5793 ----
""
"
{
+ if (!TARGET_386 && operands[2] == const0_rtx && optimize > 1)
+ {
+ rtx address;
+ rtx scratch;
+
+ /* well it seems that some optimizer does not combine a call like
+ foo(strlen(bar), strlen(bar));
+ when the move and the subtraction is done here. It does calculate
+ the length just once when these instructions are done inside of
+ output_strlen_unroll(). But I think since &bar[strlen(bar)] is
+ often used and I use one fewer register for the lifetime of
+ output_strlen_unroll() this is better. */
+ scratch = gen_reg_rtx (SImode);
+ address = force_reg (SImode, XEXP (operands[1], 0));
+
+ /* move address to scratch-register
+ this is done here because the i586 can do the following and
+ in the same cycle with the following move. */
+ if (GET_CODE (operands[3]) != CONST_INT || INTVAL (operands[3]) < 4)
+ emit_insn (gen_movsi (scratch, address));
+
+ emit_insn (gen_movsi (operands[0], address));
+
+ if(TARGET_586)
+ emit_insn (gen_strlensi_unroll5 (operands[0],
+ operands[3],
+ scratch,
+ operands[0]));
+ else
+ emit_insn (gen_strlensi_unroll4 (operands[0],
+ operands[3],
+ scratch,
+ operands[0]));
+
+ /* gen_strlensi_unroll[45] returns the address of the zero
+ at the end of the string, like memchr(), so compute the
+ length by subtracting the startaddress. */
+ emit_insn (gen_subsi3 (operands[0], operands[0], address));
+ DONE;
+ }
+
operands[1] = copy_to_mode_reg (SImode, XEXP (operands[1], 0));
operands[4] = gen_reg_rtx (SImode);
operands[5] = gen_reg_rtx (SImode);
***************
*** 5759,5765 ****
(define_insn ""
[(set (match_operand:SI 0 "register_operand" "=&c")
(unspec:SI [(mem:BLK (match_operand:SI 1 "address_operand" "D"))
! (match_operand:QI 2 "register_operand" "a")
(match_operand:SI 3 "immediate_operand" "i")] 0))
(clobber (match_dup 1))]
""
--- 5800,5806 ----
(define_insn ""
[(set (match_operand:SI 0 "register_operand" "=&c")
(unspec:SI [(mem:BLK (match_operand:SI 1 "address_operand" "D"))
! (match_operand:QI 2 "immediate_operand" "a")
(match_operand:SI 3 "immediate_operand" "i")] 0))
(clobber (match_dup 1))]
""
***************
*** 5773,5775 ****
--- 5814,5837 ----
output_asm_insn (AS2 (mov%L0,%1,%0), xops);
return \"repnz\;scas%B2\";
}")
+
+ ;; the only difference between the following patterns is the register preference
+ ;; on a pentium using a q-register saves one clock cycle per 4 characters
+
+ (define_insn "strlensi_unroll4"
+ [(set (match_operand:SI 0 "register_operand" "=r,r")
+ (unspec:SI [(mem:BLK (match_operand:SI 3 "register_operand" "0,0"))
+ (match_operand:SI 1 "immediate_operand" "i,i")
+ (match_operand:SI 2 "register_operand" "=&q,&!r")] 0))
+ (clobber (match_dup 2))]
+ "(TARGET_486 && !TARGET_586 && optimize > 1)"
+ "* return output_strlen_unroll (operands);")
+
+ (define_insn "strlensi_unroll5"
+ [(set (match_operand:SI 0 "register_operand" "=r")
+ (unspec:SI [(mem:BLK (match_operand:SI 3 "register_operand" "0"))
+ (match_operand:SI 1 "immediate_operand" "i")
+ (match_operand:SI 2 "register_operand" "=&q")] 0))
+ (clobber (match_dup 2))]
+ "(TARGET_586 && optimize > 1)"
+ "* return output_strlen_unroll (operands);")
diff -rcp -x TAGS -x c-parse.y -x c-parse.c -x c-parse.h -x c-gperf.h -x cexp.c -x bi-parser.c -x objc-parse.y -x objc-parse.c -x gcc.?? -x gcc.??s -x gcc.aux -x gcc.info* -x cpp.?? -x cpp.??s -x cpp.aux -x cpp.info* -x */cp/parse.c -x */cp/parse.h -x Makefile gcc-2.7.2/invoke.texi gcc-2.7.2-new/invoke.texi
*** gcc-2.7.2/invoke.texi Tue Oct 3 16:40:43 1995
--- gcc-2.7.2-new/invoke.texi Sun Dec 17 11:07:56 1995
*************** in addition to the above:
*** 281,288 ****
-mstats -EL -EB -G @var{num} -nocpp
@emph{i386 Options}
! -m486 -m386 -mieee-fp -mno-fancy-math-387
! -mno-fp-ret-in-387 -msoft-float -msvr3-shlib
-mno-wide-multiply -mrtd -malign-double
-mreg-alloc=@var{list} -mregparm=@var{num}
-malign-jumps=@var{num} -malign-loops=@var{num}
--- 281,288 ----
-mstats -EL -EB -G @var{num} -nocpp
@emph{i386 Options}
! -m586 -mpentium -m486 -m386 -mieee-fp -mno-fancy-math-387
! -mno-fp-ret-in-387 -msoft-float -msvr3-shlib
-mno-wide-multiply -mrtd -malign-double
-mreg-alloc=@var{list} -mregparm=@var{num}
-malign-jumps=@var{num} -malign-loops=@var{num}
*************** These @samp{-m} options are defined for
*** 3666,3673 ****
@table @code
@item -m486
@itemx -m386
! Control whether or not code is optimized for a 486 instead of an
! 386. Code generated for an 486 will run on a 386 and vice versa.
@item -mieee-fp
@itemx -mno-ieee-fp
--- 3666,3676 ----
@table @code
@item -m486
@itemx -m386
! @itemx -m586
! @itemx -mpentium
! Control whether or not code is optimized for a 486 or a pentium instead of an
! 386. Code generated for any of these processors will run on any i386 family
! processor.
@item -mieee-fp
@itemx -mno-ieee-fp
diff -rcp -x TAGS -x c-parse.y -x c-parse.c -x c-parse.h -x c-gperf.h -x cexp.c -x bi-parser.c -x objc-parse.y -x objc-parse.c -x gcc.?? -x gcc.??s -x gcc.aux -x gcc.info* -x cpp.?? -x cpp.??s -x cpp.aux -x cpp.info* -x */cp/parse.c -x */cp/parse.h -x Makefile gcc-2.7.2/md.texi gcc-2.7.2-new/md.texi
*** gcc-2.7.2/md.texi Sun Nov 26 20:05:33 1995
--- gcc-2.7.2-new/md.texi Mon Dec 11 09:37:22 1995
*************** define these patterns.
*** 2046,2052 ****
@cindex @code{s@var{cond}} instruction pattern
@item @samp{s@var{cond}}
Store zero or nonzero in the operand according to the condition codes.
! Value stored is nonzero iff the condition @var{cond} is true.
@var{cond} is the name of a comparison operation expression code, such
as @code{eq}, @code{lt} or @code{leu}.
--- 2046,2052 ----
@cindex @code{s@var{cond}} instruction pattern
@item @samp{s@var{cond}}
Store zero or nonzero in the operand according to the condition codes.
! Value stored is nonzero if the condition @var{cond} is true.
@var{cond} is the name of a comparison operation expression code, such
as @code{eq}, @code{lt} or @code{leu}.
-
Wolfgang Formann (wolfi@exner.tgm.ac.at)
Technical Details
Message-ID:
<199512180948.KAA24900@asus1.fw.com>Path:
arc1-ca.pugleaf.net!unknown!not-for-mailShow Full Headers
Complete Article Headers
X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 113d02,ef0c82b2c9aaf016,start X-Google-Attributes: gid113d02,public From: wolfi@asus1.fw.COM (Wolfgang Formann) Subject: patch for faster version of strlen Date: 1995/12/18 Message-ID: <199512180948.KAA24900@asus1.fw.com> X-Deja-AN: 122223930 distribution: gnu sender: daemon@cis.ohio-state.edu organization: GNUs Not Usenet newsgroups: gnu.gcc.bug