[runtime] Rework how unaligned memcpy/memset are generated.
[mono.git] / mono / mini / memory-access.c
1 /**
2  * Emit memory access for the front-end.
3  *
4  */
5
6 #include <config.h>
7 #include <mono/utils/mono-compiler.h>
8
9 #ifndef DISABLE_JIT
10
11 #include <mono/utils/mono-memory-model.h>
12
13 #include "mini.h"
14 #include "ir-emit.h"
15
16 #define MAX_INLINE_COPIES 10
17
18 void 
19 mini_emit_memset (MonoCompile *cfg, int destreg, int offset, int size, int val, int align)
20 {
21         int val_reg;
22
23         /*FIXME arbitrary hack to avoid unbound code expansion.*/
24         g_assert (size < 10000);
25         g_assert (val == 0);
26         g_assert (align > 0);
27
28         if ((size <= SIZEOF_REGISTER) && (size <= align)) {
29                 switch (size) {
30                 case 1:
31                         MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI1_MEMBASE_IMM, destreg, offset, val);
32                         return;
33                 case 2:
34                         MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI2_MEMBASE_IMM, destreg, offset, val);
35                         return;
36                 case 4:
37                         MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI4_MEMBASE_IMM, destreg, offset, val);
38                         return;
39 #if SIZEOF_REGISTER == 8
40                 case 8:
41                         MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI8_MEMBASE_IMM, destreg, offset, val);
42                         return;
43 #endif
44                 }
45         }
46
47         val_reg = alloc_preg (cfg);
48
49         if (SIZEOF_REGISTER == 8)
50                 MONO_EMIT_NEW_I8CONST (cfg, val_reg, val);
51         else
52                 MONO_EMIT_NEW_ICONST (cfg, val_reg, val);
53
54         if (align < SIZEOF_VOID_P) {
55                 if (align % 2 == 1)
56                         goto set_1;
57                 if (align % 4 == 2)
58                         goto set_2;
59                 if (SIZEOF_VOID_P == 8 && align % 8 == 4)
60                         goto set_4;
61         }
62
63         //Unaligned offsets don't naturaly happen in the runtime, so it's ok to be conservative in how we copy
64         //We assume that input src and dest are be aligned to `align` so offset just worsen it
65         int offsets_mask = offset & 0x7; //we only care about the misalignment part
66         if (offsets_mask) {
67                 if (offsets_mask % 2 == 1)
68                         goto set_1;
69                 if (offsets_mask % 4 == 2)
70                         goto set_2;
71                 if (SIZEOF_VOID_P == 8 && offsets_mask % 8 == 4)
72                         goto set_4;
73         }
74
75         if (SIZEOF_REGISTER == 8) {
76                 while (size >= 8) {
77                         MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI8_MEMBASE_REG, destreg, offset, val_reg);
78                         offset += 8;
79                         size -= 8;
80                 }
81         }
82
83 set_4:
84         while (size >= 4) {
85                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI4_MEMBASE_REG, destreg, offset, val_reg);
86                 offset += 4;
87                 size -= 4;
88         }
89
90
91 set_2:
92         while (size >= 2) {
93                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI2_MEMBASE_REG, destreg, offset, val_reg);
94                 offset += 2;
95                 size -= 2;
96         }
97
98 set_1:
99         while (size >= 1) {
100                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI1_MEMBASE_REG, destreg, offset, val_reg);
101                 offset += 1;
102                 size -= 1;
103         }
104 }
105
106 void 
107 mini_emit_memcpy (MonoCompile *cfg, int destreg, int doffset, int srcreg, int soffset, int size, int align)
108 {
109         int cur_reg;
110
111         /*FIXME arbitrary hack to avoid unbound code expansion.*/
112         g_assert (size < 10000);
113         g_assert (align > 0);
114
115         if (align < SIZEOF_VOID_P) {
116                 if (align == 4)
117                         goto copy_4;
118                 if (align == 2)
119                         goto copy_2;
120                 goto copy_1;
121         }
122
123         //Unaligned offsets don't naturaly happen in the runtime, so it's ok to be conservative in how we copy
124         //We assume that input src and dest are be aligned to `align` so offset just worsen it
125         int offsets_mask = (doffset | soffset) & 0x7; //we only care about the misalignment part
126         if (offsets_mask) {
127                 if (offsets_mask % 2 == 1)
128                         goto copy_1;
129                 if (offsets_mask % 4 == 2)
130                         goto copy_2;
131                 if (SIZEOF_VOID_P == 8 && offsets_mask % 8 == 4)
132                         goto copy_4;
133         }
134
135
136         if (SIZEOF_REGISTER == 8) {
137                 while (size >= 8) {
138                         cur_reg = alloc_preg (cfg);
139                         MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI8_MEMBASE, cur_reg, srcreg, soffset);
140                         MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI8_MEMBASE_REG, destreg, doffset, cur_reg);
141                         doffset += 8;
142                         soffset += 8;
143                         size -= 8;
144                 }
145         }
146
147 copy_4:
148         while (size >= 4) {
149                 cur_reg = alloc_preg (cfg);
150                 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI4_MEMBASE, cur_reg, srcreg, soffset);
151                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI4_MEMBASE_REG, destreg, doffset, cur_reg);
152                 doffset += 4;
153                 soffset += 4;
154                 size -= 4;
155         }
156
157 copy_2:
158         while (size >= 2) {
159                 cur_reg = alloc_preg (cfg);
160                 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI2_MEMBASE, cur_reg, srcreg, soffset);
161                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI2_MEMBASE_REG, destreg, doffset, cur_reg);
162                 doffset += 2;
163                 soffset += 2;
164                 size -= 2;
165         }
166
167 copy_1:
168         while (size >= 1) {
169                 cur_reg = alloc_preg (cfg);
170                 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI1_MEMBASE, cur_reg, srcreg, soffset);
171                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI1_MEMBASE_REG, destreg, doffset, cur_reg);
172                 doffset += 1;
173                 soffset += 1;
174                 size -= 1;
175         }
176 }
177
178 static void
179 mini_emit_memcpy_internal (MonoCompile *cfg, MonoInst *dest, MonoInst *src, MonoInst *size_ins, int size, int align)
180 {
181         /* FIXME: Optimize the case when src/dest is OP_LDADDR */
182
183         /* We can't do copies at a smaller granule than the provided alignment */
184         if (size_ins || ((size / align > MAX_INLINE_COPIES) && !(cfg->opt & MONO_OPT_INTRINS))) {
185                 MonoInst *iargs [3];
186                 iargs [0] = dest;
187                 iargs [1] = src;
188
189                 if (!size_ins)
190                         EMIT_NEW_ICONST (cfg, size_ins, size);
191                 iargs [2] = size_ins;
192                 mono_emit_method_call (cfg, mini_get_memcpy_method (), iargs, NULL);
193         } else {
194                 mini_emit_memcpy (cfg, dest->dreg, 0, src->dreg, 0, size, align);
195         }
196 }
197
198 static void
199 mini_emit_memset_internal (MonoCompile *cfg, MonoInst *dest, MonoInst *value_ins, int value, MonoInst *size_ins, int size, int align)
200 {
201         /* FIXME: Optimize the case when dest is OP_LDADDR */
202
203         /* We can't do copies at a smaller granule than the provided alignment */
204         if (value_ins || size_ins || value != 0 || ((size / align > MAX_INLINE_COPIES) && !(cfg->opt & MONO_OPT_INTRINS))) {
205                 MonoInst *iargs [3];
206                 iargs [0] = dest;
207
208                 if (!value_ins)
209                         EMIT_NEW_ICONST (cfg, value_ins, value);
210                 iargs [1] = value_ins;
211
212                 if (!size_ins)
213                         EMIT_NEW_ICONST (cfg, size_ins, size);
214                 iargs [2] = size_ins;
215
216                 mono_emit_method_call (cfg, mini_get_memset_method (), iargs, NULL);
217         } else {
218                 mini_emit_memset (cfg, dest->dreg, 0, size, value, align);
219         }
220 }
221
222 static void
223 mini_emit_memcpy_const_size (MonoCompile *cfg, MonoInst *dest, MonoInst *src, int size, int align)
224 {
225         mini_emit_memcpy_internal (cfg, dest, src, NULL, size, align);
226 }
227
228 static void
229 mini_emit_memset_const_size (MonoCompile *cfg, MonoInst *dest, int value, int size, int align)
230 {
231         mini_emit_memset_internal (cfg, dest, NULL, value, NULL, size, align);
232 }
233
234 MonoInst*
235 mini_emit_memory_load (MonoCompile *cfg, MonoType *type, MonoInst *src, int offset, int ins_flag)
236 {
237         MonoInst *ins;
238
239         EMIT_NEW_LOAD_MEMBASE_TYPE (cfg, ins, type, src->dreg, offset);
240         ins->flags |= ins_flag;
241
242         if (ins_flag & MONO_INST_VOLATILE) {
243                 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
244                 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_ACQ);
245         }
246
247         return ins;
248 }
249
250
251 void
252 mini_emit_memory_store (MonoCompile *cfg, MonoType *type, MonoInst *dest, MonoInst *value, int ins_flag)
253 {
254         MonoInst *ins;
255
256         if (ins_flag & MONO_INST_VOLATILE) {
257                 /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
258                 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL);
259         }
260         /* FIXME: should check item at sp [1] is compatible with the type of the store. */
261
262         EMIT_NEW_STORE_MEMBASE_TYPE (cfg, ins, type, dest->dreg, 0, value->dreg);
263         ins->flags |= ins_flag;
264         if (cfg->gen_write_barriers && cfg->method->wrapper_type != MONO_WRAPPER_WRITE_BARRIER &&
265                 mini_type_is_reference (type) && !MONO_INS_IS_PCONST_NULL (value)) {
266                 /* insert call to write barrier */
267                 mini_emit_write_barrier (cfg, dest, value);
268         }
269 }
270
271 void
272 mini_emit_memory_copy_bytes (MonoCompile *cfg, MonoInst *dest, MonoInst *src, MonoInst *size, int ins_flag)
273 {
274         int align = SIZEOF_VOID_P;
275
276         /*
277          * FIXME: It's unclear whether we should be emitting both the acquire
278          * and release barriers for cpblk. It is technically both a load and
279          * store operation, so it seems like that's the sensible thing to do.
280          *
281          * FIXME: We emit full barriers on both sides of the operation for
282          * simplicity. We should have a separate atomic memcpy method instead.
283          */
284         if (ins_flag & MONO_INST_VOLATILE) {
285                 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
286                 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
287         }
288
289         if ((cfg->opt & MONO_OPT_INTRINS) && (size->opcode == OP_ICONST) && size->inst_c0 < 10000) {
290                 mini_emit_memcpy_const_size (cfg, dest, src, size->inst_c0, align);
291         } else {
292                 if (cfg->verbose_level > 3)
293                         printf ("EMITING REGULAR COPY\n");
294                 mini_emit_memcpy_internal (cfg, dest, src, size, 0, align);
295         }
296
297         if (ins_flag & MONO_INST_VOLATILE) {
298                 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
299                 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
300         }
301 }
302
303 void
304 mini_emit_memory_init_bytes (MonoCompile *cfg, MonoInst *dest, MonoInst *value, MonoInst *size, int ins_flag)
305 {
306         int align = SIZEOF_VOID_P;
307
308         if (ins_flag & MONO_INST_VOLATILE) {
309                 /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
310                 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL);
311         }
312
313         //FIXME unrolled memset only supports zeroing
314         if ((cfg->opt & MONO_OPT_INTRINS) && (size->opcode == OP_ICONST) && (value->opcode == OP_ICONST) && (value->inst_c0 == 0)) {
315                 mini_emit_memset_const_size (cfg, dest, value->inst_c0, size->inst_c0, align);
316         } else {
317                 mini_emit_memset_internal (cfg, dest, value, 0, size, 0, align);
318         }
319
320 }
321
322 #endif