mirror of
https://github.com/pkivolowitz/asm_book.git
synced 2026-06-21 07:36:48 +08:00
235 lines
11 KiB
Markdown
235 lines
11 KiB
Markdown
# Section 1 / Chapter 8 / Passing Parameters To Functions
|
|
|
|
Up to 8 parameters can be passed directly via registers. Each parameter can be up to the size of an address, long or double (8 bytes). If you need to pass more than 8 parameters or you need to pass parameters which are larger than 8 bytes or are `structs`, you would use a different technique described later.
|
|
|
|
For the purposes of the present discussion, we assume all parameters are `long int` and are therefore stored in `x` registers.
|
|
|
|
Up to 8 parameters are passed in the scratch registers (of which there are 8). These are `x0` through `x7`. *Scratch* means the value of the register can be changed at will without any need to backup or restore their values.
|
|
|
|
**This also means that you cannot count on the contents of the scratch registers maintaining their value if your function makes any function calls itself.**
|
|
|
|
For example:
|
|
|
|
```c
|
|
long func(long p1, long p2) // 1
|
|
{ // 2
|
|
return p1 + p2; // 3
|
|
} // 4
|
|
```
|
|
|
|
is implemented as:
|
|
|
|
```asm
|
|
func: add x0, x0, x1 // 1
|
|
ret // 2
|
|
```
|
|
|
|
The first parameter (`p1`) goes in the first scratch register (`x0`). It's an `x` because the parameter's type is `long int`. It is the `0` register, because that is the first scratch register.
|
|
|
|
The second parameter (`p2`) goes in the second scratch register (the `1` register) because it is the second argument, and so on.
|
|
|
|
`Line 1` of the assembly language provides the label `func` to which a `bl` can be made.
|
|
|
|
`Line 1` also provides the full body of the function - the third argument to `add` is added to the second and the result is put in the first. Thus it is: `x0 = x0 + x1`.
|
|
|
|
Just as scratch registers are used for passing (up to 8) parameters, the `0` register is used for function returns.
|
|
In the case of the current code, the result of the addition is already sitting in `x0` so all we do is `ret` on `Line 2`.
|
|
|
|
## Passing Pointers
|
|
|
|
A pointer is an address of something. The word *pointer* is scary. The words *address of* are not as scary. They mean **exactly** the same thing.
|
|
|
|
Here is a function which *also* adds two parameters together but this time using pointers to `long int` rather than the values themselves.
|
|
|
|
```c
|
|
void func(long * p1, long * p2) // 1
|
|
{ // 2
|
|
*p1 = *p1 + *p2; // 3
|
|
} // 4
|
|
```
|
|
|
|
`Line 1` passes the *address of* `p1` and `p2` as parameters. That is, the addresses of `p1` and `p2` are passed in registers `x0` and `x1` rather than their contents. The contents of the underlying
|
|
longs still reside in memory. That is:
|
|
|
|
* The address of `p1` arrives in `x0`.
|
|
* The value of `p1` is found in memory at the address specified by the parameter.
|
|
|
|
`Line 3` *dereferences* the addresses to fetch their underlying values.
|
|
The values are added together and the result overwrites the value pointed to by `p1`.
|
|
|
|
Here it is in assembly language:
|
|
|
|
```asm
|
|
func: ldr x2, [x0] // 1
|
|
ldr x3, [x1] // 2
|
|
add x2, x2, x3 // 3
|
|
str x2, [x0] // 4
|
|
ret // 5
|
|
```
|
|
|
|
The `add` instruction cannot operate on values in memory. With little exception, all the *action* takes place in registers, not memory. Therefore, the underlying values pointed to by the parameters must be fetched from memory.
|
|
|
|
`Line 1` provides the label to which a use of `bl` can branch with link register.
|
|
|
|
Remember that up to the first 8 parameters are passed in the 8 scratch registers. Thus, the address of `p1` and the address of `p2` are stored in `x0` and `x1` respectively. `0` and `1` because these are the first two parameters. The
|
|
`x` form of the `0` and `1` registers are used because the parameters' type are addresses.
|
|
|
|
* Addresses (pointers) to any type are 64 bits wide and therefore must use `x` registers.
|
|
* `long` and `unsigned long` integers are 64 bits wide and ...
|
|
* `double` floats are 64 bits wide
|
|
|
|
`Line 1` also dereferences the address held in `x0` going out to memory and loading (`ldr`) the value found there into `x2`, another scratch register. It's scratch so it doesn't need backing up and restoring.
|
|
|
|
`Line 2` does the same for `p2`, putting its value in `x3`.
|
|
|
|
Why didn't we reuse `x0` and `x1` as in:
|
|
|
|
```asm
|
|
ldr x0, [x0]
|
|
ldr x1, [x1]
|
|
```
|
|
|
|
Doing so would be legal but would end in tears. Doing so would blow away the address of `p1` (and `p2` too
|
|
but this doesn't matter). Destroying the address of `p1` would prevent us from copying the result of the
|
|
addition back into memory since the address to which we would want to store the result of the addition
|
|
would be gone. Can't have that!
|
|
|
|
So, as the smart *human*, we decided to use `x2` and `x3` because, well, they're scratch.
|
|
|
|
`Line 3` performs the addition.
|
|
|
|
`Line 4` stored the value in `x2` at the address in memory still sitting in `x0`.
|
|
|
|
### `const`
|
|
|
|
Suppose we had:
|
|
|
|
```c++
|
|
long func(const long p1, const long p2) // 1
|
|
{ // 2
|
|
return p1 + p2; // 3
|
|
} // 4
|
|
```
|
|
|
|
how would the assembly language change?
|
|
|
|
Answer: no change at all!
|
|
|
|
`const` is an instruction to the compiler ordering it to prohibit changing the values of `p1` and `p2`. We're smart humans and realize that our assembly language makes no attempt to change `p1` and `p2` so no changes are warranted.
|
|
|
|
### Passing by Reference
|
|
|
|
Suppose we had:
|
|
|
|
```c++
|
|
long func(long & p1, long & p2) // 1
|
|
{ // 2
|
|
return p1 + p2; // 3
|
|
} // 4
|
|
```
|
|
|
|
how would the assembly language change?
|
|
|
|
Answer: no change at all!
|
|
|
|
Passing by reference is also an instruction to the compiler to treat pointers a little differently - the differences don't show up here so there is no change needed to the assembly language we wrote to handle passing pointers.
|
|
|
|
## What If We Need More Than Eight Parameters?
|
|
|
|
First, do you **really** need to pass more than 8 parameters? **REALLY?**
|
|
|
|
If for some reason you do, you can pass the first 8 in registers are described above. Beginning with the ninth parameters, these would be passed on the stack.
|
|
|
|
**REMEMBER THAT ANY ADJUSTMENT TO THE STACK MUST BE DONE IN MULTIPLES OF 16!**
|
|
|
|
* If you need just 1 byte of stack, the stack pointer must be changed by 16.
|
|
* If you need 17 bytes of stack, the stack pointer must be changed by 32 and so on.
|
|
|
|
Here is a sample function that requires 9 parameters (for who knows what reason):
|
|
|
|
```c++
|
|
#include <stdio.h>
|
|
|
|
void SillyFunction(long p1, long p2, long p3, long p4, long p5, long p6,
|
|
long p7, long p8, long p9) {
|
|
printf("This example hurts my brain: %ld %ld\n", p8, p9);
|
|
}
|
|
|
|
int main() {
|
|
SillyFunction(1, 2, 3, 4, 5, 6, 7, 8, 9);
|
|
}
|
|
```
|
|
|
|
This prints:
|
|
|
|
```text
|
|
This example hurts my brain: 8 9
|
|
```
|
|
|
|
In assembly language, this program could be written as:
|
|
|
|
```text
|
|
.text // 1
|
|
.global main // 2
|
|
// 3
|
|
SillyFunction: // 4
|
|
str x30, [sp, -16]! // 5
|
|
ldr x0, =fmt // 6
|
|
mov x1, x7 // 7
|
|
ldr x2, [sp, 16] // 8
|
|
bl printf // 9
|
|
ldr x30, [sp], 32 // 10
|
|
ret // 11
|
|
// 12
|
|
main: // 13
|
|
str x30, [sp, -16]! // 14
|
|
mov x0, 9 // 15
|
|
str x0, [sp, -16]! // 16
|
|
mov x0, 1 // 17
|
|
mov x1, 2 // 18
|
|
mov x2, 3 // 19
|
|
mov x3, 4 // 20
|
|
mov x4, 5 // 21
|
|
mov x5, 6 // 22
|
|
mov x6, 7 // 23
|
|
mov x7, 8 // 24
|
|
bl SillyFunction // 25
|
|
ldr x30, [sp], 32 // 26
|
|
ret // 27
|
|
// 28
|
|
.data // 29
|
|
fmt: .asciz "This example hurts my brain: %ld %ld\n" // 30
|
|
// 31
|
|
.end
|
|
```
|
|
|
|
Notice how `main()` puts the first 8 parameters into the scratch registers `x0` through `x7` using `Lines 17` to `24`. But first, it put the ninth parameter on the stack. It did the stack parameter first so that the stack pointer could be manipulated in a scratch register.
|
|
|
|
After executing `Line 14`, the stack will have:
|
|
|
|
```text
|
|
sp + 0 return address for main
|
|
sp + 8 zero
|
|
```
|
|
|
|
After executing `Line 16`, the stack will have:
|
|
|
|
```text
|
|
sp + 0 9
|
|
sp + 8 garbage
|
|
sp + 16 return address for main
|
|
sp + 24 zero
|
|
```
|
|
|
|
After executing `Line 5`, the stack will have:
|
|
|
|
```text
|
|
sp + 0 return address for SillyFunction
|
|
sp + 8 garbage
|
|
sp + 16 9
|
|
sp + 24 garbage
|
|
sp + 32 return address for main
|
|
sp + 40 zero
|
|
```
|
|
|
|
This means that `Line 8` fetches `p9` from memory and puts its value into x2 (where it becomes the third argument to `printf()`).
|