diff --git a/llvm/docs/RISCVUsage.rst b/llvm/docs/RISCVUsage.rst
--- a/llvm/docs/RISCVUsage.rst
+++ b/llvm/docs/RISCVUsage.rst
@@ -172,6 +172,9 @@
 ``XTHeadBa``
   LLVM implements `the THeadBa (address-generation) vendor-defined instructions specified in <https://github.com/T-head-Semi/thead-extension-spec/releases/download/2.2.2/xthead-2023-01-30-2.2.2.pdf>`_  by T-HEAD of Alibaba.  Instructions are prefixed with `th.` as described in the specification.
 
+``XTHeadBs``
+  LLVM implements `the THeadBs (single-bit operations) vendor-defined instructions specified in <https://github.com/T-head-Semi/thead-extension-spec/releases/download/2.2.2/xthead-2023-01-30-2.2.2.pdf>`_  by T-HEAD of Alibaba.  Instructions are prefixed with `th.` as described in the specification.
+
 ``XTHeadVdot``
   LLVM implements `version 1.0.0 of the THeadV-family custom instructions specification <https://github.com/T-head-Semi/thead-extension-spec/releases/download/2.2.0/xthead-2022-12-04-2.2.0.pdf>`_ by T-HEAD of Alibaba.  All instructions are prefixed with `th.` as described in the specification, and the riscv-toolchain-convention document linked above.
 
diff --git a/llvm/docs/ReleaseNotes.rst b/llvm/docs/ReleaseNotes.rst
--- a/llvm/docs/ReleaseNotes.rst
+++ b/llvm/docs/ReleaseNotes.rst
@@ -103,6 +103,7 @@
 * Assembler support for version 1.0.1 of the Zcb extension was added.
 * Zca, Zcf, and Zcd extensions were upgraded to version 1.0.1.
 * Adds support for the vendor-defined XTHeadBa (address-generation) extension.
+* Adds support for the vendor-defined XTHeadBs (single-bit) extension.
 
 Changes to the WebAssembly Backend
 ----------------------------------
diff --git a/llvm/lib/Support/RISCVISAInfo.cpp b/llvm/lib/Support/RISCVISAInfo.cpp
--- a/llvm/lib/Support/RISCVISAInfo.cpp
+++ b/llvm/lib/Support/RISCVISAInfo.cpp
@@ -110,6 +110,7 @@
 
     // vendor-defined ('X') extensions
     {"xtheadba", RISCVExtensionVersion{1, 0}},
+    {"xtheadbs", RISCVExtensionVersion{1, 0}},
     {"xtheadvdot", RISCVExtensionVersion{1, 0}},
     {"xventanacondops", RISCVExtensionVersion{1, 0}},
 };
diff --git a/llvm/lib/Target/RISCV/RISCVFeatures.td b/llvm/lib/Target/RISCV/RISCVFeatures.td
--- a/llvm/lib/Target/RISCV/RISCVFeatures.td
+++ b/llvm/lib/Target/RISCV/RISCVFeatures.td
@@ -470,6 +470,13 @@
                                   AssemblerPredicate<(all_of FeatureVendorXTHeadBa),
                                   "'xtheadba' (T-Head address calculation instructions)">;
 
+def FeatureVendorXTHeadBs
+    : SubtargetFeature<"xtheadbs", "HasVendorXTHeadBs", "true",
+                       "'xtheadbs' (T-Head single-bit instructions)">;
+def HasVendorXTHeadBs : Predicate<"Subtarget->hasVendorXTHeadBs()">,
+                                  AssemblerPredicate<(all_of FeatureVendorXTHeadBs),
+                                  "'xtheadbs' (T-Head single-bit instructions)">;
+
 def FeatureVendorXTHeadVdot
     : SubtargetFeature<"xtheadvdot", "HasVendorXTHeadVdot", "true",
                        "'xtheadvdot' (T-Head Vector Extensions for Dot)",
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -797,13 +797,15 @@
       break;
 
     // If C2 is (1 << ShAmt) use bexti if possible.
