f657b5532ee76999e868360a7dbc69f3e5fe4b0b
[mono.git] / mono / utils / memfuncs.c
1 /*
2  * memfuncs.c: Our own bzero/memmove.
3  *
4  * Copyright (C) 2013-2015 Xamarin Inc
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License 2.0 as published by the Free Software Foundation;
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License 2.0 along with this library; if not, write to the Free
17  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19
20 /*
21  * SGen cannot deal with invalid pointers on the heap or in registered roots.  Sometimes we
22  * need to copy or zero out memory in code that might be interrupted by collections.  To
23  * guarantee that those operations will not result in invalid pointers, we must do it
24  * word-atomically.
25  *
26  * libc's bzero() and memcpy()/memmove() functions do not guarantee word-atomicity, even in
27  * cases where one would assume so.  For instance, some implementations (like Darwin's on
28  * x86) have variants of memcpy() using vector instructions.  Those may copy bytewise for
29  * the region preceding the first vector-aligned address.  That region could be
30  * word-aligned, but it would still be copied byte-wise.
31  *
32  * All our memory writes here are to "volatile" locations.  This is so that C compilers
33  * don't "optimize" our code back to calls to bzero()/memmove().  LLVM, specifically, will
34  * do that.
35  */
36
37 #include <config.h>
38 #include <glib.h>
39 #include <string.h>
40
41 #include "memfuncs.h"
42
43 #define ptr_mask ((sizeof (void*) - 1))
44 #define _toi(ptr) ((size_t)ptr)
45 #define unaligned_bytes(ptr) (_toi(ptr) & ptr_mask)
46 #define align_down(ptr) ((void*)(_toi(ptr) & ~ptr_mask))
47 #define align_up(ptr) ((void*) ((_toi(ptr) + ptr_mask) & ~ptr_mask))
48 #if SIZEOF_VOID_P == 4
49 #define bytes_to_words(n)       ((size_t)(n) >> 2)
50 #elif SIZEOF_VOID_P == 8
51 #define bytes_to_words(n)       ((size_t)(n) >> 3)
52 #else
53 #error We only support 32 and 64 bit architectures.
54 #endif
55
56 #define BZERO_WORDS(dest,words) do {                    \
57                 void * volatile *__d = (void* volatile*)(dest);         \
58                 int __n = (words);                      \
59                 int __i;                                \
60                 for (__i = 0; __i < __n; ++__i)         \
61                         __d [__i] = NULL;               \
62         } while (0)
63
64
65 /**
66  * mono_gc_bzero_aligned:
67  * @dest: address to start to clear
68  * @size: size of the region to clear
69  *
70  * Zero @size bytes starting at @dest.
71  * The address of @dest MUST be aligned to word boundaries
72  *
73  * FIXME borrow faster code from some BSD libc or bionic
74  */
75 void
76 mono_gc_bzero_aligned (void *dest, size_t size)
77 {
78         volatile char *d = (char*)dest;
79         size_t tail_bytes, word_bytes;
80
81         g_assert (unaligned_bytes (dest) == 0);
82
83         /* copy all words with memmove */
84         word_bytes = (size_t)align_down (size);
85         switch (word_bytes) {
86         case sizeof (void*) * 1:
87                 BZERO_WORDS (d, 1);
88                 break;
89         case sizeof (void*) * 2:
90                 BZERO_WORDS (d, 2);
91                 break;
92         case sizeof (void*) * 3:
93                 BZERO_WORDS (d, 3);
94                 break;
95         case sizeof (void*) * 4:
96                 BZERO_WORDS (d, 4);
97                 break;
98         default:
99                 BZERO_WORDS (d, bytes_to_words (word_bytes));
100         }
101
102         tail_bytes = unaligned_bytes (size);
103         if (tail_bytes) {
104                 d += word_bytes;
105                 do {
106                         *d++ = 0;
107                 } while (--tail_bytes);
108         }
109 }
110
111 /**
112  * mono_gc_bzero_atomic:
113  * @dest: address to start to clear
114  * @size: size of the region to clear
115  *
116  * Zero @size bytes starting at @dest.
117  *
118  * Use this to zero memory without word tearing when dest is aligned.
119  */
120 void
121 mono_gc_bzero_atomic (void *dest, size_t size)
122 {
123         if (unaligned_bytes (dest))
124                 memset (dest, 0, size);
125         else
126                 mono_gc_bzero_aligned (dest, size);
127 }
128
129 #define MEMMOVE_WORDS_UPWARD(dest,src,words) do {       \
130                 void * volatile *__d = (void* volatile*)(dest);         \
131                 void **__s = (void**)(src);             \
132                 int __n = (int)(words);                 \
133                 int __i;                                \
134                 for (__i = 0; __i < __n; ++__i)         \
135                         __d [__i] = __s [__i];          \
136         } while (0)
137
138 #define MEMMOVE_WORDS_DOWNWARD(dest,src,words) do {     \
139                 void * volatile *__d = (void* volatile*)(dest);         \
140                 void **__s = (void**)(src);             \
141                 int __n = (int)(words);                 \
142                 int __i;                                \
143                 for (__i = __n - 1; __i >= 0; --__i)    \
144                         __d [__i] = __s [__i];          \
145         } while (0)
146
147
148 /**
149  * mono_gc_memmove_aligned:
150  * @dest: destination of the move
151  * @src: source
152  * @size: size of the block to move
153  *
154  * Move @size bytes from @src to @dest.
155  *
156  * Use this to copy memory without word tearing when both pointers are aligned
157  */void
158 mono_gc_memmove_aligned (void *dest, const void *src, size_t size)
159 {
160         g_assert (unaligned_bytes (dest) == 0);
161         g_assert (unaligned_bytes (src) == 0);
162
163         /*
164         If we're copying less than a word we don't need to worry about word tearing
165         so we bailout to memmove early.
166         */
167         if (size < sizeof(void*)) {
168                 memmove (dest, src, size);
169                 return;
170         }
171
172         /*
173          * A bit of explanation on why we align only dest before doing word copies.
174          * Pointers to managed objects must always be stored in word aligned addresses, so
175          * even if dest is misaligned, src will be by the same amount - this ensure proper atomicity of reads.
176          *
177          * We don't need to case when source and destination have different alignments since we only do word stores
178          * using memmove, which must handle it.
179          */
180         if (dest > src && ((size_t)((char*)dest - (char*)src) < size)) { /*backward copy*/
181                         volatile char *p = (char*)dest + size;
182                         char *s = (char*)src + size;
183                         char *start = (char*)dest;
184                         char *align_end = MAX((char*)dest, (char*)align_down (p));
185                         char *word_start;
186                         size_t bytes_to_memmove;
187
188                         while (p > align_end)
189                                 *--p = *--s;
190
191                         word_start = (char *)align_up (start);
192                         bytes_to_memmove = p - word_start;
193                         p -= bytes_to_memmove;
194                         s -= bytes_to_memmove;
195                         MEMMOVE_WORDS_DOWNWARD (p, s, bytes_to_words (bytes_to_memmove));
196         } else {
197                 volatile char *d = (char*)dest;
198                 const char *s = (const char*)src;
199                 size_t tail_bytes;
200
201                 /* copy all words with memmove */
202                 MEMMOVE_WORDS_UPWARD (d, s, bytes_to_words (align_down (size)));
203
204                 tail_bytes = unaligned_bytes (size);
205                 if (tail_bytes) {
206                         d += (size_t)align_down (size);
207                         s += (size_t)align_down (size);
208                         do {
209                                 *d++ = *s++;
210                         } while (--tail_bytes);
211                 }
212         }
213 }
214
215 /**
216  * mono_gc_memmove_atomic:
217  * @dest: destination of the move
218  * @src: source
219  * @size: size of the block to move
220  *
221  * Move @size bytes from @src to @dest.
222  *
223  * Use this to copy memory without word tearing when both pointers are aligned
224  */
225 void
226 mono_gc_memmove_atomic (void *dest, const void *src, size_t size)
227 {
228         if (unaligned_bytes (_toi (dest) | _toi (src)))
229                 memmove (dest, src, size);
230         else
231                 mono_gc_memmove_aligned (dest, src, size);
232 }