[mini] Memory barrier fixes, simplifications, and optimizations.
authorAlex Rønne Petersen <alexrp@xamarin.com>
Thu, 22 Jan 2015 06:16:08 +0000 (07:16 +0100)
committerAlex Rønne Petersen <alexrp@xamarin.com>
Sat, 24 Jan 2015 00:08:01 +0000 (01:08 +0100)
* Barrier semantics is now specified as acquire, release, and sequentially
  consistent. This is in line with CIL/.NET semantics and also maps better to
  LLVM's fence types.
* Use the mfence instruction for memory barriers on AMD64 as it's smaller
  (and presumably also faster).
* We now emit specific barriers when using LLVM instead of always emitting
  sequentially consistent barriers.
* Barriers emitted for the CIL volatile prefix instruction are now more
  specific instead of always being sequentially consistent.
* Fixed emission of CEE_MONO_MEMORY_BARRIER in SGen's managed allocators. For
  some reason (that I haven't had time to look into), mono_mb_emit_op ()
  clobbers the barrier kind operand. This meant that in many cases we ended
  up not actually emitting any barriers because the switches in the backends
  didn't handle the clobbered values. mono_mb_emit_i4 () seems to work.
* Fixed handling of CEE_MONO_MEMORY_BARRIER in mono_method_to_ir (). Since it's
  a custom opcode, the opcode is actually 2 bytes long. Another source of
  barriers not actually being emitted.

13 files changed:
mono/arch/x86/x86-codegen.h
mono/metadata/sgen-alloc.c
mono/mini/cpu-amd64.md
mono/mini/cpu-arm.md
mono/mini/cpu-arm64.md
mono/mini/method-to-ir.c
mono/mini/mini-amd64.c
mono/mini/mini-llvm-cpp.cpp
mono/mini/mini-llvm-cpp.h
mono/mini/mini-llvm.c
mono/mini/mini-x86.c
mono/mini/mini.h
mono/utils/mono-memory-model.h

index ad6282f64090332eb6f3b23a97f9589f4f9e594e..ff3fe325a791983f0d28784154b7149543e877bd 100644 (file)
@@ -522,6 +522,14 @@ typedef union {
 
 #endif /* __native_client_codegen__ */
 
+#define x86_mfence(inst) \
+       do {    \
+               x86_codegen_pre(&(inst), 3); \
+               *(inst)++ = 0x0f;       \
+               *(inst)++ = 0xae;       \
+               *(inst)++ = 0xf0;       \
+       } while (0)
+
 #define x86_rdtsc(inst) \
        do {    \
                x86_codegen_pre(&(inst), 2); \
index 96cd79bcb73991afc2775b637d7a1f19cd83a2a8..12beef893f16a8f871527fcba4d24a51c3346752 100644 (file)
@@ -1010,8 +1010,9 @@ create_allocator (int atype)
        mono_mb_emit_byte (mb, CEE_STIND_I);
 
        /*The tlab store must be visible before the the vtable store. This could be replaced with a DDS but doing it with IL would be tricky. */
-       mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX);
-       mono_mb_emit_op (mb, CEE_MONO_MEMORY_BARRIER, (gpointer)StoreStoreBarrier);
+       mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
+       mono_mb_emit_byte (mb, CEE_MONO_MEMORY_BARRIER);
+       mono_mb_emit_i4 (mb, MONO_MEMORY_BARRIER_REL);
 
        /* *p = vtable; */
        mono_mb_emit_ldloc (mb, p_var);
@@ -1049,8 +1050,9 @@ create_allocator (int atype)
        /*
        We must make sure both vtable and max_length are globaly visible before returning to managed land.
        */
-       mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX);
-       mono_mb_emit_op (mb, CEE_MONO_MEMORY_BARRIER, (gpointer)StoreStoreBarrier);
+       mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
+       mono_mb_emit_byte (mb, CEE_MONO_MEMORY_BARRIER);
+       mono_mb_emit_i4 (mb, MONO_MEMORY_BARRIER_REL);
 
        /* return p */
        mono_mb_emit_ldloc (mb, p_var);
index ca62b660056ade35cb649dc15c7534e421c09d1a..fdfea25e740a61f718e683048d138c1cafd4bacd 100755 (executable)
@@ -304,7 +304,7 @@ atomic_exchange_i4: src1:b src2:i dest:a len:32
 atomic_exchange_i8: src1:b src2:i dest:a len:32
 atomic_cas_i4: src1:b src2:i src3:a dest:a len:24
 atomic_cas_i8: src1:b src2:i src3:a dest:a len:24
-memory_barrier: len:16
+memory_barrier: len:3
 adc: dest:i src1:i src2:i len:3 clob:1
 addcc: dest:i src1:i src2:i len:3 clob:1
 subcc: dest:i src1:i src2:i len:3 clob:1
index 1ff8af77acb46825fae0cfeb0afbca416ffb8cc7..765af044b6759c52316514920588298aa986f315 100644 (file)
@@ -48,7 +48,6 @@
 #
 # See the code in mini-x86.c for more details on how the specifiers are used.
 #
-memory_barrier: len:8 clob:a
 nop: len:4
 relaxed_nop: len:4
 break: len:4
@@ -351,3 +350,4 @@ gc_param_slot_liveness_def: len:0
 atomic_add_i4: dest:i src1:i src2:i len:64
 atomic_exchange_i4: dest:i src1:i src2:i len:64
 atomic_cas_i4: dest:i src1:i src2:i src3:i len:64
+memory_barrier: len:8 clob:a
index 3ef04bb2e2d9b7ef3f0e7dd9e4b1819c4447087e..70d2fde3c2aa70db48a9b2e4c95f6112ad0032ce 100644 (file)
@@ -48,7 +48,6 @@
 #
 # See the code in mini-x86.c for more details on how the specifiers are used.
 #
-memory_barrier: len:8 clob:a
 nop: len:4
 relaxed_nop: len:4
 break: len:20
@@ -421,5 +420,4 @@ atomic_exchange_i4: dest:i src1:i src2:i len:32
 atomic_exchange_i8: dest:i src1:i src2:i len:32
 atomic_cas_i4: dest:i src1:i src2:i src3:i len:32
 atomic_cas_i8: dest:i src1:i src2:i src3:i len:32
-
-
+memory_barrier: len:8 clob:a
index a99241f3892175b94a7f95c8a257b7036a2589fa..a98782ccecb69ff783af8728515950486aa8cd37 100755 (executable)
@@ -5566,7 +5566,7 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign
                        MONO_ADD_INS (cfg->cbb, ins);
                        return ins;
                } else if (strcmp (cmethod->name, "MemoryBarrier") == 0) {
-                       return emit_memory_barrier (cfg, FullBarrier);
+                       return emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
                }
        } else if (cmethod->klass == mono_defaults.monitor_class) {
 #if defined(MONO_ARCH_MONITOR_OBJECT_REG)
@@ -5630,7 +5630,7 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign
                if (strcmp (cmethod->name, "Read") == 0 && (fsig->params [0]->type == MONO_TYPE_I8)) {
                        MonoInst *load_ins;
 
-                       emit_memory_barrier (cfg, FullBarrier);
+                       emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
 
                        /* 64 bit reads are already atomic */
                        MONO_INST_NEW (cfg, load_ins, OP_LOADI8_MEMBASE);
@@ -5639,7 +5639,7 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign
                        load_ins->inst_offset = 0;
                        MONO_ADD_INS (cfg->cbb, load_ins);
 
-                       emit_memory_barrier (cfg, FullBarrier);
+                       emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
 
                        ins = load_ins;
                }
@@ -5813,7 +5813,7 @@ mini_emit_inst_for_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign
                }
 
                if (strcmp (cmethod->name, "MemoryBarrier") == 0)
