diff --git a/gcc/ChangeLog b/gcc/ChangeLog
index 43098de9f47590ae3309442343b81239c4e1bf8e..331c059eeac52f61274b0c81750d91690658720f 100644
--- a/gcc/ChangeLog
+++ b/gcc/ChangeLog
@@ -1,4 +1,12 @@
-2005-12-31  Andrew Pinski  <pinskia@ohysics.uc.edu>
+2006-01-01  David Edelsohn  <edelsohn@gnu.org>
+
+	* config/rs6000/rs6000.c (rs6000_expand_compare_and_swapqhi): New.
+	(rs6000_split_compare_and_swapqhi): New.
+	* config/rs6000/sync.md (sync_compare_and_swap{hi,qi}): New.
+	(sync_compare_and_swapqhi_internal): New.
+	* config/rs6000/rs6000-protos.h: Declare.
+
+2005-12-31  Andrew Pinski  <pinskia@physics.uc.edu>
 
 	PR tree-opt/25612
 	* tree-ssa-pre.c (compute_avail): Treat the static chain decl as a
diff --git a/gcc/config/rs6000/rs6000-protos.h b/gcc/config/rs6000/rs6000-protos.h
index a7cc3a6ddc1f26eb47f451ec2705fe81e30f3671..c1f6a045b4802b739742f6b9f5fc844ca069e383 100644
--- a/gcc/config/rs6000/rs6000-protos.h
+++ b/gcc/config/rs6000/rs6000-protos.h
@@ -86,6 +86,8 @@ extern void rs6000_emit_sync (enum rtx_code, enum machine_mode,
 			      rtx, rtx, rtx, rtx, bool);
 extern void rs6000_split_atomic_op (enum rtx_code, rtx, rtx, rtx, rtx, rtx);
 extern void rs6000_split_compare_and_swap (rtx, rtx, rtx, rtx, rtx);
+extern void rs6000_expand_compare_and_swapqhi (rtx, rtx, rtx, rtx);
+extern void rs6000_split_compare_and_swapqhi (rtx, rtx, rtx, rtx, rtx, rtx);
 extern void rs6000_split_lock_test_and_set (rtx, rtx, rtx, rtx);
 extern void rs6000_emit_swdivsf (rtx, rtx, rtx);
 extern void rs6000_emit_swdivdf (rtx, rtx, rtx);
diff --git a/gcc/config/rs6000/rs6000.c b/gcc/config/rs6000/rs6000.c
index 7bec9103c559e60c9fd111f280705d2ebbadb3e1..196fb2f87a05d3fa42bafb00d1d12a157abf4095 100644
--- a/gcc/config/rs6000/rs6000.c
+++ b/gcc/config/rs6000/rs6000.c
@@ -12275,6 +12275,96 @@ rs6000_split_lock_test_and_set (rtx retval, rtx mem, rtx val, rtx scratch)
   emit_insn (gen_isync ());
 }
 
