aboutsummaryrefslogtreecommitdiff
path: root/gcc/config/arm/arm.cc
diff options
context:
space:
mode:
authorRichard Earnshaw <rearnsha@arm.com>2024-08-21 16:15:34 +0100
committerRichard Earnshaw <rearnsha@arm.com>2024-09-11 15:51:24 +0100
commit670cfd5fe6433ee8f2e86eedb197d2523dbb033b (patch)
tree0cc8be054d9c2e4517d83a29506f5dcf4ef3bb8b /gcc/config/arm/arm.cc
parent09a514fbb67caf7e33a6ceddf524ee21024c33c5 (diff)
downloadgcc-670cfd5fe6433ee8f2e86eedb197d2523dbb033b.zip
gcc-670cfd5fe6433ee8f2e86eedb197d2523dbb033b.tar.gz
gcc-670cfd5fe6433ee8f2e86eedb197d2523dbb033b.tar.bz2
arm: avoid indirect sibcalls when IP is live [PR116597]
On Arm only r0-r3 (the argument registers) and IP are available for use as an address for an indirect sibcall. But if all the argument registers are used and IP is clobbered during the epilogue, or is used to pass closure information, then there is no spare register to hold the address and we must reject the sibcall. arm_function_ok_for_sibcall did try to handle this, but it did this by examining the function declaration. That doesn't work if the function has no prototype, or if the prototype has variadic arguments: we must, instead, look at the list of actuals for the call rather than the list of formals. The old code also worked by laying out all the arguments and then trying to add one more integer argument at the end of the list, but this missed a corner case where a hole had been left in the argument register list due to argument alignment. We fix all of this by now scanning the list of actual values to be passed and then checking if a core register has been assigned to that argument. If it has, then we record which registers were assigned. Once done we then look to see if all the argument registers have been assigned and only block the sibcall if that is the case. This permits us to sibcall: int (*d)(int, ...); int g(void); int i () { return d(g(), 2LL);} because r1 remains free (the 2LL argument is passed in {r2,r3}). gcc/ PR target/116597 * config/arm/arm.cc (arm_function_ok_for_sibcall): Use the list of actuals for the call, not the list of formals. gcc/testsuite/ PR target/116597 * gcc.target/arm/pac-sibcall-2.c: New test. * gcc.target/arm/pac-sibcall-3.c: New test.
Diffstat (limited to 'gcc/config/arm/arm.cc')
-rw-r--r--gcc/config/arm/arm.cc38
1 files changed, 27 insertions, 11 deletions
diff --git a/gcc/config/arm/arm.cc b/gcc/config/arm/arm.cc
index 1748544..de34e98 100644
--- a/gcc/config/arm/arm.cc
+++ b/gcc/config/arm/arm.cc
@@ -8007,10 +8007,11 @@ arm_function_ok_for_sibcall (tree decl, tree exp)
&& DECL_WEAK (decl))
return false;
- /* We cannot tailcall an indirect call by descriptor if all the call-clobbered
- general registers are live (r0-r3 and ip). This can happen when:
- - IP contains the static chain, or
- - IP is needed for validating the PAC signature. */
+ /* Indirect tailcalls need a call-clobbered register to hold the function
+ address. But we only have r0-r3 and ip in that class. If r0-r3 all hold
+ function arguments, then we can only use IP. But IP may be needed in the
+ epilogue (for PAC validation), or for passing the static chain. We have
+ to disable the tail call if nothing is available. */
if (!decl
&& ((CALL_EXPR_BY_DESCRIPTOR (exp) && !flag_trampolines)
|| arm_current_function_pac_enabled_p()))
@@ -8022,18 +8023,33 @@ arm_function_ok_for_sibcall (tree decl, tree exp)
arm_init_cumulative_args (&cum, fntype, NULL_RTX, NULL_TREE);
cum_v = pack_cumulative_args (&cum);
- for (tree t = TYPE_ARG_TYPES (fntype); t; t = TREE_CHAIN (t))
+ tree arg;
+ call_expr_arg_iterator iter;
+ unsigned used_regs = 0;
+
+ /* Layout each actual argument in turn. If it is allocated to
+ core regs, note which regs have been allocated. */
+ FOR_EACH_CALL_EXPR_ARG (arg, iter, exp)
{
- tree type = TREE_VALUE (t);
- if (!VOID_TYPE_P (type))
+ tree type = TREE_TYPE (arg);
+ function_arg_info arg_info (type, /*named=*/true);
+ rtx reg = arm_function_arg (cum_v, arg_info);
+ if (reg && REG_P (reg)
+ && REGNO (reg) <= LAST_ARG_REGNUM)
{
- function_arg_info arg (type, /*named=*/true);
- arm_function_arg_advance (cum_v, arg);
+ /* Avoid any chance of UB here. We don't care if TYPE
+ is very large since it will use up all the argument regs. */
+ unsigned nregs = MIN (ARM_NUM_REGS2 (GET_MODE (reg), type),
+ LAST_ARG_REGNUM + 1);
+ used_regs |= ((1 << nregs) - 1) << REGNO (reg);
}
+ arm_function_arg_advance (cum_v, arg_info);
}
- function_arg_info arg (integer_type_node, /*named=*/true);
- if (!arm_function_arg (cum_v, arg))
+ /* We've used all the argument regs, and we know IP is live during the
+ epilogue for some reason, so we can't tailcall. */
+ if ((used_regs & ((1 << (LAST_ARG_REGNUM + 1)) - 1))
+ == ((1 << (LAST_ARG_REGNUM + 1)) - 1))
return false;
}