-                       ins = emit_memory_barrier (cfg, FullBarrier);
+                       ins = emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
 
                if (ins)
                        return ins;
@@ -9299,8 +9299,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                        *sp++ = ins;
                        if (ins_flag & MONO_INST_VOLATILE) {
                                /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
-                               /* FIXME it's questionable if acquire semantics require full barrier or just LoadLoad*/
-                               emit_memory_barrier (cfg, FullBarrier);
+                               emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_ACQ);
                        }
                        ins_flag = 0;
                        ++ip;
@@ -9318,8 +9317,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
 
                        if (ins_flag & MONO_INST_VOLATILE) {
                                /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
-                               /* FIXME it's questionable if acquire semantics require full barrier or just LoadLoad*/
-                               emit_memory_barrier (cfg, FullBarrier);
+                               emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL);
                        }
 
                        NEW_STORE_MEMBASE (cfg, ins, stind_to_store_membase (*ip), sp [0]->dreg, 0, sp [1]->dreg);
@@ -9585,8 +9583,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                                ip += stloc_len;
                                if (ins_flag & MONO_INST_VOLATILE) {
                                        /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
-                                       /* FIXME it's questionable if acquire semantics require full barrier or just LoadLoad*/
-                                       emit_memory_barrier (cfg, FullBarrier);
+                                       emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_ACQ);
                                }
                                ins_flag = 0;
                                break;
