[mini] Remove assert and document reason it can't be used.
[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/metadata/gc-internals.h>
12 #include <mono/utils/mono-memory-model.h>
13
14 #include "mini.h"
15 #include "ir-emit.h"
16 #include "jit-icalls.h"
17
18 #define MAX_INLINE_COPIES 10
19
20 void 
21 mini_emit_memset (MonoCompile *cfg, int destreg, int offset, int size, int val, int align)
22 {
23         int val_reg;
24
25         /*FIXME arbitrary hack to avoid unbound code expansion.*/
26         g_assert (size < 10000);
27         g_assert (val == 0);
28         g_assert (align > 0);
29
30         if ((size <= SIZEOF_REGISTER) && (size <= align)) {
31                 switch (size) {
32                 case 1:
33                         MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI1_MEMBASE_IMM, destreg, offset, val);
34                         return;
35                 case 2:
36                         MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI2_MEMBASE_IMM, destreg, offset, val);
37                         return;
38                 case 4:
39                         MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI4_MEMBASE_IMM, destreg, offset, val);
40                         return;
41 #if SIZEOF_REGISTER == 8
42                 case 8:
43                         MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI8_MEMBASE_IMM, destreg, offset, val);
44                         return;
45 #endif
46                 }
47         }
48
49         val_reg = alloc_preg (cfg);
50
51         if (SIZEOF_REGISTER == 8)
52                 MONO_EMIT_NEW_I8CONST (cfg, val_reg, val);
53         else
54                 MONO_EMIT_NEW_ICONST (cfg, val_reg, val);
55
56         if (align < SIZEOF_VOID_P) {
57                 if (align % 2 == 1)
58                         goto set_1;
59                 if (align % 4 == 2)
60                         goto set_2;
61                 if (SIZEOF_VOID_P == 8 && align % 8 == 4)
62                         goto set_4;
63         }
64
65         //Unaligned offsets don't naturaly happen in the runtime, so it's ok to be conservative in how we copy
66         //We assume that input src and dest are be aligned to `align` so offset just worsen it
67         int offsets_mask = offset & 0x7; //we only care about the misalignment part
68         if (offsets_mask) {
69                 if (offsets_mask % 2 == 1)
70                         goto set_1;
71                 if (offsets_mask % 4 == 2)
72                         goto set_2;
73                 if (SIZEOF_VOID_P == 8 && offsets_mask % 8 == 4)
74                         goto set_4;
75         }
76
77         if (SIZEOF_REGISTER == 8) {
78                 while (size >= 8) {
79                         MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI8_MEMBASE_REG, destreg, offset, val_reg);
80                         offset += 8;
81                         size -= 8;
82                 }
83         }
84
85 set_4:
86         while (size >= 4) {
87                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI4_MEMBASE_REG, destreg, offset, val_reg);
88                 offset += 4;
89                 size -= 4;
90         }
91
92
93 set_2:
94         while (size >= 2) {
95                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI2_MEMBASE_REG, destreg, offset, val_reg);
96                 offset += 2;
97                 size -= 2;
98         }
99
100 set_1:
101         while (size >= 1) {
102                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI1_MEMBASE_REG, destreg, offset, val_reg);
103                 offset += 1;
104                 size -= 1;
105         }
106 }
107
108 void 
109 mini_emit_memcpy (MonoCompile *cfg, int destreg, int doffset, int srcreg, int soffset, int size, int align)
110 {
111         int cur_reg;
112
113         /*FIXME arbitrary hack to avoid unbound code expansion.*/
114         g_assert (size < 10000);
115         g_assert (align > 0);
116
117         if (align < SIZEOF_VOID_P) {
118                 if (align == 4)
119                         goto copy_4;
120                 if (align == 2)
121                         goto copy_2;
122                 goto copy_1;
123         }
124
125         //Unaligned offsets don't naturaly happen in the runtime, so it's ok to be conservative in how we copy
126         //We assume that input src and dest are be aligned to `align` so offset just worsen it
127         int offsets_mask = (doffset | soffset) & 0x7; //we only care about the misalignment part
128         if (offsets_mask) {
129                 if (offsets_mask % 2 == 1)
130                         goto copy_1;
131                 if (offsets_mask % 4 == 2)
132                         goto copy_2;
133                 if (SIZEOF_VOID_P == 8 && offsets_mask % 8 == 4)
134                         goto copy_4;
135         }
136
137
138         if (SIZEOF_REGISTER == 8) {
139                 while (size >= 8) {
140                         cur_reg = alloc_preg (cfg);
141                         MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI8_MEMBASE, cur_reg, srcreg, soffset);
142                         MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI8_MEMBASE_REG, destreg, doffset, cur_reg);
143                         doffset += 8;
144                         soffset += 8;
145                         size -= 8;
146                 }
147         }
148
149 copy_4:
150         while (size >= 4) {
151                 cur_reg = alloc_preg (cfg);
152                 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI4_MEMBASE, cur_reg, srcreg, soffset);
153                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI4_MEMBASE_REG, destreg, doffset, cur_reg);
154                 doffset += 4;
155                 soffset += 4;
156                 size -= 4;
157         }
158
159 copy_2:
160         while (size >= 2) {
161                 cur_reg = alloc_preg (cfg);
162                 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI2_MEMBASE, cur_reg, srcreg, soffset);
163                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI2_MEMBASE_REG, destreg, doffset, cur_reg);
164                 doffset += 2;
165                 soffset += 2;
166                 size -= 2;
167         }
168
169 copy_1:
170         while (size >= 1) {
171                 cur_reg = alloc_preg (cfg);
172                 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI1_MEMBASE, cur_reg, srcreg, soffset);
173                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI1_MEMBASE_REG, destreg, doffset, cur_reg);
174                 doffset += 1;
175                 soffset += 1;
176                 size -= 1;
177         }
178 }
179
180 static void
181 mini_emit_memcpy_internal (MonoCompile *cfg, MonoInst *dest, MonoInst *src, MonoInst *size_ins, int size, int align)
182 {
183         /* FIXME: Optimize the case when src/dest is OP_LDADDR */
184
185         /* We can't do copies at a smaller granule than the provided alignment */
186         if (size_ins || (size / align > MAX_INLINE_COPIES) || !(cfg->opt & MONO_OPT_INTRINS)) {
187                 MonoInst *iargs [3];
188                 iargs [0] = dest;
189                 iargs [1] = src;
190
191                 if (!size_ins)
192                         EMIT_NEW_ICONST (cfg, size_ins, size);
193                 iargs [2] = size_ins;
194                 mono_emit_method_call (cfg, mini_get_memcpy_method (), iargs, NULL);
195         } else {
196                 mini_emit_memcpy (cfg, dest->dreg, 0, src->dreg, 0, size, align);
197         }
198 }
199
200 static void
201 mini_emit_memset_internal (MonoCompile *cfg, MonoInst *dest, MonoInst *value_ins, int value, MonoInst *size_ins, int size, int align)
202 {
203         /* FIXME: Optimize the case when dest is OP_LDADDR */
204
205         /* We can't do copies at a smaller granule than the provided alignment */
206         if (value_ins || size_ins || value != 0 || (size / align > MAX_INLINE_COPIES) || !(cfg->opt & MONO_OPT_INTRINS)) {
207                 MonoInst *iargs [3];
208                 iargs [0] = dest;
209
210                 if (!value_ins)
211                         EMIT_NEW_ICONST (cfg, value_ins, value);
212                 iargs [1] = value_ins;
213
214                 if (!size_ins)
215                         EMIT_NEW_ICONST (cfg, size_ins, size);
216                 iargs [2] = size_ins;
217
218                 mono_emit_method_call (cfg, mini_get_memset_method (), iargs, NULL);
219         } else {
220                 mini_emit_memset (cfg, dest->dreg, 0, size, value, align);
221         }
222 }
223
224 static void
225 mini_emit_memcpy_const_size (MonoCompile *cfg, MonoInst *dest, MonoInst *src, int size, int align)
226 {
227         mini_emit_memcpy_internal (cfg, dest, src, NULL, size, align);
228 }
229
230 static void
231 mini_emit_memset_const_size (MonoCompile *cfg, MonoInst *dest, int value, int size, int align)
232 {
233         mini_emit_memset_internal (cfg, dest, NULL, value, NULL, size, align);
234 }
235
236
237 static void
238 create_write_barrier_bitmap (MonoCompile *cfg, MonoClass *klass, unsigned *wb_bitmap, int offset)
239 {
240         MonoClassField *field;
241         gpointer iter = NULL;
242
243         while ((field = mono_class_get_fields (klass, &iter))) {
244                 int foffset;
245
246                 if (field->type->attrs & FIELD_ATTRIBUTE_STATIC)
247                         continue;
248                 foffset = klass->valuetype ? field->offset - sizeof (MonoObject): field->offset;
249                 if (mini_type_is_reference (mono_field_get_type (field))) {
250                         g_assert ((foffset % SIZEOF_VOID_P) == 0);
251                         *wb_bitmap |= 1 << ((offset + foffset) / SIZEOF_VOID_P);
252                 } else {
253                         MonoClass *field_class = mono_class_from_mono_type (field->type);
254                         if (field_class->has_references)
255                                 create_write_barrier_bitmap (cfg, field_class, wb_bitmap, offset + foffset);
256                 }
257         }
258 }
259
260 static gboolean
261 mini_emit_wb_aware_memcpy (MonoCompile *cfg, MonoClass *klass, MonoInst *iargs[4], int size, int align)
262 {
263         int dest_ptr_reg, tmp_reg, destreg, srcreg, offset;
264         unsigned need_wb = 0;
265
266         if (align == 0)
267                 align = 4;
268
269         /*types with references can't have alignment smaller than sizeof(void*) */
270         if (align < SIZEOF_VOID_P)
271                 return FALSE;
272
273         if (size > 5 * SIZEOF_VOID_P)
274                 return FALSE;
275
276         create_write_barrier_bitmap (cfg, klass, &need_wb, 0);
277
278         destreg = iargs [0]->dreg;
279         srcreg = iargs [1]->dreg;
280         offset = 0;
281
282         dest_ptr_reg = alloc_preg (cfg);
283         tmp_reg = alloc_preg (cfg);
284
285         /*tmp = dreg*/
286         EMIT_NEW_UNALU (cfg, iargs [0], OP_MOVE, dest_ptr_reg, destreg);
287
288         while (size >= SIZEOF_VOID_P) {
289                 MonoInst *load_inst;
290                 MONO_INST_NEW (cfg, load_inst, OP_LOAD_MEMBASE);
291                 load_inst->dreg = tmp_reg;
292                 load_inst->inst_basereg = srcreg;
293                 load_inst->inst_offset = offset;
294                 MONO_ADD_INS (cfg->cbb, load_inst);
295
296                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREP_MEMBASE_REG, dest_ptr_reg, 0, tmp_reg);
297
298                 if (need_wb & 0x1)
299                         mini_emit_write_barrier (cfg, iargs [0], load_inst);
300
301                 offset += SIZEOF_VOID_P;
302                 size -= SIZEOF_VOID_P;
303                 need_wb >>= 1;
304
305                 /*tmp += sizeof (void*)*/
306                 if (size >= SIZEOF_VOID_P) {
307                         NEW_BIALU_IMM (cfg, iargs [0], OP_PADD_IMM, dest_ptr_reg, dest_ptr_reg, SIZEOF_VOID_P);
308                         MONO_ADD_INS (cfg->cbb, iargs [0]);
309                 }
310         }
311
312         /* Those cannot be references since size < sizeof (void*) */
313         while (size >= 4) {
314                 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI4_MEMBASE, tmp_reg, srcreg, offset);
315                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI4_MEMBASE_REG, destreg, offset, tmp_reg);
316                 offset += 4;
317                 size -= 4;
318         }
319
320         while (size >= 2) {
321                 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI2_MEMBASE, tmp_reg, srcreg, offset);
322                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI2_MEMBASE_REG, destreg, offset, tmp_reg);
323                 offset += 2;
324                 size -= 2;
325         }
326
327         while (size >= 1) {
328                 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI1_MEMBASE, tmp_reg, srcreg, offset);
329                 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI1_MEMBASE_REG, destreg, offset, tmp_reg);
330                 offset += 1;
331                 size -= 1;
332         }
333
334         return TRUE;
335 }
336
337 static void
338 mini_emit_memory_copy_internal (MonoCompile *cfg, MonoInst *dest, MonoInst *src, MonoClass *klass, gboolean native)
339 {
340         MonoInst *iargs [4];
341         int size;
342         guint32 align = 0;
343         MonoInst *size_ins = NULL;
344         MonoInst *memcpy_ins = NULL;
345
346         g_assert (klass);
347         /*
348         Fun fact about @native. It's false that @klass will have no ref when @native is true.
349         This happens in pinvoke2. What goes is that marshal.c uses CEE_MONO_LDOBJNATIVE and pass klass.
350         The actual stuff being copied will have no refs, but @klass might.
351         This means we can't assert !(klass->has_references && native).
352         */
353
354         if (cfg->gshared)
355                 klass = mono_class_from_mono_type (mini_get_underlying_type (&klass->byval_arg));
356
357         /*
358          * This check breaks with spilled vars... need to handle it during verification anyway.
359          * g_assert (klass && klass == src->klass && klass == dest->klass);
360          */
361
362         if (mini_is_gsharedvt_klass (klass)) {
363                 g_assert (!native);
364                 size_ins = mini_emit_get_gsharedvt_info_klass (cfg, klass, MONO_RGCTX_INFO_VALUE_SIZE);
365                 memcpy_ins = mini_emit_get_gsharedvt_info_klass (cfg, klass, MONO_RGCTX_INFO_MEMCPY);
366         }
367
368         if (native)
369                 size = mono_class_native_size (klass, &align);
370         else
371                 size = mono_class_value_size (klass, &align);
372
373         if (!align)
374                 align = SIZEOF_VOID_P;
375
376         if (mini_type_is_reference (&klass->byval_arg)) {
377                 MonoInst *store, *load;
378                 int dreg = alloc_ireg_ref (cfg);
379
380                 NEW_LOAD_MEMBASE (cfg, load, OP_LOAD_MEMBASE, dreg, src->dreg, 0);
381                 MONO_ADD_INS (cfg->cbb, load);
382
383                 NEW_STORE_MEMBASE (cfg, store, OP_STORE_MEMBASE_REG, dest->dreg, 0, dreg);
384                 MONO_ADD_INS (cfg->cbb, store);
385
386                 mini_emit_write_barrier (cfg, dest, src);
387         } else if (cfg->gen_write_barriers && (klass->has_references || size_ins) && !native) {         /* if native is true there should be no references in the struct */
388                 /* Avoid barriers when storing to the stack */
389                 if (!((dest->opcode == OP_ADD_IMM && dest->sreg1 == cfg->frame_reg) ||
390                           (dest->opcode == OP_LDADDR))) {
391                         int context_used;
392
393                         iargs [0] = dest;
394                         iargs [1] = src;
395
396                         context_used = mini_class_check_context_used (cfg, klass);
397
398                         /* It's ok to intrinsify under gsharing since shared code types are layout stable. */
399                         if (!size_ins && (cfg->opt & MONO_OPT_INTRINS) && mini_emit_wb_aware_memcpy (cfg, klass, iargs, size, align)) {
400                         } else if (size_ins || align < SIZEOF_VOID_P) {
401                                 if (context_used) {
402                                         iargs [2] = mini_emit_get_rgctx_klass (cfg, context_used, klass, MONO_RGCTX_INFO_KLASS);
403                                 }  else {
404                                         iargs [2] = mini_emit_runtime_constant (cfg, MONO_PATCH_INFO_CLASS, klass);
405                                         if (!cfg->compile_aot)
406                                                 mono_class_compute_gc_descriptor (klass);
407                                 }
408                                 if (size_ins)
409                                         mono_emit_jit_icall (cfg, mono_gsharedvt_value_copy, iargs);
410                                 else
411                                         mono_emit_jit_icall (cfg, mono_value_copy, iargs);
412                         } else {
413                                 /* We don't unroll more than 5 stores to avoid code bloat. */
414                                 /*This is harmless and simplify mono_gc_get_range_copy_func */
415                                 size += (SIZEOF_VOID_P - 1);
416                                 size &= ~(SIZEOF_VOID_P - 1);
417
418                                 EMIT_NEW_ICONST (cfg, iargs [2], size);
419                                 mono_emit_jit_icall (cfg, mono_gc_get_range_copy_func (), iargs);
420                         }
421                         return;
422                 }
423         }
424
425         if (size_ins) {
426                 iargs [0] = dest;
427                 iargs [1] = src;
428                 iargs [2] = size_ins;
429                 mini_emit_calli (cfg, mono_method_signature (mini_get_memcpy_method ()), iargs, memcpy_ins, NULL, NULL);
430         } else {
431                 mini_emit_memcpy_const_size (cfg, dest, src, size, align);
432         }
433 }
434
435 MonoInst*
436 mini_emit_memory_load (MonoCompile *cfg, MonoType *type, MonoInst *src, int offset, int ins_flag)
437 {
438         MonoInst *ins;
439
440         EMIT_NEW_LOAD_MEMBASE_TYPE (cfg, ins, type, src->dreg, offset);
441         ins->flags |= ins_flag;
442
443         if (ins_flag & MONO_INST_VOLATILE) {
444                 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
445                 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_ACQ);
446         }
447
448         return ins;
449 }
450
451
452 void
453 mini_emit_memory_store (MonoCompile *cfg, MonoType *type, MonoInst *dest, MonoInst *value, int ins_flag)
454 {
455         MonoInst *ins;
456
457         if (ins_flag & MONO_INST_VOLATILE) {
458                 /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
459                 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL);
460         }
461         /* FIXME: should check item at sp [1] is compatible with the type of the store. */
462
463         EMIT_NEW_STORE_MEMBASE_TYPE (cfg, ins, type, dest->dreg, 0, value->dreg);
464         ins->flags |= ins_flag;
465         if (cfg->gen_write_barriers && cfg->method->wrapper_type != MONO_WRAPPER_WRITE_BARRIER &&
466                 mini_type_is_reference (type) && !MONO_INS_IS_PCONST_NULL (value)) {
467                 /* insert call to write barrier */
468                 mini_emit_write_barrier (cfg, dest, value);
469         }
470 }
471
472 void
473 mini_emit_memory_copy_bytes (MonoCompile *cfg, MonoInst *dest, MonoInst *src, MonoInst *size, int ins_flag)
474 {
475         int align = SIZEOF_VOID_P;
476
477         /*
478          * FIXME: It's unclear whether we should be emitting both the acquire
479          * and release barriers for cpblk. It is technically both a load and
480          * store operation, so it seems like that's the sensible thing to do.
481          *
482          * FIXME: We emit full barriers on both sides of the operation for
483          * simplicity. We should have a separate atomic memcpy method instead.
484          */
485         if (ins_flag & MONO_INST_VOLATILE) {
486                 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
487                 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
488         }
489
490         if ((cfg->opt & MONO_OPT_INTRINS) && (size->opcode == OP_ICONST) && size->inst_c0 < 10000) {
491                 mini_emit_memcpy_const_size (cfg, dest, src, size->inst_c0, align);
492         } else {
493                 if (cfg->verbose_level > 3)
494                         printf ("EMITING REGULAR COPY\n");
495                 mini_emit_memcpy_internal (cfg, dest, src, size, 0, align);
496         }
497
498         if (ins_flag & MONO_INST_VOLATILE) {
499                 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
500                 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
501         }
502 }
503
504 void
505 mini_emit_memory_init_bytes (MonoCompile *cfg, MonoInst *dest, MonoInst *value, MonoInst *size, int ins_flag)
506 {
507         int align = SIZEOF_VOID_P;
508
509         if (ins_flag & MONO_INST_VOLATILE) {
510                 /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
511                 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL);
512         }
513
514         //FIXME unrolled memset only supports zeroing
515         if ((cfg->opt & MONO_OPT_INTRINS) && (size->opcode == OP_ICONST) && (value->opcode == OP_ICONST) && (value->inst_c0 == 0)) {
516                 mini_emit_memset_const_size (cfg, dest, value->inst_c0, size->inst_c0, align);
517         } else {
518                 mini_emit_memset_internal (cfg, dest, value, 0, size, 0, align);
519         }
520
521 }
522
523 /*
524  * If @klass is a valuetype, emit code to copy a value with source address in @src and destination address in @dest.
525  * If @klass is a ref type, copy a pointer instead.
526  */
527
528 void
529 mini_emit_memory_copy (MonoCompile *cfg, MonoInst *dest, MonoInst *src, MonoClass *klass, gboolean native, int ins_flag)
530 {
531         /*
532          * FIXME: It's unclear whether we should be emitting both the acquire
533          * and release barriers for cpblk. It is technically both a load and
534          * store operation, so it seems like that's the sensible thing to do.
535          *
536          * FIXME: We emit full barriers on both sides of the operation for
537          * simplicity. We should have a separate atomic memcpy method instead.
538          */
539         if (ins_flag & MONO_INST_VOLATILE) {
540                 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
541                 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
542         }
543
544         mini_emit_memory_copy_internal (cfg, dest, src, klass, native);
545
546         if (ins_flag & MONO_INST_VOLATILE) {
547                 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
548                 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
549         }
550 }
551
552 #endif