diff --git a/llvm/lib/Target/RISCV/RISCV.td b/llvm/lib/Target/RISCV/RISCV.td
--- a/llvm/lib/Target/RISCV/RISCV.td
+++ b/llvm/lib/Target/RISCV/RISCV.td
@@ -132,7 +132,9 @@
 // subextensions. They should be enabled if either has been specified.
 def HasStdExtZbbOrZbp
     : Predicate<"Subtarget->hasStdExtZbb() || Subtarget->hasStdExtZbp()">,
-                AssemblerPredicate<(any_of FeatureExtZbb, FeatureExtZbp)>;
+                AssemblerPredicate<(any_of FeatureExtZbb, FeatureExtZbp),
+                                   "'Zbb' (Base 'B' Instructions) or "
+                                   "'Zbp' (Permutation 'B' Instructions)">;
 
 def FeatureExtZbproposedc
     : SubtargetFeature<"experimental-zbproposedc", "HasStdExtZbproposedc", "true",
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoB.td b/llvm/lib/Target/RISCV/RISCVInstrInfoB.td
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoB.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoB.td
@@ -461,6 +461,22 @@
 let Predicates = [HasStdExtZbf, IsRV64] in
 def BFPW : ALUW_rr<0b0100100, 0b111, "bfpw">, Sched<[]>;
 
+let Predicates = [HasStdExtZbbOrZbp, IsRV32] in {
+let hasSideEffects = 0, mayLoad = 0, mayStore = 0 in
+def ZEXTH_RV32 : RVInstR<0b0000100, 0b100, OPC_OP, (outs GPR:$rd),
+                         (ins GPR:$rs1), "zext.h", "$rd, $rs1">, Sched<[]> {
+  let rs2 = 0b00000;
+}
+} // Predicates = [HasStdExtZbbOrZbp, IsRV64]
+
+let Predicates = [HasStdExtZbbOrZbp, IsRV64] in {
+let hasSideEffects = 0, mayLoad = 0, mayStore = 0 in
+def ZEXTH_RV64 : RVInstR<0b0000100, 0b100, OPC_OP_32, (outs GPR:$rd),
+                         (ins GPR:$rs1), "zext.h", "$rd, $rs1">, Sched<[]> {
+  let rs2 = 0b00000;
+}
+} // Predicates = [HasStdExtZbbOrZbp, IsRV64]
+
 //===----------------------------------------------------------------------===//
 // Future compressed instructions
 //===----------------------------------------------------------------------===//
@@ -496,14 +512,6 @@
 // Pseudo Instructions
 //===----------------------------------------------------------------------===//
 
-let Predicates = [HasStdExtZbp, IsRV32] in {
-def : InstAlias<"zext.h $rd, $rs", (PACK GPR:$rd, GPR:$rs, X0)>;
-} // Predicates = [HasStdExtZbb, IsRV32]
-
-let Predicates = [HasStdExtZbp, IsRV64] in {
-def : InstAlias<"zext.h $rd, $rs", (PACKW GPR:$rd, GPR:$rs, X0)>;
-} // Predicates = [HasStdExtZbb, IsRV64]
-
 let Predicates = [HasStdExtZba, IsRV64] in {
 def : InstAlias<"zext.w $rd, $rs", (ADDUW GPR:$rd, GPR:$rs, X0)>;
 }
@@ -783,10 +791,10 @@
               (and GPR:$rs1, 0x00FF)),
           (PACKH GPR:$rs1, GPR:$rs2)>;
 
