[PATCH 1 of 1 v10] aarch64: Support PAC and BTI
Bill Roberts
bill.roberts at arm.com
Wed Sep 23 16:08:39 CEST 2026
On 9/18/26 2:46 PM, Niels Möller wrote:
> Bill Roberts<bill.roberts at arm.com> writes:
>
>> Testing was done under the following CFLAGS and
>> CXXFLAGS for all combinations:
>> 1. -mbranch-protection=none
>> 2. -mbranch-protection=standard
>> 3. -mbranch-protection=pac-ret
>> 4. -mbranch-protection=pac-ret+b-key
>> 5. -mbranch-protection=bti
> Question for Torbjörn: Which of the gmp machines (if any) would be
> suitable for running tests?
>
>> --- a/configure.ac Sun Sep 06 19:15:00 2026 +0200
>> +++ b/configure.ac Wed Sep 16 15:00:02 2026 -0500
>> @@ -3421,14 +3421,14 @@
>> CPUVEC_SETUP="$CPUVEC_SETUP decided_cpuvec.$tmp_fbase = __gmpn_${tmp_fbase}_${tmp_suffix}; \\
>> "
>> # Ditto for any preinv variant (preinv_divrem_1, preinv_mod_1).
>> - if grep "^PROLOGUE(mpn_preinv_$tmp_fn)" $tmp_file >/dev/null; then
>> + if grep "^PROLOGUE_\(NONLEAF\)?(mpn_preinv_$tmp_fn)" $tmp_file >/dev/null; then
I think I will also change this to use -e instead of modifying the
regex, which also looks wrong to me right now. Here and below.
>> echo "DECL_preinv_$tmp_fbase (__gmpn_preinv_${tmp_fbase}_$tmp_suffix);" >>fat.h
>> CPUVEC_SETUP="$CPUVEC_SETUP decided_cpuvec.preinv_$tmp_fbase = __gmpn_preinv_${tmp_fbase}_${tmp_suffix}; \\
>> "
>> fi
>>
>> # Ditto for any mod_1...cps variant
>> - if grep "^PROLOGUE(mpn_${tmp_fbase}_cps)" $tmp_file >/dev/null; then
>> + if grep "^PROLOGUE_\(_NONLEAF\)?(mpn_${tmp_fbase}_cps)" $tmp_file >/dev/null; then
>> echo "DECL_${tmp_fbase}_cps (__gmpn_${tmp_fbase}_cps_$tmp_suffix);" >>fat.h
>> CPUVEC_SETUP="$CPUVEC_SETUP decided_cpuvec.${tmp_fbase}_cps = __gmpn_${tmp_fbase}_cps_${tmp_suffix}; \\
>> "
>> @@ -3561,6 +3561,7 @@
>> gmp_ep=[`
>> sed -n 's/^[ ]*MULFUNC_PROLOGUE(\(.*\))/\1/p' $tmp_file ;
>> sed -n 's/^[ ]*PROLOGUE(\([^,]*\).*)/\1/p' $tmp_file
>> + sed -n 's/^[ ]*NONLEAF_PROLOGUE(\([^,]*\).*)/\1/p' $tmp_file
> There appears to be some confusion about NONLEAF_PROLOGUE (here) and
> PROLOGUE_NONLEAF (above). Probably makes most sense to use
> NONLEAF_PROLOGUE everywhere, for consistency with MULFUNC_PROLOGUE.
Yeah looks like I accidentally left a dead definition laying around AFAICT.
>> `]
>> for gmp_tmp in $gmp_ep; do
>> AC_DEFINE_UNQUOTED(HAVE_NATIVE_$gmp_tmp)
>> @@ -3767,7 +3768,77 @@
>> *-*-darwin*)
>> GMP_INCLUDE_MPN(arm64/darwin.m4) ;;
>> *)
>> - GMP_INCLUDE_MPN(arm64/arm64-defs.m4) ;;
>> + GMP_INCLUDE_MPN(arm64/arm64-defs.m4)
>> +
>> + AC_CACHE_CHECK([if Armv8.5-A BTI is enabled],
>> + [gmp_cv_asm_arm64_bti],
>> + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
>> + #if !(defined __ARM_FEATURE_BTI_DEFAULT)
>> + #error Armv8.5-A BTI is not enabled
>> + #endif
>> + ]], [[]])],
>> + [gmp_cv_asm_arm64_bti=yes],
>> + [gmp_cv_asm_arm64_bti=no])])
>> + # Convert yes no values to BTI value, yes no is nice for the output message on the screen
>> + # Example: checking if Armv8.5-A BTI is enabled... yes
>> + AS_IF([test "$gmp_cv_asm_arm64_bti" = yes],
>> + [ARM64_FEATURE_BTI_DEFAULT=1], [ARM64_FEATURE_BTI_DEFAULT=0])
>> +
>> + GMP_DEFINE_RAW(["define(<ARM64_FEATURE_BTI_DEFAULT>,$ARM64_FEATURE_BTI_DEFAULT)"])
>> + AC_SUBST([ARM64_FEATURE_BTI_DEFAULT])
> From the docs, it seems GMP_DEFINE_RAW adds a definition to config.m4.
> You combine with AC_SUBST, so you want ARM64_FEATURE_BTI_DEFAULT to go
> into both config.m4 and other substituted files, e.g, Makefile? And same
> for other variables.
Yes, IIUC, I need the variable in both m4 definitions, like
mpn/arm64/arm64-defs.m4 and Makefiles.
>> + AS_IF([test "$gmp_cv_asm_arm64_pac" = yes], [
>> + AC_CACHE_CHECK([if PAC is using A or B key],
>> + [gmp_cv_asm_arm64_pac_key],
>> + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
>> + #if __ARM_FEATURE_PAC_DEFAULT & 1
>> + // Armv8.3-A PAC A Key enabled else B Key enabled
>> + #endif
>> + ]], [[]])],
>> + [gmp_cv_asm_arm64_pac_key=A],
>> + [gmp_cv_asm_arm64_pac_key=B])])
>> + ])
> This test looks weird, should that C++-style comment be an #error
> instead?
Yes, good catch.
>> + AC_CACHE_CHECK([whether __ELF__ is defined],
>> + [gmp_cv_asm_arm64_have_elf],
>> + [AC_PREPROC_IFELSE(
>> + [AC_LANG_SOURCE([[
>> + #ifdef __ELF__
>> + int main(void) { return 0; }
>> + #else
>> + #error "__ELF__ not defined"
>> + #endif
>> + ]])],
> If this is just a test running the preprocessor, having the main
> function seems unnecessary. For consistency, I think it would make sense
> to use AC_COMPILE_IFELSE and same style as the other tests above.
No makes perfect sense, No idea why I did that.
>> + [gmp_cv_asm_arm64_have_elf=yes],
>> + [gmp_cv_asm_arm64_have_elf=no])])
>> +
>> + if test "$gmp_cv_asm_arm64_have_elf" = yes; then
>> + gmp_cv_asm_arm64_have_elf_value=1
>> + else
>> + gmp_cv_asm_arm64_have_elf_value=0
>> + fi
>> +
>> + GMP_DEFINE_RAW(["define(<ARM64_ELF>,$gmp_cv_asm_arm64_have_elf_value)"])
>> + ;;
>> esac
>> ;;
>> esac
>> @@ -4058,6 +4129,9 @@
>> AC_PROG_YACC
>> AM_PROG_LEX
>>
>> +AM_CONDITIONAL([ARM64_PAC_OR_BTI],
>> + [test $ARM64_FEATURE_BTI_DEFAULT -ne 0 || test $ARM64_FEATURE_BTI_DEFAULT])
>> +
> I think it would make sense to move this closer to the tests that set
> these variables.
>
>> +#define(`NONLEAF_PROLOGUE', `indir(`PROLOGUE', $1, true)')
>> +define(`PROLOGUE_NONLEAF', `PROLOGUE($1, true)')
> More naming.
Yeah we only want NONLEAF_PROLOGUE so need to drop the dead reference.
>> --- /dev/null Thu Jan 01 00:00:00 1970 +0000
>> +++ b/tests/mpn/t-arm64_bti.c Wed Sep 16 15:00:02 2026 -0500
>> +int
>> +main (int argc, char **argv)
>> +{
>> + unsigned long hwcap2 = getauxval(AT_HWCAP2);
>> + if (!(hwcap2 & HWCAP2_BTI)) {
>> + fprintf(stderr, "Hardware does not support BTI\n");
>> + return SKIP;
>> + }
>> +
>> + /*
>> + * 0x1001 is the value we will left shift by one, we expect 0x2002 as
>> + * the valid result.
>> + */
>> + mp_limb_t xp = 0x1001, wp;
>> +
>> + fn_mpn_lshift fn = dlsym(RTLD_DEFAULT, "__gmpn_lshift");
>> + if (!fn) {
>> + fprintf(stderr, "Could not find the symbol __gmpn_lshift\n");
>> + return 0;
>> + }
> I would expect this symbol lookup to fail in a static build. Maybe it
> should be SKIP instead?
I think we can just assign the symbol to the function pointer without
invoking the dynamic linker.
>> + /* should work as this will land on a BTI landing pad as expected */
>> + fn (&wp, &xp, (mp_size_t) 1, 1);
>> + /* And provide the proper result 0x1001 << 1 = 0x2002 */
>> + ASSERT_ALWAYS (wp == 0x2002);
> Since this is an XFAIL test, hitting this assert will look like an
> expected failure, right? I find XFAIL generally a bit confusing, it
> would be nicer if the test could detect if an exception was raised, and
> return success when things are as expected. E.g., installing a signal
> handler for the expected signal just before running the code that is
> expected to raise that exception, and have the exception handler just
> call exit (0).
Yeah we can invert the logic here by pushing the test to a child test that
forks the expected failure code and the parent can wait for the result and
return correctly based on the expected behavior of the test.
> Regards,
> /Niels
Thanks Niels, ill spin a v11, which I sent yesterday but I don't see it on
the archiver, did you get it?
Thanks,
Bill
IMPORTANT NOTICE: The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you.
More information about the gmp-devel
mailing list