@@ -9613,8 +9610,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
 
                        if (ins_flag & MONO_INST_VOLATILE) {
                                /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
-                               /* FIXME it's questionable if acquire semantics require full barrier or just LoadLoad*/
-                               emit_memory_barrier (cfg, FullBarrier);
+                               emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_ACQ);
                        }
 
                        ip += 5;
@@ -10563,8 +10559,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
 
                        if ((op == CEE_STFLD || op == CEE_STSFLD) && (ins_flag & MONO_INST_VOLATILE)) {
                                /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
-                               /* FIXME it's questionable if acquire semantics require full barrier or just LoadLoad*/
-                               emit_memory_barrier (cfg, FullBarrier);
+                               emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL);
                        }
 
                        if (op == CEE_LDSFLDA) {
@@ -10675,8 +10670,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
 
                        if ((op == CEE_LDFLD || op == CEE_LDSFLD) && (ins_flag & MONO_INST_VOLATILE)) {
                                /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
-                               /* FIXME it's questionable if acquire semantics require full barrier or just LoadLoad*/
-                               emit_memory_barrier (cfg, FullBarrier);
+                               emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_ACQ);
                        }
 
                        ins_flag = 0;
@@ -10692,8 +10686,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                        CHECK_TYPELOAD (klass);
                        if (ins_flag & MONO_INST_VOLATILE) {
                                /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
-                               /* FIXME it's questionable if acquire semantics require full barrier or just LoadLoad*/
-                               emit_memory_barrier (cfg, FullBarrier);
+                               emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL);
                        }
                        /* FIXME: should check item at sp [1] is compatible with the type of the store. */
                        EMIT_NEW_STORE_MEMBASE_TYPE (cfg, ins, &klass->byval_arg, sp [0]->dreg, 0, sp [1]->dreg);
