AVR Arduino assembly coding

Function call - parameter passing

EXAMPLES IN BOTTOM

The following is just a copy cat from the page above. Jens

Register Layout (from liit)

Values that occupy more than one 8-bit register start in an even register.

Fixed Registers

Fixed Registers are registers that won't be allocated by GCC's register allocator.
Registers R0 and R1 are fixed and used implicitly while printing out assembler instructions:

R0

is used as scratch register that need not to be restored after its usage. It must be saved and restored in interrupt service routine's (ISR) prologue and epilogue. In inline assembler you can use __tmp_reg__ for the scratch register.

R1

always contains zero. During an insn the content might be destroyed, e.g. by a MUL instruction that uses R0/R1 as implicit output register. If an insn destroys R1, the insn must restore R1 to zero afterwards. This register must be saved in ISR prologues and must then be set to zero because R1 might contain values other than zero. The ISR epilogue restores the value. In inline assembler you can use __zero_reg__ for the zero register.

T

the T flag in the status register (SREG) is used in the same way like the temporary scratch register R0.

User-defined global registers by means of global register asm and / or -ffixed-n won't be saved or restored in function pro- and epilogue.

Call-Used Registers

The call-used or call-clobbered general purpose registers (GPRs) are registers that might be destroyed (clobbered) by a function call.

R18–R27, R30, R31

These GPRs are call clobbered. An ordinary function may use them without restoring the contents. Interrupt service routines (ISRs) must save and restore each register they use.

R0, T-Flag

The temporary register and the T-flag in SREG are also call-clobbered, but this knowledge is not exposed explicitly to the compiler (R0 is a fixed register).

Call-Saved Registers

R2–R17, R28, R29

The remaining GPRs are call-saved, i.e. a function that uses such a registers must restore its original content.
This applies even if the register is used to pass a function argument.

R1

The zero-register is implicity call-saved (implicit because R1 is a fixed register).

Frame Layout

During compilation the compiler may come up with an arbitrary number of pseudo registers which will be allocated to hard registers during register allocation.

  1. Pseudos that don't get a hard register will be put into a stack slot and loaded / stored as needed.

  2. In order to access stack locations, avr-gcc will set up a 16-bit frame pointer in R29:R28 (Y) because the stack pointer (SP) cannot be used to access stack slots.

  3. The stack grows downwards. Smaller addresses are at the bottom of the drawing at the right.

  4. Stack pointer and frame pointer are not aligned, i.e. 1-byte aligned.

  5. After the function prologue, the frame pointer will point one byte below the stack frame, i.e. Y+1 points to the bottom of the stack frame.

  6. Any of “incoming arguments”, “saved registers” or “stack slots” in the drawing at the right may be empty.

  7. Even “return address” may be empty which happens for functions that are tail-called.

Calling Convention

  1. Neither function arguments nor function return values are promoted to wider types.

  2. An argument is passed either completely in registers or completely in memory.

  3. To find the register where a function argument is passed, initialize the register number Rn with R26 and follow this procedure:

    1. If the argument size is an odd number of bytes, round up the size to the next even number.

    2. Subtract the rounded size from the register number Rn.

    3. If the new Rn is at least R8 and the size of the object is non-zero, then the low-byte of the argument is passed in Rn. Subsequent bytes of the argument are passed in the subsequent registers, i.e. in increasing register numbers.

    4. If the new register number Rn is smaller than R8 or the size of the argument is zero, the argument will be passed in memory.

    5. If the current argument is passed in memory, stop the procedure: All subsequent arguments will also be passed in memory.

    6. If there are arguments left, goto 1. and proceed with the next argument.

  4. Return values with a size of 1 byte up to and including a size of 8 bytes will be returned in registers. Return values whose size is outside that range will be returned in memory. Sizes of structures up to 8 bytes are padded to the next power of two when they are returned in registers.

  5. If a return value cannot be returned in registers, the caller will allocate stack space and pass the address as implicit first pointer argument to the callee. The callee will put the return value into the space provided by the caller.

  6. If the return value of a function is returned in registers, the same registers are used as if the value was the first parameter of a non-varargs function. For example, an 8-bit value is returned in R24 and an 32-bit value is returned R22…R25.

  7. Arguments of varargs functions are passed on the stack. This applies even to the named arguments.

