Stefan Monnier <
monnier@iro.umontreal.ca> writes:
[Someone wrote:]
ABI calling conventions tend to be designed to support at least C,
including varargs and often also tolerant of differences between the
number of arguments in the caller and callee.
>
I can agree that it's important to support those use-cases (varargs
obviously, mismatched arg numbers less so),
You are head of a group of people who design a new architecture (say,
it's 2010 and you design ARM A64, or it's 2014 and you design RISC-V).
Your ABI designer comes to you and tells you that his life would be
easier if it was ok that programs with mismatched arguments don't need
to work. Would you tell him that they don't need to work?
If yes, a few years down the road your prospective customers have to
decide whether to go for your newfangled architecture or one of the
established ones. They learn that a number of programs work
everywhere else, but not on your architecture. How many of them will
be placated by your reasoning that these programs are not strictly
confoming standard programs? How many will be alarmed by your
admission that you find it ok that you find it ok that such programs
don't work on your architecture? After all, hardly any program is a
strictly conforming standard program.
only sloppy ancient C calls
functions without proper declarations)
You find it ok to design a calling convention such that ancient C
programs do not work?
What benefit do you expect from such a calling convention? To allow
to use registers as arguments (and not callee-saved) that would
otherwise be preferably used as callee-saved registers?
However, I wonder why, e.g., RISC-V does not allow the use of all
caller-saved registers as arguments. In addition to the 8 argument
registers (a0-a7=x10-x17), RISC-V has 7 additional caller-saved
registers: t0-t6(=x5-x7,x28-x31); for FP register's it's even more
extreme: 8 argument registers fa0-fa7=f10-f17, and 12 additional
caller-saved registers ft0-ft12=f0-f7,f28-f31.
even if it comes at the cost of
using different calling conventions for the two cases.
That would mean that you find it ok that existing programs that use
vararg functions like printf but do not declare them before use don't
work on your newfangled architecture. Looking at
<
https://pdos.csail.mit.edu/6.828/2023/readings/riscv-calling.pdf>,
the RISC-V people find that acceptable:
|If argument i < 8 is a floating-point type, it is passed in
|floating-point register fai; [...] Additionally, floating-point
|arguments to variadic functions (except those that are explicitly
|named in the parameter list) are passed in integer registers.
So if I 'printf("%f",1.0)' without first declaring printf, the program
won't work. I just tried out compiling the following program on
RISC-V with gcc 10.3.1:
int main()
{
printf("%f\n",1.0);
}
int xxx()
{
yyy("%f\n",1.0,2);
}
Note that there is no "#include <stdio.h>" or any declaration of
printf() or yyy(). Yet 1.0 is passed to printf() in a1, while it is
passed to yyy() in fa0, and 2 is passed to yyy() in a1.
And gcc works around the varargs decision by using the varargs calling
convention for some well-known vararg functions like printf, while
other undeclared functions use the non-varargs calling convention.
Apparently the fallout of that decision by the RISC-V people hit a
"relevant" program.
[1] Apparently they stuck with the decision to deal differently with
varargs, and then decided to change the rest of the calling convention
to benefit from that decision by not leaving holes in the FP argument
registers for integers and vice versa. I don't find this clearly
expressed in
<
https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc>.
The only thing that points in that direction is:
|Values are passed in floating-point registers whenever possible,
|whether or not the integer registers have been exhausted.
But this does not talk about how the integer argument register
numbering is changed by the "Hardware Floating-point Calling
Convention".
I certainly have a use for as many arguments as the ABI provides,
>
Ah, yes, machine-generated code can always defy intuitions about what
is "typical".
While I use a generator for my interpreter engines, many other people
hand-code them. They would probably use macros for the function
declaration and the tail-call, though. Or maybe a macro that wraps
the whole payload so that one can easily switch between this technique
and one of the others.
- anton
-- 'Anyone trying for "industrial quality" ISA should avoid undefined behavior.' Mitch Alsup, <c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>