diff --git a/gcc/ChangeLog b/gcc/ChangeLog
index 77e029e14144002c58b0cf02091639acf134425e..069b61c9535b5ab44e4a642e5df697586617aa45 100644
--- a/gcc/ChangeLog
+++ b/gcc/ChangeLog
@@ -1,3 +1,20 @@
+2012-06-19  Tom de Vries  <vries@codesourcery.com>
+	    Maxim Kuvyrkov  <maxim@codesourcery.com>
+
+	* config/mips/mips.c (mips_emit_pre_atomic_barrier_p,)
+	(mips_emit_post_atomic_barrier_p): New static functions.
+	(mips_process_sync_loop): Use them.  Emit sync memory barriers in
+	accordance with memory model semantics.  Add return of CMP result for
+	compare_and_swap.
+	* config/mips/mips.md: Update comment.
+	(sync_cmp): New attribute.
+	(sync_memmodel): New attribute replacing sync_release_barrier.
+	* config/mips/sync.md (UNSPEC_ATOMIC_COMPARE_AND_SWAP,)
+	(UNSPEC_ATOMIC_EXCHANGE, UNSPEC_ATOMIC_FETCH_OP): New constants.
+	(sync_lock_test_and_set, test_and_set_12): Update.
+	(atomic_compare_and_swap, atomic_exchange, atomic_exchange_llsc,)
+	(atomic_fetch_add, atomic_fetch_add_llsc): New patterns.
+
 2012-06-19  Joseph Myers  <joseph@codesourcery.com>
 
 	* config/rs6000/spe.md (*mov_si<mode>_e500_subreg0): Rename to
diff --git a/gcc/config/mips/mips.c b/gcc/config/mips/mips.c
index 6e0f39c1d1c19bfd5d797f9c5d66811d266a4cb9..f37c19499d7ff2f442b9b9502970b7ed255a87d2 100644
--- a/gcc/config/mips/mips.c
+++ b/gcc/config/mips/mips.c
@@ -11976,6 +11976,45 @@ mips_sync_insn2_template (enum attr_sync_insn2 type)
   gcc_unreachable ();
 }
 
+/* Subroutines of the mips_process_sync_loop.
+   Emit barriers as needed for the memory MODEL.  */
+
+static bool
+mips_emit_pre_atomic_barrier_p (enum memmodel model)
+{
+  switch (model)
+    {
+    case MEMMODEL_RELAXED:
+    case MEMMODEL_CONSUME:
+    case MEMMODEL_ACQUIRE:
+      return false;
+    case MEMMODEL_RELEASE:
+    case MEMMODEL_ACQ_REL:
+    case MEMMODEL_SEQ_CST:
+      return true;
+    default:
+      gcc_unreachable ();
+    }
+}
+
+static bool
+mips_emit_post_atomic_barrier_p (enum memmodel model)
+{
+  switch (model)
+    {
+    case MEMMODEL_RELAXED:
+    case MEMMODEL_CONSUME:
+    case MEMMODEL_RELEASE:
+      return false;
+    case MEMMODEL_ACQUIRE:
+    case MEMMODEL_ACQ_REL:
+    case MEMMODEL_SEQ_CST:
+      return true;
+    default:
+      gcc_unreachable ();
+    }
+}
+
 /* OPERANDS are the operands to a sync loop instruction and INDEX is
    the value of the one of the sync_* attributes.  Return the operand
    referred to by the attribute, or DEFAULT_VALUE if the insn doesn't
@@ -11996,11 +12035,13 @@ static void
 mips_process_sync_loop (rtx insn, rtx *operands)
 {
   rtx at, mem, oldval, newval, inclusive_mask, exclusive_mask;
-  rtx required_oldval, insn1_op2, tmp1, tmp2, tmp3;
+  rtx required_oldval, insn1_op2, tmp1, tmp2, tmp3, cmp;
   unsigned int tmp3_insn;
   enum attr_sync_insn1 insn1;
   enum attr_sync_insn2 insn2;
   bool is_64bit_p;
+  int memmodel_attr;
+  enum memmodel model;
 
   /* Read an operand from the sync_WHAT attribute and store it in
      variable WHAT.  DEFAULT is the default value if no attribute
@@ -12017,6 +12058,7 @@ mips_process_sync_loop (rtx insn, rtx *operands)
   /* Read the other attributes.  */
   at = gen_rtx_REG (GET_MODE (mem), AT_REGNUM);
   READ_OPERAND (oldval, at);
