diff --git a/bfd/elfnn-aarch64.c b/bfd/elfnn-aarch64.c
index 000564672df..0c48e20f013 100644
--- a/bfd/elfnn-aarch64.c
+++ b/bfd/elfnn-aarch64.c
@@ -431,7 +431,7 @@ elfNN_aarch64_tlsdesc_small_plt_bti_entry[PLT_TLSDESC_ENTRY_SIZE] =
 
 /* Indexed by the bfd interal reloc enumerators.
    Therefore, the table needs to be synced with BFD_RELOC_AARCH64_*
-   in reloc.c.   */
+   in bfd/reloc.c, and with a RELOC_NUMBER in include/elf/aarch64.h */
 
 static reloc_howto_type elfNN_aarch64_howto_table[] =
 {
@@ -822,6 +822,8 @@ static reloc_howto_type elfNN_aarch64_howto_table[] =
 	 0xffff,		/* dst_mask */
 	 true),		/* pcrel_offset */
 
+  EMPTY_HOWTO (0), // BFD_RELOC_AARCH64_AUTI_LO16_PCREL
+
 /* Relocations to generate 19, 21 and 33 bit PC-relative load/store
    addresses: PG(x) is (x & ~0xfff).  */
 
@@ -8262,7 +8264,7 @@ elfNN_aarch64_maybe_function_sym (const asymbol *sym, asection *sec,
     return 0;
 
   size = (sym->flags & BSF_SYNTHETIC) ? 0 : elf_sym->internal_elf_sym.st_size;
-  
+
   if (!(sym->flags & BSF_SYNTHETIC))
     switch (ELF_ST_TYPE (elf_sym->internal_elf_sym.st_info))
       {
@@ -8280,7 +8282,7 @@ elfNN_aarch64_maybe_function_sym (const asymbol *sym, asection *sec,
 	default:
 	  return 0;
       }
-  
+
   if ((sym->flags & BSF_LOCAL)
       && bfd_is_aarch64_special_symbol_name (sym->name,
 					     BFD_AARCH64_SPECIAL_SYM_TYPE_ANY))
diff --git a/bfd/reloc.c b/bfd/reloc.c
index a187afe9b56..d9386a7fdc7 100644
--- a/bfd/reloc.c
+++ b/bfd/reloc.c
@@ -6776,6 +6776,7 @@ ENUMDOC
   This is a 64 bit reloc that stores the 32 bit offset value in two
   words (with an imm instruction).  The relocation is relative offset
   from start of TEXT.
+
 ENUM
   BFD_RELOC_KVX_RELOC_START
 ENUMDOC
@@ -6961,6 +6962,7 @@ ENUMDOC
   a few more enumerators after this one; those are mainly used by the
   KVX assembler for the internal fixup or to select one of the above
   enumerators.
+
 ENUM
   BFD_RELOC_AARCH64_RELOC_START
 ENUMDOC
@@ -6968,6 +6970,11 @@ ENUMDOC
   relocation enumerators.  N.B. the order of the enumerators is
   important as several tables in the AArch64 bfd backend are indexed
   by these enumerators; make sure they are all synced.
+  - bfd/elfnn-aarch64.c:elfNN_aarch64_howto_table
+    If the reloc is local and does not require bfd, fill in the gap
+    with EMPTY_HOWTO (0).
+  - include/elf/aarch64.h:RELOC_NUMBER
+    If the reloc does not depend on elf, you can skip this.
 ENUM
   BFD_RELOC_AARCH64_NULL
 ENUMDOC
@@ -7085,6 +7092,12 @@ ENUM
 ENUMDOC
   AArch64 MOVK instruction with most significant bits 47 to 63 of a
   signed value.
+ENUM
+  BFD_RELOC_AARCH64_AUTI_LO16_PCREL
+ENUMDOC
+  AArch64 AUTI<k>SPPC instructions, holding a 16 bit pc-relative word
+  offset.  The lowest two bits must be zero and are not stored in the
+  instruction, giving a 18 bit unsigned byte offset.
 ENUM
   BFD_RELOC_AARCH64_LD_LO19_PCREL
 ENUMDOC
@@ -7309,7 +7322,7 @@ ENUM
   BFD_RELOC_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC
 ENUMDOC
   Similar to BFD_RELOC_AARCH64_TLSLD_LDST32_DTPREL_LO12, but no
-  overflow check. 
+  overflow check.
 ENUM
   BFD_RELOC_AARCH64_TLSLD_LDST64_DTPREL_LO12
 ENUMDOC
@@ -7525,6 +7538,7 @@ ENUM
 ENUMDOC
   AArch64 pseudo relocation code to be used internally by the AArch64
   assembler and not (currently) written to any object files.
+
 ENUM
   BFD_RELOC_TILEPRO_COPY
 ENUMX
@@ -7685,6 +7699,7 @@ ENUMX
   BFD_RELOC_TILEPRO_IMM16_X1_TLS_LE_HA
 ENUMDOC
   Tilera TILEPro Relocations.
+
 ENUM
   BFD_RELOC_TILEGX_HW0
 ENUMX
diff --git a/gas/config/tc-aarch64.c b/gas/config/tc-aarch64.c
index f69b0b0c0e2..9115e20a186 100644
--- a/gas/config/tc-aarch64.c
+++ b/gas/config/tc-aarch64.c
@@ -5123,6 +5123,12 @@ encode_addsub_imm_shift_amount (uint32_t cnt)
   return cnt << 22;
 }
 
+/* encode the imm16 field of RETA<k>SPPC instruction */
+static inline uint32_t
+encode_pauthauti_imm16(uint32_t imm)
+{
+  return imm << 5;
+}
 
 /* encode the imm field of Adr instruction */
 static inline uint32_t
@@ -7413,6 +7419,7 @@ parse_operands (char *str, const aarch64_opcode *opcode)
 	  break;
 
 	case AARCH64_OPND_ADDR_PCREL14:
+	case AARCH64_OPND_ADDR_UPCREL16:
 	case AARCH64_OPND_ADDR_PCREL19:
 	case AARCH64_OPND_ADDR_PCREL21:
 	case AARCH64_OPND_ADDR_PCREL26:
@@ -7464,6 +7471,11 @@ parse_operands (char *str, const aarch64_opcode *opcode)
 		      (opcode->op == OP_BL) ? BFD_RELOC_AARCH64_CALL26
 			 : BFD_RELOC_AARCH64_JUMP26;
 		    break;
+		  case ic_pauth_lr:
+		    /* e.g. AUTI<k>SPPC or RETA<k>SPPC  */
+		    gas_assert (operands[i] == AARCH64_OPND_ADDR_UPCREL16);
+		    inst.reloc.type = BFD_RELOC_AARCH64_AUTI_LO16_PCREL;
+		    break;
 		  case loadlit:
 		    gas_assert (operands[i] == AARCH64_OPND_ADDR_PCREL19);
 		    inst.reloc.type = BFD_RELOC_AARCH64_LD_LO19_PCREL;
@@ -9639,6 +9651,41 @@ md_apply_fix (fixS * fixP, valueT * valP, segT seg)
 	}
       break;
 
