xref: /netbsd-src/common/lib/libc/string/memset2.c (revision 5ceb9d96fa1f8e4baa9529dbcb36d3f91004f421)
1 /*	$NetBSD: memset2.c,v 1.11 2022/01/15 10:38:56 andvar Exp $	*/
2 
3 /*-
4  * Copyright (c) 2009 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Matt Thomas <matt@3am-software.com>.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #if defined(LIBC_SCCS) && !defined(lint)
34 __RCSID("$NetBSD: memset2.c,v 1.11 2022/01/15 10:38:56 andvar Exp $");
35 #endif /* LIBC_SCCS and not lint */
36 
37 #include <sys/types.h>
38 
39 #if !defined(_KERNEL) && !defined(_STANDALONE)
40 #include <assert.h>
41 #include <limits.h>
42 #include <string.h>
43 #include <inttypes.h>
44 #else
45 #include <lib/libkern/libkern.h>
46 #include <machine/limits.h>
47 #endif
48 
49 #include <sys/endian.h>
50 #include <machine/types.h>
51 
52 #undef __OPTIMIZE_SIZE__
53 #define __OPTIMIZE_SIZE__ 1	/* other code path is very broken */
54 
55 #ifdef TEST
56 #include <assert.h>
57 #define _DIAGASSERT(a)		assert(a)
58 #endif
59 
60 #ifdef _FORTIFY_SOURCE
61 #undef bzero
62 #endif
63 #undef memset
64 
65 /*
66  * Assume __register_t is the widest non-synthetic unsigned type.
67  */
68 typedef __register_t memword_t;
69 
70 #ifdef BZERO
71 static inline
72 #define	memset memset0
73 #endif
74 
75 #ifdef TEST
76 static
77 #define memset test_memset
78 #endif
79 
80 void *
memset(void * addr,int c,size_t len)81 memset(void *addr, int c, size_t len)
82 {
83 	memword_t *dstp = addr;
84 	memword_t *edstp;
85 	memword_t fill;
86 #ifndef __OPTIMIZE_SIZE__
87 	memword_t keep_mask = 0;
88 #endif
89 	size_t fill_count;
90 
91 	_DIAGASSERT(addr != 0);
92 
93 	if (__predict_false(len == 0))
94 		return addr;
95 
96 	/*
97 	 * Pad out the fill byte (v) across a memword_t.
98 	 * The conditional at the end prevents GCC from complaining about
99 	 * shift count >= width of type
100 	 */
101 	fill = (unsigned char)c;
102 	fill |= fill << 8;
103 	fill |= fill << 16;
104 	fill |= fill << (sizeof(c) < sizeof(fill) ? 32 : 0);
105 
106 	/*
107 	 * Get the number of unaligned bytes to fill in the first word.
108 	 */
109 	fill_count = -(uintptr_t)addr & (sizeof(memword_t) - 1);
110 
111 	if (__predict_false(fill_count != 0)) {
112 #ifndef __OPTIMIZE_SIZE__
113 		/*
114 		 * We want to clear <fill_count> trailing bytes in the word.
115 		 * On big/little endian, these are the least/most significant,
116 		 * bits respectively.  So as we shift, the keep_mask will only
117 		 * have bits set for the bytes we won't be filling.
118 		 */
119 #if BYTE_ORDER == BIG_ENDIAN
120 		keep_mask = ~(memword_t)0U << (fill_count * 8);
121 #endif
122 #if BYTE_ORDER == LITTLE_ENDIAN
123 		keep_mask = ~(memword_t)0U >> (fill_count * 8);
124 #endif
125 		/*
126 		 * Make sure dstp is aligned to a memword_t boundary.
127 		 */
128 		dstp = (memword_t *)((uintptr_t)addr & -sizeof(memword_t));
129 		if (len >= fill_count) {
130 			/*
131 			 * If we can fill the rest of this word, then we mask
132 			 * off the bytes we are filling and then fill in those
133 			 * bytes with the new fill value.
134 			 */
135 			*dstp = (*dstp & keep_mask) | (fill & ~keep_mask);
136 			len -= fill_count;
137 			if (__predict_false(len == 0))
138 				return addr;
139 			/*
140 			 * Since we were able to fill the rest of this word,
141 			 * we will advance to the next word and thus have no
142 			 * bytes to preserve.
143 			 *
144 			 * If we don't have enough to fill the rest of this
145 			 * word, we will fall through the following loop
146 			 * (since there are no full words to fill).  Then we
147 			 * use the keep_mask above to preserve the leading
148 			 * bytes of word.
149 			 */
150 			dstp++;
151 			keep_mask = 0;
152 		} else {
153 			len += (uintptr_t)addr & (sizeof(memword_t) - 1);
154 		}
155 #else /* __OPTIMIZE_SIZE__ */
156 		uint8_t *dp, *ep;
157 		if (len < fill_count)
158 			fill_count = len;
159 		for (dp = (uint8_t *)dstp, ep = dp + fill_count;
160 		     dp != ep; dp++)
161 			*dp = fill;
162 		if ((len -= fill_count) == 0)
163 			return addr;
164 		dstp = (memword_t *)ep;
165 #endif /* __OPTIMIZE_SIZE__ */
166 	}
167 
168 	/*
169 	 * Simply fill memory one word at time (for as many full words we have
170 	 * to write).
171 	 */
172 	for (edstp = dstp + len / sizeof(memword_t); dstp != edstp; dstp++)
173 		*dstp = fill;
174 
175 	/*
176 	 * We didn't subtract out the full words we just filled since we know
177 	 * by the time we get here we will have less than a words worth to
178 	 * write.  So we can concern ourselves with only the subword len bits.
179 	 */
180 	len &= sizeof(memword_t)-1;
181 	if (len > 0) {
182 #ifndef __OPTIMIZE_SIZE__
183 		/*
184 		 * We want to clear <len> leading bytes in the word.
185 		 * On big/little endian, these are the most/least significant
186 		 * bits, respectively.  But as we want the mask of the bytes to
187 		 * keep, we have to complement the mask.  So after we shift,
188 		 * the keep_mask will only have bits set for the bytes we won't
189 		 * be filling.
190 		 *
191 		 * But the keep_mask could already have bytes to preserve
192 		 * if the amount to fill was less than the amount of trailing
193 		 * space in the first word.
194 		 */
195 #if BYTE_ORDER == BIG_ENDIAN
196 		keep_mask |= ~(memword_t)0U >> (len * 8);
197 #endif
198 #if BYTE_ORDER == LITTLE_ENDIAN
199 		keep_mask |= ~(memword_t)0U << (len * 8);
200 #endif
201 		/*
202 		 * Now we mask off the bytes we are filling and then fill in
203 		 * those bytes with the new fill value.
204 		 */
205 		*dstp = (*dstp & keep_mask) | (fill & ~keep_mask);
206 #else /* __OPTIMIZE_SIZE__ */
207 		uint8_t *dp, *ep;
208 		for (dp = (uint8_t *)dstp, ep = dp + len;
209 		     dp != ep; dp++)
210 			*dp = fill;
211 #endif /* __OPTIMIZE_SIZE__ */
212 	}
213 
214 	/*
215 	 * Return the initial addr
216 	 */
217 	return addr;
218 }
219 
220 #ifdef BZERO
221 /*
222  * For bzero, simply inline memset and let the compiler optimize things away.
223  */
224 void
bzero(void * addr,size_t len)225 bzero(void *addr, size_t len)
226 {
227 	memset(addr, 0, len);
228 }
229 #endif
230 
231 #ifdef TEST
232 #include <stdbool.h>
233 #include <stdio.h>
234 
235 #undef memset
236 
237 static union {
238 	uint8_t bytes[sizeof(memword_t) * 4];
239 	memword_t words[4];
240 } testmem;
241 
242 int
main(int argc,char ** argv)243 main(int argc, char **argv)
244 {
245 	size_t start;
246 	size_t len;
247 	bool failed = false;
248 
249 	for (start = 1; start < sizeof(testmem) - 1; start++) {
250 		for (len = 1; start + len < sizeof(testmem) - 1; len++) {
251 			bool ok = true;
252 			size_t i;
253 			uint8_t check_value;
254 			memset(testmem.bytes, 0xff, sizeof(testmem));
255 			test_memset(testmem.bytes + start, 0x00, len);
256 			for (i = 0; i < sizeof(testmem); i++) {
257 				if (i == 0 || i == start + len)
258 					check_value = 0xff;
259 				else if (i == start)
260 					check_value = 0x00;
261 				if (testmem.bytes[i] != check_value) {
262 					if (ok)
263 						printf("pass @ %zu .. %zu failed",
264 						    start, start + len - 1);
265 					ok = false;
266 					printf(" [%zu]=0x%02x(!0x%02x)",
267 					    i, testmem.bytes[i], check_value);
268 				}
269 			}
270 			if (!ok) {
271 				printf("\n");
272 				failed = 1;
273 			}
274 		}
275 	}
276 
277 	return failed ? 1 : 0;
278 }
279 #endif /* TEST */
280