How arguments passed into Ñ function?
The question is on the knowledge of such a thing as?Procedure Call Standard. It differs for different architectures. I will consider this standard for the?Arm?architecture.
Arguments pass into function
According to paragraph 6?of Procedure Call Standard for the Arm Architecture?registers?R0-R3?are used to pass arguments to the function, if the number of arguments exceeds the number of free registers, then the arguments are passed through the stack.
Let's take a look at an example:?
Let's call this function and look at the contents of the registers?R0-R3:
As we can see according?Procedure Call Standard for the Arm Architecture?all 4 arguments stores in?R0-R3?registers?accordingly.
Let`s see now what happens if we add one more argument:
The arguments?a,?b,?c,?d?stores in?R0-R3?as in previous function but?e?stores in the stack.
Everything seems to look clear and logical, but nevertheless there are several features here that we will now consider.
Passing of uint8_t, uint16_t arguments
Since the registers?R0-R3?have a size of 32 bits, they perfectly store any arguments having a size equal to or less than 32 bits.
Moreover, arguments with a size less than 32 bits (uint8_t,?int8_t,?uint16_t,?int16_t) occupy the entire register and are not packed into it, even if two or more arguments can be placed in the one register:
As you can see, 8-bit arguments occupy each their own 32-bit register.
Passing of uint64_t arguments
- uint64_t?type must be aligned to the boundary of 8 bytes in memory
- The 64 bits arguments may occupy only:?R0 + R1?or?R2 + R3?registers
Due to the fact that the 64 bits arguments can occupy only pairs of registers?R0 + R1?or?R2 + R3, when passing a function of mixed arguments (uint64_t?and smaller types), some registers may not be used to pass arguments.
For example in the function:
The register?R1?remains unused, but all 4 registers are spent, although only two parameters are passed and they should occupy only three registers in size:
If there was a third argument, for example, uint32_t, it would already be located on the stack.
领英推è
Therefore, when creating a prototype of a function, especially if there are?uint64_t?arguments, you need to take a closer look at the issue of the location of the arguments, because the correct location will help to win a little in performance due to the location of all arguments in the processor registers, and not on the stack. So for example, if in the previous example you swap the arguments?b?and?c, then all the arguments will fit in the registers?R0-R3:
Alignment when passing arguments through the stack
When using the stack to pass arguments to a function, all arguments (except?uint64_t) in the stack are aligned along the 4-byte boundary:
64-bit arguments are aligned along the 8-byte boundary:
Passing a structure as an argument
When passing a structure as an argument to a function, a simple rule applies:?if the size of the structure is less than 16 bytes (the size of 4 registers?R0-R3), then the structure (its fields) are stored in processor registers, if the size is greater than 16 bytes, then part of the structure will be stored in stack.
Its size is 16 bytes, which means it should be placed entirely in the registers?R0-R3. Let's show this with an example:
As we can see by the example of the register?R2?in this case, the packaging works and two?uint16_t?fields of the structure are stored in this register. This is due to the peculiarities of storing the structure in memory (you can read more about this in a previous?post) and the fact that the fields of the structure?c?and?d?are not separate arguments and therefore the rule?one argument is one register?does not apply to them.
Now consider the case of a structure whose size is greater than 16 bytes:
In this case the the all struct can`t be stored in registers and some part of it (Ñ and d members) was store in stack.
How arguments end up in registers and on the stack
How do all the arguments fall into the places reserved for them (registers or stack)? It's very simple: the compiler adds a special assembler code before calling each function, which deals with the distribution of arguments:
- Copying the d argument to the stack
- Copying the?c?argument to the stack
- Copying the b argument to R2-R3 registers
- Copying the a argument to R0-R1 registers
- Function call
This is another confirmation of the recommendation not to write functions with a large number of arguments, because in this way you not only worsen readability, but also actually increase the size occupied by the function in program memory and reduce the speed of access to this function.
Administrative Assistant at Health and Human Services
1 å¹´One Character equals 8 bits. How do we as the caller pass to the callee a sensible query?
Lead Software Engineer at Siemens | Embedded | Cybersecurity
2 å¹´This is really a nice artcle, most of the times when people had to use ARM inline assembly they will bump in to this situation that these registers are already used by the function . Thanks for writing this.