diff --git a/gcc/config/riscv/bitmanip.md b/gcc/config/riscv/bitmanip.md
index 4d624514049..59779b48f27 100644
--- a/gcc/config/riscv/bitmanip.md
+++ b/gcc/config/riscv/bitmanip.md
@@ -40,6 +40,7 @@
 				 (ctz "ctz")
 				 (popcount "cpop")])
 
+(define_mode_attr shiftm1 [(SI "const31_operand") (DI "const63_operand")])
 
 ;; ZBA extension.
 
@@ -238,3 +239,104 @@
   "TARGET_ZBB"
   "<bitmanip_insn>\t%0,%1,%2"
   [(set_attr "type" "bitmanip")])
+
+;; ZBS extension.
+
+(define_insn "*bset<mode>"
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(ior:X (ashift:X (const_int 1)
+			 (match_operand:QI 2 "register_operand" "r"))
+	       (match_operand:X 1 "register_operand" "r")))]
+  "TARGET_ZBS"
+  "bset\t%0,%1,%2"
+  [(set_attr "type" "bitmanip")])
+
+(define_insn "*bset<mode>_mask"
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(ior:X (ashift:X (const_int 1)
+			 (subreg:QI
+			  (and:X (match_operand:X 2 "register_operand" "r")
+				 (match_operand 3 "<X:shiftm1>" "i")) 0))
+	       (match_operand:X 1 "register_operand" "r")))]
+  "TARGET_ZBS"
+  "bset\t%0,%1,%2"
+  [(set_attr "type" "bitmanip")])
+
+(define_insn "*bset<mode>_1"
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(ashift:X (const_int 1)
+		  (match_operand:QI 1 "register_operand" "r")))]
+  "TARGET_ZBS"
+  "bset\t%0,x0,%1"
+  [(set_attr "type" "bitmanip")])
+
+(define_insn "*bset<mode>_1_mask"
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(ashift:X (const_int 1)
+		  (subreg:QI
+		   (and:X (match_operand:X 1 "register_operand" "r")
+			  (match_operand 2 "<X:shiftm1>" "i")) 0)))]
+  "TARGET_ZBS"
+  "bset\t%0,x0,%1"
+  [(set_attr "type" "bitmanip")])
+
+(define_insn "*bseti<mode>"
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(ior:X (match_operand:X 1 "register_operand" "r")
+	       (match_operand 2 "single_bit_mask_operand" "i")))]
+  "TARGET_ZBS"
+  "bseti\t%0,%1,%S2"
+  [(set_attr "type" "bitmanip")])
+
+(define_insn "*bclr<mode>"
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(and:X (rotate:X (const_int -2)
+			 (match_operand:QI 2 "register_operand" "r"))
+	       (match_operand:X 1 "register_operand" "r")))]
+  "TARGET_ZBS"
+  "bclr\t%0,%1,%2"
+  [(set_attr "type" "bitmanip")])
+
+(define_insn "*bclri<mode>"
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(and:X (match_operand:X 1 "register_operand" "r")
+	       (match_operand 2 "not_single_bit_mask_operand" "i")))]
+  "TARGET_ZBS"
+  "bclri\t%0,%1,%T2"
+  [(set_attr "type" "bitmanip")])
+
+(define_insn "*binv<mode>"
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(xor:X (ashift:X (const_int 1)
+			 (match_operand:QI 2 "register_operand" "r"))
+	       (match_operand:X 1 "register_operand" "r")))]
+  "TARGET_ZBS"
+  "binv\t%0,%1,%2"
+  [(set_attr "type" "bitmanip")])
+
+(define_insn "*binvi<mode>"
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(xor:X (match_operand:X 1 "register_operand" "r")
+	       (match_operand 2 "single_bit_mask_operand" "i")))]
+  "TARGET_ZBS"
+  "binvi\t%0,%1,%S2"
+  [(set_attr "type" "bitmanip")])
+
+(define_insn "*bext<mode>"
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(zero_extract:X (match_operand:X 1 "register_operand" "r")
+			(const_int 1)
+			(zero_extend:X
+			 (match_operand:QI 2 "register_operand" "r"))))]
+  "TARGET_ZBS"
+  "bext\t%0,%1,%2"
+  [(set_attr "type" "bitmanip")])
+
+(define_insn "*bexti"
+  [(set (match_operand:X 0 "register_operand" "=r")
+	(zero_extract:X (match_operand:X 1 "register_operand" "r")
+			(const_int 1)
+			(match_operand 2 "immediate_operand" "i")))]
+  "TARGET_ZBS"
+  "bexti\t%0,%1,%2"
+  [(set_attr "type" "bitmanip")])
diff --git a/gcc/config/riscv/predicates.md b/gcc/config/riscv/predicates.md
index 23211513554..3da6fd4c049 100644
--- a/gcc/config/riscv/predicates.md
+++ b/gcc/config/riscv/predicates.md
@@ -74,6 +74,11 @@
   if (GET_MODE_SIZE (mode) > UNITS_PER_WORD)
     return false;
 