+  READ_OPERAND (cmp, 0);
   READ_OPERAND (newval, at);
   READ_OPERAND (inclusive_mask, 0);
   READ_OPERAND (exclusive_mask, 0);
@@ -12025,10 +12067,23 @@ mips_process_sync_loop (rtx insn, rtx *operands)
   insn1 = get_attr_sync_insn1 (insn);
   insn2 = get_attr_sync_insn2 (insn);
 
+  memmodel_attr = get_attr_sync_memmodel (insn);
+  switch (memmodel_attr)
+    {
+    case 10:
+      model = MEMMODEL_ACQ_REL;
+      break;
+    case 11:
+      model = MEMMODEL_ACQUIRE;
+      break;
+    default:
+      model = INTVAL (operands[memmodel_attr]);
+    }
+
   mips_multi_start ();
 
   /* Output the release side of the memory barrier.  */
-  if (get_attr_sync_release_barrier (insn) == SYNC_RELEASE_BARRIER_YES)
+  if (mips_emit_pre_atomic_barrier_p (model))
     {
       if (required_oldval == 0 && TARGET_OCTEON)
 	{
@@ -12066,6 +12121,10 @@ mips_process_sync_loop (rtx insn, rtx *operands)
 	  tmp1 = at;
 	}
       mips_multi_add_insn ("bne\t%0,%z1,2f", tmp1, required_oldval, NULL);
+
+      /* CMP = 0 [delay slot].  */
+      if (cmp)
+        mips_multi_add_insn ("li\t%0,0", cmp, NULL);
     }
 
   /* $TMP1 = OLDVAL & EXCLUSIVE_MASK.  */
@@ -12129,11 +12188,15 @@ mips_process_sync_loop (rtx insn, rtx *operands)
       mips_multi_copy_insn (tmp3_insn);
       mips_multi_set_operand (mips_multi_last_index (), 0, newval);
     }
-  else
+  else if (!(required_oldval && cmp))
     mips_multi_add_insn ("nop", NULL);
 
+  /* CMP = 1 -- either standalone or in a delay slot.  */
+  if (required_oldval && cmp)
+    mips_multi_add_insn ("li\t%0,1", cmp, NULL);
+
   /* Output the acquire side of the memory barrier.  */
-  if (TARGET_SYNC_AFTER_SC)
+  if (TARGET_SYNC_AFTER_SC && mips_emit_post_atomic_barrier_p (model))
     mips_multi_add_insn ("sync", NULL);
 
   /* Output the exit label, if needed.  */
diff --git a/gcc/config/mips/mips.md b/gcc/config/mips/mips.md
index 0d8534015555e19d02aaf39561ccea5f1a54490b..5b1735fe7f580aeb14b166cf6f4ea642c0802aeb 100644
--- a/gcc/config/mips/mips.md
+++ b/gcc/config/mips/mips.md
@@ -349,13 +349,15 @@
 ;;       if (RELEASE_BARRIER == YES) sync
 ;;    1: OLDVAL = *MEM
 ;;       if ((OLDVAL & INCLUSIVE_MASK) != REQUIRED_OLDVAL) goto 2
+;;         CMP  = 0 [delay slot]
 ;;       $TMP1 = OLDVAL & EXCLUSIVE_MASK
 ;;       $TMP2 = INSN1 (OLDVAL, INSN1_OP2)
 ;;       $TMP3 = INSN2 ($TMP2, INCLUSIVE_MASK)
 ;;       $AT |= $TMP1 | $TMP3
 ;;       if (!commit (*MEM = $AT)) goto 1.
 ;;         if (INSN1 != MOVE && INSN1 != LI) NEWVAL = $TMP3 [delay slot]
-;;       sync
+;;       CMP  = 1
+;;       if (ACQUIRE_BARRIER == YES) sync
 ;;    2:
 ;;
 ;; where "$" values are temporaries and where the other values are
@@ -364,6 +366,7 @@
 ;; specified, the following values are used instead:
 ;;
 ;;    - OLDVAL: $AT
+;;    - CMP: NONE
 ;;    - NEWVAL: $AT
 ;;    - INCLUSIVE_MASK: -1
 ;;    - REQUIRED_OLDVAL: OLDVAL & INCLUSIVE_MASK
@@ -375,6 +378,7 @@
 ;; but the gen* programs don't yet support that.
 (define_attr "sync_mem" "none,0,1,2,3,4,5" (const_string "none"))
 (define_attr "sync_oldval" "none,0,1,2,3,4,5" (const_string "none"))
