diff --git a/gcc/config/aarch64/aarch64.md b/gcc/config/aarch64/aarch64.md
index 5a1894063a1e..6bc73b5f8e2a 100644
--- a/gcc/config/aarch64/aarch64.md
+++ b/gcc/config/aarch64/aarch64.md
@@ -5491,6 +5491,40 @@
   [(set_attr "type" "bfm")]
 )
 
+;; Bitfield extract and zero extend (extzv)
+(define_expand "extzv<mode>"
+  [(set (match_operand:GPI 0 "register_operand")
+  (zero_extract:GPI (match_operand:GPI 1 "register_operand")
+                         (match_operand 2 "const_int_operand")
+                         (match_operand 3 "const_int_operand")))]
+  ""
+{
+  unsigned HOST_WIDE_INT width = UINTVAL (operands[2]);
+  unsigned HOST_WIDE_INT pos = UINTVAL (operands[3]);
+  rtx value = operands[1];
+
+  if (width == 0 || (pos + width) > GET_MODE_BITSIZE (<MODE>mode))
+    FAIL;
+  operands[1] = force_reg (<MODE>mode, value);
+})
+
+;; Bitfield extract and sign extend (extv)
+(define_expand "extv<mode>"
+  [(set (match_operand:GPI 0 "register_operand")
+  (sign_extract:GPI (match_operand:GPI 1 "register_operand")
+                         (match_operand 2 "const_int_operand")
+                         (match_operand 3 "const_int_operand")))]
+  ""
+{
+  unsigned HOST_WIDE_INT width = UINTVAL (operands[2]);
+  unsigned HOST_WIDE_INT pos = UINTVAL (operands[3]);
+  rtx value = operands[1];
+
+  if (width == 0 || (pos + width) > GET_MODE_BITSIZE (<MODE>mode))
+    FAIL;
+  operands[1] = force_reg (<MODE>mode, value);
+})
+
 ;;  Match a bfi instruction where the shift of OP3 means that we are
 ;;  actually copying the least significant bits of OP3 into OP0 by way
 ;;  of the AND masks and the IOR instruction.  A similar instruction
diff --git a/gcc/expmed.c b/gcc/expmed.c
index d7f8e9a5d767..926f4e8f1eaf 100644
--- a/gcc/expmed.c
+++ b/gcc/expmed.c
@@ -1546,7 +1546,7 @@ extract_bit_field_using_extv (const extraction_insn *extv, rtx op0,
       if (REG_P (target)
 	  && TRULY_NOOP_TRUNCATION_MODES_P (GET_MODE (target), ext_mode))
 	{
-	  target = gen_lowpart (ext_mode, target);
+	  target = gen_lowpart_if_possible (ext_mode, target);
 	  if (partial_subreg_p (GET_MODE (spec_target), ext_mode))
 	    spec_target_subreg = target;
 	}
diff --git a/gcc/testsuite/gcc.target/aarch64/pr86901.c b/gcc/testsuite/gcc.target/aarch64/pr86901.c
new file mode 100644
index 000000000000..5dd6fdf75f44
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/pr86901.c
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+typedef unsigned int uint32_t;
+
+float g (float);
+
+static inline uint32_t
+top12 (float x)
+{
+  union
+  {
+    float f;
+    uint32_t i;
+  } u = {x};
+  return (u.i >> 20) & 0x7ff;
+}
+
+void
+f2 (float y, float *p)
+{
+  if (__builtin_expect (top12 (y) < top12 (1.0), 1))
+    *p = y * y;
+  else
+    g (y);
+}
+
+/* { dg-final { scan-assembler "fmov\tw" } } */