+  /* Check whether the constant can be loaded in a single
+     instruction with zbs extensions.  */
+  if (TARGET_64BIT && TARGET_ZBS && SINGLE_BIT_MASK_OPERAND (INTVAL (op)))
+    return false;
+
   /* Otherwise check whether the constant can be loaded in a single
      instruction.  */
   return !LUI_OPERAND (INTVAL (op)) && !SMALL_OPERAND (INTVAL (op));
@@ -217,3 +222,20 @@
 {
   return riscv_gpr_save_operation_p (op);
 })
+
+;; Predicates for the ZBS extension.
+(define_predicate "single_bit_mask_operand"
+  (and (match_code "const_int")
+       (match_test "pow2p_hwi (INTVAL (op))")))
+
+(define_predicate "not_single_bit_mask_operand"
+  (and (match_code "const_int")
+       (match_test "pow2p_hwi (~INTVAL (op))")))
+
+(define_predicate "const31_operand"
+  (and (match_code "const_int")
+       (match_test "INTVAL (op) == 31")))
+
+(define_predicate "const63_operand"
+  (and (match_code "const_int")
+       (match_test "INTVAL (op) == 63")))
diff --git a/gcc/config/riscv/riscv.c b/gcc/config/riscv/riscv.c
index 66daebbbc8f..77981d8e818 100644
--- a/gcc/config/riscv/riscv.c
+++ b/gcc/config/riscv/riscv.c
@@ -409,6 +409,13 @@ riscv_build_integer_1 (struct riscv_integer_op codes[RISCV_MAX_INTEGER_OPS],
       codes[0].value = value;
       return 1;
     }
+  if (TARGET_ZBS && SINGLE_BIT_MASK_OPERAND (value))
+    {
+      /* Simply BSETI.  */
+      codes[0].code = UNKNOWN;
+      codes[0].value = value;
+      return 1;
+    }
 
   /* End with ADDI.  When constructing HImode constants, do not generate any
      intermediate value that is not itself a valid HImode constant.  The
@@ -2219,7 +2226,17 @@ riscv_output_move (rtx dest, rtx src)
 	  }
 
       if (src_code == CONST_INT)
-	return "li\t%0,%1";
+	{
+	  if (SMALL_OPERAND (INTVAL (src)) || LUI_OPERAND (INTVAL (src)))
+	    return "li\t%0,%1";
+
+	  if (TARGET_ZBS
+	      && SINGLE_BIT_MASK_OPERAND (INTVAL (src)))
+	    return "bseti\t%0,zero,%S1";
+
+	  /* Should never reach here.  */
+	  abort ();
+	}
 
       if (src_code == HIGH)
 	return "lui\t%0,%h1";
@@ -3560,7 +3577,9 @@ riscv_memmodel_needs_release_fence (enum memmodel model)
    'A'	Print the atomic operation suffix for memory model OP.
    'F'	Print a FENCE if the memory model requires a release.
    'z'	Print x0 if OP is zero, otherwise print OP normally.
-   'i'	Print i if the operand is not a register.  */
+   'i'	Print i if the operand is not a register.
+   'S'	Print shift-index of single-bit mask OP.
+   'T'	Print shift-index of inverted single-bit mask OP.  */
 
 static void
 riscv_print_operand (FILE *file, rtx op, int letter)
@@ -3600,6 +3619,18 @@ riscv_print_operand (FILE *file, rtx op, int letter)
         fputs ("i", file);
       break;
 