-let Predicates = [HasStdExtZbp, IsRV32] in
-def : Pat<(and GPR:$rs, 0x0000FFFF), (PACK GPR:$rs, X0)>;
-let Predicates = [HasStdExtZbp, IsRV64] in {
-def : Pat<(and GPR:$rs, 0x000000000000FFFF), (PACKW GPR:$rs, X0)>;
+let Predicates = [HasStdExtZbbOrZbp, IsRV32] in
+def : Pat<(and GPR:$rs, 0x0000FFFF), (ZEXTH_RV32 GPR:$rs)>;
+let Predicates = [HasStdExtZbbOrZbp, IsRV64] in {
+def : Pat<(and GPR:$rs, 0x000000000000FFFF), (ZEXTH_RV64 GPR:$rs)>;
 }
 
 let Predicates = [HasStdExtZbp, IsRV32] in {
diff --git a/llvm/test/CodeGen/RISCV/rv32Zbb.ll b/llvm/test/CodeGen/RISCV/rv32Zbb.ll
--- a/llvm/test/CodeGen/RISCV/rv32Zbb.ll
+++ b/llvm/test/CodeGen/RISCV/rv32Zbb.ll
@@ -952,3 +952,48 @@
   %abs = tail call i64 @llvm.abs.i64(i64 %x, i1 true)
   ret i64 %abs
 }
+
+define i32 @zexth_i32(i32 %a) nounwind {
+; RV32I-LABEL: zexth_i32:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    lui a1, 16
+; RV32I-NEXT:    addi a1, a1, -1
+; RV32I-NEXT:    and a0, a0, a1
+; RV32I-NEXT:    ret
+;
+; RV32IB-LABEL: zexth_i32:
+; RV32IB:       # %bb.0:
+; RV32IB-NEXT:    zext.h a0, a0
+; RV32IB-NEXT:    ret
+;
+; RV32IBB-LABEL: zexth_i32:
+; RV32IBB:       # %bb.0:
+; RV32IBB-NEXT:    zext.h a0, a0
+; RV32IBB-NEXT:    ret
+  %and = and i32 %a, 65535
+  ret i32 %and
+}
+
+define i64 @zexth_i64(i64 %a) nounwind {
+; RV32I-LABEL: zexth_i64:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    lui a1, 16
+; RV32I-NEXT:    addi a1, a1, -1
+; RV32I-NEXT:    and a0, a0, a1
+; RV32I-NEXT:    mv a1, zero
+; RV32I-NEXT:    ret
+;
+; RV32IB-LABEL: zexth_i64:
+; RV32IB:       # %bb.0:
+; RV32IB-NEXT:    zext.h a0, a0
+; RV32IB-NEXT:    mv a1, zero
+; RV32IB-NEXT:    ret
+;
+; RV32IBB-LABEL: zexth_i64:
+; RV32IBB:       # %bb.0:
+; RV32IBB-NEXT:    zext.h a0, a0
+; RV32IBB-NEXT:    mv a1, zero
+; RV32IBB-NEXT:    ret
+  %and = and i64 %a, 65535
+  ret i64 %and
+}
diff --git a/llvm/test/CodeGen/RISCV/rv64Zbb.ll b/llvm/test/CodeGen/RISCV/rv64Zbb.ll
--- a/llvm/test/CodeGen/RISCV/rv64Zbb.ll
+++ b/llvm/test/CodeGen/RISCV/rv64Zbb.ll
@@ -777,3 +777,45 @@
   %abs = tail call i64 @llvm.abs.i64(i64 %x, i1 true)
   ret i64 %abs
 }
+
+define i32 @zexth_i32(i32 %a) nounwind {
+; RV64I-LABEL: zexth_i32:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    lui a1, 16
+; RV64I-NEXT:    addiw a1, a1, -1
+; RV64I-NEXT:    and a0, a0, a1
+; RV64I-NEXT:    ret
+;
+; RV64IB-LABEL: zexth_i32:
+; RV64IB:       # %bb.0:
+; RV64IB-NEXT:    zext.h a0, a0
+; RV64IB-NEXT:    ret
+;
+; RV64IBB-LABEL: zexth_i32:
+; RV64IBB:       # %bb.0:
+; RV64IBB-NEXT:    zext.h a0, a0
+; RV64IBB-NEXT:    ret
+  %and = and i32 %a, 65535
+  ret i32 %and
+}
+
+define i64 @zexth_i64(i64 %a) nounwind {
+; RV64I-LABEL: zexth_i64:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    lui a1, 16
+; RV64I-NEXT:    addiw a1, a1, -1
+; RV64I-NEXT:    and a0, a0, a1
+; RV64I-NEXT:    ret
+;
+; RV64IB-LABEL: zexth_i64:
+; RV64IB:       # %bb.0:
+; RV64IB-NEXT:    zext.h a0, a0
+; RV64IB-NEXT:    ret
+;
+; RV64IBB-LABEL: zexth_i64:
+; RV64IBB:       # %bb.0:
+; RV64IBB-NEXT:    zext.h a0, a0
+; RV64IBB-NEXT:    ret
+  %and = and i64 %a, 65535
+  ret i64 %and
+}
diff --git a/llvm/test/MC/RISCV/rv32b-aliases-valid.s b/llvm/test/MC/RISCV/rv32b-aliases-valid.s
--- a/llvm/test/MC/RISCV/rv32b-aliases-valid.s
+++ b/llvm/test/MC/RISCV/rv32b-aliases-valid.s
@@ -19,7 +19,7 @@
 # CHECK-S-OBJ: andi t0, t1, 255
 zext.b x5, x6
 