@@ -11613,9 +11606,9 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                                break;
                        }
                        case CEE_MONO_MEMORY_BARRIER: {
-                               CHECK_OPSIZE (5);
-                               emit_memory_barrier (cfg, (int)read32 (ip + 1));
-                               ip += 5;
+                               CHECK_OPSIZE (6);
+                               emit_memory_barrier (cfg, (int)read32 (ip + 2));
+                               ip += 6;
                                break;
                        }
                        case CEE_MONO_JIT_ATTACH: {
@@ -12101,26 +12094,25 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                                                 * FIXME: It's unclear whether we should be emitting both the acquire
                                                 * and release barriers for cpblk. It is technically both a load and
                                                 * store operation, so it seems like that's the sensible thing to do.
+                                                *
+                                                * FIXME: We emit full barriers on both sides of the operation for
+                                                * simplicity. We should have a separate atomic memcpy method instead.
                                                 */
                                                MonoMethod *memcpy_method = get_memcpy_method ();
-                                               if (ins_flag & MONO_INST_VOLATILE) {
-                                                       /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
-                                                       /* FIXME it's questionable if acquire semantics require full barrier or just LoadLoad*/
-                                                       emit_memory_barrier (cfg, FullBarrier);
-                                               }
+
+                                               if (ins_flag & MONO_INST_VOLATILE)
+                                                       emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
+
                                                call = mono_emit_method_call (cfg, memcpy_method, iargs, NULL);
                                                call->flags |= ins_flag;
-                                               if (ins_flag & MONO_INST_VOLATILE) {
-                                                       /* Volatile loads have acquire semantics, see 12.6.7 in Ecma 335 */
-                                                       /* FIXME it's questionable if acquire semantics require full barrier or just LoadLoad*/
-                                                       emit_memory_barrier (cfg, FullBarrier);
-                                               }
+
+                                               if (ins_flag & MONO_INST_VOLATILE)
+                                                       emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_SEQ);
                                        } else {
                                                MonoMethod *memset_method = get_memset_method ();
                                                if (ins_flag & MONO_INST_VOLATILE) {
                                                        /* Volatile stores have release semantics, see 12.6.7 in Ecma 335 */
-                                                       /* FIXME it's questionable if acquire semantics require full barrier or just LoadLoad*/
-                                                       emit_memory_barrier (cfg, FullBarrier);
+                                                       emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL);
                                                }
                                                call = mono_emit_method_call (cfg, memset_method, iargs, NULL);
                                                call->flags |= ins_flag;
index 6d6d3290e82fec73431af6ef395ed9fd1e63302b..4516285c09ad66f693e05ce5b7f37adacb640a19 100755 (executable)
@@ -5525,14 +5525,8 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                        break;
                }
                case OP_MEMORY_BARRIER: {
-                       switch (ins->backend.memory_barrier_kind) {
-                       case StoreLoadBarrier:
-                       case FullBarrier:
-                               /* http://blogs.sun.com/dave/resource/NHM-Pipeline-Blog-V2.txt */
-                               x86_prefix (code, X86_LOCK_PREFIX);
-                               amd64_alu_membase_imm (code, X86_ADD, AMD64_RSP, 0, 0);
-                               break;
-                       }
+                       if (ins->backend.memory_barrier_kind == MONO_MEMORY_BARRIER_SEQ)
+                               x86_mfence (code);
                        break;
                }
                case OP_ATOMIC_ADD_I4:
index bf5e16f8fac47d2e005ca5c7c4e2f205cc2513c8..34dde6880528df5212270b1660334e904becc7b2 100644 (file)
@@ -390,11 +390,29 @@ mono_llvm_build_atomic_rmw (LLVMBuilderRef builder, AtomicRMWOp op, LLVMValueRef
 }
 
 LLVMValueRef
-mono_llvm_build_fence (LLVMBuilderRef builder)
+mono_llvm_build_fence (LLVMBuilderRef builder, BarrierKind kind)
 {
        FenceInst *ins;
+       AtomicOrdering ordering;
 
-       ins = unwrap (builder)->CreateFence (AcquireRelease);
+       g_assert (kind != LLVM_BARRIER_NONE);
+
+       switch (kind) {
+       case LLVM_BARRIER_ACQ:
+               ordering = Acquire;
+               break;
+       case LLVM_BARRIER_REL:
+               ordering = Release;
+               break;
+       case LLVM_BARRIER_SEQ:
+               ordering = SequentiallyConsistent;
+               break;
+       default:
+               g_assert_not_reached ();
+               break;
+       }
+
+       ins = unwrap (builder)->CreateFence (ordering);
        return wrap (ins);
 }
 
index 554532fd6c929ed1fcb52884d33502cbc36dc58a..f4d542141910621f4d769bb97f6d411a46cb2799 100644 (file)
 
 G_BEGIN_DECLS
 
