2 * Emit memory access for the front-end.
7 #include <mono/utils/mono-compiler.h>
11 #include <mono/metadata/gc-internals.h>
12 #include <mono/metadata/abi-details.h>
13 #include <mono/utils/mono-memory-model.h>
17 #include "jit-icalls.h"
20 #define MAX_INLINE_COPIES 16
22 #define MAX_INLINE_COPIES 10
24 #define MAX_INLINE_COPY_SIZE 10000
27 mini_emit_memset (MonoCompile
*cfg
, int destreg
, int offset
, int size
, int val
, int align
)
31 /*FIXME arbitrary hack to avoid unbound code expansion.*/
32 g_assert (size
< MAX_INLINE_COPY_SIZE
);
36 if ((size
<= SIZEOF_REGISTER
) && (size
<= align
)) {
39 MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg
, OP_STOREI1_MEMBASE_IMM
, destreg
, offset
, val
);
42 MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg
, OP_STOREI2_MEMBASE_IMM
, destreg
, offset
, val
);
45 MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg
, OP_STOREI4_MEMBASE_IMM
, destreg
, offset
, val
);
47 #if SIZEOF_REGISTER == 8
49 MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg
, OP_STOREI8_MEMBASE_IMM
, destreg
, offset
, val
);
55 val_reg
= alloc_preg (cfg
);
57 if (SIZEOF_REGISTER
== 8)
58 MONO_EMIT_NEW_I8CONST (cfg
, val_reg
, val
);
60 MONO_EMIT_NEW_ICONST (cfg
, val_reg
, val
);
62 if (align
< TARGET_SIZEOF_VOID_P
) {
67 if (TARGET_SIZEOF_VOID_P
== 8 && align
% 8 == 4)
71 //Unaligned offsets don't naturaly happen in the runtime, so it's ok to be conservative in how we copy
72 //We assume that input src and dest are be aligned to `align` so offset just worsen it
74 offsets_mask
= offset
& 0x7; //we only care about the misalignment part
76 if (offsets_mask
% 2 == 1)
78 if (offsets_mask
% 4 == 2)
80 if (TARGET_SIZEOF_VOID_P
== 8 && offsets_mask
% 8 == 4)
84 if (SIZEOF_REGISTER
== 8) {
86 MONO_EMIT_NEW_STORE_MEMBASE (cfg
, OP_STOREI8_MEMBASE_REG
, destreg
, offset
, val_reg
);
94 MONO_EMIT_NEW_STORE_MEMBASE (cfg
, OP_STOREI4_MEMBASE_REG
, destreg
, offset
, val_reg
);
102 MONO_EMIT_NEW_STORE_MEMBASE (cfg
, OP_STOREI2_MEMBASE_REG
, destreg
, offset
, val_reg
);
109 MONO_EMIT_NEW_STORE_MEMBASE (cfg
, OP_STOREI1_MEMBASE_REG
, destreg
, offset
, val_reg
);
116 mini_emit_memcpy (MonoCompile
*cfg
, int destreg
, int doffset
, int srcreg
, int soffset
, int size
, int align
)
120 /*FIXME arbitrary hack to avoid unbound code expansion.*/
121 g_assert (size
< MAX_INLINE_COPY_SIZE
);
122 g_assert (align
> 0);
124 if (align
< TARGET_SIZEOF_VOID_P
) {
132 //Unaligned offsets don't naturaly happen in the runtime, so it's ok to be conservative in how we copy
133 //We assume that input src and dest are be aligned to `align` so offset just worsen it
135 offsets_mask
= (doffset
| soffset
) & 0x7; //we only care about the misalignment part
137 if (offsets_mask
% 2 == 1)
139 if (offsets_mask
% 4 == 2)
141 if (TARGET_SIZEOF_VOID_P
== 8 && offsets_mask
% 8 == 4)
146 if (SIZEOF_REGISTER
== 8) {
148 cur_reg
= alloc_preg (cfg
);
149 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg
, OP_LOADI8_MEMBASE
, cur_reg
, srcreg
, soffset
);
150 MONO_EMIT_NEW_STORE_MEMBASE (cfg
, OP_STOREI8_MEMBASE_REG
, destreg
, doffset
, cur_reg
);
159 cur_reg
= alloc_preg (cfg
);
160 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg
, OP_LOADI4_MEMBASE
, cur_reg
, srcreg
, soffset
);
161 MONO_EMIT_NEW_STORE_MEMBASE (cfg
, OP_STOREI4_MEMBASE_REG
, destreg
, doffset
, cur_reg
);
169 cur_reg
= alloc_preg (cfg
);
170 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg
, OP_LOADI2_MEMBASE
, cur_reg
, srcreg
, soffset
);
171 MONO_EMIT_NEW_STORE_MEMBASE (cfg
, OP_STOREI2_MEMBASE_REG
, destreg
, doffset
, cur_reg
);
179 cur_reg
= alloc_preg (cfg
);
180 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg
, OP_LOADI1_MEMBASE
, cur_reg
, srcreg
, soffset
);
181 MONO_EMIT_NEW_STORE_MEMBASE (cfg
, OP_STOREI1_MEMBASE_REG
, destreg
, doffset
, cur_reg
);
189 mini_emit_memcpy_internal (MonoCompile
*cfg
, MonoInst
*dest
, MonoInst
*src
, MonoInst
*size_ins
, int size
, int align
)
191 /* FIXME: Optimize the case when src/dest is OP_LDADDR */
193 /* We can't do copies at a smaller granule than the provided alignment */
194 if (size_ins
|| (size
/ align
> MAX_INLINE_COPIES
) || !(cfg
->opt
& MONO_OPT_INTRINS
)) {
200 EMIT_NEW_ICONST (cfg
, size_ins
, size
);
201 iargs
[2] = size_ins
;
202 mono_emit_method_call (cfg
, mini_get_memcpy_method (), iargs
, NULL
);
204 mini_emit_memcpy (cfg
, dest
->dreg
, 0, src
->dreg
, 0, size
, align
);
209 mini_emit_memset_internal (MonoCompile
*cfg
, MonoInst
*dest
, MonoInst
*value_ins
, int value
, MonoInst
*size_ins
, int size
, int align
)
211 /* FIXME: Optimize the case when dest is OP_LDADDR */
213 /* We can't do copies at a smaller granule than the provided alignment */
214 if (value_ins
|| size_ins
|| value
!= 0 || (size
/ align
> MAX_INLINE_COPIES
) || !(cfg
->opt
& MONO_OPT_INTRINS
)) {
219 EMIT_NEW_ICONST (cfg
, value_ins
, value
);
220 iargs
[1] = value_ins
;
223 EMIT_NEW_ICONST (cfg
, size_ins
, size
);
224 iargs
[2] = size_ins
;
226 mono_emit_method_call (cfg
, mini_get_memset_method (), iargs
, NULL
);
228 mini_emit_memset (cfg
, dest
->dreg
, 0, size
, value
, align
);
233 mini_emit_memcpy_const_size (MonoCompile
*cfg
, MonoInst
*dest
, MonoInst
*src
, int size
, int align
)
235 mini_emit_memcpy_internal (cfg
, dest
, src
, NULL
, size
, align
);
239 mini_emit_memset_const_size (MonoCompile
*cfg
, MonoInst
*dest
, int value
, int size
, int align
)
241 mini_emit_memset_internal (cfg
, dest
, NULL
, value
, NULL
, size
, align
);
246 create_write_barrier_bitmap (MonoCompile
*cfg
, MonoClass
*klass
, unsigned *wb_bitmap
, int offset
)
248 MonoClassField
*field
;
249 gpointer iter
= NULL
;
251 while ((field
= mono_class_get_fields_internal (klass
, &iter
))) {
254 if (field
->type
->attrs
& FIELD_ATTRIBUTE_STATIC
)
256 foffset
= m_class_is_valuetype (klass
) ? field
->offset
- MONO_ABI_SIZEOF (MonoObject
): field
->offset
;
257 if (mini_type_is_reference (mono_field_get_type_internal (field
))) {
258 g_assert ((foffset
% TARGET_SIZEOF_VOID_P
) == 0);
259 *wb_bitmap
|= 1 << ((offset
+ foffset
) / TARGET_SIZEOF_VOID_P
);
261 MonoClass
*field_class
= mono_class_from_mono_type_internal (field
->type
);
262 if (m_class_has_references (field_class
))
263 create_write_barrier_bitmap (cfg
, field_class
, wb_bitmap
, offset
+ foffset
);
269 mini_emit_wb_aware_memcpy (MonoCompile
*cfg
, MonoClass
*klass
, MonoInst
*iargs
[4], int size
, int align
)
271 int dest_ptr_reg
, tmp_reg
, destreg
, srcreg
, offset
;
272 unsigned need_wb
= 0;
277 /*types with references can't have alignment smaller than sizeof(void*) */
278 if (align
< TARGET_SIZEOF_VOID_P
)
281 if (size
> 5 * TARGET_SIZEOF_VOID_P
)
284 create_write_barrier_bitmap (cfg
, klass
, &need_wb
, 0);
286 destreg
= iargs
[0]->dreg
;
287 srcreg
= iargs
[1]->dreg
;
290 dest_ptr_reg
= alloc_preg (cfg
);
291 tmp_reg
= alloc_preg (cfg
);
294 EMIT_NEW_UNALU (cfg
, iargs
[0], OP_MOVE
, dest_ptr_reg
, destreg
);
296 while (size
>= TARGET_SIZEOF_VOID_P
) {
298 MONO_INST_NEW (cfg
, load_inst
, OP_LOAD_MEMBASE
);
299 load_inst
->dreg
= tmp_reg
;
300 load_inst
->inst_basereg
= srcreg
;
301 load_inst
->inst_offset
= offset
;
302 MONO_ADD_INS (cfg
->cbb
, load_inst
);
304 MONO_EMIT_NEW_STORE_MEMBASE (cfg
, OP_STOREP_MEMBASE_REG
, dest_ptr_reg
, 0, tmp_reg
);
307 mini_emit_write_barrier (cfg
, iargs
[0], load_inst
);
309 offset
+= TARGET_SIZEOF_VOID_P
;
310 size
-= TARGET_SIZEOF_VOID_P
;
313 /*tmp += sizeof (void*)*/
314 if (size
>= TARGET_SIZEOF_VOID_P
) {
315 NEW_BIALU_IMM (cfg
, iargs
[0], OP_PADD_IMM
, dest_ptr_reg
, dest_ptr_reg
, TARGET_SIZEOF_VOID_P
);
316 MONO_ADD_INS (cfg
->cbb
, iargs
[0]);
320 /* Those cannot be references since size < sizeof (void*) */
322 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg
, OP_LOADI4_MEMBASE
, tmp_reg
, srcreg
, offset
);
323 MONO_EMIT_NEW_STORE_MEMBASE (cfg
, OP_STOREI4_MEMBASE_REG
, destreg
, offset
, tmp_reg
);
329 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg
, OP_LOADI2_MEMBASE
, tmp_reg
, srcreg
, offset
);
330 MONO_EMIT_NEW_STORE_MEMBASE (cfg
, OP_STOREI2_MEMBASE_REG
, destreg
, offset
, tmp_reg
);
336 MONO_EMIT_NEW_LOAD_MEMBASE_OP (cfg
, OP_LOADI1_MEMBASE
, tmp_reg
, srcreg
, offset
);
337 MONO_EMIT_NEW_STORE_MEMBASE (cfg
, OP_STOREI1_MEMBASE_REG
, destreg
, offset
, tmp_reg
);
346 mini_emit_memory_copy_internal (MonoCompile
*cfg
, MonoInst
*dest
, MonoInst
*src
, MonoClass
*klass
, int explicit_align
, gboolean native
)
351 MonoInst
*size_ins
= NULL
;
352 MonoInst
*memcpy_ins
= NULL
;
356 Fun fact about @native. It's false that @klass will have no ref when @native is true.
357 This happens in pinvoke2. What goes is that marshal.c uses CEE_MONO_LDOBJNATIVE and pass klass.
358 The actual stuff being copied will have no refs, but @klass might.
359 This means we can't assert !(klass->has_references && native).
363 klass
= mono_class_from_mono_type_internal (mini_get_underlying_type (m_class_get_byval_arg (klass
)));
366 * This check breaks with spilled vars... need to handle it during verification anyway.
367 * g_assert (klass && klass == src->klass && klass == dest->klass);
370 if (mini_is_gsharedvt_klass (klass
)) {
372 size_ins
= mini_emit_get_gsharedvt_info_klass (cfg
, klass
, MONO_RGCTX_INFO_VALUE_SIZE
);
373 memcpy_ins
= mini_emit_get_gsharedvt_info_klass (cfg
, klass
, MONO_RGCTX_INFO_MEMCPY
);
377 size
= mono_class_native_size (klass
, &align
);
379 size
= mono_class_value_size (klass
, &align
);
382 align
= TARGET_SIZEOF_VOID_P
;
384 align
= explicit_align
;
386 if (mini_type_is_reference (m_class_get_byval_arg (klass
))) { // Refs *MUST* be naturally aligned
387 MonoInst
*store
, *load
;
388 int dreg
= alloc_ireg_ref (cfg
);
390 NEW_LOAD_MEMBASE (cfg
, load
, OP_LOAD_MEMBASE
, dreg
, src
->dreg
, 0);
391 MONO_ADD_INS (cfg
->cbb
, load
);
393 NEW_STORE_MEMBASE (cfg
, store
, OP_STORE_MEMBASE_REG
, dest
->dreg
, 0, dreg
);
394 MONO_ADD_INS (cfg
->cbb
, store
);
396 mini_emit_write_barrier (cfg
, dest
, src
);
399 } else if (cfg
->gen_write_barriers
&& (m_class_has_references (klass
) || size_ins
) && !native
) { /* if native is true there should be no references in the struct */
400 /* Avoid barriers when storing to the stack */
401 if (!((dest
->opcode
== OP_ADD_IMM
&& dest
->sreg1
== cfg
->frame_reg
) ||
402 (dest
->opcode
== OP_LDADDR
))) {
408 context_used
= mini_class_check_context_used (cfg
, klass
);
410 /* It's ok to intrinsify under gsharing since shared code types are layout stable. */
411 if (!size_ins
&& (cfg
->opt
& MONO_OPT_INTRINS
) && mini_emit_wb_aware_memcpy (cfg
, klass
, iargs
, size
, align
)) {
412 } else if (size_ins
|| align
< TARGET_SIZEOF_VOID_P
) {
414 iargs
[2] = mini_emit_get_rgctx_klass (cfg
, context_used
, klass
, MONO_RGCTX_INFO_KLASS
);
416 iargs
[2] = mini_emit_runtime_constant (cfg
, MONO_PATCH_INFO_CLASS
, klass
);
417 if (!cfg
->compile_aot
)
418 mono_class_compute_gc_descriptor (klass
);
421 mono_emit_jit_icall (cfg
, mono_gsharedvt_value_copy
, iargs
);
423 mono_emit_jit_icall (cfg
, mono_value_copy_internal
, iargs
);
425 /* We don't unroll more than 5 stores to avoid code bloat. */
426 /*This is harmless and simplify mono_gc_get_range_copy_func */
427 size
+= (TARGET_SIZEOF_VOID_P
- 1);
428 size
&= ~(TARGET_SIZEOF_VOID_P
- 1);
430 EMIT_NEW_ICONST (cfg
, iargs
[2], size
);
431 mono_emit_jit_icall (cfg
, mono_gc_wbarrier_range_copy
, iargs
);
440 iargs
[2] = size_ins
;
441 mini_emit_calli (cfg
, mono_method_signature_internal (mini_get_memcpy_method ()), iargs
, memcpy_ins
, NULL
, NULL
);
443 mini_emit_memcpy_const_size (cfg
, dest
, src
, size
, align
);
448 mini_emit_memory_load (MonoCompile
*cfg
, MonoType
*type
, MonoInst
*src
, int offset
, int ins_flag
)
452 if (ins_flag
& MONO_INST_UNALIGNED
) {
453 MonoInst
*addr
, *tmp_var
;
455 int size
= mono_type_size (type
, &align
);
458 MonoInst
*add_offset
;
459 NEW_BIALU_IMM (cfg
, add_offset
, OP_PADD_IMM
, alloc_preg (cfg
), src
->dreg
, offset
);
460 MONO_ADD_INS (cfg
->cbb
, add_offset
);
464 tmp_var
= mono_compile_create_var (cfg
, type
, OP_LOCAL
);
465 EMIT_NEW_VARLOADA (cfg
, addr
, tmp_var
, tmp_var
->inst_vtype
);
467 mini_emit_memcpy_const_size (cfg
, addr
, src
, size
, 1);
468 EMIT_NEW_TEMPLOAD (cfg
, ins
, tmp_var
->inst_c0
);
470 EMIT_NEW_LOAD_MEMBASE_TYPE (cfg
, ins
, type
, src
->dreg
, offset
);
472 ins
->flags
|= ins_flag
;
474 if (ins_flag
& MONO_INST_VOLATILE
) {
475 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
476 mini_emit_memory_barrier (cfg
, MONO_MEMORY_BARRIER_ACQ
);
484 mini_emit_memory_store (MonoCompile
*cfg
, MonoType
*type
, MonoInst
*dest
, MonoInst
*value
, int ins_flag
)
486 if (ins_flag
& MONO_INST_VOLATILE
) {
487 /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
488 mini_emit_memory_barrier (cfg
, MONO_MEMORY_BARRIER_REL
);
491 if (ins_flag
& MONO_INST_UNALIGNED
) {
492 MonoInst
*addr
, *mov
, *tmp_var
;
494 tmp_var
= mono_compile_create_var (cfg
, type
, OP_LOCAL
);
495 EMIT_NEW_TEMPSTORE (cfg
, mov
, tmp_var
->inst_c0
, value
);
496 EMIT_NEW_VARLOADA (cfg
, addr
, tmp_var
, tmp_var
->inst_vtype
);
497 mini_emit_memory_copy_internal (cfg
, dest
, addr
, mono_class_from_mono_type_internal (type
), 1, FALSE
);
501 /* FIXME: should check item at sp [1] is compatible with the type of the store. */
502 EMIT_NEW_STORE_MEMBASE_TYPE (cfg
, ins
, type
, dest
->dreg
, 0, value
->dreg
);
503 ins
->flags
|= ins_flag
;
506 if (cfg
->gen_write_barriers
&& cfg
->method
->wrapper_type
!= MONO_WRAPPER_WRITE_BARRIER
&&
507 mini_type_is_reference (type
) && !MONO_INS_IS_PCONST_NULL (value
)) {
508 /* insert call to write barrier */
509 mini_emit_write_barrier (cfg
, dest
, value
);
514 mini_emit_memory_copy_bytes (MonoCompile
*cfg
, MonoInst
*dest
, MonoInst
*src
, MonoInst
*size
, int ins_flag
)
516 int align
= (ins_flag
& MONO_INST_UNALIGNED
) ? 1 : TARGET_SIZEOF_VOID_P
;
519 * FIXME: It's unclear whether we should be emitting both the acquire
520 * and release barriers for cpblk. It is technically both a load and
521 * store operation, so it seems like that's the sensible thing to do.
523 * FIXME: We emit full barriers on both sides of the operation for
524 * simplicity. We should have a separate atomic memcpy method instead.
526 if (ins_flag
& MONO_INST_VOLATILE
) {
527 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
528 mini_emit_memory_barrier (cfg
, MONO_MEMORY_BARRIER_SEQ
);
531 if ((cfg
->opt
& MONO_OPT_INTRINS
) && (size
->opcode
== OP_ICONST
)) {
532 mini_emit_memcpy_const_size (cfg
, dest
, src
, size
->inst_c0
, align
);
534 mini_emit_memcpy_internal (cfg
, dest
, src
, size
, 0, align
);
537 if (ins_flag
& MONO_INST_VOLATILE
) {
538 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
539 mini_emit_memory_barrier (cfg
, MONO_MEMORY_BARRIER_SEQ
);
544 mini_emit_memory_init_bytes (MonoCompile
*cfg
, MonoInst
*dest
, MonoInst
*value
, MonoInst
*size
, int ins_flag
)
546 int align
= (ins_flag
& MONO_INST_UNALIGNED
) ? 1 : TARGET_SIZEOF_VOID_P
;
548 if (ins_flag
& MONO_INST_VOLATILE
) {
549 /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
550 mini_emit_memory_barrier (cfg
, MONO_MEMORY_BARRIER_REL
);
553 //FIXME unrolled memset only supports zeroing
554 if ((cfg
->opt
& MONO_OPT_INTRINS
) && (size
->opcode
== OP_ICONST
) && (value
->opcode
== OP_ICONST
) && (value
->inst_c0
== 0)) {
555 mini_emit_memset_const_size (cfg
, dest
, value
->inst_c0
, size
->inst_c0
, align
);
557 mini_emit_memset_internal (cfg
, dest
, value
, 0, size
, 0, align
);
563 * If @klass is a valuetype, emit code to copy a value with source address in @src and destination address in @dest.
564 * If @klass is a ref type, copy a pointer instead.
568 mini_emit_memory_copy (MonoCompile
*cfg
, MonoInst
*dest
, MonoInst
*src
, MonoClass
*klass
, gboolean native
, int ins_flag
)
570 int explicit_align
= 0;
571 if (ins_flag
& MONO_INST_UNALIGNED
)
575 * FIXME: It's unclear whether we should be emitting both the acquire
576 * and release barriers for cpblk. It is technically both a load and
577 * store operation, so it seems like that's the sensible thing to do.
579 * FIXME: We emit full barriers on both sides of the operation for
580 * simplicity. We should have a separate atomic memcpy method instead.
582 if (ins_flag
& MONO_INST_VOLATILE
) {
583 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
584 mini_emit_memory_barrier (cfg
, MONO_MEMORY_BARRIER_SEQ
);
587 mini_emit_memory_copy_internal (cfg
, dest
, src
, klass
, explicit_align
, native
);
589 if (ins_flag
& MONO_INST_VOLATILE
) {
590 /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
591 mini_emit_memory_barrier (cfg
, MONO_MEMORY_BARRIER_SEQ
);
594 #else /* !DISABLE_JIT */
596 MONO_EMPTY_SOURCE_FILE (memory_access
);