For example, suppose a function with the following prototype:

    int func (char a, long b);

then

  • a will be passed in R24.

  • b will be passed in R20, R21, R22 and R23 with the LSB in R20 and the MSB in R23.

  • The result is returned in R24 (LSB) and R25 (MSB).

Exceptions to the Calling Convention

GCC comes with libgcc, a runtime support library. This library implements functions that are too complicated to be emit inline by GCC. What functions are used when depends on the target architecture, what instructions are available, how expensive they are and on the optimization level.

Functions in libgcc are implemented in C or hand-written assembly. In the latter case, some functions use a special ABI that allows better code generation by the compiler.

For example, the function that computes unsigned 8-bit quotient and remainder, __udivmodqi4, just returns the quotient and the remainder and clobbers R22 and R23. The compiler knows that the function does not destroy R30, for example, and may hold a value in R30 across the function call. This reduces the register pressure in functions that call __udivmodqi4.

See https://gcc.gnu.org/wiki/avr-gcc#Call-Used_Registers

getting parameters on stak

example:

; we use R28,29 (aka Y double register) to point on stak

in r28, SPL  ; now R28:R29 aka points to bottom of stak
in R290, SPH

<stak> when entering function
+3 char var on stak
+2 ret ; ret address w
+1 ret ;
+0  <-- SP

Now we do push r28,r29 to save them
push r28
push r29 ; to save them

<stak> is now
+5 char var on stak
+4 ret ; ret address w
+3 ret ;
+2 r28 ; pushed
+1 r29 ;- pushed
+0  <- SP