+/*
+ * Keep in sync with the enum in utils/mono-memory-model.h.
+ */
+typedef enum {
+       LLVM_BARRIER_NONE = 0,
+       LLVM_BARRIER_ACQ = 1,
+       LLVM_BARRIER_REL = 2,
+       LLVM_BARRIER_SEQ = 3,
+} BarrierKind;
+
 typedef enum {
        LLVM_ATOMICRMW_OP_XCHG = 0,
        LLVM_ATOMICRMW_OP_ADD = 1,
@@ -66,7 +76,7 @@ LLVMValueRef
 mono_llvm_build_atomic_rmw (LLVMBuilderRef builder, AtomicRMWOp op, LLVMValueRef ptr, LLVMValueRef val);
 
 LLVMValueRef
-mono_llvm_build_fence (LLVMBuilderRef builder);
+mono_llvm_build_fence (LLVMBuilderRef builder, BarrierKind kind);
 
 void
 mono_llvm_replace_uses_of (LLVMValueRef var, LLVMValueRef v);
index c3b9a344d68b6e62e0b4c71c08b70643f2a4b6ab..435f94b913bf8dda0fa785c275e71f4bbab2ee02 100644 (file)
@@ -3412,7 +3412,7 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb)
                        break;
                }
                case OP_MEMORY_BARRIER: {
-                       mono_llvm_build_fence (builder);
+                       mono_llvm_build_fence (builder, (BarrierKind) ins->backend.memory_barrier_kind);
                        break;
                }
                case OP_RELAXED_NOP: {
index 2bdd415bd149ef98b7df7d40d2906e928b428cee..089c3fee2c05164cd1e8d85aae91ef0b18db4991 100644 (file)
@@ -4213,14 +4213,9 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                        break;
                }
                case OP_MEMORY_BARRIER: {
-                       /* x86 only needs barrier for StoreLoad and FullBarrier */
-                       switch (ins->backend.memory_barrier_kind) {
-                       case StoreLoadBarrier:
-                       case FullBarrier:
-                               /* http://blogs.sun.com/dave/resource/NHM-Pipeline-Blog-V2.txt */
+                       if (ins->backend.memory_barrier_kind == MONO_MEMORY_BARRIER_SEQ) {
                                x86_prefix (code, X86_LOCK_PREFIX);
                                x86_alu_membase_imm (code, X86_ADD, X86_ESP, 0, 0);
-                               break;
                        }
                        break;
                }
index bc825eff879e154785b731aa308b514af20ecac2..e38dba5483614b124ce766229bef5373fcaec7f8 100755 (executable)
@@ -783,7 +783,11 @@ struct MonoInst {
                MonoInst *spill_var; /* for OP_ICONV_TO_R8_RAW and OP_FCONV_TO_R8_X */
                guint16 source_opcode; /*OP_XCONV_R8_TO_I4 needs to know which op was used to do proper widening*/
                int pc_offset; /* OP_GC_LIVERANGE_START/END */
-               int memory_barrier_kind; /* see mono-memory-model.h for valid values */
+
+               /*
+                * memory_barrier: MONO_MEMORY_BARRIER_{ACQ,REL,SEQ}
+                */
+               int memory_barrier_kind;
        } backend;
        
        MonoClass *klass;
index a97a7cc16b213dcd170ee94ab7032a69450d5aad..9762dc5142666639ea57ad2cbeecbf334a39b680 100644 (file)
@@ -34,18 +34,20 @@ TODO: if we find places where a data depencency could replace barriers, add macr
 TODO: some arch with strong consistency, such as x86, support weaker access. We might need to expose more kinds of barriers once we exploit this.
 */
 
+/*
+ * Keep in sync with the enum in mini/mini-llvm-cpp.h.
+ */
+enum {
+    MONO_MEMORY_BARRIER_NONE = 0,
+    MONO_MEMORY_BARRIER_ACQ = 1,
+    MONO_MEMORY_BARRIER_REL = 2,
+    MONO_MEMORY_BARRIER_SEQ = 3,
+};
+
 #define MEMORY_BARRIER mono_memory_barrier ()
 #define LOAD_BARRIER mono_memory_read_barrier ()
 #define STORE_BARRIER mono_memory_write_barrier ()
 
-enum {
-       StoreStoreBarrier,
-       LoadLoadBarrier,
-       StoreLoadBarrier,
-       LoadStoreBarrier,
-       FullBarrier
-};
-
 #if defined(__i386__) || defined(__x86_64__)
 /*
 Both x86 and amd64 follow the SPO memory model: