AVR Arduino assembly codingFunction call - parameter passingEXAMPLES 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 RegistersFixed Registers are registers that won't be allocated by GCC's register allocator.
R0is 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. R1always 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. Tthe 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 RegistersThe call-used or call-clobbered general purpose registers (GPRs) are registers that might be destroyed (clobbered) by a function call. R18–R27, R30, R31These 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-FlagThe 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 RegistersR2–R17, R28, R29The remaining GPRs are call-saved, i.e. a function that uses such a registers must restore its original content.
R1The zero-register is implicity call-saved (implicit because R1 is a fixed register). Frame LayoutDuring compilation the compiler may come up with an arbitrary number of pseudo registers which will be allocated to hard registers during register allocation.
Calling Convention
For example, suppose a function with the following prototype:
int func (char a, long b);
then
Exceptions to the Calling ConventionGCC 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 stakexample: ; 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 stakSee 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
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,
push r28
You can access arguments 10 and 11 by:
Jens : draw stack layout Examplesc 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
|