summaryrefslogtreecommitdiff
path: root/usr/src/lib/libc/i386/gen/strcmp.c
blob: 6f204e2e0aa1ccd4cac3d5e7abfbd4630af4260a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Fast strcmp.  This works one int at a time, using aligned pointers
 * if possible, misaligned pointers if necessary.  To avoid taking
 * faults from going off the end of a page, the code is careful to go
 * a byte-at-a-time when a misaligned pointer is near a page boundary.
 * The code is almost portable, but see the assumptions below.
 */

/*
 * ASSUMPTIONS:
 * sizeof (int) is not greater than 8.
 * sizeof (int) is a power of 2.
 * An int pointer can always be dereferenced even if it is not properly
 *   aligned (though aligned references are assumed to be faster).
 * It is OK to assign bogus values to a pointer (in particular, a
 *   value that is before the beginning of the string) as long as that
 *   pointer is only used with indices big enough to bring us back into
 *   the string.
 * It is OK to reference bytes past the end of a string as long as we
 *   don't cross a page boundary.
 */

#include "lint.h"
#include <limits.h>
#include <unistd.h>
#include <sys/sysconfig.h>
#include "libc.h"

/*
 * This strange expression will test to see if *any* byte in the int is
 * a NUL.  The constants are big enough to allow for ints up to 8 bytes.
 * The two arguments are actually two copies of the same value; this
 * allows the compiler freedom to play with both values for efficiency.
 */
#define	ANYNUL(i1, i2)	(((i1) - (int)0x0101010101010101LL) & ~(i2) & \
		(int)0x8080808080808080ULL)

int
strcmp(const char *str1, const char *str2)
{
	int *s1, *s2;
	int i1, i2;
	int count;
	int b1, b2;
	static int pagesize;

	if (str1 == str2)
		return (0);

	/*
	 * Go 1 byte at a time until at least one pointer is word aligned.
	 * Assumes that sizeof (int) is a power of 2.
	 */
	while ((((int) str1) & (sizeof (int) - 1)) &&
	    (((int) str2) & (sizeof (int) - 1))) {
one_byte:
		if (*str1 != *str2)
			return ((unsigned char)*str1 - (unsigned char)*str2);
		if (*str1 == '\0')
			return (0);
		++str1;
		++str2;
	}

	/*
	 * If one pointer is misaligned, we must be careful not to
	 * dereference it when it points across a page boundary.
	 * If we did, we might go past the end of the segment and
	 * get a SIGSEGV.  Set "count" to the number of ints we can
	 * scan before running into such a boundary.
	 */
	count = INT_MAX;
	if (((int) str1) & (sizeof (int) - 1)) {
		if (pagesize == 0)
			pagesize = _sysconfig(_CONFIG_PAGESIZE);
		count = (pagesize - ((int)str1 & (pagesize - 1))) /
			sizeof (int);
	} else if (((int) str2) & (sizeof (int) - 1)) {
		if (pagesize == 0)
			pagesize = _sysconfig(_CONFIG_PAGESIZE);
		count = (pagesize - ((int)str2 & (pagesize - 1))) /
			sizeof (int);
	}

	s1 = (void *) str1;
	s2 = (void *) str2;

	/*
	 * Go "sizeof (int)" bytes at a time until at least one pointer
	 * is word aligned.
	 *
	 * Unwrap the loop for even a bit more speed.
	 */
	for (;;) {
		/*
		 * Check whether we can test the next 4 ints without
		 * hitting a page boundary.  If we can only test 1, 2,
		 * or 3, go and do that first.  If we can't check any
		 * more, go and test one byte, realign, and start again.
		 */
		count -= 4;
		switch (count) {
		case -1:
			--s1;
			--s2;
			goto do3;	/* check only 3 ints */
		case -2:
			s1 -= 2;
			s2 -= 2;
			goto do2;	/* check only 2 ints */
		case -3:
			s1 -= 3;
			s2 -= 3;
			goto do1;	/* check only 1 int */
		case -4:
		case -5:		/* -5, -6, and -7 come up on the */
		case -6:		/* next time around after we do one */
		case -7:		/* of the 3 gotos above */
			str1 = (void *) s1;
			str2 = (void *) s2;
			goto one_byte;
			/*
			 * The goto above should be explained.  By going
			 * into the middle of the loop, it makes sure
			 * that we advance at least one byte.  We will
			 * stay in that loop until the misaligned pointer
			 * becomes aligned (at the page boundary).  We
			 * will then break out of that loop with the
			 * formerly misaligned pointer now aligned, the
			 * formerly aligned pointer now misaligned, and
			 * we will come back into this loop until the
			 * latter pointer reaches a page boundary.
			 */
		default:		/* at least 4 ints to go */
			break;
		}

		i1 = s1[0];
		i2 = s2[0];
		if (i1 != i2)
			break;
		else if (ANYNUL(i1, i2))
			return (0);

do3:
		i1 = s1[1];
		i2 = s2[1];
		if (i1 != i2)
			break;
		else if (ANYNUL(i1, i2))
			return (0);

do2:
		i1 = s1[2];
		i2 = s2[2];
		if (i1 != i2)
			break;
		else if (ANYNUL(i1, i2))
			return (0);

do1:
		i1 = s1[3];
		i2 = s2[3];
		if (i1 != i2)
			break;
		else if (ANYNUL(i1, i2))
			return (0);

		s1 += 4;
		s2 += 4;
	}

	/* We found a difference.  Go one byte at a time to find where. */
	b1 = i1;		/* save the ints in memory */
	b2 = i2;
	str1 = (void *) &b1;	/* point at them */
	str2 = (void *) &b2;
	while (*str1 == *str2) {
		if (*str1 == '\0')
			return (0);
		++str1;
		++str2;
	}
	return ((unsigned char)*str1 - (unsigned char)*str2);
}