Build fixes for none Windows desktop platforms.
[mono-project.git] / mono / mini / memory-access.c
blob630e0442b8ff16bab6d774fb625c6c19d4146178
1 /**
2 * Emit memory access for the front-end.
4 */
6 #include <config.h>
7 #include <mono/utils/mono-compiler.h>
9 #ifndef DISABLE_JIT
11 #include <mono/metadata/gc-internals.h>
12 #include <mono/utils/mono-memory-model.h>
14 #include "mini.h"
15 #include "ir-emit.h"
16 #include "jit-icalls.h"
18 #define MAX_INLINE_COPIES 10
19 #define MAX_INLINE_COPY_SIZE 10000
21 void
22 mini_emit_memset (MonoCompile *cfg, int destreg, int offset, int size, int val, int align)
24 int val_reg;
26 /*FIXME arbitrary hack to avoid unbound code expansion.*/
27 g_assert (size < MAX_INLINE_COPY_SIZE);
28 g_assert (val == 0);
29 g_assert (align > 0);
31 if ((size <= SIZEOF_REGISTER) && (size <= align)) {
32 switch (size) {
33 case 1:
34 MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI1_MEMBASE_IMM, destreg, offset, val);
35 return;
36 case 2:
37 MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI2_MEMBASE_IMM, destreg, offset, val);
38 return;
39 case 4:
40 MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI4_MEMBASE_IMM, destreg, offset, val);
41 return;
42 #if SIZEOF_REGISTER == 8
43 case 8:
44 MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI8_MEMBASE_IMM, destreg, offset, val);
45 return;
46 #endif
50 val_reg = alloc_preg (cfg);
52 if (SIZEOF_REGISTER == 8)
53 MONO_EMIT_NEW_I8CONST (cfg, val_reg, val);
54 else
55 MONO_EMIT_NEW_ICONST (cfg, val_reg, val);
57 if (align < SIZEOF_VOID_P) {
58 if (align % 2 == 1)
59 goto set_1;
60 if (align % 4 == 2)
61 goto set_2;
62 if (SIZEOF_VOID_P == 8 && align % 8 == 4)
63 goto set_4;
66 //Unaligned offsets don't naturaly happen in the runtime, so it's ok to be conservative in how we copy
67 //We assume that input src and dest are be aligned to `align` so offset just worsen it
68 int offsets_mask = offset & 0x7; //we only care about the misalignment part
69 if (offsets_mask) {
70 if (offsets_mask % 2 == 1)
71 goto set_1;
72 if (offsets_mask % 4 == 2)
73 goto set_2;
74 if (SIZEOF_VOID_P == 8 && offsets_mask % 8 == 4)
75 goto set_4;
78 if (SIZEOF_REGISTER == 8) {
79 while (size >= 8) {
80 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI8_MEMBASE_REG, destreg, offset, val_reg);
81 offset += 8;
82 size -= 8;
86 set_4:
87 while (size >= 4) {
88 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI4_MEMBASE_REG, destreg, offset, val_reg);
89 offset += 4;
90 size -= 4;
94 set_2:
95 while (size >= 2) {
96 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI2_MEMBASE_REG, destreg, offset, val_reg);
97 offset += 2;
98 size -= 2;
101 set_1:
102 while (size >= 1) {
103 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI1_MEMBASE_REG, destreg, offset, val_reg);
104 offset += 1;
105 size -= 1;
109 void
110 mini_emit_memcpy (MonoCompile *cfg, int destreg, int doffset, int srcreg, int soffset, int size, int align)
112 int cur_reg;
114 /*FIXME arbitrary hack to avoid unbound code expansion.*/
115 g_assert (size < MAX_INLINE_COPY_SIZE);
116 g_assert (align > 0);
118 if (align < SIZEOF_VOID_P) {
119 if (align == 4)
120 goto copy_4;
121 if (align == 2)
122 goto copy_2;
123 goto copy_1;
126 //Unaligned offsets don't naturaly happen in the runtime, so it's ok to be conservative in how we copy
127 //We assume that input src and dest are be aligned to `align` so offset just worsen it
128 int offsets_mask = (doffset | soffset) & 0x7; //we only care about the misalignment part
129 if (offsets_mask) {
130 if (offsets_mask % 2 == 1)
131 goto copy_1;
132 if (offsets_mask % 4 == 2)
133 goto copy_2;
134 if (SIZEOF_VOID_P == 8 && offsets_mask % 8 == 4)
135 goto copy_4;
139 if (SIZEOF_REGISTER == 8) {
140 while (size >= 8) {
141 cur_reg = alloc_preg (cfg);
142 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI8_MEMBASE, cur_reg, srcreg, soffset);
143 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI8_MEMBASE_REG, destreg, doffset, cur_reg);
144 doffset += 8;
145 soffset += 8;
146 size -= 8;
150 copy_4:
151 while (size >= 4) {
152 cur_reg = alloc_preg (cfg);
153 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI4_MEMBASE, cur_reg, srcreg, soffset);
154 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI4_MEMBASE_REG, destreg, doffset, cur_reg);
155 doffset += 4;
156 soffset += 4;
157 size -= 4;
160 copy_2:
161 while (size >= 2) {
162 cur_reg = alloc_preg (cfg);
163 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI2_MEMBASE, cur_reg, srcreg, soffset);
164 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI2_MEMBASE_REG, destreg, doffset, cur_reg);
165 doffset += 2;
166 soffset += 2;
167 size -= 2;
170 copy_1:
171 while (size >= 1) {
172 cur_reg = alloc_preg (cfg);
173 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI1_MEMBASE, cur_reg, srcreg, soffset);
174 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI1_MEMBASE_REG, destreg, doffset, cur_reg);
175 doffset += 1;
176 soffset += 1;
177 size -= 1;
181 static void
182 mini_emit_memcpy_internal (MonoCompile *cfg, MonoInst *dest, MonoInst *src, MonoInst *size_ins, int size, int align)
184 /* FIXME: Optimize the case when src/dest is OP_LDADDR */
186 /* We can't do copies at a smaller granule than the provided alignment */
187 if (size_ins || (size / align > MAX_INLINE_COPIES) || !(cfg->opt & MONO_OPT_INTRINS)) {
188 MonoInst *iargs [3];
189 iargs [0] = dest;
190 iargs [1] = src;
192 if (!size_ins)
193 EMIT_NEW_ICONST (cfg, size_ins, size);
194 iargs [2] = size_ins;
195 mono_emit_method_call (cfg, mini_get_memcpy_method (), iargs, NULL);
196 } else {
197 mini_emit_memcpy (cfg, dest->dreg, 0, src->dreg, 0, size, align);
201 static void
202 mini_emit_memset_internal (MonoCompile *cfg, MonoInst *dest, MonoInst *value_ins, int value, MonoInst *size_ins, int size, int align)
204 /* FIXME: Optimize the case when dest is OP_LDADDR */
206 /* We can't do copies at a smaller granule than the provided alignment */
207 if (value_ins || size_ins || value != 0 || (size / align > MAX_INLINE_COPIES) || !(cfg->opt & MONO_OPT_INTRINS)) {
208 MonoInst *iargs [3];
209 iargs [0] = dest;
211 if (!value_ins)
212 EMIT_NEW_ICONST (cfg, value_ins, value);
213 iargs [1] = value_ins;
215 if (!size_ins)
216 EMIT_NEW_ICONST (cfg, size_ins, size);
217 iargs [2] = size_ins;
219 mono_emit_method_call (cfg, mini_get_memset_method (), iargs, NULL);
220 } else {
221 mini_emit_memset (cfg, dest->dreg, 0, size, value, align);
225 static void
226 mini_emit_memcpy_const_size (MonoCompile *cfg, MonoInst *dest, MonoInst *src, int size, int align)
228 mini_emit_memcpy_internal (cfg, dest, src, NULL, size, align);
231 static void
232 mini_emit_memset_const_size (MonoCompile *cfg, MonoInst *dest, int value, int size, int align)
234 mini_emit_memset_internal (cfg, dest, NULL, value, NULL, size, align);
238 static void
239 create_write_barrier_bitmap (MonoCompile *cfg, MonoClass *klass, unsigned *wb_bitmap, int offset)
241 MonoClassField *field;
242 gpointer iter = NULL;
244 while ((field = mono_class_get_fields (klass, &iter))) {
245 int foffset;
247 if (field->type->attrs & FIELD_ATTRIBUTE_STATIC)
248 continue;
249 foffset = klass->valuetype ? field->offset - sizeof (MonoObject): field->offset;
250 if (mini_type_is_reference (mono_field_get_type (field))) {
251 g_assert ((foffset % SIZEOF_VOID_P) == 0);
252 *wb_bitmap |= 1 << ((offset + foffset) / SIZEOF_VOID_P);
253 } else {
254 MonoClass *field_class = mono_class_from_mono_type (field->type);
255 if (field_class->has_references)
256 create_write_barrier_bitmap (cfg, field_class, wb_bitmap, offset + foffset);
261 static gboolean
262 mini_emit_wb_aware_memcpy (MonoCompile *cfg, MonoClass *klass, MonoInst *iargs[4], int size, int align)
264 int dest_ptr_reg, tmp_reg, destreg, srcreg, offset;
265 unsigned need_wb = 0;
267 if (align == 0)
268 align = 4;
270 /*types with references can't have alignment smaller than sizeof(void*) */
271 if (align < SIZEOF_VOID_P)
272 return FALSE;
274 if (size > 5 * SIZEOF_VOID_P)
275 return FALSE;
277 create_write_barrier_bitmap (cfg, klass, &need_wb, 0);
279 destreg = iargs [0]->dreg;
280 srcreg = iargs [1]->dreg;
281 offset = 0;
283 dest_ptr_reg = alloc_preg (cfg);
284 tmp_reg = alloc_preg (cfg);
286 /*tmp = dreg*/
287 EMIT_NEW_UNALU (cfg, iargs [0], OP_MOVE, dest_ptr_reg, destreg);
289 while (size >= SIZEOF_VOID_P) {
290 MonoInst *load_inst;
291 MONO_INST_NEW (cfg, load_inst, OP_LOAD_MEMBASE);
292 load_inst->dreg = tmp_reg;
293 load_inst->inst_basereg = srcreg;
294 load_inst->inst_offset = offset;
295 MONO_ADD_INS (cfg->cbb, load_inst);
297 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREP_MEMBASE_REG, dest_ptr_reg, 0, tmp_reg);
299 if (need_wb & 0x1)
300 mini_emit_write_barrier (cfg, iargs [0], load_inst);
302 offset += SIZEOF_VOID_P;
303 size -= SIZEOF_VOID_P;
304 need_wb >>= 1;
306 /*tmp += sizeof (void*)*/
307 if (size >= SIZEOF_VOID_P) {
308 NEW_BIALU_IMM (cfg, iargs [0], OP_PADD_IMM, dest_ptr_reg, dest_ptr_reg, SIZEOF_VOID_P);
309 MONO_ADD_INS (cfg->cbb, iargs [0]);
313 /* Those cannot be references since size < sizeof (void*) */
314 while (size >= 4) {
315 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI4_MEMBASE, tmp_reg, srcreg, offset);
316 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI4_MEMBASE_REG, destreg, offset, tmp_reg);
317 offset += 4;
318 size -= 4;
321 while (size >= 2) {
322 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI2_MEMBASE, tmp_reg, srcreg, offset);
323 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI2_MEMBASE_REG, destreg, offset, tmp_reg);
324 offset += 2;
325 size -= 2;
328 while (size >= 1) {
329 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg, OP_LOADI1_MEMBASE, tmp_reg, srcreg, offset);
330 MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI1_MEMBASE_REG, destreg, offset, tmp_reg);
331 offset += 1;
332 size -= 1;
335 return TRUE;
338 static void
339 mini_emit_memory_copy_internal (MonoCompile *cfg, MonoInst *dest, MonoInst *src, MonoClass *klass, int explicit_align, gboolean native)
341 MonoInst *iargs [4];
342 int size;
343 guint32 align = 0;
344 MonoInst *size_ins = NULL;
345 MonoInst *memcpy_ins = NULL;
347 g_assert (klass);
349 Fun fact about @native. It's false that @klass will have no ref when @native is true.
350 This happens in pinvoke2. What goes is that marshal.c uses CEE_MONO_LDOBJNATIVE and pass klass.
351 The actual stuff being copied will have no refs, but @klass might.
352 This means we can't assert !(klass->has_references && native).
355 if (cfg->gshared)
356 klass = mono_class_from_mono_type (mini_get_underlying_type (&klass->byval_arg));
359 * This check breaks with spilled vars... need to handle it during verification anyway.
360 * g_assert (klass && klass == src->klass && klass == dest->klass);
363 if (mini_is_gsharedvt_klass (klass)) {
364 g_assert (!native);
365 size_ins = mini_emit_get_gsharedvt_info_klass (cfg, klass, MONO_RGCTX_INFO_VALUE_SIZE);
366 memcpy_ins = mini_emit_get_gsharedvt_info_klass (cfg, klass, MONO_RGCTX_INFO_MEMCPY);
369 if (native)
370 size = mono_class_native_size (klass, &align);
371 else
372 size = mono_class_value_size (klass, &align);
374 if (!align)
375 align = SIZEOF_VOID_P;
376 if (explicit_align)
377 align = explicit_align;
379 if (mini_type_is_reference (&klass->byval_arg)) { // Refs *MUST* be naturally aligned
380 MonoInst *store, *load;
381 int dreg = alloc_ireg_ref (cfg);
383 NEW_LOAD_MEMBASE (cfg, load, OP_LOAD_MEMBASE, dreg, src->dreg, 0);
384 MONO_ADD_INS (cfg->cbb, load);
386 NEW_STORE_MEMBASE (cfg, store, OP_STORE_MEMBASE_REG, dest->dreg, 0, dreg);
387 MONO_ADD_INS (cfg->cbb, store);
389 mini_emit_write_barrier (cfg, dest, src);
390 } else if (cfg->gen_write_barriers && (klass->has_references || size_ins) && !native) { /* if native is true there should be no references in the struct */
391 /* Avoid barriers when storing to the stack */
392 if (!((dest->opcode == OP_ADD_IMM && dest->sreg1 == cfg->frame_reg) ||
393 (dest->opcode == OP_LDADDR))) {
394 int context_used;
396 iargs [0] = dest;
397 iargs [1] = src;
399 context_used = mini_class_check_context_used (cfg, klass);
401 /* It's ok to intrinsify under gsharing since shared code types are layout stable. */
402 if (!size_ins && (cfg->opt & MONO_OPT_INTRINS) && mini_emit_wb_aware_memcpy (cfg, klass, iargs, size, align)) {
403 } else if (size_ins || align < SIZEOF_VOID_P) {
404 if (context_used) {
405 iargs [2] = mini_emit_get_rgctx_klass (cfg, context_used, klass, MONO_RGCTX_INFO_KLASS);
406 } else {
407 iargs [2] = mini_emit_runtime_constant (cfg, MONO_PATCH_INFO_CLASS, klass);
408 if (!cfg->compile_aot)
409 mono_class_compute_gc_descriptor (klass);
411 if (size_ins)
412 mono_emit_jit_icall (cfg, mono_gsharedvt_value_copy, iargs);
413 else
414 mono_emit_jit_icall (cfg, mono_value_copy, iargs);
415 } else {
416 /* We don't unroll more than 5 stores to avoid code bloat. */
417 /*This is harmless and simplify mono_gc_get_range_copy_func */
418 size += (SIZEOF_VOID_P - 1);
419 size &= ~(SIZEOF_VOID_P - 1);
421 EMIT_NEW_ICONST (cfg, iargs [2], size);
422 mono_emit_jit_icall (cfg, mono_gc_get_range_copy_func (), iargs);
424 return;
428 if (size_ins) {
429 iargs [0] = dest;
430 iargs [1] = src;
431 iargs [2] = size_ins;
432 mini_emit_calli (cfg, mono_method_signature (mini_get_memcpy_method ()), iargs, memcpy_ins, NULL, NULL);
433 } else {
434 mini_emit_memcpy_const_size (cfg, dest, src, size, align);
438 MonoInst*
439 mini_emit_memory_load (MonoCompile *cfg, MonoType *type, MonoInst *src, int offset, int ins_flag)
441 MonoInst *ins;
443 if (ins_flag & MONO_INST_UNALIGNED) {
444 MonoInst *addr, *tmp_var;
445 int align;
446 int size = mono_type_size (type, &align);
448 if (offset) {
449 MonoInst *add_offset;
450 NEW_BIALU_IMM (cfg, add_offset, OP_PADD_IMM, alloc_preg (cfg), src->dreg, offset);
451 MONO_ADD_INS (cfg->cbb, add_offset);
452 src = add_offset;
455 tmp_var = mono_compile_create_var (cfg, type, OP_LOCAL);
456 EMIT_NEW_VARLOADA (cfg, addr, tmp_var, tmp_var->inst_vtype);
458 mini_emit_memcpy_const_size (cfg, addr, src, size, 1);
459 EMIT_NEW_TEMPLOAD (cfg, ins, tmp_var->inst_c0);
460 } else {
461 EMIT_NEW_LOAD_MEMBASE_TYPE (cfg, ins, type, src->dreg, offset);
463 ins->flags |= ins_flag;
465 if (ins_flag & MONO_INST_VOLATILE) {
466 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
467 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_ACQ);
470 return ins;
474 void
475 mini_emit_memory_store (MonoCompile *cfg, MonoType *type, MonoInst *dest, MonoInst *value, int ins_flag)
477 MonoInst *ins;
479 if (ins_flag & MONO_INST_VOLATILE) {
480 /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
481 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL);
484 if (ins_flag & MONO_INST_UNALIGNED) {
485 MonoInst *addr, *mov, *tmp_var;
487 tmp_var = mono_compile_create_var (cfg, type, OP_LOCAL);
488 EMIT_NEW_TEMPSTORE (cfg, mov, tmp_var->inst_c0, value);
489 EMIT_NEW_VARLOADA (cfg, addr, tmp_var, tmp_var->inst_vtype);
490 mini_emit_memory_copy_internal (cfg, dest, addr, mono_class_from_mono_type (type), 1, FALSE);
493 /* FIXME: should check item at sp [1] is compatible with the type of the store. */
495 EMIT_NEW_STORE_MEMBASE_TYPE (cfg, ins, type, dest->dreg, 0, value->dreg);
496 ins->flags |= ins_flag;
497 if (cfg->gen_write_barriers && cfg->method->wrapper_type != MONO_WRAPPER_WRITE_BARRIER &&
498 mini_type_is_reference (type) && !MONO_INS_IS_PCONST_NULL (value)) {
499 /* insert call to write barrier */
500 mini_emit_write_barrier (cfg, dest, value);
504 void
505 mini_emit_memory_copy_bytes (MonoCompile *cfg, MonoInst *dest, MonoInst *src, MonoInst *size, int ins_flag)
507 int align = (ins_flag & MONO_INST_UNALIGNED) ? 1 : SIZEOF_VOID_P;
510 * FIXME: It's unclear whether we should be emitting both the acquire
511 * and release barriers for cpblk. It is technically both a load and
512 * store operation, so it seems like that's the sensible thing to do.
514 * FIXME: We emit full barriers on both sides of the operation for
515 * simplicity. We should have a separate atomic memcpy method instead.
517 if (ins_flag & MONO_INST_VOLATILE) {
518 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
519 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
522 if ((cfg->opt & MONO_OPT_INTRINS) && (size->opcode == OP_ICONST)) {
523 mini_emit_memcpy_const_size (cfg, dest, src, size->inst_c0, align);
524 } else {
525 mini_emit_memcpy_internal (cfg, dest, src, size, 0, align);
528 if (ins_flag & MONO_INST_VOLATILE) {
529 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
530 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
534 void
535 mini_emit_memory_init_bytes (MonoCompile *cfg, MonoInst *dest, MonoInst *value, MonoInst *size, int ins_flag)
537 int align = (ins_flag & MONO_INST_UNALIGNED) ? 1 : SIZEOF_VOID_P;
539 if (ins_flag & MONO_INST_VOLATILE) {
540 /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
541 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL);
544 //FIXME unrolled memset only supports zeroing
545 if ((cfg->opt & MONO_OPT_INTRINS) && (size->opcode == OP_ICONST) && (value->opcode == OP_ICONST) && (value->inst_c0 == 0)) {
546 mini_emit_memset_const_size (cfg, dest, value->inst_c0, size->inst_c0, align);
547 } else {
548 mini_emit_memset_internal (cfg, dest, value, 0, size, 0, align);
554 * If @klass is a valuetype, emit code to copy a value with source address in @src and destination address in @dest.
555 * If @klass is a ref type, copy a pointer instead.
558 void
559 mini_emit_memory_copy (MonoCompile *cfg, MonoInst *dest, MonoInst *src, MonoClass *klass, gboolean native, int ins_flag)
561 int explicit_align = 0;
562 if (ins_flag & MONO_INST_UNALIGNED)
563 explicit_align = 1;
566 * FIXME: It's unclear whether we should be emitting both the acquire
567 * and release barriers for cpblk. It is technically both a load and
568 * store operation, so it seems like that's the sensible thing to do.
570 * FIXME: We emit full barriers on both sides of the operation for
571 * simplicity. We should have a separate atomic memcpy method instead.
573 if (ins_flag & MONO_INST_VOLATILE) {
574 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
575 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
578 mini_emit_memory_copy_internal (cfg, dest, src, klass, explicit_align, native);
580 if (ins_flag & MONO_INST_VOLATILE) {
581 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
582 mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
585 #else /* !DISABLE_JIT */
587 MONO_EMPTY_SOURCE_FILE (memory_access);
588 #endif