-    if (Subtarget->hasStdExtZbs() && ShAmt + 1 == TrailingOnes) {
-      SDNode *BEXTI =
-          CurDAG->getMachineNode(RISCV::BEXTI, DL, VT, N0->getOperand(0),
-                                 CurDAG->getTargetConstant(ShAmt, DL, VT));
+    if ((Subtarget->hasStdExtZbs() || Subtarget->hasVendorXTHeadBs()) &&
+        ShAmt + 1 == TrailingOnes) {
+      SDNode *BEXTI = CurDAG->getMachineNode(
+          Subtarget->hasStdExtZbs() ? RISCV::BEXTI : RISCV::TH_TST, DL, VT,
+          N0->getOperand(0), CurDAG->getTargetConstant(ShAmt, DL, VT));
       ReplaceNode(Node, BEXTI);
       return;
     }
+
     unsigned LShAmt = Subtarget->getXLen() - TrailingOnes;
     SDNode *SLLI =
         CurDAG->getMachineNode(RISCV::SLLI, DL, VT, N0->getOperand(0),
@@ -927,6 +929,7 @@
                       cast<VTSDNode>(X.getOperand(1))->getVT() == MVT::i32;
           // Also Skip if we can use bexti.
           Skip |= Subtarget->hasStdExtZbs() && Leading == XLen - 1;
+          Skip |= Subtarget->hasVendorXTHeadBs() && Leading == XLen - 1;
           if (OneUseOrZExtW && !Skip) {
             SDNode *SLLI = CurDAG->getMachineNode(
                 RISCV::SLLI, DL, VT, X,
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXTHead.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXTHead.td
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXTHead.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXTHead.td
@@ -75,6 +75,12 @@
                Sched<[WriteSHXADD, ReadSHXADD, ReadSHXADD]>;
 } // Predicates = [HasVendorXTHeadBa]
 
+let Predicates = [HasVendorXTHeadBs] in {
+let IsSignExtendingOpW = 1 in
+def TH_TST : RVBShift_ri<0b10001, 0b001, OPC_CUSTOM_0, "th.tst">,
+             Sched<[WriteSingleBitImm, ReadSingleBitImm]>;
+} // Predicates = [HasVendorXTHeadBs]
+
 let Predicates = [HasVendorXTHeadVdot],
     Constraints = "@earlyclobber $vd",
     RVVConstraint = WidenV in {
@@ -224,6 +230,14 @@
 		       (TH_ADDSL GPR:$r, GPR:$r, 2), 2), 3)>;
 } // Predicates = [HasVendorXTHeadBa]
 
+let Predicates = [HasVendorXTHeadBs] in {
+def : Pat<(and (srl GPR:$rs1, uimmlog2xlen:$shamt), 1),
+          (TH_TST GPR:$rs1, uimmlog2xlen:$shamt)>;
+def : Pat<(seteq (and GPR:$rs1, SingleBitSetMask:$mask), 0),
+          (TH_TST (XORI GPR:$rs1, -1), SingleBitSetMask:$mask)>;
+} // Predicates = [HasVendorXTHeadBs]
+
+
 defm PseudoTHVdotVMAQA      : VPseudoVMAQA_VV_VX;
 defm PseudoTHVdotVMAQAU     : VPseudoVMAQA_VV_VX;
 defm PseudoTHVdotVMAQASU    : VPseudoVMAQA_VV_VX;
diff --git a/llvm/test/CodeGen/RISCV/attributes.ll b/llvm/test/CodeGen/RISCV/attributes.ll
--- a/llvm/test/CodeGen/RISCV/attributes.ll
+++ b/llvm/test/CodeGen/RISCV/attributes.ll
@@ -87,6 +87,7 @@
 ; RUN: llc -mtriple=riscv64 -mattr=+svinval %s -o - | FileCheck --check-prefix=RV64SVINVAL %s
 ; RUN: llc -mtriple=riscv64 -mattr=+xventanacondops %s -o - | FileCheck --check-prefix=RV64XVENTANACONDOPS %s
 ; RUN: llc -mtriple=riscv64 -mattr=+xtheadba %s -o - | FileCheck --check-prefix=RV64XTHEADBA %s
+; RUN: llc -mtriple=riscv64 -mattr=+xtheadbs %s -o - | FileCheck --check-prefix=RV64XTHEADBS %s
 ; RUN: llc -mtriple=riscv64 -mattr=+xtheadvdot %s -o - | FileCheck --check-prefix=RV64XTHEADVDOT %s
 ; RUN: llc -mtriple=riscv64 -mattr=+experimental-zawrs %s -o - | FileCheck --check-prefix=RV64ZAWRS %s
 ; RUN: llc -mtriple=riscv64 -mattr=+experimental-ztso %s -o - | FileCheck --check-prefix=RV64ZTSO %s
@@ -182,6 +183,7 @@
 ; RV64SVINVAL: .attribute 5, "rv64i2p0_svinval1p0"
 ; RV64XVENTANACONDOPS: .attribute 5, "rv64i2p0_xventanacondops1p0"
 ; RV64XTHEADBA: .attribute 5, "rv64i2p0_xtheadba1p0"
+; RV64XTHEADBS: .attribute 5, "rv64i2p0_xtheadbs1p0"
 ; RV64XTHEADVDOT: .attribute 5, "rv64i2p0_f2p0_d2p0_v1p0_zve32f1p0_zve32x1p0_zve64d1p0_zve64f1p0_zve64x1p0_zvl128b1p0_zvl32b1p0_zvl64b1p0_xtheadvdot1p0"
 ; RV64ZTSO: .attribute 5, "rv64i2p0_ztso0p1"
 ; RV64ZCA: .attribute 5, "rv64i2p0_zca1p0"
diff --git a/llvm/test/CodeGen/RISCV/rv32xtheadbs.ll b/llvm/test/CodeGen/RISCV/rv32xtheadbs.ll
new file mode 100644
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/rv32xtheadbs.ll
@@ -0,0 +1,76 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv32 -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s -check-prefixes=CHECK,RV32I
+; RUN: llc -mtriple=riscv32 -mattr=+xtheadbs -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s -check-prefixes=CHECK,RV32XTHEADBS
+
+define i32 @bexti_i32(i32 %a) nounwind {
+; RV32I-LABEL: bexti_i32:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    slli a0, a0, 26
+; RV32I-NEXT:    srli a0, a0, 31
+; RV32I-NEXT:    ret
+;
+; RV32XTHEADBS-LABEL: bexti_i32:
+; RV32XTHEADBS:       # %bb.0:
+; RV32XTHEADBS-NEXT:    th.tst a0, a0, 5
+; RV32XTHEADBS-NEXT:    ret
+  %shr = lshr i32 %a, 5
+  %and = and i32 %shr, 1
+  ret i32 %and
+}
+
+define i64 @bexti_i64(i64 %a) nounwind {
+; RV32I-LABEL: bexti_i64:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    slli a0, a0, 26
+; RV32I-NEXT:    srli a0, a0, 31
+; RV32I-NEXT:    li a1, 0
+; RV32I-NEXT:    ret
+;
+; RV32XTHEADBS-LABEL: bexti_i64:
+; RV32XTHEADBS:       # %bb.0:
+; RV32XTHEADBS-NEXT:    th.tst a0, a0, 5
+; RV32XTHEADBS-NEXT:    li a1, 0
+; RV32XTHEADBS-NEXT:    ret
+  %shr = lshr i64 %a, 5
+  %and = and i64 %shr, 1
+  ret i64 %and
+}
+
+define signext i32 @bexti_i32_cmp(i32 signext %a) nounwind {
+; RV32I-LABEL: bexti_i32_cmp:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    slli a0, a0, 26
+; RV32I-NEXT:    srli a0, a0, 31
+; RV32I-NEXT:    ret
+;
+; RV32XTHEADBS-LABEL: bexti_i32_cmp:
+; RV32XTHEADBS:       # %bb.0:
+; RV32XTHEADBS-NEXT:    th.tst a0, a0, 5
+; RV32XTHEADBS-NEXT:    ret
+  %and = and i32 %a, 32
+  %cmp = icmp ne i32 %and, 0
+  %zext = zext i1 %cmp to i32
+  ret i32 %zext
+}
+
+define i64 @bexti_i64_cmp(i64 %a) nounwind {
+; RV32I-LABEL: bexti_i64_cmp:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    slli a0, a0, 26
+; RV32I-NEXT:    srli a0, a0, 31
+; RV32I-NEXT:    li a1, 0
+; RV32I-NEXT:    ret
+;
+; RV32XTHEADBS-LABEL: bexti_i64_cmp:
+; RV32XTHEADBS:       # %bb.0:
+; RV32XTHEADBS-NEXT:    th.tst a0, a0, 5
+; RV32XTHEADBS-NEXT:    li a1, 0
+; RV32XTHEADBS-NEXT:    ret
+  %and = and i64 %a, 32
+  %cmp = icmp ne i64 %and, 0
+  %zext = zext i1 %cmp to i64
+  ret i64 %zext
+}
+
diff --git a/llvm/test/CodeGen/RISCV/rv64xtheadbs.ll b/llvm/test/CodeGen/RISCV/rv64xtheadbs.ll
new file mode 100644
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/rv64xtheadbs.ll
@@ -0,0 +1,72 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv64 -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s -check-prefixes=CHECK,RV64I
+; RUN: llc -mtriple=riscv64 -mattr=+xtheadbs -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s -check-prefixes=CHECK,RV64XTHEADBS
+
+define signext i32 @bexti_i32(i32 signext %a) nounwind {
+; RV64I-LABEL: bexti_i32:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    slli a0, a0, 58
+; RV64I-NEXT:    srli a0, a0, 63
+; RV64I-NEXT:    ret
+;
+; RV64XTHEADBS-LABEL: bexti_i32:
+; RV64XTHEADBS:       # %bb.0:
+; RV64XTHEADBS-NEXT:    th.tst a0, a0, 5
+; RV64XTHEADBS-NEXT:    ret
+  %shr = lshr i32 %a, 5
+  %and = and i32 %shr, 1
+  ret i32 %and
+}
+
+define i64 @bexti_i64(i64 %a) nounwind {
+; RV64I-LABEL: bexti_i64:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    slli a0, a0, 58
+; RV64I-NEXT:    srli a0, a0, 63
+; RV64I-NEXT:    ret
+;
+; RV64XTHEADBS-LABEL: bexti_i64:
+; RV64XTHEADBS:       # %bb.0:
+; RV64XTHEADBS-NEXT:    th.tst a0, a0, 5
+; RV64XTHEADBS-NEXT:    ret
+  %shr = lshr i64 %a, 5
+  %and = and i64 %shr, 1
+  ret i64 %and
+}
+
+define signext i32 @bexti_i32_cmp(i32 signext %a) nounwind {
+; RV64I-LABEL: bexti_i32_cmp:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    slli a0, a0, 58
+; RV64I-NEXT:    srli a0, a0, 63
+; RV64I-NEXT:    ret
+;
+; RV64XTHEADBS-LABEL: bexti_i32_cmp:
+; RV64XTHEADBS:       # %bb.0:
+; RV64XTHEADBS-NEXT:    th.tst a0, a0, 5
+; RV64XTHEADBS-NEXT:    ret
+  %and = and i32 %a, 32
+  %cmp = icmp ne i32 %and, 0
+  %zext = zext i1 %cmp to i32
+  ret i32 %zext
+}
+
+define i64 @bexti_i64_cmp(i64 %a) nounwind {
+; RV64I-LABEL: bexti_i64_cmp:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    slli a0, a0, 58
+; RV64I-NEXT:    srli a0, a0, 63
+; RV64I-NEXT:    ret
+;
+; RV64XTHEADBS-LABEL: bexti_i64_cmp:
+; RV64XTHEADBS:       # %bb.0:
+; RV64XTHEADBS-NEXT:    th.tst a0, a0, 5
+; RV64XTHEADBS-NEXT:    ret
+  %and = and i64 %a, 32
+  %cmp = icmp ne i64 %and, 0
+  %zext = zext i1 %cmp to i64
+  ret i64 %zext
+}
+
diff --git a/llvm/test/MC/RISCV/rv32xtheadbs-invalid.s b/llvm/test/MC/RISCV/rv32xtheadbs-invalid.s
new file mode 100644
--- /dev/null
+++ b/llvm/test/MC/RISCV/rv32xtheadbs-invalid.s
@@ -0,0 +1,7 @@
+# RUN: not llvm-mc -triple riscv32 -mattr=+xtheadbs < %s 2>&1 | FileCheck %s
+
+# Too few operands
+th.tst t0, t1 # CHECK: :[[@LINE]]:1: error: too few operands for instruction
+# Immediate operand out of range
+th.tst t0, t1, 32 # CHECK: :[[@LINE]]:16: error: immediate must be an integer in the range [0, 31]
+th.tst t0, t1, -1 # CHECK: :[[@LINE]]:16: error: immediate must be an integer in the range [0, 31]
diff --git a/llvm/test/MC/RISCV/rv32xtheadbs-valid.s b/llvm/test/MC/RISCV/rv32xtheadbs-valid.s
new file mode 100644
--- /dev/null
+++ b/llvm/test/MC/RISCV/rv32xtheadbs-valid.s
@@ -0,0 +1,17 @@
+# RUN: llvm-mc %s -triple=riscv32 -mattr=+xtheadbs -show-encoding \
+# RUN:     | FileCheck -check-prefixes=CHECK-ASM,CHECK-ASM-AND-OBJ %s
+# RUN: llvm-mc %s -triple=riscv64 -mattr=+xtheadbs -show-encoding \
+# RUN:     | FileCheck -check-prefixes=CHECK-ASM,CHECK-ASM-AND-OBJ %s
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+xtheadbs < %s \
+# RUN:     | llvm-objdump --mattr=+xtheadbs -d -r - \
+# RUN:     | FileCheck --check-prefix=CHECK-ASM-AND-OBJ %s
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+xtheadbs < %s \
+# RUN:     | llvm-objdump --mattr=+xtheadbs -d -r - \
+# RUN:     | FileCheck --check-prefix=CHECK-ASM-AND-OBJ %s
+
+# CHECK-ASM-AND-OBJ: th.tst t0, t1, 1
+# CHECK-ASM: encoding: [0x8b,0x12,0x13,0x88]
+th.tst t0, t1, 1
+# CHECK-ASM-AND-OBJ: th.tst t0, t1, 31
+# CHECK-ASM: encoding: [0x8b,0x12,0xf3,0x89]
+th.tst t0, t1, 31
diff --git a/llvm/test/MC/RISCV/rv64xtheadbs-invalid.s b/llvm/test/MC/RISCV/rv64xtheadbs-invalid.s
new file mode 100644
--- /dev/null
+++ b/llvm/test/MC/RISCV/rv64xtheadbs-invalid.s
@@ -0,0 +1,5 @@
+# RUN: not llvm-mc -triple riscv64 -mattr=+xtheadbs < %s 2>&1 | FileCheck %s
+
+# Immediate operand out of range
+th.tst t0, t1, 64 # CHECK: :[[@LINE]]:16: error: immediate must be an integer in the range [0, 63]
+th.tst t0, t1, -1 # CHECK: :[[@LINE]]:16: error: immediate must be an integer in the range [0, 63]