Now we load r28:r29 with SP sso r28:r29( aka Y word register
in r28, SPL  ; now R28:R29 aka points to bottom of stak  rel +0
in R29, SPH

So now we can get a byte from stak

ldd r7,Y+5  loads r7 with char var  on addr +5 on stak



Vars on stak

See

Parameters to functions are passed in registers r8-r25

IS more parameters are needed you have to transfer on stak

To access the parameters in the stack frame (activation record), you need to copy the stack pointer SP to the Y pointer register:

in r28, SPL
in r29, SPH

If we have a function with 11 1-byte arguments, the first nine will be passed in the even- numbered registers from r24 down to r8, and the last two will be passed on the stack. If we use the Y pointer for accessing these values, we must save it first,
so the beginning of our function should look like:

push r28
push r29
in r28, SPL
in r29, SPH

You can access arguments 10 and 11 by:
ldd r7, Y+5
ldd r7, Y+6

Jens : draw stack layout


Examples

c code


extern "C" {
  char asmfct(char v1, char v2void);
}


void setup() {
char res;
  Serial.begin(115200);
  Serial.println(__FILE__);   Serial.println(__DATE__);   Serial.println(__TIME__);
  Serial.println("\nbef");
  res= asmfct(3 , 0x40);  // call asmfct which will call cfct abovce and return

  Serial.println("after");
  Serial.print(res);
}
void loop() {}

asm code

; Arduino sketch .S subroutine written in AVR assembly code

#include "avr/io.h"

.text


;
.global asmfct
 ;  char asmfct(char v1, char v2) {return v1+v2;}
 ; https://gcc.gnu.org/wiki/avr-gcc#Call-Used_Registers
 ; https://jensd.dk/doc/arduino/asm/parm.html
 ; r24  v1
 ; r23 0
 ; r22 v2
 ; r21 ---
 ;
 ; return v1+v2 jens way
 asmfct:
   add r24,r22
   ret

c code


extern "C" {
  int asmfct(int v1, char v2);
}


void setup() {
int res;
  Serial.begin(115200);
  Serial.println(__FILE__);   Serial.println(__DATE__);   Serial.println(__TIME__);
  Serial.println("\nbef");

/* from disassembly
 576:	0e 94 7b 01 	call	0x2f6	; 0x2f6 <_ZN5Print7printlnEPKc.constprop.3>
  res= asmfct(3 , 0x40);  // call asmfct which will call cfct abovce and return
  RULE SEEMS ;-)  always start on even register to by able to use movw  alike X,Y,Z (R26:27, R28:29, R:30:31  )
  even ifs only a byte to avoid misaligment
 57a:	60 e4       	ldi	r22, 0x40	; 64
 57c:	83 e0       	ldi	r24, 0x03	; 3
 57e:	90 e0       	ldi	r25, 0x00	; 0
 580:	0e 94 5f 00 	call	0xbe	; 0xbe <asmfct>
 584:	6c 01       	movw	r12, r24
 */

  res= asmfct(3 , 0x40);  // call asmfct which will call cfct abovce and return

  Serial.println("after");
  Serial.print(res,HEX);
}
void loop() {}

asm code


; Arduino sketch .S subroutine written in AVR assembly code

#include "avr/io.h"

.text


;
.global asmfct
 ;  int asmfct(int v1, char v2) {return v1+v2;}
 ; https://gcc.gnu.org/wiki/avr-gcc#Call-Used_Registers
 ; https://jensd.dk/doc/arduino/asm/parm.html
;  asmfct:  from diassembly
;   add r24,r22
;  be:	86 0f       	add	r24, r22
;  c0:	08 95       	ret

 ; r25 v1 (msb)
 ; r24 v1 (lsb)
 ; r23 0
 ; r22 v2
 ; r21 ---
 ;
 ; return v1+v2 jens way
 asmfct:
   add r24,r22
   ret

c code


extern "C" {
  int asmfct(int v1, char v2, char v3, char v4, char v5, char v6, char v7);
}


void setup() {
int res;
  Serial.begin(115200);
  Serial.println(__FILE__);   Serial.println(__DATE__);   Serial.println(__TIME__);
  Serial.println("\nbef");

/* from disassembly
  res= asmfct(1,2,3,4,5,6,7);  // call asmfct which will call cfct abovce and return
 57a:	37 e0       	ldi	r19, 0x07	; 7
 57c:	c3 2e       	mov	r12, r19
 57e:	46 e0       	ldi	r20, 0x06	; 6
 580:	e4 2e       	mov	r14, r20
 582:	05 e0       	ldi	r16, 0x05	; 5
 584:	24 e0       	ldi	r18, 0x04	; 4
 586:	43 e0       	ldi	r20, 0x03	; 3
 588:	62 e0       	ldi	r22, 0x02	; 2
 58a:	81 e0       	ldi	r24, 0x01	; 1
 58c:	90 e0       	ldi	r25, 0x00	; 0
 58e:	0e 94 5f 00 	call	0xbe	; 0xbe <asmfct>
 592:	8c 01       	movw	r16, r24
 */

  res= asmfct(1,2,3,4,5,6,7);  // call asmfct which will call cfct abovce and return

  Serial.println("after");
  Serial.print(res,HEX);
}
void loop() {}

asm code

; Arduino sketch .S subroutine written in AVR assembly code

#include "avr/io.h"

.text


;
.global asmfct
 ;  int asmfct(int v1, char v2) {return v1+v2;}
 ; https://gcc.gnu.org/wiki/avr-gcc#Call-Used_Registers
 ; https://jensd.dk/doc/arduino/asm/parm.html
;  asmfct:  from diassembly
;   add r24,r22
;  be:	86 0f       	add	r24, r22
;  c0:	08 95       	ret

 ; r25 v1 (msb)
 ; r24 v1 (lsb)
 ; r23 0
 ; r22 v2
 ; r21 ---
 ;
 ; return v1+v2 jens way
 asmfct:
   add r24,r22
   ret

c code


extern "C" {
  int asmfct(int v1, char v2, char v3, char v4, char v5, char v6, char v7,int v8, int v9, int v10, int v11, int v12);
}

void setup() {
int res;
  Serial.begin(115200);
  Serial.println(__FILE__);   Serial.println(__DATE__);   Serial.println(__TIME__);
  Serial.println("\nbef");

/* from disassembly
 res= asmfct(1,2,3,4,5,6,7,8,9,10,11,12);  // call asmfct which will call cfct abovce and return
 NB NB 10,11,12 is transfered on stack bw do not have enough registers : R8-R25 (18 regs)
 byte and ints both fills 2 bytes like they also take two registers
 57a:	1f 92       	push	r1
 57c:	8c e0       	ldi	r24, 0x0C	; 12
 57e:	8f 93       	push	r24
 580:	1f 92       	push	r1
 582:	8b e0       	ldi	r24, 0x0B	; 11
 584:	8f 93       	push	r24
 586:	1f 92       	push	r1
 588:	8a e0       	ldi	r24, 0x0A	; 10
 58a:	8f 93       	push	r24
 58c:	39 e0       	ldi	r19, 0x09	; 9
 58e:	83 2e       	mov	r8, r19
 590:	91 2c       	mov	r9, r1
 592:	48 e0       	ldi	r20, 0x08	; 8
 594:	a4 2e       	mov	r10, r20
 596:	b1 2c       	mov	r11, r1
 598:	57 e0       	ldi	r21, 0x07	; 7
 59a:	c5 2e       	mov	r12, r21
 59c:	66 e0       	ldi	r22, 0x06	; 6
 59e:	e6 2e       	mov	r14, r22
 JDN: From here you have direct acces to register
 just ldi <R16-R31>, vaule
For R8-R15 you have to use one of R16-R31 and move it:
   ldi	r20, 0x08
   mov	r10, r20

 5a0:	05 e0       	ldi	r16, 0x05	; 5
 5a2:	24 e0       	ldi	r18, 0x04	; 4
 5a4:	43 e0       	ldi	r20, 0x03	; 3
 5a6:	62 e0       	ldi	r22, 0x02	; 2
 5a8:	81 e0       	ldi	r24, 0x01	; 1
 5aa:	90 e0       	ldi	r25, 0x00	; 0
 5ac:	0e 94 5f 00 	call	0xbe	; 0xbe <asmfct>
 5b0:	8c 01       	movw	r16, r24


int asmfct(int v1, char v2, char v3, char v4, char v5, char v6, char v7,int v8, int v9, int v10, int v11, int v12);


Saved on stack
+08 00 ; r1  msb
+07    ; 12  lsb   v12
+06    ;  0  msb
+05    ; 11  lsb   v11
+04    ;  0  msb
+03    ; 10  lsb   v10
+02    ; ret msb  return address
+01    ; ret lsb
+00    ; <--P

Register used for transfers of parm there is max  (max 9  rest on stack above)
all vars in regs has lsb on even register no
So transfering int you waste every second register
 Remember: Immediate instructions only work with registers 16 through 31.
 Trying to use them with registers 0 through 15 will result in an error.


R8   9 int lsb  v9
R9   0 int msb
R10  8 int lsb  v8
R11  0 int msb
R12  7 char     v7
R13  unused
R14  6 char     v6
R15  unused
R16  5 char     v5
R17  unused
R18  4 char     v4
R19  unused
R20  3 char     v3
R21  unused
R22  2 char     v2
R23  unused
R24  1 char     v1
R25  unused
 */

  res= asmfct(1,2,3,4,5,6,7,8,9,10,11,12);  // call asmfct which will call cfct abovce and return

  Serial.println("after");
  Serial.print(res,HEX);


}
void loop() {}

asm code

; Arduino sketch .S subroutine written in AVR assembly code

#include "avr/io.h"

.text

;;;; ASSEMBLYE IS NOT IN ACC WITH C
; JUST TO SEE HOW C IS CALLING
;
.global asmfct
 ;  int asmfct(int v1, char v2) {return v1+v2;}
 ; https://gcc.gnu.org/wiki/avr-gcc#Call-Used_Registers
 ; https://jensd.dk/doc/arduino/asm/parm.html
;  asmfct:  from diassembly
;   add r24,r22
;  be:	86 0f       	add	r24, r22
;  c0:	08 95       	ret

 ; r25 v1 (msb)
 ; r24 v1 (lsb)
 ; r23 0
 ; r22 v2
 ; r21 ---
 ;
 ; return v1+v2 jens way
 asmfct:
   add r24,r22
   ret