-# CHECK-S-OBJ-NOALIAS: pack t0, t1, zero
+# CHECK-S-OBJ-NOALIAS: zext.h t0, t1
 # CHECK-S-OBJ: zext.h t0, t1
 zext.h x5, x6
 
diff --git a/llvm/test/MC/RISCV/rv32zbb-valid.s b/llvm/test/MC/RISCV/rv32zbb-valid.s
--- a/llvm/test/MC/RISCV/rv32zbb-valid.s
+++ b/llvm/test/MC/RISCV/rv32zbb-valid.s
@@ -6,10 +6,10 @@
 # RUN:     | FileCheck --check-prefix=CHECK-ASM-AND-OBJ %s
 
 # With Bitmanip base extension:
-# RUN: llvm-mc %s -triple=riscv32 -mattr=+experimental-zbb -show-encoding \
+# RUN: llvm-mc %s -triple=riscv32 -mattr=+experimental-zbb -riscv-no-aliases -show-encoding \
 # RUN:     | FileCheck -check-prefixes=CHECK-ASM,CHECK-ASM-AND-OBJ %s
 # RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+experimental-zbb < %s \
-# RUN:     | llvm-objdump --mattr=+experimental-zbb -d -r - \
+# RUN:     | llvm-objdump --mattr=+experimental-zbb -M no-aliases -d -r - \
 # RUN:     | FileCheck --check-prefix=CHECK-ASM-AND-OBJ %s
 
 # CHECK-ASM-AND-OBJ: clz t0, t1
@@ -39,3 +39,6 @@
 # CHECK-ASM-AND-OBJ: maxu t0, t1, t2
 # CHECK-ASM: encoding: [0xb3,0x72,0x73,0x0a]
 maxu t0, t1, t2
+# CHECK-ASM-AND-OBJ: zext.h t0, t1
+# CHECK-ASM: encoding: [0xb3,0x42,0x03,0x08]
+zext.h t0, t1
diff --git a/llvm/test/MC/RISCV/rv32zbp-valid.s b/llvm/test/MC/RISCV/rv32zbp-valid.s
--- a/llvm/test/MC/RISCV/rv32zbp-valid.s
+++ b/llvm/test/MC/RISCV/rv32zbp-valid.s
@@ -3,14 +3,14 @@
 # RUN:     | FileCheck -check-prefixes=CHECK-ASM,CHECK-ASM-AND-OBJ %s
 # RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+experimental-b < %s \
 # RUN:     | llvm-objdump --mattr=+experimental-b -d -r - \
-# RUN:     | FileCheck --check-prefix=CHECK-ASM-AND-OBJ %s
+# RUN:     | FileCheck --check-prefixes=CHECK-OBJ,CHECK-ASM-AND-OBJ %s
 
 # With Bitmanip permutation extension:
 # RUN: llvm-mc %s -triple=riscv32 -mattr=+experimental-zbp -show-encoding \
 # RUN:     | FileCheck -check-prefixes=CHECK-ASM,CHECK-ASM-AND-OBJ %s
 # RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+experimental-zbp < %s \
 # RUN:     | llvm-objdump --mattr=+experimental-zbp -d -r - \
-# RUN:     | FileCheck --check-prefix=CHECK-ASM-AND-OBJ %s
+# RUN:     | FileCheck --check-prefixes=CHECK-OBJ,CHECK-ASM-AND-OBJ %s
 
 # CHECK-ASM-AND-OBJ: slo t0, t1, t2
 # CHECK-ASM: encoding: [0xb3,0x12,0x73,0x20]
@@ -57,3 +57,10 @@
 # CHECK-ASM-AND-OBJ: packh t0, t1, t2
 # CHECK-ASM: encoding: [0xb3,0x72,0x73,0x08]
 packh t0, t1, t2
+# CHECK-ASM-AND-OBJ: zext.h t0, t1
+# CHECK-ASM: encoding: [0xb3,0x42,0x03,0x08]
+zext.h t0, t1
+# CHECK-ASM: pack t0, t1, zero
+# CHECK-OBJ: zext.h t0, t1
+# CHECK-ASM: encoding: [0xb3,0x42,0x03,0x08]
+pack t0, t1, x0
diff --git a/llvm/test/MC/RISCV/rv64b-aliases-valid.s b/llvm/test/MC/RISCV/rv64b-aliases-valid.s
--- a/llvm/test/MC/RISCV/rv64b-aliases-valid.s
+++ b/llvm/test/MC/RISCV/rv64b-aliases-valid.s
@@ -19,7 +19,7 @@
 # CHECK-S-OBJ: andi t0, t1, 255
 zext.b x5, x6
 