+void
+rs6000_expand_compare_and_swapqhi (rtx dst, rtx mem, rtx oldval, rtx newval)
+{
+  enum machine_mode mode = GET_MODE (mem);
+  rtx addrSI, align, wdst, shift, mask;
+  HOST_WIDE_INT shift_mask = mode == QImode ? 0x18 : 0x10;
+  HOST_WIDE_INT imask = GET_MODE_MASK (mode);
+
+  /* Shift amount for subword relative to aligned word.  */
+  addrSI = force_reg (SImode, gen_lowpart_common (SImode, XEXP (mem, 0)));
+  shift = gen_reg_rtx (SImode);
+  emit_insn (gen_rlwinm (shift, addrSI, GEN_INT (3),
+			 GEN_INT (shift_mask)));
+  emit_insn (gen_xorsi3 (shift, shift, GEN_INT (shift_mask)));
+
+  /* Shift and mask old value into position within word.  */
+  oldval = convert_modes (SImode, mode, oldval, 1);
+  oldval = expand_binop (SImode, and_optab,
+			 oldval, GEN_INT (imask), NULL_RTX,
+			 1, OPTAB_LIB_WIDEN);
+  emit_insn (gen_ashlsi3 (oldval, oldval, shift));
+
+  /* Shift and mask new value into position within word.  */
+  newval = convert_modes (SImode, mode, newval, 1);
+  newval = expand_binop (SImode, and_optab,
+			 newval, GEN_INT (imask), NULL_RTX,
+			 1, OPTAB_LIB_WIDEN);
+  emit_insn (gen_ashlsi3 (newval, newval, shift));
+
+  /* Mask for insertion.  */
+  mask = gen_reg_rtx (SImode);
+  emit_move_insn (mask, GEN_INT (imask));
+  emit_insn (gen_ashlsi3 (mask, mask, shift));
+
+  /* Address of aligned word containing subword.  */
+  align = expand_binop (Pmode, and_optab, XEXP (mem, 0), GEN_INT (-4),
+			NULL_RTX, 1, OPTAB_LIB_WIDEN);
+  mem = change_address (mem, SImode, align);
+  set_mem_align (mem, 32);
+  MEM_VOLATILE_P (mem) = 1;
+
+  wdst = gen_reg_rtx (SImode);
+  emit_insn (gen_sync_compare_and_swapqhi_internal (wdst, mask,
+						    oldval, newval, mem));
+
+  emit_move_insn (dst, gen_lowpart (mode, wdst));
+}
+
+void
+rs6000_split_compare_and_swapqhi (rtx dest, rtx mask,
+				  rtx oldval, rtx newval, rtx mem,
+				  rtx scratch)
+{
+  rtx label1, label2, x, cond = gen_rtx_REG (CCmode, CR0_REGNO);
+
+  emit_insn (gen_memory_barrier ());
+  label1 = gen_rtx_LABEL_REF (VOIDmode, gen_label_rtx ());
+  label2 = gen_rtx_LABEL_REF (VOIDmode, gen_label_rtx ());
+  emit_label (XEXP (label1, 0));
+
+  emit_load_locked (SImode, scratch, mem);
+
+  /* Mask subword within loaded value for comparison with oldval.
+     Use UNSPEC_AND to avoid clobber.*/
+  emit_insn (gen_rtx_SET (SImode, dest,
+			  gen_rtx_UNSPEC (SImode,
+					  gen_rtvec (2, scratch, mask),
+					  UNSPEC_AND)));
+
+  x = gen_rtx_COMPARE (CCmode, dest, oldval);
+  emit_insn (gen_rtx_SET (VOIDmode, cond, x));
+
+  x = gen_rtx_NE (VOIDmode, cond, const0_rtx);
+  emit_unlikely_jump (x, label2);
+
+  /* Clear subword within loaded value for insertion of new value.  */
+  emit_insn (gen_rtx_SET (SImode, scratch,
+			  gen_rtx_AND (SImode,
+				       gen_rtx_NOT (SImode, mask), scratch)));
+  emit_insn (gen_iorsi3 (scratch, scratch, newval));
+  emit_store_conditional (SImode, cond, mem, scratch);
+
+  x = gen_rtx_NE (VOIDmode, cond, const0_rtx);
+  emit_unlikely_jump (x, label1);
+
+  emit_insn (gen_isync ());
+  emit_label (XEXP (label2, 0));
+}
+
+
   /* Emit instructions to move SRC to DST.  Called by splitters for
    multi-register moves.  It will emit at most one instruction for
    each register that is accessed; that is, it won't emit li/lis pairs
diff --git a/gcc/config/rs6000/sync.md b/gcc/config/rs6000/sync.md
index 92a422376050f562d2e1caf202310602a0992786..b244ef639863a074929c1a3278c4b7d3580a75aa 100644
--- a/gcc/config/rs6000/sync.md
+++ b/gcc/config/rs6000/sync.md
@@ -86,6 +86,52 @@
   DONE;
 })
 
+(define_expand "sync_compare_and_swaphi"
+  [(match_operand:HI 0 "gpc_reg_operand" "")
+   (match_operand:HI 1 "memory_operand" "")
+   (match_operand:HI 2 "gpc_reg_operand" "")
+   (match_operand:HI 3 "gpc_reg_operand" "")]
+  "TARGET_POWERPC"
+{
+  rs6000_expand_compare_and_swapqhi (operands[0], operands[1],
+				     operands[2], operands[3]);
+  DONE;
+})
+
+(define_expand "sync_compare_and_swapqi"
+  [(match_operand:QI 0 "gpc_reg_operand" "")
+   (match_operand:QI 1 "memory_operand" "")
+   (match_operand:QI 2 "gpc_reg_operand" "")
+   (match_operand:QI 3 "gpc_reg_operand" "")]
+  "TARGET_POWERPC"
+{
+  rs6000_expand_compare_and_swapqhi (operands[0], operands[1],
+				     operands[2], operands[3]);
+  DONE;
+})
+
+(define_insn_and_split "sync_compare_and_swapqhi_internal"
+  [(set (match_operand:SI 0 "gpc_reg_operand" "=&r")
+	(match_operand:SI 4 "memory_operand" "+Z"))
+   (set (match_dup 4)
+        (unspec:SI
+          [(match_operand:SI 1 "gpc_reg_operand" "r")
+           (match_operand:SI 2 "gpc_reg_operand" "r")
+           (match_operand:SI 3 "gpc_reg_operand" "r")]
+          UNSPEC_CMPXCHG))
+   (clobber (match_scratch:SI 5 "=&r"))
+   (clobber (match_scratch:CC 6 "=&x"))]
+  "TARGET_POWERPC"
+  "#"
+  "&& reload_completed"
+  [(const_int 0)]
+{
+  rs6000_split_compare_and_swapqhi (operands[0], operands[1],
+				    operands[2], operands[3], operands[4],
+				    operands[5]);
+  DONE;
+})
+
 (define_insn_and_split "sync_lock_test_and_set<mode>"
   [(set (match_operand:GPR 0 "gpc_reg_operand" "=&r")
 	(match_operand:GPR 1 "memory_operand" "+Z"))