Hello all the experts here,
I would like to know how a typical porcessor executes a function call
or subroutine call in a assembly code.
I know it uses stack for doing it and I do understand the mechanism
about the stack here.
This is not necessarily true. Using the stack is theoretically what
you'd like to do because it makes the function calls re-entrant.
However the crippled architecture of small micros (and possibly issues
with caching with larger ones) make it sometimes necessary to just use
part of memory of some kind for the required storage.
I would want to go one step further and ask you
how it exaclty saves the internal variable, returned variable and call
in functions and all those things. If you see I am asking something
like a compiler design, where in it maps the high-level language into
architecture specific assembly code. I would really appreciate if you
could do with an example.
Yes, it's pretty much that.
I propose something like this,
main {
int k = foo (int a, int b, int c);
foo uses lets says 5 local varaibles.
where in foo calls another function
int l = foo1 (int d, int e);
foo1 uses 2 local variables.
Okay, it's a little more complex than that because there will be
registers that have to be saved in addition to the automatic variables
in foo/foo1. At least the accumulator(s), but maybe some floating
point registers etc. The compiler will hopefully figure out the
minimum that is required, rather than just saving everything every
time (safer from compiler bugs, but potentially very inefficient).
One way is just to push the accumulator(s), index registers etc. on
the stack, and assign automatic variables within a stack frame for the
function. There might be more than one stack, BTW. Some small
processors have a hardware stack for function calls. That creates
re-entrant code to the extent you don't run out of stack space.
But in brain-dead small micros with Harvard architecture, often
non-re-entrant functions are used- some general purpose (on-chip or
off) RAM is allocated for the storage of automatic variables etc. .
This is not as elegant.
A von Neuman 8-bit micro such the HC908 might have a compiler that
would do something like (generated by Metrowerks CodeWarrior C/C++
cross compiler):
;int foo1(int d, int e) {
; int v,w;
; v = d; w = e;
; return d + e;
;}
0000 87 PSHA
0001 89 PSHX
0002 95 TSX
0003 e601 LDA 1,X
0005 eb05 ADD 5,X
0007 87 PSHA
0008 e604 LDA 4,X
000a f9 ADC ,X
000b 97 TAX
000c 86 PULA
000d a702 AIS #2
000f 81 RTS
;int foo(int a, int b, int c) {
; int q,r,s,t,u;
; q=r=s=a; t=b; u=c;
; return a + foo1(b, c);
; }
0000 87 PSHA
0001 89 PSHX
0002 a7fe AIS #-2
0004 9ee608 LDA 8,SP
0007 87 PSHA
0008 9ee608 LDA 8,SP
000b 87 PSHA
000c 9ee60c LDA 12,SP
000f 9ee703 STA 3,SP
0012 9ee606 LDA 6,SP
0015 ad00 BSR foo1
0017 a702 AIS #2
0019 9ee702 STA 2,SP
001c 9ee609 LDA 9,SP
001f 87 PSHA
0020 9ee602 LDA 2,SP
0023 9eeb03 ADD 3,SP
0026 87 PSHA
0027 9f TXA
0028 95 TSX
0029 e901 ADC 1,X
002b 97 TAX
002c 86 PULA
002d 8a PULH
002e a704 AIS #4
0030 81 RTS
;void main(void) {
; int x;
; x = foo(1,2,3);
; for(;
{
; __RESET_WATCHDOG();
; }
0000 a7fe AIS #-2
0002 ae01 LDX #1
0004 8c CLRH
0005 89 PSHX
0006 8b PSHH
0007 5c INCX
0008 89 PSHX
0009 8b PSHH
000a a603 LDA #3
000c 5f CLRX
000d ad00 BSR foo
000f a704 AIS #4
0011 9ee702 STA 2,SP
0014 9eef01 STX 1,SP
Now, here's a similar dissembly listing generated by PIC18
cross-compiler for the PIC18 core-- Quite a difference! This one uses
fixed memory locations rather than Push/pull from the stack.
2:
3: int foo1(int d, int e) {
00001E 0D013 BRA 0x46
4: int v,w;
5: v = d; w = e;
000020 0C0F8 MOVFF 0xf8, 0xfc
000022 0F0FC NOP
000024 0C0F9 MOVFF 0xf9, 0xfd
000026 0F0FD NOP
000028 0C0FA MOVFF 0xfa, 0xfe
00002A 0F0FE NOP
00002C 0C0FB MOVFF 0xfb, 0xff
00002E 0F0FF NOP
6: return d + e;
000030 0C0F8 MOVFF 0xf8, 0
000032 0F000 NOP
000034 0C0F9 MOVFF 0xf9, 0x1
000036 0F001 NOP
000038 00100 MOVLB 0
00003A 051FA MOVF 0xfa, W, BANKED
00003C 02600 ADDWF 0, F, ACCESS
00003E 051FB MOVF 0xfb, W, BANKED
000040 02201 ADDWFC 0x1, F, ACCESS
000042 0D000 BRA 0x44
7: }
000044 00012 RETURN 0
8:
9:
10: int foo(int a, int b, int c) {
00004A 0D023 BRA 0x92
11: int q,r,s,t,u;
12: q=r=s=a; t=b; u=c;
00004C 0C0E8 MOVFF 0xe8, 0xf2
00004E 0F0F2 NOP
000050 0C0E9 MOVFF 0xe9, 0xf3
000052 0F0F3 NOP
000054 0C0F2 MOVFF 0xf2, 0xf0
000056 0F0F0 NOP
000058 0C0F3 MOVFF 0xf3, 0xf1
00005A 0F0F1 NOP
00005C 0C0F0 MOVFF 0xf0, 0xee
00005E 0F0EE NOP
000060 0C0F1 MOVFF 0xf1, 0xef
000062 0F0EF NOP
000064 0C0EA MOVFF 0xea, 0xf4
000066 0F0F4 NOP
000068 0C0EB MOVFF 0xeb, 0xf5
00006A 0F0F5 NOP
00006C 0C0EC MOVFF 0xec, 0xf6
00006E 0F0F6 NOP
000070 0C0ED MOVFF 0xed, 0xf7
000072 0F0F7 NOP
13: return a + foo1(b, c);
000074 0C0EA MOVFF 0xea, 0xf8
000076 0F0F8 NOP
000078 0C0EB MOVFF 0xeb, 0xf9
00007A 0F0F9 NOP
00007C 0C0EC MOVFF 0xec, 0xfa
00007E 0F0FA NOP
000080 0C0ED MOVFF 0xed, 0xfb
000082 0F0FB NOP
000084 0DFCC RCALL 0x1e
000086 051E8 MOVF 0xe8, W, BANKED
000088 02600 ADDWF 0, F, ACCESS
00008A 051E9 MOVF 0xe9, W, BANKED
00008C 02201 ADDWFC 0x1, F, ACCESS
00008E 0D000 BRA 0x90
14: }
000090 00012 RETURN 0
15:
16:
17: void main(void) {
000094 0D012 BRA 0xba
18: int x;
19:
20: x = foo(1,2,3);
000096 00E01 MOVLW 0x1
000098 00100 MOVLB 0
00009A 06FE8 MOVWF 0xe8, BANKED
00009C 06BE9 CLRF 0xe9, BANKED
00009E 00E02 MOVLW 0x2
0000A0 06FEA MOVWF 0xea, BANKED
0000A2 06BEB CLRF 0xeb, BANKED
0000A4 00E03 MOVLW 0x3
0000A6 06FEC MOVWF 0xec, BANKED
0000A8 06BED CLRF 0xed, BANKED
0000AA 0DFCF RCALL 0x4a
0000AC 0C000 MOVFF 0, 0xe6
0000AE 0F0E6 NOP
0000B0 0C001 MOVFF 0x1, 0xe7
0000B2 0F0E7 NOP
21: for(;
;
0000B4 0D7FF BRA 0xb4
22:
23:
24: }
0000B6 0EF0C GOTO 0x18
Please shed some thought on this.
Thanks in advance
Hariharan K Srinivasan.
Best regards,
Spehro Pefhany