+    case BFD_RELOC_AARCH64_AUTI_LO16_PCREL:
+      if (fixP->fx_done)
+	{
+	  // FIXME: I don't think this inversion should be done here. There
+	  // should be a better generic mechanism to handle unsigned relative
+	  // offsets. However, I am not sure yet where to add it, and the
+	  // complexity of the change.
+	  unsigned long value_ = (unsigned long) -value;
+	  if (value > 0)
+	    as_bad_where (fixP->fx_file, fixP->fx_line,
+			  _("negative pc-relative address offset not allowed"\
+			    " in this context: %ld"), -value);
+	  else if (unsigned_overflow(value_, 18))
+	    as_bad_where (fixP->fx_file, fixP->fx_line,
+			  _("pc-relative address offset out of range: %#lx"), value_);
+	  if (value_ & 3)
+	    as_bad_where (fixP->fx_file, fixP->fx_line,
+			  _("pc-relative address offset not word aligned"));
+	  insn = get_aarch64_insn (buf);
+	  insn |= encode_pauthauti_imm16 (value_ >> 2);
+	  put_aarch64_insn (buf, insn);
+	}
+      else
+	{
+	  // The symbold is probably external or undeclared. This is unsupported
+	  // for this relocation type which is only used for computing the
+	  // PC-relative offset, but does no relocation.
+	  gas_assert (fixP->fx_addsy && !S_IS_DEFINED (fixP->fx_addsy));
+	  as_bad_where (fixP->fx_file, fixP->fx_line,
+			_("undefined symbol %s used as an immediate value"),
+			S_GET_NAME (fixP->fx_addsy));
+	  goto apply_fix_return;
+	}
+      break;
+
     case BFD_RELOC_AARCH64_ADR_LO21_PCREL:
       if (fixP->fx_done || !seg->use_rela_p)
 	{
diff --git a/gas/testsuite/gas/aarch64/pauth_lr-bad.l b/gas/testsuite/gas/aarch64/pauth_lr-bad.l
index d0a629f216f..184e3f8398f 100644
--- a/gas/testsuite/gas/aarch64/pauth_lr-bad.l
+++ b/gas/testsuite/gas/aarch64/pauth_lr-bad.l
@@ -8,4 +8,12 @@
 .*: Info:    did you mean this\?
 .*: Info:    	retaasppcr x0
 .*: Error: unexpected register type at operand 1 -- `retaasppcr sp'
-.*: Error: unexpected register type at operand 1 -- `retaasppcr xzr'
\ No newline at end of file
+.*: Error: unexpected register type at operand 1 -- `retaasppcr xzr'
+.*: Error: immediate value must be a multiple of 4 at operand 1 -- `retaasppc 0xff'
+.*: Error: immediate value must be a multiple of 4 at operand 1 -- `retaasppc 0x3fffd'
+.*: Error: immediate value out of range 0 to 65535 at operand 1 -- `retaasppc 0x4fffc'
+.*: Error: immediate value out of range 0 to 65535 at operand 1 -- `retaasppc -0x4'
+.*: Error: negative pc-relative address offset not allowed in this context: -12
+.*: Error: undefined symbol external_symbol used as an immediate value
+.*: Error: pc-relative address offset out of range: 0x40000
+.*: Error: negative pc-relative address offset not allowed in this context: -12
diff --git a/gas/testsuite/gas/aarch64/pauth_lr-bad.s b/gas/testsuite/gas/aarch64/pauth_lr-bad.s
index 98840d4c257..32f5c4b65cf 100644
--- a/gas/testsuite/gas/aarch64/pauth_lr-bad.s
+++ b/gas/testsuite/gas/aarch64/pauth_lr-bad.s
@@ -10,3 +10,18 @@ lr_signing:
 	retaasppcr w0 // 32-bit registers not allowed
 	retaasppcr sp // SP not allowed
 	retaasppcr xzr // zero register not allowed
+
+	// Invalid values
+	retaasppc 0xff // not multiple of 4
+	retaasppc 0x3fffd // max value overflowed but might not be detected due to bit shift
+	retaasppc 0x4fffc // aligned overflow: not possible to represent the value on 16 bits
+
+	retaasppc -0x4  // unsupported negative offset
+	retaasppc .+0xc // it should be impossible to jump before .text [NOK. FIXME: . notation seems to be taken into account as expected]
+	retaasppc external_symbol
+
+	.balign 0x40000 // NOP filling
+
+	retaasppc lr_signing // the distance between the label and the reta<k>sppc instruction
+			     // is above the limit that can be encoded (=0x3fffc).
+	retaasppc .+0xc // current PC + offset is above the limit that can be encoded. [NOK. FIXME: . notation seems to be taken into account as expected]
diff --git a/gas/testsuite/gas/aarch64/pauth_lr.d b/gas/testsuite/gas/aarch64/pauth_lr.d
index ddd65b77fd1..ab5700897dc 100644
--- a/gas/testsuite/gas/aarch64/pauth_lr.d
+++ b/gas/testsuite/gas/aarch64/pauth_lr.d
@@ -21,4 +21,17 @@ Disassembly of section \.text:
 .*:	dac1941e 	autibsppcr	x0
 .*:	d65f0be0 	retaasppcr	x0
 .*:	d65f0bfe 	retaasppcr	x30
-.*:	d65f0fe0 	retabsppcr	x0
\ No newline at end of file
+.*:	d65f0fe0 	retabsppcr	x0
+[^\s]+\s<lr_signing>:
+      3c:	f380001f 	autiasppc	3c <lr_signing>
+      40:	f3a0003f 	autibsppc	3c <lr_signing>
+      44:	5500005f 	retaasppc	3c <lr_signing>
+      48:	5520007f 	retabsppc	3c <lr_signing>
+      4c:	5500001f 	retaasppc	4c <lr_signing\+0x10>
+      50:	5500001f 	retaasppc	50 <lr_signing\+0x14>
+      54:	5500003f 	retaasppc	50 <lr_signing\+0x14>
+      58:	5500011f 	retaasppc	38 <lr_signing-0x4>
+      5c:	550002df 	retaasppc	4 <lr_signing-0x38>
+#...
+   40000:	55091a7f 	retaasppc	2dcb4 <lr_signing\+0x2dc78>
+   40004:	551fffff 	retaasppc	8 <lr_signing-0x34>
diff --git a/gas/testsuite/gas/aarch64/pauth_lr.s b/gas/testsuite/gas/aarch64/pauth_lr.s
index cdedd6e4588..3f57559d3df 100644
--- a/gas/testsuite/gas/aarch64/pauth_lr.s
+++ b/gas/testsuite/gas/aarch64/pauth_lr.s
@@ -28,3 +28,20 @@
 	retaasppcr x0
 	retaasppcr x30
 	retabsppcr x0
+
+lr_signing:
+	autiasppc lr_signing
+	autibsppc lr_signing
+	retaasppc lr_signing
+	retabsppc lr_signing
+
+	retaasppc 0x0 // min: reference to itself
+	retaasppc .+0x0 // min: current PC + 0
+	retaasppc 0x4 // first valid value after min: point to previous instruction
+	retaasppc 0x20
+        retaasppc .-0x58 // point to pacm
+
+	.balign 0x40000 // NOP filling
+
+	retaasppc 0x1234c // a random value in the valid range
+	retaasppc 0x3fffc // max
diff --git a/include/opcode/aarch64.h b/include/opcode/aarch64.h
index 81c9b1f1220..4661dc83e17 100644
--- a/include/opcode/aarch64.h
+++ b/include/opcode/aarch64.h
@@ -639,6 +639,7 @@ enum aarch64_opnd
 
   AARCH64_OPND_ADDR_ADRP,	/* Memory address for ADRP */
   AARCH64_OPND_ADDR_PCREL14,	/* 14-bit PC-relative address for e.g. TBZ.  */
+  AARCH64_OPND_ADDR_UPCREL16,	/* unsigned 16-bit PC-relative offset (imm16:'00') for e.g. RETA<k>... */
   AARCH64_OPND_ADDR_PCREL19,	/* 19-bit PC-relative address for e.g. LDR.  */
   AARCH64_OPND_ADDR_PCREL21,	/* 21-bit PC-relative address for e.g. ADR.  */
   AARCH64_OPND_ADDR_PCREL26,	/* 26-bit PC-relative address for e.g. BL.  */
diff --git a/opcodes/aarch64-opc.c b/opcodes/aarch64-opc.c
index ad5dfdc27b6..9848e15392a 100644
--- a/opcodes/aarch64-opc.c
+++ b/opcodes/aarch64-opc.c
@@ -2383,6 +2383,7 @@ operand_general_constraint_met_p (const aarch64_opnd_info *opnds, int idx,
 	  break;
 
 	case AARCH64_OPND_ADDR_PCREL14:
+	case AARCH64_OPND_ADDR_UPCREL16:
 	case AARCH64_OPND_ADDR_PCREL19:
 	case AARCH64_OPND_ADDR_PCREL21:
 	case AARCH64_OPND_ADDR_PCREL26:
@@ -4730,19 +4731,26 @@ aarch64_print_operand (char *buf, size_t size, bfd_vma pc,
       break;
 
     case AARCH64_OPND_ADDR_PCREL14:
+    case AARCH64_OPND_ADDR_UPCREL16:
     case AARCH64_OPND_ADDR_PCREL19:
     case AARCH64_OPND_ADDR_PCREL21:
     case AARCH64_OPND_ADDR_PCREL26:
-      addr = pc + AARCH64_PCREL_OFFSET + opnd->imm.value;
-      if (pcrel_p)
-	*pcrel_p = 1;
-      if (address)
-	*address = addr;
-      /* This is not necessary during the disassembling, as print_address_func
-	 in the disassemble_info will take care of the printing.  But some
-	 other callers may be still interested in getting the string in *STR,
-	 so here we do snprintf regardless.  */
-      snprintf (buf, size, "%s", style_addr (styler, "#0x%" PRIx64, addr));
+      {
+	const aarch64_operand *operand = get_operand_from_code (opnd->type);
+	if (operand_need_unsigned_offset (operand))
+	  addr = pc + AARCH64_PCREL_OFFSET - opnd->imm.value;
+	else
+	  addr = pc + AARCH64_PCREL_OFFSET + opnd->imm.value;
+	if (pcrel_p)
+	  *pcrel_p = 1;
+	if (address)
+	  *address = addr;
+	/* This is not necessary during the disassembling, as print_address_func
+	   in the disassemble_info will take care of the printing.  But some
+	   other callers may be still interested in getting the string in *STR,
+	   so here we do snprintf regardless.  */
+	snprintf (buf, size, "%s", style_addr (styler, "#0x%" PRIx64, addr));
+      }
       break;
 
     case AARCH64_OPND_ADDR_SIMPLE:
diff --git a/opcodes/aarch64-tbl.h b/opcodes/aarch64-tbl.h
index ef7b69e4970..58d294b0d33 100644
--- a/opcodes/aarch64-tbl.h
+++ b/opcodes/aarch64-tbl.h
@@ -1034,6 +1034,12 @@
   QLF4(V_16B, V_16B, V_16B, imm_0_15),	\
 }
 
+/* e.g. AUTI<k>SPPC <label_imm16:00>  */
+#define QL_PAUTH_IMM16		\
+{				\
+  QLF1(imm_0_15),		\
+}
+
 /* e.g. AUTI<k>SPPCR Xn  */
 #define QL_PAUTH_REG		\
 {				\
@@ -3896,6 +3902,8 @@ const struct aarch64_opcode aarch64_opcode_table[] =
   PAUTH_INSN ("eretab", 0xd69f0fff, 0xffffffff, branch_reg, OP0 (), {}, 0),
   PAUTH_LR_INSN ("retaasppcr", 0xd65f0be0, 0xffffffe0, ic_pauth_lr, OP1 (Rd), QL_PAUTH_REG, 0),
   PAUTH_LR_INSN ("retabsppcr", 0xd65f0fe0, 0xffffffe0, ic_pauth_lr, OP1 (Rd), QL_PAUTH_REG, 0),
+  PAUTH_LR_INSN ("retaasppc", 0x5500001f, 0xffe0001f, ic_pauth_lr, OP1 (ADDR_UPCREL16), QL_PAUTH_IMM16, 0),
+  PAUTH_LR_INSN ("retabsppc", 0x5520001f, 0xffe0001f, ic_pauth_lr, OP1 (ADDR_UPCREL16), QL_PAUTH_IMM16, 0),
   /* Compare & branch (immediate).  */
   CORE_INSN ("cbz", 0x34000000, 0x7f000000, compbranch, 0, OP2 (Rt, ADDR_PCREL19), QL_R_PCREL, F_SF),
   CORE_INSN ("cbnz", 0x35000000, 0x7f000000, compbranch, 0, OP2 (Rt, ADDR_PCREL19), QL_R_PCREL, F_SF),
@@ -3959,6 +3967,8 @@ const struct aarch64_opcode aarch64_opcode_table[] =
   PAUTH_INSN ("autdb", 0xdac11c00, 0xfffffc00, dp_1src, OP2 (Rd, Rn_SP), QL_I2SAMEX, 0),
   PAUTH_LR_INSN ("autiasppcr", 0xdac1901e, 0xfffffc1f, ic_pauth_lr, OP1 (Rn), QL_PAUTH_REG, 0),
   PAUTH_LR_INSN ("autibsppcr", 0xdac1941e, 0xfffffc1f, ic_pauth_lr, OP1 (Rn), QL_PAUTH_REG, 0),
+  PAUTH_LR_INSN ("autiasppc", 0xf380001f, 0xffe0001f, ic_pauth_lr, OP1 (ADDR_UPCREL16), QL_PAUTH_IMM16, 0),
+  PAUTH_LR_INSN ("autibsppc", 0xf3a0001f, 0xffe0001f, ic_pauth_lr, OP1 (ADDR_UPCREL16), QL_PAUTH_IMM16, 0),
   PAUTH_LR_INSN ("autia171615", 0xdac1bbfe, 0xffffffff, ic_system, OP0 (), {}, 0),
   PAUTH_LR_INSN ("autib171615", 0xdac1bffe, 0xffffffff, ic_system, OP0 (), {}, 0),
   PAUTH_INSN ("paciza", 0xdac123e0, 0xffffffe0, dp_1src, OP1 (Rd), QL_I1X, 0),
@@ -7003,6 +7013,8 @@ const struct aarch64_opcode aarch64_opcode_table[] =
       "21-bit PC-relative address of a 4KB page")			\
     Y(ADDRESS, imm, "ADDR_PCREL14", OPD_F_SEXT | OPD_F_SHIFT_BY_2,	\
       F(FLD_imm14), "14-bit PC-relative address")			\
+    Y(ADDRESS, imm, "ADDR_UPCREL16", OPD_F_SHIFT_BY_2 | OPD_F_UNSIGNED,	\
+      F(FLD_imm16_5), "unsigned 16-bit PC-relative offset (imm16:'00')")\
     Y(ADDRESS, imm, "ADDR_PCREL19", OPD_F_SEXT | OPD_F_SHIFT_BY_2,	\
       F(FLD_imm19), "19-bit PC-relative address")			\
     Y(ADDRESS, imm, "ADDR_PCREL21", OPD_F_SEXT, F(FLD_immhi,FLD_immlo),	\