+(define_attr "sync_cmp" "none,0,1,2,3,4,5" (const_string "none"))
 (define_attr "sync_newval" "none,0,1,2,3,4,5" (const_string "none"))
 (define_attr "sync_inclusive_mask" "none,0,1,2,3,4,5" (const_string "none"))
 (define_attr "sync_exclusive_mask" "none,0,1,2,3,4,5" (const_string "none"))
@@ -384,8 +388,11 @@
   (const_string "move"))
 (define_attr "sync_insn2" "nop,and,xor,not"
   (const_string "nop"))
-(define_attr "sync_release_barrier" "yes,no"
-  (const_string "yes"))
+;; Memory model specifier.
+;; "0"-"9" values specify the operand that stores the memory model value.
+;; "10" specifies MEMMODEL_ACQ_REL,
+;; "11" specifies MEMMODEL_ACQUIRE.
+(define_attr "sync_memmodel" "" (const_int 10))
 
 ;; Length of instruction in bytes.
 (define_attr "length" ""
diff --git a/gcc/config/mips/sync.md b/gcc/config/mips/sync.md
index 1b4097ec225f0ea251fe39fbe266b989b4bf0c9a..604aefa3d0e5180e09292d121e4c0c6a09fcef80 100644
--- a/gcc/config/mips/sync.md
+++ b/gcc/config/mips/sync.md
@@ -29,6 +29,9 @@
   UNSPEC_SYNC_EXCHANGE
   UNSPEC_SYNC_EXCHANGE_12
   UNSPEC_MEMORY_BARRIER
+  UNSPEC_ATOMIC_COMPARE_AND_SWAP
+  UNSPEC_ATOMIC_EXCHANGE
+  UNSPEC_ATOMIC_FETCH_OP
 ])
 
 ;; Atomic fetch bitwise operations.
@@ -54,6 +57,7 @@
   "GENERATE_SYNC"
   { return mips_output_sync (); })
 
+;; Can be removed in favor of atomic_compare_and_swap below.
 (define_insn "sync_compare_and_swap<mode>"
   [(set (match_operand:GPR 0 "register_operand" "=&d,&d")
 	(match_operand:GPR 1 "memory_operand" "+R,R"))
@@ -368,6 +372,7 @@
    (set_attr "sync_mem" "0")
    (set_attr "sync_insn1_op2" "1")])
 
+;; Can be removed in favor of atomic_fetch_add below.
 (define_insn "sync_old_add<mode>"
   [(set (match_operand:GPR 0 "register_operand" "=&d,&d")
 	(match_operand:GPR 1 "memory_operand" "+R,R"))
@@ -521,7 +526,7 @@
 	 UNSPEC_SYNC_EXCHANGE))]
   "GENERATE_LL_SC"
   { return mips_output_sync_loop (insn, operands); }
-  [(set_attr "sync_release_barrier" "no")
+  [(set_attr "sync_memmodel" "11")
    (set_attr "sync_insn1" "li,move")
    (set_attr "sync_oldval" "0")
    (set_attr "sync_mem" "1")
@@ -550,7 +555,7 @@
 	  UNSPEC_SYNC_EXCHANGE_12))]
   "GENERATE_LL_SC"
   { return mips_output_sync_loop (insn, operands); }
-  [(set_attr "sync_release_barrier" "no")
+  [(set_attr "sync_memmodel" "11")
    (set_attr "sync_oldval" "0")
    (set_attr "sync_mem" "1")
    ;; Unused, but needed to give the number of operands expected by
@@ -558,3 +563,101 @@
    (set_attr "sync_inclusive_mask" "2")
    (set_attr "sync_exclusive_mask" "3")
    (set_attr "sync_insn1_op2" "4")])
