https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118330

            Bug ID: 118330
           Summary: Improving calling convention for 32-bit ARM target
           Product: gcc
           Version: 15.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: david at westcontrol dot com
  Target Milestone: ---

My main focus here is for small-systems embedded 32-bit ARM targets (Cortex-M
devices), but the points here could apply to many targets.

The calling conventions used for C are often based on common usage and compiler
limitations from decades ago - from the days when C functions were regularly
used without declarations, and return types were usually "int", or occasionally
a pointer or a floating point type.

Modern programming languages - C++, D, Rust, etc., - and modern usage is very
different.  Struct return types are increasingly common.  (They are used in C
too, such as for the standard library functions "div" and "clock".)  With
C++23, we can expect to see steadily more usage of std::tuple<>,
std::optional<>, std::variant<> and std::expected<>, all of which should be
fine for targets like this.  But old-fashioned and limited calling conventions
cripple their efficiency.

The key problem is that only one register - r0 - is used for aggregate returns.
 The pair r0:r1 is used for 64-bit scalar returns (long long int, and doubles
if there are no floating point registers), but otherwise structs bigger than
32-bits are returned by the caller allocating space for the returned struct on
the stack and passing a pointer to that in r0.  This is a very significant
overhead for smaller functions passing around small structs, such as the
aforementioned C++ types or even just strong types wrapping 64-bit integers in
a class.

Ideally, this is something that ARM should deal with - making a calling
convention that suits modern usage of modern cores.  However, I don't think
this is the only gcc target that could be improved in this way.  gcc already
has a variety of ad-hoc target-specific methods of picking calling conventions
for different targets, with compiler flags and a mix of function attributes. 
Perhaps, in the spirit of <https://xkcd.com/927/>, it would be better to make a
common choice that could be used on multiple targets?

As a suggestion, "abiopt" could be the attribute name to indicate that this is
an option adding to the normal abi, rather than replacing it (so that things
like the use of floating point registers for parameters or returns is not
affected).  Then "reg-aggregate-return" could say that the same set of
registers (gprs, floating point, vector, etc.) are used for the returning
aggregate as would be used for a set of parameters of the same types as the
aggregate members.

This could then be used with :

1. Compiler flag -mabiopt=reg-aggregate-return

2. #pragma GCC abiopt reg-aggregate-return

3. Function __attribute__((abiopt(reg-aggregate-return)))

4. Type __attribute__((abiopt(reg-aggregate-return)))

The type attribute would mean that when that type is used as a return type for
a function, the function would gain the corresponding abi option.  (If other
abi options are added in the future that apply to parameter passing, then use
of the type attribute here would mean that the type is always passed in this
way.)  I am not sure of this type attribute is practical or not.

Of course this kind of calling convention change can be at conflict with code
compiled with different options - the user has to be careful here.

Reply via email to