+    case 'S':
+      {
+	rtx newop = GEN_INT (ctz_hwi (INTVAL (op)));
+	output_addr_const (file, newop);
+	break;
+      }
+    case 'T':
+      {
+	rtx newop = GEN_INT (ctz_hwi (~INTVAL (op)));
+	output_addr_const (file, newop);
+	break;
+      }
     default:
       switch (code)
 	{
diff --git a/gcc/config/riscv/riscv.h b/gcc/config/riscv/riscv.h
index f47d5b40a66..64287124735 100644
--- a/gcc/config/riscv/riscv.h
+++ b/gcc/config/riscv/riscv.h
@@ -526,6 +526,14 @@ enum reg_class
   (((VALUE) | ((1UL<<31) - IMM_REACH)) == ((1UL<<31) - IMM_REACH)	\
    || ((VALUE) | ((1UL<<31) - IMM_REACH)) + IMM_REACH == 0)
 
+/* If this is a single bit mask, then we can load it with bseti.  But this
+   is not useful for any of the low 31 bits because we can use addi or lui
+   to load them.  It is wrong for loading SImode 0x80000000 on rv64 because it
+   needs to be sign-extended.  So we restrict this to the upper 32-bits
+   only.  */
+#define SINGLE_BIT_MASK_OPERAND(VALUE) \
+  (pow2p_hwi (VALUE) && (ctz_hwi (VALUE) >= 32))
+
 /* Stack layout; function entry, exit and calling.  */
 
 #define STACK_GROWS_DOWNWARD 1
diff --git a/gcc/testsuite/gcc.target/riscv/zba-slliuw.c b/gcc/testsuite/gcc.target/riscv/zba-slliuw.c
index 50399f68e08..a7a3dc77d53 100644
--- a/gcc/testsuite/gcc.target/riscv/zba-slliuw.c
+++ b/gcc/testsuite/gcc.target/riscv/zba-slliuw.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-march=rv64gc_zba -mabi=lp64 -O2" } */
+/* { dg-options "-march=rv64gc_zba_zbs -mabi=lp64 -O2" } */
 
 long
 foo (long i)
@@ -8,4 +8,4 @@ foo (long i)
 }
 /* XXX: This pattern need combine improvement or intermediate instruction
  *      from zbs.   */
-/* { dg-final { scan-assembler-not "slli.uw" } } */
+/* { dg-final { scan-assembler "slli.uw" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/zbs-bclr.c b/gcc/testsuite/gcc.target/riscv/zbs-bclr.c
new file mode 100644
index 00000000000..4a3c2f1cdaf
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/zbs-bclr.c
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc_zbs -mabi=lp64 -O2" } */
+
+/* bclr */
+long
+foo0 (long i, long j)
+{
+  return i & ~(1L << j);
+}
+
+/* bclri */
+long
+foo1 (long i)
+{
+  return i & ~(1L << 20);
+}
+
+/* { dg-final { scan-assembler-times "bclr\t" 1 } } */
+/* { dg-final { scan-assembler-times "bclri\t" 1 } } */
+/* { dg-final { scan-assembler-not "andi" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/zbs-bext.c b/gcc/testsuite/gcc.target/riscv/zbs-bext.c
new file mode 100644
index 00000000000..a093cdc8d1e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/zbs-bext.c
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc_zbs -mabi=lp64 -O2" } */
+
+/* bext */
+long
+foo0 (long i, long j)
+{
+  return 1L & (i >> j);
+}
+
+/* bexti */
+long
+foo1 (long i)
+{
+  return 1L & (i >> 20);
+}
+
+/* { dg-final { scan-assembler-times "bexti\t" 1 } } */
+/* { dg-final { scan-assembler-times "bext\t" 1 } } */
+/* { dg-final { scan-assembler-not "andi" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/zbs-binv.c b/gcc/testsuite/gcc.target/riscv/zbs-binv.c
new file mode 100644
index 00000000000..e4e48b9cdfd
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/zbs-binv.c
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc_zbs -mabi=lp64 -O2" } */
+
+/* binv */
+long
+foo0 (long i, long j)
+{
+  return i ^ (1L << j);
+}
+
+/* binvi */
+long
+foo1 (long i)
+{
+  return i ^ (1L << 20);
+}
+
+/* { dg-final { scan-assembler-times "binv\t" 1 } } */
+/* { dg-final { scan-assembler-times "binvi\t" 1 } } */
+/* { dg-final { scan-assembler-not "andi" } } */
diff --git a/gcc/testsuite/gcc.target/riscv/zbs-bset.c b/gcc/testsuite/gcc.target/riscv/zbs-bset.c
new file mode 100644
index 00000000000..733d4279d3a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/zbs-bset.c
@@ -0,0 +1,41 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc_zbs -mabi=lp64 -O2" } */
+
+/* bset */
+long
+sub0 (long i, long j)
+{
+  return i | (1L << j);
+}
+
+/* bset_mask */
+long
+sub1 (long i, long j)
+{
+  return i | (1L << (j & 0x3f));
+}
+
+/* bset_1 */
+long
+sub2 (long i)
+{
+  return 1L << i;
+}
+
+/* bset_1_mask */
+long
+sub3 (long i)
+{
+  return 1L << (i & 0x3f);
+}
+
+/* bseti */
+long
+sub4 (long i)
+{
+  return i | (1L << 20);
+}
+
+/* { dg-final { scan-assembler-times "bset\t" 4 } } */
+/* { dg-final { scan-assembler-times "bseti\t" 1 } } */
+/* { dg-final { scan-assembler-not "andi" } } */
