diff --git a/gcc/config/riscv/bitmanip.md b/gcc/config/riscv/bitmanip.md
index 8769a6b818b..9d4247ec8b9 100644
--- a/gcc/config/riscv/bitmanip.md
+++ b/gcc/config/riscv/bitmanip.md
@@ -877,6 +877,29 @@ (define_insn_and_split ""
    }"
   [(set_attr "type" "bitmanip")])
 
+;; In theory these might be better handled with match.pd patterns, but
+;; the type changes tend to make it ugly, at least for the perl testcases
+(define_insn ""
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(ior:X (and:X (rotate:X (const_int -2)
+				(match_operand:QI 1 "register_operand" "r"))
+		      (match_operand:X 2 "register_operand" "r"))
+	       (ashift:X (const_int 1) (match_operand:QI 3 "register_operand" "r"))))]
+  "TARGET_ZBS && rtx_equal_p (operands[1], operands[3])"
+  "bset\t%0,%2,%1"
+  [(set_attr "type" "bitmanip")])
+
+(define_insn ""
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(and:X (any_or:X (ashift:X (const_int 1)
+				   (match_operand:QI 1 "register_operand" "r"))
+			 (match_operand:X 2 "register_operand" "r"))
+	       (rotate:X (const_int -2)
+			 (match_operand:QI 3 "register_operand" "r"))))]
+  "TARGET_ZBS && rtx_equal_p (operands[1], operands[3])"
+  "bclr\t%0,%2,%1"
+  [(set_attr "type" "bitmanip")])
+
 ;; IF_THEN_ELSE: test for 2 bits of opposite polarity
 (define_insn_and_split "*branch<X:mode>_mask_twobits_equals_singlebit"
   [(set (pc)
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index b0a14a2a82d..78a4a1cd554 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -3712,6 +3712,22 @@ riscv_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno ATTRIBUTE_UN
 	  return true;
 	}
 
+      /* Special case for bset followed by bclr.  */
+      if (GET_CODE (x) == AND
+	  && (GET_CODE (XEXP (x, 0)) == IOR
+	      || GET_CODE (XEXP (x, 0)) == XOR)
+	  && GET_CODE (XEXP (XEXP (x, 0), 0)) == ASHIFT
+	  && XEXP (XEXP (XEXP (x, 0), 0), 0) == CONST1_RTX (word_mode)
+	  && GET_CODE (XEXP (x, 1)) == ROTATE
+	  && CONST_INT_P (XEXP (XEXP (x, 1), 0))
+	  && INTVAL (XEXP (XEXP (x, 1), 0)) == -2
+	  && rtx_equal_p (XEXP (XEXP (XEXP (x, 0), 0), 1),
+			 (XEXP (XEXP (x, 1), 1))))
+	{
+	  *total = COSTS_N_INSNS (1);
+	  return true;
+	}
+
       gcc_fallthrough ();
     case IOR:
     case XOR:
@@ -3734,6 +3750,21 @@ riscv_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno ATTRIBUTE_UN
 	  return true;
 	}
 
+      /* Special case for bclr followed by bset.  */
+      if (GET_CODE (x) == IOR
+	  && GET_CODE (XEXP (x, 0)) == AND
+	  && GET_CODE (XEXP (XEXP (x, 0), 0)) == ROTATE
+	  && CONST_INT_P (XEXP (XEXP (XEXP (x, 0), 0), 0))
+	  && INTVAL (XEXP (XEXP (XEXP (x, 0), 0), 0)) == -2
+	  && GET_CODE (XEXP (x, 1)) == ASHIFT
+	  && XEXP (XEXP (x, 1), 0) == CONST1_RTX (word_mode)
+	  && rtx_equal_p (XEXP (XEXP (XEXP (x, 0), 0), 1),
+			  (XEXP (XEXP (x, 1), 1))))
+	{
+	  *total = COSTS_N_INSNS (1);
+	  return true;
+	}
+
       /* Double-word operations use two single-word operations.  */
       *total = riscv_binary_cost (x, 1, 2);
       return false;
diff --git a/gcc/testsuite/g++.target/riscv/redundant-bitmap-1.C b/gcc/testsuite/g++.target/riscv/redundant-bitmap-1.C
new file mode 100644
index 00000000000..85be608bdc8
--- /dev/null
+++ b/gcc/testsuite/g++.target/riscv/redundant-bitmap-1.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc_zbs -mabi=lp64" } */
+/* { dg-skip-if "" { *-*-* } { "-O0" "-Og" } } */
+
+void setBit(char &a, int b) {
+    char c = 0x1UL << b;
+    a &= ~c;
+    a |= c;
+}
+
+/* { dg-final { scan-assembler-not "bclr\t" } } */
+
diff --git a/gcc/testsuite/g++.target/riscv/redundant-bitmap-2.C b/gcc/testsuite/g++.target/riscv/redundant-bitmap-2.C
new file mode 100644
index 00000000000..9060eb1d769
--- /dev/null
+++ b/gcc/testsuite/g++.target/riscv/redundant-bitmap-2.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc_zbs -mabi=lp64" } */
+/* { dg-skip-if "" { *-*-* } { "-O0" "-Og" } } */
+
+void setBit(char &a, int b) {
+    char c = 0x1UL << b;
+    a |= c;
+    a &= ~c;
+}
+
+/* { dg-final { scan-assembler-not "bset\t" } } */
+
diff --git a/gcc/testsuite/g++.target/riscv/redundant-bitmap-3.C b/gcc/testsuite/g++.target/riscv/redundant-bitmap-3.C
new file mode 100644
index 00000000000..a33d4d11969
--- /dev/null
+++ b/gcc/testsuite/g++.target/riscv/redundant-bitmap-3.C
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc_zbs -mabi=lp64" } */
+/* { dg-skip-if "" { *-*-* } { "-O0" "-Og" } } */
+
+void setBit(char &a, int b) {
+    char c = 0x1UL << b;
+    a ^= c;
+    a &= ~c;
+}
+
+/* { dg-final { scan-assembler-not "binv\t" } } */
+
