First, well done on a great product, to all those who
have worked on the compiler.
I have used Keil and HiTech compilers for an 8052, and
both use the registers to pass parameters to functions.
My project which compiles using the Keil compiler is 96
bytes short of data when compiled using SDCC because
most of the parameters are passed in data space.
Have you considered using the registers to pass
parameters as part of the optimisation of data space?
or allowing this as an option?
Logged In: YES
user_id=203539
What about --parms-in-bank1?
Logged In: YES
user_id=1071031
I didn't know about that option.
Helps with data optimisation, but I see the libraries need to
be re-compiled.
Would it not make sense to use the active register bank to
pass some of the parameters because often the parameters
are being used in the registers. They are copied into bank 1
to be passed as parameters, and then the called function
moves them back into registers to use.
Logged In: YES
user_id=203539
At the moment the register allocator is not clever enough to
efficiently deal with parameters in registers.
In general the benefit from register parameters is often
overestimated. It certainly makes sense on CPUs with plenty
of large registers. On a 8051 tiny functions will look much
better. But in larger or complex functions you'll often find that
the registers must be saved immediately after the entrance
code. In many cases it's better to let the register allocator
decide which variables should be kept in registers. It would be
a big performance drawback, if e.g. an automatic variable
used as a loop counter is located on the stack, because the
parameters eat up all the register space.
sample code using different parameter passing methods
Logged In: YES
user_id=589052
Originator: NO
Hi,
I also see SDCC at a disadvantage versus other compilers
because of SDCC's parameter passing via DPTR.
Access to DPL needs one byte more than access to
register R0..R7 or A.
This adds up... In the functions within the
appended file ("parameters.c") the caller prepares,
the callee accesses, the callee sets up return value,
and caller stores the returned value...
For small function bodies the disadvantage of
using DPTR can be substantial (more than
40% longer code in the constructed sample code.)
This parameter passing (and returning) scheme
is probably noticeably better adapted to the 8051?
a) 1 byte: A
b) 2 byte: R6(LSB), R7(MSB)
c) 2 byte: DPTR (xdata/code pointer)
d) 3 byte: B,DPTR (generic pointer)
e) 4 byte: R4(LSB)..R7(MSB)
The advantage of a) is evident.
(13 versus 20 bytes in the constructed example)
Point b) probably does not (yet) fall into the
category which Bernhard mentioned (parameters
eating up register space).
(24 versus 35 bytes in the constructed example)
Point c) and d) is currently in use and well
adapted to pointers.
Point e) is (for more complex functions) in the
category which Bernhard mentioned. Yet it would
be (3 byte) shorter to put R4..R7 out of the way
than it is to move DPTR,B,A out of the way.
(And one doesn't have to (whereas DPTR,B,A
would usually have to be saved if the parameter
is to be used later)).
(37 versus 54 bytes in the constructed example)
File Added: parameters.c
Logged In: YES
user_id=564030
Originator: NO
How about letting the user decide? The c standard allows the use of the register storage class specififer for function arguments.
While the standard allows to use the storage class specifier it doesn't allow passing the argument in a different way depending on it's presence.
Philipp
The topic is still relevant
When looking at some of my larger firmware running on mcs51, yes, this is a real bummer. mcs51 is not good at accessing variables on the stack. And that tends to accumulate as code bloat. In some cases I even resort to passing gptr as void* or uint32_t just to avoid ferrying the args through the stack. The stack calling also prevents tail call optimizations, or just simple functions that only forward arguments to another function (common for wrappers/ adapter functions).
Although DPTR is convenient for passing 16-bit values, especially constants, its use in arguments prohibits using 'jmp @a+dptr' for pointer-to-function calls/jumps.
I'd propose something like this (inspired by other RISC type ABIs):
IMO, the best way to approach this would be:
1) Do the big overhaul of the mcs51 port that includes switching to the new register allocator.
2) Make codegen flexible wrt. calling conventions (e.g. introduce the aopArg / aopRet / isFuncCalleeStackCleanup mechanism we have for some other ports to mcs51).
3) Run experiments to get a lot of data that will help find a good calling convention (i.e. something like https://arxiv.org/abs/2112.01397, but for mcs51 and the then-new mcs51-derived ports).
4) Decide on the new calling convention.
Breaking the ABI inconveniences users, so we shouldn't do it often, so we better do it right the first time.
Last edit: Philipp Klaus Krause 2023-10-21
For that, we now have [feature-requests:#934].
Related
Feature Requests: #934
I do have a situation where newer ABI code would need to call the older ABI code (for some compatibility reason). It'd be good if the backend was a bit more flexible regarding handling of different calling conventions and could eventually support both (new + old). It kinda does already ... with e.g. callee_saves and also special libcalls.
The fact that register based calling conventions are generally are a good thing (given enough registers) is actually nothing new, I think.
This is pretty much what I have also observed on mcs51, just looking through the generated code of larger firmware projects. Since the output of computations is usually in 'a', using that as a return value / first 8-bit argument seems natural. However, 'a' is also often used for calculating pointers (on the stack frame etc.) so having it free might be actually better. Would be worth an experiment to compare both approaches, but sounds like a lot of work.
For the ports (stm8, z80 and related) where the ABI was changed to a more efficient calling convention, this is possible: assuming the default is the new ABI, all declarations of the old-ABI function will need to have __sdcccall(0) added (also all function pointers to old-ABI functions), then the old ABI will be used when calling them.
Sounds workable!