diff --git a/gcc/config/arm/arm-modes.def b/gcc/config/arm/arm-modes.def
index 4fa7f1b43e5..65cddf68cdb 100644
--- a/gcc/config/arm/arm-modes.def
+++ b/gcc/config/arm/arm-modes.def
@@ -34,12 +34,16 @@ ADJUST_FLOAT_FORMAT (HF, ((arm_fp16_format == ARM_FP16_FORMAT_ALTERNATIVE)
    CC_Cmode should be used if only the C flag is set correctly, after an
      addition.
    CC_Nmode should be used if only the N (sign) flag is set correctly
+   CC_NVmode should be used if only the N and V bits are set correctly,
+     (used for signed comparisons when the carry is propagated in).
    CC_CZmode should be used if only the C and Z flags are correct
    (used for DImode unsigned comparisons).
    CC_RSBmode should be used where the comparison is set by an RSB immediate,
      or NEG instruction.  The form of the comparison for (const - reg) will
      be (COMPARE (not (reg)) (~const)).
    CC_NCVmode should be used if only the N, C, and V flags are correct
+   CC_Bmode should be used if only the C flag is correct after a subtract
+     (eg after an unsigned borrow with carry-in propagation).
    (used for DImode signed comparisons).
    CCmode should be used otherwise.  */
 
@@ -47,6 +51,7 @@ CC_MODE (CC_NOOV);
 CC_MODE (CC_Z);
 CC_MODE (CC_CZ);
 CC_MODE (CC_NCV);
+CC_MODE (CC_NV);
 CC_MODE (CC_SWP);
 CC_MODE (CC_RSB);
 CC_MODE (CCFP);
@@ -62,6 +67,7 @@ CC_MODE (CC_DLTU);
 CC_MODE (CC_DGEU);
 CC_MODE (CC_DGTU);
 CC_MODE (CC_C);
+CC_MODE (CC_B);
 CC_MODE (CC_N);
 CC_MODE (CC_V);
 
diff --git a/gcc/config/arm/arm.c b/gcc/config/arm/arm.c
index ddfe4335169..99c8bd79d30 100644
--- a/gcc/config/arm/arm.c
+++ b/gcc/config/arm/arm.c
@@ -15348,6 +15348,22 @@ arm_select_cc_mode (enum rtx_code op, rtx x, rtx y)
       && (rtx_equal_p (XEXP (x, 0), y) || rtx_equal_p (XEXP (x, 1), y)))
     return CC_Cmode;
 
+  if (GET_MODE (x) == DImode
+      && (op == GE || op == LT)
+      && GET_CODE (x) == SIGN_EXTEND
+      && ((GET_CODE (y) == PLUS
+	   && arm_borrow_operation (XEXP (y, 0), DImode))
+	  || arm_borrow_operation (y, DImode)))
+    return CC_NVmode;
+
+  if (GET_MODE (x) == DImode
+      && (op == GEU || op == LTU)
+      && GET_CODE (x) == ZERO_EXTEND
+      && ((GET_CODE (y) == PLUS
+	   && arm_borrow_operation (XEXP (y, 0), DImode))
+	  || arm_borrow_operation (y, DImode)))
+    return CC_Bmode;
+
   if (GET_MODE (x) == DImode || GET_MODE (y) == DImode)
     {
       switch (op)
@@ -15410,16 +15426,198 @@ arm_select_cc_mode (enum rtx_code op, rtx x, rtx y)
 static rtx
 arm_gen_dicompare_reg (rtx_code code, rtx x, rtx y, rtx scratch)
 {
-  /* We don't currently handle DImode in thumb1, but rely on libgcc.  */
+  machine_mode mode;
+  rtx cc_reg;
+
+    /* We don't currently handle DImode in thumb1, but rely on libgcc.  */
   gcc_assert (TARGET_32BIT);
 
+  rtx x_lo = simplify_gen_subreg (SImode, x, DImode,
+				  subreg_lowpart_offset (SImode, DImode));
+  rtx x_hi = simplify_gen_subreg (SImode, x, DImode,
+				  subreg_highpart_offset (SImode, DImode));
+  rtx y_lo = simplify_gen_subreg (SImode, y, DImode,
+				  subreg_lowpart_offset (SImode, DImode));
+  rtx y_hi = simplify_gen_subreg (SImode, y, DImode,
+				  subreg_highpart_offset (SImode, DImode));
+  switch (code)
+    {
+    case EQ:
+    case NE:
+      {
+	/* We should never have X as a const_int in this case.  */
+	gcc_assert (!CONST_INT_P (x));
+
+	if (y_lo == const0_rtx || y_hi == const0_rtx)
+	  {
+	    if (y_lo != const0_rtx)
+	      {
+		rtx scratch2 = scratch ? scratch : gen_reg_rtx (SImode);
+
+		gcc_assert (y_hi == const0_rtx);
+		y_lo = gen_int_mode (-INTVAL (y_lo), SImode);
+		if (!arm_add_operand (y_lo, SImode))
+		  y_lo = force_reg (SImode, y_lo);
+		emit_insn (gen_addsi3 (scratch2, x_lo, y_lo));
+		x_lo = scratch2;
+	      }
+	    else if (y_hi != const0_rtx)
+	      {
+		rtx scratch2 = scratch ? scratch : gen_reg_rtx (SImode);
+
+		y_hi = gen_int_mode (-INTVAL (y_hi), SImode);
+		if (!arm_add_operand (y_hi, SImode))
+		  y_hi = force_reg (SImode, y_hi);
+		emit_insn (gen_addsi3 (scratch2, x_hi, y_hi));
+		x_hi = scratch2;
+	      }
+
+	    if (!scratch)
+	      {
+		gcc_assert (!reload_completed);
+		scratch = gen_rtx_SCRATCH (SImode);
+	      }
+
+	    rtx clobber = gen_rtx_CLOBBER (VOIDmode, scratch);
+	    cc_reg = gen_rtx_REG (CC_NOOVmode, CC_REGNUM);
+
+	    rtx set
+	      = gen_rtx_SET (cc_reg,
+			     gen_rtx_COMPARE (CC_NOOVmode,
+					      gen_rtx_IOR (SImode, x_lo, x_hi),
+					      const0_rtx));
+	    emit_insn (gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, set,
+							      clobber)));
+	    return cc_reg;
+	  }
+
+	if (!arm_add_operand (y_lo, SImode))
+	  y_lo = force_reg (SImode, y_lo);
+
+	if (!arm_add_operand (y_hi, SImode))
+	  y_hi = force_reg (SImode, y_hi);
+
+	rtx cmp1 = gen_rtx_NE (SImode, x_lo, y_lo);
+	rtx cmp2 = gen_rtx_NE (SImode, x_hi, y_hi);
+	rtx conjunction = gen_rtx_IOR (SImode, cmp1, cmp2);
+	mode = SELECT_CC_MODE (code, conjunction, const0_rtx);
+	cc_reg = gen_rtx_REG (mode, CC_REGNUM);
+
+	emit_insn (gen_rtx_SET (cc_reg,
+				gen_rtx_COMPARE (VOIDmode, conjunction,
+						 const0_rtx)));
+	return cc_reg;
+      }
+
+    case LT:
+    case GE:
+      {
+	if (y_lo == const0_rtx)
+	  {
+	    /* If the low word of y is 0, then this is simply a normal
+	       compare of the upper words.  */
+	    if (!arm_add_operand (y_hi, SImode))
+	      y_hi = force_reg (SImode, y_hi);
+
+	    return arm_gen_compare_reg (code, x_hi, y_hi, NULL_RTX);
+	  }
+
+	if (!arm_add_operand (y_lo, SImode))
+	  y_lo = force_reg (SImode, y_lo);
+
+	/* Just for now.  */
+	if (!register_operand (x_lo, SImode))
+	  x_lo = force_reg (SImode, x_lo);
+
+	rtx cmp1
+	  = gen_rtx_LTU (DImode,
+			 arm_gen_compare_reg (LTU, x_lo, y_lo, NULL_RTX),
+			 const0_rtx);
+
+	if (!scratch)
+	  scratch = gen_rtx_SCRATCH (SImode);
+	if (!arm_not_operand (y_hi, SImode))
+	  y_hi = force_reg (SImode, y_hi);
+
+	/* Just for now.  */
+	if (!register_operand (x_hi, SImode))
+	  x_hi = force_reg (SImode, x_hi);
+
+	rtx_insn *insn;
+	if (y_hi == const0_rtx)
+	  insn = emit_insn (gen_cmpsi3_0_carryin_CC_NVout (scratch, x_hi,
+							   cmp1));
+	else if (CONST_INT_P (y_hi))
+	  insn = emit_insn (gen_cmpsi3_imm_carryin_CC_NVout (scratch, x_hi,
+							     y_hi, cmp1));
+	else
+	  insn = emit_insn (gen_cmpsi3_carryin_CC_NVout (scratch, x_hi, y_hi,
+							 cmp1));
+	return SET_DEST (single_set (insn));
+      }
+
+    case LTU:
+    case GEU:
+      {
+	if (y_lo == const0_rtx)
+	  {
+	    /* If the low word of y is 0, then this is simply a normal
+	       compare of the upper words.  */
+	    if (!arm_add_operand (y_hi, SImode))
+	      y_hi = force_reg (SImode, y_hi);
+
+	    return arm_gen_compare_reg (code, x_hi, y_hi, NULL_RTX);
+	  }
+
+	if (!arm_add_operand (y_lo, SImode))
+	  y_lo = force_reg (SImode, y_lo);
+
+	/* Just for now.  */
+	if (!register_operand (x_lo, SImode))
+	  x_lo = force_reg (SImode, x_lo);
+
+	rtx cmp1
+	  = gen_rtx_LTU (DImode,
+			 arm_gen_compare_reg (LTU, x_lo, y_lo, NULL_RTX),
+			 const0_rtx);
+
+	if (!scratch)
+	  scratch = gen_rtx_SCRATCH (SImode);
+	if (!arm_not_operand (y_hi, SImode))
+	  y_hi = force_reg (SImode, y_hi);
+
+	/* Just for now.  */
+	if (!register_operand (x_hi, SImode))
+	  x_hi = force_reg (SImode, x_hi);
+
+	rtx_insn *insn;
+	if (y_hi == const0_rtx)
+	  insn = emit_insn (gen_cmpsi3_0_carryin_CC_Bout (scratch, x_hi,
+							  cmp1));
+	else if (CONST_INT_P (y_hi))
+	  {
+	    /* Constant is viewed as unsigned when zero-extended.  */
+	    y_hi = GEN_INT (UINTVAL (y_hi) & 0xffffffffULL);
+	    insn = emit_insn (gen_cmpsi3_imm_carryin_CC_Bout (scratch, x_hi,
+							      y_hi, cmp1));
+	  }
+	else
+	  insn = emit_insn (gen_cmpsi3_carryin_CC_Bout (scratch, x_hi, y_hi,
+							cmp1));
+	return SET_DEST (single_set (insn));
+      }
+
+    default:
+      break;
+    }
+
   /* We might have X as a constant, Y as a register because of the predicates
      used for cmpdi.  If so, force X to a register here.  */
   if (!REG_P (x))
     x = force_reg (DImode, x);
 
-  machine_mode mode = SELECT_CC_MODE (code, x, y);
-  rtx cc_reg = gen_rtx_REG (mode, CC_REGNUM);
+  mode = SELECT_CC_MODE (code, x, y);
+  cc_reg = gen_rtx_REG (mode, CC_REGNUM);
 
   if (mode != CC_CZmode)
     {
@@ -23803,6 +24001,22 @@ maybe_get_arm_condition_code (rtx comparison)
 	default: return ARM_NV;
 	}
 
+    case E_CC_NVmode:
+      switch (comp_code)
+	{
+	case GE: return ARM_GE;
+	case LT: return ARM_LT;
+	default: return ARM_NV;
+	}
+
+    case E_CC_Bmode:
+      switch (comp_code)
+	{
+	case GEU: return ARM_CS;
+	case LTU: return ARM_CC;
+	default: return ARM_NV;
+	}
+
     case E_CC_Vmode:
       switch (comp_code)
 	{
diff --git a/gcc/config/arm/arm.md b/gcc/config/arm/arm.md
index 9d8b137651f..f0ff4dda396 100644
--- a/gcc/config/arm/arm.md
+++ b/gcc/config/arm/arm.md
@@ -1009,6 +1009,51 @@ (define_insn "subsi3_carryin"
    (set_attr "type" "adc_reg,adc_imm,alu_shift_imm")]
 )
 
+(define_insn "cmpsi3_carryin_<CC_EXTEND>out"
+  [(set (reg:<CC_EXTEND> CC_REGNUM)
+	(compare:<CC_EXTEND>
+	 (SE:DI (match_operand:SI 1 "s_register_operand" "0,r"))
+	 (plus:DI (match_operand:DI 3 "arm_borrow_operation" "")
+		  (SE:DI (match_operand:SI 2 "s_register_operand" "l,r")))))
+   (clobber (match_scratch:SI 0 "=l,r"))]
+  "TARGET_32BIT"
+  "sbcs\\t%0, %1, %2"
+  [(set_attr "conds" "set")
+   (set_attr "arch" "t2,*")
+   (set_attr "length" "2,4")
+   (set_attr "type" "adc_reg")]
+)
+
+;; Similar to the above, but handling a constant which has a different
+;; canonicalization.
+(define_insn "cmpsi3_imm_carryin_<CC_EXTEND>out"
+  [(set (reg:<CC_EXTEND> CC_REGNUM)
+	(compare:<CC_EXTEND>
+	 (SE:DI (match_operand:SI 1 "s_register_operand" "r,r"))
+	 (plus:DI (match_operand:DI 3 "arm_borrow_operation" "")
+		  (match_operand:DI 2 "arm_adcimm_operand" "I,K"))))
+   (clobber (match_scratch:SI 0 "=l,r"))]
+  "TARGET_32BIT"
+  "@
+   sbcs\\t%0, %1, %2
+   adcs\\t%0, %1, #%B2"
+  [(set_attr "conds" "set")
+   (set_attr "type" "adc_imm")]
+)
+
+;; Further canonicalization when the constant is zero.
+(define_insn "cmpsi3_0_carryin_<CC_EXTEND>out"
+  [(set (reg:<CC_EXTEND> CC_REGNUM)
+	(compare:<CC_EXTEND>
+	 (SE:DI (match_operand:SI 1 "s_register_operand" "r,r"))
+	 (match_operand:DI 2 "arm_borrow_operation" "")))
+   (clobber (match_scratch:SI 0 "=l,r"))]
+  "TARGET_32BIT"
+  "sbcs\\t%0, %1, #0"
+  [(set_attr "conds" "set")
+   (set_attr "type" "adc_imm")]
+)
+
 (define_insn "*subsi3_carryin_const"
   [(set (match_operand:SI 0 "s_register_operand" "=r")
 	(minus:SI (plus:SI
diff --git a/gcc/config/arm/iterators.md b/gcc/config/arm/iterators.md
index 77e1645083f..5f1c833ad80 100644
--- a/gcc/config/arm/iterators.md
+++ b/gcc/config/arm/iterators.md
@@ -792,6 +792,10 @@ (define_mode_attr vsi2qi [(V2SI "v8qi") (V4SI "v16qi")])
 ;; Code attributes
 ;;----------------------------------------------------------------------------
 
+;; Determine the mode of a 'wide compare', ie where the carry flag is
+;; propagated into the comparison.
+(define_code_attr CC_EXTEND [(sign_extend "CC_NV") (zero_extend "CC_B")])
+
 ;; Assembler mnemonics for vqh_ops and vqhs_ops iterators.
 (define_code_attr VQH_mnem [(plus "vadd") (smin "vmin") (smax "vmax")
                 (umin "vmin") (umax "vmax")])
diff --git a/gcc/config/arm/predicates.md b/gcc/config/arm/predicates.md
index ed7495b69fc..d9470df8093 100644
--- a/gcc/config/arm/predicates.md
+++ b/gcc/config/arm/predicates.md
@@ -229,6 +229,12 @@ (define_predicate "arm_not_operand"
   (ior (match_operand 0 "arm_rhs_operand")
        (match_operand 0 "arm_not_immediate_operand")))
 
+;; A constant that can be used with ADC(SBC) or SBC(ADC) when bit-wise
+;; inverted.  Similar to arm_not_operand, but excludes registers.
+(define_predicate "arm_adcimm_operand"
+  (ior (match_operand 0 "arm_immediate_operand")
+       (match_operand 0 "arm_not_immediate_operand")))
+
 (define_predicate "arm_di_operand"
   (ior (match_operand 0 "s_register_operand")
        (match_operand 0 "arm_immediate_di_operand")))