+
+(define_insn "atomic_compare_and_swap<mode>"
+  [(set (match_operand:GPR 0 "register_operand" "=&d,&d")
+	;; Logically this unspec is an "eq" operator, but we need to obscure
+	;; reads and writes from/to memory with an unspec to prevent
+	;; optimizations on shared memory locations.  Otherwise, comparison in
+	;; { mem = 2; if (atomic_cmp_swap(mem,...) == 2) ...; }
+	;; would be optimized away.  In addition to that we need to use
+	;; unspec_volatile, not just plain unspec -- for the sake of other
+	;; threads -- to make sure we don't remove the entirety of the pattern
+	;; just because current thread doesn't observe any effect from it.
+	;; TODO: the obscuring unspec can be relaxed for permissive memory
+	;; models.
+	;; Same applies to other atomic_* patterns.
+	(unspec_volatile:GPR [(match_operand:GPR 2 "memory_operand" "+R,R")
+			      (match_operand:GPR 3 "reg_or_0_operand" "dJ,dJ")]
+	 UNSPEC_ATOMIC_COMPARE_AND_SWAP))
+   (set (match_operand:GPR 1 "register_operand" "=&d,&d")
+	(unspec_volatile:GPR [(match_dup 2)]
+	 UNSPEC_ATOMIC_COMPARE_AND_SWAP))
+   (set (match_dup 2)
+	(unspec_volatile:GPR [(match_dup 2)
+			      (match_dup 3)
+			      (match_operand:GPR 4 "arith_operand" "I,d")]
+	 UNSPEC_ATOMIC_COMPARE_AND_SWAP))
+   (unspec_volatile:GPR [(match_operand:SI 5 "const_int_operand")
+			 (match_operand:SI 6 "const_int_operand")
+			 (match_operand:SI 7 "const_int_operand")]
+    UNSPEC_ATOMIC_COMPARE_AND_SWAP)]
+  "GENERATE_LL_SC"
+  { return mips_output_sync_loop (insn, operands); }
+  [(set_attr "sync_insn1" "li,move")
+   (set_attr "sync_oldval" "1")
+   (set_attr "sync_cmp" "0")
+   (set_attr "sync_mem" "2")
+   (set_attr "sync_required_oldval" "3")
+   (set_attr "sync_insn1_op2" "4")
+   (set_attr "sync_memmodel" "6")])
+
+(define_expand "atomic_exchange<mode>"
+  [(match_operand:GPR 0 "register_operand")
+   (match_operand:GPR 1 "memory_operand")
+   (match_operand:GPR 2 "arith_operand")
+   (match_operand:SI 3 "const_int_operand")]
+  "GENERATE_LL_SC"
+{
+    emit_insn (gen_atomic_exchange<mode>_llsc (operands[0], operands[1],
+					       operands[2], operands[3]));
+  DONE;
+})
+
+(define_insn "atomic_exchange<mode>_llsc"
+  [(set (match_operand:GPR 0 "register_operand" "=&d,&d")
+	(unspec_volatile:GPR [(match_operand:GPR 1 "memory_operand" "+R,R")]
+	 UNSPEC_ATOMIC_EXCHANGE))
+   (set (match_dup 1)
+	(unspec_volatile:GPR [(match_operand:GPR 2 "arith_operand" "I,d")]
+	 UNSPEC_ATOMIC_EXCHANGE))
+   (unspec_volatile:GPR [(match_operand:SI 3 "const_int_operand")]
+    UNSPEC_ATOMIC_EXCHANGE)]
+  "GENERATE_LL_SC"
+  { return mips_output_sync_loop (insn, operands); }
+  [(set_attr "sync_insn1" "li,move")
+   (set_attr "sync_oldval" "0")
+   (set_attr "sync_mem" "1")
+   (set_attr "sync_insn1_op2" "2")
+   (set_attr "sync_memmodel" "3")])
+
+(define_expand "atomic_fetch_add<mode>"
+  [(match_operand:GPR 0 "register_operand")
+   (match_operand:GPR 1 "memory_operand")
+   (match_operand:GPR 2 "arith_operand")
+   (match_operand:SI 3 "const_int_operand")]
+  "GENERATE_LL_SC"
+{
+    emit_insn (gen_atomic_fetch_add<mode>_llsc (operands[0], operands[1],
+						operands[2], operands[3]));
+  DONE;
+})
+
+(define_insn "atomic_fetch_add<mode>_llsc"
+  [(set (match_operand:GPR 0 "register_operand" "=&d,&d")
+	(unspec_volatile:GPR [(match_operand:GPR 1 "memory_operand" "+R,R")]
+	 UNSPEC_ATOMIC_FETCH_OP))
+   (set (match_dup 1)
+	(unspec_volatile:GPR
+	 [(plus:GPR (match_dup 1)
+		    (match_operand:GPR 2 "arith_operand" "I,d"))]
+	 UNSPEC_ATOMIC_FETCH_OP))
+   (unspec_volatile:GPR [(match_operand:SI 3 "const_int_operand")]
+    UNSPEC_ATOMIC_FETCH_OP)]
+  "GENERATE_LL_SC"
+  { return mips_output_sync_loop (insn, operands); }
+  [(set_attr "sync_insn1" "addiu,addu")
+   (set_attr "sync_oldval" "0")
+   (set_attr "sync_mem" "1")
+   (set_attr "sync_insn1_op2" "2")
+   (set_attr "sync_memmodel" "3")])