-# CHECK-S-OBJ-NOALIAS: packw t0, t1, zero
+# CHECK-S-OBJ-NOALIAS: zext.h t0, t1
 # CHECK-S-OBJ: zext.h t0, t1
 zext.h x5, x6
 
diff --git a/llvm/test/MC/RISCV/rv64zbb-valid.s b/llvm/test/MC/RISCV/rv64zbb-valid.s
--- a/llvm/test/MC/RISCV/rv64zbb-valid.s
+++ b/llvm/test/MC/RISCV/rv64zbb-valid.s
@@ -6,10 +6,10 @@
 # RUN:     | FileCheck --check-prefix=CHECK-ASM-AND-OBJ %s
 
 # With Bitmanip base extension:
-# RUN: llvm-mc %s -triple=riscv64 -mattr=+experimental-zbb -show-encoding \
+# RUN: llvm-mc %s -triple=riscv64 -mattr=+experimental-zbb -riscv-no-aliases -show-encoding \
 # RUN:     | FileCheck -check-prefixes=CHECK-ASM,CHECK-ASM-AND-OBJ %s
 # RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+experimental-zbb < %s \
-# RUN:     | llvm-objdump --mattr=+experimental-zbb -d -r - \
+# RUN:     | llvm-objdump --mattr=+experimental-zbb -M no-aliases -d -r - \
 # RUN:     | FileCheck --check-prefix=CHECK-ASM-AND-OBJ %s
 
 # CHECK-ASM-AND-OBJ: clzw t0, t1
@@ -21,3 +21,6 @@
 # CHECK-ASM-AND-OBJ: cpopw t0, t1
 # CHECK-ASM: encoding: [0x9b,0x12,0x23,0x60]
 cpopw t0, t1
+# CHECK-ASM-AND-OBJ: zext.h t0, t1
+# CHECK-ASM: encoding: [0xbb,0x42,0x03,0x08]
+zext.h t0, t1
diff --git a/llvm/test/MC/RISCV/rv64zbp-valid.s b/llvm/test/MC/RISCV/rv64zbp-valid.s
--- a/llvm/test/MC/RISCV/rv64zbp-valid.s
+++ b/llvm/test/MC/RISCV/rv64zbp-valid.s
@@ -3,14 +3,14 @@
 # RUN:     | FileCheck -check-prefixes=CHECK-ASM,CHECK-ASM-AND-OBJ %s
 # RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+experimental-b < %s \
 # RUN:     | llvm-objdump --mattr=+experimental-b -d -r - \
-# RUN:     | FileCheck --check-prefix=CHECK-ASM-AND-OBJ %s
+# RUN:     | FileCheck --check-prefixes=CHECK-OBJ,CHECK-ASM-AND-OBJ %s
 
 # With Bitmanip permutation extension:
 # RUN: llvm-mc %s -triple=riscv64 -mattr=+experimental-zbp -show-encoding \
 # RUN:     | FileCheck -check-prefixes=CHECK-ASM,CHECK-ASM-AND-OBJ %s
 # RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+experimental-zbp < %s \
 # RUN:     | llvm-objdump --mattr=+experimental-zbp -d -r - \
-# RUN:     | FileCheck --check-prefix=CHECK-ASM-AND-OBJ %s
+# RUN:     | FileCheck --check-prefixes=CHECK-OBJ,CHECK-ASM-AND-OBJ %s
 
 # CHECK-ASM-AND-OBJ: slow t0, t1, t2
 # CHECK-ASM: encoding: [0xbb,0x12,0x73,0x20]
@@ -48,3 +48,10 @@
 # CHECK-ASM-AND-OBJ: packuw t0, t1, t2
 # CHECK-ASM: encoding: [0xbb,0x42,0x73,0x48]
 packuw t0, t1, t2
+# CHECK-ASM-AND-OBJ: zext.h t0, t1
+# CHECK-ASM: encoding: [0xbb,0x42,0x03,0x08]
+zext.h t0, t1
+# CHECK-ASM: packw t0, t1, zero
+# CHECK-OBJ: zext.h t0, t1
+# CHECK-ASM: encoding: [0xbb,0x42,0x03,0x08]
+packw t0, t1, x0